classify_card 2.0.1
classify_card: ^2.0.1 copied to clipboard
On-device Card Gate V3 pre-check: decides whether a photo holds an identity document before you call a classify/OCR backend. Runs offline in ~20ms, fails open, Android and iOS.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'dev_mode_page.dart';
import 'user_mode_flow.dart';
void main() {
runApp(const CardGateDemoApp());
}
class CardGateDemoApp extends StatelessWidget {
const CardGateDemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Card Gate demo',
theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
home: const ModePickerPage(),
);
}
}
/// Two doors, because two very different people open this app.
///
/// Someone integrating the package needs every number on screen. Someone trying the capture
/// experience needs none of them, and showing them anyway is how a demo ends up feeling like a
/// diagnostics tool rather than the thing it is demonstrating.
class ModePickerPage extends StatelessWidget {
const ModePickerPage({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 40, 24, 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Card Gate', style: theme.textTheme.displaySmall),
const SizedBox(height: 6),
Text(
'Choose how you want to try this demo',
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
const Spacer(),
_ModeCard(
icon: Icons.center_focus_strong_outlined,
title: 'Scan a card',
subtitle: 'Frame the card, hold still, done.',
primary: true,
onTap: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const UserModeFlow(),
),
),
),
const SizedBox(height: 16),
_ModeCard(
icon: Icons.tune,
title: 'Dev mode',
subtitle: 'Every knob, every score, every reason.',
primary: false,
onTap: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const CardGateDemoPage(),
),
),
),
const Spacer(flex: 2),
],
),
),
),
);
}
}
/// One door. The primary one is filled and the other outlined, so the choice reads as a
/// recommendation rather than a fork with two equal arms.
class _ModeCard extends StatelessWidget {
const _ModeCard({
required this.icon,
required this.title,
required this.subtitle,
required this.primary,
required this.onTap,
});
final IconData icon;
final String title;
final String subtitle;
final bool primary;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final foreground = primary ? scheme.onPrimaryContainer : scheme.onSurface;
return Material(
color: primary ? scheme.primaryContainer : scheme.surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: primary
? BorderSide.none
: BorderSide(color: scheme.outlineVariant),
),
child: InkWell(
borderRadius: BorderRadius.circular(20),
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 22),
child: Row(
children: [
Icon(icon, size: 30, color: foreground),
const SizedBox(width: 18),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(color: foreground),
),
const SizedBox(height: 2),
Text(
subtitle,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(color: foreground.withOpacity(0.75)),
),
],
),
),
Icon(Icons.chevron_right, color: foreground.withOpacity(0.6)),
],
),
),
),
);
}
}