You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
9.8 KiB
Dart

import 'package:finger_print_verification/theme/app_theme.dart';
import 'package:flutter/material.dart';
import '../models/auth_result_ui_model.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// Shared, public building blocks for rendering an [AuthResultUiModel].
///
/// These used to live as private widgets inside `auth_result_screen.dart`.
/// They're now public and standalone so the SAME widgets can be reused:
/// • full-screen, on phones, via [AuthResultScreen] (pushed route)
/// • inline, on tablets, embedded directly in [FingerprintView]'s result
/// pane — no navigation needed, the result just appears next to the form.
/// ─────────────────────────────────────────────────────────────────────────
class ResultStatusBanner extends StatelessWidget {
final AuthResultUiModel result;
const ResultStatusBanner({super.key, required this.result});
@override
Widget build(BuildContext context) {
final accent = result.isSuccess ? AppTheme.saudiGreen : AppTheme.redAlert;
final message = result.statusMessage.isEmpty
? (result.isSuccess ? 'Authentication successful!' : 'Authentication failed')
: result.statusMessage;
return GlowPanel(
glowColor: accent,
padding: const EdgeInsets.all(20),
child: Row(
children: [
Container(
width: 52,
height: 52,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: accent.withValues(alpha: 0.12),
border: Border.all(color: accent.withValues(alpha: 0.4)),
),
child: Icon(
result.isSuccess ? Icons.check_circle_outline : Icons.cancel_outlined,
color: accent,
size: 28,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'STATUS',
style: TextStyle(
color: AppTheme.textMuted,
fontSize: 11,
fontWeight: FontWeight.w700,
letterSpacing: 1.1,
),
),
const SizedBox(height: 4),
Text(
message,
style: TextStyle(color: accent, fontWeight: FontWeight.w700, fontSize: 16),
),
],
),
),
],
),
);
}
}
class ResultPersonInfoPanel extends StatelessWidget {
final AuthResultUiModel result;
final bool twoColumn;
const ResultPersonInfoPanel({super.key, required this.result, this.twoColumn = false});
@override
Widget build(BuildContext context) {
final fullName = [result.firstName, result.lastName].where((s) => s.isNotEmpty).join(' ');
final rows = <ResultDetailRow>[
ResultDetailRow(label: 'Father', value: result.fatherName, icon: Icons.man_outlined),
ResultDetailRow(label: 'Grandfather', value: result.grandfatherName, icon: Icons.elderly_outlined),
ResultDetailRow(label: 'Occupation', value: result.occupation, icon: Icons.work_outline),
ResultDetailRow(label: 'Gender', value: result.gender, icon: Icons.wc_outlined),
ResultDetailRow(label: 'Sponsor', value: result.sponsor, icon: Icons.shield_outlined),
ResultDetailRow(label: 'ID Number', value: result.idNumber, icon: Icons.badge_outlined),
ResultDetailRow(label: 'Birth Date', value: result.birthDate, icon: Icons.cake_outlined),
ResultDetailRow(label: 'Nationality', value: result.nationality, icon: Icons.flag_outlined),
];
return GlowPanel(
glowColor: AppTheme.saudiGreen,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 44,
height: 44,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppTheme.saudiGreen.withValues(alpha: 0.12),
border: Border.all(color: AppTheme.saudiGreen.withValues(alpha: 0.4)),
),
child: const Icon(Icons.person_outline, color: AppTheme.saudiGreen, size: 22),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'PERSON INFORMATION',
style: TextStyle(
color: AppTheme.saudiGreen,
fontSize: 12,
fontWeight: FontWeight.w700,
letterSpacing: 1.0,
),
),
const SizedBox(height: 2),
Text(
fullName.isEmpty ? '' : fullName,
style: const TextStyle(
color: AppTheme.textPrimary,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
],
),
),
],
),
const SizedBox(height: 8),
const Divider(height: 1),
const SizedBox(height: 8),
if (twoColumn)
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 4,
crossAxisSpacing: 16,
childAspectRatio: 5.4,
children: rows,
)
else
Column(children: rows),
],
),
);
}
}
class ResultErrorPanel extends StatelessWidget {
final AuthResultUiModel result;
const ResultErrorPanel({super.key, required this.result});
@override
Widget build(BuildContext context) {
return GlowPanel(
glowColor: AppTheme.redAlert,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 44,
height: 44,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppTheme.redAlert.withValues(alpha: 0.12),
border: Border.all(color: AppTheme.redAlert.withValues(alpha: 0.4)),
),
child: const Icon(Icons.error_outline, color: AppTheme.redAlert, size: 22),
),
const SizedBox(width: 12),
const Text(
'ERROR DETAILS',
style: TextStyle(
color: AppTheme.redAlert,
fontSize: 12,
fontWeight: FontWeight.w700,
letterSpacing: 1.0,
),
),
],
),
const SizedBox(height: 8),
const Divider(height: 1),
const SizedBox(height: 8),
ResultDetailRow(label: 'Type', value: result.errorType ?? '', icon: Icons.category_outlined),
ResultDetailRow(label: 'Message', value: result.errorMessage ?? '', icon: Icons.notes_outlined),
],
),
);
}
}
/// Reusable detail row (icon + label + value).
class ResultDetailRow extends StatelessWidget {
final String label;
final String value;
final IconData icon;
const ResultDetailRow({super.key, required this.label, required this.value, required this.icon});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 16, color: AppTheme.textMuted),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: AppTheme.textMuted,
letterSpacing: 0.4,
),
),
const SizedBox(height: 2),
Text(
value.isEmpty ? '' : value,
style: const TextStyle(
fontSize: 14,
color: AppTheme.textPrimary,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
);
}
}
/// Full result body (banner + person-info-or-error), used by both the
/// pushed phone route and the inline tablet pane.
class ResultBody extends StatelessWidget {
final AuthResultUiModel result;
final bool twoColumn;
const ResultBody({super.key, required this.result, this.twoColumn = false});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ResultStatusBanner(result: result),
const SizedBox(height: 16),
if (result.isSuccess) ResultPersonInfoPanel(result: result, twoColumn: twoColumn),
if (!result.isSuccess) ResultErrorPanel(result: result),
],
);
}
}