import 'dart:convert'; import 'dart:typed_data'; import 'package:finger_print_verification/fingerprint/view/result_banner.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../theme/app_theme.dart'; import '../bloc/fingerprint_bloc.dart'; import '../utils/const.dart' show countryAlpha2List, fingerIndexMap; import 'auth_result_screen.dart'; /// ───────────────────────────────────────────────────────────────────────── /// FingerprintView — "Futuristic Hospital, KSA" verification kiosk screen. /// /// Behaviour, BLoC events/state, and field validation are UNCHANGED from the /// original implementation — only presentation has been redesigned for /// Material 3 and tablet/desktop layouts (a two-column layout appears on /// shortestSide >= 600, otherwise a single scrollable column as on phones). /// ───────────────────────────────────────────────────────────────────────── class FingerprintView extends StatefulWidget { const FingerprintView({super.key}); @override State createState() => _FingerprintViewState(); } class _FingerprintViewState extends State { final _nationalIdController = TextEditingController(); final _nationalityController = TextEditingController(); final _passportController = TextEditingController(); @override void dispose() { _nationalIdController.dispose(); _nationalityController.dispose(); _passportController.dispose(); super.dispose(); } void _clearControllers() { _nationalIdController.clear(); _nationalityController.clear(); _passportController.clear(); } @override Widget build(BuildContext context) { return BlocProvider( create: (_) => FingerprintBloc()..add(const AppStarted()), child: _buildScaffold(), ); } Widget _buildScaffold() { return Scaffold( extendBodyBehindAppBar: false, appBar: AppBar( title: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 34, height: 34, alignment: Alignment.center, decoration: BoxDecoration( shape: BoxShape.circle, color: AppTheme.cyan.withValues(alpha: 0.12), border: Border.all(color: AppTheme.cyan.withValues(alpha: 0.4)), ), child: const Icon(Icons.fingerprint, color: AppTheme.cyan, size: 20), ), const SizedBox(width: 12), const Text('Biometric Verification'), ], ), actions: const [ Padding( padding: EdgeInsets.only(right: 16), child: _KsaBadge(), ), ], ), body: TechGridBackground( child: BlocConsumer( listenWhen: (prev, curr) => prev.status != curr.status, listener: (context, state) { if (state.status == FormStatus.error && state.errorMessage != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: AppTheme.inkRaised2, content: Row( children: [ const Icon(Icons.error_outline, color: AppTheme.redAlert, size: 18), const SizedBox(width: 10), Expanded(child: Text(state.errorMessage!, style: const TextStyle(color: AppTheme.textPrimary), )), ], ), ), ); } else if (state.status == FormStatus.scanned) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: AppTheme.inkRaised2, content: Row( children: const [ Icon(Icons.check_circle_outline, color: AppTheme.saudiGreen, size: 18), SizedBox(width: 10), Text('Fingerprint scanned successfully!', style: const TextStyle(color: AppTheme.textPrimary), ), ], ), ), ); } else if (state.status == FormStatus.verified && state.authResult != null) { // ── Navigation mechanism ────────────────────────────────── // Phones: not enough room for form + result side-by-side, so // push the dedicated AuthResultScreen route as before. // Tablets: the form and the result panel are already fused // into one screen (see _TabletLayout's right-hand pane), so // we deliberately do NOT navigate, ever — the inline panel // below updates itself and the user never leaves this screen. if (!isTablet(context)) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => AuthResultScreen(result: state.authResult!), ), ); } } else if (state.status == FormStatus.idle) { _clearControllers(); } }, builder: (context, state) { final isBusy = state.status == FormStatus.scanning || state.status == FormStatus.connecting || state.status == FormStatus.verifying; if (state.status == FormStatus.loggingIn) { return const _LoggingInOverlay(); } return SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _DeviceStatusBar(state: state), Expanded( child: LayoutBuilder( builder: (context, constraints) { final tablet = constraints.maxWidth >= 760; if (tablet) { return _TabletLayout( state: state, isBusy: isBusy, nationalIdController: _nationalIdController, passportController: _passportController, onClearControllers: _clearControllers, ); } return _PhoneLayout( state: state, isBusy: isBusy, nationalIdController: _nationalIdController, passportController: _passportController, onClearControllers: _clearControllers, ); }, ), ), ], ), ); }, ), ), ); } } class _KsaBadge extends StatelessWidget { const _KsaBadge(); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( color: AppTheme.saudiGreen.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(20), border: Border.all(color: AppTheme.saudiGreen.withValues(alpha: 0.4)), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.health_and_safety_outlined, size: 14, color: AppTheme.saudiGreen), SizedBox(width: 5), Text('KSA MOH', style: TextStyle(fontSize: 11, color: AppTheme.saudiGreen, fontWeight: FontWeight.w700)), ], ), ); } } class _LoggingInOverlay extends StatelessWidget { const _LoggingInOverlay(); @override Widget build(BuildContext context) { return TechGridBackground( child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 56, height: 56, child: CircularProgressIndicator( strokeWidth: 3, valueColor: const AlwaysStoppedAnimation(AppTheme.cyan), ), ), const SizedBox(height: 20), const Text( 'Logging in…', style: TextStyle(color: AppTheme.textPrimary, fontWeight: FontWeight.w600, fontSize: 16), ), ], ), ), ); } } // ── Phone layout: single scrollable column ────────────────────────────────── class _PhoneLayout extends StatelessWidget { final FingerprintFormState state; final bool isBusy; final TextEditingController nationalIdController; final TextEditingController passportController; final VoidCallback onClearControllers; const _PhoneLayout({ required this.state, required this.isBusy, required this.nationalIdController, required this.passportController, required this.onClearControllers, }); @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _FormCard( state: state, isBusy: isBusy, nationalIdController: nationalIdController, passportController: passportController, onClearControllers: onClearControllers, ), const SizedBox(height: 16), if ((state.status == FormStatus.scanned || state.status == FormStatus.verifying || state.status == FormStatus.verified) && state.base64Data != null) _ScanResultCard(state: state), ], ), ); } } // ── Tablet layout: form on the left, live scan/status panel on the right ── class _TabletLayout extends StatelessWidget { final FingerprintFormState state; final bool isBusy; final TextEditingController nationalIdController; final TextEditingController passportController; final VoidCallback onClearControllers; const _TabletLayout({ required this.state, required this.isBusy, required this.nationalIdController, required this.passportController, required this.onClearControllers, }); @override Widget build(BuildContext context) { final isVerified = state.status == FormStatus.verified && state.authResult != null; final hasScan = !isVerified && (state.status == FormStatus.scanned || state.status == FormStatus.verifying || state.status == FormStatus.verified) && state.base64Data != null; return Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 1180), child: Padding( padding: const EdgeInsets.all(24), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 5, child: SingleChildScrollView( child: _FormCard( state: state, isBusy: isBusy, nationalIdController: nationalIdController, passportController: passportController, onClearControllers: onClearControllers, ), ), ), const SizedBox(width: 24), Expanded( flex: 4, child: SingleChildScrollView( child: isVerified ? _InlineResultPane( state: state, onNewVerification: () { onClearControllers(); context.read().add(const ResetBlocEvent()); }, ) : hasScan ? _ScanResultCard(state: state) : const _ScanPlaceholderPanel(), ), ), ], ), ), ), ); } } /// The "fused screen" mechanism for tablets: instead of navigating to /// AuthResultScreen, the verification result is rendered right here, in the /// same pane that previously showed the live scan preview. A "New /// Verification" action resets the BLoC in place so the user can run /// another check without ever leaving this screen. class _InlineResultPane extends StatelessWidget { final FingerprintFormState state; final VoidCallback onNewVerification; const _InlineResultPane({required this.state, required this.onNewVerification}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ResultBody(result: state.authResult!), const SizedBox(height: 16), OutlinedButton.icon( onPressed: onNewVerification, icon: const Icon(Icons.refresh), label: const Text('New Verification'), ), ], ); } } class _ScanPlaceholderPanel extends StatelessWidget { const _ScanPlaceholderPanel(); @override Widget build(BuildContext context) { return GlowPanel( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 88, height: 88, alignment: Alignment.center, decoration: BoxDecoration( shape: BoxShape.circle, color: AppTheme.cyan.withValues(alpha: 0.07), border: Border.all(color: AppTheme.cyan.withValues(alpha: 0.25)), ), child: const Icon(Icons.fingerprint, color: AppTheme.cyan, size: 44), ), const SizedBox(height: 20), const Text( 'Awaiting scan', style: TextStyle(color: AppTheme.textPrimary, fontWeight: FontWeight.w700, fontSize: 16), ), const SizedBox(height: 6), const Text( 'Connect the device and place a finger on the scanner to begin capture.', textAlign: TextAlign.center, style: TextStyle(color: AppTheme.textMuted, fontSize: 13), ), ], ), ); } } // ── Form card: document type, finger, ID/passport fields, action buttons ── class _FormCard extends StatelessWidget { final FingerprintFormState state; final bool isBusy; final TextEditingController nationalIdController; final TextEditingController passportController; final VoidCallback onClearControllers; const _FormCard({ required this.state, required this.isBusy, required this.nationalIdController, required this.passportController, required this.onClearControllers, }); @override Widget build(BuildContext context) { final isVerified = state.status == FormStatus.verified; final isVerifying = state.status == FormStatus.verifying; return GlowPanel( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const _SectionLabel(icon: Icons.badge_outlined, text: 'IDENTITY DOCUMENT'), const SizedBox(height: 14), // ── Document Type ────────────────────────────────────────────── DropdownButtonFormField( initialValue: state.documentType, dropdownColor: AppTheme.inkRaised2, style: const TextStyle(color: AppTheme.textPrimary), decoration: const InputDecoration( labelText: 'Document Type', prefixIcon: Icon(Icons.description_outlined, color: AppTheme.textMuted), ), items: const [ DropdownMenuItem(value: DocumentType.nationalId, child: Text('National ID')), DropdownMenuItem(value: DocumentType.passport, child: Text('Passport')), ], onChanged: isBusy ? null : (val) { if (val != null) { context.read().add(DocumentTypeChanged(val)); } }, ), const SizedBox(height: 16), // ── Finger Selection ──────────────────────────────────────────── DropdownButtonFormField( value: state.selectedFingerIndex, dropdownColor: AppTheme.inkRaised2, style: const TextStyle(color: AppTheme.textPrimary), decoration: const InputDecoration( labelText: 'Finger', prefixIcon: Icon(Icons.back_hand_outlined, color: AppTheme.textMuted), ), items: fingerIndexMap.entries .map((e) => DropdownMenuItem(value: e.key, child: Text(e.value))) .toList(), onChanged: isBusy ? null : (val) { if (val != null) { context.read().add(FingerTypeChanged(val)); } }, ), const SizedBox(height: 16), // ── Conditional document fields ───────────────────────────────── if (state.documentType == DocumentType.nationalId) ...[ TextField( controller: nationalIdController, keyboardType: TextInputType.number, style: const TextStyle(color: AppTheme.textPrimary,), maxLength: 10, decoration: InputDecoration( labelText: 'National ID', hintText: '10-digit number starting with 1 or 2', prefixIcon: const Icon(Icons.pin_outlined, color: AppTheme.textMuted), errorText: state.nationalIdError, ), onChanged: (val) => context.read().add(NationalIdChanged(val)), ), ] else ...[ _SearchableCountryDropdown( selectedCode: state.nationality.isEmpty ? null : state.nationality, errorText: state.nationalityError, onChanged: (code) => context.read().add(NationalityChanged(code)), ), const SizedBox(height: 12), TextField( controller: passportController, textCapitalization: TextCapitalization.characters, decoration: InputDecoration( labelText: 'Passport Number', hintText: '6–9 alphanumeric characters', prefixIcon: const Icon(Icons.numbers_outlined, color: AppTheme.textMuted), errorText: state.passportNumberError, ), onChanged: (val) => context.read().add(PassportNumberChanged(val)), ), ], const SizedBox(height: 24), const _SectionLabel(icon: Icons.sensors, text: 'SCANNER ACTIONS'), const SizedBox(height: 14), // ── Action buttons ────────────────────────────────────────────── Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: isBusy ? null : () => context.read().add(const ConnectDevice()), icon: state.status == FormStatus.connecting && !state.isScanConnecting ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: AppTheme.cyan), ) : const Icon(Icons.usb, color: AppTheme.cyan), label: Text( state.status == FormStatus.connecting && !state.isScanConnecting ? 'Connecting…' : 'Connect', ), ), ), const SizedBox(width: 12), Expanded( child: FilledButton.icon( onPressed: isBusy ? null : () => context.read().add(const ScanFingerprintEvent()), icon: state.isScanConnecting || state.status == FormStatus.scanning ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF002022)), ) : const Icon(Icons.fingerprint), label: Text( state.isScanConnecting ? 'Connecting…' : state.status == FormStatus.scanning ? 'Scanning…' : 'Scan', ), ), ), ], ), const SizedBox(height: 12), // ── Reset ──────────────────────────────────────────────────────── if (state.status == FormStatus.scanned || state.status == FormStatus.verified || state.status == FormStatus.error) TextButton.icon( onPressed: () { onClearControllers(); context.read().add(const ResetBlocEvent()); }, style: TextButton.styleFrom(foregroundColor: AppTheme.textMuted), icon: const Icon(Icons.refresh, size: 18), label: const Text('Reset'), ), const SizedBox(height: 8), // ── Verify button ──────────────────────────────────────────────── if (!isVerified) FilledButton.icon( onPressed: isVerifying ? null : () => context.read().add(const VerifyFingerprintEvent()), style: FilledButton.styleFrom( backgroundColor: AppTheme.saudiGreen, foregroundColor: Colors.white, ), icon: isVerifying ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), ) : const Icon(Icons.verified_user_outlined), label: Text(isVerifying ? 'Verifying…' : 'Verify Fingerprint'), ), if (isVerified) Container( padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( color: AppTheme.saudiGreen.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(14), border: Border.all(color: AppTheme.saudiGreen.withValues(alpha: 0.4)), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.check_circle, color: AppTheme.saudiGreen, size: 20), SizedBox(width: 8), Text( 'Identity verified successfully', style: TextStyle(color: AppTheme.saudiGreen, fontWeight: FontWeight.w700), ), ], ), ), ], ), ); } } class _SectionLabel extends StatelessWidget { final IconData icon; final String text; const _SectionLabel({required this.icon, required this.text}); @override Widget build(BuildContext context) { return Row( children: [ Icon(icon, size: 16, color: AppTheme.cyan), const SizedBox(width: 8), Text( text, style: const TextStyle( color: AppTheme.cyan, fontSize: 12, fontWeight: FontWeight.w700, letterSpacing: 1.1, ), ), ], ); } } // ── Searchable country dropdown ───────────────────────────────────────────── class _SearchableCountryDropdown extends StatefulWidget { final String? selectedCode; final String? errorText; final bool enabled; final ValueChanged onChanged; const _SearchableCountryDropdown({ required this.onChanged, this.selectedCode, this.errorText, this.enabled = true, }); @override State<_SearchableCountryDropdown> createState() => _SearchableCountryDropdownState(); } class _SearchableCountryDropdownState extends State<_SearchableCountryDropdown> { String? _displayLabel(String? code) { if (code == null || code.isEmpty) return null; final match = countryAlpha2List.firstWhere((c) => c['code'] == code, orElse: () => {}); if (match.isEmpty) return code; return '${match['name']} (${match['code']})'; } void _openSearch() { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: AppTheme.inkRaised, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (_) => _CountrySearchSheet( selectedCode: widget.selectedCode, onSelected: (code) { Navigator.of(context).pop(); widget.onChanged(code); }, ), ); } @override Widget build(BuildContext context) { final label = _displayLabel(widget.selectedCode); final hasError = widget.errorText != null; return GestureDetector( onTap: widget.enabled ? _openSearch : null, child: InputDecorator( decoration: InputDecoration( labelText: 'Nationality', hintText: 'Select country', errorText: widget.errorText, prefixIcon: const Icon(Icons.flag_outlined, color: AppTheme.textMuted), suffixIcon: const Icon(Icons.arrow_drop_down, color: AppTheme.textMuted), enabled: widget.enabled, ), isEmpty: label == null, child: Text( label ?? '', style: TextStyle( fontSize: 16, color: widget.enabled ? (hasError ? AppTheme.redAlert : AppTheme.textPrimary) : AppTheme.textMuted, ), overflow: TextOverflow.ellipsis, ), ), ); } } // ── Country search bottom sheet ───────────────────────────────────────────── class _CountrySearchSheet extends StatefulWidget { final String? selectedCode; final ValueChanged onSelected; const _CountrySearchSheet({required this.onSelected, this.selectedCode}); @override State<_CountrySearchSheet> createState() => _CountrySearchSheetState(); } class _CountrySearchSheetState extends State<_CountrySearchSheet> { final _searchController = TextEditingController(); List> _filtered = List.from(countryAlpha2List); @override void dispose() { _searchController.dispose(); super.dispose(); } void _onSearch(String query) { final lower = query.toLowerCase().trim(); setState(() { _filtered = lower.isEmpty ? List.from(countryAlpha2List) : countryAlpha2List .where((c) => c['name']!.toLowerCase().contains(lower) || c['code']!.toLowerCase().contains(lower)) .toList(); }); } @override Widget build(BuildContext context) { final bottomInset = MediaQuery.of(context).viewInsets.bottom; return Padding( padding: EdgeInsets.only(bottom: bottomInset), child: SizedBox( height: MediaQuery.of(context).size.height * 0.75, child: Column( children: [ const SizedBox(height: 8), Container( width: 40, height: 4, decoration: BoxDecoration( color: AppTheme.hairline, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 16), const Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Align( alignment: Alignment.centerLeft, child: Text( 'Select Nationality', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: AppTheme.textPrimary), ), ), ), const SizedBox(height: 10), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: TextField( controller: _searchController, autofocus: true, decoration: InputDecoration( hintText: 'Search country…', prefixIcon: const Icon(Icons.search, color: AppTheme.textMuted), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.clear, color: AppTheme.textMuted), onPressed: () { _searchController.clear(); _onSearch(''); }, ) : null, ), onChanged: _onSearch, ), ), const SizedBox(height: 8), const Divider(height: 1), Expanded( child: _filtered.isEmpty ? const Center( child: Text('No countries found', style: TextStyle(color: AppTheme.textMuted))) : ListView.builder( keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, itemCount: _filtered.length, itemBuilder: (_, index) { final country = _filtered[index]; final code = country['code']!; final name = country['name']!; final isSelected = code == widget.selectedCode; return ListTile( dense: true, leading: isSelected ? const Icon(Icons.check_circle, color: AppTheme.cyan, size: 20) : const Icon(Icons.flag_outlined, color: AppTheme.textMuted, size: 20), title: Text( '$name ($code)', style: TextStyle( fontSize: 14, fontWeight: isSelected ? FontWeight.w700 : FontWeight.normal, color: isSelected ? AppTheme.cyan : AppTheme.textPrimary, ), ), selected: isSelected, onTap: () => widget.onSelected(code), ); }, ), ), ], ), ), ); } } // ── Scan result card with image + chips ───────────────────────────────────── class _ScanResultCard extends StatelessWidget { final FingerprintFormState state; const _ScanResultCard({required this.state}); static Uint8List _decodeBase64(String raw) { final clean = raw.contains(',') ? raw.split(',').last : raw; return base64Decode(clean); } @override Widget build(BuildContext context) { final isVerified = state.status == FormStatus.verified; late final Uint8List imageBytes; try { imageBytes = _decodeBase64(state.base64Data!); } catch (_) { return const SizedBox.shrink(); } final accent = isVerified ? AppTheme.saudiGreen : AppTheme.cyan; return GlowPanel( glowColor: accent, padding: EdgeInsets.zero, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( color: accent.withValues(alpha: 0.10), borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), ), child: Row( children: [ Icon(isVerified ? Icons.verified : Icons.fingerprint, color: accent, size: 20), const SizedBox(width: 8), Text( isVerified ? 'Fingerprint Verified' : 'Fingerprint Captured', style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: accent), ), if (isVerified) ...[ const Spacer(), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: AppTheme.saudiGreen, borderRadius: BorderRadius.circular(20), ), child: const Text( 'VERIFIED', style: TextStyle( color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 0.8), ), ), ], ], ), ), Padding( padding: const EdgeInsets.all(16), child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Container( height: 220, color: const Color(0xFF000000), alignment: Alignment.center, child: Image.memory( imageBytes, fit: BoxFit.contain, errorBuilder: (_, __, ___) => const Center( child: Text('Unable to render image', style: TextStyle(color: Colors.white54)), ), ), ), ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 18), child: Wrap( spacing: 8, runSpacing: 8, children: [ _InfoChip( icon: Icons.badge_outlined, label: state.documentType == DocumentType.nationalId ? 'National ID' : 'Passport', ), _InfoChip( icon: Icons.fingerprint, label: fingerIndexMap[state.selectedFingerIndex] ?? 'Unknown', ), if (state.documentType == DocumentType.nationalId) _InfoChip(icon: Icons.numbers, label: state.nationalIdInput) else ...[ _InfoChip(icon: Icons.flag_outlined, label: state.nationality), _InfoChip(icon: Icons.book_outlined, label: state.passportNumber), ], ], ), ), ], ), ); } } class _InfoChip extends StatelessWidget { final IconData icon; final String label; const _InfoChip({required this.icon, required this.label}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: AppTheme.inkRaised2, borderRadius: BorderRadius.circular(20), border: Border.all(color: AppTheme.hairline), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 14, color: AppTheme.cyan), const SizedBox(width: 5), Text(label, style: const TextStyle(fontSize: 12, color: AppTheme.textMuted)), ], ), ); } } // ── Top device connection status bar ──────────────────────────────────────── class _DeviceStatusBar extends StatelessWidget { final FingerprintFormState state; const _DeviceStatusBar({required this.state}); @override Widget build(BuildContext context) { final cfg = _resolveConfig(); return AnimatedContainer( duration: const Duration(milliseconds: 300), color: cfg.background, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: SafeArea( top: false, bottom: false, child: Row( children: [ cfg.showSpinner ? SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(cfg.foreground)), ) : Icon(cfg.icon, size: 16, color: cfg.foreground), const SizedBox(width: 8), Text( cfg.label, style: TextStyle(color: cfg.foreground, fontWeight: FontWeight.w700, fontSize: 13), ), if (state.deviceStatusMessage != null) ...[ const SizedBox(width: 6), Expanded( child: Text( '– ${state.deviceStatusMessage}', style: TextStyle(color: cfg.foreground.withValues(alpha: 0.85), fontSize: 12), overflow: TextOverflow.ellipsis, ), ), ], ], ), ), ); } _StatusConfig _resolveConfig() { final isConnecting = state.status == FormStatus.connecting; if (isConnecting) { return _StatusConfig( background: AppTheme.amberAlert.withValues(alpha: 0.14), foreground: AppTheme.amberAlert, icon: Icons.sync, showSpinner: true, label: 'Connecting to device…', ); } if (state.isDeviceConnected) { return _StatusConfig( background: AppTheme.saudiGreen.withValues(alpha: 0.14), foreground: AppTheme.saudiGreen, icon: Icons.usb, showSpinner: false, label: 'Device connected', ); } if (state.status == FormStatus.error && state.deviceStatusMessage != null) { return _StatusConfig( background: AppTheme.redAlert.withValues(alpha: 0.14), foreground: AppTheme.redAlert, icon: Icons.usb_off, showSpinner: false, label: 'Device not connected', ); } return _StatusConfig( background: AppTheme.inkRaised2, foreground: AppTheme.textMuted, icon: Icons.usb_off, showSpinner: false, label: 'Device disconnected', ); } } class _StatusConfig { final Color background; final Color foreground; final IconData icon; final bool showSpinner; final String label; const _StatusConfig({ required this.background, required this.foreground, required this.icon, required this.showSpinner, required this.label, }); } // import 'dart:convert'; // import 'dart:typed_data'; // import 'package:flutter/material.dart'; // import 'package:flutter_bloc/flutter_bloc.dart'; // import '../bloc/fingerprint_bloc.dart'; // import '../utils/const.dart' show countryAlpha2List, fingerIndexMap; // import 'auth_result_screen.dart'; // // class FingerprintView extends StatefulWidget { // const FingerprintView({super.key}); // // @override // State createState() => _FingerprintViewState(); // } // // class _FingerprintViewState extends State { // final _nationalIdController = TextEditingController(); // final _nationalityController = TextEditingController(); // final _passportController = TextEditingController(); // // @override // void dispose() { // _nationalIdController.dispose(); // _nationalityController.dispose(); // _passportController.dispose(); // super.dispose(); // } // // void _clearControllers() { // _nationalIdController.clear(); // _nationalityController.clear(); // _passportController.clear(); // } // // @override // Widget build(BuildContext context) { // return BlocProvider( // create: (_) => FingerprintBloc()..add(const AppStarted()), // child: _buildScaffold(), // ); // } // // Widget _buildScaffold() { // return Scaffold( // appBar: AppBar(title: const Text('Fingerprint Verification')), // body: BlocConsumer( // listenWhen: (prev, curr) => prev.status != curr.status, // listener: (context, state) { // if (state.status == FormStatus.error && state.errorMessage != null) { // ScaffoldMessenger.of(context).showSnackBar( // SnackBar(content: Text(state.errorMessage!)), // ); // } else if (state.status == FormStatus.scanned) { // ScaffoldMessenger.of(context).showSnackBar( // const SnackBar(content: Text('Fingerprint scanned successfully!')), // ); // } else if (state.status == FormStatus.verified && // state.authResult != null) { // // Navigate to the result screen — keep the fingerprint view in the // // back-stack so the user can return with the AppBar back arrow or // // the "Return to Fingerprint" button. // Navigator.of(context).push( // MaterialPageRoute( // builder: (_) => AuthResultScreen(result: state.authResult!), // ), // ); // } else if (state.status == FormStatus.idle) { // _clearControllers(); // } // }, // builder: (context, state) { // final isBusy = state.status == FormStatus.scanning || // state.status == FormStatus.connecting || // state.status == FormStatus.verifying; // // return (state.status == FormStatus.loggingIn)?SizedBox( // height: MediaQuery.sizeOf(context).height, // child: Center( // child: Column( // children: [ // CircularProgressIndicator( // ), // Text("Logging in"), // ], // ), // ), // ):Column( // crossAxisAlignment: CrossAxisAlignment.stretch, // children: [ // // ── Top device connection status bar ─────────────────────── // _DeviceStatusBar(state: state), // // // ── Scrollable form body ─────────────────────────────────── // Expanded( // child: SingleChildScrollView( // padding: const EdgeInsets.all(16), // child: Column( // crossAxisAlignment: CrossAxisAlignment.stretch, // children: [ // // ── Document Type ──────────────────────────────── // DropdownButtonFormField( // initialValue: state.documentType, // decoration: InputDecoration( // labelText: 'Document Type', // border: OutlineInputBorder( // borderRadius: BorderRadius.circular(8)), // ), // items: const [ // DropdownMenuItem( // value: DocumentType.nationalId, // child: Text('National ID'), // ), // DropdownMenuItem( // value: DocumentType.passport, // child: Text('Passport'), // ), // ], // onChanged: isBusy // ? null // : (val) { // if (val != null) { // context // .read() // .add(DocumentTypeChanged(val)); // } // }, // ), // const SizedBox(height: 16), // // // ── Finger Selection ────────────────────────────── // DropdownButtonFormField( // value: state.selectedFingerIndex, // decoration: InputDecoration( // labelText: 'Finger', // border: OutlineInputBorder( // borderRadius: BorderRadius.circular(8)), // ), // items: fingerIndexMap.entries // .map((e) => DropdownMenuItem( // value: e.key, // child: Text(e.value), // )) // .toList(), // onChanged: isBusy // ? null // : (val) { // if (val != null) { // context // .read() // .add(FingerTypeChanged(val)); // } // }, // ), // const SizedBox(height: 16), // // // ── Conditional form fields ─────────────────────── // if (state.documentType == DocumentType.nationalId) ...[ // TextField( // controller: _nationalIdController, // keyboardType: TextInputType.number, // enabled: !isBusy, // maxLength: 10, // decoration: InputDecoration( // labelText: 'National ID', // hintText: '10-digit number starting with 1 or 2', // border: OutlineInputBorder( // borderRadius: BorderRadius.circular(8)), // errorText: state.nationalIdError, // ), // onChanged: (val) => context // .read() // .add(NationalIdChanged(val)), // ), // ] else ...[ // _SearchableCountryDropdown( // selectedCode: state.nationality.isEmpty ? null : state.nationality, // errorText: state.nationalityError, // enabled: !isBusy, // onChanged: (code) => context // .read() // .add(NationalityChanged(code)), // ), // // const SizedBox(height: 12), // TextField( // controller: _passportController, // enabled: !isBusy, // textCapitalization: TextCapitalization.characters, // decoration: InputDecoration( // labelText: 'Passport Number', // hintText: '6–9 alphanumeric characters', // border: OutlineInputBorder( // borderRadius: BorderRadius.circular(8)), // errorText: state.passportNumberError, // ), // onChanged: (val) => context // .read() // .add(PassportNumberChanged(val)), // ), // ], // const SizedBox(height: 24), // // // ── Action buttons ──────────────────────────────── // Row( // children: [ // Expanded( // child: ElevatedButton.icon( // onPressed: isBusy // ? null // : () => context // .read() // .add(const ConnectDevice()), // icon: state.status == FormStatus.connecting && // !state.isScanConnecting // ? const SizedBox( // width: 16, // height: 16, // child: CircularProgressIndicator( // strokeWidth: 2), // ) // : const Icon(Icons.usb), // label: Text( // state.status == FormStatus.connecting && // !state.isScanConnecting // ? 'Connecting…' // : 'Connect', // ), // ), // ), // const SizedBox(width: 12), // Expanded( // child: ElevatedButton.icon( // onPressed: isBusy // ? null // : () => context // .read() // .add(const ScanFingerprintEvent()), // icon: state.isScanConnecting || // state.status == FormStatus.scanning // ? const SizedBox( // width: 16, // height: 16, // child: CircularProgressIndicator( // strokeWidth: 2), // ) // : const Icon(Icons.fingerprint), // label: Text( // state.isScanConnecting // ? 'Connecting…' // : state.status == FormStatus.scanning // ? 'Scanning…' // : 'Scan', // ), // ), // ), // ], // ), // const SizedBox(height: 12), // // // ── Reset button ────────────────────────────────── // if (state.status == FormStatus.scanned || // state.status == FormStatus.verified || // state.status == FormStatus.error) // ElevatedButton( // onPressed: () { // _clearControllers(); // context // .read() // .add(const ResetBlocEvent()); // }, // style: ElevatedButton.styleFrom( // backgroundColor: Colors.grey), // child: const Text('Reset'), // ), // const SizedBox(height: 24), // // // ── Scan result ─────────────────────────────────── // if ((state.status == FormStatus.scanned || // state.status == FormStatus.verifying || // state.status == FormStatus.verified) && // state.base64Data != null) // _ScanResultCard(state: state), // ], // ), // ), // ), // ], // ); // }, // ), // ); // } // } // // // ── Searchable country dropdown ──────────────────────────────────────────────── // // class _SearchableCountryDropdown extends StatefulWidget { // final String? selectedCode; // final String? errorText; // final bool enabled; // final ValueChanged onChanged; // // const _SearchableCountryDropdown({ // required this.onChanged, // this.selectedCode, // this.errorText, // this.enabled = true, // }); // // @override // State<_SearchableCountryDropdown> createState() => // _SearchableCountryDropdownState(); // } // // class _SearchableCountryDropdownState // extends State<_SearchableCountryDropdown> { // String? _displayLabel(String? code) { // if (code == null || code.isEmpty) return null; // final match = countryAlpha2List.firstWhere( // (c) => c['code'] == code, // orElse: () => {}, // ); // if (match.isEmpty) return code; // return '${match['name']} (${match['code']})'; // } // // void _openSearch() { // showModalBottomSheet( // context: context, // isScrollControlled: true, // shape: const RoundedRectangleBorder( // borderRadius: BorderRadius.vertical(top: Radius.circular(16)), // ), // builder: (_) => _CountrySearchSheet( // selectedCode: widget.selectedCode, // onSelected: (code) { // Navigator.of(context).pop(); // widget.onChanged(code); // }, // ), // ); // } // // @override // Widget build(BuildContext context) { // final label = _displayLabel(widget.selectedCode); // final hasError = widget.errorText != null; // // return GestureDetector( // onTap: widget.enabled ? _openSearch : null, // child: InputDecorator( // decoration: InputDecoration( // labelText: 'Nationality', // hintText: 'Select country', // errorText: widget.errorText, // border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), // suffixIcon: const Icon(Icons.arrow_drop_down), // enabled: widget.enabled, // errorBorder: OutlineInputBorder( // borderRadius: BorderRadius.circular(8), // borderSide: BorderSide(color: Theme.of(context).colorScheme.error), // ), // ), // isEmpty: label == null, // child: Text( // label ?? '', // style: TextStyle( // fontSize: 16, // color: widget.enabled // ? (hasError // ? Theme.of(context).colorScheme.error // : Theme.of(context).textTheme.bodyLarge?.color) // : Theme.of(context).disabledColor, // ), // overflow: TextOverflow.ellipsis, // ), // ), // ); // } // } // // // ── Country search bottom sheet ──────────────────────────────────────────────── // // class _CountrySearchSheet extends StatefulWidget { // final String? selectedCode; // final ValueChanged onSelected; // // const _CountrySearchSheet({ // required this.onSelected, // this.selectedCode, // }); // // @override // State<_CountrySearchSheet> createState() => _CountrySearchSheetState(); // } // // class _CountrySearchSheetState extends State<_CountrySearchSheet> { // final _searchController = TextEditingController(); // List> _filtered = List.from(countryAlpha2List); // // @override // void dispose() { // _searchController.dispose(); // super.dispose(); // } // // void _onSearch(String query) { // final lower = query.toLowerCase().trim(); // setState(() { // _filtered = lower.isEmpty // ? List.from(countryAlpha2List) // : countryAlpha2List // .where((c) => // c['name']!.toLowerCase().contains(lower) || // c['code']!.toLowerCase().contains(lower)) // .toList(); // }); // } // // @override // Widget build(BuildContext context) { // final bottomInset = MediaQuery.of(context).viewInsets.bottom; // // return Padding( // padding: EdgeInsets.only(bottom: bottomInset), // child: SizedBox( // height: MediaQuery.of(context).size.height * 0.75, // child: Column( // children: [ // // ── Handle ────────────────────────────────────────────────── // const SizedBox(height: 8), // Container( // width: 40, // height: 4, // decoration: BoxDecoration( // color: Colors.grey.shade300, // borderRadius: BorderRadius.circular(2), // ), // ), // const SizedBox(height: 12), // // // ── Title ──────────────────────────────────────────────────── // const Padding( // padding: EdgeInsets.symmetric(horizontal: 16), // child: Align( // alignment: Alignment.centerLeft, // child: Text( // 'Select Nationality', // style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), // ), // ), // ), // const SizedBox(height: 10), // // // ── Search field ───────────────────────────────────────────── // Padding( // padding: const EdgeInsets.symmetric(horizontal: 16), // child: TextField( // controller: _searchController, // autofocus: true, // decoration: InputDecoration( // hintText: 'Search country…', // prefixIcon: const Icon(Icons.search), // suffixIcon: _searchController.text.isNotEmpty // ? IconButton( // icon: const Icon(Icons.clear), // onPressed: () { // _searchController.clear(); // _onSearch(''); // }, // ) // : null, // border: OutlineInputBorder( // borderRadius: BorderRadius.circular(8)), // contentPadding: const EdgeInsets.symmetric( // vertical: 10, horizontal: 12), // ), // onChanged: _onSearch, // ), // ), // const SizedBox(height: 8), // const Divider(height: 1), // // // ── List ───────────────────────────────────────────────────── // Expanded( // child: _filtered.isEmpty // ? const Center(child: Text('No countries found')) // : ListView.builder( // keyboardDismissBehavior: // ScrollViewKeyboardDismissBehavior.onDrag, // itemCount: _filtered.length, // itemBuilder: (_, index) { // final country = _filtered[index]; // final code = country['code']!; // final name = country['name']!; // final isSelected = code == widget.selectedCode; // // return ListTile( // dense: true, // leading: isSelected // ? const Icon(Icons.check_circle, // color: Colors.indigo, size: 20) // : const Icon(Icons.flag_outlined, // color: Colors.grey, size: 20), // title: Text( // '$name ($code)', // style: TextStyle( // fontSize: 14, // fontWeight: isSelected // ? FontWeight.w600 // : FontWeight.normal, // color: isSelected ? Colors.indigo : null, // ), // ), // selected: isSelected, // onTap: () => widget.onSelected(code), // ); // }, // ), // ), // ], // ), // ), // ); // } // } // // // ── Scan result card with image + verify button ──────────────────────────── // // class _ScanResultCard extends StatelessWidget { // final FingerprintFormState state; // const _ScanResultCard({required this.state}); // // /// Strips the optional data-URI header (data:image/png;base64,...) and // /// returns raw bytes ready for [Image.memory]. // static Uint8List _decodeBase64(String raw) { // final clean = raw.contains(',') ? raw.split(',').last : raw; // return base64Decode(clean); // } // // @override // Widget build(BuildContext context) { // final isVerifying = state.status == FormStatus.verifying; // final isVerified = state.status == FormStatus.verified; // // late final Uint8List imageBytes; // try { // imageBytes = _decodeBase64(state.base64Data!); // } catch (_) { // return const SizedBox.shrink(); // } // // return Container( // decoration: BoxDecoration( // color: isVerified ? Colors.green.shade50 : Colors.grey.shade50, // borderRadius: BorderRadius.circular(12), // border: Border.all( // color: isVerified ? Colors.green.shade400 : Colors.grey.shade300, // width: 1.5, // ), // ), // child: Column( // crossAxisAlignment: CrossAxisAlignment.stretch, // children: [ // // ── Header ────────────────────────────────────────────────── // Container( // padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), // decoration: BoxDecoration( // color: isVerified ? Colors.green.shade100 : Colors.blueGrey.shade50, // borderRadius: const BorderRadius.vertical(top: Radius.circular(10)), // ), // child: Row( // children: [ // Icon( // isVerified ? Icons.verified : Icons.fingerprint, // color: isVerified ? Colors.green.shade700 : Colors.blueGrey, // size: 20, // ), // const SizedBox(width: 8), // Text( // isVerified ? 'Fingerprint Verified' : 'Fingerprint Captured', // style: TextStyle( // fontSize: 15, // fontWeight: FontWeight.bold, // color: isVerified // ? Colors.green.shade800 // : Colors.blueGrey.shade800, // ), // ), // if (isVerified) ...[ // const Spacer(), // Container( // padding: // const EdgeInsets.symmetric(horizontal: 8, vertical: 3), // decoration: BoxDecoration( // color: Colors.green.shade600, // borderRadius: BorderRadius.circular(20), // ), // child: const Text( // 'VERIFIED', // style: TextStyle( // color: Colors.white, // fontSize: 11, // fontWeight: FontWeight.bold, // letterSpacing: 0.8), // ), // ), // ], // ], // ), // ), // // // ── Fingerprint image ──────────────────────────────────────── // Padding( // padding: const EdgeInsets.all(16), // child: ClipRRect( // borderRadius: BorderRadius.circular(8), // child: Container( // height: 200, // color: Colors.black, // alignment: Alignment.center, // child: Image.memory( // imageBytes, // fit: BoxFit.contain, // errorBuilder: (_, __, ___) => const Center( // child: Text( // 'Unable to render image', // style: TextStyle(color: Colors.white54), // ), // ), // ), // ), // ), // ), // // // ── Document info row ──────────────────────────────────────── // Padding( // padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), // child: Wrap( // spacing: 8, // runSpacing: 6, // children: [ // _InfoChip( // icon: Icons.badge_outlined, // label: state.documentType == DocumentType.nationalId // ? 'National ID' // : 'Passport', // ), // _InfoChip( // icon: Icons.fingerprint, // label: fingerIndexMap[state.selectedFingerIndex] ?? 'Unknown', // ), // if (state.documentType == DocumentType.nationalId) // _InfoChip( // icon: Icons.numbers, // label: state.nationalIdInput, // ) // else ...[ // _InfoChip( // icon: Icons.flag_outlined, // label: state.nationality, // ), // _InfoChip( // icon: Icons.book_outlined, // label: state.passportNumber, // ), // ], // ], // ), // ), // // const SizedBox(height: 16), // // // ── Verify button ──────────────────────────────────────────── // if (!isVerified) // Padding( // padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), // child: ElevatedButton.icon( // onPressed: isVerifying // ? null // : () => context // .read() // .add(const VerifyFingerprintEvent()), // style: ElevatedButton.styleFrom( // backgroundColor: Colors.indigo, // foregroundColor: Colors.white, // padding: const EdgeInsets.symmetric(vertical: 14), // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(8)), // ), // icon: isVerifying // ? const SizedBox( // width: 18, // height: 18, // child: CircularProgressIndicator( // strokeWidth: 2, // valueColor: // AlwaysStoppedAnimation(Colors.white), // ), // ) // : const Icon(Icons.verified_user_outlined), // label: Text( // isVerifying ? 'Verifying…' : 'Verify Fingerprint', // style: const TextStyle( // fontSize: 15, fontWeight: FontWeight.w600), // ), // ), // ), // // if (isVerified) // Padding( // padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), // child: Row( // children: [ // const Icon(Icons.check_circle, // color: Colors.green, size: 20), // const SizedBox(width: 8), // const Text( // 'Identity verified successfully', // style: TextStyle( // color: Colors.green, // fontWeight: FontWeight.w600, // ), // ), // ], // ), // ), // ], // ), // ); // } // } // // class _InfoChip extends StatelessWidget { // final IconData icon; // final String label; // const _InfoChip({required this.icon, required this.label}); // // @override // Widget build(BuildContext context) { // return Container( // padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), // decoration: BoxDecoration( // color: Colors.blueGrey.shade50, // borderRadius: BorderRadius.circular(20), // border: Border.all(color: Colors.blueGrey.shade200), // ), // child: Row( // mainAxisSize: MainAxisSize.min, // children: [ // Icon(icon, size: 14, color: Colors.blueGrey), // const SizedBox(width: 4), // Text( // label, // style: TextStyle(fontSize: 12, color: Colors.blueGrey.shade700), // ), // ], // ), // ); // } // } // // // ── Top connection status bar ────────────────────────────────────────────────── // // class _DeviceStatusBar extends StatelessWidget { // final FingerprintFormState state; // const _DeviceStatusBar({required this.state}); // // @override // Widget build(BuildContext context) { // final _StatusConfig cfg = _resolveConfig(); // // return AnimatedContainer( // duration: const Duration(milliseconds: 300), // color: cfg.background, // padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), // child: SafeArea( // top: false, // bottom: false, // child: Row( // children: [ // // Icon / spinner // cfg.showSpinner // ? SizedBox( // width: 16, // height: 16, // child: CircularProgressIndicator( // strokeWidth: 2, // valueColor: // AlwaysStoppedAnimation(cfg.foreground), // ), // ) // : Icon(cfg.icon, size: 16, color: cfg.foreground), // const SizedBox(width: 8), // // Primary label // Text( // cfg.label, // style: TextStyle( // color: cfg.foreground, // fontWeight: FontWeight.w600, // fontSize: 13, // ), // ), // // Live status message (during connect/retry) // if (state.deviceStatusMessage != null) ...[ // const SizedBox(width: 6), // Expanded( // child: Text( // '– ${state.deviceStatusMessage}', // style: TextStyle( // color: cfg.foreground.withValues(alpha: 0.85), // fontSize: 12, // ), // overflow: TextOverflow.ellipsis, // ), // ), // ], // ], // ), // ), // ); // } // // _StatusConfig _resolveConfig() { // final isConnecting = state.status == FormStatus.connecting; // // if (isConnecting) { // return _StatusConfig( // background: const Color(0xFFFFF3CD), // amber-50 // foreground: const Color(0xFF7B5800), // icon: Icons.sync, // showSpinner: true, // label: 'Connecting to device…', // ); // } // // if (state.isDeviceConnected) { // return _StatusConfig( // background: const Color(0xFFD4EDDA), // green-50 // foreground: const Color(0xFF155724), // icon: Icons.usb, // showSpinner: false, // label: 'Device connected', // ); // } // // if (state.status == FormStatus.error && // state.deviceStatusMessage != null) { // return _StatusConfig( // background: const Color(0xFFF8D7DA), // red-50 // foreground: const Color(0xFF721C24), // icon: Icons.usb_off, // showSpinner: false, // label: 'Device not connected', // ); // } // // // Default: idle / not yet attempted // return _StatusConfig( // background: const Color(0xFFF0F0F0), // foreground: const Color(0xFF555555), // icon: Icons.usb_off, // showSpinner: false, // label: 'Device disconnected', // ); // } // } // // class _StatusConfig { // final Color background; // final Color foreground; // final IconData icon; // final bool showSpinner; // final String label; // // const _StatusConfig({ // required this.background, // required this.foreground, // required this.icon, // required this.showSpinner, // required this.label, // }); // }