non hmg mark attendance module added.
parent
561881c7f2
commit
ef38c3d2bd
Binary file not shown.
Binary file not shown.
@ -0,0 +1,420 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
// import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
import 'package:google_api_availability/google_api_availability.dart';
|
||||||
|
// import 'package:mohem_flutter_app/app_state/app_state.dart';
|
||||||
|
// import 'package:mohem_flutter_app/classes/colors.dart';
|
||||||
|
// import 'package:mohem_flutter_app/config/routes.dart';
|
||||||
|
// import 'package:mohem_flutter_app/exceptions/api_exception.dart';
|
||||||
|
// import 'package:mohem_flutter_app/extensions/int_extensions.dart';
|
||||||
|
// import 'package:mohem_flutter_app/extensions/string_extensions.dart';
|
||||||
|
// import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
|
||||||
|
// import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
||||||
|
// import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart';
|
||||||
|
// import 'package:mohem_flutter_app/widgets/loading_dialog.dart';
|
||||||
|
import 'package:nfc_manager/nfc_manager.dart';
|
||||||
|
import 'package:nfc_manager/platform_tags.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart';
|
||||||
|
import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: avoid_annotating_with_dynamic
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
static bool _isLoadingVisible = false;
|
||||||
|
|
||||||
|
static bool get isLoading => _isLoadingVisible;
|
||||||
|
|
||||||
|
static void showToast(String message, {bool longDuration = true}) {
|
||||||
|
Fluttertoast.showToast(
|
||||||
|
msg: message,
|
||||||
|
toastLength: longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT,
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
timeInSecForIosWeb: 1,
|
||||||
|
backgroundColor: Colors.black54,
|
||||||
|
textColor: Colors.white,
|
||||||
|
fontSize: 13.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static dynamic getNotNullValue(List<dynamic> list, int index) {
|
||||||
|
try {
|
||||||
|
return list[index];
|
||||||
|
} catch (ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int stringToHex(String colorCode) {
|
||||||
|
try {
|
||||||
|
return int.parse(colorCode.replaceAll("#", "0xff"));
|
||||||
|
} catch (ex) {
|
||||||
|
return (0xff000000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future delay(int millis) async {
|
||||||
|
return await Future.delayed(Duration(milliseconds: millis));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void showLoading(BuildContext context) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_isLoadingVisible = true;
|
||||||
|
|
||||||
|
// showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading());
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierColor: Colors.black.withOpacity(0.5),
|
||||||
|
useRootNavigator: false,
|
||||||
|
builder: (BuildContext context) => const AppLazyLoading(),
|
||||||
|
).then((value) {
|
||||||
|
_isLoadingVisible = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hideLoading(BuildContext context) {
|
||||||
|
if (_isLoadingVisible) {
|
||||||
|
_isLoadingVisible = false;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
_isLoadingVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<String> getStringFromPrefs(String key) async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString(key) ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> removeStringFromPrefs(String key) async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> saveStringFromPrefs(String key, String value) async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
return await prefs.setString(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static void handleException(dynamic exception, cxt, Function(String)? onErrorMessage) {
|
||||||
|
// String errorMessage;
|
||||||
|
// if (exception.error.errorType != null && exception.error.errorType == 4) {
|
||||||
|
// Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.appUpdateScreen, (_) => false, arguments: exception.error?.errorMessage);
|
||||||
|
// } else {
|
||||||
|
// if (exception is APIException) {
|
||||||
|
// if (exception.message == APIException.UNAUTHORIZED) {
|
||||||
|
// return;
|
||||||
|
// } else {
|
||||||
|
// errorMessage = exception.error?.errorMessage ?? exception.message;
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// errorMessage = APIException.UNKNOWN;
|
||||||
|
// }
|
||||||
|
// if (onErrorMessage != null) {
|
||||||
|
// onErrorMessage(errorMessage);
|
||||||
|
// } else {
|
||||||
|
// if (!AppState().isAuthenticated) {
|
||||||
|
// showDialog(
|
||||||
|
// barrierDismissible: false,
|
||||||
|
// context: cxt,
|
||||||
|
// builder: (cxt) => ConfirmDialog(
|
||||||
|
// message: errorMessage,
|
||||||
|
// onTap: () {
|
||||||
|
// Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.login, (Route<dynamic> route) => false);
|
||||||
|
// },
|
||||||
|
// onCloseTap: () {},
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// if (cxt != null) {
|
||||||
|
// confirmDialog(cxt, errorMessage);
|
||||||
|
// } else {
|
||||||
|
// showToast(errorMessage);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static Future showErrorDialog({required BuildContext context, required VoidCallback onOkTapped, required String message}) async {
|
||||||
|
// return showDialog(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext context) => ConfirmDialog(
|
||||||
|
// message: message,
|
||||||
|
// onTap: onOkTapped,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
static void confirmDialog(cxt, String message, {VoidCallback? onTap}) {
|
||||||
|
showDialog(
|
||||||
|
context: cxt,
|
||||||
|
builder: (BuildContext cxt) => ConfirmDialog(
|
||||||
|
message: message,
|
||||||
|
onTap: onTap,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// static Widget getNoDataWidget(BuildContext context) {
|
||||||
|
// return Column(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
// children: [
|
||||||
|
// SvgPicture.asset('assets/images/not_found.svg', width: 110.0, height: 110.0),
|
||||||
|
// LocaleKeys.noDataAvailable.tr().toText16().paddingOnly(top: 15),
|
||||||
|
// ],
|
||||||
|
// ).center;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static Widget getNoChatWidget(BuildContext context) {
|
||||||
|
// return Column(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
// children: [
|
||||||
|
// SvgPicture.asset('assets/images/not_found.svg', width: 110.0, height: 110.0),
|
||||||
|
// LocaleKeys.noDataAvailable.tr().toText16().paddingOnly(top: 15),
|
||||||
|
// ],
|
||||||
|
// ).center;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static Uint8List getPostBytes(img) {
|
||||||
|
// try {
|
||||||
|
// var b64 = img.replaceFirst('data:image/png;base64,', '');
|
||||||
|
// if (img != null && Utils.isBase64(b64)) return Utils.dataFromBase64String(b64);
|
||||||
|
// } catch (e) {}
|
||||||
|
// return Uint8List.fromList([]);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static String getBase64FromJpeg(img) {
|
||||||
|
// try {
|
||||||
|
// var b64 = img.replaceFirst('data:image/jpeg;base64,', '');
|
||||||
|
// return b64;
|
||||||
|
// } catch (e) {}
|
||||||
|
// return "";
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static bool isBase64(String str) {
|
||||||
|
// RegExp _base64 = RegExp(r'^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$');
|
||||||
|
// return _base64.hasMatch(str);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static Uint8List dataFromBase64String(String base64String) {
|
||||||
|
// return base64Decode(base64String);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static Widget tableColumnTitle(String? text, {bool showDivider = true, bool alignCenter = false}) {
|
||||||
|
// text ??= "";
|
||||||
|
// return Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
// children: [
|
||||||
|
// 6.height,
|
||||||
|
// alignCenter ? text.toText12().center : text.toText12(),
|
||||||
|
// 5.height,
|
||||||
|
// if (showDivider)
|
||||||
|
// const Divider(
|
||||||
|
// height: 1,
|
||||||
|
// color: Color(0xff2E303A),
|
||||||
|
// thickness: 1,
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static Decoration containerRadius(Color background, double radius) {
|
||||||
|
// return BoxDecoration(
|
||||||
|
// color: background,
|
||||||
|
// border: Border.all(
|
||||||
|
// width: 1, //
|
||||||
|
// color: background // <--- border width here
|
||||||
|
// ),
|
||||||
|
// borderRadius: BorderRadius.circular(radius),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static Widget mHeight(double h) {
|
||||||
|
// return Container(
|
||||||
|
// height: h,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static Widget mDivider(Color color) {
|
||||||
|
// return Divider(
|
||||||
|
// // width: double.infinity,
|
||||||
|
// height: 1,
|
||||||
|
// color: color,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static Widget tableColumnValue(String text, {bool isCapitable = true, bool alignCenter = false}) {
|
||||||
|
// return Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
// children: [
|
||||||
|
// 12.height,
|
||||||
|
// if (alignCenter)
|
||||||
|
// (isCapitable ? text.toLowerCase().capitalizeFirstofEach : text).toText12(color: MyColors.normalTextColor).center
|
||||||
|
// else
|
||||||
|
// (isCapitable ? text.toLowerCase().capitalizeFirstofEach : text).toText12(color: MyColors.normalTextColor),
|
||||||
|
// 12.height,
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// EIT Forms date formats
|
||||||
|
//
|
||||||
|
// static String getMonthNamedFormat(DateTime date) {
|
||||||
|
// /// it will return like "29-Sep-2022"
|
||||||
|
// return DateFormat('dd-MMM-yyyy', "en_US").format(date);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static String reverseFormatDate(String date) {
|
||||||
|
// String formattedDate;
|
||||||
|
// if (date.isNotEmpty) {
|
||||||
|
// formattedDate = date.replaceAll('/', '-');
|
||||||
|
// formattedDate = formattedDate.replaceAll(' 00:00:00', '');
|
||||||
|
// } else {
|
||||||
|
// formattedDate = date;
|
||||||
|
// }
|
||||||
|
// return formattedDate;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static String formatStandardDate(String date) {
|
||||||
|
// String formattedDate;
|
||||||
|
// if (date.isNotEmpty) {
|
||||||
|
// formattedDate = date.replaceAll('-', '/');
|
||||||
|
// } else {
|
||||||
|
// formattedDate = date;
|
||||||
|
// }
|
||||||
|
// return formattedDate;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static String reverseFormatStandardDate(String date) {
|
||||||
|
// String formattedDate;
|
||||||
|
// if (date.isNotEmpty) {
|
||||||
|
// formattedDate = date.replaceAll('/', '-');
|
||||||
|
// } else {
|
||||||
|
// formattedDate = date;
|
||||||
|
// }
|
||||||
|
// return formattedDate;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static String formatDate(String date) {
|
||||||
|
// String formattedDate;
|
||||||
|
//
|
||||||
|
// if (date.isNotEmpty) {
|
||||||
|
// date = date.substring(0, 10);
|
||||||
|
// formattedDate = date.replaceAll('-', '/');
|
||||||
|
// formattedDate = formattedDate + ' 00:00:00';
|
||||||
|
// } else {
|
||||||
|
// formattedDate = date;
|
||||||
|
// }
|
||||||
|
// return formattedDate;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static String formatDateNew(String date) {
|
||||||
|
// String formattedDate;
|
||||||
|
// if (date.isNotEmpty) {
|
||||||
|
// formattedDate = date.split('T')[0];
|
||||||
|
// if (!formattedDate.contains("00:00:00")) {
|
||||||
|
// formattedDate = formattedDate + ' 00:00:00';
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// formattedDate = date;
|
||||||
|
// }
|
||||||
|
// return formattedDate;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static String formatDateDefault(String date) {
|
||||||
|
// if (date.isNotEmpty) {
|
||||||
|
// if (date.toLowerCase().contains("t")) {
|
||||||
|
// date = date.toLowerCase().split("t")[0];
|
||||||
|
// if (!date.contains("00:00:00")) {
|
||||||
|
// date = date + ' 00:00:00';
|
||||||
|
// }
|
||||||
|
// return date;
|
||||||
|
// } else {
|
||||||
|
// if (date.toLowerCase().split("-")[1].length == 3) {
|
||||||
|
// return DateFormat('dd-MM-yyyy', "en_US").format(DateFormat('dd-MMM-yyyy', "en_US").parseLoose(date));
|
||||||
|
// } else {
|
||||||
|
// return DateFormat('dd-MM-yyyy', "en_US").format(DateFormat('yyyy-MM-dd', "en_US").parseLoose(date));
|
||||||
|
// }
|
||||||
|
// // return DateFormat('yyyy-MM-dd').format(DateFormat('dd-MM-yyyy').parseLoose(date));
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// return date;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static Future<DateTime> selectDate(BuildContext context, DateTime selectedDate) async {
|
||||||
|
// if (!Platform.isIOS) {
|
||||||
|
// await showCupertinoModalPopup(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext cxt) => Container(
|
||||||
|
// height: 250,
|
||||||
|
// color: Colors.white,
|
||||||
|
// child: CupertinoDatePicker(
|
||||||
|
// backgroundColor: Colors.white,
|
||||||
|
// mode: CupertinoDatePickerMode.date,
|
||||||
|
// onDateTimeChanged: (DateTime value) {
|
||||||
|
// if (value != null && value != selectedDate) {
|
||||||
|
// selectedDate = value;
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// initialDateTime: selectedDate,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// DateTime? picked = await showDatePicker(context: context, initialDate: selectedDate, initialEntryMode: DatePickerEntryMode.calendarOnly, firstDate: DateTime(2015, 8), lastDate: DateTime(2101));
|
||||||
|
// if (picked != null && picked != selectedDate) {
|
||||||
|
// selectedDate = picked;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return selectedDate;
|
||||||
|
// }
|
||||||
|
|
||||||
|
static void readNFc({required Function(String) onRead}) {
|
||||||
|
NfcManager.instance.startSession(onDiscovered: (NfcTag tag) async {
|
||||||
|
MifareUltralight f;
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
f = MifareUltralight(tag: tag, identifier: tag.data["nfca"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22);
|
||||||
|
} else {
|
||||||
|
f = MifareUltralight(tag: tag, identifier: tag.data["mifare"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22);
|
||||||
|
}
|
||||||
|
String identifier = f.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
|
||||||
|
NfcManager.instance.stopSession();
|
||||||
|
onRead(identifier);
|
||||||
|
}).catchError((err) {
|
||||||
|
print(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//HUAWEI DECISION MAKING
|
||||||
|
static Future<bool> isGoogleServicesAvailable() async {
|
||||||
|
GooglePlayServicesAvailability availability = await GoogleApiAvailability.instance.checkGooglePlayServicesAvailability();
|
||||||
|
String status = availability.toString().split('.').last;
|
||||||
|
if (status == "success") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// static bool isDate(String input, String format) {
|
||||||
|
// try {
|
||||||
|
// DateTime d = DateFormat(format).parseStrict(input);
|
||||||
|
// //print(d);
|
||||||
|
// return true;
|
||||||
|
// } catch (e) {
|
||||||
|
// //print(e);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@ -0,0 +1,677 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:geolocator/geolocator.dart';
|
||||||
|
import 'package:huawei_location/huawei_location.dart';
|
||||||
|
// import 'package:mohem_flutter_app/widgets/nfc/nfc_reader_sheet.dart';
|
||||||
|
// import 'package:mohem_flutter_app/widgets/qr_scanner_dialog.dart';
|
||||||
|
import 'package:nfc_manager/nfc_manager.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
// import 'package:platform_device_id/platform_device_id.dart';
|
||||||
|
import 'package:test_sa/dashboard_latest/widgets/app_bar_widget.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/helper/utils.dart';
|
||||||
|
import 'package:test_sa/nfc/nfc_reader_sheet.dart';
|
||||||
|
// import 'package:mohem_flutter_app/api/dashboard_api_client.dart';
|
||||||
|
// import 'package:mohem_flutter_app/app_state/app_state.dart';
|
||||||
|
// import 'package:mohem_flutter_app/classes/colors.dart';
|
||||||
|
// import 'package:mohem_flutter_app/classes/utils.dart';
|
||||||
|
// import 'package:mohem_flutter_app/extensions/string_extensions.dart';
|
||||||
|
// import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
|
||||||
|
// import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
||||||
|
// import 'package:mohem_flutter_app/models/generic_response_model.dart';
|
||||||
|
// import 'package:mohem_flutter_app/models/privilege_list_model.dart';
|
||||||
|
// import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart';
|
||||||
|
// import 'package:mohem_flutter_app/ui/dialogs/success_dialog.dart';
|
||||||
|
// import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart';
|
||||||
|
// import 'package:mohem_flutter_app/widgets/dialogs/dialogs.dart';im
|
||||||
|
|
||||||
|
import 'package:test_sa/utilities/Location.dart' as location;
|
||||||
|
import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart';
|
||||||
|
import 'package:test_sa/views/widgets/dialogs/success_dialog.dart';
|
||||||
|
import 'package:test_sa/views/widgets/qr_scanner_dialog.dart';
|
||||||
|
import 'package:wifi_iot/wifi_iot.dart';
|
||||||
|
|
||||||
|
import '../../app_style/app_color.dart';
|
||||||
|
|
||||||
|
class MarkAttendanceWidget extends StatefulWidget {
|
||||||
|
// DashboardProviderModel model;
|
||||||
|
double topPadding;
|
||||||
|
bool isFromDashboard;
|
||||||
|
|
||||||
|
MarkAttendanceWidget( {Key? key, this.topPadding = 0, this.isFromDashboard = false}) : super(key: key);
|
||||||
|
// todo MarkAttendanceWidget(this.model, {Key? key, this.topPadding = 0, this.isFromDashboard = false}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_MarkAttendanceWidgetState createState() {
|
||||||
|
return _MarkAttendanceWidgetState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MarkAttendanceWidgetState extends State<MarkAttendanceWidget> {
|
||||||
|
bool isNfcEnabled = false, isNfcLocationEnabled = false, isQrEnabled = false, isQrLocationEnabled = false, isWifiEnabled = false, isWifiLocationEnabled = false;
|
||||||
|
|
||||||
|
int _locationUpdateCbId = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
checkAttendanceAvailability();
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkAttendanceAvailability() async {
|
||||||
|
bool isAvailable = await NfcManager.instance.isAvailable();
|
||||||
|
// setState(() {
|
||||||
|
// AppState().privilegeListModel!.forEach((PrivilegeListModel element) {
|
||||||
|
// if (element.serviceName == "enableNFC") {
|
||||||
|
// if (isAvailable) if (element.previlege ?? false) isNfcEnabled = true;
|
||||||
|
// } else if (element.serviceName == "enableQR") {
|
||||||
|
// if (element.previlege ?? false) isQrEnabled = true;
|
||||||
|
// } else if (element.serviceName == "enableWIFI") {
|
||||||
|
// if (element.previlege ?? false) isWifiEnabled = true;
|
||||||
|
// } else if (element.serviceName!.trim() == "enableLocationNFC") {
|
||||||
|
// if (element.previlege ?? false) isNfcLocationEnabled = true;
|
||||||
|
// } else if (element.serviceName == "enableLocationQR") {
|
||||||
|
// if (element.previlege ?? false) isQrLocationEnabled = true;
|
||||||
|
// } else if (element.serviceName == "enableLocationWIFI") {
|
||||||
|
// if (element.previlege ?? false) isWifiLocationEnabled = true;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkHuaweiLocationPermission(String attendanceType) async {
|
||||||
|
// Permission_Handler permissionHandler = PermissionHandler();
|
||||||
|
location.Location.isEnabled((bool isEnabled) async {
|
||||||
|
if (isEnabled) {
|
||||||
|
location.Location.havePermission((bool permission) async {
|
||||||
|
if (permission) {
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} 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);
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> requestPermissions() async {
|
||||||
|
var result = await [
|
||||||
|
Permission.location,
|
||||||
|
].request();
|
||||||
|
return (result[Permission.location] == PermissionStatus.granted || result[Permission.locationAlways] == PermissionStatus.granted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
// Stop Session
|
||||||
|
NfcManager.instance.stopSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(left: 21, right: 21, bottom: 21, top: widget.topPadding),
|
||||||
|
decoration: const BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: Colors.white),
|
||||||
|
width: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// LocaleKeys.markAttendance.tr().toSectionHeading(),
|
||||||
|
// LocaleKeys.selectMethodOfAttendance.tr().toText11(color: const Color(0xff535353)),
|
||||||
|
GridView(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
padding: const EdgeInsets.only(bottom: 0, top: 21),
|
||||||
|
gridDelegate:
|
||||||
|
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: (MediaQuery.of(context).size.width < 550) ? 3 : 5, childAspectRatio: 1 / 1, crossAxisSpacing: 8, mainAxisSpacing: 8),
|
||||||
|
children: <Widget>[
|
||||||
|
attendanceMethod("NFC", Icons.nfc, isNfcEnabled, () {
|
||||||
|
// if (AppState().getIsHuawei) {
|
||||||
|
if (false) {
|
||||||
|
checkHuaweiLocationPermission("NFC");
|
||||||
|
} else {
|
||||||
|
location.Location.isEnabled((bool isEnabled) {
|
||||||
|
if (isEnabled) {
|
||||||
|
location.Location.havePermission((bool permission) {
|
||||||
|
if (permission) {
|
||||||
|
Utils.showLoading(context);
|
||||||
|
location.Location.getCurrentLocation(
|
||||||
|
(Position position, bool isMocked) {
|
||||||
|
if (isMocked) {
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
markFakeAttendance("NFC", position.latitude.toString() ?? "", position.longitude.toString() ?? "");
|
||||||
|
} else {
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
//todo performNfcAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? "");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() {
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
Utils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working.");
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext cxt) => ConfirmDialog(
|
||||||
|
message: "You need to give location permission to mark attendance",
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
await Geolocator.openAppSettings();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} 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 (isWifiEnabled) //todo
|
||||||
|
attendanceMethod("Wifi", Icons.wifi, isWifiEnabled, () {
|
||||||
|
// if (AppState().getIsHuawei) {
|
||||||
|
if (false) {
|
||||||
|
checkHuaweiLocationPermission("WIFI");
|
||||||
|
} else {
|
||||||
|
location.Location.isEnabled((bool isEnabled) {
|
||||||
|
if (isEnabled) {
|
||||||
|
location.Location.havePermission((bool permission) {
|
||||||
|
if (permission) {
|
||||||
|
Utils.showLoading(context);
|
||||||
|
location.Location.getCurrentLocation(
|
||||||
|
(Position position, bool isMocked) {
|
||||||
|
if (isMocked) {
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
markFakeAttendance("WIFI", position.latitude.toString() ?? "", position.longitude.toString() ?? "");
|
||||||
|
} else {
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
//todo performWifiAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? "");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() {
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
Utils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working.");
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext cxt) => ConfirmDialog(
|
||||||
|
message: "You need to give location permission to mark attendance",
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
await Geolocator.openAppSettings();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} 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 (isQrEnabled) //todo
|
||||||
|
attendanceMethod("QR", Icons.qr_code_2, isQrEnabled, () async {
|
||||||
|
// if (AppState().getIsHuawei) {
|
||||||
|
if (false) {
|
||||||
|
checkHuaweiLocationPermission("QR");
|
||||||
|
} else {
|
||||||
|
location.Location.isEnabled((bool isEnabled) {
|
||||||
|
if (isEnabled) {
|
||||||
|
location.Location.havePermission((bool permission) {
|
||||||
|
if (permission) {
|
||||||
|
Utils.showLoading(context);
|
||||||
|
location.Location.getCurrentLocation(
|
||||||
|
(Position position, bool isMocked) {
|
||||||
|
if (isMocked) {
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
markFakeAttendance("QR", position.latitude.toString() ?? "", position.longitude.toString() ?? "");
|
||||||
|
} else {
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
//todo performQrCodeAttendance(widget.model, lat: position.latitude.toString() ?? "", lng: position.longitude.toString() ?? "");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() {
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
Utils.confirmDialog(context, "Unable to determine your location, Please make sure that your location services are turned on & working.");
|
||||||
|
},
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext cxt) => ConfirmDialog(
|
||||||
|
message: "You need to give location permission to mark attendance",
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
await Geolocator.openAppSettings();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} 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();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getHuaweiCurrentLocation(String attendanceType) async {
|
||||||
|
try {
|
||||||
|
Utils.showLoading(context);
|
||||||
|
FusedLocationProviderClient locationService = FusedLocationProviderClient()..initFusedLocationService();
|
||||||
|
LocationRequest locationRequest = LocationRequest();
|
||||||
|
locationRequest.priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
|
||||||
|
locationRequest.interval = 500;
|
||||||
|
List<LocationRequest> locationRequestList = <LocationRequest>[locationRequest];
|
||||||
|
LocationSettingsRequest locationSettingsRequest = LocationSettingsRequest(requests: locationRequestList);
|
||||||
|
|
||||||
|
late StreamSubscription<Location> _streamSubscription;
|
||||||
|
int requestCode = (await (locationService.requestLocationUpdates(locationRequest)))!;
|
||||||
|
|
||||||
|
_streamSubscription = locationService.onLocationData!.listen(
|
||||||
|
(Location location) async {
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
await locationService.removeLocationUpdates(requestCode);
|
||||||
|
if (attendanceType == "QR") {
|
||||||
|
// todo performQrCodeAttendance(widget.model, lat: location.latitude.toString() ?? "", lng: location.longitude.toString() ?? "");
|
||||||
|
}
|
||||||
|
if (attendanceType == "WIFI") {
|
||||||
|
// todo performWifiAttendance(widget.model, lat: location.latitude.toString() ?? "", lng: location.longitude.toString() ?? "");
|
||||||
|
}
|
||||||
|
if (attendanceType == "NFC") {
|
||||||
|
//todo performNfcAttendance(widget.model, lat: location.latitude.toString() ?? "", lng: location.longitude.toString() ?? "");
|
||||||
|
}
|
||||||
|
requestCode = 0;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// locationService.checkLocationSettings(locationSettingsRequest).then((settings) async {
|
||||||
|
// await locationService.getLastLocation().then((value) {
|
||||||
|
// if (value.latitude == null || value.longitude == null) {
|
||||||
|
// showDialog(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext cxt) => ConfirmDialog(
|
||||||
|
// message: "Unable to get your location, Please check your location settings & try again.",
|
||||||
|
// onTap: () {
|
||||||
|
// Navigator.pop(context);
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// if (attendanceType == "QR") {
|
||||||
|
// performQrCodeAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? "");
|
||||||
|
// }
|
||||||
|
// if (attendanceType == "WIFI") {
|
||||||
|
// performWifiAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? "");
|
||||||
|
// }
|
||||||
|
// if (attendanceType == "NFC") {
|
||||||
|
// performNfcAttendance(widget.model, lat: value.latitude.toString() ?? "", lng: value.longitude.toString() ?? "");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }).catchError((error) {
|
||||||
|
// print("HUAWEI LOCATION getLastLocation ERROR!!!!!");
|
||||||
|
// print(error);
|
||||||
|
// });
|
||||||
|
// }).catchError((error) {
|
||||||
|
// print("HUAWEI LOCATION checkLocationSettings ERROR!!!!!");
|
||||||
|
// print(error);
|
||||||
|
// if (error.code == "LOCATION_SETTINGS_NOT_AVAILABLE") {
|
||||||
|
// // Location service not enabled.
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
} catch (error) {
|
||||||
|
print("HUAWEI LOCATION ERROR!!!!!");
|
||||||
|
print(error);
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
// Utils.handleException(error, context, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Future<void> performNfcAttendance(DashboardProviderModel model, {String lat = "0", String lng = "0"}) async {
|
||||||
|
// if (Platform.isIOS) {
|
||||||
|
// Utils.readNFc(onRead: (String nfcId) async {
|
||||||
|
// Utils.showLoading(context);
|
||||||
|
// try {
|
||||||
|
// GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId, isGpsRequired: isNfcLocationEnabled, lat: lat, long: lng);
|
||||||
|
// if (g?.messageStatus != 1) {
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// showDialog(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext cxt) => ConfirmDialog(
|
||||||
|
// message: g?.errorEndUserMessage ?? "Unexpected error occurred",
|
||||||
|
// onTap: () {
|
||||||
|
// Navigator.pop(context);
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// bool status = await model.fetchAttendanceTracking(context);
|
||||||
|
// if (Platform.isIOS) await Future.delayed(const Duration(seconds: 3));
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// showMDialog(
|
||||||
|
// context,
|
||||||
|
// backgroundColor: Colors.transparent,
|
||||||
|
// isDismissable: true,
|
||||||
|
// child: SuccessDialog(widget.isFromDashboard),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// } catch (ex) {
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// Utils.handleException(ex, context, null);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// showNfcReader(context, onNcfScan: (String? nfcId) async {
|
||||||
|
// Utils.showLoading(context);
|
||||||
|
// try {
|
||||||
|
// GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? "", isGpsRequired: isNfcLocationEnabled, lat: lat, long: lng);
|
||||||
|
// if (g?.messageStatus != 1) {
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// showDialog(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext cxt) => ConfirmDialog(
|
||||||
|
// message: g?.errorEndUserMessage ?? "Unexpected error occurred",
|
||||||
|
// onTap: () {
|
||||||
|
// Navigator.pop(context);
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// bool status = await model.fetchAttendanceTracking(context);
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// showMDialog(
|
||||||
|
// context,
|
||||||
|
// backgroundColor: Colors.transparent,
|
||||||
|
// isDismissable: false,
|
||||||
|
// child: SuccessDialog(widget.isFromDashboard),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// } catch (ex) {
|
||||||
|
// print(ex);
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// // Utils.handleException(ex, context, (String msg) {
|
||||||
|
// // Utils.confirmDialog(context, msg);
|
||||||
|
// // });
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
void showMDialog(context, {Widget? child, Color? backgroundColor, bool isDismissable = true, bool isBusniessCard = false}) async {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: isDismissable,
|
||||||
|
builder: (context) {
|
||||||
|
return Dialog(
|
||||||
|
shape: isBusniessCard
|
||||||
|
? const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(15.0),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Future<bool> checkSession() async {
|
||||||
|
// try {
|
||||||
|
// Utils.showLoading(context);
|
||||||
|
// await DashboardApiClient().getOpenMissingSwipes();
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// return true;
|
||||||
|
// } catch (ex) {
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// Utils.handleException(ex, context, null);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Future<void> performWifiAttendance(DashboardProviderModel model, {String lat = "0", String lng = "0"}) async {
|
||||||
|
// if (Platform.isAndroid) {
|
||||||
|
// if (!(await checkSession())) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// Utils.showLoading(context);
|
||||||
|
// bool isConnected = await WiFiForIoTPlugin.connect(AppState().getMohemmWifiSSID ?? "",
|
||||||
|
// password: AppState().getMohemmWifiPassword ?? "", joinOnce: Platform.isIOS ? false : true, security: NetworkSecurity.WPA, withInternet: false);
|
||||||
|
//
|
||||||
|
// if (Platform.isIOS) {
|
||||||
|
// if (await WiFiForIoTPlugin.getSSID() == AppState().getMohemmWifiSSID) {
|
||||||
|
// isConnected = true;
|
||||||
|
// } else {
|
||||||
|
// isConnected = false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (isConnected && AppState().isAuthenticated) {
|
||||||
|
// await WiFiForIoTPlugin.forceWifiUsage(true);
|
||||||
|
// await Future.delayed(const Duration(seconds: 6));
|
||||||
|
// try {
|
||||||
|
// GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 3, nfcValue: "", isGpsRequired: isWifiLocationEnabled, lat: lat, long: lng);
|
||||||
|
// bool status = await model.fetchAttendanceTracking(context);
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// await closeWifiRequest();
|
||||||
|
// if (g?.messageStatus == 2) {
|
||||||
|
// showDialog(
|
||||||
|
// barrierDismissible: true,
|
||||||
|
// context: context,
|
||||||
|
// builder: (cxt) => ConfirmDialog(
|
||||||
|
// message: g?.errorEndUserMessage ?? "",
|
||||||
|
// onTap: () {
|
||||||
|
// Navigator.pop(context);
|
||||||
|
// },
|
||||||
|
// onCloseTap: () {},
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// showMDialog(
|
||||||
|
// context,
|
||||||
|
// backgroundColor: Colors.transparent,
|
||||||
|
// isDismissable: false,
|
||||||
|
// child: SuccessDialog(widget.isFromDashboard),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// } catch (ex) {
|
||||||
|
// await closeWifiRequest();
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// Utils.handleException(ex, context, null);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if (AppState().isAuthenticated) {
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// Utils.confirmDialog(context, "LocaleKeys.comeNearHMGWifi.tr()");
|
||||||
|
// } else {
|
||||||
|
// await closeWifiRequest();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
Future<bool> closeWifiRequest() async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
await WiFiForIoTPlugin.forceWifiUsage(false);
|
||||||
|
}
|
||||||
|
return await WiFiForIoTPlugin.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Future<void> performQrCodeAttendance(DashboardProviderModel model, {String lat = "0", String lng = "0"}) async {
|
||||||
|
// var qrCodeValue = await Navigator.of(context).push(
|
||||||
|
// MaterialPageRoute(
|
||||||
|
// builder: (BuildContext context) => QrScannerDialog(),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// if (qrCodeValue != null) {
|
||||||
|
// Utils.showLoading(context);
|
||||||
|
// try {
|
||||||
|
// GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 1, isGpsRequired: isQrLocationEnabled, lat: lat, long: lng, QRValue: qrCodeValue);
|
||||||
|
// bool status = await model.fetchAttendanceTracking(context);
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// if (g?.messageStatus == 2) {
|
||||||
|
// showDialog(
|
||||||
|
// barrierDismissible: true,
|
||||||
|
// context: context,
|
||||||
|
// builder: (cxt) => ConfirmDialog(
|
||||||
|
// message: g?.errorEndUserMessage ?? "",
|
||||||
|
// onTap: () {
|
||||||
|
// Navigator.pop(context);
|
||||||
|
// },
|
||||||
|
// onCloseTap: () {},
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// showMDialog(
|
||||||
|
// context,
|
||||||
|
// backgroundColor: Colors.transparent,
|
||||||
|
// isDismissable: true,
|
||||||
|
// child: SuccessDialog(widget.isFromDashboard),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// } catch (ex) {
|
||||||
|
// print(ex);
|
||||||
|
// Utils.hideLoading(context);
|
||||||
|
// Utils.handleException(ex, context, null);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
void markFakeAttendance(String sourceName, String lat, String long) async {
|
||||||
|
Utils.showLoading(context);
|
||||||
|
try {
|
||||||
|
// await DashboardApiClient().markFakeLocation(sourceName: sourceName, lat: lat, long: long);
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
Utils.confirmDialog(context, "LocaleKeys.fakeLocation.tr()");
|
||||||
|
} catch (ex) {
|
||||||
|
print(ex);
|
||||||
|
Utils.hideLoading(context);
|
||||||
|
//Utils.handleException(ex, context, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget attendanceMethod(String title, IconData iconData, bool isEnabled, VoidCallback onPress) => Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
color: isEnabled ? null : Colors.grey.withOpacity(.5),
|
||||||
|
gradient: isEnabled
|
||||||
|
? const LinearGradient(
|
||||||
|
transform: GradientRotation(.64),
|
||||||
|
begin: Alignment.topRight,
|
||||||
|
end: Alignment.bottomLeft,
|
||||||
|
colors: [
|
||||||
|
//MyColors.gradiantEndColor,
|
||||||
|
// MyColors.gradiantStartColor,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
padding: const EdgeInsets.only(left: 10, right: 10, top: 14, bottom: 14),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// SvgPicture.asset(image, color: Colors.white, alignment: Alignment.topLeft).expanded,
|
||||||
|
Icon(iconData, color: isEnabled ? AppColor.black35 : Colors.grey),
|
||||||
|
title.heading6(context),
|
||||||
|
// title.toText17(isBold: true, color: Colors.white),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).onPress(
|
||||||
|
() {
|
||||||
|
if (!isEnabled) return;
|
||||||
|
onPress();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,199 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:nfc_manager/nfc_manager.dart';
|
||||||
|
import 'package:nfc_manager/platform_tags.dart';
|
||||||
|
|
||||||
|
void showNfcReader(BuildContext context, {required Function(String? nfcId) onNcfScan}) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
enableDrag: false,
|
||||||
|
isDismissible: false,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)),
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
builder: (context) {
|
||||||
|
return NfcLayout(
|
||||||
|
onNcfScan: onNcfScan,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class NfcLayout extends StatefulWidget {
|
||||||
|
Function(String? nfcId) onNcfScan;
|
||||||
|
|
||||||
|
NfcLayout({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 {
|
||||||
|
var f;
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
f = MifareUltralight(tag: tag, identifier: tag.data["nfca"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22);
|
||||||
|
} else {
|
||||||
|
f = MifareUltralight(tag: tag, identifier: tag.data["mifare"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22);
|
||||||
|
}
|
||||||
|
String identifier = f.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
|
||||||
|
nfcId = identifier;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_reading = true;
|
||||||
|
mainWidget = doneNfc();
|
||||||
|
});
|
||||||
|
|
||||||
|
Future.delayed(const Duration(seconds: 1), () {
|
||||||
|
NfcManager.instance.stopSession();
|
||||||
|
Navigator.pop(context);
|
||||||
|
// if (Platform.isAndroid) {
|
||||||
|
// Navigator.pop(context);
|
||||||
|
// } else {
|
||||||
|
// Navigator.pop(context);
|
||||||
|
// Navigator.pop(context);
|
||||||
|
// }
|
||||||
|
widget.onNcfScan(nfcId);
|
||||||
|
});
|
||||||
|
}).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(
|
||||||
|
key: ValueKey(1),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Ready To Scan",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
Image.asset(
|
||||||
|
"assets/icons/nfc/ic_nfc.png",
|
||||||
|
height: MediaQuery.of(context).size.width / 3,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Approach an NFC Tag",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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: Text("CANCEL"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget doneNfc() {
|
||||||
|
return Container(
|
||||||
|
key: ValueKey(2),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Successfully Scanned",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
Image.asset(
|
||||||
|
"assets/icons/nfc/ic_done.png",
|
||||||
|
height: MediaQuery.of(context).size.width / 3,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Approach an NFC Tag",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:test_sa/extensions/int_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/text_extensions.dart';
|
||||||
|
import 'package:test_sa/extensions/widget_extensions.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
|
||||||
|
|
||||||
|
class ConfirmDialog extends StatelessWidget {
|
||||||
|
final String? title;
|
||||||
|
final String message;
|
||||||
|
final String? okTitle;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
final VoidCallback? onCloseTap;
|
||||||
|
|
||||||
|
const ConfirmDialog({Key? key, this.title, required this.message, this.okTitle, this.onTap, this.onCloseTap}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
shape: const RoundedRectangleBorder(),
|
||||||
|
insetPadding: const EdgeInsets.only(left: 21, right: 21),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 20, right: 20, top: 18, bottom: 28),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
title ?? "Confirm",
|
||||||
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: Colors.black87, height: 35 / 24, letterSpacing: -0.96),
|
||||||
|
).paddingOnly(top: 16),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
color: Colors.black87,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
onPressed: () => onCloseTap ?? Navigator.pop(context),
|
||||||
|
// onPressed: () => Navigator.pop(context),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
message.bodyText(context),
|
||||||
|
28.height,
|
||||||
|
AppFilledButton(
|
||||||
|
label: okTitle ?? "OK",
|
||||||
|
onPressed: onTap ?? () => Navigator.pop(context),
|
||||||
|
textColor: Colors.white,
|
||||||
|
//color: Ap.green,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:just_audio/just_audio.dart';
|
||||||
|
import 'package:lottie/lottie.dart';
|
||||||
|
|
||||||
|
class SuccessDialog extends StatefulWidget {
|
||||||
|
bool isFromDashboard;
|
||||||
|
|
||||||
|
SuccessDialog(this.isFromDashboard);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SuccessDialog> createState() => _SuccessDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SuccessDialogState extends State<SuccessDialog> with TickerProviderStateMixin {
|
||||||
|
late AnimationController _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_controller = AnimationController(vsync: this);
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> playSuccessSound() async {
|
||||||
|
AudioPlayer player = AudioPlayer();
|
||||||
|
String audioAsset = "";
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
audioAsset = "assets/audio/success_tone_android.mp3";
|
||||||
|
} else {
|
||||||
|
audioAsset = "assets/audio/success_tone_ios.caf";
|
||||||
|
}
|
||||||
|
await player.setAsset(audioAsset);
|
||||||
|
await player.load();
|
||||||
|
player.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
double size = MediaQuery.of(context).size.width / 1.8;
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
child: Card(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(25.0),
|
||||||
|
),
|
||||||
|
child: Lottie.asset(
|
||||||
|
'assets/lottie/lt_success.json',
|
||||||
|
repeat: false,
|
||||||
|
reverse: false,
|
||||||
|
controller: _controller,
|
||||||
|
frameRate: FrameRate(60.0),
|
||||||
|
onLoaded: (LottieComposition v) async {
|
||||||
|
await playSuccessSound();
|
||||||
|
_controller
|
||||||
|
..duration = v.duration
|
||||||
|
..forward().whenComplete(() async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (widget.isFromDashboard) Navigator.pop(context);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
||||||
|
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
|
||||||
|
|
||||||
|
class QrScannerDialog extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<QrScannerDialog> createState() => _QrScannerDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _QrScannerDialogState extends State<QrScannerDialog> {
|
||||||
|
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
|
||||||
|
Barcode? result;
|
||||||
|
QRViewController? controller;
|
||||||
|
bool isPicked = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
color: Colors.white,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: QRView(
|
||||||
|
key: qrKey,
|
||||||
|
onQRViewCreated: _onQRViewCreated,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Expanded(
|
||||||
|
// flex: 1,
|
||||||
|
// child: Center(
|
||||||
|
// child: (result != null)
|
||||||
|
// ? Text(
|
||||||
|
// 'Barcode Type: ${result!.format} Data: ${result!.code}')
|
||||||
|
// : Text('Scan a code'),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: AppFilledButton(
|
||||||
|
label: "LocaleKeys.cancel.tr()",
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onQRViewCreated(QRViewController controller) {
|
||||||
|
this.controller = controller;
|
||||||
|
|
||||||
|
controller.scannedDataStream.listen((scanData) {
|
||||||
|
setState(() {
|
||||||
|
result = scanData;
|
||||||
|
if (!isPicked) {
|
||||||
|
isPicked = true;
|
||||||
|
Navigator.pop(context, result!.code);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
controller.pauseCamera();
|
||||||
|
controller.resumeCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue