From 2df279aedd1225cde5d8a21ea467d58d3f4c2236 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:48:02 +0300 Subject: [PATCH] nfc package update and location services improvements --- .../dialoge/nfc_reader_sheet.dart | 389 ++++++++---- .../swipe_module/utils/location_utils.dart | 164 +++-- .../utils/swipe_general_utils.dart | 577 +++++++++++++----- lib/views/widgets/qr/scan_qr.dart | 58 +- pubspec.lock | 40 ++ pubspec.yaml | 2 + 6 files changed, 861 insertions(+), 369 deletions(-) diff --git a/lib/new_views/swipe_module/dialoge/nfc_reader_sheet.dart b/lib/new_views/swipe_module/dialoge/nfc_reader_sheet.dart index 7eecd8e2..f0214184 100644 --- a/lib/new_views/swipe_module/dialoge/nfc_reader_sheet.dart +++ b/lib/new_views/swipe_module/dialoge/nfc_reader_sheet.dart @@ -1,26 +1,26 @@ import 'dart:async'; -import 'dart:io'; - +import 'dart:developer'; import 'package:flutter/material.dart'; -import 'package:nfc_manager/nfc_manager.dart'; -import 'package:test_sa/dashboard_latest/dashboard_view.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; -import 'package:test_sa/views/app_style/sizing.dart'; void showNfcReader(BuildContext context, {Function(String?)? onNcfScan}) { showModalBottomSheet( context: context, enableDrag: false, isDismissible: false, + barrierColor: Colors.black.withOpacity(0.5), shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + ), ), backgroundColor: Colors.white, builder: (context) { - return NfcLayout( - onNcfScan: onNcfScan, - ); + return NfcLayout(onNcfScan: onNcfScan); }, ); } @@ -38,123 +38,105 @@ class _NfcLayoutState extends State { bool _reading = false; Widget? mainWidget; String? nfcId; + bool _nfcScanning = false; @override void initState() { super.initState(); + _startNfcScan(); + } - NfcManager.instance.startSession( - onDiscovered: (NfcTag tag) async { - String? identifier; - - try { - final dynamic tagDynamic = tag; - final Map tagData = Map.from(tagDynamic.data as Map); - - if (Platform.isAndroid) { - if (tagData.containsKey('nfca')) { - final nfcaData = tagData['nfca'] as Map; - if (nfcaData.containsKey('identifier')) { - final List idBytes = List.from(nfcaData['identifier'] as List); - identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); - } - } - } else { - if (tagData.containsKey('mifare')) { - final mifareData = tagData['mifare'] as Map; - if (mifareData.containsKey('identifier')) { - final List idBytes = List.from(mifareData['identifier'] as List); - identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); - } - } - } - } catch (e) { - print('Error reading NFC tag: $e'); - } - - nfcId = identifier; + Future _startNfcScan() async { + if (_nfcScanning) return; + _nfcScanning = true; + try { + NFCTag tag = await FlutterNfcKit.poll( + timeout: const Duration(seconds: 20), + ); + nfcId = tag.id.toUpperCase(); setState(() { _reading = true; - mainWidget = doneNfc(); + // mainWidget = doneNfc(); }); + await Future.delayed(const Duration(seconds: 1)); + await FlutterNfcKit.finish(); + Navigator.pop(context); + widget.onNcfScan?.call(nfcId); + log("NFC Tag ID: $nfcId"); + } catch (e) { + if (e.toString().contains("408")) { + debugPrint("NFC scan timeout"); + } else if (e.toString().contains("cancelled")) { + debugPrint("NFC cancelled by user"); + } else { + debugPrint("NFC error: $e"); + "Failed to read NFC card".showToast; + } - Future.delayed(const Duration(seconds: 1), () { - NfcManager.instance.stopSession(); - Navigator.pop(context); - widget.onNcfScan!(nfcId!); - }); - }, - pollingOptions: {NfcPollingOption.iso14443}, - ).catchError((err) { - print(err); - }); + try { + await FlutterNfcKit.finish(); + } catch (_) {} + + Navigator.pop(context); + } finally { + _nfcScanning = false; + } } @override Widget build(BuildContext context) { (mainWidget == null && !_reading) ? mainWidget = scanNfc() : mainWidget = doneNfc(); - return AnimatedSwitcher(duration: Duration(milliseconds: 500), child: mainWidget); + + return AnimatedSwitcher(duration: const Duration(milliseconds: 500), child: mainWidget); } Widget scanNfc() { return Container( color: AppColor.background(context), - key: ValueKey(1), + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom + MediaQuery.of(context).padding.bottom), + key: const ValueKey(1), child: Column( mainAxisSize: MainAxisSize.min, children: [ - SizedBox( - height: 30, - ), - Text( + const SizedBox(height: 30), + Text( "Ready To Scan", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 24, - color:AppColor.headingTextColor(context) + color: AppColor.headingTextColor(context), ), ), - SizedBox( - height: 30, - ), + const SizedBox(height: 30), Image.asset( "assets/images/ic_nfc.png", height: MediaQuery.of(context).size.width / 3, color: AppColor.iconColor(context), width: double.infinity, ), - const SizedBox( - height: 30, - ), + const SizedBox(height: 30), const Text( "Approach an NFC Tag", - style: TextStyle( - fontSize: 18, - ), - ), - const SizedBox( - height: 30, + style: TextStyle(fontSize: 18), ), + const SizedBox(height: 30), ButtonTheme( minWidth: MediaQuery.of(context).size.width / 1.2, height: 45.0, buttonColor: Colors.grey[300], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), child: TextButton( - onPressed: () { - NfcManager.instance.stopSession(); + onPressed: () async { + try { + await FlutterNfcKit.finish(); + } catch (_) {} Navigator.pop(context); }, - // elevation: 0, child: const Text("CANCEL"), ), ), - SizedBox( - height: 30, - ), + const SizedBox(height: 30), ], ), ); @@ -163,66 +145,253 @@ class _NfcLayoutState extends State { Widget doneNfc() { return Container( color: AppColor.background(context), - key: ValueKey(2), + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom + MediaQuery.of(context).padding.bottom), + key: const ValueKey(2), child: Column( mainAxisSize: MainAxisSize.min, children: [ - SizedBox( - height: 30, - ), + const SizedBox(height: 30), Text( "Successfully Scanned", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 24, - color:AppColor.headingTextColor(context) + color: AppColor.headingTextColor(context), ), ), - SizedBox( - height: 30, - ), + const SizedBox(height: 30), Image.asset( - // "assets/icons/nfc/ic_done.png", "assets/images/ic_done.png", height: MediaQuery.of(context).size.width / 3, width: double.infinity, color: AppColor.iconColor(context), ), - SizedBox( - height: 30, - ), - Text( + const SizedBox(height: 30), + const Text( "Approach an NFC Tag", - style: TextStyle( - fontSize: 18, - ), - ), - SizedBox( - height: 30, + style: TextStyle(fontSize: 18), ), + const SizedBox(height: 30), ButtonTheme( minWidth: MediaQuery.of(context).size.width / 1.2, height: 45.0, buttonColor: Colors.grey[300], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), child: TextButton( - // onPressed: () { - // _stream?.cancel(); - // widget.onNcfScan(nfcId); - // Navigator.pop(context); - // }, onPressed: null, - // elevation: 0, - child: Text("DONE",style: TextStyle(color: context.isDark?AppColor.primary10:null),), + child: Text( + "DONE", + style: TextStyle( + color: context.isDark ? AppColor.primary10 : null, + ), + ), ), ), - SizedBox( - height: 30, - ), + const SizedBox(height: 30), ], ), ); } } + +// class NfcLayout extends StatefulWidget { +// final Function(String? nfcId)? onNcfScan; +// +// const NfcLayout({super.key, required this.onNcfScan}); +// +// @override +// _NfcLayoutState createState() => _NfcLayoutState(); +// } +// +// class _NfcLayoutState extends State { +// bool _reading = false; +// Widget? mainWidget; +// String? nfcId; +// +// @override +// void initState() { +// super.initState(); +// +// NfcManager.instance.startSession( +// onDiscovered: (NfcTag tag) async { +// String? identifier; +// +// try { +// final dynamic tagDynamic = tag; +// final Map tagData = Map.from(tagDynamic.data as Map); +// +// if (Platform.isAndroid) { +// if (tagData.containsKey('nfca')) { +// final nfcaData = tagData['nfca'] as Map; +// if (nfcaData.containsKey('identifier')) { +// final List idBytes = List.from(nfcaData['identifier'] as List); +// identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); +// } +// } +// } else { +// if (tagData.containsKey('mifare')) { +// final mifareData = tagData['mifare'] as Map; +// if (mifareData.containsKey('identifier')) { +// final List idBytes = List.from(mifareData['identifier'] as List); +// identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); +// } +// } +// } +// } catch (e) { +// print('Error reading NFC tag: $e'); +// } +// +// nfcId = identifier; +// +// setState(() { +// _reading = true; +// mainWidget = doneNfc(); +// }); +// +// Future.delayed(const Duration(seconds: 1), () { +// NfcManager.instance.stopSession(); +// Navigator.pop(context); +// widget.onNcfScan!(nfcId!); +// }); +// }, +// pollingOptions: {NfcPollingOption.iso14443}, +// ).catchError((err) { +// print(err); +// }); +// } +// +// @override +// Widget build(BuildContext context) { +// (mainWidget == null && !_reading) ? mainWidget = scanNfc() : mainWidget = doneNfc(); +// return AnimatedSwitcher(duration: Duration(milliseconds: 500), child: mainWidget); +// } +// +// Widget scanNfc() { +// return Container( +// color: AppColor.background(context), +// key: const ValueKey(1), +// child: Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// const SizedBox( +// height: 30, +// ), +// Text( +// "Ready To Scan", +// style: TextStyle( +// fontWeight: FontWeight.bold, +// fontSize: 24, +// color:AppColor.headingTextColor(context) +// ), +// ), +// const SizedBox( +// height: 30, +// ), +// Image.asset( +// "assets/images/ic_nfc.png", +// height: MediaQuery.of(context).size.width / 3, +// color: AppColor.iconColor(context), +// width: double.infinity, +// ), +// const SizedBox( +// height: 30, +// ), +// const Text( +// "Approach an NFC Tag", +// style: TextStyle( +// fontSize: 18, +// ), +// ), +// const SizedBox( +// height: 30, +// ), +// ButtonTheme( +// minWidth: MediaQuery.of(context).size.width / 1.2, +// height: 45.0, +// buttonColor: Colors.grey[300], +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(6), +// ), +// child: TextButton( +// onPressed: () { +// NfcManager.instance.stopSession(); +// Navigator.pop(context); +// }, +// // elevation: 0, +// child: const Text("CANCEL"), +// ), +// ), +// const SizedBox( +// height: 30, +// ), +// ], +// ), +// ); +// } +// +// Widget doneNfc() { +// return Container( +// color: AppColor.background(context), +// key: ValueKey(2), +// child: Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// const SizedBox( +// height: 30, +// ), +// Text( +// "Successfully Scanned", +// style: TextStyle( +// fontWeight: FontWeight.bold, +// fontSize: 24, +// color:AppColor.headingTextColor(context) +// ), +// ), +// const SizedBox( +// height: 30, +// ), +// Image.asset( +// // "assets/icons/nfc/ic_done.png", +// "assets/images/ic_done.png", +// height: MediaQuery.of(context).size.width / 3, +// width: double.infinity, +// color: AppColor.iconColor(context), +// ), +// const SizedBox( +// height: 30, +// ), +// const Text( +// "Approach an NFC Tag", +// style: TextStyle( +// fontSize: 18, +// ), +// ), +// const SizedBox( +// height: 30, +// ), +// ButtonTheme( +// minWidth: MediaQuery.of(context).size.width / 1.2, +// height: 45.0, +// buttonColor: Colors.grey[300], +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(6), +// ), +// child: TextButton( +// // onPressed: () { +// // _stream?.cancel(); +// // widget.onNcfScan(nfcId); +// // Navigator.pop(context); +// // }, +// onPressed: null, +// // elevation: 0, +// child: Text("DONE",style: TextStyle(color: context.isDark?AppColor.primary10:null),), +// ), +// ), +// const SizedBox( +// height: 30, +// ), +// ], +// ), +// ); +// } +// } diff --git a/lib/new_views/swipe_module/utils/location_utils.dart b/lib/new_views/swipe_module/utils/location_utils.dart index b3627cc3..82650174 100644 --- a/lib/new_views/swipe_module/utils/location_utils.dart +++ b/lib/new_views/swipe_module/utils/location_utils.dart @@ -20,6 +20,21 @@ class LocationUtilities { Geolocator.isLocationServiceEnabled().then((value) => callback(value)); } + static Future isEnabledAsync() async { + return await Geolocator.isLocationServiceEnabled(); + } + + static Future havePermissionAsync() async { + LocationPermission permission = await Geolocator.checkPermission(); + + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + } + + return permission == LocationPermission.always || + permission == LocationPermission.whileInUse; + } + static bool _listeningSettingChange = true; static void listenGPS({bool change = true, Function(bool)? onChange}) async { @@ -44,61 +59,100 @@ class LocationUtilities { completion(isGranted); }); } + static Future getCurrentLocation( + Function(Position position, bool isMocked) callback, + Function(String error) errorCallBack, + BuildContext context, + ) async { + debugPrint("📍 Fetching current location..."); - static void getCurrentLocation(Function(Position position, bool isMocked) callback, Function errorCallBack, BuildContext context) { - Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) { - bool isMocked = position.isMocked; - callback(position, isMocked); - }).catchError((err) { - errorCallBack(); - }); - // return; - // Permission.location.isGranted.then((isGranted) { - // if (!isGranted) { - // Permission.location.request().then((granted) { - // print("granted:$granted"); - // if (granted == PermissionStatus.granted) { - // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) { - // bool isMocked = position.isMocked; - // callback(position, isMocked); - // }).catchError((err) { - // print("getCurrentPositionError:$err"); - // errorCallBack(); - // }); - // } else { - // errorCallBack(); - // } - // }); - // } else { - // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) { - // bool isMocked = position.isMocked; - // callback(position, isMocked); - // }).catchError((err) { - // print("getCurrentPositionError:$err"); - // errorCallBack(); - // }); - // } - // }); - // - // // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) { - // // bool isMocked = position.isMocked; - // // callback(position, isMocked); - // // }).catchError((err) { - // // print("getCurrentPositionError:$err"); - // // errorCallBack(); - // // }); - // - // // locationFun((granted) { - // // if (granted) { - // // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((value) { - // // done(value); - // // }).catchError((err) { - // // print("getCurrentPositionError:$err"); - // // errorCallBack(); - // // }); - // // } else { - // // // AppPermissions - // // } - // // }, context); + try { + final position = await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.high, + timeLimit: const Duration(seconds: 10), + ); + debugPrint("✅ Location: ${position.latitude}, ${position.longitude}"); + + callback(position, position.isMocked); + } catch (e) { + debugPrint("❌ Primary location failed: $e"); + try { + final lastPosition = await Geolocator.getLastKnownPosition(); + if (lastPosition != null) { + debugPrint("⚠️ Using last known location"); + callback(lastPosition, lastPosition.isMocked); + return; + } + } catch (fallbackError) { + debugPrint("❌ Fallback failed: $fallbackError"); + } + String message = "Unable to determine your location"; + final error = e.toString().toLowerCase(); + if (error.contains("timeout")) { + message = "Location request timed out. Please try again."; + } else if (error.contains("denied")) { + message = "Location permission denied."; + } else if (error.contains("disabled")) { + message = "Location services are disabled."; + } + errorCallBack(message); + } } + + // static void getCurrentLocation(Function(Position position, bool isMocked) callback, Function errorCallBack, BuildContext context) { + // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) { + // bool isMocked = position.isMocked; + // callback(position, isMocked); + // }).catchError((err) { + // errorCallBack(); + // }); + // // return; + // // Permission.location.isGranted.then((isGranted) { + // // if (!isGranted) { + // // Permission.location.request().then((granted) { + // // print("granted:$granted"); + // // if (granted == PermissionStatus.granted) { + // // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) { + // // bool isMocked = position.isMocked; + // // callback(position, isMocked); + // // }).catchError((err) { + // // print("getCurrentPositionError:$err"); + // // errorCallBack(); + // // }); + // // } else { + // // errorCallBack(); + // // } + // // }); + // // } else { + // // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) { + // // bool isMocked = position.isMocked; + // // callback(position, isMocked); + // // }).catchError((err) { + // // print("getCurrentPositionError:$err"); + // // errorCallBack(); + // // }); + // // } + // // }); + // // + // // // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) { + // // // bool isMocked = position.isMocked; + // // // callback(position, isMocked); + // // // }).catchError((err) { + // // // print("getCurrentPositionError:$err"); + // // // errorCallBack(); + // // // }); + // // + // // // locationFun((granted) { + // // // if (granted) { + // // // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((value) { + // // // done(value); + // // // }).catchError((err) { + // // // print("getCurrentPositionError:$err"); + // // // errorCallBack(); + // // // }); + // // // } else { + // // // // AppPermissions + // // // } + // // // }, context); + // } } diff --git a/lib/new_views/swipe_module/utils/swipe_general_utils.dart b/lib/new_views/swipe_module/utils/swipe_general_utils.dart index ded2daa6..48fb6c8e 100644 --- a/lib/new_views/swipe_module/utils/swipe_general_utils.dart +++ b/lib/new_views/swipe_module/utils/swipe_general_utils.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; import 'package:geolocator/geolocator.dart'; import 'package:google_api_availability/google_api_availability.dart'; import 'package:huawei_location/huawei_location.dart'; @@ -16,6 +17,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:test_sa/controllers/providers/api/user_provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/main.dart'; @@ -23,6 +25,8 @@ import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; import 'package:test_sa/new_views/swipe_module/dialoge/confirm_dialog.dart'; import 'package:test_sa/new_views/swipe_module/dialoge/nfc_reader_sheet.dart'; + +// import 'package:test_sa/new_views/swipe_module/dialoge/nfc_reader_sheet.dart'; import 'package:test_sa/new_views/swipe_module/enums/swipe_type.dart'; import 'package:test_sa/new_views/swipe_module/models/swipe_model.dart'; import 'package:test_sa/new_views/swipe_module/swipe_success_view.dart'; @@ -38,7 +42,7 @@ class SwipeGeneralUtils { static bool get isLoading => _isLoadingVisible; - void markFakeAttendance(dynamic sourceName, String lat, String long, @required BuildContext context) async { + void markFakeAttendance(dynamic sourceName, String lat, String long, {required BuildContext context}) async { showLoading(context); try { hideLoading(navigatorKey.currentState!.overlay!.context); @@ -112,20 +116,33 @@ class SwipeGeneralUtils { } Widget attendanceTypeCard(String title, String icon, bool isEnabled, VoidCallback onPress, BuildContext context) { - return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: isEnabled ?context.isDark ? AppColor.neutral60 : Colors.white : AppColor.background(context), + color: isEnabled + ? context.isDark + ? AppColor.neutral60 + : Colors.white + : AppColor.background(context), borderRadius: BorderRadius.circular(18), - border: Border.all(color:context.isDark ? AppColor.neutral60 : Colors.white70 , width: 2), + border: Border.all(color: context.isDark ? AppColor.neutral60 : Colors.white70, width: 2), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - icon.toSvgAsset(color: isEnabled ?context.isDark ? AppColor.neutral30 : AppColor.neutral50 : Colors.grey.withOpacity(0.5)), - title.heading5(context).custom(color: isEnabled ?context.isDark ? AppColor.neutral30 : AppColor.neutral50 : Colors.grey.withOpacity(0.5)), + icon.toSvgAsset( + color: isEnabled + ? context.isDark + ? AppColor.neutral30 + : AppColor.neutral50 + : Colors.grey.withOpacity(0.5)), + title.heading5(context).custom( + color: isEnabled + ? context.isDark + ? AppColor.neutral30 + : AppColor.neutral50 + : Colors.grey.withOpacity(0.5)), ], ), ).onPress( @@ -173,67 +190,130 @@ class SwipeGeneralUtils { return (result[Permission.location] == PermissionStatus.granted || result[Permission.locationAlways] == PermissionStatus.granted); } - void checkHuaweiLocationPermission({required SwipeTypeEnum attendanceType, required BuildContext context}) async { - // Permission_Handler permissionHandler = PermissionHandler(); - LocationUtilities.isEnabled((bool isEnabled) async { - if (isEnabled) { - LocationUtilities.havePermission((bool permission) async { - if (permission) { - getHuaweiCurrentLocation(attendanceType: attendanceType, context: context); - } else { - bool has = await requestPermissions(); - if (has) { - getHuaweiCurrentLocation(attendanceType: attendanceType, context: context); - } else { - showDialog( - context: context, - builder: (BuildContext cxt) => ConfirmDialog( - message: "You need to give location permission to mark attendance", - onTap: () { - Navigator.pop(context); - }, - ), - ); - } - } - }); - } else { + Future checkHuaweiLocationPermission({ + required SwipeTypeEnum attendanceType, + required BuildContext context, + }) async { + try { + // Check if location service is enabled + final isEnabled = await LocationUtilities.isEnabledAsync(); + if (!isEnabled) { showDialog( context: context, - builder: (BuildContext cxt) => ConfirmDialog( + builder: (cxt) => ConfirmDialog( message: "You need to enable location services to mark attendance", onTap: () async { - Navigator.pop(context); + Navigator.pop(cxt); await Geolocator.openLocationSettings(); }, ), ); + return; } - }); - // if (await permissionHandler.hasLocationPermission()) { - // getHuaweiCurrentLocation(attendanceType); - // } else { - // bool has = await requestPermissions(); - // if (has) { - // getHuaweiCurrentLocation(attendanceType); - // } else { - // showDialog( - // context: context, - // builder: (BuildContext cxt) => ConfirmDialog( - // message: "You need to give location permission to mark attendance", - // onTap: () { - // Navigator.pop(context); - // }, - // ), - // ); - // } - // } + // Check permission + bool hasPermission = await LocationUtilities.havePermissionAsync(); + + // Request if not granted + if (!hasPermission) { + hasPermission = await requestPermissions(); + } + + if (!hasPermission) { + showDialog( + context: context, + builder: (cxt) => ConfirmDialog( + message: "You need to give location permission to mark attendance", + onTap: () { + Navigator.pop(cxt); + }, + ), + ); + return; + } + + // All good → get location + getHuaweiCurrentLocation( + attendanceType: attendanceType, + context: context, + ); + } catch (e) { + debugPrint("❌ Huawei location error: $e"); + + showDialog( + context: context, + builder: (cxt) => ConfirmDialog( + message: "Something went wrong. Please try again.", + onTap: () { + Navigator.pop(cxt); + }, + ), + ); + } } + // void checkHuaweiLocationPermission({required SwipeTypeEnum attendanceType, required BuildContext context}) async { + // // Permission_Handler permissionHandler = PermissionHandler(); + // LocationUtilities.isEnabled((bool isEnabled) async { + // if (isEnabled) { + // LocationUtilities.havePermission((bool permission) async { + // if (permission) { + // getHuaweiCurrentLocation(attendanceType: attendanceType, context: context); + // } else { + // bool has = await requestPermissions(); + // if (has) { + // getHuaweiCurrentLocation(attendanceType: attendanceType, context: context); + // } else { + // showDialog( + // context: context, + // builder: (BuildContext cxt) => ConfirmDialog( + // message: "You need to give location permission to mark attendance", + // onTap: () { + // Navigator.pop(context); + // }, + // ), + // ); + // } + // } + // }); + // } else { + // showDialog( + // context: context, + // builder: (BuildContext cxt) => ConfirmDialog( + // message: "You need to enable location services to mark attendance", + // onTap: () async { + // Navigator.pop(context); + // await Geolocator.openLocationSettings(); + // }, + // ), + // ); + // } + // }); + // + // // if (await permissionHandler.hasLocationPermission()) { + // // getHuaweiCurrentLocation(attendanceType); + // // } else { + // // bool has = await requestPermissions(); + // // if (has) { + // // getHuaweiCurrentLocation(attendanceType); + // // } else { + // // showDialog( + // // context: context, + // // builder: (BuildContext cxt) => ConfirmDialog( + // // message: "You need to give location permission to mark attendance", + // // onTap: () { + // // Navigator.pop(context); + // // }, + // // ), + // // ); + // // } + // // } + // } + void handleSwipeOperation({required SwipeTypeEnum swipeType, required double lat, required double long, required BuildContext context}) { switch (swipeType) { case SwipeTypeEnum.NFC: + log('showing nfc sheet'); handleNfcAttendance(latitude: lat, longitude: long, context: context); return; case SwipeTypeEnum.QR: @@ -253,61 +333,140 @@ class SwipeGeneralUtils { UserProvider userProvider = Provider.of(context, listen: false); String qrCodeValue = await Navigator.of(context).push( - MaterialPageRoute(builder: (_) => ScanQr()), + MaterialPageRoute(builder: (_) => const ScanQr()), ) as String; - if (qrCodeValue != null) { - showLoading(context); - try { - final swipeModel = Swipe( - swipeTypeValue: SwipeTypeEnum.QR.getIntFromSwipeTypeEnum(), - value: qrCodeValue, - latitude: latitude, - longitude: longitude, - ); + showLoading(context); + try { + final swipeModel = Swipe( + swipeTypeValue: SwipeTypeEnum.QR.getIntFromSwipeTypeEnum(), + value: qrCodeValue, + latitude: latitude, + longitude: longitude, + ); - await userProvider.makeSwipe(model: swipeModel).then((swipeResponse) { - if (swipeResponse.isSuccess == true) { - hideLoading(context); - Navigator.pushNamed(context, SwipeSuccessView.routeName); - } else { - hideLoading(context); - showDialog( - barrierDismissible: true, - context: context, - builder: (cxt) => ConfirmDialog( - message: swipeResponse.message ?? "", - onTap: () { - Navigator.pop(context); - }, - onCloseTap: () {}, - ), - ); - } - }); - } catch (ex) { - log('$ex'); - hideLoading(context); - //this need to confirm where it comes.. - // handleException(ex, context, null); - } + await userProvider.makeSwipe(model: swipeModel).then((swipeResponse) { + if (swipeResponse.isSuccess == true) { + hideLoading(context); + Navigator.pushNamed(context, SwipeSuccessView.routeName); + } else { + hideLoading(context); + showDialog( + barrierDismissible: true, + context: context, + builder: (cxt) => ConfirmDialog( + message: swipeResponse.message ?? "", + onTap: () { + Navigator.pop(context); + }, + onCloseTap: () {}, + ), + ); + } + }); + } catch (ex) { + log('$ex'); + hideLoading(context); + //this need to confirm where it comes.. + // handleException(ex, context, null); } } - Future handleNfcAttendance({double? latitude = 0, double? longitude = 0, required BuildContext context}) async { - // UserProvider _userProvider = Provider.of(context,listen:false); + // if (Platform.isAndroid) { + // // Android: show custom bottom sheet with your scan/done UI + // showNfcReader(context, onNcfScan: (String? nfcId) async { + // if (nfcId == null || nfcId.isEmpty) return; + // + // try { + // await _processNfcAttendance(nfcId, latitude, longitude, context); + // } finally { + // _nfcScanning = false; + // } + // }); + // } +// dart +// Add this field to the class (near the top, e.g. after the constructor) + bool _nfcScanning = false; + +// dart + Future handleNfcAttendance({ + double? latitude = 0, + double? longitude = 0, + required BuildContext context, + }) async { + // if (_nfcScanning) return; + // _nfcScanning = true; - if (Platform.isIOS) { - readNFc(onRead: (String nfcId) async { - await _processNfcAttendance(nfcId, latitude, longitude, context); - }); - } else { - showNfcReader(context, onNcfScan: (String? nfcId) async { - await _processNfcAttendance(nfcId ?? '', latitude, longitude, context); - }); + try { + var availability = await FlutterNfcKit.nfcAvailability; + if (availability != NFCAvailability.available) { + "NFC is not available on this device".showToast; + return; + } + + if (Platform.isAndroid) { + try { + showNfcReader(context, onNcfScan: (String? nfcId) async { + log('nfc id from sheet: $nfcId'); + + if (nfcId == null || nfcId.isEmpty) return; + try { + await _processNfcAttendance(nfcId, latitude, longitude, context); + } catch (e) { + // handle per-scan error if needed + } + }); + } catch (e) { + debugPrint('Error showing NFC reader sheet: $e'); + } + } else { + try { + NFCTag tag = await FlutterNfcKit.poll( + timeout: const Duration(seconds: 20), + iosAlertMessage: "Hold your card near the phone", + iosMultipleTagMessage: "Multiple cards detected. Please scan only one.", + ); + + String nfcId = tag.id.toUpperCase(); + + await FlutterNfcKit.finish(); + + await _processNfcAttendance(nfcId, latitude, longitude, context); + } catch (e) { + if (!e.toString().contains("cancelled")) { + "Failed to read NFC card".showToast; + + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar(content: Text("Failed to read NFC card")), + // ); + } + try { + await FlutterNfcKit.finish(); + } catch (_) {} + } + } + } catch (e) { + debugPrint("NFC error: $e"); + } finally { + _nfcScanning = false; } } + // Future handleNfcAttendance({double? latitude = 0, double? longitude = 0, required BuildContext context}) async { + // bool _nfcScanning = false; + // // UserProvider _userProvider = Provider.of(context,listen:false); + // + // if (Platform.isIOS) { + // readNFc(onRead: (String nfcId) async { + // await _processNfcAttendance(nfcId, latitude, longitude, context); + // }); + // } else { + // showNfcReader(context, onNcfScan: (String? nfcId) async { + // await _processNfcAttendance(nfcId ?? '', latitude, longitude, context); + // }); + // } + // } + Future _processNfcAttendance( String nfcId, double? latitude, @@ -316,7 +475,12 @@ class SwipeGeneralUtils { ) async { showLoading(context); try { - final swipeModel = Swipe(swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), value: nfcId, latitude: latitude, longitude: longitude); + final swipeModel = Swipe( + swipeTypeValue: SwipeTypeEnum.NFC.getIntFromSwipeTypeEnum(), + value: nfcId, + latitude: latitude, + longitude: longitude, + ); UserProvider userProvider = Provider.of(context, listen: false); final swipeResponse = await userProvider.makeSwipe(model: swipeModel); @@ -333,50 +497,127 @@ class SwipeGeneralUtils { } } - void handleSwipe({required SwipeTypeEnum swipeType, required bool isEnable, required BuildContext context}) async { - if (Platform.isAndroid && !(await isGoogleServicesAvailable())) { - checkHuaweiLocationPermission(attendanceType: swipeType, context: context); - } else { - LocationUtilities.isEnabled((bool isEnabled) { - if (isEnabled) { - LocationUtilities.havePermission((bool permission) { - if (permission) { - showLoading(context); - LocationUtilities.getCurrentLocation( - (Position position, bool isMocked) { - if (isMocked) { - hideLoading(context); - markFakeAttendance(swipeType.name, position.latitude.toString() ?? "", position.longitude.toString() ?? "", context); - } else { - hideLoading(context); - handleSwipeOperation(swipeType: swipeType, lat: position.latitude, long: position.longitude, context: context); - } - }, - () { - hideLoading(context); - confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); - }, - context, - ); - } else { - showInfoDialog( - message: "You need to give location permission to mark attendance", - onTap: () async { - await Geolocator.openAppSettings(); - }); - } - }); - } else { - showInfoDialog( - message: "You need to enable location services to mark attendance", - onTap: () async { - await Geolocator.openLocationSettings(); - }); - } - }); + Future handleSwipe({ + required SwipeTypeEnum swipeType, + required bool isEnable, + required BuildContext context, + }) async { + try { + if (Platform.isAndroid && !(await isGoogleServicesAvailable())) { + checkHuaweiLocationPermission( + attendanceType: swipeType, + context: context, + ); + return; + } + // Check if location is enabled + final isEnabled = await LocationUtilities.isEnabledAsync(); + if (!isEnabled) { + showInfoDialog( + message: "You need to enable location services to mark attendance", + onTap: () async => await Geolocator.openLocationSettings(), + ); + return; + } + + // Check permission + final hasPermission = await LocationUtilities.havePermissionAsync(); + if (!hasPermission) { + showInfoDialog( + message: "You need to give location permission to mark attendance", + onTap: () async => await Geolocator.openAppSettings(), + ); + return; + } + + // Show loader + showLoading(context); + + // Get location + await LocationUtilities.getCurrentLocation( + (Position position, bool isMocked) { + hideLoading(context); + + if (isMocked) { + markFakeAttendance( + swipeType.name, + position.latitude.toString(), + position.longitude.toString(), + context: context, + ); + } else { + handleSwipeOperation( + swipeType: swipeType, + lat: position.latitude, + long: position.longitude, + context: context, + ); + } + }, + (String error) { + hideLoading(context); + + confirmDialog( + context, + error.isNotEmpty ? error : "Unable to determine your location. Please try again.", + ); + }, + context, + ); + } catch (e) { + hideLoading(context); + confirmDialog( + context, + "Something went wrong. Please try again.", + ); + debugPrint("❌ handleSwipe error: $e"); } } + // void handleSwipe({required SwipeTypeEnum swipeType, required bool isEnable, required BuildContext context}) async { + // if (Platform.isAndroid && !(await isGoogleServicesAvailable())) { + // checkHuaweiLocationPermission(attendanceType: swipeType, context: context); + // } else { + // LocationUtilities.isEnabled((bool isEnabled) { + // if (isEnabled) { + // LocationUtilities.havePermission((bool permission) { + // if (permission) { + // showLoading(context); + // LocationUtilities.getCurrentLocation( + // (Position position, bool isMocked) { + // if (isMocked) { + // hideLoading(context); + // markFakeAttendance(swipeType.name, position.latitude.toString(), position.longitude.toString() ?? "", context: context); + // } else { + // hideLoading(context); + // handleSwipeOperation(swipeType: swipeType, lat: position.latitude, long: position.longitude, context: context); + // } + // }, + // () { + // hideLoading(context); + // confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working."); + // }, + // context, + // ); + // } else { + // showInfoDialog( + // message: "You need to give location permission to mark attendance", + // onTap: () async { + // await Geolocator.openAppSettings(); + // }); + // } + // }); + // } else { + // showInfoDialog( + // message: "You need to enable location services to mark attendance", + // onTap: () async { + // await Geolocator.openLocationSettings(); + // }); + // } + // }); + // } + // } + void showInfoDialog({required String message, VoidCallback? onTap}) { showDialog( context: navigatorKey.currentState!.overlay!.context, @@ -431,16 +672,15 @@ class SwipeGeneralUtils { ), backgroundColor: Theme.of(context).scaffoldBackgroundColor, clipBehavior: Clip.antiAliasWithSaveLayer, - builder: (BuildContext context) =>Padding( + builder: (BuildContext context) => Padding( padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom + - MediaQuery.of(context).padding.bottom, + bottom: MediaQuery.of(context).viewInsets.bottom + MediaQuery.of(context).padding.bottom, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - context.translation.markAttendance.heading4(context).custom(color:context.isDark?AppColor.white50: AppColor.white936), + context.translation.markAttendance.heading4(context).custom(color: context.isDark ? AppColor.white50 : AppColor.white936), 8.height, context.translation.selectMethodToMarkAttendance.bodyText2(context).custom(color: AppColor.neutral120), 12.height, @@ -456,39 +696,40 @@ class SwipeGeneralUtils { ); } +//older code.. void readNFc({Function(String)? onRead}) { NfcManager.instance.startSession( onDiscovered: (NfcTag tag) async { - String identifier = ''; - - try { - final dynamic tagDynamic = tag; - final Map tagData = Map.from(tagDynamic.data as Map); - - if (Platform.isAndroid) { - if (tagData.containsKey('nfca')) { - final nfcaData = tagData['nfca'] as Map; - if (nfcaData.containsKey('identifier')) { - final List idBytes = List.from(nfcaData['identifier'] as List); - identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); + String identifier = ''; + + try { + final dynamic tagDynamic = tag; + final Map tagData = Map.from(tagDynamic.data as Map); + + if (Platform.isAndroid) { + if (tagData.containsKey('nfca')) { + final nfcaData = tagData['nfca'] as Map; + if (nfcaData.containsKey('identifier')) { + final List idBytes = List.from(nfcaData['identifier'] as List); + identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); + } } - } - } else { - if (tagData.containsKey('mifare')) { - final mifareData = tagData['mifare'] as Map; - if (mifareData.containsKey('identifier')) { - final List idBytes = List.from(mifareData['identifier'] as List); - identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); + } else { + if (tagData.containsKey('mifare')) { + final mifareData = tagData['mifare'] as Map; + if (mifareData.containsKey('identifier')) { + final List idBytes = List.from(mifareData['identifier'] as List); + identifier = idBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); + } } } + } catch (e) { + print('Error reading NFC: $e'); } - } catch (e) { - print('Error reading NFC: $e'); - } - NfcManager.instance.stopSession(); - onRead!(identifier); - }, + NfcManager.instance.stopSession(); + onRead!(identifier); + }, pollingOptions: {NfcPollingOption.iso14443}, ).catchError((err) { print(err); diff --git a/lib/views/widgets/qr/scan_qr.dart b/lib/views/widgets/qr/scan_qr.dart index bbef7f70..def01183 100644 --- a/lib/views/widgets/qr/scan_qr.dart +++ b/lib/views/widgets/qr/scan_qr.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; import '../buttons/app_icon_button.dart'; @@ -15,10 +14,7 @@ class ScanQr extends StatefulWidget { class _ScanQrState extends State { // Barcode result; - // QRViewController? _controller; - - MobileScannerController? controller; - + QRViewController? _controller; bool _scanDone = false; final GlobalKey qrKey = GlobalKey(debugLabel: 'QR_scanner'); @@ -26,34 +22,19 @@ class _ScanQrState extends State { // is android, or resume the camera if the platform is iOS. @override - void initState() { - // TODO: implement initState - super.initState(); - controller = MobileScannerController( - detectionSpeed: DetectionSpeed.noDuplicates, - formats: [], - returnImage: false, - torchEnabled: false, - invertImage: false, - autoZoom: true, - ); + void reassemble() { + super.reassemble(); + if (Platform.isAndroid) { + _controller?.pauseCamera(); + } else if (Platform.isIOS) { + _controller?.resumeCamera(); + } } - // - // @override - // void reassemble() { - // super.reassemble(); - // if (Platform.isAndroid) { - // controller?.pauseCamera(); - // } else if (Platform.isIOS) { - // _controller?.resumeCamera(); - // } - // } - @override void dispose() { super.dispose(); - // _controller?.dispose(); + _controller?.dispose(); } @override @@ -61,15 +42,20 @@ class _ScanQrState extends State { return Scaffold( body: Stack( children: [ - MobileScanner( - tapToFocus: true, - controller: controller, - onDetect: (result) { - if (!_scanDone) { - _scanDone = true; - Navigator.of(context).pop(result.barcodes.first.rawValue); - } + QRView( + key: qrKey, + onQRViewCreated: (QRViewController controller) { + setState(() { + _controller = controller; + }); + controller.scannedDataStream.listen((scanData) { + if (!_scanDone) { + _scanDone = true; + Navigator.of(context).pop(scanData.code); + } + }); }, + overlay: QrScannerOverlayShape(borderColor: Colors.red, borderRadius: 10, borderLength: 30, borderWidth: 10, cutOutSize: 280), ), SafeArea( child: Padding( diff --git a/pubspec.lock b/pubspec.lock index 50ce5e15..4a74d349 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -217,6 +217,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: @@ -532,6 +540,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_nfc_kit: + dependency: "direct main" + description: + name: flutter_nfc_kit + sha256: a2b324785173930f1b5afac8b190637d612593b994ecfd0a5334b72a841b9298 + url: "https://pub.dev" + source: hosted + version: "3.6.2" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -1030,6 +1046,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + mobile_scanner: + dependency: "direct main" + description: + name: mobile_scanner + sha256: c92c26bf2231695b6d3477c8dcf435f51e28f87b1745966b1fe4c47a286171ce + url: "https://pub.dev" + source: hosted + version: "7.2.0" native_toolchain_c: dependency: transitive description: @@ -1038,6 +1062,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.17.4" + ndef: + dependency: transitive + description: + name: ndef + sha256: "198ba3798e80cea381648569d84059dbba64cd140079fb7b0d9c3f1e0f5973f3" + url: "https://pub.dev" + source: hosted + version: "0.4.0" ndef_record: dependency: transitive description: @@ -1747,6 +1779,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: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 6143d6c3..7f21ad2e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -97,6 +97,8 @@ dependencies: huawei_location: ^6.16.0+300 geolocator: ^9.0.2 nfc_manager: ^4.1.1 + flutter_nfc_kit: ^3.6.2 + wifi_iot: ^0.3.19+2 just_audio: ^0.9.46 safe_device: ^1.3.8