chnages has been made
This commit is contained in:
@@ -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 doesn’t 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;
|
||||
|
||||
Reference in New Issue
Block a user