From c4b7046a90dadafcf0f3d141a17e21eeb1318754 Mon Sep 17 00:00:00 2001 From: devmirza121 Date: Tue, 1 Mar 2022 10:39:16 +0300 Subject: [PATCH 1/7] Dashboard API's 1.3 --- assets/icons/create_req.svg | 7 +++ assets/icons/home.svg | 7 +++ assets/icons/item_for_sale.svg | 3 + assets/icons/work_list.svg | 15 +++++ lib/ui/landing/dashboard_screen.dart | 65 +++++++++++++++++++++- lib/ui/landing/widget/services_widget.dart | 25 +++++---- lib/ui/login/login_screen.dart | 2 +- 7 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 assets/icons/create_req.svg create mode 100644 assets/icons/home.svg create mode 100644 assets/icons/item_for_sale.svg create mode 100644 assets/icons/work_list.svg diff --git a/assets/icons/create_req.svg b/assets/icons/create_req.svg new file mode 100644 index 0000000..a87e809 --- /dev/null +++ b/assets/icons/create_req.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/home.svg b/assets/icons/home.svg new file mode 100644 index 0000000..fb67997 --- /dev/null +++ b/assets/icons/home.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/item_for_sale.svg b/assets/icons/item_for_sale.svg new file mode 100644 index 0000000..0a87567 --- /dev/null +++ b/assets/icons/item_for_sale.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/work_list.svg b/assets/icons/work_list.svg new file mode 100644 index 0000000..a802c53 --- /dev/null +++ b/assets/icons/work_list.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index 92ea49d..747ef1c 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -46,7 +46,7 @@ class _DashboardScreenState extends State { data.fetchWorkListCounter(); data.fetchMissingSwipe(); data.fetchLeaveTicketBalance(); - // data.fetchMenuEntries(); + data.fetchMenuEntries(); } @override @@ -329,6 +329,69 @@ class _DashboardScreenState extends State { ) ], ), + bottomNavigationBar: BottomNavigationBar( + items: [ + BottomNavigationBarItem( + icon: Padding( + padding: const EdgeInsets.all(4.0), + child: SvgPicture.asset( + "assets/icons/home.svg", + width: 20, + height: 20, + ), + ), + label: 'Home', + ), + BottomNavigationBarItem( + icon: Padding( + padding: const EdgeInsets.all(4.0), + child: SvgPicture.asset( + "assets/icons/create_req.svg", + width: 20, + height: 20, + ), + ), + label: 'Create Request', + ), + BottomNavigationBarItem( + icon: Padding( + padding: const EdgeInsets.all(4.0), + child: SvgPicture.asset( + "assets/icons/work_list.svg", + width: 20, + height: 20, + ), + ), + label: 'Work List', + ), + BottomNavigationBarItem( + icon: Padding( + padding: const EdgeInsets.all(4.0), + child: SvgPicture.asset( + "assets/icons/item_for_sale.svg", + width: 20, + height: 20, + ), + ), + label: 'Items for Sale', + ), + ], + currentIndex: 0, + selectedLabelStyle: TextStyle( + fontSize: 8, + color: Color(0xff989898), + fontWeight: FontWeight.w600, + ), + unselectedLabelStyle: TextStyle( + fontSize: 8, + color: Color(0xff989898), + fontWeight: FontWeight.w600, + ), + type: BottomNavigationBarType.fixed, + selectedItemColor: Colors.black, + backgroundColor: Color(0xffF8F8F8), + onTap: (v) {}, + ), ); } } diff --git a/lib/ui/landing/widget/services_widget.dart b/lib/ui/landing/widget/services_widget.dart index f397a4c..89a0aa6 100644 --- a/lib/ui/landing/widget/services_widget.dart +++ b/lib/ui/landing/widget/services_widget.dart @@ -128,18 +128,19 @@ class ServicesWidget extends StatelessWidget { SizedBox( height: 105 + 26, child: ListView.separated( - shrinkWrap: true, - physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.only(left: 21, right: 21, top: 13, bottom: 13), - scrollDirection: Axis.horizontal, - itemBuilder: (cxt, index) { - return AspectRatio( - aspectRatio: 105 / 105, - child: ServicesMenuShimmer(), - ); - }, - separatorBuilder: (cxt, index) => 9.width, - itemCount: 4), + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.only(left: 21, right: 21, top: 13, bottom: 13), + scrollDirection: Axis.horizontal, + itemBuilder: (cxt, index) { + return AspectRatio( + aspectRatio: 105 / 105, + child: ServicesMenuShimmer(), + ); + }, + separatorBuilder: (cxt, index) => 9.width, + itemCount: 4, + ), ), ], ); diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 2353f99..fe7f118 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -63,7 +63,7 @@ class _LoginScreenState extends State { @override Widget build(BuildContext context) { username.text="15153"; - password.text="e123e123e123"; + password.text="s12s12s12"; return Scaffold( body: Column( children: [ From 48a261d674bc728bcd71c27aef32764bee6843cd Mon Sep 17 00:00:00 2001 From: devmirza121 Date: Wed, 2 Mar 2022 10:52:31 +0300 Subject: [PATCH 2/7] Dashboard API's 1.4 --- lib/provider/dashboard_provider_model.dart | 6 +- lib/ui/login/login_screen.dart | 2 +- pubspec.lock | 514 --------------------- pubspec.yaml | 6 +- 4 files changed, 7 insertions(+), 521 deletions(-) delete mode 100644 pubspec.lock diff --git a/lib/provider/dashboard_provider_model.dart b/lib/provider/dashboard_provider_model.dart index b52448e..1a83041 100644 --- a/lib/provider/dashboard_provider_model.dart +++ b/lib/provider/dashboard_provider_model.dart @@ -125,9 +125,9 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { GenericResponseModel? genericResponseModel = await DashbaordApiClient().getListMenu(); Map map = {}; print(jsonEncode(genericResponseModel!.listMenu)); - for (int i = 0; i < genericResponseModel!.listMenu!.length; i++) { - print(genericResponseModel!.listMenu![i]!.menuName ?? ""); - map[genericResponseModel!.listMenu![i]!.menuName ?? ""] = i.toString(); + for (int i = 0; i < genericResponseModel.listMenu!.length; i++) { + print(genericResponseModel.listMenu![i].menuName ?? ""); + map[genericResponseModel.listMenu![i].menuName ?? ""] = i.toString(); } logger.i(map); notifyListeners(); diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 533e9ee..1ad1d62 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -128,7 +128,7 @@ class _LoginScreenState extends State { @override Widget build(BuildContext context) { username.text="15153"; - password.text="Riyadh@1234"; + password.text="Ab123456@"; return Scaffold( body: Column( children: [ diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index 58ce64a..0000000 --- a/pubspec.lock +++ /dev/null @@ -1,514 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.0" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.2" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - easy_localization: - dependency: "direct main" - description: - name: easy_localization - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" - easy_logger: - dependency: transitive - description: - name: easy_logger - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.2" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - ffi: - dependency: transitive - description: - name: ffi - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.2" - file: - dependency: transitive - description: - name: file - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.2" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_countdown_timer: - dependency: "direct main" - description: - name: flutter_countdown_timer - url: "https://pub.dartlang.org" - source: hosted - version: "4.1.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - flutter_localizations: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.5" - flutter_svg: - dependency: "direct main" - description: - name: flutter_svg - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - fluttertoast: - dependency: "direct main" - description: - name: fluttertoast - url: "https://pub.dartlang.org" - source: hosted - version: "8.0.8" - http: - dependency: "direct main" - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.4" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.0" - injector: - dependency: "direct main" - description: - name: injector - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - intl: - dependency: transitive - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.17.0" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.3" - lints: - dependency: transitive - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - local_auth: - dependency: "direct main" - description: - name: local_auth - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.9" - logger: - dependency: "direct main" - description: - name: logger - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.11" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" - nested: - dependency: transitive - description: - name: nested - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - path_drawing: - dependency: transitive - description: - name: path_drawing - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - path_parsing: - dependency: transitive - description: - name: path_parsing - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - path_provider: - dependency: "direct main" - description: - name: path_provider - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.8" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.9" - path_provider_ios: - dependency: transitive - description: - name: path_provider_ios - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.7" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" - path_provider_macos: - dependency: transitive - description: - name: path_provider_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - permission_handler: - dependency: "direct main" - description: - name: permission_handler - url: "https://pub.dartlang.org" - source: hosted - version: "8.3.0" - permission_handler_platform_interface: - dependency: transitive - description: - name: permission_handler_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "3.7.0" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "4.4.0" - platform: - dependency: transitive - description: - name: platform - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.2" - process: - dependency: transitive - description: - name: process - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.4" - provider: - dependency: "direct main" - description: - name: provider - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.1" - shared_preferences: - dependency: transitive - description: - name: shared_preferences - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.11" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.9" - shared_preferences_ios: - dependency: transitive - description: - name: shared_preferences_ios - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.8" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.3" - shared_preferences_macos: - dependency: transitive - description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.2" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.2" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.3" - shimmer: - dependency: "direct main" - description: - name: shimmer - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - sizer: - dependency: "direct main" - description: - name: sizer - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.15" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.3" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - universal_io: - dependency: transitive - description: - name: universal_io - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - win32: - dependency: transitive - description: - name: win32 - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.1" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "5.3.1" -sdks: - dart: ">=2.14.0 <3.0.0" - flutter: ">=2.5.0" diff --git a/pubspec.yaml b/pubspec.yaml index ca8fe9c..37a3352 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.16.0 <3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -39,13 +39,13 @@ dependencies: provider: ^6.0.1 easy_localization: ^3.0.0 http: ^0.13.4 - permission_handler: ^8.3.0 + permission_handler: ^9.2.0 flutter_svg: ^1.0.0 sizer: ^2.0.15 local_auth: ^1.1.9 fluttertoast: ^8.0.8 shared_preferences: ^2.0.12 - firebase_messaging: ^11.2.6 + firebase_messaging: ^11.2.8 shimmer: ^2.0.0 logger: ^1.1.0 flutter_countdown_timer: ^4.1.0 From 68729ddbb9294650fefc85c81c878aeb0ce89bed Mon Sep 17 00:00:00 2001 From: devmirza121 Date: Mon, 14 Mar 2022 15:41:23 +0300 Subject: [PATCH 3/7] NFC Attendance implement --- android/app/src/main/AndroidManifest.xml | 3 + assets/icons/nfc/ic_done.png | Bin 0 -> 18338 bytes assets/icons/nfc/ic_nfc.png | Bin 0 -> 19686 bytes lib/api/dashboard_api_client.dart | 24 ++ lib/app_state/app_state.dart | 2 +- lib/classes/app_permissions.dart | 30 ++ lib/classes/consts.dart | 4 +- lib/classes/utils.dart | 4 + lib/main.dart | 143 +++++++- lib/models/member_information_list_model.dart | 2 +- lib/provider/dashboard_provider_model.dart | 37 +- lib/ui/landing/dashboard_screen.dart | 10 +- lib/ui/landing/today_attendance_screen.dart | 334 ++++++++++++------ lib/ui/login/login_screen.dart | 8 +- lib/widgets/location/Location.dart | 249 +++++++++++++ lib/widgets/nfc/nfc_reader_sheet.dart | 187 ++++++++++ lib/widgets/swipe/nfc_reader_sheet.dart | 194 ++++++++++ pubspec.yaml | 12 + 18 files changed, 1105 insertions(+), 138 deletions(-) create mode 100644 assets/icons/nfc/ic_done.png create mode 100644 assets/icons/nfc/ic_nfc.png create mode 100644 lib/classes/app_permissions.dart create mode 100644 lib/widgets/location/Location.dart create mode 100644 lib/widgets/nfc/nfc_reader_sheet.dart create mode 100644 lib/widgets/swipe/nfc_reader_sheet.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8d53eaf..7be27d5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,9 @@ package="com.mohem_flutter_app"> + + + diff --git a/assets/icons/nfc/ic_done.png b/assets/icons/nfc/ic_done.png new file mode 100644 index 0000000000000000000000000000000000000000..5b802855ce8942032b0948c1fdaa56e885b4c78e GIT binary patch literal 18338 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 literal 0 HcmV?d00001 diff --git a/assets/icons/nfc/ic_nfc.png b/assets/icons/nfc/ic_nfc.png new file mode 100644 index 0000000000000000000000000000000000000000..274e1b8c0d81daca3127e8a2b6d0522b43d6bb8d GIT binary patch literal 19686 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 literal 0 HcmV?d00001 diff --git a/lib/api/dashboard_api_client.dart b/lib/api/dashboard_api_client.dart index 76e2453..f4c583d 100644 --- a/lib/api/dashboard_api_client.dart +++ b/lib/api/dashboard_api_client.dart @@ -8,6 +8,7 @@ import 'package:mohem_flutter_app/models/dashboard/get_attendance_tracking_list_ import 'package:mohem_flutter_app/models/dashboard/itg_forms_model.dart'; import 'package:mohem_flutter_app/models/generic_response_model.dart'; import 'package:mohem_flutter_app/models/member_login_list_model.dart'; +import 'package:uuid/uuid.dart'; import 'api_client.dart'; @@ -89,4 +90,27 @@ class DashboardApiClient { return responseData; }, url, postParams); } + + //Mark Attendance + Future markAttendance({String lat = "0", String? long = "0", required int pointType, String nfcValue = "", bool isGpsRequired = false}) async { + String url = "${ApiConsts.swpRest}AuthenticateAndSwipeUserSupportNFC"; + var uuid = Uuid(); + // Generate a v4 (random) id + + Map postParams = { + "UID": uuid.v4(), //Mobile Id + "Latitude": lat, + "Longitude": long, + "QRValue": "", + "PointType": pointType, // NFC=2, Wifi = 3, QR= 1, + "NFCValue": nfcValue, + "WifiValue": "", + "IsGpsRequired": isGpsRequired + }; + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject((json) { + GenericResponseModel responseData = GenericResponseModel.fromJson(json); + return responseData; + }, url, postParams); + } } diff --git a/lib/app_state/app_state.dart b/lib/app_state/app_state.dart index 50aff0a..08d2f70 100644 --- a/lib/app_state/app_state.dart +++ b/lib/app_state/app_state.dart @@ -36,6 +36,7 @@ class AppState { bool isArabic(context) => EasyLocalization.of(context)?.locale.languageCode == "ar"; String? _username; + // todo ''sikander' added password for now, later will remove & improve String? password; @@ -43,7 +44,6 @@ class AppState { String? get getUserName => _username; - set setUserPassword(_password) => password = _password; MemberLoginListModel? _memberLoginList; diff --git a/lib/classes/app_permissions.dart b/lib/classes/app_permissions.dart new file mode 100644 index 0000000..a70342c --- /dev/null +++ b/lib/classes/app_permissions.dart @@ -0,0 +1,30 @@ +import 'package:permission_handler/permission_handler.dart'; + +class AppPermissions{ + static location(Function(bool) completion) { + Permission.location.isGranted.then((isGranted){ + if(!isGranted){ + Permission.location.request().then((granted){ + completion(granted == PermissionStatus.granted); + }); + } + completion(isGranted); + }); + + } + + static checkAll(Function(bool) completion){ + [ + Permission.location + ].request().then((value){ + + bool allGranted = false; + value.values.forEach((element) { + allGranted = allGranted && element == PermissionStatus.granted; + }); + + completion(allGranted); + + }); + } +} \ No newline at end of file diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index a11edec..a95e5b4 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -1,10 +1,12 @@ class ApiConsts { //static String baseUrl = "http://10.200.204.20:2801/"; // Local server static String baseUrl = "https://uat.hmgwebservices.com"; // UAT server - static String baseUrlServices = baseUrl + "/services/"; // server + // static String baseUrl = "https://hmgwebservices.com"; // Live server + static String baseUrlServices = baseUrl + "/Services/"; // server // static String baseUrlServices = "https://api.cssynapses.com/tangheem/"; // Live server static String utilitiesRest = baseUrlServices + "Utilities.svc/REST/"; static String erpRest = baseUrlServices + "ERP.svc/REST/"; + static String swpRest = baseUrlServices + "SWP.svc/REST/"; static String user = baseUrlServices + "api/User/"; static String cocRest = baseUrlServices + "COCWS.svc/REST/"; } diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart index ae8041f..fc942c4 100644 --- a/lib/classes/utils.dart +++ b/lib/classes/utils.dart @@ -46,6 +46,10 @@ class Utils { }); } + static Future delay(int millis) async { + return await Future.delayed(Duration(milliseconds: millis)); + } + static void hideLoading(BuildContext context) { if (_isLoadingVisible) { _isLoadingVisible = false; diff --git a/lib/main.dart b/lib/main.dart index 20759d9..29a8e17 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -8,6 +9,9 @@ import 'package:mohem_flutter_app/generated/codegen_loader.g.dart'; import 'package:mohem_flutter_app/models/post_params_model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/theme/app_theme.dart'; +import 'package:mohem_flutter_app/widgets/nfc/nfc_reader_sheet.dart'; +import 'package:nfc_manager/nfc_manager.dart'; +import 'package:nfc_manager/platform_tags.dart'; import 'package:provider/provider.dart'; import 'package:sizer/sizer.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -25,7 +29,7 @@ Future main() async { await EasyLocalization.ensureInitialized(); await Firebase.initializeApp(); AppState().setPostParamsModel( - PostParamsModel(channel: 31, versionID: 3.2, mobileType: Platform.isAndroid ? "android" : "ios"), + PostParamsModel(channel: 31, versionID: 3.4, mobileType: Platform.isAndroid ? "android" : "ios"), ); runApp( EasyLocalization( @@ -77,3 +81,140 @@ class MyApp extends StatelessWidget { ); } } + +// class MyApp extends StatefulWidget { +// @override +// State createState() => MyAppState(); +// } +// +// class MyAppState extends State { +// ValueNotifier result = ValueNotifier(null); +// +// @override +// Widget build(BuildContext context) { +// return MaterialApp( +// home: Scaffold( +// appBar: AppBar(title: Text('NfcManager Plugin Example')), +// body: SafeArea( +// child: FutureBuilder( +// future: NfcManager.instance.isAvailable(), +// builder: (context, ss) => ss.data != true +// ? Center(child: Text('NfcManager.isAvailable(): ${ss.data}')) +// : Flex( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// direction: Axis.vertical, +// children: [ +// Flexible( +// flex: 2, +// child: Container( +// margin: EdgeInsets.all(4), +// constraints: BoxConstraints.expand(), +// decoration: BoxDecoration(border: Border.all()), +// child: SingleChildScrollView( +// child: ValueListenableBuilder( +// valueListenable: result, +// builder: (context, value, _) => Text('${value ?? ''}'), +// ), +// ), +// ), +// ), +// Flexible( +// flex: 3, +// child: GridView.count( +// padding: EdgeInsets.all(4), +// crossAxisCount: 2, +// childAspectRatio: 4, +// crossAxisSpacing: 4, +// mainAxisSpacing: 4, +// children: [ +// ElevatedButton(child: Text('Tag Read'), onPressed: _tagRead), +// ElevatedButton(child: Text('Ndef Write'), onPressed: _ndefWrite), +// ElevatedButton(child: Text('Ndef Write Lock'), onPressed: _ndefWriteLock), +// ], +// ), +// ), +// ], +// ), +// ), +// ), +// ), +// ); +// } +// +// void _tagRead() { +// showNfcReader( +// context, +// onNcfScan: (String? nfcId) {}, +// ); +// // NfcManager.instance.startSession(onDiscovered: (NfcTag tag) async { +// // result.value = tag.data; +// // print(tag.data); +// // var ndef = Ndef.from(tag); +// // +// // var f = MifareUltralight(tag: tag, identifier: tag.data["nfca"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22); +// // final String identifier = f.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); +// // print(identifier); // => 0428fcf2255e81 +// // print(ndef!.additionalData); +// // +// // // onDiscovered callback +// // // final mifare = MiFare.from(tag); +// // // if (mifare == null) { +// // // print('Tag is not compatible with MiFare.'); +// // // return; +// // // } +// // // print(mifare.identifier); +// // // final String identifier = mifare.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); +// // // print(identifier); // => 0428fcf2255e81 +// // NfcManager.instance.stopSession(); +// // }); +// } +// +// void _ndefWrite() { +// NfcManager.instance.startSession(onDiscovered: (NfcTag tag) async { +// var ndef = Ndef.from(tag); +// if (ndef == null || !ndef.isWritable) { +// result.value = 'Tag is not ndef writable'; +// NfcManager.instance.stopSession(errorMessage: result.value); +// return; +// } +// +// NdefMessage message = NdefMessage([ +// NdefRecord.createText('Hello World!'), +// NdefRecord.createUri(Uri.parse('https://flutter.dev')), +// NdefRecord.createMime('text/plain', Uint8List.fromList('Hello'.codeUnits)), +// NdefRecord.createExternal('com.example', 'mytype', Uint8List.fromList('mydata'.codeUnits)), +// ]); +// +// try { +// await ndef.write(message); +// result.value = 'Success to "Ndef Write"'; +// NfcManager.instance.stopSession(); +// } catch (e) { +// result.value = e; +// NfcManager.instance.stopSession(errorMessage: result.value.toString()); +// return; +// } +// }); +// } +// +// void _ndefWriteLock() { +// NfcManager.instance.startSession(onDiscovered: (NfcTag tag) async { +// var ndef = Ndef.from(tag); +// if (ndef == null) { +// result.value = 'Tag is not ndef'; +// NfcManager.instance.stopSession(errorMessage: result.value.toString()); +// return; +// } +// +// try { +// await ndef.writeLock(); +// result.value = 'Success to "Ndef Write Lock"'; +// NfcManager.instance.stopSession(); +// } catch (e) { +// result.value = e; +// NfcManager.instance.stopSession(errorMessage: result.value.toString()); +// return; +// } +// }); +// } +// } diff --git a/lib/models/member_information_list_model.dart b/lib/models/member_information_list_model.dart index da95ed0..dc64e53 100644 --- a/lib/models/member_information_list_model.dart +++ b/lib/models/member_information_list_model.dart @@ -32,7 +32,7 @@ class MemberInformationListModel { String? fREQUENCY; String? fREQUENCYMEANING; int? fROMROWNUM; - String? gRADEID; + int? gRADEID; String? gRADENAME; String? hIREDATE; int? jOBID; diff --git a/lib/provider/dashboard_provider_model.dart b/lib/provider/dashboard_provider_model.dart index f15e244..52fb6ad 100644 --- a/lib/provider/dashboard_provider_model.dart +++ b/lib/provider/dashboard_provider_model.dart @@ -19,6 +19,7 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { //Attendance Tracking bool isAttendanceTrackingLoading = true; int endTime = 0, isTimeRemainingInSeconds = 0; + double progress = 0.0; GetAttendanceTracking? attendanceTracking; //Work List @@ -40,19 +41,27 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { List? getMenuEntriesList; //Attendance Tracking API's & Methods - void fetchAttendanceTracking() async { + Future fetchAttendanceTracking() async { try { attendanceTracking = await DashboardApiClient().getAttendanceTracking(); + print("attendanceTracking:" + (attendanceTracking!.pRemainingHours).toString()); isAttendanceTrackingLoading = false; - isTimeRemainingInSeconds = calculateSeconds("00:00:00"); - endTime = DateTime.now().millisecondsSinceEpoch + Duration(seconds: isTimeRemainingInSeconds).inMilliseconds; - print("isTimeRemainingInSeconds " + isTimeRemainingInSeconds.toString()); - print("endTime " + endTime.toString()); + // isTimeRemainingInSeconds = calculateSeconds( "00:00:00"); + if (attendanceTracking?.pSwipeIn != null) { + isTimeRemainingInSeconds = calculateSeconds(attendanceTracking!.pRemainingHours ?? "00:00:00"); + int totalShiftTimeInSeconds = calculateSeconds(attendanceTracking!.pScheduledHours ?? "00:00:00"); + print("totalShiftTimeInSeconds: " + totalShiftTimeInSeconds.toString()); + print("isTimeRemainingInSeconds: " + isTimeRemainingInSeconds.toString()); + progress = (isTimeRemainingInSeconds / totalShiftTimeInSeconds); + endTime = DateTime.now().millisecondsSinceEpoch + Duration(seconds: isTimeRemainingInSeconds).inMilliseconds; + print("endTime " + endTime.toString()); + } - // notifyListeners(); + notifyListeners(); } catch (ex) { Utils.handleException(ex, null); } + return true; } int calculateSeconds(String time) { @@ -63,16 +72,18 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } update() { - isAttendanceTrackingLoading = !isAttendanceTrackingLoading; - isWorkListLoading = !isWorkListLoading; - attendanceTracking?.pSwipeIn = "a"; - isTimeRemainingInSeconds = calculateSeconds("00:10:30"); - endTime = DateTime.now().millisecondsSinceEpoch + Duration(seconds: isTimeRemainingInSeconds).inMilliseconds; - notifyListeners(); + fetchAttendanceTracking(); + // isAttendanceTrackingLoading = !isAttendanceTrackingLoading; + // isWorkListLoading = !isWorkListLoading; + // attendanceTracking?.pSwipeIn = "a"; + // isTimeRemainingInSeconds = calculateSeconds("00:10:30"); + // endTime = DateTime.now().millisecondsSinceEpoch + Duration(seconds: isTimeRemainingInSeconds).inMilliseconds; + // notifyListeners(); } ItgFormsModel? itgFormsModel; List? getOpenNotificationsList; + //Work List API's & Methods Future fetchWorkListCounter(context, {bool showLoading = false}) async { try { @@ -167,7 +178,7 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } } - //Verify Menus by printing in log + // Verify Menus by printing in log // for(int i=0;i { ), LocaleKeys.timeLeftToday.tr().toText12(color: Colors.white), 9.height, - const ClipRRect( + ClipRRect( borderRadius: BorderRadius.all( Radius.circular(20), ), child: LinearProgressIndicator( - value: 0.7, + value: model.progress, minHeight: 8, valueColor: const AlwaysStoppedAnimation(Colors.white), backgroundColor: const Color(0xff196D73), @@ -184,8 +184,10 @@ class _DashboardScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.checkIn.tr().toText12(color: Colors.white), - (model.isTimeRemainingInSeconds == 0 ? "--:--" : "09:00").toText14(color: Colors.white, isBold: true), - 4.height + (model.attendanceTracking!.pSwipeIn == null ? "--:--" : model.attendanceTracking!.pSwipeIn) + .toString() + .toText14(color: Colors.white, isBold: true), + 4.height, ], ).paddingOnly(left: 12), ), diff --git a/lib/ui/landing/today_attendance_screen.dart b/lib/ui/landing/today_attendance_screen.dart index b2ab7aa..2008484 100644 --- a/lib/ui/landing/today_attendance_screen.dart +++ b/lib/ui/landing/today_attendance_screen.dart @@ -1,12 +1,25 @@ import 'package:easy_localization/src/public_ext.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_countdown_timer/flutter_countdown_timer.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:google_maps_flutter/google_maps_flutter.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/date_uitl.dart'; +import 'package:mohem_flutter_app/classes/utils.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/models/generic_response_model.dart'; import 'package:mohem_flutter_app/widgets/circular_step_progress_bar.dart'; +import 'package:mohem_flutter_app/widgets/nfc/nfc_reader_sheet.dart'; +import 'package:nfc_manager/nfc_manager.dart'; +import 'package:provider/provider.dart'; + +import '../../provider/dashboard_provider_model.dart'; +import '../../widgets/location/Location.dart'; class TodayAttendanceScreen extends StatefulWidget { TodayAttendanceScreen({Key? key}) : super(key: key); @@ -18,14 +31,45 @@ class TodayAttendanceScreen extends StatefulWidget { } class _TodayAttendanceScreenState extends State { + ValueNotifier result = ValueNotifier(null); + late DashboardProviderModel data; + bool isNfcEnabled = false, isNfcLocationEnabled = false, isQrEnabled = false, isQrLocationEnabled = false, isWifiEnabled = false, isWifiLocationEnabled = false; + @override void initState() { super.initState(); + checkAttendanceAvailablity(); + data = Provider.of(context, listen: false); + } + + checkAttendanceAvailablity() async { + bool isAvailable = await NfcManager.instance.isAvailable(); + setState(() { + AppState().privilegeListModel!.forEach((element) { + // Check availability + if (isAvailable) if (element.serviceName == "enableNFC") { + // 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 == "enableLocatoinNFC") { + 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; + } + }); + }); } @override void dispose() { super.dispose(); + // Stop Session + NfcManager.instance.stopSession(); } @override @@ -37,123 +81,170 @@ class _TodayAttendanceScreenState extends State { icon: const Icon(Icons.arrow_back_ios, color: Colors.white), onPressed: () => Navigator.pop(context), ), - ), - backgroundColor: Colors.white, - body: ListView( - children: [ - Container( - color: MyColors.backgroundBlackColor, - padding: EdgeInsets.only(top: 4,left: 21, right: 21, bottom: 21), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "June 13, 2021".toText24(isBold: true, color: Colors.white), - LocaleKeys.timeLeftToday.tr().toText16(color: Color(0xffACACAC)), - 21.height, - Center( - child: CircularStepProgressBar( - totalSteps: 16 * 4, - currentStep: 16, - width: 216, - height: 216, - selectedColor: MyColors.gradiantEndColor, - unselectedColor: MyColors.grey70Color, - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - "08:58:15".toText32(color: Colors.white, isBold: true), - 19.height, - "Shift Time".tr().toText12(color: MyColors.greyACColor), - "08:00 - 17:00".toText22(color: Colors.white, isBold: true), - ], - ), - ), - ), - ), - ], - ), - ), - Container( - color: MyColors.backgroundBlackColor, - child: Stack( - children: [ - Container( - height: 187, - padding: EdgeInsets.only(left: 31, right: 31, top: 31, bottom: 16), - decoration: BoxDecoration( - borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), - gradient: const LinearGradient(transform: GradientRotation(.64), begin: Alignment.topRight, end: Alignment.bottomLeft, colors: [ - MyColors.gradiantEndColor, - MyColors.gradiantStartColor, - ]), - ), - child: Column( - children: [ - Row( - children: [commonStatusView("Check In", "09:27"), commonStatusView("Check Out", "- - : - -")], - ), - 21.height, - Row( - children: [commonStatusView("Late In", "00:27"), commonStatusView("Regular", "08:00")], - ), - ], - ), - ), - Container( - width: double.infinity, - decoration: BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: Colors.white), - margin: EdgeInsets.only(top: 187 - 31), - padding: EdgeInsets.only(left: 21, right: 21, top: 24, bottom: 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - "Mark".tr().toText12(), - "Attendance".tr().toText24(), - "Select the method to mark the attendance".tr().toText12(color: Color(0xff535353)), - 24.height, - GridView( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - padding: EdgeInsets.zero, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1 / 1, crossAxisSpacing: 8, mainAxisSpacing: 8), - children: [ - attendanceMethod("NFC", "assets/images/nfc.svg", () {}), - attendanceMethod("Wifi", "assets/images/wufu.svg", () {}), - ], - ) - ], - ), - ), - // Positioned( - // top: 187 - 21, - // child: Container( - // padding: EdgeInsets.only(left: 31, right: 31, top: 31, bottom: 16), - // decoration: BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: Colors.white), - // child: Column( - // children: [ - // Row( - // children: [commonStatusView("Check In", "09:27"), commonStatusView("Check Out", "- - : - -")], - // ), - // 21.height, - // Row( - // children: [commonStatusView("Late In", "00:27"), commonStatusView("Regular", "08:00")], - // ), - // ], - // ), - // ), - // ), - ], + actions: [ + IconButton( + onPressed: () { + data.fetchAttendanceTracking(); + }, + icon: Icon( + Icons.ac_unit, + color: Colors.white, ), ) ], ), + backgroundColor: MyColors.backgroundBlackColor, + body: Consumer( + builder: (context, model, child) { + return (model.isAttendanceTrackingLoading + ? Center(child: CircularProgressIndicator()) + : ListView( + children: [ + Container( + color: MyColors.backgroundBlackColor, + padding: EdgeInsets.only(top: 4, left: 21, right: 21, bottom: 21), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DateUtil.getWeekDayMonthDayYearDateFormatted(DateTime.now(), "en").toText24(isBold: true, color: Colors.white), + LocaleKeys.timeLeftToday.tr().toText16(color: Color(0xffACACAC)), + 21.height, + Center( + child: CircularStepProgressBar( + totalSteps: 16 * 4, + currentStep: (model.progress * 100).toInt(), + width: 216, + height: 216, + selectedColor: MyColors.gradiantEndColor, + unselectedColor: MyColors.grey70Color, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CountdownTimer( + endTime: model.endTime, + onEnd: null, + endWidget: "00:00:00".toText32(color: Colors.white, isBold: true), + textStyle: TextStyle(color: Colors.white, fontSize: 32, letterSpacing: -1.92, fontWeight: FontWeight.bold, height: 1), + ), + 19.height, + "Shift Time".tr().toText12(color: MyColors.greyACColor), + (model.attendanceTracking!.pShtName ?? "00:00:00").toString().toText22(color: Colors.white, isBold: true), + ], + ), + ), + ), + ), + ], + ), + ), + Container( + color: MyColors.backgroundBlackColor, + child: Stack( + children: [ + Container( + height: 187, + padding: EdgeInsets.only(left: 31, right: 31, top: 31, bottom: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), + gradient: const LinearGradient(transform: GradientRotation(.64), begin: Alignment.topRight, end: Alignment.bottomLeft, colors: [ + MyColors.gradiantEndColor, + MyColors.gradiantStartColor, + ]), + ), + child: Column( + children: [ + Row( + children: [ + commonStatusView("Check In", (model.attendanceTracking!.pSwipeIn) ?? "- - : - -"), + commonStatusView("Check Out", (model.attendanceTracking!.pSwipeOut) ?? "- - : - -") + ], + ), + 21.height, + Row( + children: [ + commonStatusView("Late In", (model.attendanceTracking!.pLateInHours) ?? "- - : - -"), + commonStatusView("Regular", (model.attendanceTracking!.pScheduledHours) ?? "- - : - -") + ], + ), + ], + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: Colors.white), + margin: EdgeInsets.only(top: 187 - 31), + padding: EdgeInsets.only(left: 21, right: 21, top: 24, bottom: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + "Mark".tr().toText12(), + "Attendance".tr().toText24(), + "Select the method to mark the attendance".tr().toText12(color: Color(0xff535353)), + 24.height, + GridView( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: EdgeInsets.zero, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1 / 1, crossAxisSpacing: 8, mainAxisSpacing: 8), + children: [ + attendanceMethod("NFC", "assets/images/nfc.svg", isNfcEnabled, () { + showNfcReader(context, onNcfScan: (String? nfcId) async { + print(nfcId); + Utils.showLoading(context); + try { + GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? ""); + bool status = await model.fetchAttendanceTracking(); + Utils.hideLoading(context); + } catch (ex) { + print(ex); + Utils.hideLoading(context); + Utils.handleException(ex, (msg) { + Utils.confirmDialog(context, msg); + }); + } + }); + // Location.getCurrentLocation((LatLng? latlng) { + // print(latlng!.longitude.toString()); + // }); + }), + attendanceMethod("Wifi", "assets/images/wufu.svg", isWifiEnabled, () {}), + ], + ) + ], + ), + ), + // Positioned( + // top: 187 - 21, + // child: Container( + // padding: EdgeInsets.only(left: 31, right: 31, top: 31, bottom: 16), + // decoration: BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: Colors.white), + // child: Column( + // children: [ + // Row( + // children: [commonStatusView("Check In", "09:27"), commonStatusView("Check Out", "- - : - -")], + // ), + // 21.height, + // Row( + // children: [commonStatusView("Late In", "00:27"), commonStatusView("Regular", "08:00")], + // ), + // ], + // ), + // ), + // ), + ], + ), + ) + ], + )) + .animatedSwither(); + }, + ), ); } - Widget attendanceMethod(String title, String image, VoidCallback onPress) => Container( - padding: const EdgeInsets.only(left: 10, right: 10, top: 14, bottom: 14), + Widget attendanceMethod(String title, String image, bool isEnabled, VoidCallback onPress) => Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), gradient: const LinearGradient(transform: GradientRotation(.64), begin: Alignment.topRight, end: Alignment.bottomLeft, colors: [ @@ -161,9 +252,26 @@ class _TodayAttendanceScreenState extends State { MyColors.gradiantStartColor, ]), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [Expanded(child: SvgPicture.asset(image)), title.toText17(isBold: true, color: Colors.white)], + clipBehavior: Clip.antiAlias, + child: Stack( + children: [ + Container( + padding: const EdgeInsets.only(left: 10, right: 10, top: 14, bottom: 14), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: SvgPicture.asset(image)), + title.toText17(isBold: true, color: Colors.white), + ], + ), + ), + if (!isEnabled) + Container( + width: double.infinity, + height: double.infinity, + color: Colors.grey.withOpacity(0.7), + ) + ], ), ).onPress(onPress); diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 06ecb22..d5a6061 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -69,12 +69,12 @@ class _LoginScreenState extends State { } String? firebaseToken; - GetMobileLoginInfoListModel? loginInfo; + Future checkFirebaseToken() async { try { Utils.showLoading(context); firebaseToken = await _firebaseMessaging.getToken(); - loginInfo = await LoginApiClient().getMobileLoginInfoNEW(firebaseToken ?? "", Platform.isAndroid ? "android" : "ios"); + GetMobileLoginInfoListModel? loginInfo = await LoginApiClient().getMobileLoginInfoNEW(firebaseToken ?? "", Platform.isAndroid ? "android" : "ios"); if (loginInfo == null) { Utils.hideLoading(context); print("Device token not found"); @@ -112,7 +112,7 @@ class _LoginScreenState extends State { } Utils.hideLoading(context); if (_autoLogin) { - Navigator.pushNamed(context, AppRoutes.verifyLastLogin, arguments: loginInfo); + Navigator.pushNamed(context, AppRoutes.verifyLastLogin); } else { Navigator.pushNamed(context, AppRoutes.verifyLogin, arguments: "$firebaseToken"); } @@ -128,7 +128,7 @@ class _LoginScreenState extends State { @override Widget build(BuildContext context) { username.text="15153"; - password.text="Ab123456@"; + password.text="Xy12345@"; return Scaffold( body: Column( children: [ diff --git a/lib/widgets/location/Location.dart b/lib/widgets/location/Location.dart new file mode 100644 index 0000000..bd39e2e --- /dev/null +++ b/lib/widgets/location/Location.dart @@ -0,0 +1,249 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:google_directions_api/google_directions_api.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +// import 'package:geodesy/geodesy.dart' as geodesy; + +import '../../classes/app_permissions.dart'; +import '../../theme/colors.dart'; + + +//Created By Mr.Zohaib +class Location { + static _Map map = _Map(); + + static havePermission(Function(bool) callback) { + Geolocator.checkPermission().then((value) async { + if (value == LocationPermission.denied) { + value = await Geolocator.requestPermission(); + callback(![LocationPermission.denied, LocationPermission.deniedForever].contains(value)); + } else { + callback(true); + } + }); + } + + static isEnabled(Function(bool) callback) { + Geolocator.isLocationServiceEnabled().then((value) => callback(value)); + } + + static bool _listeningSettingChange = true; + + static listenGPS({bool change = true, Function(bool)? onChange}) async { + _listeningSettingChange = change; + if (change == false) return; + + Future.doWhile(() async { + await Utils.delay(1000); + var enable = await Geolocator.isLocationServiceEnabled(); + onChange!(enable); + return _listeningSettingChange; + }); + } + + static getCurrentLocation(Function(LatLng?) callback) { + done(Position position) { + //AppStorage.sp.saveLocation(position); + + LatLng? myCurrentLocation = LatLng(position.latitude, position.longitude); + callback(myCurrentLocation); + } + + AppPermissions.location((granted) { + + if (granted) + Geolocator.getLastKnownPosition(forceAndroidLocationManager: true).then((value) { + if (value == null) { + Geolocator.getCurrentPosition().then((value) { + done(value); + }); + } else { + done(value); + } + }); + }); + } + + // static LatLng locationAwayFrom( + // {required LatLng loc1, num distanceMeters = 200.0, num bearing = 270.0}) { + // geodesy.LatLng l1 = geodesy.LatLng(loc1.latitude, loc1.longitude); + // geodesy.LatLng destinationPoint = geodesy.Geodesy() + // .destinationPointByDistanceAndBearing(l1, distanceMeters, bearing); + // return LatLng(destinationPoint.latitude, destinationPoint.longitude); + // } + + static Future distanceTo(LatLng destination) async { + var myLoc = await Geolocator.getLastKnownPosition(); + var distance = 0.0; + if (myLoc != null) { + distance = Geolocator.distanceBetween(destination.latitude, destination.longitude, myLoc.latitude, myLoc.longitude); + } + return distance; + } +} + +class _Map { + Marker createMarker( + String id, { + required LatLng coordinates, + BitmapDescriptor? icon, + VoidCallback? onTap, + }) { + final MarkerId markerId = MarkerId(id); + return Marker( + icon: icon ?? BitmapDescriptor.defaultMarker, + markerId: markerId, + position: coordinates, + flat: false, + // infoWindow: InfoWindow(title: id, snippet: '*'), + onTap: onTap, + ); + } + + CameraPosition initialCamera({required Completer mapController, LatLng? position, double zoom = 12}) { + position = position ?? LatLng(24.7249303, 46.5416656); + CameraPosition riyadhEye = CameraPosition( + target: position, + zoom: zoom, + ); + mapController.future.then((controller) { + controller.animateCamera(CameraUpdate.newCameraPosition(riyadhEye)); + }); + return riyadhEye; + } + + CameraPosition moveTo(LatLng location, {double zoom = 12, double direction = 0.0, required Completer mapController, bool? animation}) { + var camera = CameraPosition(target: location, zoom: zoom, bearing: direction); + mapController.future.then((controller) { + animation ?? false ? controller.animateCamera(CameraUpdate.newCameraPosition(camera)) : controller.moveCamera(CameraUpdate.newCameraPosition(camera)); + }); + return camera; + } + + moveCamera(CameraPosition camera, @required Completer mapController, bool animation) { + mapController.future.then((controller) { + animation ? controller.animateCamera(CameraUpdate.newCameraPosition(camera)) : controller.moveCamera(CameraUpdate.newCameraPosition(camera)); + }); + } + + scrollBy({double x = 0, double y = 0, required Completer mapController, bool animation = true}) { + var camera = CameraUpdate.scrollBy(x, y); + mapController.future.then((controller) { + animation ? controller.animateCamera(camera) : controller.moveCamera(camera); + }); + } + + goToCurrentLocation({Completer? mapController, double? direction = 0.0, bool? animation}) { + Location.getCurrentLocation((location) { + moveTo(location!, zoom: 17, mapController: mapController!, animation: animation, direction: direction!); + }); + } + + var routes = Map(); + + setRoutePolylines(LatLng? source, LatLng? destination, Set polylines, Completer mapController, Function(DirectionsRoute?) completion) { + if (source == null || destination == null) { + completion(null); + return; + } + + var origin = '${source.latitude},${source.longitude}'; + var destin = '${destination.latitude},${destination.longitude}'; + var routeId = '$origin->$destination'; + + createPolyline(DirectionsRoute results) { + List polylineCoordinates = results.overviewPath!.map((e) => LatLng(e.latitude, e.longitude)).toList(); + PolylineId id = PolylineId("route"); + Polyline polyline = Polyline( + polylineId: id, + color: accentColor, + width: 5, + jointType: JointType.round, + startCap: Cap.roundCap, + endCap: Cap.roundCap, + points: polylineCoordinates, + ); + + polylines.removeWhere((element) => true); + polylines.add(polyline); + + LatLngBounds bound = getBounds(coordinates: polylineCoordinates); + focusCameraToLatLngBounds(bound: bound, mapController: mapController, padding: 100); + completion(routes[routeId]); + } + + var availableRoute = routes[routeId]; + if (availableRoute == null) { + var request = DirectionsRequest(origin: origin, destination: destin); + DirectionsService().route(request, (response, status) { + if (status == DirectionsStatus.ok && response.routes!.isNotEmpty) { + routes[routeId] = response.routes!.first; + createPolyline(response.routes!.first); + } + }); + } else { + createPolyline(availableRoute); + } + } + + LatLngBounds getBounds({required List coordinates}) { + var lngs = coordinates.map((c) => c.longitude).toList(); + var lats = coordinates.map((c) => c.latitude).toList(); + + double bottomMost = lngs.reduce(min); + double topMost = lngs.reduce(max); + double leftMost = lats.reduce(min); + double rightMost = lats.reduce(max); + + LatLngBounds bounds = LatLngBounds( + northeast: LatLng(rightMost, topMost), + southwest: LatLng(leftMost, bottomMost), + ); + return bounds; + + double? x0, x1, y0, y1; + for (LatLng latLng in coordinates) { + if (x0 == null) { + x0 = x1 = latLng.latitude; + y0 = y1 = latLng.longitude; + } else { + if (latLng.latitude > x1!) x1 = latLng.latitude; + if (latLng.latitude < x0) x0 = latLng.latitude; + if (latLng.longitude > y1!) y1 = latLng.longitude; + if (latLng.longitude < y0!) y0 = latLng.longitude; + } + } + return LatLngBounds(northeast: LatLng(x1!, y1!), southwest: LatLng(x0!, y0!)); + } + + focusCameraToLatLngBounds({LatLngBounds? bound, Completer? mapController, double? padding}) async { + if (bound == null) return; + + CameraUpdate camera = CameraUpdate.newLatLngBounds(bound, padding!); + final GoogleMapController controller = await mapController!.future; + controller.animateCamera(camera); + } + + focusCameraTo2Points({LatLng? point1, LatLng? point2, Completer? mapController, double? padding}) async { + var source = point1; + var destination = point2; + if (source != null && destination != null) { + // 'package:google_maps_flutter_platform_interface/src/types/location.dart': Failed assertion: line 72 pos 16: 'southwest.latitude <= northeast.latitude': is not true. + LatLngBounds bound; + if (source.latitude <= destination.latitude) { + bound = LatLngBounds(southwest: source, northeast: destination); + } else { + bound = LatLngBounds(southwest: destination, northeast: source); + } + + if (bound == null) return; + + focusCameraToLatLngBounds(bound: bound, mapController: mapController, padding: padding); + } + } +} diff --git a/lib/widgets/nfc/nfc_reader_sheet.dart b/lib/widgets/nfc/nfc_reader_sheet.dart new file mode 100644 index 0000000..84397ba --- /dev/null +++ b/lib/widgets/nfc/nfc_reader_sheet.dart @@ -0,0 +1,187 @@ +import 'dart:async'; + +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 { + bool _reading = false; + Widget? mainWidget; + String? nfcId; + + @override + void initState() { + super.initState(); + + NfcManager.instance.startSession(onDiscovered: (NfcTag tag) async { + print(tag.data); + var f = MifareUltralight(tag: tag, identifier: tag.data["nfca"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22); + final String identifier = f.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); + // print(identifier); // => 0428fcf2255e81 + nfcId = identifier; + + setState(() { + _reading = true; + mainWidget = doneNfc(); + }); + + Future.delayed(const Duration(seconds: 1), () { + NfcManager.instance.stopSession(); + Navigator.pop(context); + widget.onNcfScan(nfcId); + }); + }); + } + + @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: [ + 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: RaisedButton( + 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: [ + 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: RaisedButton( + // onPressed: () { + // _stream?.cancel(); + // widget.onNcfScan(nfcId); + // Navigator.pop(context); + // }, + onPressed: null, + elevation: 0, + child: Text("DONE"), + ), + ), + SizedBox( + height: 30, + ), + ], + ), + ); + } +} diff --git a/lib/widgets/swipe/nfc_reader_sheet.dart b/lib/widgets/swipe/nfc_reader_sheet.dart new file mode 100644 index 0000000..3dc357d --- /dev/null +++ b/lib/widgets/swipe/nfc_reader_sheet.dart @@ -0,0 +1,194 @@ +// import 'dart:async'; +// +// import 'package:flutter/material.dart'; +// +// +// void showNfcReader(BuildContext context, {Function 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 onNcfScan; +// +// NfcLayout({this.onNcfScan}); +// +// @override +// _NfcLayoutState createState() => _NfcLayoutState(); +// } +// +// class _NfcLayoutState extends State { +// StreamSubscription _stream; +// bool _reading = false; +// Widget mainWidget; +// String nfcId; +// +// @override +// void initState() { +// super.initState(); +// +// setState(() { +// // _reading = true; +// // Start reading using NFC.readNDEF() +// _stream = NFC.readNDEF(once: false, throwOnUserCancel: false, readerMode: NFCDispatchReaderMode()).listen((NDEFMessage message) { +// setState(() { +// _reading = true; +// mainWidget = doneNfc(); +// }); +// Future.delayed(const Duration(milliseconds: 500), () { +// _stream?.cancel(); +// widget.onNcfScan(nfcId); +// Navigator.pop(context); +// }); +// print("read NDEF id: ${message.id}"); +// print("NFC Record " + message.payload); +// print("NFC Record Lenght " + message.records.length.toString()); +// print("NFC Record " + message.records.first.id); +// print("NFC Record " + message.records.first.payload); +// print("NFC Record " + message.records.first.data); +// print("NFC Record " + message.records.first.type); +// // widget.onNcfScan(message.id); +// nfcId = message.id; +// }, onError: (e) { +// // Check error handling guide below +// }); +// }); +// } +// +// @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: [ +// SizedBox( +// height: 30, +// ), +// Text( +// "Ready To Scan", +// style: TextStyle( +// fontWeight: FontWeight.bold, +// fontSize: 24, +// ), +// ), +// SizedBox( +// height: 30, +// ), +// Image.asset( +// "assets/images/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: RaisedButton( +// onPressed: () { +// _stream?.cancel(); +// Navigator.pop(context); +// }, +// elevation: 0, +// child: Text("CANCEL"), +// ), +// ), +// SizedBox( +// height: 30, +// ), +// ], +// ), +// ); +// } +// +// Widget doneNfc() { +// return Container( +// key: ValueKey(2), +// child: Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// SizedBox( +// height: 30, +// ), +// Text( +// "Successfully Scanned", +// style: TextStyle( +// fontWeight: FontWeight.bold, +// fontSize: 24, +// ), +// ), +// SizedBox( +// height: 30, +// ), +// Image.asset( +// "assets/images/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: RaisedButton( +// // onPressed: () { +// // _stream?.cancel(); +// // widget.onNcfScan(nfcId); +// // Navigator.pop(context); +// // }, +// onPressed: null, +// elevation: 0, +// child: Text("DONE"), +// ), +// ), +// SizedBox( +// height: 30, +// ), +// ], +// ), +// ); +// } +// } diff --git a/pubspec.yaml b/pubspec.yaml index 37a3352..d38cc5a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,16 @@ dependencies: shimmer: ^2.0.0 logger: ^1.1.0 flutter_countdown_timer: ^4.1.0 + nfc_manager: ^3.1.1 + uuid: ^3.0.6 + + # maps + google_maps_flutter: ^2.0.2 + google_maps_utils: ^1.4.0+1 + google_directions_api: ^0.9.0 + geolocator: any + # flutter_compass: ^0.6.1 + google_maps_flutter_web: ^0.3.2 dev_dependencies: @@ -84,6 +94,8 @@ flutter: - assets/images/ - assets/images/login/ - assets/images/logos/ + - assets/icons/nfc/ic_nfc.png + - assets/icons/nfc/ic_done.png # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. From 35d683a8ae2eca25970f8280e52e9fd2afbe9a40 Mon Sep 17 00:00:00 2001 From: devmirza121 Date: Mon, 14 Mar 2022 16:14:15 +0300 Subject: [PATCH 4/7] NFC Attendance implement 1.0 --- lib/ui/login/login_screen.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index d5a6061..88caab9 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -69,12 +69,12 @@ class _LoginScreenState extends State { } String? firebaseToken; - + GetMobileLoginInfoListModel? loginInfo; Future checkFirebaseToken() async { try { Utils.showLoading(context); firebaseToken = await _firebaseMessaging.getToken(); - GetMobileLoginInfoListModel? loginInfo = await LoginApiClient().getMobileLoginInfoNEW(firebaseToken ?? "", Platform.isAndroid ? "android" : "ios"); + loginInfo = await LoginApiClient().getMobileLoginInfoNEW(firebaseToken ?? "", Platform.isAndroid ? "android" : "ios"); if (loginInfo == null) { Utils.hideLoading(context); print("Device token not found"); @@ -112,7 +112,7 @@ class _LoginScreenState extends State { } Utils.hideLoading(context); if (_autoLogin) { - Navigator.pushNamed(context, AppRoutes.verifyLastLogin); + Navigator.pushNamed(context, AppRoutes.verifyLastLogin, arguments: loginInfo); } else { Navigator.pushNamed(context, AppRoutes.verifyLogin, arguments: "$firebaseToken"); } @@ -125,10 +125,11 @@ class _LoginScreenState extends State { } } + @override Widget build(BuildContext context) { - username.text="15153"; - password.text="Xy12345@"; + username.text = "15153"; + password.text = "Xy12345@"; return Scaffold( body: Column( children: [ From eb3ddb3d9ef47579bf405d83a751739c24da4cc8 Mon Sep 17 00:00:00 2001 From: devmirza121 Date: Sun, 20 Mar 2022 15:58:33 +0300 Subject: [PATCH 5/7] NFC Attendance implement 1.3 --- lib/api/login_api_client.dart | 2 +- lib/ui/landing/today_attendance_screen.dart | 37 ++++++++++++--------- lib/ui/login/login_screen.dart | 3 ++ lib/ui/login/verify_login_screen.dart | 5 ++- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lib/api/login_api_client.dart b/lib/api/login_api_client.dart index 070fa27..2761ebe 100644 --- a/lib/api/login_api_client.dart +++ b/lib/api/login_api_client.dart @@ -79,7 +79,7 @@ class LoginApiClient { Future checkActivationCode(bool isDeviceNFC, String? mobileNumber, String activationCode, String? pUserName) async { String url = "${ApiConsts.erpRest}CheckActivationCode"; - Map postParams = {"isDeviceNFC": isDeviceNFC, "MobileNumber": mobileNumber, "activationCode": activationCode, "P_USER_NAME": pUserName}; + Map postParams = {"IsDeviceNFC": isDeviceNFC, "MobileNumber": mobileNumber, "activationCode": activationCode, "P_USER_NAME": pUserName}; postParams.addAll(AppState().postParamsJson); return await ApiClient().postJsonForObject((json) { GenericResponseModel responseData = GenericResponseModel.fromJson(json); diff --git a/lib/ui/landing/today_attendance_screen.dart b/lib/ui/landing/today_attendance_screen.dart index 2008484..b1ce089 100644 --- a/lib/ui/landing/today_attendance_screen.dart +++ b/lib/ui/landing/today_attendance_screen.dart @@ -46,7 +46,7 @@ class _TodayAttendanceScreenState extends State { bool isAvailable = await NfcManager.instance.isAvailable(); setState(() { AppState().privilegeListModel!.forEach((element) { - // Check availability + print(element.serviceName.toString() + " " + element.previlege.toString()); // Check availability if (isAvailable) if (element.serviceName == "enableNFC") { // if (element.previlege ?? false) isNfcEnabled = true; @@ -190,21 +190,26 @@ class _TodayAttendanceScreenState extends State { gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1 / 1, crossAxisSpacing: 8, mainAxisSpacing: 8), children: [ attendanceMethod("NFC", "assets/images/nfc.svg", isNfcEnabled, () { - showNfcReader(context, onNcfScan: (String? nfcId) async { - print(nfcId); - Utils.showLoading(context); - try { - GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? ""); - bool status = await model.fetchAttendanceTracking(); - Utils.hideLoading(context); - } catch (ex) { - print(ex); - Utils.hideLoading(context); - Utils.handleException(ex, (msg) { - Utils.confirmDialog(context, msg); - }); - } - }); + if (isNfcLocationEnabled) { + print("nfc location enabled"); + } else { + print("nfc not location enabled"); + } + // showNfcReader(context, onNcfScan: (String? nfcId) async { + // print(nfcId); + // Utils.showLoading(context); + // try { + // GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? ""); + // bool status = await model.fetchAttendanceTracking(); + // Utils.hideLoading(context); + // } catch (ex) { + // print(ex); + // Utils.hideLoading(context); + // Utils.handleException(ex, (msg) { + // Utils.confirmDialog(context, msg); + // }); + // } + // }); // Location.getCurrentLocation((LatLng? latlng) { // print(latlng!.longitude.toString()); // }); diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 88caab9..944a11f 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -130,6 +130,9 @@ class _LoginScreenState extends State { Widget build(BuildContext context) { username.text = "15153"; password.text = "Xy12345@"; + + // username.text = "287742"; + // password.text = "509@Shafi"; return Scaffold( body: Column( children: [ diff --git a/lib/ui/login/verify_login_screen.dart b/lib/ui/login/verify_login_screen.dart index 8e9255f..7db2151 100644 --- a/lib/ui/login/verify_login_screen.dart +++ b/lib/ui/login/verify_login_screen.dart @@ -666,7 +666,7 @@ class _VerifyLoginScreenState extends State { (value) async { Utils.showLoading(context); try { - GenericResponseModel? genericResponseModel = await LoginApiClient().checkActivationCode(false, AppState().memberLoginList?.pMOBILENUMBER, value, AppState().getUserName); + GenericResponseModel? genericResponseModel = await LoginApiClient().checkActivationCode(true, AppState().memberLoginList?.pMOBILENUMBER, value, AppState().getUserName); GenericResponseModel? genericResponseModel1 = await LoginApiClient().insertMobileLoginInfoNEW( AppState().memberLoginList?.pEMAILADDRESS ?? "", genericResponseModel?.pSESSIONID ?? 0, @@ -682,6 +682,9 @@ class _VerifyLoginScreenState extends State { AppState().setPrivilegeListModel = genericResponseModel!.privilegeList ?? []; AppState().setMemberInformationListModel = genericResponseModel.memberInformationList?.first; MemberInformationListModel.saveToPrefs(genericResponseModel.memberInformationList ?? []); + genericResponseModel.privilegeList!.forEach((element) { + print(element.serviceName.toString() + " " + element.previlege.toString()); // Check availability + }); PrivilegeListModel.saveToPrefs(genericResponseModel.privilegeList ?? []); Utils.saveStringFromPrefs(SharedPrefsConsts.username, AppState().getUserName!); Utils.saveStringFromPrefs(SharedPrefsConsts.password, AppState().password!); From a4fb526b684d2988d27e4a600aa793ede37f031f Mon Sep 17 00:00:00 2001 From: devmirza121 Date: Wed, 6 Apr 2022 12:22:00 +0300 Subject: [PATCH 6/7] NFC Attendance implement 1.4 --- lib/dialogs/otp_dialog.dart | 1 + lib/ui/landing/today_attendance_screen.dart | 59 ++++++++++++--------- lib/ui/login/login_screen.dart | 2 +- lib/ui/login/verify_last_login_screen.dart | 12 +++++ 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/lib/dialogs/otp_dialog.dart b/lib/dialogs/otp_dialog.dart index d82cf9c..3789980 100644 --- a/lib/dialogs/otp_dialog.dart +++ b/lib/dialogs/otp_dialog.dart @@ -226,3 +226,4 @@ class OtpDialog { // } } } + diff --git a/lib/ui/landing/today_attendance_screen.dart b/lib/ui/landing/today_attendance_screen.dart index b1ce089..cfba3fd 100644 --- a/lib/ui/landing/today_attendance_screen.dart +++ b/lib/ui/landing/today_attendance_screen.dart @@ -33,7 +33,7 @@ class TodayAttendanceScreen extends StatefulWidget { class _TodayAttendanceScreenState extends State { ValueNotifier result = ValueNotifier(null); late DashboardProviderModel data; - bool isNfcEnabled = false, isNfcLocationEnabled = false, isQrEnabled = false, isQrLocationEnabled = false, isWifiEnabled = false, isWifiLocationEnabled = false; + bool isNfcEnabled = true, isNfcLocationEnabled = false, isQrEnabled = false, isQrLocationEnabled = false, isWifiEnabled = false, isWifiLocationEnabled = false; @override void initState() { @@ -47,14 +47,14 @@ class _TodayAttendanceScreenState extends State { setState(() { AppState().privilegeListModel!.forEach((element) { print(element.serviceName.toString() + " " + element.previlege.toString()); // Check availability - if (isAvailable) if (element.serviceName == "enableNFC") { - // if (element.previlege ?? false) - isNfcEnabled = true; + + 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 == "enableLocatoinNFC") { + } else if (element.serviceName!.trim() == "enableLocationNFC") { if (element.previlege ?? false) isNfcLocationEnabled = true; } else if (element.serviceName == "enableLocationQR") { if (element.previlege ?? false) isQrLocationEnabled = true; @@ -191,28 +191,13 @@ class _TodayAttendanceScreenState extends State { children: [ attendanceMethod("NFC", "assets/images/nfc.svg", isNfcEnabled, () { if (isNfcLocationEnabled) { - print("nfc location enabled"); + Location.getCurrentLocation((LatLng? latlng) { + print(latlng!.longitude.toString()); + performNfcAttendance(model, lat: latlng.latitude.toString() ?? "", lng: latlng.longitude.toString() ?? ""); + }); } else { - print("nfc not location enabled"); + performNfcAttendance(model); } - // showNfcReader(context, onNcfScan: (String? nfcId) async { - // print(nfcId); - // Utils.showLoading(context); - // try { - // GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? ""); - // bool status = await model.fetchAttendanceTracking(); - // Utils.hideLoading(context); - // } catch (ex) { - // print(ex); - // Utils.hideLoading(context); - // Utils.handleException(ex, (msg) { - // Utils.confirmDialog(context, msg); - // }); - // } - // }); - // Location.getCurrentLocation((LatLng? latlng) { - // print(latlng!.longitude.toString()); - // }); }), attendanceMethod("Wifi", "assets/images/wufu.svg", isWifiEnabled, () {}), ], @@ -249,6 +234,30 @@ class _TodayAttendanceScreenState extends State { ); } + Future performNfcAttendance(DashboardProviderModel model, {String lat = "0", String lng = "0"}) async { + if (isNfcLocationEnabled) { + print("nfc location enabled"); + } else { + print("nfc not location enabled"); + } + + showNfcReader(context, onNcfScan: (String? nfcId) async { + print(nfcId); + Utils.showLoading(context); + try { + GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? "", isGpsRequired: isNfcLocationEnabled, lat: lat, long: lng); + bool status = await model.fetchAttendanceTracking(); + Utils.hideLoading(context); + } catch (ex) { + print(ex); + Utils.hideLoading(context); + Utils.handleException(ex, (msg) { + Utils.confirmDialog(context, msg); + }); + } + }); + } + Widget attendanceMethod(String title, String image, bool isEnabled, VoidCallback onPress) => Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 944a11f..ad4e3c2 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -75,6 +75,7 @@ class _LoginScreenState extends State { Utils.showLoading(context); firebaseToken = await _firebaseMessaging.getToken(); loginInfo = await LoginApiClient().getMobileLoginInfoNEW(firebaseToken ?? "", Platform.isAndroid ? "android" : "ios"); + loginInfo!.deviceToken = firebaseToken; if (loginInfo == null) { Utils.hideLoading(context); print("Device token not found"); @@ -125,7 +126,6 @@ class _LoginScreenState extends State { } } - @override Widget build(BuildContext context) { username.text = "15153"; diff --git a/lib/ui/login/verify_last_login_screen.dart b/lib/ui/login/verify_last_login_screen.dart index 52ec751..3a7ce86 100644 --- a/lib/ui/login/verify_last_login_screen.dart +++ b/lib/ui/login/verify_last_login_screen.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:easy_localization/src/public_ext.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -246,6 +248,16 @@ class _VerifyLastLoginScreenState extends State { Utils.showLoading(context); try { GenericResponseModel? genericResponseModel = await LoginApiClient().checkActivationCode(false, AppState().memberLoginList?.pMOBILENUMBER, value, AppState().getUserName); + GenericResponseModel? genericResponseModel1 = await LoginApiClient().insertMobileLoginInfoNEW( + AppState().memberLoginList?.pEMAILADDRESS ?? "", + genericResponseModel?.pSESSIONID ?? 0, + genericResponseModel?.memberInformationList![0].eMPLOYEENAME ?? "", + _flag, + AppState().memberLoginList?.pMOBILENUMBER ?? "", + AppState().getUserName!, + mobileLoginInfoListModel!.deviceToken!, + Platform.isAndroid ? "android" : "ios"); + if (genericResponseModel?.errorMessage != null) { Utils.showToast(genericResponseModel?.errorMessage ?? ""); // Navigator.pop(context); From 9b5147b565a73533ca6e39d47bd6b241cef4acf8 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 10 May 2022 17:38:19 +0300 Subject: [PATCH 7/7] merge improvements --- lib/api/api_client.dart | 6 +++++- lib/app_state/app_state.dart | 4 ++++ lib/classes/utils.dart | 5 +++-- lib/provider/dashboard_provider_model.dart | 4 ++-- lib/ui/landing/dashboard_screen.dart | 2 +- lib/ui/landing/today_attendance_screen.dart | 11 +++++------ lib/ui/login/login_screen.dart | 4 +--- .../missing_swipe/fragments/info_fragments.dart | 4 ++-- 8 files changed, 23 insertions(+), 17 deletions(-) diff --git a/lib/api/api_client.dart b/lib/api/api_client.dart index 17eac0d..3263bf4 100644 --- a/lib/api/api_client.dart +++ b/lib/api/api_client.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; import 'package:http/io_client.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/exceptions/api_exception.dart'; import '../main.dart'; @@ -81,6 +82,9 @@ class ApiClient { logger.i("res: " + response.body); } var jsonData = jsonDecode(response.body); + if (jsonData["IsAuthenticated"] != null) { + AppState().setIsAuthenticated = jsonData["IsAuthenticated"]; + } if (jsonData["ErrorMessage"] == null) { return factoryConstructor(jsonData); } else { @@ -237,4 +241,4 @@ class ApiClient { } Future _post(url, {Map? headers, body, Encoding? encoding}) => _withClient((client) => client.post(url, headers: headers, body: body, encoding: encoding)); - } +} diff --git a/lib/app_state/app_state.dart b/lib/app_state/app_state.dart index 51337ff..de05b56 100644 --- a/lib/app_state/app_state.dart +++ b/lib/app_state/app_state.dart @@ -12,6 +12,10 @@ class AppState { factory AppState() => _instance; + bool isAuthenticated = false; + + set setIsAuthenticated(v) => isAuthenticated = v; + bool isLogged = false; set setLogged(v) => isLogged = v; diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart index d3081d4..1634fd5 100644 --- a/lib/classes/utils.dart +++ b/lib/classes/utils.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/config/routes.dart'; // import 'package:fluttertoast/fluttertoast.dart'; @@ -83,12 +84,12 @@ class Utils { if (onErrorMessage != null) { onErrorMessage(errorMessage); } else { - if (errorMessage.contains("User session is not valid")) { + if (!AppState().isAuthenticated) { showDialog( context: cxt, builder: (cxt) => ConfirmDialog( message: errorMessage, - onTap: (){ + onTap: () { Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.login, (Route route) => false); }, ), diff --git a/lib/provider/dashboard_provider_model.dart b/lib/provider/dashboard_provider_model.dart index 61b66c6..9316182 100644 --- a/lib/provider/dashboard_provider_model.dart +++ b/lib/provider/dashboard_provider_model.dart @@ -71,8 +71,8 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { return ((hour * 60 * 60) + (mints * 60) + seconds); } - update() { - fetchAttendanceTracking(); + update(context) { + fetchAttendanceTracking(context); // isAttendanceTrackingLoading = !isAttendanceTrackingLoading; // isWorkListLoading = !isWorkListLoading; // attendanceTracking?.pSwipeIn = "a"; diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index 4d9fdef..029da02 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -96,7 +96,7 @@ class _DashboardScreenState extends State { ], ), ).onPress(() { - data.update(); + data.update(context); }) ], ).paddingOnly(left: 21, right: 21, top: 48, bottom: 7), diff --git a/lib/ui/landing/today_attendance_screen.dart b/lib/ui/landing/today_attendance_screen.dart index cfba3fd..6268425 100644 --- a/lib/ui/landing/today_attendance_screen.dart +++ b/lib/ui/landing/today_attendance_screen.dart @@ -42,7 +42,7 @@ class _TodayAttendanceScreenState extends State { data = Provider.of(context, listen: false); } - checkAttendanceAvailablity() async { + void checkAttendanceAvailablity() async { bool isAvailable = await NfcManager.instance.isAvailable(); setState(() { AppState().privilegeListModel!.forEach((element) { @@ -84,7 +84,7 @@ class _TodayAttendanceScreenState extends State { actions: [ IconButton( onPressed: () { - data.fetchAttendanceTracking(); + data.fetchAttendanceTracking(context); }, icon: Icon( Icons.ac_unit, @@ -192,8 +192,7 @@ class _TodayAttendanceScreenState extends State { attendanceMethod("NFC", "assets/images/nfc.svg", isNfcEnabled, () { if (isNfcLocationEnabled) { Location.getCurrentLocation((LatLng? latlng) { - print(latlng!.longitude.toString()); - performNfcAttendance(model, lat: latlng.latitude.toString() ?? "", lng: latlng.longitude.toString() ?? ""); + performNfcAttendance(model, lat: latlng?.latitude.toString() ?? "", lng: latlng?.longitude.toString() ?? ""); }); } else { performNfcAttendance(model); @@ -246,12 +245,12 @@ class _TodayAttendanceScreenState extends State { Utils.showLoading(context); try { GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? "", isGpsRequired: isNfcLocationEnabled, lat: lat, long: lng); - bool status = await model.fetchAttendanceTracking(); + bool status = await model.fetchAttendanceTracking(context); Utils.hideLoading(context); } catch (ex) { print(ex); Utils.hideLoading(context); - Utils.handleException(ex, (msg) { + Utils.handleException(ex, context, (msg) { Utils.confirmDialog(context, msg); }); } diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 9b09023..29fbbfe 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -75,18 +75,16 @@ class _LoginScreenState extends State { Utils.showLoading(context); firebaseToken = await _firebaseMessaging.getToken(); loginInfo = await LoginApiClient().getMobileLoginInfoNEW(firebaseToken ?? "", Platform.isAndroid ? "android" : "ios"); - loginInfo!.deviceToken = firebaseToken; if (loginInfo == null) { Utils.hideLoading(context); - print("Device token not found"); return; } else { + loginInfo!.deviceToken = firebaseToken; await checkPrefs(); Utils.hideLoading(context); performLogin(); } } catch (ex) { - print(ex); Utils.hideLoading(context); Utils.handleException(ex, context, null); } diff --git a/lib/ui/work_list/missing_swipe/fragments/info_fragments.dart b/lib/ui/work_list/missing_swipe/fragments/info_fragments.dart index df883c8..ba0e57e 100644 --- a/lib/ui/work_list/missing_swipe/fragments/info_fragments.dart +++ b/lib/ui/work_list/missing_swipe/fragments/info_fragments.dart @@ -165,13 +165,13 @@ class InfoFragment extends StatelessWidget { ItemDetailView(LocaleKeys.operatingUnit.tr(), itemCreationHeader[index].oPERATINGUNIT?.toString() ?? ""), ItemDetailView(LocaleKeys.category.tr(), itemCreationHeader[index].cATEGORY?.toString() ?? ""), ItemDetailView(LocaleKeys.requester.tr(), itemCreationHeader[index].rEQUESTER?.toString() ?? ""), - ItemDetailView(LocaleKeys.analyzedBy.tr(), itemCreationHeader[index].aNALYZEDBY.toString() ?? ""), + ItemDetailView(LocaleKeys.analyzedBy.tr(), itemCreationHeader[index].aNALYZEDBY?.toString() ?? ""), ItemDetailView(LocaleKeys.approvedDate.tr(), itemCreationHeader[index].aPPROVEDDATE?.toString() ?? ""), ItemDetailView(LocaleKeys.itemType.tr(), itemCreationHeader[index].iTEMTYPE?.toString() ?? ""), ItemDetailView(LocaleKeys.relatedTo.tr(), itemCreationHeader[index].rELATEDTO?.toString() ?? ""), ItemDetailView(LocaleKeys.requestDate.tr(), DateUtil.formatDateToDate(DateUtil.convertStringToDate(itemCreationHeader[index].rEQUESTDATE.toString()), false) ?? ""), ItemDetailView(LocaleKeys.analyzedDate.tr(), itemCreationHeader[index].aNALYZEDDATE?.toString() ?? ""), - ItemDetailView(LocaleKeys.urgent.tr(), itemCreationHeader[index].uRGENTFLAGDISP.toString() ?? ""), + ItemDetailView(LocaleKeys.urgent.tr(), itemCreationHeader[index].uRGENTFLAGDISP?.toString() ?? ""), ], ).objectContainerView(), separatorBuilder: (cxt, index) => 18.height,