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.
648 lines
23 KiB
Dart
648 lines
23 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../../theme/app_theme.dart';
|
|
import '../models/auth_result_ui_model.dart';
|
|
|
|
/// Full-screen view that displays the authentication result returned by the
|
|
/// verification API. Redesigned with a "futuristic hospital, KSA" theme:
|
|
/// dark glassy panels, glowing cyan/green accents, Material 3 components,
|
|
/// and a responsive two-column layout on tablets.
|
|
class AuthResultScreen extends StatelessWidget {
|
|
final AuthResultUiModel result;
|
|
|
|
const AuthResultScreen({super.key, required this.result});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final accent = result.isSuccess ? AppTheme.saudiGreen : AppTheme.redAlert;
|
|
|
|
return Scaffold(
|
|
backgroundColor: AppTheme.ink,
|
|
appBar: AppBar(
|
|
leading: const BackButton(),
|
|
title: const Text('Authentication Result'),
|
|
actions: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: 16),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
|
decoration: BoxDecoration(
|
|
color: accent.withValues(alpha: 0.12),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: accent.withValues(alpha: 0.4)),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(
|
|
result.isSuccess ? Icons.verified_outlined : Icons.error_outline,
|
|
size: 14,
|
|
color: accent,
|
|
),
|
|
const SizedBox(width: 5),
|
|
Text(
|
|
result.isSuccess ? 'VERIFIED' : 'FAILED',
|
|
style: TextStyle(fontSize: 11, color: accent, fontWeight: FontWeight.w700),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
body: TechGridBackground(
|
|
child: SafeArea(
|
|
child: LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
final tablet = constraints.maxWidth >= 760;
|
|
return Column(
|
|
children: [
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
|
child: Center(
|
|
child: ConstrainedBox(
|
|
constraints: BoxConstraints(maxWidth: tablet ? 1000 : 720),
|
|
child: tablet ? _TabletBody(result: result) : _PhoneBody(result: result),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 4, 16, 16),
|
|
child: Center(
|
|
child: ConstrainedBox(
|
|
constraints: BoxConstraints(maxWidth: tablet ? 1000 : 720),
|
|
child: FilledButton.icon(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
icon: const Icon(Icons.fingerprint),
|
|
label: const Text('Return to Fingerprint'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── Phone body: stacked sections ────────────────────────────────────────────
|
|
|
|
class _PhoneBody extends StatelessWidget {
|
|
final AuthResultUiModel result;
|
|
const _PhoneBody({required this.result});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
_StatusBanner(result: result),
|
|
const SizedBox(height: 16),
|
|
if (result.isSuccess) _PersonInfoPanel(result: result),
|
|
if (!result.isSuccess) _ErrorPanel(result: result),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── Tablet body: status banner full width, info/error two-column grid ─────
|
|
|
|
class _TabletBody extends StatelessWidget {
|
|
final AuthResultUiModel result;
|
|
const _TabletBody({required this.result});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
_StatusBanner(result: result),
|
|
const SizedBox(height: 20),
|
|
if (result.isSuccess) _PersonInfoPanel(result: result, twoColumn: true),
|
|
if (!result.isSuccess) _ErrorPanel(result: result),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── Status banner ───────────────────────────────────────────────────────────
|
|
|
|
class _StatusBanner extends StatelessWidget {
|
|
final AuthResultUiModel result;
|
|
const _StatusBanner({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),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── Person information panel ────────────────────────────────────────────────
|
|
|
|
class _PersonInfoPanel extends StatelessWidget {
|
|
final AuthResultUiModel result;
|
|
final bool twoColumn;
|
|
const _PersonInfoPanel({required this.result, this.twoColumn = false});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final fullName = [result.firstName, result.lastName].where((s) => s.isNotEmpty).join(' ');
|
|
|
|
final rows = <_DetailRow>[
|
|
_DetailRow(label: 'Father', value: result.fatherName, icon: Icons.man_outlined),
|
|
_DetailRow(label: 'Grandfather', value: result.grandfatherName, icon: Icons.elderly_outlined),
|
|
_DetailRow(label: 'Occupation', value: result.occupation, icon: Icons.work_outline),
|
|
_DetailRow(label: 'Gender', value: result.gender, icon: Icons.wc_outlined),
|
|
_DetailRow(label: 'Sponsor', value: result.sponsor, icon: Icons.shield_outlined),
|
|
_DetailRow(label: 'ID Number', value: result.idNumber, icon: Icons.badge_outlined),
|
|
_DetailRow(label: 'Birth Date', value: result.birthDate, icon: Icons.cake_outlined),
|
|
_DetailRow(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),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── Error panel ──────────────────────────────────────────────────────────────
|
|
|
|
class _ErrorPanel extends StatelessWidget {
|
|
final AuthResultUiModel result;
|
|
const _ErrorPanel({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),
|
|
_DetailRow(label: 'Type', value: result.errorType ?? '', icon: Icons.category_outlined),
|
|
_DetailRow(label: 'Message', value: result.errorMessage ?? '', icon: Icons.notes_outlined),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── Reusable detail row (icon + label + value) ──────────────────────────────
|
|
|
|
class _DetailRow extends StatelessWidget {
|
|
final String label;
|
|
final String value;
|
|
final IconData icon;
|
|
|
|
const _DetailRow({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,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
// import 'package:flutter/material.dart';
|
|
// import '../models/auth_result_ui_model.dart';
|
|
//
|
|
// /// Full-screen view that displays the authentication result returned by the
|
|
// /// verification API. Designed with the Material 3 design system.
|
|
// class AuthResultScreen extends StatelessWidget {
|
|
// final AuthResultUiModel result;
|
|
//
|
|
// const AuthResultScreen({super.key, required this.result});
|
|
//
|
|
// @override
|
|
// Widget build(BuildContext context) {
|
|
// final cs = Theme.of(context).colorScheme;
|
|
//
|
|
// return Scaffold(
|
|
// backgroundColor: cs.surfaceContainerLowest,
|
|
// appBar: AppBar(
|
|
// backgroundColor: cs.surface,
|
|
// surfaceTintColor: cs.surfaceTint,
|
|
// leading: const BackButton(),
|
|
// title: const Text('Authentication Result'),
|
|
// titleTextStyle: Theme.of(context).textTheme.titleLarge?.copyWith(
|
|
// fontWeight: FontWeight.bold,
|
|
// ),
|
|
// centerTitle: false,
|
|
// elevation: 0,
|
|
// scrolledUnderElevation: 1,
|
|
// ),
|
|
// body: SafeArea(
|
|
// child: Column(
|
|
// children: [
|
|
// // ── Scrollable content ───────────────────────────────────────────
|
|
// Expanded(
|
|
// child: SingleChildScrollView(
|
|
// padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
|
// child: Card(
|
|
// elevation: 0,
|
|
// shape: RoundedRectangleBorder(
|
|
// borderRadius: BorderRadius.circular(16),
|
|
// side: BorderSide(color: cs.outlineVariant),
|
|
// ),
|
|
// child: Padding(
|
|
// padding: const EdgeInsets.symmetric(
|
|
// vertical: 20, horizontal: 16),
|
|
// child: Column(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: [
|
|
// // ── Status ────────────────────────────────────────
|
|
// _StatusSection(result: result),
|
|
//
|
|
// _divider(),
|
|
//
|
|
// // ── Person information ───────────────────────────
|
|
// _PersonInfoSection(result: result),
|
|
//
|
|
// _divider(),
|
|
//
|
|
// // ── Error ─────────────────────────────────────────
|
|
// _ErrorSection(result: result),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
//
|
|
// // ── Return button ────────────────────────────────────────────────
|
|
// Padding(
|
|
// padding: const EdgeInsets.fromLTRB(16, 4, 16, 16),
|
|
// child: FilledButton.icon(
|
|
// onPressed: () => Navigator.of(context).pop(),
|
|
// icon: const Icon(Icons.fingerprint),
|
|
// label: const Text('Return to Fingerprint'),
|
|
// style: FilledButton.styleFrom(
|
|
// minimumSize: const Size.fromHeight(52),
|
|
// shape: RoundedRectangleBorder(
|
|
// borderRadius: BorderRadius.circular(12),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
// );
|
|
// }
|
|
//
|
|
// Widget _divider() => const Padding(
|
|
// padding: EdgeInsets.symmetric(vertical: 12),
|
|
// child: Divider(height: 1),
|
|
// );
|
|
// }
|
|
//
|
|
// // ── Status section ─────────────────────────────────────────────────────────────
|
|
//
|
|
// class _StatusSection extends StatelessWidget {
|
|
// final AuthResultUiModel result;
|
|
//
|
|
// const _StatusSection({required this.result});
|
|
//
|
|
// @override
|
|
// Widget build(BuildContext context) {
|
|
// final tt = Theme.of(context).textTheme;
|
|
// final successColor = const Color(0xFF2E7D32); // green-800
|
|
// final errorColor = Theme.of(context).colorScheme.error;
|
|
//
|
|
// return Column(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: [
|
|
// Text('Status:', style: tt.titleSmall?.copyWith(fontWeight: FontWeight.bold)),
|
|
// const SizedBox(height: 4),
|
|
// Text(
|
|
// result.statusMessage.isEmpty
|
|
// ? (result.isSuccess ? 'Authentication successful!' : 'Authentication failed')
|
|
// : result.statusMessage,
|
|
// style: tt.bodyMedium?.copyWith(
|
|
// color: result.isSuccess ? successColor : errorColor,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// );
|
|
// }
|
|
// }
|
|
//
|
|
// // ── Person information section ─────────────────────────────────────────────────
|
|
//
|
|
// class _PersonInfoSection extends StatelessWidget {
|
|
// final AuthResultUiModel result;
|
|
//
|
|
// const _PersonInfoSection({required this.result});
|
|
//
|
|
// @override
|
|
// Widget build(BuildContext context) {
|
|
// final tt = Theme.of(context).textTheme;
|
|
// const successGreen = Color(0xFF2E7D32);
|
|
//
|
|
// return Column(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: [
|
|
// // Section header
|
|
// Row(
|
|
// children: [
|
|
// const Icon(Icons.check, color: successGreen, size: 18),
|
|
// const SizedBox(width: 6),
|
|
// Text(
|
|
// 'Person Information:',
|
|
// style: tt.titleSmall?.copyWith(
|
|
// fontWeight: FontWeight.bold,
|
|
// color: successGreen,
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// const SizedBox(height: 12),
|
|
//
|
|
// // ── Full Name (two visual lines: first name + last name) ──────────
|
|
// _DetailRow(
|
|
// label: 'Full Name:',
|
|
// value: result.firstName,
|
|
// valueBold: true,
|
|
// ),
|
|
// if (result.lastName.isNotEmpty) ...[
|
|
// const SizedBox(height: 2),
|
|
// Padding(
|
|
// padding: const EdgeInsets.only(bottom: 8),
|
|
// child: Text(
|
|
// result.lastName,
|
|
// style: tt.bodyMedium?.copyWith(fontWeight: FontWeight.bold),
|
|
// ),
|
|
// ),
|
|
// ],
|
|
//
|
|
// _DetailRow(label: 'Father:', value: result.fatherName),
|
|
// _DetailRow(label: 'Grandfather:', value: result.grandfatherName),
|
|
// _DetailRow(label: 'Occupation:', value: result.occupation),
|
|
// _DetailRow(label: 'Gender:', value: result.gender),
|
|
// _DetailRow(label: 'Sponsor:', value: result.sponsor),
|
|
//
|
|
// const SizedBox(height: 4),
|
|
//
|
|
// _DetailRow(label: 'ID Number:', value: result.idNumber),
|
|
// _DetailRow(label: 'Birth Date:', value: result.birthDate),
|
|
// _DetailRow(label: 'Nationality:', value: result.nationality),
|
|
// ],
|
|
// );
|
|
// }
|
|
// }
|
|
//
|
|
// // ── Error section ──────────────────────────────────────────────────────────────
|
|
//
|
|
// class _ErrorSection extends StatelessWidget {
|
|
// final AuthResultUiModel result;
|
|
//
|
|
// const _ErrorSection({required this.result});
|
|
//
|
|
// @override
|
|
// Widget build(BuildContext context) {
|
|
// final tt = Theme.of(context).textTheme;
|
|
// final errorColor = Theme.of(context).colorScheme.error;
|
|
//
|
|
// return Column(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: [
|
|
// // Section header
|
|
// Row(
|
|
// children: [
|
|
// Icon(Icons.close, color: errorColor, size: 18),
|
|
// const SizedBox(width: 6),
|
|
// Text(
|
|
// 'Error:',
|
|
// style: tt.titleSmall?.copyWith(
|
|
// fontWeight: FontWeight.bold,
|
|
// color: errorColor,
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// const SizedBox(height: 12),
|
|
// _DetailRow(label: 'Type:', value: result.errorType ?? ''),
|
|
// _DetailRow(label: 'Message:', value: result.errorMessage ?? ''),
|
|
// ],
|
|
// );
|
|
// }
|
|
// }
|
|
//
|
|
// // ── Reusable detail row (label + value) ────────────────────────────────────────
|
|
//
|
|
// class _DetailRow extends StatelessWidget {
|
|
// final String label;
|
|
// final String value;
|
|
// final bool valueBold;
|
|
//
|
|
// const _DetailRow({
|
|
// required this.label,
|
|
// required this.value,
|
|
// this.valueBold = false,
|
|
// });
|
|
//
|
|
// @override
|
|
// Widget build(BuildContext context) {
|
|
// final tt = Theme.of(context).textTheme;
|
|
//
|
|
// return Padding(
|
|
// padding: const EdgeInsets.only(bottom: 8),
|
|
// child: Column(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: [
|
|
// Text(
|
|
// label,
|
|
// style: tt.bodySmall?.copyWith(
|
|
// fontWeight: FontWeight.bold,
|
|
// color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
// ),
|
|
// ),
|
|
// const SizedBox(height: 2),
|
|
// Text(
|
|
// value.isEmpty ? '—' : value,
|
|
// style: tt.bodyMedium?.copyWith(
|
|
// fontWeight: valueBold ? FontWeight.bold : FontWeight.normal,
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// );
|
|
// }
|
|
// }
|