diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 081231adc77b41a5213559fbb19a1d0ec17bb1bd..38e58e0937c862b83a324e36bcb0dae282fe9c5b 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 3a3bc90e641aac6107218eeff42699e13c8ed5d7..3cd081b063fedb81d7a5ebb7c980b23092683c45 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,6 @@ + + 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 phone 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(); diff --git a/lib/views/home.dart b/lib/views/home.dart index 052259bf95de27add2fe2686cdd8fb2aa33c1b81..4a214220cdb62c25cd91a1482d2050a731502d34 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 0000000000000000000000000000000000000000..338bddfc3be3aeedeb4fa2c8bcd82cce3d8cc81f --- /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 diff --git a/pubspec.lock b/pubspec.lock index 5339b02bf034bb47be8ea3ad7f688476b8185a71..6fb9382cae2aeca36535a17853fa737594105eab 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" cross_file: dependency: transitive description: @@ -118,6 +126,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_nfc_kit: + dependency: "direct main" + description: + name: flutter_nfc_kit + sha256: "3cf589592373f1d0b0bd9583532368bb85e7cd76ae014a2b67a5ab2d68ae9450" + url: "https://pub.dev" + source: hosted + version: "3.6.1" flutter_test: dependency: "direct dev" description: flutter @@ -160,6 +176,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.18.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -192,6 +216,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -232,6 +264,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.2.3" + ndef: + dependency: transitive + description: + name: ndef + sha256: "198ba3798e80cea381648569d84059dbba64cd140079fb7b0d9c3f1e0f5973f3" + url: "https://pub.dev" + source: hosted + version: "0.4.0" nested: dependency: transitive description: @@ -405,6 +445,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" url_launcher_linux: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b9db18d100a382e761f560d82a854425ce7f3e58..dda218b5a23b5288411bf63ea5ec0a3e3a06aa69 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,7 +45,7 @@ dependencies: path_provider: ^2.1.1 share_plus: ^10.0.0 # nfc_manager: ^3.3.0 - + flutter_nfc_kit: ^3.6.1 dev_dependencies: flutter_test: