chnages has been made

This commit is contained in:
Daniah Ayad Al-sultani
2026-02-22 11:18:10 +03:00
parent 3a9e7ca8db
commit f616a2c104
26 changed files with 1130 additions and 201 deletions

View File

@@ -12,10 +12,11 @@ import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
import '../../core/error/exceptions.dart';
import '../face/face_feedback.dart';
import 'package:local_auth/local_auth.dart';
class OvalCameraCapturePage extends StatefulWidget {
final bool isLogin;
final Future<void> Function(File image) onCapture;
final Future<void> Function(File image, {required bool localAuth}) onCapture;
const OvalCameraCapturePage({
super.key,
@@ -37,6 +38,13 @@ class _OvalCameraCapturePageState extends State<OvalCameraCapturePage> {
bool _isSubmitting = false;
bool _isStreaming = false;
//to handel the state of auth when it gives 422 status code
final LocalAuthentication _localAuth = LocalAuthentication();
bool _handlingAuth422 = false; // prevents multiple dialogs/auth prompts
File? _lastCapturedFile; // keep the same image for retry
// Smart feedback
FaceFeedback _feedback = FaceFeedback(
type: FaceHintType.noFace,
@@ -252,6 +260,49 @@ class _OvalCameraCapturePageState extends State<OvalCameraCapturePage> {
}
}
Future<bool?> _showLocalAuthDialog() {
return showDialog<bool>(
context: context,
barrierDismissible: false,
builder:
(_) => AlertDialog(
title: const Text('فشل التحقق بالوجه', textAlign: TextAlign.center),
content: const Text(
'لم يتم التعرف على الوجه.\n\nيرجى استخدام بصمة الإصبع أو رمز القفل (PIN/النمط) للمتابعة.',
textAlign: TextAlign.center,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('إلغاء'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('استخدام البصمة / رمز القفل'),
),
],
),
);
}
Future<bool> _authenticateLocally() async {
try {
final isSupported = await _localAuth.isDeviceSupported();
if (!isSupported) return false;
return await _localAuth.authenticate(
localizedReason: 'تأكيد هويتك لإكمال تسجيل الحضور.',
options: const AuthenticationOptions(
biometricOnly: false, // ✅ allows PIN/pattern fallback
stickyAuth: true,
useErrorDialogs: true,
),
);
} catch (_) {
return false;
}
}
Future<void> _stopImageStream() async {
if (!_isStreaming || _cameraController == null) return;
try {
@@ -408,7 +459,9 @@ class _OvalCameraCapturePageState extends State<OvalCameraCapturePage> {
final xFile = await _cameraController!.takePicture();
final file = File(xFile.path);
await widget.onCapture(file);
_lastCapturedFile = file;
await widget.onCapture(file, localAuth: false);
if (mounted) {
setState(() {
@@ -423,8 +476,13 @@ class _OvalCameraCapturePageState extends State<OvalCameraCapturePage> {
});
}
} on ServerException catch (e) {
// Check if this is an "already logged in" error from the API
final msg = e.message.toLowerCase();
// If your ServerException has statusCode, prefer that:
if (e.statusCode == 422 || msg.contains('face verification failed')) {
await _handleFaceVerificationFailed422(e);
return;
}
if (msg.contains('already logged in') ||
msg.contains('مسجل دخول بالفعل')) {
// Stop camera and go back with a dialog
@@ -545,6 +603,86 @@ class _OvalCameraCapturePageState extends State<OvalCameraCapturePage> {
return null;
}
Future<void> _handleFaceVerificationFailed422(ServerException e) async {
if (!mounted) return;
if (_handlingAuth422) return;
_handlingAuth422 = true;
// stop everything so camera doesnt keep scanning
await _stopImageStream();
setState(() {
_isSubmitting = false;
_errorMessage = null;
_debugInfo = "Face verification failed (422) → Local Auth...";
});
final proceed = await _showLocalAuthDialog();
if (proceed != true) {
_handlingAuth422 = false;
_stopCameraCompletely();
if (!mounted) return;
// Go back to attendance + show message there
Navigator.of(context).pop(false);
// If you prefer to show inside this screen before pop:
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('تم الإلغاء.')));
return;
}
final ok = await _authenticateLocally();
if (!ok) {
_handlingAuth422 = false;
_stopCameraCompletely();
if (!mounted) return;
// Return to attendance; attendance screen should show snack "failed fingerprint/pattern"
Navigator.of(context).pop("local_auth_failed");
return;
}
// Local auth success → retry SAME image with localAuth=true
final file = _lastCapturedFile;
if (file == null) {
_handlingAuth422 = false;
_stopCameraCompletely();
if (!mounted) return;
Navigator.of(context).pop("retry_missing_file");
return;
}
setState(() {
_isSubmitting = true;
_debugInfo = "Local auth success → retrying with localAuth=true...";
});
try {
await widget.onCapture(file, localAuth: true);
if (!mounted) return;
setState(() {
_isSuccess = true;
_isSubmitting = false;
});
Future.delayed(const Duration(seconds: 1), () {
if (mounted) Navigator.of(context).pop(true);
});
} on ServerException catch (e2) {
_handlingAuth422 = false;
_stopCameraCompletely();
if (!mounted) return;
// Retry failed → go back to attendance
Navigator.of(context).pop("retry_failed");
} catch (_) {
_handlingAuth422 = false;
_stopCameraCompletely();
if (!mounted) return;
Navigator.of(context).pop("retry_failed");
}
}
Uint8List _convertYUV420ToNV21(CameraImage image) {
final int width = image.width;
final int height = image.height;