From 8f8838b72fdbd3eb5ad11233bbd9af446c47c211 Mon Sep 17 00:00:00 2001 From: amine-aitmokhtar Date: Sun, 11 Jan 2026 10:34:03 +0100 Subject: [PATCH 1/4] build(nfc): setup dependencies and android configuration - Add flutter_nfc_kit (v3.6.1) dependency - Update Android minSdk to 26 (required by nfc_kit) - Add NFC permissions and hardware feature in AndroidManifest --- android/app/build.gradle.kts | 2 +- android/app/src/main/AndroidManifest.xml | 2 + pubspec.lock | 48 ++++++++++++++++++++++++ pubspec.yaml | 2 +- 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 081231a..38e58e0 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -24,7 +24,7 @@ android { applicationId = "univlh.m2iwocs.flutter.checkmein.checkmein" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion + minSdk = 26 targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3a3bc90..3cd081b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,6 @@ + + Date: Tue, 20 Jan 2026 20:15:05 +0100 Subject: [PATCH 2/4] feat(nfc): implement badge scanning logic in AttendanceService -Add scanStudentBadge method using flutter_nfc_kit - Map NFC Tag ID to 'Student.leoId' and use placeholders for missing name data (to be resolved by backend). - Integrate with existing pushStudentQueue to support offline mode. - Ensure NFC session cleanup with finish() in finally block to prevent hardware locking. --- lib/services/attendance_service.dart | 59 +++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/services/attendance_service.dart b/lib/services/attendance_service.dart index 0f3a9ce..89af299 100644 --- a/lib/services/attendance_service.dart +++ b/lib/services/attendance_service.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:collection'; import 'dart:convert'; import 'package:flutter/foundation.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; import 'package:http/http.dart' as http; import 'package:rxdart/rxdart.dart'; import '../models/student.dart'; @@ -126,8 +127,64 @@ class AttendanceService { } } + // ========================================== + // NFC Handling + // ========================================== + + /// Scans a card, creates a temporary Student object, and adds it to the queue. + /// Returns the scanned Badge ID. + Future scanStudentBadge() async { + // Check Hardware Availability + var availability = await FlutterNfcKit.nfcAvailability; + if (availability != NFCAvailability.available) { + throw Exception("NFC not available (State: $availability)"); + } + + try { + // Poll for tag (10s timeout) + // We explicitly look for ISO14443A (Mifare / Student Cards) + NFCTag tag = await FlutterNfcKit.poll( + timeout: const Duration(seconds: 10), + iosAlertMessage: "Hold iPhone near the student card", + readIso14443A: true, + ); + + // Extract Badge ID + String badgeId = tag.id; + if (kDebugMode) { + print("NFC Service: Badge detected -> $badgeId"); + } + + // Create Student Object + // Note: Filling required fields with placeholders. + // The Google Sheet logic will map the ID to the real Name. + Student scannedStudent = Student( + firstName: "Unknown", + lastName: "Pending Sync", + studentId: "N/A", + leoId: badgeId, // Real Data from NFC + entryTime: DateTime.now(), //Real Scan Time + ); + + // Add to local queue (using existing logic) + pushStudentQueue(scannedStudent); + + return badgeId; + + } catch (e) { + if (kDebugMode) { + print("Scan Error: $e"); + } + rethrow; // Pass error to UI + } finally { + + // Clean up must always finish the session to release hardware + await FlutterNfcKit.finish(); + } + } + void dispose() { _flushTimer?.cancel(); _queueLengthSubject.close(); } -} +} \ No newline at end of file -- GitLab From edebb6c611dc1eadfd76e9ede3cb0ce99643240f Mon Sep 17 00:00:00 2001 From: amine-aitmokhtar Date: Tue, 20 Jan 2026 20:33:16 +0100 Subject: [PATCH 3/4] feat(ui): implement NFC scanning screen and integrate navigation - Create 'NfcScanScreen' to handle NFC interactions and user feedback. - Connect the main Home button to the new NFC screen. - Remove deprecated simulation logic from Home view. --- lib/views/home.dart | 50 ++++--------- lib/views/nfc_scan_screen.dart | 128 +++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 38 deletions(-) create mode 100644 lib/views/nfc_scan_screen.dart diff --git a/lib/views/home.dart b/lib/views/home.dart index 052259b..4a21422 100644 --- a/lib/views/home.dart +++ b/lib/views/home.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import '../utils/locator.dart'; -import '../models/student.dart'; import '../services/attendance_service.dart'; import '../components/sync_status_indicator.dart'; import 'manual_entry.dart'; import 'qr_scanner_screen.dart'; +import 'nfc_scan_screen.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -17,39 +17,7 @@ class _HomePageState extends State { final _service = getIt(); final TextEditingController _urlController = TextEditingController(); - // simulates the NFC interaction and adds to queue - void _simulateNfcScan() { - if (_urlController.text.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Configurez l'URL d'abord !")), - ); - return; - } - - // Create Data (Scan Time is fixed here) - final newStudent = Student( - firstName: "Jean", - lastName: "Dupont", - //studentId: "E${DateTime.now().millisecondsSinceEpoch}", - studentId: "gm213204", - leoId: "LEO123456", - entryTime: DateTime.now(), // Precise Scan Time - ); - - // Add to Service Queue immediately - _service.pushStudentQueue(newStudent); - - // Instant Visual Feedback - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("Scan enregistré : ${newStudent.firstName}"), - backgroundColor: Colors.blue, - duration: const Duration(seconds: 1), - ), - ); - } - - // Launches the QR Scanner screen + // Launches the QR Scanner screen for configuration Future _scanQrCode() async { // Push to the scanner page and await the result (the scanned URL) final String? scannedUrl = await Navigator.push( @@ -102,7 +70,7 @@ class _HomePageState extends State { const Spacer(), - // NFC Button + // NFC Scan Button SizedBox( width: 200, height: 200, @@ -111,12 +79,18 @@ class _HomePageState extends State { shape: const CircleBorder(), backgroundColor: Colors.blueAccent, ), - onPressed: _simulateNfcScan, + onPressed: () { + // Navigate to the real NFC scanning screen + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const NfcScanScreen()), + ); + }, child: const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.nfc, size: 50, color: Colors.white), - Text("SCAN", style: TextStyle(color: Colors.white)), + Text("SCAN NFC", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)), ], ), ), @@ -139,4 +113,4 @@ class _HomePageState extends State { ), ); } -} +} \ No newline at end of file diff --git a/lib/views/nfc_scan_screen.dart b/lib/views/nfc_scan_screen.dart new file mode 100644 index 0000000..338bddf --- /dev/null +++ b/lib/views/nfc_scan_screen.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import '../services/attendance_service.dart'; +import '../utils/locator.dart'; + +class NfcScanScreen extends StatefulWidget { + const NfcScanScreen({super.key}); + + @override + State createState() => _NfcScanScreenState(); +} + +class _NfcScanScreenState extends State { + // Retrieve the singleton instance via Service Locator + final AttendanceService _service = getIt(); + + bool _isScanning = false; + String _statusMessage = "Ready to scan"; + Color _statusColor = Colors.grey; + + Future _startNfcScan() async { + // Update UI to show scanning state + setState(() { + _isScanning = true; + _statusMessage = "Hold card near the phone..."; + _statusColor = Colors.blue; + }); + + try { + // Trigger the NFC scan logic defined in the service layer + String badgeId = await _service.scanStudentBadge(); + + // Handle successful scan + if (mounted) { + setState(() { + _statusMessage = "✅ Success!\nBadge: $badgeId"; + _statusColor = Colors.green; + }); + + // Provide visual feedback + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Badge $badgeId added to upload queue"), + backgroundColor: Colors.green, + ), + ); + + // Close screen automatically after a short delay + await Future.delayed(const Duration(milliseconds: 1500)); + if (mounted) { + Navigator.pop(context); + } + } + } catch (e) { + // Handle errors (hardware unavailable, timeout, etc.) + if (mounted) { + setState(() { + // Clean up the exception message for display + _statusMessage = "Info:\n${e.toString().replaceAll('Exception: ', '')}"; + _statusColor = Colors.orange; + }); + } + } finally { + // Reset scanning state (run in finally to ensure execution) + if (mounted) { + setState(() { + _isScanning = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text("NFC Scan")), + body: Center( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Visual Indicator + Container( + padding: const EdgeInsets.all(30), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: _statusColor, width: 4), + color: _statusColor.withOpacity(0.1), + ), + child: Icon( + Icons.nfc, + size: 80, + color: _statusColor, + ), + ), + const SizedBox(height: 40), + + // Status Text + Text( + _statusMessage, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: _statusColor, + ), + ), + const SizedBox(height: 50), + + // Action Button + if (!_isScanning) + ElevatedButton.icon( + onPressed: _startNfcScan, + icon: const Icon(Icons.wifi_tethering), + label: const Text("START SCAN"), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 15), + ), + ) + else + const CircularProgressIndicator(), + ], + ), + ), + ), + ); + } +} \ No newline at end of file -- GitLab From 06be7a9603a1768704a034cc56f1627070f16b64 Mon Sep 17 00:00:00 2001 From: Massiles Ghernaout <749-gm213204@users.noreply.www-apps.univ-lehavre.fr> Date: Fri, 30 Jan 2026 16:13:04 +0000 Subject: [PATCH 4/4] Update attendance_service.dart --- lib/services/attendance_service.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/attendance_service.dart b/lib/services/attendance_service.dart index 89af299..48343d0 100644 --- a/lib/services/attendance_service.dart +++ b/lib/services/attendance_service.dart @@ -145,7 +145,7 @@ class AttendanceService { // We explicitly look for ISO14443A (Mifare / Student Cards) NFCTag tag = await FlutterNfcKit.poll( timeout: const Duration(seconds: 10), - iosAlertMessage: "Hold iPhone near the student card", + iosAlertMessage: "Hold phone near the student card", readIso14443A: true, ); @@ -187,4 +187,4 @@ class AttendanceService { _flushTimer?.cancel(); _queueLengthSubject.close(); } -} \ No newline at end of file +} -- GitLab