nfc package update and location services improvements

main_production_upgrade
WaseemAbbasi22 2 days ago
parent e4b84bb3e0
commit 2df279aedd

@ -1,26 +1,26 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:nfc_manager/nfc_manager.dart'; import 'package:flutter_nfc_kit/flutter_nfc_kit.dart';
import 'package:test_sa/dashboard_latest/dashboard_view.dart';
import 'package:test_sa/extensions/context_extension.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/new_views/app_style/app_color.dart';
import 'package:test_sa/views/app_style/sizing.dart';
void showNfcReader(BuildContext context, {Function(String?)? onNcfScan}) { void showNfcReader(BuildContext context, {Function(String?)? onNcfScan}) {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
enableDrag: false, enableDrag: false,
isDismissible: false, isDismissible: false,
barrierColor: Colors.black.withOpacity(0.5),
shape: const RoundedRectangleBorder( 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, backgroundColor: Colors.white,
builder: (context) { builder: (context) {
return NfcLayout( return NfcLayout(onNcfScan: onNcfScan);
onNcfScan: onNcfScan,
);
}, },
); );
} }
@ -38,123 +38,105 @@ class _NfcLayoutState extends State<NfcLayout> {
bool _reading = false; bool _reading = false;
Widget? mainWidget; Widget? mainWidget;
String? nfcId; String? nfcId;
bool _nfcScanning = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_startNfcScan();
NfcManager.instance.startSession(
onDiscovered: (NfcTag tag) async {
String? identifier;
try {
final dynamic tagDynamic = tag;
final Map<String, dynamic> tagData = Map<String, dynamic>.from(tagDynamic.data as Map);
if (Platform.isAndroid) {
if (tagData.containsKey('nfca')) {
final nfcaData = tagData['nfca'] as Map<dynamic, dynamic>;
if (nfcaData.containsKey('identifier')) {
final List<int> idBytes = List<int>.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<dynamic, dynamic>;
if (mifareData.containsKey('identifier')) {
final List<int> idBytes = List<int>.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<void> _startNfcScan() async {
if (_nfcScanning) return;
_nfcScanning = true;
try {
NFCTag tag = await FlutterNfcKit.poll(
timeout: const Duration(seconds: 20),
);
nfcId = tag.id.toUpperCase();
setState(() { setState(() {
_reading = true; _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;
}
try {
await FlutterNfcKit.finish();
} catch (_) {}
Future.delayed(const Duration(seconds: 1), () {
NfcManager.instance.stopSession();
Navigator.pop(context); Navigator.pop(context);
widget.onNcfScan!(nfcId!); } finally {
}); _nfcScanning = false;
}, }
pollingOptions: {NfcPollingOption.iso14443},
).catchError((err) {
print(err);
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
(mainWidget == null && !_reading) ? mainWidget = scanNfc() : mainWidget = doneNfc(); (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() { Widget scanNfc() {
return Container( return Container(
color: AppColor.background(context), 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( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
SizedBox( const SizedBox(height: 30),
height: 30,
),
Text( Text(
"Ready To Scan", "Ready To Scan",
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 24, fontSize: 24,
color:AppColor.headingTextColor(context) color: AppColor.headingTextColor(context),
),
), ),
SizedBox(
height: 30,
), ),
const SizedBox(height: 30),
Image.asset( Image.asset(
"assets/images/ic_nfc.png", "assets/images/ic_nfc.png",
height: MediaQuery.of(context).size.width / 3, height: MediaQuery.of(context).size.width / 3,
color: AppColor.iconColor(context), color: AppColor.iconColor(context),
width: double.infinity, width: double.infinity,
), ),
const SizedBox( const SizedBox(height: 30),
height: 30,
),
const Text( const Text(
"Approach an NFC Tag", "Approach an NFC Tag",
style: TextStyle( style: TextStyle(fontSize: 18),
fontSize: 18,
),
),
const SizedBox(
height: 30,
), ),
const SizedBox(height: 30),
ButtonTheme( ButtonTheme(
minWidth: MediaQuery.of(context).size.width / 1.2, minWidth: MediaQuery.of(context).size.width / 1.2,
height: 45.0, height: 45.0,
buttonColor: Colors.grey[300], buttonColor: Colors.grey[300],
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
borderRadius: BorderRadius.circular(6),
),
child: TextButton( child: TextButton(
onPressed: () { onPressed: () async {
NfcManager.instance.stopSession(); try {
await FlutterNfcKit.finish();
} catch (_) {}
Navigator.pop(context); Navigator.pop(context);
}, },
// elevation: 0,
child: const Text("CANCEL"), child: const Text("CANCEL"),
), ),
), ),
SizedBox( const SizedBox(height: 30),
height: 30,
),
], ],
), ),
); );
@ -163,66 +145,253 @@ class _NfcLayoutState extends State<NfcLayout> {
Widget doneNfc() { Widget doneNfc() {
return Container( return Container(
color: AppColor.background(context), 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( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
SizedBox( const SizedBox(height: 30),
height: 30,
),
Text( Text(
"Successfully Scanned", "Successfully Scanned",
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 24, fontSize: 24,
color:AppColor.headingTextColor(context) color: AppColor.headingTextColor(context),
),
), ),
SizedBox(
height: 30,
), ),
const SizedBox(height: 30),
Image.asset( Image.asset(
// "assets/icons/nfc/ic_done.png",
"assets/images/ic_done.png", "assets/images/ic_done.png",
height: MediaQuery.of(context).size.width / 3, height: MediaQuery.of(context).size.width / 3,
width: double.infinity, width: double.infinity,
color: AppColor.iconColor(context), color: AppColor.iconColor(context),
), ),
SizedBox( const SizedBox(height: 30),
height: 30, const Text(
),
Text(
"Approach an NFC Tag", "Approach an NFC Tag",
style: TextStyle( style: TextStyle(fontSize: 18),
fontSize: 18,
),
),
SizedBox(
height: 30,
), ),
const SizedBox(height: 30),
ButtonTheme( ButtonTheme(
minWidth: MediaQuery.of(context).size.width / 1.2, minWidth: MediaQuery.of(context).size.width / 1.2,
height: 45.0, height: 45.0,
buttonColor: Colors.grey[300], buttonColor: Colors.grey[300],
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
borderRadius: BorderRadius.circular(6),
),
child: TextButton( child: TextButton(
// onPressed: () {
// _stream?.cancel();
// widget.onNcfScan(nfcId);
// Navigator.pop(context);
// },
onPressed: null, onPressed: null,
// elevation: 0, child: Text(
child: Text("DONE",style: TextStyle(color: context.isDark?AppColor.primary10:null),), "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<NfcLayout> {
// 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<String, dynamic> tagData = Map<String, dynamic>.from(tagDynamic.data as Map);
//
// if (Platform.isAndroid) {
// if (tagData.containsKey('nfca')) {
// final nfcaData = tagData['nfca'] as Map<dynamic, dynamic>;
// if (nfcaData.containsKey('identifier')) {
// final List<int> idBytes = List<int>.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<dynamic, dynamic>;
// if (mifareData.containsKey('identifier')) {
// final List<int> idBytes = List<int>.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: <Widget>[
// 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: <Widget>[
// 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,
// ),
// ],
// ),
// );
// }
// }

@ -20,6 +20,21 @@ class LocationUtilities {
Geolocator.isLocationServiceEnabled().then((value) => callback(value)); Geolocator.isLocationServiceEnabled().then((value) => callback(value));
} }
static Future<bool> isEnabledAsync() async {
return await Geolocator.isLocationServiceEnabled();
}
static Future<bool> 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 bool _listeningSettingChange = true;
static void listenGPS({bool change = true, Function(bool)? onChange}) async { static void listenGPS({bool change = true, Function(bool)? onChange}) async {
@ -44,42 +59,59 @@ class LocationUtilities {
completion(isGranted); completion(isGranted);
}); });
} }
static Future<void> 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) { try {
Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) { final position = await Geolocator.getCurrentPosition(
bool isMocked = position.isMocked; desiredAccuracy: LocationAccuracy.high,
callback(position, isMocked); timeLimit: const Duration(seconds: 10),
}).catchError((err) { );
errorCallBack(); debugPrint("✅ Location: ${position.latitude}, ${position.longitude}");
});
// return; callback(position, position.isMocked);
// Permission.location.isGranted.then((isGranted) { } catch (e) {
// if (!isGranted) { debugPrint("❌ Primary location failed: $e");
// Permission.location.request().then((granted) { try {
// print("granted:$granted"); final lastPosition = await Geolocator.getLastKnownPosition();
// if (granted == PermissionStatus.granted) { if (lastPosition != null) {
// Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) { debugPrint("⚠️ Using last known location");
// bool isMocked = position.isMocked; callback(lastPosition, lastPosition.isMocked);
// callback(position, isMocked); return;
// }).catchError((err) { }
// print("getCurrentPositionError:$err"); } catch (fallbackError) {
// errorCallBack(); debugPrint("❌ Fallback failed: $fallbackError");
// }); }
// } else { String message = "Unable to determine your location";
// errorCallBack(); final error = e.toString().toLowerCase();
// } if (error.contains("timeout")) {
// }); message = "Location request timed out. Please try again.";
// } else { } 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) { // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) {
// bool isMocked = position.isMocked; // bool isMocked = position.isMocked;
// callback(position, isMocked); // callback(position, isMocked);
// }).catchError((err) { // }).catchError((err) {
// print("getCurrentPositionError:$err");
// errorCallBack(); // 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) { // // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) {
// // bool isMocked = position.isMocked; // // bool isMocked = position.isMocked;
// // callback(position, isMocked); // // callback(position, isMocked);
@ -87,18 +119,40 @@ class LocationUtilities {
// // print("getCurrentPositionError:$err"); // // print("getCurrentPositionError:$err");
// // errorCallBack(); // // errorCallBack();
// // }); // // });
// // // } else {
// // locationFun((granted) { // // errorCallBack();
// // if (granted) { // // }
// // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((value) { // // });
// // done(value); // // } else {
// // Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.medium, timeLimit: const Duration(seconds: 5)).then((position) {
// // bool isMocked = position.isMocked;
// // callback(position, isMocked);
// // }).catchError((err) { // // }).catchError((err) {
// // print("getCurrentPositionError:$err"); // // print("getCurrentPositionError:$err");
// // errorCallBack(); // // errorCallBack();
// // }); // // });
// // } else {
// // // AppPermissions
// // } // // }
// // }, context); // // });
} // //
// // // 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);
// }
} }

@ -5,6 +5,7 @@ import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_nfc_kit/flutter_nfc_kit.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:google_api_availability/google_api_availability.dart'; import 'package:google_api_availability/google_api_availability.dart';
import 'package:huawei_location/huawei_location.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/controllers/providers/api/user_provider.dart';
import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.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/text_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/main.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/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/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/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/enums/swipe_type.dart';
import 'package:test_sa/new_views/swipe_module/models/swipe_model.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'; import 'package:test_sa/new_views/swipe_module/swipe_success_view.dart';
@ -38,7 +42,7 @@ class SwipeGeneralUtils {
static bool get isLoading => _isLoadingVisible; 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); showLoading(context);
try { try {
hideLoading(navigatorKey.currentState!.overlay!.context); hideLoading(navigatorKey.currentState!.overlay!.context);
@ -112,20 +116,33 @@ class SwipeGeneralUtils {
} }
Widget attendanceTypeCard(String title, String icon, bool isEnabled, VoidCallback onPress, BuildContext context) { Widget attendanceTypeCard(String title, String icon, bool isEnabled, VoidCallback onPress, BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( 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), 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( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
icon.toSvgAsset(color: isEnabled ?context.isDark ? AppColor.neutral30 : AppColor.neutral50 : Colors.grey.withOpacity(0.5)), icon.toSvgAsset(
title.heading5(context).custom(color: isEnabled ?context.isDark ? AppColor.neutral30 : AppColor.neutral50 : Colors.grey.withOpacity(0.5)), 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( ).onPress(
@ -173,50 +190,79 @@ class SwipeGeneralUtils {
return (result[Permission.location] == PermissionStatus.granted || result[Permission.locationAlways] == PermissionStatus.granted); return (result[Permission.location] == PermissionStatus.granted || result[Permission.locationAlways] == PermissionStatus.granted);
} }
void checkHuaweiLocationPermission({required SwipeTypeEnum attendanceType, required BuildContext context}) async { Future<void> checkHuaweiLocationPermission({
// Permission_Handler permissionHandler = PermissionHandler(); required SwipeTypeEnum attendanceType,
LocationUtilities.isEnabled((bool isEnabled) async { required BuildContext context,
if (isEnabled) { }) async {
LocationUtilities.havePermission((bool permission) async { try {
if (permission) { // Check if location service is enabled
getHuaweiCurrentLocation(attendanceType: attendanceType, context: context); final isEnabled = await LocationUtilities.isEnabledAsync();
} else { if (!isEnabled) {
bool has = await requestPermissions();
if (has) {
getHuaweiCurrentLocation(attendanceType: attendanceType, context: context);
} else {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext cxt) => ConfirmDialog( builder: (cxt) => ConfirmDialog(
message: "You need to enable location services to mark attendance",
onTap: () async {
Navigator.pop(cxt);
await Geolocator.openLocationSettings();
},
),
);
return;
}
// 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", message: "You need to give location permission to mark attendance",
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(cxt);
}, },
), ),
); );
return;
} }
}
}); // All good get location
} else { getHuaweiCurrentLocation(
attendanceType: attendanceType,
context: context,
);
} catch (e) {
debugPrint("❌ Huawei location error: $e");
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext cxt) => ConfirmDialog( builder: (cxt) => ConfirmDialog(
message: "You need to enable location services to mark attendance", message: "Something went wrong. Please try again.",
onTap: () async { onTap: () {
Navigator.pop(context); Navigator.pop(cxt);
await Geolocator.openLocationSettings();
}, },
), ),
); );
} }
}); }
// if (await permissionHandler.hasLocationPermission()) { // void checkHuaweiLocationPermission({required SwipeTypeEnum attendanceType, required BuildContext context}) async {
// getHuaweiCurrentLocation(attendanceType); // // Permission_Handler permissionHandler = PermissionHandler();
// LocationUtilities.isEnabled((bool isEnabled) async {
// if (isEnabled) {
// LocationUtilities.havePermission((bool permission) async {
// if (permission) {
// getHuaweiCurrentLocation(attendanceType: attendanceType, context: context);
// } else { // } else {
// bool has = await requestPermissions(); // bool has = await requestPermissions();
// if (has) { // if (has) {
// getHuaweiCurrentLocation(attendanceType); // getHuaweiCurrentLocation(attendanceType: attendanceType, context: context);
// } else { // } else {
// showDialog( // showDialog(
// context: context, // context: context,
@ -229,11 +275,45 @@ class SwipeGeneralUtils {
// ); // );
// } // }
// } // }
} // });
// } 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}) { void handleSwipeOperation({required SwipeTypeEnum swipeType, required double lat, required double long, required BuildContext context}) {
switch (swipeType) { switch (swipeType) {
case SwipeTypeEnum.NFC: case SwipeTypeEnum.NFC:
log('showing nfc sheet');
handleNfcAttendance(latitude: lat, longitude: long, context: context); handleNfcAttendance(latitude: lat, longitude: long, context: context);
return; return;
case SwipeTypeEnum.QR: case SwipeTypeEnum.QR:
@ -253,10 +333,9 @@ class SwipeGeneralUtils {
UserProvider userProvider = Provider.of<UserProvider>(context, listen: false); UserProvider userProvider = Provider.of<UserProvider>(context, listen: false);
String qrCodeValue = await Navigator.of(context).push( String qrCodeValue = await Navigator.of(context).push(
MaterialPageRoute(builder: (_) => ScanQr()), MaterialPageRoute(builder: (_) => const ScanQr()),
) as String; ) as String;
if (qrCodeValue != null) {
showLoading(context); showLoading(context);
try { try {
final swipeModel = Swipe( final swipeModel = Swipe(
@ -292,22 +371,102 @@ class SwipeGeneralUtils {
// handleException(ex, context, null); // handleException(ex, context, null);
} }
} }
// 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<void> handleNfcAttendance({
double? latitude = 0,
double? longitude = 0,
required BuildContext context,
}) async {
// if (_nfcScanning) return;
// _nfcScanning = true;
try {
var availability = await FlutterNfcKit.nfcAvailability;
if (availability != NFCAvailability.available) {
"NFC is not available on this device".showToast;
return;
} }
Future<void> handleNfcAttendance({double? latitude = 0, double? longitude = 0, required BuildContext context}) async { if (Platform.isAndroid) {
// UserProvider _userProvider = Provider.of<UserProvider>(context,listen:false); try {
showNfcReader(context, onNcfScan: (String? nfcId) async {
log('nfc id from sheet: $nfcId');
if (Platform.isIOS) { if (nfcId == null || nfcId.isEmpty) return;
readNFc(onRead: (String nfcId) async { try {
await _processNfcAttendance(nfcId, latitude, longitude, context); await _processNfcAttendance(nfcId, latitude, longitude, context);
} catch (e) {
// handle per-scan error if needed
}
}); });
} catch (e) {
debugPrint('Error showing NFC reader sheet: $e');
}
} else { } else {
showNfcReader(context, onNcfScan: (String? nfcId) async { try {
await _processNfcAttendance(nfcId ?? '', latitude, longitude, context); 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<void> handleNfcAttendance({double? latitude = 0, double? longitude = 0, required BuildContext context}) async {
// bool _nfcScanning = false;
// // UserProvider _userProvider = Provider.of<UserProvider>(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<void> _processNfcAttendance( Future<void> _processNfcAttendance(
String nfcId, String nfcId,
double? latitude, double? latitude,
@ -316,7 +475,12 @@ class SwipeGeneralUtils {
) async { ) async {
showLoading(context); showLoading(context);
try { 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<UserProvider>(context, listen: false); UserProvider userProvider = Provider.of<UserProvider>(context, listen: false);
final swipeResponse = await userProvider.makeSwipe(model: swipeModel); 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 { Future<void> handleSwipe({
required SwipeTypeEnum swipeType,
required bool isEnable,
required BuildContext context,
}) async {
try {
if (Platform.isAndroid && !(await isGoogleServicesAvailable())) { if (Platform.isAndroid && !(await isGoogleServicesAvailable())) {
checkHuaweiLocationPermission(attendanceType: swipeType, context: context); checkHuaweiLocationPermission(
} else { attendanceType: swipeType,
LocationUtilities.isEnabled((bool isEnabled) { context: context,
if (isEnabled) { );
LocationUtilities.havePermission((bool permission) { return;
if (permission) { }
// 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); showLoading(context);
LocationUtilities.getCurrentLocation(
// Get location
await LocationUtilities.getCurrentLocation(
(Position position, bool isMocked) { (Position position, bool isMocked) {
if (isMocked) {
hideLoading(context); hideLoading(context);
markFakeAttendance(swipeType.name, position.latitude.toString() ?? "", position.longitude.toString() ?? "", context);
if (isMocked) {
markFakeAttendance(
swipeType.name,
position.latitude.toString(),
position.longitude.toString(),
context: context,
);
} else { } else {
hideLoading(context); handleSwipeOperation(
handleSwipeOperation(swipeType: swipeType, lat: position.latitude, long: position.longitude, context: context); swipeType: swipeType,
lat: position.latitude,
long: position.longitude,
context: context,
);
} }
}, },
() { (String error) {
hideLoading(context); hideLoading(context);
confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working.");
confirmDialog(
context,
error.isNotEmpty ? error : "Unable to determine your location. Please try again.",
);
}, },
context, context,
); );
} else { } catch (e) {
showInfoDialog( hideLoading(context);
message: "You need to give location permission to mark attendance", confirmDialog(
onTap: () async { context,
await Geolocator.openAppSettings(); "Something went wrong. Please try again.",
}); );
} debugPrint("❌ handleSwipe error: $e");
});
} else {
showInfoDialog(
message: "You need to enable location services to mark attendance",
onTap: () async {
await Geolocator.openLocationSettings();
});
}
});
} }
} }
// 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}) { void showInfoDialog({required String message, VoidCallback? onTap}) {
showDialog( showDialog(
context: navigatorKey.currentState!.overlay!.context, context: navigatorKey.currentState!.overlay!.context,
@ -431,16 +672,15 @@ class SwipeGeneralUtils {
), ),
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
clipBehavior: Clip.antiAliasWithSaveLayer, clipBehavior: Clip.antiAliasWithSaveLayer,
builder: (BuildContext context) =>Padding( builder: (BuildContext context) => Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom + bottom: MediaQuery.of(context).viewInsets.bottom + MediaQuery.of(context).padding.bottom,
MediaQuery.of(context).padding.bottom,
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ 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, 8.height,
context.translation.selectMethodToMarkAttendance.bodyText2(context).custom(color: AppColor.neutral120), context.translation.selectMethodToMarkAttendance.bodyText2(context).custom(color: AppColor.neutral120),
12.height, 12.height,
@ -456,6 +696,7 @@ class SwipeGeneralUtils {
); );
} }
//older code..
void readNFc({Function(String)? onRead}) { void readNFc({Function(String)? onRead}) {
NfcManager.instance.startSession( NfcManager.instance.startSession(
onDiscovered: (NfcTag tag) async { onDiscovered: (NfcTag tag) async {

@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
import '../buttons/app_icon_button.dart'; import '../buttons/app_icon_button.dart';
@ -15,10 +14,7 @@ class ScanQr extends StatefulWidget {
class _ScanQrState extends State<ScanQr> { class _ScanQrState extends State<ScanQr> {
// Barcode result; // Barcode result;
// QRViewController? _controller; QRViewController? _controller;
MobileScannerController? controller;
bool _scanDone = false; bool _scanDone = false;
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR_scanner'); final GlobalKey qrKey = GlobalKey(debugLabel: 'QR_scanner');
@ -26,34 +22,19 @@ class _ScanQrState extends State<ScanQr> {
// is android, or resume the camera if the platform is iOS. // is android, or resume the camera if the platform is iOS.
@override @override
void initState() { void reassemble() {
// TODO: implement initState super.reassemble();
super.initState(); if (Platform.isAndroid) {
controller = MobileScannerController( _controller?.pauseCamera();
detectionSpeed: DetectionSpeed.noDuplicates, } else if (Platform.isIOS) {
formats: [], _controller?.resumeCamera();
returnImage: false, }
torchEnabled: false,
invertImage: false,
autoZoom: true,
);
} }
//
// @override
// void reassemble() {
// super.reassemble();
// if (Platform.isAndroid) {
// controller?.pauseCamera();
// } else if (Platform.isIOS) {
// _controller?.resumeCamera();
// }
// }
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
// _controller?.dispose(); _controller?.dispose();
} }
@override @override
@ -61,15 +42,20 @@ class _ScanQrState extends State<ScanQr> {
return Scaffold( return Scaffold(
body: Stack( body: Stack(
children: [ children: [
MobileScanner( QRView(
tapToFocus: true, key: qrKey,
controller: controller, onQRViewCreated: (QRViewController controller) {
onDetect: (result) { setState(() {
_controller = controller;
});
controller.scannedDataStream.listen((scanData) {
if (!_scanDone) { if (!_scanDone) {
_scanDone = true; _scanDone = true;
Navigator.of(context).pop(result.barcodes.first.rawValue); Navigator.of(context).pop(scanData.code);
} }
});
}, },
overlay: QrScannerOverlayShape(borderColor: Colors.red, borderRadius: 10, borderLength: 30, borderWidth: 10, cutOutSize: 280),
), ),
SafeArea( SafeArea(
child: Padding( child: Padding(

@ -217,6 +217,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.19.1" version: "1.19.1"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
cross_file: cross_file:
dependency: transitive dependency: transitive
description: description:
@ -532,6 +540,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@ -1030,6 +1046,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" 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: native_toolchain_c:
dependency: transitive dependency: transitive
description: description:
@ -1038,6 +1062,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.17.4" version: "0.17.4"
ndef:
dependency: transitive
description:
name: ndef
sha256: "198ba3798e80cea381648569d84059dbba64cd140079fb7b0d9c3f1e0f5973f3"
url: "https://pub.dev"
source: hosted
version: "0.4.0"
ndef_record: ndef_record:
dependency: transitive dependency: transitive
description: description:
@ -1747,6 +1779,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" 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: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:

@ -97,6 +97,8 @@ dependencies:
huawei_location: ^6.16.0+300 huawei_location: ^6.16.0+300
geolocator: ^9.0.2 geolocator: ^9.0.2
nfc_manager: ^4.1.1 nfc_manager: ^4.1.1
flutter_nfc_kit: ^3.6.2
wifi_iot: ^0.3.19+2 wifi_iot: ^0.3.19+2
just_audio: ^0.9.46 just_audio: ^0.9.46
safe_device: ^1.3.8 safe_device: ^1.3.8

Loading…
Cancel
Save