From d65dc4ea899acf96ae6aef82eb612d743192fd22 Mon Sep 17 00:00:00 2001 From: Faiz Hashmi Date: Sun, 31 Aug 2025 14:44:50 +0300 Subject: [PATCH 01/17] initial commit --- android/build.gradle.kts | 14 + ios/Podfile.lock | 401 ----- lib/{services => core/api}/api_client.dart | 10 +- lib/core/enums.dart | 6 +- .../exceptions}/api_exception.dart | 2 +- lib/core/post_params_model.dart | 30 +- lib/extensions/route_extensions.dart | 23 + .../authentication/authentication_repo.dart | 4 + .../authentication_view_model.dart | 0 ...ent_last_login_details_response_model.dart | 0 .../book_appointments_repo.dart | 0 .../book_appointments_view_model.dart | 0 lib/features/common/common_repo.dart | 0 lib/features/common/common_view_model.dart | 0 .../my_appointments/my_appointments_repo.dart | 0 .../my_appointments_view_model.dart | 0 lib/main.dart | 2 +- lib/presentation/home/landing_page.dart | 2 +- lib/routes/app_routes.dart | 1 - .../authentication/authentication_repo.dart | 30 - lib/services/logger_service.dart | 27 + pubspec.lock | 1577 ----------------- 22 files changed, 94 insertions(+), 2035 deletions(-) delete mode 100644 ios/Podfile.lock rename lib/{services => core/api}/api_client.dart (98%) rename lib/{services => core/exceptions}/api_exception.dart (94%) create mode 100644 lib/extensions/route_extensions.dart create mode 100644 lib/features/authentication/authentication_repo.dart rename lib/{providers => features/authentication}/authentication_view_model.dart (100%) rename lib/{services/authentication/models/response_models => features/authentication/models}/get_patient_last_login_details_response_model.dart (100%) create mode 100644 lib/features/book_appointments/book_appointments_repo.dart create mode 100644 lib/features/book_appointments/book_appointments_view_model.dart create mode 100644 lib/features/common/common_repo.dart create mode 100644 lib/features/common/common_view_model.dart create mode 100644 lib/features/my_appointments/my_appointments_repo.dart create mode 100644 lib/features/my_appointments/my_appointments_view_model.dart delete mode 100644 lib/services/authentication/authentication_repo.dart create mode 100644 lib/services/logger_service.dart delete mode 100644 pubspec.lock diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 89176ef4..d60f0c02 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -1,7 +1,21 @@ +buildscript { + repositories { + google() + mavenCentral() + maven { url = uri("https://developer.huawei.com/repo/") } + } + dependencies { + classpath("com.android.tools.build:gradle:7.4.2") + classpath("com.google.gms:google-services:4.4.1") + classpath("com.huawei.agconnect:agcp:1.9.3.302") + } +} + allprojects { repositories { google() mavenCentral() + maven { url = uri("https://developer.huawei.com/repo/") } } } diff --git a/ios/Podfile.lock b/ios/Podfile.lock deleted file mode 100644 index 1c36b55f..00000000 --- a/ios/Podfile.lock +++ /dev/null @@ -1,401 +0,0 @@ -PODS: - - audio_session (0.0.1): - - Flutter - - connectivity_plus (0.0.1): - - Flutter - - CryptoSwift (1.8.4) - - device_calendar (0.0.1): - - Flutter - - device_info_plus (0.0.1): - - Flutter - - DKImagePickerController/Core (4.3.9): - - DKImagePickerController/ImageDataManager - - DKImagePickerController/Resource - - DKImagePickerController/ImageDataManager (4.3.9) - - DKImagePickerController/PhotoGallery (4.3.9): - - DKImagePickerController/Core - - DKPhotoGallery - - DKImagePickerController/Resource (4.3.9) - - DKPhotoGallery (0.0.19): - - DKPhotoGallery/Core (= 0.0.19) - - DKPhotoGallery/Model (= 0.0.19) - - DKPhotoGallery/Preview (= 0.0.19) - - DKPhotoGallery/Resource (= 0.0.19) - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Core (0.0.19): - - DKPhotoGallery/Model - - DKPhotoGallery/Preview - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Model (0.0.19): - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Preview (0.0.19): - - DKPhotoGallery/Model - - DKPhotoGallery/Resource - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Resource (0.0.19): - - SDWebImage - - SwiftyGif - - file_picker (0.0.1): - - DKImagePickerController/PhotoGallery - - Flutter - - Firebase/CoreOnly (11.15.0): - - FirebaseCore (~> 11.15.0) - - Firebase/Messaging (11.15.0): - - Firebase/CoreOnly - - FirebaseMessaging (~> 11.15.0) - - firebase_core (3.15.2): - - Firebase/CoreOnly (= 11.15.0) - - Flutter - - firebase_messaging (15.2.10): - - Firebase/Messaging (= 11.15.0) - - firebase_core - - Flutter - - FirebaseCore (11.15.0): - - FirebaseCoreInternal (~> 11.15.0) - - GoogleUtilities/Environment (~> 8.1) - - GoogleUtilities/Logger (~> 8.1) - - FirebaseCoreInternal (11.15.0): - - "GoogleUtilities/NSData+zlib (~> 8.1)" - - FirebaseInstallations (11.15.0): - - FirebaseCore (~> 11.15.0) - - GoogleUtilities/Environment (~> 8.1) - - GoogleUtilities/UserDefaults (~> 8.1) - - PromisesObjC (~> 2.4) - - FirebaseMessaging (11.15.0): - - FirebaseCore (~> 11.15.0) - - FirebaseInstallations (~> 11.0) - - GoogleDataTransport (~> 10.0) - - GoogleUtilities/AppDelegateSwizzler (~> 8.1) - - GoogleUtilities/Environment (~> 8.1) - - GoogleUtilities/Reachability (~> 8.1) - - GoogleUtilities/UserDefaults (~> 8.1) - - nanopb (~> 3.30910.0) - - FLAnimatedImage (1.0.17) - - Flutter (1.0.0) - - flutter_callkit_incoming (0.0.1): - - CryptoSwift - - Flutter - - flutter_inappwebview_ios (0.0.1): - - Flutter - - flutter_inappwebview_ios/Core (= 0.0.1) - - OrderedSet (~> 6.0.3) - - flutter_inappwebview_ios/Core (0.0.1): - - Flutter - - OrderedSet (~> 6.0.3) - - flutter_ios_voip_kit_karmm (0.8.0): - - Flutter - - flutter_local_notifications (0.0.1): - - Flutter - - flutter_zoom_videosdk (0.0.1): - - Flutter - - ZoomVideoSDK/CptShare (= 2.1.10) - - ZoomVideoSDK/zm_annoter_dynamic (= 2.1.10) - - ZoomVideoSDK/zoomcml (= 2.1.10) - - ZoomVideoSDK/ZoomVideoSDK (= 2.1.10) - - fluttertoast (0.0.2): - - Flutter - - geolocator_apple (1.2.0): - - Flutter - - FlutterMacOS - - Google-Maps-iOS-Utils (5.0.0): - - GoogleMaps (~> 8.0) - - google_maps_flutter_ios (0.0.1): - - Flutter - - Google-Maps-iOS-Utils (< 7.0, >= 5.0) - - GoogleMaps (< 10.0, >= 8.4) - - GoogleDataTransport (10.1.0): - - nanopb (~> 3.30910.0) - - PromisesObjC (~> 2.4) - - GoogleMaps (8.4.0): - - GoogleMaps/Maps (= 8.4.0) - - GoogleMaps/Base (8.4.0) - - GoogleMaps/Maps (8.4.0): - - GoogleMaps/Base - - GoogleUtilities/AppDelegateSwizzler (8.1.0): - - GoogleUtilities/Environment - - GoogleUtilities/Logger - - GoogleUtilities/Network - - GoogleUtilities/Privacy - - GoogleUtilities/Environment (8.1.0): - - GoogleUtilities/Privacy - - GoogleUtilities/Logger (8.1.0): - - GoogleUtilities/Environment - - GoogleUtilities/Privacy - - GoogleUtilities/Network (8.1.0): - - GoogleUtilities/Logger - - "GoogleUtilities/NSData+zlib" - - GoogleUtilities/Privacy - - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (8.1.0)": - - GoogleUtilities/Privacy - - GoogleUtilities/Privacy (8.1.0) - - GoogleUtilities/Reachability (8.1.0): - - GoogleUtilities/Logger - - GoogleUtilities/Privacy - - GoogleUtilities/UserDefaults (8.1.0): - - GoogleUtilities/Logger - - GoogleUtilities/Privacy - - health (13.1.3): - - Flutter - - image_picker_ios (0.0.1): - - Flutter - - just_audio (0.0.1): - - Flutter - - FlutterMacOS - - local_auth_darwin (0.0.1): - - Flutter - - FlutterMacOS - - manage_calendar_events (0.0.1): - - Flutter - - MapboxCommon (23.11.0) - - MapboxCoreMaps (10.19.1): - - MapboxCommon (~> 23.11) - - MapboxCoreNavigation (2.19.0): - - MapboxDirections (~> 2.14) - - MapboxNavigationNative (< 207.0.0, >= 206.0.1) - - MapboxDirections (2.14.2): - - Polyline (~> 5.0) - - Turf (~> 2.8.0) - - MapboxMaps (10.19.0): - - MapboxCommon (= 23.11.0) - - MapboxCoreMaps (= 10.19.1) - - MapboxMobileEvents (= 2.0.0) - - Turf (= 2.8.0) - - MapboxMobileEvents (2.0.0) - - MapboxNavigation (2.19.0): - - MapboxCoreNavigation (= 2.19.0) - - MapboxMaps (~> 10.18) - - MapboxSpeech (~> 2.0) - - Solar-dev (~> 3.0) - - MapboxNavigationNative (206.2.2): - - MapboxCommon (~> 23.10) - - MapboxSpeech (2.1.1) - - nanopb (3.30910.0): - - nanopb/decode (= 3.30910.0) - - nanopb/encode (= 3.30910.0) - - nanopb/decode (3.30910.0) - - nanopb/encode (3.30910.0) - - OrderedSet (6.0.3) - - package_info_plus (0.4.5): - - Flutter - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - permission_handler_apple (9.3.0): - - Flutter - - Polyline (5.1.0) - - PromisesObjC (2.4.0) - - SDWebImage (5.21.2): - - SDWebImage/Core (= 5.21.2) - - SDWebImage/Core (5.21.2) - - share_plus (0.0.1): - - Flutter - - shared_preferences_foundation (0.0.1): - - Flutter - - FlutterMacOS - - Solar-dev (3.0.1) - - sqflite_darwin (0.0.4): - - Flutter - - FlutterMacOS - - SwiftyGif (5.4.5) - - Turf (2.8.0) - - url_launcher_ios (0.0.1): - - Flutter - - ZoomVideoSDK/CptShare (2.1.10) - - ZoomVideoSDK/zm_annoter_dynamic (2.1.10) - - ZoomVideoSDK/zoomcml (2.1.10) - - ZoomVideoSDK/ZoomVideoSDK (2.1.10) - -DEPENDENCIES: - - audio_session (from `.symlinks/plugins/audio_session/ios`) - - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - - device_calendar (from `.symlinks/plugins/device_calendar/ios`) - - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - - file_picker (from `.symlinks/plugins/file_picker/ios`) - - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - - FLAnimatedImage - - Flutter (from `Flutter`) - - flutter_callkit_incoming (from `.symlinks/plugins/flutter_callkit_incoming/ios`) - - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - - flutter_ios_voip_kit_karmm (from `.symlinks/plugins/flutter_ios_voip_kit_karmm/ios`) - - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - - flutter_zoom_videosdk (from `.symlinks/plugins/flutter_zoom_videosdk/ios`) - - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - - geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`) - - google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`) - - health (from `.symlinks/plugins/health/ios`) - - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - - just_audio (from `.symlinks/plugins/just_audio/darwin`) - - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - - manage_calendar_events (from `.symlinks/plugins/manage_calendar_events/ios`) - - MapboxMaps (= 10.19.0) - - MapboxNavigation (= 2.19.0) - - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - share_plus (from `.symlinks/plugins/share_plus/ios`) - - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - -SPEC REPOS: - trunk: - - CryptoSwift - - DKImagePickerController - - DKPhotoGallery - - Firebase - - FirebaseCore - - FirebaseCoreInternal - - FirebaseInstallations - - FirebaseMessaging - - FLAnimatedImage - - Google-Maps-iOS-Utils - - GoogleDataTransport - - GoogleMaps - - GoogleUtilities - - MapboxCommon - - MapboxCoreMaps - - MapboxCoreNavigation - - MapboxDirections - - MapboxMaps - - MapboxMobileEvents - - MapboxNavigation - - MapboxNavigationNative - - MapboxSpeech - - nanopb - - OrderedSet - - Polyline - - PromisesObjC - - SDWebImage - - Solar-dev - - SwiftyGif - - Turf - - ZoomVideoSDK - -EXTERNAL SOURCES: - audio_session: - :path: ".symlinks/plugins/audio_session/ios" - connectivity_plus: - :path: ".symlinks/plugins/connectivity_plus/ios" - device_calendar: - :path: ".symlinks/plugins/device_calendar/ios" - device_info_plus: - :path: ".symlinks/plugins/device_info_plus/ios" - file_picker: - :path: ".symlinks/plugins/file_picker/ios" - firebase_core: - :path: ".symlinks/plugins/firebase_core/ios" - firebase_messaging: - :path: ".symlinks/plugins/firebase_messaging/ios" - Flutter: - :path: Flutter - flutter_callkit_incoming: - :path: ".symlinks/plugins/flutter_callkit_incoming/ios" - flutter_inappwebview_ios: - :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" - flutter_ios_voip_kit_karmm: - :path: ".symlinks/plugins/flutter_ios_voip_kit_karmm/ios" - flutter_local_notifications: - :path: ".symlinks/plugins/flutter_local_notifications/ios" - flutter_zoom_videosdk: - :path: ".symlinks/plugins/flutter_zoom_videosdk/ios" - fluttertoast: - :path: ".symlinks/plugins/fluttertoast/ios" - geolocator_apple: - :path: ".symlinks/plugins/geolocator_apple/darwin" - google_maps_flutter_ios: - :path: ".symlinks/plugins/google_maps_flutter_ios/ios" - health: - :path: ".symlinks/plugins/health/ios" - image_picker_ios: - :path: ".symlinks/plugins/image_picker_ios/ios" - just_audio: - :path: ".symlinks/plugins/just_audio/darwin" - local_auth_darwin: - :path: ".symlinks/plugins/local_auth_darwin/darwin" - manage_calendar_events: - :path: ".symlinks/plugins/manage_calendar_events/ios" - package_info_plus: - :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" - permission_handler_apple: - :path: ".symlinks/plugins/permission_handler_apple/ios" - share_plus: - :path: ".symlinks/plugins/share_plus/ios" - shared_preferences_foundation: - :path: ".symlinks/plugins/shared_preferences_foundation/darwin" - sqflite_darwin: - :path: ".symlinks/plugins/sqflite_darwin/darwin" - url_launcher_ios: - :path: ".symlinks/plugins/url_launcher_ios/ios" - -SPEC CHECKSUMS: - audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe - connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d - CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90 - device_calendar: 9cb33f88a02e19652ec7b8b122ca778f751b1f7b - device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 - DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c - DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 - Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e - firebase_core: 99a37263b3c27536063a7b601d9e2a49400a433c - firebase_messaging: bf6697c61f31c7cc0f654131212ff04c0115c2c7 - FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e - FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 - FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843 - FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09 - FLAnimatedImage: bbf914596368867157cc71b38a8ec834b3eeb32b - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_callkit_incoming: 417dd1b46541cdd5d855ad795ccbe97d1c18155e - flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 - flutter_ios_voip_kit_karmm: 7ea37381a8841c92d186edf1f4604df5cc437579 - flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f - flutter_zoom_videosdk: 7ddc97d617b85aeace252165d2b5fc5638678c38 - fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f - geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd - Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321 - google_maps_flutter_ios: e31555a04d1986ab130f2b9f24b6cdc861acc6d3 - GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 - GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d - GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 - health: f1207cccb625fc2e8af263b09c29661bde7b0b7e - image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 - just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79 - local_auth_darwin: fa4b06454df7df8e97c18d7ee55151c57e7af0de - manage_calendar_events: 9b2889799340398027b3e3f5c4891d41599ec257 - MapboxCommon: 119f3759f7dc9457f0695848108ab323eb643cb4 - MapboxCoreMaps: ca17f67baced23f8c952166ac6314c35bad3f66c - MapboxCoreNavigation: 3be9990fae3ed732a101001746d0e3b4234ec023 - MapboxDirections: d4fe7d43cff82aa0c15955d1b4563a5a01e2d4de - MapboxMaps: b7f29ec7c33f7dc6d2947c1148edce6db81db9a7 - MapboxMobileEvents: d044b9edbe0ec7df60f6c2c9634fe9a7f449266b - MapboxNavigation: da9cf3d773ed5b0fa0fb388fccdaa117ee681f31 - MapboxNavigationNative: 629e359f3d2590acd1ebbacaaf99e1a80ee57e42 - MapboxSpeech: cd25ef99c3a3d2e0da72620ff558276ea5991a77 - nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 - Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1 - PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a - share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - Solar-dev: 4612dc9878b9fed2667d23b327f1d4e54e16e8d0 - sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d - SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - ZoomVideoSDK: 94e939820e57a075c5e712559f927017da0de06a - -PODFILE CHECKSUM: ac23c99fc61c3e92ed1c20a19299a1205568c016 - -COCOAPODS: 1.15.2 diff --git a/lib/services/api_client.dart b/lib/core/api/api_client.dart similarity index 98% rename from lib/services/api_client.dart rename to lib/core/api/api_client.dart index 42ffc580..4e08cd1e 100644 --- a/lib/services/api_client.dart +++ b/lib/core/api/api_client.dart @@ -3,14 +3,14 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; -import 'package:hmg_patient_app_new/services/api_exception.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; import 'package:http/http.dart'; import 'package:http/io_client.dart'; -import '../core/app_state.dart'; -import '../core/consts.dart'; -import '../core/utils/utils.dart'; -import '../main.dart'; +import '../app_state.dart'; +import '../consts.dart'; +import '../utils/utils.dart'; +import '../../main.dart'; // ignore_for_file: avoid_annotating_with_dynamic diff --git a/lib/core/enums.dart b/lib/core/enums.dart index cf276020..7e65dee2 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -23,7 +23,7 @@ enum ViewState { } enum LoginType { - FROM_LOGIN, - SILENT_LOGIN, - SILENT_WITH_OTP, + fromLogin, + silentLogin, + silentWithOTP, } diff --git a/lib/services/api_exception.dart b/lib/core/exceptions/api_exception.dart similarity index 94% rename from lib/services/api_exception.dart rename to lib/core/exceptions/api_exception.dart index f0f1ff9c..7d94212b 100644 --- a/lib/services/api_exception.dart +++ b/lib/core/exceptions/api_exception.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:hmg_patient_app_new/services/api_client.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; class APIException implements Exception { static const String BAD_REQUEST = 'api_common_bad_request'; diff --git a/lib/core/post_params_model.dart b/lib/core/post_params_model.dart index 5eed9bbf..218f29f9 100644 --- a/lib/core/post_params_model.dart +++ b/lib/core/post_params_model.dart @@ -25,32 +25,32 @@ class PostParamsModel { } Map toJson() { - Map data = new Map(); - data['versionID'] = this.versionID; - data['channel'] = this.channel; - data['languageID'] = this.languageID; - data['logInTokenID'] = this.logInTokenID ?? ""; - data['tokenID'] = this.tokenID ?? ""; + Map data = {}; + data['versionID'] = versionID; + data['channel'] = channel; + data['languageID'] = languageID; + data['logInTokenID'] = logInTokenID ?? ""; + data['tokenID'] = tokenID ?? ""; return data; } Map toJsonAfterLogin() { - Map data = new Map(); - data['versionID'] = this.versionID; - data['channel'] = this.channel; - data['languageID'] = this.languageID; - data['logInTokenID'] = this.logInTokenID; - data['tokenID'] = this.tokenID; + Map data = {}; + data['versionID'] = versionID; + data['channel'] = channel; + data['languageID'] = languageID; + data['logInTokenID'] = logInTokenID; + data['tokenID'] = tokenID; return data; } - String? _LogInTokenID; + String? _logInTokenID; - String? get getLogInTokenID => _LogInTokenID ?? logInTokenID; + String? get getLogInTokenID => _logInTokenID ?? logInTokenID; set setLogInTokenID(String? value) { logInTokenID = value; - _LogInTokenID = value; + _logInTokenID = value; } set setTokenID(String? token) => tokenID = token; diff --git a/lib/extensions/route_extensions.dart b/lib/extensions/route_extensions.dart new file mode 100644 index 00000000..71f5d2d3 --- /dev/null +++ b/lib/extensions/route_extensions.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +extension NavigationExtensions on BuildContext { + void navigateWithName(String routeName, {Object? arguments}) { + Navigator.pushNamed(this, routeName, arguments: arguments); + } + + Future navigateReplaceWithName(String routeName, {Object? arguments}) async { + await Navigator.pushReplacementNamed(this, routeName, arguments: arguments); + } + + void navigateReplaceWithNameUntilRoute(String routeName, {Object? arguments}) { + Navigator.pushNamedAndRemoveUntil(this, routeName, (route) => false); + } + + void pop() { + Navigator.of(this).pop(); + } + + void navigateTo(Widget page) { + Navigator.push(this, MaterialPageRoute(builder: (context) => page)); + } +} diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart new file mode 100644 index 00000000..1755fbaf --- /dev/null +++ b/lib/features/authentication/authentication_repo.dart @@ -0,0 +1,4 @@ +class AuthenticationRepo { + + +} \ No newline at end of file diff --git a/lib/providers/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart similarity index 100% rename from lib/providers/authentication_view_model.dart rename to lib/features/authentication/authentication_view_model.dart diff --git a/lib/services/authentication/models/response_models/get_patient_last_login_details_response_model.dart b/lib/features/authentication/models/get_patient_last_login_details_response_model.dart similarity index 100% rename from lib/services/authentication/models/response_models/get_patient_last_login_details_response_model.dart rename to lib/features/authentication/models/get_patient_last_login_details_response_model.dart diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/features/common/common_repo.dart b/lib/features/common/common_repo.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/features/common/common_view_model.dart b/lib/features/common/common_view_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/main.dart b/lib/main.dart index 48ea6ee9..ce601a2b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,7 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; -import 'package:hmg_patient_app_new/providers/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/theme/app_theme.dart'; import 'package:logger/logger.dart'; diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index a80c25f1..9163a7e4 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; -import 'package:hmg_patient_app_new/providers/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; class LandingPage extends StatefulWidget { const LandingPage({super.key}); diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index ce31fb79..732e297f 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -3,6 +3,5 @@ import 'package:hmg_patient_app_new/splashPage.dart'; class AppRoutes { static const String initialRoute = '/initialRoute'; - static Map get routes => {initialRoute: (context) => SplashPage()}; } diff --git a/lib/services/authentication/authentication_repo.dart b/lib/services/authentication/authentication_repo.dart deleted file mode 100644 index 353ad59d..00000000 --- a/lib/services/authentication/authentication_repo.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'dart:convert'; - -import 'package:hmg_patient_app_new/core/app_state.dart'; -import 'package:http/http.dart'; - -import '../../core/consts.dart'; -import '../api_client.dart'; -import 'models/response_models/get_patient_last_login_details_response_model.dart'; - -class AuthenticationApiClient { - static final AuthenticationApiClient _instance = AuthenticationApiClient._internal(); - - AuthenticationApiClient._internal(); - - factory AuthenticationApiClient() => _instance; - - Future getMultipleLoginUserData(String deviceIMEI) async { - GetPatientLastLoginDetailsResponseModel getPatientLastLoginDetailsResponseModel; - - Map request = {"IMEI": deviceIMEI}; - request.addAll(AppState().postParamsJson); - String url = ApiConsts.baseUrl + ApiConsts.SELECT_DEVICE_IMEI; - Response response = await ApiClient().postJsonForResponse(url, request); - - var json = jsonDecode(response.body); - getPatientLastLoginDetailsResponseModel = GetPatientLastLoginDetailsResponseModel.fromJson(json); - - return getPatientLastLoginDetailsResponseModel; - } -} diff --git a/lib/services/logger_service.dart b/lib/services/logger_service.dart new file mode 100644 index 00000000..833a7d20 --- /dev/null +++ b/lib/services/logger_service.dart @@ -0,0 +1,27 @@ + +import 'package:logger/logger.dart'; + +abstract class LoggerService { + + void logError(String message); + + void logInfo(String message); +} + +class LoggerServiceImp implements LoggerService { + Logger logger; + + LoggerServiceImp({required this.logger}); + + + + @override + void logError(String message) { + logger.e(message); + } + + @override + void logInfo(String message) { + logger.i(message); + } +} diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index 69460823..00000000 --- a/pubspec.lock +++ /dev/null @@ -1,1577 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _flutterfire_internals: - dependency: transitive - description: - name: _flutterfire_internals - sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 - url: "https://pub.dev" - source: hosted - version: "1.3.59" - archive: - dependency: transitive - description: - name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" - url: "https://pub.dev" - source: hosted - version: "4.0.7" - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" - async: - dependency: transitive - description: - name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - audio_session: - dependency: transitive - description: - name: audio_session - sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac" - url: "https://pub.dev" - source: hosted - version: "0.1.25" - auto_size_text: - dependency: "direct main" - description: - name: auto_size_text - sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - cached_network_image: - dependency: "direct main" - description: - name: cached_network_image - sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" - url: "https://pub.dev" - source: hosted - version: "3.4.1" - cached_network_image_platform_interface: - dependency: transitive - description: - name: cached_network_image_platform_interface - sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" - url: "https://pub.dev" - source: hosted - version: "4.1.1" - cached_network_image_web: - dependency: transitive - description: - name: cached_network_image_web - sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - carp_serializable: - dependency: transitive - description: - name: carp_serializable - sha256: f039f8ea22e9437aef13fe7e9743c3761c76d401288dcb702eadd273c3e4dcef - url: "https://pub.dev" - source: hosted - version: "2.0.1" - characters: - dependency: transitive - description: - name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - connectivity_plus: - dependency: "direct main" - description: - name: connectivity_plus - sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec - url: "https://pub.dev" - source: hosted - version: "6.1.5" - connectivity_plus_platform_interface: - dependency: transitive - description: - name: connectivity_plus_platform_interface - sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" - url: "https://pub.dev" - source: hosted - version: "2.0.1" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" - url: "https://pub.dev" - source: hosted - version: "0.3.4+2" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - csslib: - dependency: transitive - description: - name: csslib - sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.dev" - source: hosted - version: "1.0.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" - dbus: - dependency: transitive - description: - name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" - url: "https://pub.dev" - source: hosted - version: "0.7.11" - device_calendar: - dependency: "direct main" - description: - path: "." - ref: HEAD - resolved-ref: "5ea5ed9e2bb499c0633383b53103f2920b634755" - url: "https://github.com/bardram/device_calendar" - source: git - version: "4.3.1" - device_info_plus: - dependency: "direct main" - description: - name: device_info_plus - sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" - url: "https://pub.dev" - source: hosted - version: "11.5.0" - device_info_plus_platform_interface: - dependency: transitive - description: - name: device_info_plus_platform_interface - sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f - url: "https://pub.dev" - source: hosted - version: "7.0.3" - dropdown_search: - dependency: "direct main" - description: - name: dropdown_search - sha256: c29b3e5147a82a06a4a08b3b574c51cb48cc17ad89893d53ee72a6f86643622e - url: "https://pub.dev" - source: hosted - version: "6.0.2" - easy_localization: - dependency: "direct main" - description: - name: easy_localization - sha256: "2ccdf9db8fe4d9c5a75c122e6275674508fd0f0d49c827354967b8afcc56bbed" - url: "https://pub.dev" - source: hosted - version: "3.0.8" - easy_logger: - dependency: transitive - description: - name: easy_logger - sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 - url: "https://pub.dev" - source: hosted - version: "0.0.2" - equatable: - dependency: transitive - description: - name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" - url: "https://pub.dev" - source: hosted - version: "2.0.7" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - file_picker: - dependency: "direct main" - description: - name: file_picker - sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22 - url: "https://pub.dev" - source: hosted - version: "10.3.2" - file_selector_linux: - dependency: transitive - description: - name: file_selector_linux - sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" - url: "https://pub.dev" - source: hosted - version: "0.9.3+2" - file_selector_macos: - dependency: transitive - description: - name: file_selector_macos - sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" - url: "https://pub.dev" - source: hosted - version: "0.9.4+4" - file_selector_platform_interface: - dependency: transitive - description: - name: file_selector_platform_interface - sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b - url: "https://pub.dev" - source: hosted - version: "2.6.2" - file_selector_windows: - dependency: transitive - description: - name: file_selector_windows - sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" - url: "https://pub.dev" - source: hosted - version: "0.9.3+4" - firebase_core: - dependency: "direct main" - description: - name: firebase_core - sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" - url: "https://pub.dev" - source: hosted - version: "3.15.2" - firebase_core_platform_interface: - dependency: transitive - description: - name: firebase_core_platform_interface - sha256: "5dbc900677dcbe5873d22ad7fbd64b047750124f1f9b7ebe2a33b9ddccc838eb" - url: "https://pub.dev" - source: hosted - version: "6.0.0" - firebase_core_web: - dependency: transitive - description: - name: firebase_core_web - sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" - url: "https://pub.dev" - source: hosted - version: "2.24.1" - firebase_messaging: - dependency: "direct main" - description: - name: firebase_messaging - sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc" - url: "https://pub.dev" - source: hosted - version: "15.2.10" - firebase_messaging_platform_interface: - dependency: transitive - description: - name: firebase_messaging_platform_interface - sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754" - url: "https://pub.dev" - source: hosted - version: "4.6.10" - firebase_messaging_web: - dependency: transitive - description: - name: firebase_messaging_web - sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390" - url: "https://pub.dev" - source: hosted - version: "3.10.10" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - fl_chart: - dependency: "direct main" - description: - name: fl_chart - sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_cache_manager: - dependency: transitive - description: - name: flutter_cache_manager - sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" - url: "https://pub.dev" - source: hosted - version: "3.4.1" - flutter_callkit_incoming: - dependency: "direct main" - description: - path: "." - ref: dev - resolved-ref: "4b5aa9798a77eaba1c1726c314c5de74cd37ad6d" - url: "https://github.com/hiennguyen92/flutter_callkit_incoming.git" - source: git - version: "2.5.8" - flutter_hooks: - dependency: transitive - description: - name: flutter_hooks - sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 - url: "https://pub.dev" - source: hosted - version: "0.20.5" - flutter_inappwebview: - dependency: "direct main" - description: - name: flutter_inappwebview - sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" - url: "https://pub.dev" - source: hosted - version: "6.1.5" - flutter_inappwebview_android: - dependency: transitive - description: - name: flutter_inappwebview_android - sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" - url: "https://pub.dev" - source: hosted - version: "1.1.3" - flutter_inappwebview_internal_annotations: - dependency: transitive - description: - name: flutter_inappwebview_internal_annotations - sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - flutter_inappwebview_ios: - dependency: transitive - description: - name: flutter_inappwebview_ios - sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" - url: "https://pub.dev" - source: hosted - version: "1.1.2" - flutter_inappwebview_macos: - dependency: transitive - description: - name: flutter_inappwebview_macos - sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 - url: "https://pub.dev" - source: hosted - version: "1.1.2" - flutter_inappwebview_platform_interface: - dependency: transitive - description: - name: flutter_inappwebview_platform_interface - sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 - url: "https://pub.dev" - source: hosted - version: "1.3.0+1" - flutter_inappwebview_web: - dependency: transitive - description: - name: flutter_inappwebview_web - sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" - url: "https://pub.dev" - source: hosted - version: "1.1.2" - flutter_inappwebview_windows: - dependency: transitive - description: - name: flutter_inappwebview_windows - sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" - url: "https://pub.dev" - source: hosted - version: "0.6.0" - flutter_ios_voip_kit_karmm: - dependency: "direct main" - description: - name: flutter_ios_voip_kit_karmm - sha256: "31a445d78aacacdf128a0354efb9f4e424285dfe4c0af3ea872e64f03e6f6bfc" - url: "https://pub.dev" - source: hosted - version: "0.8.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.dev" - source: hosted - version: "5.0.0" - flutter_local_notifications: - dependency: "direct main" - description: - name: flutter_local_notifications - sha256: a9966c850de5e445331b854fa42df96a8020066d67f125a5964cbc6556643f68 - url: "https://pub.dev" - source: hosted - version: "19.4.1" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe" - url: "https://pub.dev" - source: hosted - version: "9.1.0" - flutter_local_notifications_windows: - dependency: transitive - description: - name: flutter_local_notifications_windows - sha256: ed46d7ae4ec9d19e4c8fa2badac5fe27ba87a3fe387343ce726f927af074ec98 - url: "https://pub.dev" - source: hosted - version: "1.0.2" - flutter_localizations: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31 - url: "https://pub.dev" - source: hosted - version: "2.0.30" - flutter_rating_bar: - dependency: "direct main" - description: - name: flutter_rating_bar - sha256: d2af03469eac832c591a1eba47c91ecc871fe5708e69967073c043b2d775ed93 - url: "https://pub.dev" - source: hosted - version: "4.0.1" - flutter_svg: - dependency: "direct main" - description: - name: flutter_svg - sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845 - url: "https://pub.dev" - source: hosted - version: "2.2.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" - flutter_zoom_videosdk: - dependency: "direct main" - description: - name: flutter_zoom_videosdk - sha256: "46a4dea664b1c969099328a499c198a1755adf9ac333dea28bea5187910b3bf9" - url: "https://pub.dev" - source: hosted - version: "2.1.10" - fluttertoast: - dependency: "direct main" - description: - name: fluttertoast - sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" - url: "https://pub.dev" - source: hosted - version: "8.2.12" - geoclue: - dependency: transitive - description: - name: geoclue - sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f - url: "https://pub.dev" - source: hosted - version: "0.1.1" - geolocator: - dependency: "direct main" - description: - name: geolocator - sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516" - url: "https://pub.dev" - source: hosted - version: "14.0.2" - geolocator_android: - dependency: transitive - description: - name: geolocator_android - sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a" - url: "https://pub.dev" - source: hosted - version: "5.0.2" - geolocator_apple: - dependency: transitive - description: - name: geolocator_apple - sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 - url: "https://pub.dev" - source: hosted - version: "2.3.13" - geolocator_linux: - dependency: transitive - description: - name: geolocator_linux - sha256: c4e966f0a7a87e70049eac7a2617f9e16fd4c585a26e4330bdfc3a71e6a721f3 - url: "https://pub.dev" - source: hosted - version: "0.2.3" - geolocator_platform_interface: - dependency: transitive - description: - name: geolocator_platform_interface - sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67" - url: "https://pub.dev" - source: hosted - version: "4.2.6" - geolocator_web: - dependency: transitive - description: - name: geolocator_web - sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172 - url: "https://pub.dev" - source: hosted - version: "4.1.3" - geolocator_windows: - dependency: transitive - description: - name: geolocator_windows - sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6" - url: "https://pub.dev" - source: hosted - version: "0.2.5" - google_maps: - dependency: transitive - description: - name: google_maps - sha256: "4d6e199c561ca06792c964fa24b2bac7197bf4b401c2e1d23e345e5f9939f531" - url: "https://pub.dev" - source: hosted - version: "8.1.1" - google_maps_flutter: - dependency: "direct main" - description: - name: google_maps_flutter - sha256: e1805e5a5885bd14a1c407c59229f478af169bf4d04388586b19f53145a5db3a - url: "https://pub.dev" - source: hosted - version: "2.12.3" - google_maps_flutter_android: - dependency: transitive - description: - name: google_maps_flutter_android - sha256: a6c9d43f6a944ff4bae5c3deb34817970ac3d591dcd7f5bd2ea450ab9e9c514a - url: "https://pub.dev" - source: hosted - version: "2.18.2" - google_maps_flutter_ios: - dependency: transitive - description: - name: google_maps_flutter_ios - sha256: ca02463b19a9abc7d31fcaf22631d021d647107467f741b917a69fa26659fd75 - url: "https://pub.dev" - source: hosted - version: "2.15.5" - google_maps_flutter_platform_interface: - dependency: transitive - description: - name: google_maps_flutter_platform_interface - sha256: f4b9b44f7b12a1f6707ffc79d082738e0b7e194bf728ee61d2b3cdf5fdf16081 - url: "https://pub.dev" - source: hosted - version: "2.14.0" - google_maps_flutter_web: - dependency: transitive - description: - name: google_maps_flutter_web - sha256: "9d57993ba29b80bb637bb7b9784159ca93071d5940e4d0383ae2358ad371e375" - url: "https://pub.dev" - source: hosted - version: "0.5.13" - gsettings: - dependency: transitive - description: - name: gsettings - sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c" - url: "https://pub.dev" - source: hosted - version: "0.2.8" - health: - dependency: "direct main" - description: - name: health - sha256: "996664904194b8e0a09dfe761e168fdbf9ef43c51e7de39cd075de71c7258ada" - url: "https://pub.dev" - source: hosted - version: "13.1.3" - html: - dependency: transitive - description: - name: html - sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" - url: "https://pub.dev" - source: hosted - version: "0.15.6" - http: - dependency: "direct main" - description: - name: http - sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 - url: "https://pub.dev" - source: hosted - version: "1.5.0" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - image_picker: - dependency: "direct main" - description: - name: image_picker - sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - image_picker_android: - dependency: transitive - description: - name: image_picker_android - sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" - url: "https://pub.dev" - source: hosted - version: "0.8.13+1" - image_picker_for_web: - dependency: transitive - description: - name: image_picker_for_web - sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - image_picker_ios: - dependency: transitive - description: - name: image_picker_ios - sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e - url: "https://pub.dev" - source: hosted - version: "0.8.13" - image_picker_linux: - dependency: transitive - description: - name: image_picker_linux - sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" - url: "https://pub.dev" - source: hosted - version: "0.2.2" - image_picker_macos: - dependency: transitive - description: - name: image_picker_macos - sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 - url: "https://pub.dev" - source: hosted - version: "0.2.2" - image_picker_platform_interface: - dependency: transitive - description: - name: image_picker_platform_interface - sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - image_picker_windows: - dependency: transitive - description: - name: image_picker_windows - sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae - url: "https://pub.dev" - source: hosted - version: "0.2.2" - injector: - dependency: "direct main" - description: - name: injector - sha256: d692c37788872bfd4bd7c01b864b0712190a25ae5a346431ff69949d5728a2e1 - url: "https://pub.dev" - source: hosted - version: "4.0.0" - intl: - dependency: transitive - description: - name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.dev" - source: hosted - version: "0.20.2" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" - source: hosted - version: "4.9.0" - just_audio: - dependency: "direct main" - description: - name: just_audio - sha256: "679637a3ec5b6e00f36472f5a3663667df00ee4822cbf5dafca0f568c710960a" - url: "https://pub.dev" - source: hosted - version: "0.10.4" - just_audio_platform_interface: - dependency: transitive - description: - name: just_audio_platform_interface - sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a" - url: "https://pub.dev" - source: hosted - version: "4.6.0" - just_audio_web: - dependency: transitive - description: - name: just_audio_web - sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" - url: "https://pub.dev" - source: hosted - version: "0.4.16" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" - url: "https://pub.dev" - source: hosted - version: "10.0.9" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 - url: "https://pub.dev" - source: hosted - version: "3.0.9" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - lints: - dependency: transitive - description: - name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.dev" - source: hosted - version: "5.1.1" - local_auth: - dependency: "direct main" - description: - name: local_auth - sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" - url: "https://pub.dev" - source: hosted - version: "2.3.0" - local_auth_android: - dependency: transitive - description: - name: local_auth_android - sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3" - url: "https://pub.dev" - source: hosted - version: "1.0.52" - local_auth_darwin: - dependency: transitive - description: - name: local_auth_darwin - sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055" - url: "https://pub.dev" - source: hosted - version: "1.6.0" - local_auth_platform_interface: - dependency: transitive - description: - name: local_auth_platform_interface - sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" - url: "https://pub.dev" - source: hosted - version: "1.0.10" - local_auth_windows: - dependency: transitive - description: - name: local_auth_windows - sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 - url: "https://pub.dev" - source: hosted - version: "1.0.11" - logger: - dependency: "direct main" - description: - name: logger - sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c" - url: "https://pub.dev" - source: hosted - version: "2.6.1" - lottie: - dependency: "direct main" - description: - name: lottie - sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 - url: "https://pub.dev" - source: hosted - version: "3.3.1" - manage_calendar_events: - dependency: "direct main" - description: - name: manage_calendar_events - sha256: f17600fcb7dc7047120c185993045e493d686930237b4e3c2689c26a64513d66 - url: "https://pub.dev" - source: hosted - version: "2.0.3" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.dev" - source: hosted - version: "0.12.17" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" - source: hosted - version: "0.11.1" - meta: - dependency: transitive - description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" - source: hosted - version: "1.16.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - nm: - dependency: transitive - description: - name: nm - sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" - url: "https://pub.dev" - source: hosted - version: "0.5.0" - octo_image: - dependency: transitive - description: - name: octo_image - sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - package_info_plus: - dependency: transitive - description: - name: package_info_plus - sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" - url: "https://pub.dev" - source: hosted - version: "8.3.1" - package_info_plus_platform_interface: - dependency: transitive - description: - name: package_info_plus_platform_interface - sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - path_parsing: - dependency: transitive - description: - name: path_parsing - sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - path_provider: - dependency: transitive - description: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" - url: "https://pub.dev" - source: hosted - version: "2.2.18" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" - url: "https://pub.dev" - source: hosted - version: "2.4.2" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - permission_handler: - dependency: "direct main" - description: - name: permission_handler - sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 - url: "https://pub.dev" - source: hosted - version: "12.0.1" - permission_handler_android: - dependency: transitive - description: - name: permission_handler_android - sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" - url: "https://pub.dev" - source: hosted - version: "13.0.1" - permission_handler_apple: - dependency: transitive - description: - name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 - url: "https://pub.dev" - source: hosted - version: "9.4.7" - permission_handler_html: - dependency: transitive - description: - name: permission_handler_html - sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" - url: "https://pub.dev" - source: hosted - version: "0.1.3+5" - permission_handler_platform_interface: - dependency: transitive - description: - name: permission_handler_platform_interface - sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 - url: "https://pub.dev" - source: hosted - version: "4.3.0" - permission_handler_windows: - dependency: transitive - description: - name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" - url: "https://pub.dev" - source: hosted - version: "0.2.1" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" - url: "https://pub.dev" - source: hosted - version: "7.0.1" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - posix: - dependency: transitive - description: - name: posix - sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" - url: "https://pub.dev" - source: hosted - version: "6.0.3" - provider: - dependency: "direct main" - description: - name: provider - sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" - url: "https://pub.dev" - source: hosted - version: "6.1.5+1" - rrule: - dependency: transitive - description: - name: rrule - sha256: b7425410c594d4b6717c9f17ec8ef83c9d1ff2e513c428a135b5924fc2e8e045 - url: "https://pub.dev" - source: hosted - version: "0.2.17" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" - source: hosted - version: "0.28.0" - sanitize_html: - dependency: transitive - description: - name: sanitize_html - sha256: "12669c4a913688a26555323fb9cec373d8f9fbe091f2d01c40c723b33caa8989" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - share_plus: - dependency: "direct main" - description: - name: share_plus - sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 - url: "https://pub.dev" - source: hosted - version: "11.1.0" - share_plus_platform_interface: - dependency: transitive - description: - name: share_plus_platform_interface - sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" - url: "https://pub.dev" - source: hosted - version: "6.1.0" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" - url: "https://pub.dev" - source: hosted - version: "2.5.3" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74 - url: "https://pub.dev" - source: hosted - version: "2.4.12" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" - url: "https://pub.dev" - source: hosted - version: "2.5.4" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.dev" - source: hosted - version: "2.4.3" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shimmer: - dependency: "direct main" - description: - name: shimmer - sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - sizer: - dependency: "direct main" - description: - name: sizer - sha256: "9963c89e4d30d7c2108de3eafc0a7e6a4a8009799376ea6be5ef0a9ad87cfbad" - url: "https://pub.dev" - source: hosted - version: "3.1.3" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - sqflite: - dependency: transitive - description: - name: sqflite - sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 - url: "https://pub.dev" - source: hosted - version: "2.4.2" - sqflite_android: - dependency: transitive - description: - name: sqflite_android - sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" - url: "https://pub.dev" - source: hosted - version: "2.5.6" - sqflite_darwin: - dependency: transitive - description: - name: sqflite_darwin - sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" - url: "https://pub.dev" - source: hosted - version: "2.4.2" - sqflite_platform_interface: - dependency: transitive - description: - name: sqflite_platform_interface - sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" - url: "https://pub.dev" - source: hosted - version: "2.4.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.dev" - source: hosted - version: "2.1.1" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - syncfusion_flutter_calendar: - dependency: "direct main" - description: - name: syncfusion_flutter_calendar - sha256: "8e8a4eef01d6a82ae2c17e76d497ff289ded274de014c9f471ffabc12d1e2e71" - url: "https://pub.dev" - source: hosted - version: "30.2.7" - syncfusion_flutter_core: - dependency: transitive - description: - name: syncfusion_flutter_core - sha256: bfd026c0f9822b49ff26fed11cd3334519acb6a6ad4b0c81d9cd18df6af1c4c0 - url: "https://pub.dev" - source: hosted - version: "30.2.7" - syncfusion_flutter_datepicker: - dependency: transitive - description: - name: syncfusion_flutter_datepicker - sha256: b5f35cc808e91b229d41613efe71dadab1549a35bfd493f922fc06ccc2fe908c - url: "https://pub.dev" - source: hosted - version: "30.2.7" - syncfusion_localizations: - dependency: transitive - description: - name: syncfusion_localizations - sha256: bb32b07879b4c1dee5d4c8ad1c57343a4fdae55d65a87f492727c11b68f23164 - url: "https://pub.dev" - source: hosted - version: "30.2.7" - synchronized: - dependency: transitive - description: - name: synchronized - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 - url: "https://pub.dev" - source: hosted - version: "3.4.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test_api: - dependency: transitive - description: - name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd - url: "https://pub.dev" - source: hosted - version: "0.7.4" - time: - dependency: transitive - description: - name: time - sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - timezone: - dependency: transitive - description: - name: timezone - sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 - url: "https://pub.dev" - source: hosted - version: "0.10.1" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - url_launcher: - dependency: "direct main" - description: - name: url_launcher - sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.dev" - source: hosted - version: "6.3.2" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7" - url: "https://pub.dev" - source: hosted - version: "6.3.18" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 - url: "https://pub.dev" - source: hosted - version: "6.3.4" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f - url: "https://pub.dev" - source: hosted - version: "3.2.3" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" - url: "https://pub.dev" - source: hosted - version: "3.1.4" - uuid: - dependency: "direct main" - description: - name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff - url: "https://pub.dev" - source: hosted - version: "4.5.1" - vector_graphics: - dependency: transitive - description: - name: vector_graphics - sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 - url: "https://pub.dev" - source: hosted - version: "1.1.19" - vector_graphics_codec: - dependency: transitive - description: - name: vector_graphics_codec - sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" - url: "https://pub.dev" - source: hosted - version: "1.1.13" - vector_graphics_compiler: - dependency: transitive - description: - name: vector_graphics_compiler - sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc - url: "https://pub.dev" - source: hosted - version: "1.1.19" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 - url: "https://pub.dev" - source: hosted - version: "15.0.0" - web: - dependency: "direct main" - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - win32: - dependency: transitive - description: - name: win32 - sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" - url: "https://pub.dev" - source: hosted - version: "5.14.0" - win32_registry: - dependency: transitive - description: - name: win32_registry - sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.dev" - source: hosted - version: "6.6.1" -sdks: - dart: ">=3.8.0 <4.0.0" - flutter: ">=3.29.0" From 357cf4392e40d1530504f8b783232f79be4fde6c Mon Sep 17 00:00:00 2001 From: Faiz Hashmi Date: Mon, 1 Sep 2025 12:58:41 +0300 Subject: [PATCH 02/17] Updated architecture --- android/app/build.gradle.kts | 3 +- android/build.gradle.kts | 6 +- ios/Flutter/AppFrameworkInfo.plist | 2 +- ios/Podfile | 2 +- ios/Runner.xcodeproj/project.pbxproj | 6 +- lib/core/api/api_client.dart | 58 +++++++------- lib/core/app_state.dart | 8 +- lib/core/dependencies.dart | 30 ++++++- lib/core/exceptions/api_exception.dart | 2 + lib/core/exceptions/api_failure.dart | 42 ++++++++++ .../authentication/authentication_repo.dart | 57 +++++++++++++- .../authentication_view_model.dart | 45 ++++++++++- .../book_appointments_repo.dart | 56 +++++++++++++ lib/features/common/common_repo.dart | 13 ++++ .../my_appointments/my_appointments_repo.dart | 78 +++++++++++++++++++ lib/main.dart | 26 +++---- lib/services/cache_service.dart | 14 ++++ lib/services/logger_service.dart | 4 +- pubspec.yaml | 4 +- 19 files changed, 386 insertions(+), 70 deletions(-) create mode 100644 lib/core/exceptions/api_failure.dart create mode 100644 lib/services/cache_service.dart diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index f5315240..432ad2a2 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,10 +1,9 @@ plugins { id("com.android.application") id("kotlin-android") - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") id("com.google.gms.google-services") - id("com.huawei.agconnect") +// id("com.huawei.agconnect") } android { diff --git a/android/build.gradle.kts b/android/build.gradle.kts index d60f0c02..71a2e607 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -2,12 +2,12 @@ buildscript { repositories { google() mavenCentral() - maven { url = uri("https://developer.huawei.com/repo/") } +// maven { url = uri("https://developer.huawei.com/repo/") } } dependencies { classpath("com.android.tools.build:gradle:7.4.2") classpath("com.google.gms:google-services:4.4.1") - classpath("com.huawei.agconnect:agcp:1.9.3.302") +// classpath("com.huawei.agconnect:agcp:1.9.3.302") } } @@ -15,7 +15,7 @@ allprojects { repositories { google() mavenCentral() - maven { url = uri("https://developer.huawei.com/repo/") } +// maven { url = uri("https://developer.huawei.com/repo/") } } } diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 7c569640..1dc6cf76 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 13.0 diff --git a/ios/Podfile b/ios/Podfile index 60104726..9249e54d 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -99,4 +99,4 @@ post_integrate do |installer| end end project.save() -end \ No newline at end of file +end diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 3f365182..bac21577 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -435,7 +435,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -564,7 +564,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -615,7 +615,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 4e08cd1e..ef15c956 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -3,14 +3,15 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/consts.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:http/http.dart'; import 'package:http/io_client.dart'; -import '../app_state.dart'; -import '../consts.dart'; -import '../utils/utils.dart'; -import '../../main.dart'; // ignore_for_file: avoid_annotating_with_dynamic @@ -31,12 +32,16 @@ class APIError { } APIException _throwAPIException(Response response, Function retryCallBack) { + + +LoggerService loggerService = getIt.get(); + switch (response.statusCode) { case 200: APIError? apiError; if (response.body != null && response.body.isNotEmpty) { var jsonError = jsonDecode(response.body); - print(jsonError); + loggerService.logInfo(jsonError.toString()); apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage']); } return APIException(APIException.BAD_REQUEST, error: apiError); @@ -75,11 +80,9 @@ abstract class IApiClient { } class ApiClient implements IApiClient { - // static final ApiClient _instance = ApiClient._internal(); - - // ApiClient._internal(); + LoggerService loggerService; - // factory ApiClient() => _instance; + ApiClient({required this.loggerService}); @override Future postJsonForObject(FactoryConstructor factoryConstructor, String url, T jsonObject, @@ -89,18 +92,18 @@ class ApiClient implements IApiClient { _headers.addAll(headers); } if (!kReleaseMode) { - print("Url:$url"); + loggerService.logInfo("Url:$url"); var bodyJson = json.encode(jsonObject); - print("body:$bodyJson"); + loggerService.logInfo("body:$bodyJson"); } var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: _headers, retryTimes: retryTimes); // try { if (!kReleaseMode) { - logger.i("res: " + response.body); + loggerService.logInfo("res: ${response.body}"); } var jsonData = jsonDecode(response.body); if (jsonData["IsAuthenticated"] != null) { - AppState().setIsAuthenticated = jsonData["IsAuthenticated"]; + getIt.get().setIsAuthenticated = jsonData["IsAuthenticated"]; } if (jsonData["ErrorMessage"] == null) { return factoryConstructor(jsonData); @@ -146,16 +149,16 @@ class ApiClient implements IApiClient { } if (queryParameters != null) { // var queryString = new Uri(queryParameters: queryParameters).query; - var queryString = Uri(queryParameters: queryParameters.map((key, value) => MapEntry(key, value == null ? null : value.toString()))).query; - url = url + '?' + queryString; + var queryString = Uri(queryParameters: queryParameters.map((key, value) => MapEntry(key, value?.toString()))).query; + url = '$url?$queryString'; } // if (!kReleaseMode && url.contains("saned")) { if (!kReleaseMode) { - print("Url: $url"); - print("Headers: $_headers"); + loggerService.logInfo("Url: $url"); + loggerService.logInfo("Headers: $_headers"); // var bodyJson = json.encode(requestBody); - print("body: $requestBody"); + loggerService.logInfo("body: $requestBody"); } var response = await _post(Uri.parse(url), body: requestBody, headers: _headers).timeout(Duration(seconds: 120)); @@ -170,7 +173,7 @@ class ApiClient implements IApiClient { } } on SocketException catch (e) { if (retryTimes > 0) { - print('will retry after 3 seconds...'); + loggerService.logInfo('will retry after 3 seconds...'); await Future.delayed(Duration(seconds: 3)); return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { @@ -178,7 +181,7 @@ class ApiClient implements IApiClient { } } on HttpException catch (e) { if (retryTimes > 0) { - print('will retry after 3 seconds...'); + loggerService.logInfo('will retry after 3 seconds...'); await Future.delayed(Duration(seconds: 3)); return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { @@ -188,7 +191,7 @@ class ApiClient implements IApiClient { throw APIException(APIException.TIMEOUT, arguments: e); } on ClientException catch (e) { if (retryTimes > 0) { - print('will retry after 3 seconds...'); + loggerService.logInfo('will retry after 3 seconds...'); await Future.delayed(Duration(seconds: 3)); return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { @@ -199,7 +202,7 @@ class ApiClient implements IApiClient { @override Future getJsonForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0, bool isAuthAPI = false}) async { - logger.i("Url:$url"); + loggerService.logInfo("Url:$url"); if (headers == null) { headers = {'Content-Type': 'application/json'}; } else { @@ -225,8 +228,8 @@ class ApiClient implements IApiClient { } if (queryParameters != null) { - var queryString = new Uri(queryParameters: queryParameters).query; - url = url + '?' + queryString; + var queryString = Uri(queryParameters: queryParameters).query; + url = '$url?$queryString'; } var response = await _get(Uri.parse(url), headers: _headers).timeout(Duration(seconds: 60)); @@ -239,7 +242,7 @@ class ApiClient implements IApiClient { } } on SocketException catch (e) { if (retryTimes > 0) { - print('will retry after 3 seconds...'); + loggerService.logInfo('will retry after 3 seconds...'); await Future.delayed(Duration(seconds: 3)); return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { @@ -247,7 +250,7 @@ class ApiClient implements IApiClient { } } on HttpException catch (e) { if (retryTimes > 0) { - print('will retry after 3 seconds...'); + loggerService.logInfo('will retry after 3 seconds...'); await Future.delayed(Duration(seconds: 3)); return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { @@ -257,7 +260,7 @@ class ApiClient implements IApiClient { throw APIException(APIException.TIMEOUT, arguments: e); } on ClientException catch (e) { if (retryTimes > 0) { - print('will retry after 3 seconds...'); + loggerService.logInfo('will retry after 3 seconds...'); await Future.delayed(Duration(seconds: 3)); return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { @@ -282,7 +285,6 @@ class ApiClient implements IApiClient { Future _post(url, {Map? headers, body, Encoding? encoding}) => _withClient((client) => client.post(url, headers: headers, body: body, encoding: encoding)); - Future _put(url, {Map? headers, body, Encoding? encoding}) => _withClient((client) => client.put(url, headers: headers, body: body, encoding: encoding)); @override void setHomeUrl(String url) { diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 4bed6768..684c6003 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -5,14 +5,10 @@ import 'package:hmg_patient_app_new/main.dart'; import 'consts.dart'; class AppState { - static final AppState _instance = AppState._internal(); - - AppState._internal(); - - factory AppState() => _instance; + // Simple constructor - let get_it handle the singleton behavior + AppState(); //Tokens - bool isAuthenticated = false; set setIsAuthenticated(v) => isAuthenticated = v; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 4b3b0d6f..82e3fe77 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -1,9 +1,31 @@ +import 'package:get_it/get_it.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; -import 'package:injector/injector.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; +import 'package:hmg_patient_app_new/features/common/common_repo.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; +import 'package:logger/web.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +GetIt getIt = GetIt.instance; class AppDependencies { - static void addDependencies() { - Injector injector = Injector.appInstance; - injector.registerSingleton(() => AppState()); + static Future addDependencies() async { + // Services + getIt.registerLazySingleton(() => LoggerServiceImp(logger: Logger(printer: PrettyPrinter(lineLength: 0)))); + final sharedPreferences = await SharedPreferences.getInstance(); + getIt.registerLazySingleton(() => CacheServiceImp(sharedPreferences: sharedPreferences)); + getIt.registerSingleton(AppState()); + getIt.registerLazySingleton(() => ApiClient(loggerService: getIt())); + + // Repositories + getIt.registerLazySingleton(() => CommonRepoImp(loggerService: getIt())); + getIt.registerLazySingleton(() => AuthenticationRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => BookAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => MyAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); + } } diff --git a/lib/core/exceptions/api_exception.dart b/lib/core/exceptions/api_exception.dart index 7d94212b..5076cc32 100644 --- a/lib/core/exceptions/api_exception.dart +++ b/lib/core/exceptions/api_exception.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:equatable/equatable.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; class APIException implements Exception { @@ -27,3 +28,4 @@ class APIException implements Exception { return jsonEncode(this); } } + diff --git a/lib/core/exceptions/api_failure.dart b/lib/core/exceptions/api_failure.dart new file mode 100644 index 00000000..eaa434e3 --- /dev/null +++ b/lib/core/exceptions/api_failure.dart @@ -0,0 +1,42 @@ + +import 'package:equatable/equatable.dart'; + +abstract class Failure extends Equatable implements Exception { + final String message; + const Failure(this.message); +} + +class ServerFailure extends Failure { + const ServerFailure(super.message); + + @override + List get props => [message]; +} + +class ConnectivityFailure extends Failure { + const ConnectivityFailure(super.message); + + @override + List get props => [message]; +} + +class LocalStorageFailure extends Failure { + const LocalStorageFailure(super.message); + + @override + List get props => [message]; +} + +class DuplicateUsername extends Failure { + const DuplicateUsername({String? message}) : super(message ?? ''); + + @override + List get props => [message]; +} + +class InvalidCredentials extends Failure { + const InvalidCredentials({String? message}) : super(message ?? ''); + + @override + List get props => [message]; +} diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 1755fbaf..64bc2ca2 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -1,4 +1,59 @@ -class AuthenticationRepo { + +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class AuthenticationRepo { + Future> signIn({required String phone, required String password}); +} + +class AuthenticationRepoImp implements AuthenticationRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + AuthenticationRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future> signIn({required String phone, required String password}) async { + try { + // Mock API call with delayed response + final result = await Future.delayed( + const Duration(seconds: 2), + () => { + 'success': true, + 'data': [ + { + 'id': '1', + 'name': 'Dr. Ahmed Hassan', + 'specialty': 'Cardiology', + 'experience': '10 years', + 'rating': 4.8, + 'image': 'https://example.com/doctor1.jpg' + }, + ] + } + ); + + if (result != null && result is Map && result['success'] != null && result['success'] != null && result['success'] != false) { + return Right(result); + } else { + loggerService.errorLogs(result.toString()); + return Left(ServerFailure(result.toString())); + } + } on APIException catch (e) { + APIError? apiError; + if (e.error is APIError) { + apiError = e.error as APIError; + } + loggerService.errorLogs(e.toString()); + return Left(ServerFailure(apiError?.errorMessage ?? e.message)); + } catch (e) { + loggerService.errorLogs(e.toString()); + return Left(ServerFailure(e.toString())); + } + } } \ No newline at end of file diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index af542aca..434df16c 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1,5 +1,46 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; class AuthenticationViewModel extends ChangeNotifier { - // Add properties and methods related to authentication here -} \ No newline at end of file + AuthenticationRepo authenticationRepo; + + AuthenticationViewModel({required this.authenticationRepo}); + + Future signUp({ + required String phone, + required String password, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + Utils.showLoading(); + final resultEither = await authenticationRepo.signIn( + phone: phone, + password: password, + ); + + if (resultEither.isLeft()) { + + + } + + if (resultEither.isRight()) { + + + } + + + resultEither.fold( + (failure) { + Utils.hideLoading(); + notifyListeners(); + if (onError != null) onError(failure.message); + }, + (data) { + Utils.hideLoading(); + notifyListeners(); + if (onSuccess != null) onSuccess(data); + }, + ); + } +} diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index e69de29b..b994b35e 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -0,0 +1,56 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class BookAppointmentsRepo { + Future> getDoctors(); +} + +class BookAppointmentsRepoImp implements BookAppointmentsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + BookAppointmentsRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future> getDoctors() async { + try { + // Mock API call with delayed response + final result = await Future.delayed( + const Duration(seconds: 2), + () => { + 'success': true, + 'data': [ + { + 'id': '1', + 'name': 'Dr. Ahmed Hassan', + 'specialty': 'Cardiology', + 'experience': '10 years', + 'rating': 4.8, + 'image': 'https://example.com/doctor1.jpg' + }, + ] + } + ); + + if (result != null && result is Map && result['success'] != null && result['success'] != false) { + return Right(result); + } else { + loggerService.errorLogs(result.toString()); + return Left(ServerFailure(result.toString())); + } + } on APIException catch (e) { + APIError? apiError; + if (e.error is APIError) { + apiError = e.error as APIError; + } + loggerService.errorLogs(e.toString()); + return Left(ServerFailure(apiError?.errorMessage ?? e.message)); + } catch (e) { + loggerService.errorLogs(e.toString()); + return Left(ServerFailure(e.toString())); + } + } +} diff --git a/lib/features/common/common_repo.dart b/lib/features/common/common_repo.dart index e69de29b..1d7301c0 100644 --- a/lib/features/common/common_repo.dart +++ b/lib/features/common/common_repo.dart @@ -0,0 +1,13 @@ +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class CommonRepo { + +} + + +class CommonRepoImp implements CommonRepo { + LoggerService loggerService; + + CommonRepoImp({required this.loggerService}); + +} \ No newline at end of file diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index e69de29b..7ac0c47a 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -0,0 +1,78 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class MyAppointmentsRepo { + Future> getMyAppointments(); +} + +class MyAppointmentsRepoImp implements MyAppointmentsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + MyAppointmentsRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future> getMyAppointments() async { + try { + // Mock API call with delayed response + final result = await Future.delayed( + const Duration(seconds: 2), + () => { + 'success': true, + 'data': [ + { + 'id': '1', + 'doctorName': 'Dr. Ahmed Hassan', + 'specialty': 'Cardiology', + 'appointmentDate': '2025-09-05', + 'appointmentTime': '10:00 AM', + 'status': 'confirmed', + 'clinicName': 'HMG Hospital', + 'patientName': 'John Doe' + }, + { + 'id': '2', + 'doctorName': 'Dr. Sarah Johnson', + 'specialty': 'Dermatology', + 'appointmentDate': '2025-09-08', + 'appointmentTime': '2:30 PM', + 'status': 'pending', + 'clinicName': 'HMG Medical Center', + 'patientName': 'John Doe' + }, + { + 'id': '3', + 'doctorName': 'Dr. Mohamed Ali', + 'specialty': 'Pediatrics', + 'appointmentDate': '2025-08-25', + 'appointmentTime': '11:15 AM', + 'status': 'completed', + 'clinicName': 'HMG Children\'s Clinic', + 'patientName': 'John Doe' + } + ] + } + ); + + if (result != null && result is Map && result['success'] != null && result['success'] != false) { + return Right(result); + } else { + loggerService.errorLogs(result.toString()); + return Left(ServerFailure(result.toString())); + } + } on APIException catch (e) { + APIError? apiError; + if (e.error is APIError) { + apiError = e.error as APIError; + } + loggerService.errorLogs(e.toString()); + return Left(ServerFailure(apiError?.errorMessage ?? e.message)); + } catch (e) { + loggerService.errorLogs(e.toString()); + return Left(ServerFailure(e.toString())); + } + } +} diff --git a/lib/main.dart b/lib/main.dart index ce601a2b..8adfc4ab 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,8 +6,10 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/theme/app_theme.dart'; import 'package:logger/logger.dart'; import 'package:provider/provider.dart'; @@ -19,23 +21,11 @@ import 'firebase_options.dart'; var globalMessengerKey = GlobalKey(); final navigatorKey = GlobalKey(); -Logger logger = Logger( - printer: PrettyPrinter( - lineLength: 0, - ), -); - -late AppState appState; @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - print("Firebase backgroundMessageHandler Main!!!"); - // debugPrint('backgroundMessage: message => ${message.notification!.title.toString()}'); - // messagesOpended = message.notification!.title.toString(); - var payload = message.data; - // showCallkitIncoming(payload); - // await backgroundCallHandler(payload); + getIt.get().logInfo("Firebase backgroundMessageHandler Main!!!"); } class MyHttpOverrides extends HttpOverrides { @@ -45,12 +35,17 @@ class MyHttpOverrides extends HttpOverrides { } } -void main() async { +Future callInitializations() async { WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + AppDependencies.addDependencies(); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); HttpOverrides.global = MyHttpOverrides(); +} + +void main() async { + callInitializations(); runApp( EasyLocalization( supportedLocales: const [ @@ -61,7 +56,7 @@ void main() async { fallbackLocale: Locale('en', 'US'), child: MultiProvider(providers: [ ChangeNotifierProvider( - create: (_) => AuthenticationViewModel(), + create: (_) => AuthenticationViewModel(authenticationRepo: getIt()), ), ], child: MyApp()), ), @@ -71,7 +66,6 @@ void main() async { class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { return SafeArea( diff --git a/lib/services/cache_service.dart b/lib/services/cache_service.dart new file mode 100644 index 00000000..f25409ef --- /dev/null +++ b/lib/services/cache_service.dart @@ -0,0 +1,14 @@ + +import 'package:shared_preferences/shared_preferences.dart'; + +abstract class CacheService { + +} + +class CacheServiceImp implements CacheService { + SharedPreferences sharedPreferences; + + CacheServiceImp({required this.sharedPreferences}); + + +} diff --git a/lib/services/logger_service.dart b/lib/services/logger_service.dart index 833a7d20..0b33c524 100644 --- a/lib/services/logger_service.dart +++ b/lib/services/logger_service.dart @@ -3,7 +3,7 @@ import 'package:logger/logger.dart'; abstract class LoggerService { - void logError(String message); + void errorLogs(String message); void logInfo(String message); } @@ -16,7 +16,7 @@ class LoggerServiceImp implements LoggerService { @override - void logError(String message) { + void errorLogs(String message) { logger.e(message); } diff --git a/pubspec.yaml b/pubspec.yaml index db3d25ef..27ba216a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,8 +30,8 @@ dependencies: # firebase_core: ^3.13.1 permission_handler: ^12.0.1 flutter_local_notifications: ^19.4.1 - injector: ^4.0.0 provider: ^6.1.5+1 + get_it: ^8.2.0 just_audio: ^0.10.4 flutter_callkit_incoming: git: @@ -60,6 +60,8 @@ dependencies: dropdown_search: ^6.0.2 google_maps_flutter: ^2.12.3 flutter_zoom_videosdk: ^2.1.10 + dartz: ^0.10.1 + equatable: ^2.0.7 web: any From 45e868adbad8d4c1865b4da1ff978353c6cbb7a2 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Mon, 1 Sep 2025 15:55:21 +0300 Subject: [PATCH 03/17] pushed new API --- ios/Runner.xcodeproj/project.pbxproj | 14 +++- .../authentication/authentication_repo.dart | 37 +++------ .../authentication_view_model.dart | 13 ++-- ...ent_last_login_details_response_model.dart | 30 ++++---- .../models/select_device_by_imei.dart | 77 +++++++++++++++++++ 5 files changed, 121 insertions(+), 50 deletions(-) create mode 100644 lib/features/authentication/models/select_device_by_imei.dart diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index bac21577..dae81f24 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -279,10 +279,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; @@ -296,10 +300,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; @@ -451,7 +459,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 2; - DEVELOPMENT_TEAM = 3A359E86ZF; + DEVELOPMENT_TEAM = ZB3P5B74MA; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.6; @@ -633,7 +641,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 2; - DEVELOPMENT_TEAM = 3A359E86ZF; + DEVELOPMENT_TEAM = ZB3P5B74MA; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.6; @@ -658,7 +666,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 2; - DEVELOPMENT_TEAM = 3A359E86ZF; + DEVELOPMENT_TEAM = ZB3P5B74MA; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.6; diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 64bc2ca2..a5e9ae33 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -1,12 +1,13 @@ - import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; +import 'package:http/http.dart'; abstract class AuthenticationRepo { - Future> signIn({required String phone, required String password}); + Future> selectDeviceByImei({required String deviceIMEI}); } class AuthenticationRepoImp implements AuthenticationRepo { @@ -16,28 +17,14 @@ class AuthenticationRepoImp implements AuthenticationRepo { AuthenticationRepoImp({required this.loggerService, required this.apiClient}); @override - Future> signIn({required String phone, required String password}) async { - try { - // Mock API call with delayed response - final result = await Future.delayed( - const Duration(seconds: 2), - () => { - 'success': true, - 'data': [ - { - 'id': '1', - 'name': 'Dr. Ahmed Hassan', - 'specialty': 'Cardiology', - 'experience': '10 years', - 'rating': 4.8, - 'image': 'https://example.com/doctor1.jpg' - }, - ] - } - ); + Future> selectDeviceByImei({required String deviceIMEI}) async { + final mapDevice = {"": deviceIMEI}; - if (result != null && result is Map && result['success'] != null && result['success'] != null && result['success'] != false) { - return Right(result); + final String apiUrl = "https://hmgwebservices.com/Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI" ; + try { + final Response result = await apiClient.postJsonForResponse(apiUrl, mapDevice); + if (result !=null) { + return Right(null); } else { loggerService.errorLogs(result.toString()); return Left(ServerFailure(result.toString())); @@ -54,6 +41,4 @@ class AuthenticationRepoImp implements AuthenticationRepo { return Left(ServerFailure(e.toString())); } } - - -} \ No newline at end of file +} diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 434df16c..5b457901 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -7,6 +7,8 @@ class AuthenticationViewModel extends ChangeNotifier { AuthenticationViewModel({required this.authenticationRepo}); + + Future signUp({ required String phone, required String password, @@ -14,13 +16,12 @@ class AuthenticationViewModel extends ChangeNotifier { Function(String)? onError, }) async { Utils.showLoading(); - final resultEither = await authenticationRepo.signIn( - phone: phone, - password: password, - ); + final String deviceIMEI = + "cIkkB7h7Q7uoFkC4Qv82xG:APA91bEb53Z9XzqymCIctaLxCoMX6bm9fuKlWILQ59uUqfwhCoD42AOP1-jWGB1WYd9BVN5PT2pUUFxrT07vcNg1KH9OH39mrPgCl0m21XVIgWrzNnCkufg"; - if (resultEither.isLeft()) { + final resultEither = await authenticationRepo.selectDeviceByImei(deviceIMEI: deviceIMEI); + if (resultEither.isLeft()) { } @@ -37,7 +38,7 @@ class AuthenticationViewModel extends ChangeNotifier { if (onError != null) onError(failure.message); }, (data) { - Utils.hideLoading(); + notifyListeners(); if (onSuccess != null) onSuccess(data); }, diff --git a/lib/features/authentication/models/get_patient_last_login_details_response_model.dart b/lib/features/authentication/models/get_patient_last_login_details_response_model.dart index a14da5e9..ce48dd48 100644 --- a/lib/features/authentication/models/get_patient_last_login_details_response_model.dart +++ b/lib/features/authentication/models/get_patient_last_login_details_response_model.dart @@ -48,21 +48,21 @@ class GetPatientLastLoginDetailsResponseModel { } Map toJson() { - final Map data = new Map(); - data['ID'] = this.iD; - data['IMEI'] = this.iMEI; - data['LogInType'] = this.logInType; - data['PatientID'] = this.patientID; - data['OutSA'] = this.outSA; - data['Mobile'] = this.mobile; - data['IdentificationNo'] = this.identificationNo; - data['Name'] = this.name; - data['NameN'] = this.nameN; - data['CreatedOn'] = this.createdOn; - data['EditedOn'] = this.editedOn; - data['BiometricEnabled'] = this.biometricEnabled; - data['PatientType'] = this.patientType; - data['PreferredLanguage'] = this.preferredLanguage; + final Map data = {}; + data['ID'] = iD; + data['IMEI'] = iMEI; + data['LogInType'] = logInType; + data['PatientID'] = patientID; + data['OutSA'] = outSA; + data['Mobile'] = mobile; + data['IdentificationNo'] = identificationNo; + data['Name'] = name; + data['NameN'] = nameN; + data['CreatedOn'] = createdOn; + data['EditedOn'] = editedOn; + data['BiometricEnabled'] = biometricEnabled; + data['PatientType'] = patientType; + data['PreferredLanguage'] = preferredLanguage; return data; } } diff --git a/lib/features/authentication/models/select_device_by_imei.dart b/lib/features/authentication/models/select_device_by_imei.dart new file mode 100644 index 00000000..2ab669e2 --- /dev/null +++ b/lib/features/authentication/models/select_device_by_imei.dart @@ -0,0 +1,77 @@ +// To parse this JSON data, do +// +// final selectDeviceByImeiRespModel = selectDeviceByImeiRespModelFromJson(jsonString); + +import 'dart:convert'; + +Map selectDeviceByImeiRespModelFromJson(String str) => Map.from(json.decode(str)).map((k, v) => MapEntry(k, v)); + +String selectDeviceByImeiRespModelToJson(Map data) => json.encode(Map.from(data).map((k, v) => MapEntry(k, v))); + +class SelectDeviceByImeiRespModelElement { + int id; + String imei; + int logInType; + int patientId; + bool outSa; + String mobile; + String identificationNo; + String name; + String nameN; + String createdOn; + String editedOn; + bool biometricEnabled; + int patientType; + int preferredLanguage; + + SelectDeviceByImeiRespModelElement({ + required this.id, + required this.imei, + required this.logInType, + required this.patientId, + required this.outSa, + required this.mobile, + required this.identificationNo, + required this.name, + required this.nameN, + required this.createdOn, + required this.editedOn, + required this.biometricEnabled, + required this.patientType, + required this.preferredLanguage, + }); + + factory SelectDeviceByImeiRespModelElement.fromJson(Map json) => SelectDeviceByImeiRespModelElement( + id: json["ID"], + imei: json["IMEI"], + logInType: json["LogInType"], + patientId: json["PatientID"], + outSa: json["OutSA"], + mobile: json["Mobile"], + identificationNo: json["IdentificationNo"], + name: json["Name"], + nameN: json["NameN"], + createdOn: json["CreatedOn"], + editedOn: json["EditedOn"], + biometricEnabled: json["BiometricEnabled"], + patientType: json["PatientType"], + preferredLanguage: json["PreferredLanguage"], + ); + + Map toJson() => { + "ID": id, + "IMEI": imei, + "LogInType": logInType, + "PatientID": patientId, + "OutSA": outSa, + "Mobile": mobile, + "IdentificationNo": identificationNo, + "Name": name, + "NameN": nameN, + "CreatedOn": createdOn, + "EditedOn": editedOn, + "BiometricEnabled": biometricEnabled, + "PatientType": patientType, + "PreferredLanguage": preferredLanguage, + }; +} From d3aff9f93d9b768ee4f9653265ddb2f9a764ebf1 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Mon, 1 Sep 2025 18:44:35 +0300 Subject: [PATCH 04/17] updating structure --- android/app/build.gradle.kts | 187 +++- android/app/src/main/AndroidManifest.xml | 86 +- .../hmg/hmg_patient_app_new/MainActivity.kt | 5 - android/app/src/main/res/values/styles.xml | 12 +- android/build.gradle.kts | 93 +- ios/Podfile.lock | 58 +- ios/Runner.xcodeproj/project.pbxproj | 8 - lib/core/api/api_client.dart | 795 ++++++++++++------ lib/core/api_consts.dart | 765 +++++++++++++++++ lib/core/app_state.dart | 32 +- lib/core/consts.dart | 19 - lib/core/dependencies.dart | 2 +- lib/core/exceptions/api_exception.dart | 5 +- lib/core/post_params_model.dart | 25 +- lib/core/utils/date_util.dart | 487 +++++++++++ lib/core/utils/size_config.dart | 1 + lib/core/utils/utils.dart | 40 + .../authentication/authentication_repo.dart | 22 +- .../authentication_view_model.dart | 40 +- .../models/authenticated_user_model.dart | 292 +++++++ ...heck_activation_code_request_register.dart | 121 +++ .../book_appointments_repo.dart | 8 - .../my_appointments/my_appointments_repo.dart | 7 - lib/main.dart | 4 +- .../login.dart | 22 +- lib/presentation/home/landing_page.dart | 15 +- lib/routes/app_routes.dart | 3 +- lib/services/analytics/analytics_service.dart | 143 ++++ .../analytics/flows/advance_payments.dart | 91 ++ lib/services/analytics/flows/app_nav.dart | 30 + .../analytics/flows/appointments.dart | 268 ++++++ .../analytics/flows/error_tracking.dart | 16 + .../analytics/flows/hamburger_menu.dart | 16 + .../analytics/flows/hmg_services.dart | 19 + lib/services/analytics/flows/live_care.dart | 102 +++ .../analytics/flows/login_registration.dart | 156 ++++ .../analytics/flows/offers_promotions.dart | 17 + lib/services/analytics/flows/todo_list.dart | 111 +++ lib/splashPage.dart | 35 +- lib/theme/colors.dart | 1 + pubspec.lock | 116 ++- pubspec.yaml | 2 + 42 files changed, 3757 insertions(+), 520 deletions(-) delete mode 100644 android/app/src/main/kotlin/com/ejada/hmg/hmg_patient_app_new/MainActivity.kt create mode 100644 lib/core/api_consts.dart create mode 100644 lib/core/utils/date_util.dart create mode 100644 lib/features/authentication/models/authenticated_user_model.dart create mode 100644 lib/features/authentication/models/check_activation_code_request_register.dart rename lib/presentation/{authantication => authentication}/login.dart (92%) create mode 100644 lib/services/analytics/analytics_service.dart create mode 100644 lib/services/analytics/flows/advance_payments.dart create mode 100644 lib/services/analytics/flows/app_nav.dart create mode 100644 lib/services/analytics/flows/appointments.dart create mode 100644 lib/services/analytics/flows/error_tracking.dart create mode 100644 lib/services/analytics/flows/hamburger_menu.dart create mode 100644 lib/services/analytics/flows/hmg_services.dart create mode 100644 lib/services/analytics/flows/live_care.dart create mode 100644 lib/services/analytics/flows/login_registration.dart create mode 100644 lib/services/analytics/flows/offers_promotions.dart create mode 100644 lib/services/analytics/flows/todo_list.dart diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 432ad2a2..226d4dd6 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,45 +1,188 @@ +import java.util.Properties +import java.io.FileInputStream + plugins { id("com.android.application") id("kotlin-android") + id("com.google.gms.google-services") version "4.4.1" // Add the version here id("dev.flutter.flutter-gradle-plugin") - id("com.google.gms.google-services") -// id("com.huawei.agconnect") + id("com.huawei.agconnect") + // id("com.mapbox.gradle.application") + // id("com.mapbox.gradle.plugins.ndk") +} + +val keystoreProperties = Properties() +val keystorePropertiesFile = rootProject.file("key.properties") +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) } android { - namespace = "com.ejada.hmg.hmg_patient_app_new" - compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion + namespace = "com.ejada.hmg" + compileSdk = 36 + ndkVersion = "28.2.13676358" - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + defaultConfig { + applicationId = "com.ejada.hmg" + // minSdk = 24 + minSdk = 26 + targetSdk = 35 + compileSdk = 35 + // targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + multiDexEnabled = true } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() + buildFeatures { + viewBinding = true + dataBinding = true } - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.ejada.hmg.hmg_patient_app_new" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion - targetSdk = flutter.targetSdkVersion - versionCode = flutter.versionCode - versionName = flutter.versionName + sourceSets { +// getByName("main") { +// java.srcDirs += "src/main/kotlin" +// jniLibs.srcDir("src/main/libs") +// jni.srcDirs = listOf() // disables automatic ndk-build +// } + getByName("main") { + java.srcDir("src/main/kotlin") + jniLibs.srcDir("src/main/libs") + // To disable automatic ndk-build, do not set jni.srcDirs + // If you need to clear jni.srcDirs: + // set("jni.srcDirs", emptyList()) + } + } + + signingConfigs { + create("release") { + keyAlias = keystoreProperties.getProperty("keyAlias") + keyPassword = keystoreProperties.getProperty("keyPassword") + storeFile = keystoreProperties.getProperty("storeFile")?.let { file(it) } + storePassword = keystoreProperties.getProperty("storePassword") + } } buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. + getByName("debug") { + isDebuggable = true signingConfig = signingConfigs.getByName("debug") + isMinifyEnabled = false + isShrinkResources = false + } + getByName("release") { + isDebuggable = false + signingConfig = signingConfigs.getByName("release") + isMinifyEnabled = true + isShrinkResources = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + create("staging") { + matchingFallbacks += listOf("debug", "qa", "release") } } + + packagingOptions { + jniLibs { + pickFirsts += listOf( + "lib/x86/libc++_shared.so", + "lib/x86_64/libc++_shared.so", + "lib/armeabi-v7a/libc++_shared.so", + "lib/arm64-v8a/libc++_shared.so", + "**/*.so" + ) + useLegacyPackaging = true + } + resources { + excludes += listOf("META-INF/proguard/androidx-annotations.pro") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + isCoreLibraryDesugaringEnabled = true + } + + // kotlinOptions { + // jvmTarget = "17" + // } + + lint { + disable += "MissingTranslation" + checkReleaseBuilds = false + } } flutter { source = "../.." } + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.20") + implementation("com.google.firebase:firebase-messaging:24.1.2") + implementation("pub.devrel:easypermissions:3.0.0") + implementation("com.google.guava:guava:33.4.0-android") + implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"), "exclude" to listOf("bcprov-jdk16-1.46.jar")))) + implementation("com.google.code.gson:gson:2.12.0") + + // Zoom SDKs + implementation("us.zoom.videosdk:zoomvideosdk-core:1.12.10") + implementation("us.zoom.videosdk:zoomvideosdk-annotation:1.12.10") + implementation("us.zoom.videosdk:zoomvideosdk-videoeffects:1.12.10") + + // Networking + implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11") + implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.11") + implementation("com.squareup.retrofit2:retrofit:2.11.0") + implementation("com.squareup.retrofit2:converter-gson:2.11.0") + implementation("com.squareup.retrofit2:adapter-java8:2.11.0") + + // Google Services + implementation("com.google.android.gms:play-services-location:21.3.0") + implementation("com.google.android.gms:play-services-basement:18.7.0") + + implementation("com.android.volley:volley:1.2.1") + implementation("androidx.constraintlayout:constraintlayout:2.2.1") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.0") + implementation("androidx.navigation:navigation-ui-ktx:2.9.0") + implementation("androidx.activity:activity-ktx:1.10.1") + +// val room_version = "2.6.1" +// implementation("androidx.room:room-runtime:$room_version") +// annotationProcessor("androidx.room:room-compiler:$room_version") + + implementation("net.zetetic:android-database-sqlcipher:4.5.4") + implementation("com.intuit.ssp:ssp-android:1.1.0") + implementation("com.intuit.sdp:sdp-android:1.1.0") + +// implementation("com.github.bumptech.glide:glide:4.16.0") +// annotationProcessor("com.github.bumptech.glide:compiler:4.16.0") + + implementation("com.mapbox.maps:android:11.5.0") + // implementation("com.mapbox.maps:android:11.4.0") + + // AARs +// implementation(files("libs/PenNavUI.aar")) +// implementation(files("libs/Penguin.aar")) +// implementation(files("libs/PenguinRenderer.aar")) + + implementation("com.github.kittinunf.fuel:fuel:2.3.1") + implementation("com.github.kittinunf.fuel:fuel-android:2.3.1") + +// implementation("com.opentok.android:opentok-android-sdk:2.25.2") + +// implementation("com.facebook.stetho:stetho:1.6.0") +// implementation("com.facebook.stetho:stetho-urlconnection:1.6.0") + + implementation("androidx.core:core-ktx:1.16.0") + implementation("androidx.appcompat:appcompat:1.7.1") + implementation("com.google.android.material:material:1.12.0") + + implementation("pl.droidsonroids.gif:android-gif-drawable:1.2.25") + + androidTestImplementation("androidx.test:core:1.6.1") + implementation("com.whatsapp.otp:whatsapp-otp-android-sdk:0.1.0") + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") + // implementation(project(":vitalSignEngine")) +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9eef1012..e07754e5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -193,31 +193,31 @@ android:exported="true" android:permission="android.permission.BIND_JOB_SERVICE" /> - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/ejada/hmg/hmg_patient_app_new/MainActivity.kt b/android/app/src/main/kotlin/com/ejada/hmg/hmg_patient_app_new/MainActivity.kt deleted file mode 100644 index 1fd6200d..00000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/hmg_patient_app_new/MainActivity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.ejada.hmg.hmg_patient_app_new - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index cb1ef880..1f83a33f 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,18 +1,18 @@ - - - diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 71a2e607..4b24dd7f 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -1,13 +1,25 @@ buildscript { repositories { - google() mavenCentral() -// maven { url = uri("https://developer.huawei.com/repo/") } + google() + maven { url = uri("https://developer.huawei.com/repo/") } + maven { + url = uri("https://api.mapbox.com/downloads/v2/releases/maven") + credentials { + username = "mapbox" + password = "sk.eyJ1IjoicndhaWQiLCJhIjoiY2x6NWo0bTMzMWZodzJrcGZpemYzc3Z4dSJ9.uSSZuwNSGCcCdPAiORECmg" + if (password.isNullOrEmpty()) { + throw GradleException("MAPBOX_DOWNLOADS_TOKEN isn't set. Set it to the project properties or to the environment variables.") + } + } + authentication { + create("basic") + } + } } dependencies { - classpath("com.android.tools.build:gradle:7.4.2") - classpath("com.google.gms:google-services:4.4.1") -// classpath("com.huawei.agconnect:agcp:1.9.3.302") + classpath("com.android.tools.build:gradle:8.1.0") + classpath("com.huawei.agconnect:agcp:1.9.1.304") } } @@ -15,16 +27,73 @@ allprojects { repositories { google() mavenCentral() -// maven { url = uri("https://developer.huawei.com/repo/") } + maven { url = uri("https://developer.huawei.com/repo/") } + maven { url = uri("https://artifactory.ess-dev.com/artifactory/gradle-dev-local") } + maven { + url = uri("https://api.mapbox.com/downloads/v2/releases/maven") + credentials { + username = "mapbox" + password = "sk.eyJ1IjoicndhaWQiLCJhIjoiY2x6NWo0bTMzMWZodzJrcGZpemYzc3Z4dSJ9.uSSZuwNSGCcCdPAiORECmg" + if (password.isNullOrEmpty()) { + throw GradleException("MAPBOX_DOWNLOADS_TOKEN isn't set. Set it to the project properties or to the environment variables.") + } + } + authentication { + create("basic") + } + } + } + + configurations.all { + exclude(group = "org.bouncycastle", module = "bcprov-jdk16") + } + + tasks.withType().configureEach { + sourceCompatibility = JavaVersion.VERSION_21.toString() + targetCompatibility = JavaVersion.VERSION_21.toString() + } + + tasks.withType().configureEach { + kotlinOptions { + jvmTarget = "21" + } } -} -val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() -rootProject.layout.buildDirectory.value(newBuildDir) + subprojects { + afterEvaluate { + if (project.hasProperty("android")) { + project.extensions.findByName("android")?.let { androidExt -> + val android = androidExt as com.android.build.gradle.BaseExtension + if (android.namespace == null) { + android.namespace = project.group.toString() + } + android.buildFeatures.apply { + if (buildConfig == null) { + buildConfig = true + } + } + android.compileOptions.apply { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + } + } + project.tasks.withType().configureEach { + sourceCompatibility = JavaVersion.VERSION_21.toString() + targetCompatibility = JavaVersion.VERSION_21.toString() + } + project.tasks.withType().configureEach { + kotlinOptions { + jvmTarget = "21" + } + } + } + } +} +rootProject.buildDir = File("../build") subprojects { - val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) - project.layout.buildDirectory.value(newSubprojectBuildDir) + project.buildDir = File("${rootProject.buildDir}/${project.name}") } subprojects { project.evaluationDependsOn(":app") @@ -32,4 +101,4 @@ subprojects { tasks.register("clean") { delete(rootProject.layout.buildDirectory) -} +} \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 57280646..db4395f7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -336,40 +336,40 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0 - connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd + audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe + connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90 - device_calendar: b55b2c5406cfba45c95a59f9059156daee1f74ed - device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + device_calendar: 9cb33f88a02e19652ec7b8b122ca778f751b1f7b + device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be + file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e - firebase_core: 995454a784ff288be5689b796deb9e9fa3601818 - firebase_messaging: f4a41dd102ac18b840eba3f39d67e77922d3f707 + firebase_core: 99a37263b3c27536063a7b601d9e2a49400a433c + firebase_messaging: bf6697c61f31c7cc0f654131212ff04c0115c2c7 FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843 FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09 FLAnimatedImage: bbf914596368867157cc71b38a8ec834b3eeb32b Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_callkit_incoming: cb8138af67cda6dd981f7101a5d709003af21502 - flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 - flutter_ios_voip_kit_karmm: 371663476722afb631d5a13a39dee74c56c1abd0 - flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb - flutter_zoom_videosdk: df7353598a3f0ad081055166e12c992ac9218a8c - fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 - geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e + flutter_callkit_incoming: 417dd1b46541cdd5d855ad795ccbe97d1c18155e + flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 + flutter_ios_voip_kit_karmm: 7ea37381a8841c92d186edf1f4604df5cc437579 + flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f + flutter_zoom_videosdk: 7ddc97d617b85aeace252165d2b5fc5638678c38 + fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f + geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321 - google_maps_flutter_ios: 0291eb2aa252298a769b04d075e4a9d747ff7264 + google_maps_flutter_ios: e31555a04d1986ab130f2b9f24b6cdc861acc6d3 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 - health: f496ad838f4bd227191f5bc05060b6f6138b44fd - image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a - just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed - local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19 - manage_calendar_events: fe1541069431af035ced925ebd9def8b4b271254 + health: f1207cccb625fc2e8af263b09c29661bde7b0b7e + image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 + just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79 + local_auth_darwin: fa4b06454df7df8e97c18d7ee55151c57e7af0de + manage_calendar_events: 9b2889799340398027b3e3f5c4891d41599ec257 MapboxCommon: 119f3759f7dc9457f0695848108ab323eb643cb4 MapboxCoreMaps: ca17f67baced23f8c952166ac6314c35bad3f66c MapboxCoreNavigation: 3be9990fae3ed732a101001746d0e3b4234ec023 @@ -381,21 +381,21 @@ SPEC CHECKSUMS: MapboxSpeech: cd25ef99c3a3d2e0da72620ff558276ea5991a77 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 Solar-dev: 4612dc9878b9fed2667d23b327f1d4e54e16e8d0 - sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe ZoomVideoSDK: 94e939820e57a075c5e712559f927017da0de06a -PODFILE CHECKSUM: ac23c99fc61c3e92ed1c20a19299a1205568c016 +PODFILE CHECKSUM: 5df9d8aa8f2c105eacd5ad7a310503d93c68c86b -COCOAPODS: 1.16.2 +COCOAPODS: 1.15.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index dae81f24..a0241a14 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -279,14 +279,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; @@ -300,14 +296,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index ef15c956..d8b379de 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -1,293 +1,600 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; +import 'dart:io' show Platform; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; -import 'package:hmg_patient_app_new/core/consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; -import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; -import 'package:http/http.dart'; -import 'package:http/io_client.dart'; - - -// ignore_for_file: avoid_annotating_with_dynamic - -typedef FactoryConstructor = U Function(dynamic); - -class APIError { - int? errorCode; - String? errorMessage; - - APIError(this.errorCode, this.errorMessage); - - Map toJson() => {'errorCode': errorCode, 'errorMessage': errorMessage}; - - @override - String toString() { - return jsonEncode(this); - } -} - -APIException _throwAPIException(Response response, Function retryCallBack) { - - -LoggerService loggerService = getIt.get(); - - switch (response.statusCode) { - case 200: - APIError? apiError; - if (response.body != null && response.body.isNotEmpty) { - var jsonError = jsonDecode(response.body); - loggerService.logInfo(jsonError.toString()); - apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage']); - } - return APIException(APIException.BAD_REQUEST, error: apiError); - case 400: - APIError? apiError; - if (response.body != null && response.body.isNotEmpty) { - var jsonError = jsonDecode(response.body); - apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage']); - } - return APIException(APIException.BAD_REQUEST, error: apiError); - case 401: - return APIException(APIException.UNAUTHORIZED); - case 403: - return APIException(APIException.FORBIDDEN); - case 404: - return APIException(APIException.NOT_FOUND); - case 500: - return APIException(APIException.INTERNAL_SERVER_ERROR); - case 444: - var downloadUrl = response.headers["location"]; - return APIException(APIException.UPGRADE_REQUIRED, arguments: downloadUrl); - default: - return APIException(APIException.OTHER); - } -} - -abstract class IApiClient { - Future postJsonForObject(FactoryConstructor factoryConstructor, String url, T jsonObject, - {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); - - Future postJsonForResponse(String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); - - Future getJsonForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); - - void setHomeUrl(String url); +import 'package:http/http.dart' as http; + + +abstract class ApiClient { + Future post( + String endPoint, { + required Map body, + required Function(dynamic response, int statusCode) onSuccess, + required Function(String error, int statusCode) onFailure, + bool isAllowAny, + bool isExternal, + bool isRCService, + bool bypassConnectionCheck, + }); + + Future get( + String endPoint, { + required Function(dynamic response, int statusCode) onSuccess, + required Function(String error, int statusCode) onFailure, + Map? queryParams, + bool isExternal, + bool isRCService, + }); + + Future simplePost( + String fullUrl, { + required Map body, + required Map headers, + required Function(dynamic response, int statusCode) onSuccess, + required Function(String error, int statusCode) onFailure, + }); + + Future simpleGet( + String fullUrl, { + Function(dynamic response, int statusCode)? onSuccess, + Function(String error, int statusCode)? onFailure, + Map? queryParams, + Map? headers, + }); + + Future simplePut( + String fullUrl, { + Map? body, + Map? headers, + Function(dynamic response, int statusCode)? onSuccess, + Function(String error, int statusCode)? onFailure, + }); + + Future simpleDelete( + String fullUrl, { + Function(dynamic response, int statusCode)? onSuccess, + Function(String error, int statusCode)? onFailure, + Map? queryParams, + Map? headers, + }); + + Future handleUnauthorized(int statusCode, {required String forUrl}); + + String getSessionId(String id); + + Future generatePackagesToken(); } -class ApiClient implements IApiClient { - LoggerService loggerService; - - ApiClient({required this.loggerService}); - - @override - Future postJsonForObject(FactoryConstructor factoryConstructor, String url, T jsonObject, - {String? token, Map? queryParameters, Map? headers, int retryTimes = 0, bool isFormData = false}) async { - var _headers = {'Accept': 'application/json'}; - if (headers != null && headers.isNotEmpty) { - _headers.addAll(headers); - } - if (!kReleaseMode) { - loggerService.logInfo("Url:$url"); - var bodyJson = json.encode(jsonObject); - loggerService.logInfo("body:$bodyJson"); - } - var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: _headers, retryTimes: retryTimes); - // try { - if (!kReleaseMode) { - loggerService.logInfo("res: ${response.body}"); - } - var jsonData = jsonDecode(response.body); - if (jsonData["IsAuthenticated"] != null) { - getIt.get().setIsAuthenticated = jsonData["IsAuthenticated"]; - } - if (jsonData["ErrorMessage"] == null) { - return factoryConstructor(jsonData); +class ApiClientImp implements ApiClient { + final _analytics = getIt(); + + final LoggerService loggerService; + ApiClientImp({required this.loggerService}); + + post(String endPoint, + {required Map body, + required Function(dynamic response, int statusCode) onSuccess, + required Function(String error, int statusCode) onFailure, + bool isAllowAny = false, + bool isExternal = false, + bool isRCService = false, + bool bypassConnectionCheck = false}) async { + AppState appState = getIt.get(); + String url; + if (isExternal) { + url = endPoint; } else { - APIError? apiError; - apiError = APIError(jsonData['ErrorCode'], jsonData['ErrorEndUserMessage']); - throw APIException(APIException.BAD_REQUEST, error: apiError); - } - // } catch (ex) { - // if (ex is APIException) { - // rethrow; - // } else { - // throw APIException(APIException.BAD_RESPONSE_FORMAT, arguments: ex); - // } - // } - } - - @override - Future postJsonForResponse(String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { - String? requestBody; - late Map stringObj; - if (jsonObject != null) { - requestBody = jsonEncode(jsonObject); - if (headers == null) { - headers = {'Content-Type': 'application/json'}; + if (isRCService) { + url = RC_BASE_URL + endPoint; } else { - headers['Content-Type'] = 'application/json'; + url = BASE_URL + endPoint; } } - - return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes); - } - - Future _postForResponse(String url, requestBody, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { try { - var _headers = {}; - // if (token != null) { - // _headers['Authorization'] = 'Bearer $token'; - // } - - if (headers != null && headers.isNotEmpty) { - _headers.addAll(headers); - } - if (queryParameters != null) { - // var queryString = new Uri(queryParameters: queryParameters).query; - var queryString = Uri(queryParameters: queryParameters.map((key, value) => MapEntry(key, value?.toString()))).query; - url = '$url?$queryString'; + var user = appState.getAuthenticatedUser; + Map headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }; + if (!isExternal) { + String? token = appState.appAuthToken; + String? languageID = + (appState.postParamsObject?.languageID == 1 ? 'ar' : 'en') ?? 'ar'; + if (endPoint == ApiConsts.sendActivationCode) { + languageID = 'en'; + } + if (body.containsKey('SetupID')) { + body['SetupID'] = body.containsKey('SetupID') + ? body['SetupID'] ?? body[''] + : SETUP_ID; + } else {} + + if (body.containsKey('LanguageID')) { + if (body['LanguageID'] != null) { + //change this line because language issue happened on dental + body['LanguageID'] = body['LanguageID'] == 'ar' + ? 1 + : body['LanguageID'] == 'en' + ? 2 + : body['LanguageID']; + } + } + + if (body.containsKey('isDentalAllowedBackend')) { + body['isDentalAllowedBackend'] = + body.containsKey('isDentalAllowedBackend') + ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND + : IS_DENTAL_ALLOWED_BACKEND; + } + + body['DeviceTypeID'] = Platform.isIOS + ? 1 + : await Utils.isGoogleServicesAvailable() + ? 2 + : 3; + + if (!body.containsKey('IsPublicRequest')) { + // if (!body.containsKey('PatientType')) { + if (user != null && user.patientType != null) { + body['PatientType'] = user.patientType; + } else { + body['PatientType'] = PATIENT_TYPE; + } + // } + + // body['PatientType'] = body.containsKey('PatientType') + // ? body['PatientType'] != null + // ? body['PatientType'] + // : user['PatientType'] != null + // ? user['PatientType'] + // : PATIENT_TYPE + // : PATIENT_TYPE; + + // if (!body.containsKey('PatientTypeID')) { + if (user != null && user.patientType != null) { + body['PatientTypeID'] = user.patientType; + } else { + body['PatientType'] = PATIENT_TYPE_ID; + } + // } + + // body['PatientTypeID'] = body.containsKey('PatientTypeID') + // ? body['PatientTypeID'] != null + // ? body['PatientTypeID'] + // : user['PatientType'] != null + // ? user['PatientType'] + // : PATIENT_TYPE_ID + // : PATIENT_TYPE_ID; + + if (user != null) { + body['TokenID'] = body['TokenID'] ?? token; + body['PatientID'] = body['PatientID'] ?? user.patientID; + + body['PatientOutSA'] = body.containsKey('PatientOutSA') + ? body['PatientOutSA'] ?? user.outSA + : user.outSA; + body['SessionID'] = getSessionId(body['TokenID'] ?? ""); //getSe + } + } } - // if (!kReleaseMode && url.contains("saned")) { - if (!kReleaseMode) { - loggerService.logInfo("Url: $url"); - loggerService.logInfo("Headers: $_headers"); - // var bodyJson = json.encode(requestBody); - loggerService.logInfo("body: $requestBody"); - } + body.removeWhere((key, value) => value == null); - var response = await _post(Uri.parse(url), body: requestBody, headers: _headers).timeout(Duration(seconds: 120)); - - if (response.statusCode >= 200 && response.statusCode < 300) { - return response; + debugPrint("URL : $url"); + final jsonBody = json.encode(body); + debugPrint(jsonBody); + // } + // return; + if (await Utils.checkConnection( + bypassConnectionCheck: bypassConnectionCheck)) { + final response = await http.post(Uri.parse(url.trim()), + body: json.encode(body), headers: headers); + final int statusCode = response.statusCode; + if (statusCode < 200 || statusCode >= 400) { + onFailure('Error While Fetching data', statusCode); + logApiEndpointError( + endPoint, 'Error While Fetching data', statusCode); + } else { + // var decoded = utf8.decode(response.bodyBytes); + var parsed; + // if (url.contains('Services/NHIC.svc/REST/GetPatientInfo')) { + // parsed = json.decode(sampleNHICResponse); + // } else { + parsed = json.decode(utf8.decode(response.bodyBytes)); + // } + + // print("Response: $parsed"); + + if (isAllowAny) { + onSuccess(parsed, statusCode); + } else { + if (parsed['Response_Message'] != null) { + onSuccess(parsed, statusCode); + } else { + if (parsed['ErrorType'] == 4) { + //TODO : handle app update + logApiEndpointError( + endPoint, + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode); + } + if (parsed['ErrorType'] == 2) { + // todo: handle Logout + logApiEndpointError(endPoint, "session logged out", statusCode); + } + if (isAllowAny) { + onSuccess(parsed, statusCode); + } else if (parsed['IsAuthenticated'] == null) { + if (parsed['isSMSSent'] == true) { + onSuccess(parsed, statusCode); + } else if (parsed['MessageStatus'] == 1) { + onSuccess(parsed, statusCode); + } else if (parsed['Result'] == 'OK') { + onSuccess(parsed, statusCode); + } else { + // if (parsed != null) { + // onSuccess(parsed, statusCode); + // } else { + onFailure( + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode); + logApiEndpointError( + endPoint, + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode); + // logout(); + // } + } + } else if (parsed['MessageStatus'] == 1 || + parsed['SMSLoginRequired'] == true) { + onSuccess(parsed, statusCode); + } else if (parsed['MessageStatus'] == 2 && + parsed['IsAuthenticated']) { + if (parsed['SameClinicApptList'] != null) { + onSuccess(parsed, statusCode); + } else { + if (parsed['message'] == null && + parsed['ErrorEndUserMessage'] == null) { + if (parsed['ErrorSearchMsg'] == null) { + onFailure("Server Error found with no available message", + statusCode); + logApiEndpointError( + endPoint, + "Server Error found with no available message", + statusCode); + } else { + onFailure(parsed['ErrorSearchMsg'], statusCode); + logApiEndpointError( + endPoint, parsed['ErrorSearchMsg'], statusCode); + } + } else { + onFailure( + parsed['message'] ?? + parsed['ErrorEndUserMessage'] ?? + parsed['ErrorMessage'], + statusCode); + logApiEndpointError(endPoint, + parsed['message'] ?? parsed['message'], statusCode); + } + } + } + // else if (!parsed['IsAuthenticated']) { + // await logout(); + // } + else { + if (parsed['SameClinicApptList'] != null) { + onSuccess(parsed, statusCode); + } else { + if (parsed['message'] != null) { + onFailure( + parsed['message'] ?? parsed['message'], statusCode); + logApiEndpointError(endPoint, + parsed['message'] ?? parsed['message'], statusCode); + } else { + onFailure( + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode); + logApiEndpointError( + endPoint, + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode); + } + } + } + } + } + } } else { - throw _throwAPIException(response, () { - // _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes, isAuthAPI: isAuthAPI); - postJsonForResponse(url, requestBody); - }); + onFailure('Please Check The Internet Connection 1', -1); + _analytics.errorTracking + .log("internet_connectivity", error: "no internet available"); } - } on SocketException catch (e) { - if (retryTimes > 0) { - loggerService.logInfo('will retry after 3 seconds...'); - await Future.delayed(Duration(seconds: 3)); - return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); + } catch (e) { + print(e); + if (e.toString().contains("ClientException")) { + onFailure('Something went wrong, plase try again', -1); + _analytics.errorTracking + .log("internet_connectivity", error: "no internet available"); } else { - throw APIException(APIException.OTHER, arguments: e); + onFailure(e.toString(), -1); } - } on HttpException catch (e) { - if (retryTimes > 0) { - loggerService.logInfo('will retry after 3 seconds...'); - await Future.delayed(Duration(seconds: 3)); - return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); + _analytics.errorTracking + .log(endPoint, error: "api exception: $e - API Path: $url"); + } + } + + get(String endPoint, + {required Function(dynamic response, int statusCode) onSuccess, + required Function(String error, int statusCode) onFailure, + Map? queryParams, + bool isExternal = false, + bool isRCService = false}) async { + String url; + if (isExternal) { + url = endPoint; + } else { + if (isRCService) { + url = RC_BASE_URL + endPoint; } else { - throw APIException(APIException.OTHER, arguments: e); + url = BASE_URL + endPoint; } - } on TimeoutException catch (e) { - throw APIException(APIException.TIMEOUT, arguments: e); - } on ClientException catch (e) { - if (retryTimes > 0) { - loggerService.logInfo('will retry after 3 seconds...'); - await Future.delayed(Duration(seconds: 3)); - return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); + } + if (queryParams != null) { + String queryString = Uri(queryParameters: queryParams).query; + url += '?$queryString'; + } + + debugPrint("URL : $url"); + // print("Body : ${json.encode(body)}"); + + if (await Utils.checkConnection()) { + final response = await http.get( + Uri.parse(url.trim()), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + ); + final int statusCode = response.statusCode; + // print("statusCode :$statusCode"); + + if (statusCode < 200 || statusCode >= 400) { + onFailure!('Error While Fetching data', statusCode); + logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); } else { - throw APIException(APIException.OTHER, arguments: e); + var parsed = json.decode(utf8.decode(response.bodyBytes)); + onSuccess!(parsed, statusCode); } + } else { + onFailure!('Please Check The Internet Connection', -1); + _analytics.errorTracking + .log("internet_connectivity", error: "no internet available"); } } - @override - Future getJsonForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0, bool isAuthAPI = false}) async { - loggerService.logInfo("Url:$url"); - if (headers == null) { - headers = {'Content-Type': 'application/json'}; + simplePost( + String fullUrl, { + required Map body, + required Map headers, + required Function(dynamic response, int statusCode) onSuccess, + required Function(String error, int statusCode) onFailure, + }) async { + String url = fullUrl; + // print("URL Query String: $url"); + // print("body: $body"); + + if (await Utils.checkConnection()) { + headers!.addAll( + {'Content-Type': 'application/json', 'Accept': 'application/json'}); + final response = await http.post( + Uri.parse(url.trim()), + body: json.encode(body), + headers: headers, + ); + final int statusCode = response.statusCode; + // print("statusCode :$statusCode"); + if (await handleUnauthorized(statusCode, forUrl: fullUrl)) + simplePost(fullUrl, + onFailure: onFailure, + onSuccess: onSuccess, + body: body, + headers: headers); + + // print(response.body.toString()); + + if (statusCode < 200 || statusCode >= 400) { + onFailure!('Error While Fetching data', statusCode); + logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); + } else { + onSuccess!(response.body.toString(), statusCode); + } } else { - headers['Content-Type'] = 'application/json'; + onFailure!('Please Check The Internet Connection', -1); + _analytics.errorTracking + .log("internet_connectivity", error: "no internet available"); } - return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes, isAuthAPI: isAuthAPI); } - Future _getForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0, bool isAuthAPI = false}) async { - try { - var _headers = {}; - if (token != null) { - _headers['Authorization'] = 'Bearer $token'; - } + simpleGet(String fullUrl, + {Function(dynamic response, int statusCode)? onSuccess, + Function(String error, int statusCode)? onFailure, + Map? queryParams, + Map? headers}) async { + headers = headers ?? {}; + String url = fullUrl; + + var haveParams = (queryParams != null); + if (haveParams) { + String queryString = Uri(queryParameters: queryParams).query; + url += '?$queryString'; + // print("URL Query String: $url"); + } - if (headers != null && headers.isNotEmpty) { - _headers.addAll(headers); + if (await Utils.checkConnection()) { + headers.addAll( + {'Content-Type': 'application/json', 'Accept': 'application/json'}); + final response = await http.get( + Uri.parse(url.trim()), + headers: headers, + ); + + final int statusCode = response.statusCode; + // print("statusCode :$statusCode"); + if (await handleUnauthorized(statusCode, forUrl: fullUrl)) + simpleGet(fullUrl, + onFailure: onFailure, + onSuccess: onSuccess, + headers: headers, + queryParams: queryParams); + + if (statusCode < 200 || statusCode >= 400) { + onFailure!('Error While Fetching data', statusCode); + logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); + } else { + onSuccess!(response.body.toString(), statusCode); } + } else { + onFailure!('Please Check The Internet Connection', -1); + _analytics.errorTracking + .log("internet_connectivity", error: "no internet available"); + } + } - if (isAuthAPI) { - String token = await Utils.getStringFromPrefs(SharedPrefsConsts.appAuthToken); - _headers['Authorization'] = "Bearer $token"; + simplePut(String fullUrl, + {Map? body, + Map? headers, + Function(dynamic response, int statusCode)? onSuccess, + Function(String error, int statusCode)? onFailure}) async { + String url = fullUrl; + // print("URL Query String: $url"); + + if (await Utils.checkConnection()) { + headers!.addAll( + {'Content-Type': 'application/json', 'Accept': 'application/json'}); + final response = await http.put( + Uri.parse(url.trim()), + body: json.encode(body), + headers: headers, + ); + + final int statusCode = response.statusCode; + // print("statusCode :$statusCode"); + if (await handleUnauthorized(statusCode, forUrl: fullUrl)) + simplePut(fullUrl, + onFailure: onFailure, + onSuccess: onSuccess, + headers: headers, + body: body); + + if (statusCode < 200 || statusCode >= 400) { + onFailure!('Error While Fetching data', statusCode); + logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); + } else { + onSuccess!(response.body.toString(), statusCode); } + } else { + onFailure!('Please Check The Internet Connection', -1); + _analytics.errorTracking + .log("internet_connectivity", error: "no internet available"); + } + } - if (queryParameters != null) { - var queryString = Uri(queryParameters: queryParameters).query; - url = '$url?$queryString'; - } - var response = await _get(Uri.parse(url), headers: _headers).timeout(Duration(seconds: 60)); + simpleDelete(String fullUrl, + {Function(dynamic response, int statusCode)? onSuccess, + Function(String error, int statusCode)? onFailure, + Map? queryParams, + Map? headers}) async { + String url = fullUrl; + // print("URL Query String: $url"); + + var haveParams = (queryParams != null); + if (haveParams) { + String queryString = Uri(queryParameters: queryParams).query; + url += '?$queryString'; + // print("URL Query String: $url"); + } - if (response.statusCode >= 200 && response.statusCode < 300) { - return response; - } else { - throw _throwAPIException(response, () { - _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes); - }); - } - } on SocketException catch (e) { - if (retryTimes > 0) { - loggerService.logInfo('will retry after 3 seconds...'); - await Future.delayed(Duration(seconds: 3)); - return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); + if (await Utils.checkConnection()) { + headers!.addAll( + {'Content-Type': 'application/json', 'Accept': 'application/json'}); + final response = await http.delete( + Uri.parse(url.trim()), + headers: headers, + ); + + final int statusCode = response.statusCode; + // print("statusCode :$statusCode"); + if (await handleUnauthorized(statusCode, forUrl: fullUrl)) + simpleDelete(fullUrl, + onFailure: onFailure, + onSuccess: onSuccess, + queryParams: queryParams, + headers: headers); + + if (statusCode < 200 || statusCode >= 400) { + onFailure!('Error While Fetching data', statusCode); + logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); } else { - throw APIException(APIException.OTHER, arguments: e); - } - } on HttpException catch (e) { - if (retryTimes > 0) { - loggerService.logInfo('will retry after 3 seconds...'); - await Future.delayed(Duration(seconds: 3)); - return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); - } else { - throw APIException(APIException.OTHER, arguments: e); - } - } on TimeoutException catch (e) { - throw APIException(APIException.TIMEOUT, arguments: e); - } on ClientException catch (e) { - if (retryTimes > 0) { - loggerService.logInfo('will retry after 3 seconds...'); - await Future.delayed(Duration(seconds: 3)); - return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); - } else { - throw APIException(APIException.OTHER, arguments: e); + onSuccess!(response.body.toString(), statusCode); } + } else { + onFailure!('Please Check The Internet Connection', -1); + _analytics.errorTracking + .log("internet_connectivity", error: "no internet available"); } } - Future _get(url, {Map? headers}) => _withClient((client) => client.get(url, headers: headers)); - - bool _certificateCheck(X509Certificate cert, String host, int port) => true; - - Future _withClient(Future Function(Client) fn) async { - var httpClient = HttpClient()..badCertificateCallback = _certificateCheck; - var client = IOClient(httpClient); - try { - return await fn(client); - } finally { - client.close(); + Future handleUnauthorized(int statusCode, + {required String forUrl}) async { + if (forUrl.startsWith(EXA_CART_API_BASE_URL) && statusCode == 401) { + final token = await generatePackagesToken(); + ApiConsts.packagesAuthHeader['Authorization'] = 'Bearer $token'; + return (token is String); } + return false; } - Future _post(url, {Map? headers, body, Encoding? encoding}) => _withClient((client) => client.post(url, headers: headers, body: body, encoding: encoding)); + String getSessionId(String id) { + return id.replaceAll(RegExp('/[^a-zA-Z]'), ''); + } + Future generatePackagesToken() async { + var url = EXA_CART_API_BASE_URL + PACKAGES_TOKEN; + var body = { + "api_client": { + "client_id": "a4ab6be4-424f-4836-b032-46caed88e184", + "client_secret": "3c1a3e07-4a40-4510-9fb0-ee5f0a72752c" + } + }; + String? token; + final completer = Completer(); + simplePost(url, body: body, headers: {}, + onSuccess: (dynamic stringResponse, int statusCode) { + if (statusCode == 200) { + var jsonResponse = json.decode(stringResponse); + token = jsonResponse['auth_token']; + completer.complete(); + } + }, onFailure: (String error, int statusCode) { + completer.complete(); + logApiFullUrlError(url, error, statusCode); + }); + await completer.future; + return token!; + } + + logApiFullUrlError(String fullUrl, error, code) { + final endpoint = Uri.parse(fullUrl).pathSegments.last; + logApiEndpointError(endpoint, error, code); + } - @override - void setHomeUrl(String url) { - // TODO: implement setHomeUrl + logApiEndpointError(String endPoint, error, code) { + _analytics.errorTracking.log(endPoint, error: error); } } diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart new file mode 100644 index 00000000..dc952bf5 --- /dev/null +++ b/lib/core/api_consts.dart @@ -0,0 +1,765 @@ +import 'dart:io'; + +var MAX_SMALL_SCREEN = 660; +final OPENTOK_API_KEY = '46209962'; +// final OPENTOK_API_KEY = '47464241'; + +// PACKAGES and OFFERS +var EXA_CART_API_BASE_URL = 'https://mdlaboratories.com/offersdiscounts'; +// var EXA_CART_API_BASE_URL = 'http://10.200.101.75:9000'; +var PACKAGES_CATEGORIES = '/api/categories'; +var PACKAGES_STORES = '/api/stores'; +var PACKAGES_TOKEN = '/api/token'; +var PACKAGES_PRODUCTS = '/api/products'; +var PACKAGES_CUSTOMER = '/api/customers'; +var PACKAGES_SHOPPING_CART = '/api/shopping_cart_items'; +var PACKAGES_ORDERS = '/api/orders'; +var PACKAGES_ORDER_HISTORY = '/api/orders/items'; +var PACKAGES_TAMARA_OPT = '/api/orders/paymentoptions/tamara'; +// var BASE_URL = 'http://10.50.100.198:2018/'; +var BASE_URL = 'https://uat.hmgwebservices.com/'; +// var BASE_URL = 'https://hmgwebservices.com/'; +// var BASE_URL = 'http://10.201.204.103/'; +// var BASE_URL = 'https://orash.cloudsolutions.com.sa/'; +// var BASE_URL = 'https://vidauat.cloudsolutions.com.sa/'; +// var BASE_URL = 'https://vidamergeuat.cloudsolutions.com.sa/'; + +// var BASE_URL = 'https://webservices.hmg.com/'; + +// var BASE_URL = 'http://10.50.100.198:4422/'; + +// Pharmacy UAT URLs +// var BASE_PHARMACY_URL = 'https://uat.hmgwebservices.com/epharmacy/api/'; +// var PHARMACY_BASE_URL = 'https://uat.hmgwebservices.com/epharmacy/api/'; + +// // Pharmacy Production URLs +var BASE_PHARMACY_URL = 'https://mdlaboratories.com/exacartapi/api/'; +var PHARMACY_BASE_URL = 'https://mdlaboratories.com/exacartapi/api/'; + +var PHARMACY_REDIRECT_URL = 'https://bit.ly/AlhabibPharmacy'; + +// Pharmacy VidaPlus URLs +// var BASE_PHARMACY_URL = 'https://mdlaboratories.com/exacartapitest/api/'; +// var PHARMACY_BASE_URL = 'https://mdlaboratories.com/exacartapitest/api/'; + +// // Pharmacy Pre-Production URLs +// var BASE_PHARMACY_URL = 'https://mdlaboratories.com/exacartapitest/api/'; +// var PHARMACY_BASE_URL = 'https://mdlaboratories.com/exacartapitest/api/'; + +// RC API URL +var RC_BASE_URL = 'https://rc.hmg.com/'; + +// var RC_BASE_URL = 'https://rc.hmg.com/test/'; + +// var RC_BASE_URL = 'https://ms.hmg.com/rc/'; + +var PING_SERVICE = 'Services/Weather.svc/REST/CheckConnectivity'; + +var GET_PROJECT = 'Services/Lists.svc/REST/GetProject'; + +///Geofencing +var GET_GEO_ZONES = 'Services/Patients.svc/REST/GeoF_GetAllPoints'; +var LOG_GEO_ZONES = 'Services/Patients.svc/REST/GeoF_InsertPatientFileInfo'; + +// Delivery Driver +var DRIVER_LOCATION = 'Services/Patients.svc/REST/PatientER_GetDriverLocation'; + +//weather +var WEATHER_INDICATOR = 'Services/Weather.svc/REST/GetCityInfo'; + +var GET_PRIVILEGE = 'Services/Patients.svc/REST/Service_Privilege'; + +// Wifi Credentials +var WIFI_CREDENTIALS = "Services/Patients.svc/Hmg_SMS_Get_By_ProjectID_And_PatientID"; + +///Doctor +var GET_MY_DOCTOR = 'Services/Doctors.svc/REST/GetPatientDoctorAppointmentResult'; +var GET_DOCTOR_PROFILE = 'Services/Doctors.svc/REST/GetDocProfiles'; +var GET_DOCTOR_PRE_POST_IMAGES = 'Services/Doctors.svc/REST/GetDoctorPrePostImages'; +var GET_DOCTOR_RATING_NOTES = 'Services/Doctors.svc/REST/dr_GetNotesDoctorRating'; +var GET_DOCTOR_RATING_DETAILS = 'Services/Doctors.svc/REST/dr_GetDoctorRatingDetails'; + +var GET_DOCTOR_RATING = 'Services/Doctors.svc/REST/dr_GetAvgDoctorRating'; + +///Prescriptions +// var PRESCRIPTIONS = 'Services/Patients.svc/REST/GetPrescriptionApptList'; +var PRESCRIPTIONS = 'Services/Patients.svc/REST/GetPrescriptionApptList_Async'; + +var GET_PRESCRIPTIONS_ALL_ORDERS = 'Services/Patients.svc/REST/PatientER_GetPatientAllPresOrders'; +var GET_PRESCRIPTION_REPORT = 'Services/Patients.svc/REST/INP_GetPrescriptionReport'; +var SEND_PRESCRIPTION_EMAIL = 'Services/Notifications.svc/REST/SendPrescriptionEmail'; +var GET_PRESCRIPTION_REPORT_ENH = 'Services/Patients.svc/REST/GetPrescriptionReport_enh'; + +///Lab Order +var GET_Patient_LAB_ORDERS = 'Services/Patients.svc/REST/GetPatientLabOrders'; +var GET_Patient_LAB_SPECIAL_RESULT = 'Services/Patients.svc/REST/GetPatientLabSpecialResults'; +var SEND_LAB_RESULT_EMAIL = 'Services/Notifications.svc/REST/SendLabReportEmail'; +var GET_Patient_LAB_RESULT = 'Services/Patients.svc/REST/GetPatientLabResults'; +var GET_Patient_LAB_ORDERS_RESULT = 'Services/Patients.svc/REST/GetPatientLabOrdersResults'; +var SEND_COVID_LAB_RESULT_EMAIL = 'Services/Notifications.svc/REST/GenerateCOVIDReport'; +var COVID_PASSPORT_UPDATE = 'Services/Patients.svc/REST/Covid19_Certificate_PassportUpdate'; +var GET_PATIENT_PASSPORT_NUMBER = 'Services/Patients.svc/REST/Covid19_Certificate_GetPassport'; +var SEND_LAB_RESULT_EMAIL_NEW = 'ReportsAPI/api/reports/labReport'; + +var UPDATE_WORKPLACE_NAME = 'Services/Patients.svc/REST/ActivateSickLeave_FromVida'; +var GET_SICKLEAVE_STATUS_ADMISSION_NO = 'Services/ChatBot_Service.svc/REST/GetSickLeaveStatusByAdmissionNo'; + +/// +var GET_PATIENT_ORDERS = 'Services/Patients.svc/REST/GetPatientRadOrders'; +var GET_PATIENT_LAB_ORDERS_BY_APPOINTMENT = 'Services/Patients.svc/REST/GetPatientLabResultsByAppointmentNo'; + +var GET_PATIENT_ORDERS_DETAILS = 'Services/Patients.svc/REST/Rad_UpdatePatientRadOrdersToRead'; +var GET_RAD_IMAGE_URL = 'Services/Patients.svc/Rest/GetRadImageURL'; +var SEND_RAD_REPORT_EMAIL = 'Services/Notifications.svc/REST/SendRadReportEmail'; + +///Feedback +var SEND_FEEDBACK = 'Services/COCWS.svc/REST/InsertCOCItemInSPList'; +var GET_STATUS_FOR_COCO = 'Services/COCWS.svc/REST/GetStatusforCOC'; +// var GET_PATIENT_AppointmentHistory = 'Services' +// '/Doctors.svc/REST/PateintHasAppoimentHistory'; + +var GET_PATIENT_AppointmentHistory = 'Services' + '/Doctors.svc/REST/PateintHasAppoimentHistory_Async'; + +///VITAL SIGN +var GET_PATIENT_VITAL_SIGN = 'Services/Doctors.svc/REST/Doctor_GetPatientVitalSign'; + +///Er Nearest +var GET_NEAREST_HOSPITAL = 'Services/Patients.svc/REST/Patient_GetProjectAvgERWaitingTime'; + +///ED Online +var ER_GET_VISUAL_TRIAGE_QUESTIONS = "services/Doctors.svc/REST/ER_GetVisualTriageQuestions"; +var ER_SAVE_TRIAGE_INFORMATION = "services/Doctors.svc/REST/ER_SaveTriageInformation"; +var ER_GetPatientPaymentInformationForERClinic = "services/Doctors.svc/REST/ER_GetPatientPaymentInformationForERClinic"; + +///Er Nearest +var GET_AMBULANCE_REQUEST = 'Services/Patients.svc/REST/PatientER_RRT_GetAllTransportationMethod'; +var GET_PATIENT_ALL_PRES_ORDERS = 'Services/Patients.svc/REST/PatientER_GetPatientAllPresOrders'; +var GET_PICK_UP_REQUEST_BY_PRES_ORDER_ID = 'Services/Patients.svc/REST/PatientER_RRT_GetPickUpRequestByPresOrderID'; +var UPDATE_PRESS_ORDER = 'Services/Patients.svc/REST/PatientER_UpdatePresOrder'; +var INSERT_ER_INERT_PRES_ORDER = 'Services/Patients.svc/REST/PatientER_InsertPresOrder'; + +/// ER RRT +var GET_ALL_RC_TRANSPORTATION = 'api/Transportation/getalltransportation'; +var GET_ALL_TRANSPORTATIONS_RC = 'api/Transportation/getalltransportation'; +var GET_ALL_RRT_QUESTIONS = 'Services/Patients.svc/REST/PatientER_RRT_GetAllQuestions'; +var GET_RRT_SERVICE_PRICE = 'Services/Patients.svc/REST/PatientE_RealRRT_GetServicePrice'; + +var GET_ALL_TRANSPORTATIONS_ORDERS = 'api/Transportation/get'; + +var CANCEL_AMBULANCE_REQUEST = "api/Transportation/update"; + +var INSERT_TRANSPORTATION_ORDER_RC = "api/Transportation/add"; + +///FindUs +var GET_FINDUS_REQUEST = 'Services/Lists.svc/REST/Get_HMG_Locations'; + +///LiveChat +var GET_LIVECHAT_REQUEST = 'Services/Patients.svc/REST/GetPatientICProjects'; + +///babyInformation +var GET_BABYINFORMATION_REQUEST = 'Services/Community.svc/REST/GetBabyByUserID'; + +///Get Baby By User ID +var GET_BABY_BY_USER_ID = 'Services/Community.svc/REST/GetBabyByUserID'; + +///userInformation +var GET_USERINFORMATION_REQUEST = 'Services/Community.svc/REST/GetUserInformation_New'; + +///Update email +var UPDATE_PATENT_EMAIL = 'Services/Patients.svc/REST/UpdatePateintEmail'; +var UPDATE_PATENT_INFO = 'Services/Community.svc/REST/UpdateUserInfo_New'; + +///addNewChild +var GET_NEWCHILD_REQUEST = 'Services/Community.svc/REST/CreateNewBaby'; + +///newUserId +var GET_NEW_USER_REQUEST = 'Services/Community.svc/REST/CreateNewUser_New'; + +///delete Child +var DELETE_CHILD_REQUEST = 'Services/Community.svc/REST/DeleteBaby'; + +///addNewTABLE +var GET_TABLE_REQUEST = 'Services/Community.svc/REST/CreateVaccinationTable'; + +///BloodDenote +var GET_CITIES_REQUEST = 'Services/Lists.svc/REST/GetAllCities'; + +///BloodDetails +var GET_BLOOD_REQUEST = 'services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails'; + +var SAVE_BLOOD_REQUEST = 'Services/PatientVarification.svc/REST/BloodDonation_RegisterBloodType'; + +var GET_BLOOD_AGREEMENT = 'Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation'; +var SAVE_BLOOD_AGREEMENT = 'Services/PatientVarification.svc/REST/AddUserAgreementForBloodDonation'; + +///Reports +var REPORTS = 'Services/Doctors.svc/REST/GetPatientMedicalReportStatusInfo'; +var INSERT_REQUEST_FOR_MEDICAL_REPORT = 'Services/Doctors.svc/REST/InsertRequestForMedicalReport'; +var SEND_MEDICAL_REPORT_EMAIL = 'Services/Notifications.svc/REST/SendMedicalReportEmail'; +var GET_INPATIENT_ADMISSIONS = 'Services/inps.svc/REST/getAdmissionForMedicalReport'; +var GET_MEDICAL_REPORT_PDF = 'Services/inps.svc/REST/getMedicalReportPDF'; + +///Rate +// var IS_LAST_APPOITMENT_RATED = 'Services/Doctors.svc/REST/IsLastAppoitmentRated'; +var IS_LAST_APPOITMENT_RATED = 'Services/Doctors.svc/REST/IsLastAppoitmentRated_Async'; +var GET_APPOINTMENT_DETAILS_BY_NO = 'Services/MobileNotifications.svc/REST/GetAppointmentDetailsByApptNo'; +var NEW_RATE_APPOINTMENT_URL = "Services/Doctors.svc/REST/AppointmentsRating_InsertAppointmentRate"; +var NEW_RATE_DOCTOR_URL = "Services/Doctors.svc/REST/DoctorsRating_InsertDoctorRate"; + +var GET_QR_PARKING = 'Services/SWP.svc/REST/GetQRParkingByID'; + +//URL to get clinic list +var GET_CLINICS_LIST_URL = "Services/lists.svc/REST/GetClinicCentralized"; +var GET_CLINICS_LIST_WRT_HOSPITAL_URL = "Services/Lists.svc/REST/GetClinicFromDoctorSchedule"; + +//URL to get active appointment list +var GET_ACTIVE_APPOINTMENTS_LIST_URL = "Services/Doctors.svc/Rest/Dr_GetAppointmentActiveNumber"; + +//URL to get projects list +var GET_PROJECTS_LIST = 'Services/Lists.svc/REST/GetProject'; + +//URL to get doctors list +var GET_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/SearchDoctorsByTime"; + +//URL to dental doctors list +var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping"; + +//URL to get doctor free slots +var GET_DOCTOR_FREE_SLOTS = "Services/Doctors.svc/REST/GetDoctorFreeSlots"; + +//URL to insert appointment +var INSERT_SPECIFIC_APPOINTMENT = "Services/Doctors.svc/REST/InsertSpecificAppointment"; + +//URL to get patient share +var GET_PATIENT_SHARE = "Services/Doctors.svc/REST/GetCheckinScreenAppointmentDetailsByAppointmentNO"; + +//URL to get patient appointment history +var GET_PATIENT_APPOINTMENT_HISTORY = "Services/Doctors.svc/REST/PateintHasAppoimentHistory"; + +var GET_OBGYNE_ORDERS_LIST = "services/Patients.svc/REST/HIS_OBGYNEProcedureGet"; + +var GET_OBGYNE_DOCTORS_LIST = "services/Doctors.svc/REST/HIS_ObgyneUltrasoundDoctors"; + +var OBGYNE_PROCEDURE_UPDATE = "services/Patients.svc/REST/HIS_OBGYNEProcedure_Update"; + +var GET_RRT_PROCEDURE_LIST = "Services/Patients.svc/REST/GetRRTProcedureDetailsListFromVida"; + +var DOCTOR_SCHEDULE_URL = 'Services/Doctors.svc/REST/GetDoctorWorkingHoursTable'; + +var SEND_REPORT_EYE_EMAIL = "Services/Notifications.svc/REST/SendGlassesPrescriptionEmail"; + +var SEND_CONTACT_LENS_PRESCRIPTION_EMAIL = "Services/Notifications.svc/REST/SendContactLensPrescriptionEmail"; + +//URL to get patient appointment curfew history +// var GET_PATIENT_APPOINTMENT_CURFEW_HISTORY = "Services/Doctors.svc/REST/AppoimentHistoryForCurfew"; +var GET_PATIENT_APPOINTMENT_CURFEW_HISTORY = "Services/Doctors.svc/REST/AppoimentHistoryForCurfew_Async"; + +//URL to confirm appointment +var CONFIRM_APPOINTMENT = "Services/MobileNotifications.svc/REST/ConfirmAppointment"; + +var INSERT_VIDA_REQUEST = "Services/ER_VirtualCall.svc/REST/PatientER_VidaRequestInseart"; + +//URL to cancel appointment +var CANCEL_APPOINTMENT = "Services/Doctors.svc/REST/CancelAppointment"; + +//URL get appointment QR +var GENERATE_QR_APPOINTMENT = "Services/Doctors.svc/REST/GenerateQRAppointmentNo"; + +//URL send email appointment QR +var EMAIL_QR_APPOINTMENT = "Services/Notifications.svc/REST/sendEmailForOnLineCheckin"; + +//URL check payment status +var CHECK_PAYMENT_STATUS = "Services/PayFort_Serv.svc/REST/GetRequestStatusByRequestID"; + +//URL create advance payment +var CREATE_ADVANCE_PAYMENT = "Services/Doctors.svc/REST/CreateAdvancePayment"; + +var HIS_CREATE_ADVANCE_PAYMENT = "Services/Patients.svc/REST/HIS_CreateAdvancePayment"; + +var ER_CREATE_ADVANCE_PAYMENT = "services/Doctors.svc/REST/ER_CreateAdvancePaymentForClinic"; + +var ER_INSERT_ADVANCE_PAYMENT = "services/Doctors.svc/REST/ER_InsertEROnlinePaymentDetails"; + +var ADD_ADVANCE_NUMBER_REQUEST = 'Services/PayFort_Serv.svc/REST/AddAdvancedNumberRequest'; + +var GENERATE_ANCILLARY_ORDERS_INVOICE = 'Services/Doctors.svc/REST/AutoGenerateAncillaryOrderInvoice'; + +var IS_ALLOW_ASK_DOCTOR = 'Services/Doctors.svc/REST/GetPatientDoctorAppointmentResult'; +var GET_CALL_REQUEST_TYPE = 'Services/Doctors.svc/REST/GetCallRequestType_LOV'; +var ADD_VIDA_REQUEST = 'Services/ER_VirtualCall.svc/REST/PatientER_VidaRequestInseart'; + +var SEND_CALL_REQUEST = 'Services/Doctors.svc/REST/InsertCallInfo'; + +var GET_LIVECARE_CLINICS = 'Services/ER_VirtualCall.svc/REST/PatientER_GetClinics'; + +var GET_LIVECARE_SCHEDULE_CLINICS = 'Services/Doctors.svc/REST/PatientER_GetClinicsHaveSchedule'; + +var GET_LIVECARE_SCHEDULE_CLINIC_DOCTOR_LIST = 'Services/Doctors.svc/REST/PatientER_GetDoctorByClinicID'; + +var GET_LIVECARE_SCHEDULE_DOCTOR_TIME_SLOTS = 'Services/Doctors.svc/REST/PatientER_GetDoctorFreeSlots'; + +var INSERT_LIVECARE_SCHEDULE_APPOINTMENT = 'Services/Doctors.svc/REST/InsertSpecificAppoitmentForSchedule'; + +var GET_PATIENT_SHARE_LIVECARE = "Services/Doctors.svc/REST/GetCheckinScreenAppointmentDetailsByAppointmentNOForLiveCare"; + +var SET_ONLINE_CHECKIN_FOR_APPOINTMENT = "Services/Patients.svc/REST/SetOnlineCheckInForAppointment"; + +var GET_LIVECARE_CLINIC_TIMING = 'Services/ER_VirtualCall.svc/REST/PatientER_GetClinicsServiceTimingsSchedule'; + +var GET_ER_APPOINTMENT_FEES = 'Services/DoctorApplication.svc/REST/GetERAppointmentFees'; +var GET_ER_APPOINTMENT_TIME = 'Services/ER_VirtualCall.svc/REST/GetRestTime'; + +var CHECK_PATIENT_DERMA_PACKAGE = 'Services/OUTPs.svc/REST/getPatientPackageComponentsForOnlineCheckIn'; + +var ADD_NEW_CALL_FOR_PATIENT_ER = 'Services/DoctorApplication.svc/REST/NewCallForPatientER'; + +var GET_LIVECARE_HISTORY = 'Services/ER_VirtualCall.svc/REST/GetPatientErVirtualHistory'; +var CANCEL_LIVECARE_REQUEST = 'Services/ER_VirtualCall.svc/REST/DeleteErRequest'; +var SEND_LIVECARE_INVOICE_EMAIL = 'Services/Notifications.svc/REST/SendInvoiceForLiveCare'; + +var CHANGE_PATIENT_ER_SESSION = 'Services/DoctorApplication.svc/REST/ChangePatientERSession'; + +var APPLE_PAY_INSERT_REQUEST = 'Services/PayFort_Serv.svc/REST/PayFort_ApplePayRequestData_Insert'; + +var GET_USER_TERMS = 'Services/Patients.svc/REST/GetUserTermsAndConditions'; + +var TAMARA_REQUEST_INSERT = 'Services/PayFort_Serv.svc/REST/AddTamaraRequest'; + +var UPDATE_HEALTH_TERMS = 'services/Patients.svc/REST/UpdatePateintHealthSummaryReport'; + +var GET_PATIENT_HEALTH_STATS = 'Services/Patients.svc/REST/Med_GetTransactionsSts'; + +var SEND_CHECK_IN_NFC_REQUEST = 'Services/Patients.svc/REST/Patient_CheckAppointmentValidation_ForNFC'; + +var CHECK_SCANNED_NFC_QR_CODE = 'Services/Patients.svc/REST/Patient_ValidationMachine_ForNFC'; + +var HAS_DENTAL_PLAN = 'Services/Doctors.svc/REST/Dental_IsPatientHasOnGoingEstimation'; + +var LASER_BODY_PARTS = 'Services/Patients.svc/REST/Laser_GetBodyPartsByCategory'; + +var INSERT_COVID_QUESTIONNAIRE = 'Services/Doctors.svc/REST/COVID19_Questionnarie_Insert'; + +var UPDATE_COVID_QUESTIONNAIRE = 'Services/Doctors.svc/REST/COVID19_Questionnarie_Update'; + +var GET_PATIENT_SHARE_FOR_WALKIN_APPOINTMENT = 'Services/Doctors.svc/REST/GetCheckinScreenAppointmentDetailsByAppointmentNOForWalkIn'; + +var CAN_PAY_FOR_FOR_WALKIN_APPOINTMENT = 'Services/Doctors.svc/REST/CanPayForWalkinAppointment'; + +//URL to get medicine and pharmacies list +var CHANNEL = 3; +var GENERAL_ID = 'Cs2020@2016\$2958'; +var IP_ADDRESS = '10.20.10.20'; +var VERSION_ID = 18.7; +var SETUP_ID = '91877'; +var LANGUAGE = 2; +// var PATIENT_OUT_SA = 0; +var SESSION_ID = 'TMRhVmkGhOsvamErw'; +var IS_DENTAL_ALLOWED_BACKEND = false; +var PATIENT_TYPE = 1; +var PATIENT_TYPE_ID = 1; +var DEVICE_TOKEN = ""; +var IS_VOICE_COMMAND_CLOSED = true; +var IS_TEXT_COMPLETED = false; +// var DeviceTypeID = Platform.isIOS ? 1 : 2; +// var LANGUAGE_ID = 2; + +var GET_PHARMCY_ITEMS = "Services/Lists.svc/REST/GetPharmcyItems_Region"; +var GET_PHARMACY_LIST = "Services/Patients.svc/REST/GetPharmcyList"; +var GET_PAtIENTS_INSURANCE = "Services/Patients.svc/REST/Get_PatientInsuranceDetails"; +var GET_PAtIENTS_INSURANCE_UPDATED = "Services/Patients.svc/REST/PatientER_GetPatientInsuranceCardUpdateHistory"; + +var INSURANCE_DETAILS = "Services/Patients.svc/REST/Get_InsuranceCheckList"; +var INSURANCE_SCHEMES = "Services/Patients.svc/REST/PatientER_SchemesOfAactiveCompaniesGet"; +var UPDATE_MANUAL_INSURANCE = "Services/Patients.svc/REST/PatientER_PatientInfoForInsuranceCardUpdate"; +var INSURANCE_COMPANIES = "Services/Patients.svc/REST/PatientER_InsuranceCompanyGet"; +var GET_PATIENT_INSURANCE_DETAILS = "Services/Patients.svc/REST/PatientER_GetPatientInsuranceDetails"; +var UPLOAD_INSURANCE_CARD = 'Services/Patients.svc/REST/PatientER_PatientInfoForInsuranceCardUpdate'; + +var GET_VACCINES = "Services/Patients.svc/REST/GetDoneVaccinesByPatientID"; +var GET_VACCINES_EMAIL = "Services/Notifications.svc/REST/SendVaccinesEmail"; +var GET_PAtIENTS_INSURANCE_APPROVALS = "Services/Patients.svc/REST/GetApprovalStatus_Async"; +// var GET_PAtIENTS_INSURANCE_APPROVALS = "Services/Patients.svc/REST/GetApprovalStatus"; +var SEARCH_BOT = 'HabibiChatBotApi/BotInterface/GetVoiceCommandResponse'; + +var GET_VACCINATIONS_ITEMS = "/Services/ERP.svc/REST/GET_VACCINATIONS_ITEMS"; +var GET_VACCINATION_ONHAND = "/Services/ERP.svc/REST/GET_VACCINATION_ONHAND"; + +var GET_PATIENT_SICK_LEAVE = 'Services/Patients.svc/REST/GetPatientSickLeave'; + +var GET_PATIENT_SICK_LEAVE_STATUS = 'Services/Patients.svc/REST/GetPatientSickLeave_Status'; + +var SendSickLeaveEmail = 'Services/Notifications.svc/REST/SendSickLeaveEmail'; + +var GET_PATIENT_AdVANCE_BALANCE_AMOUNT = 'Services/Patients.svc/REST/GetPatientAdvanceBalanceAmount'; +var GET_PATIENT_INFO_BY_ID = 'Services/Doctors.svc/REST/GetPatientInfoByPatientID'; +var GET_PATIENT_INFO_BY_ID_AND_MOBILE_NUMBER = 'Services/Patients.svc/REST/AP_GetPatientInfoByPatientIDandMobileNumber'; +var SEND_ACTIVATION_CODE_FOR_ADVANCE_PAYMENT = 'Services/Authentication.svc/REST/SendActivationCodeForAdvancePayment'; +var CHECK_ACTIVATION_CODE_FOR_ADVANCE_PAYMENT = 'Services/Authentication.svc/REST/CheckActivationCodeForAdvancePayment'; + +var GET_COVID_DRIVETHRU_PROJECT_LIST = 'Services/Doctors.svc/REST/COVID19_ProjectDriveThroughTestingCenter'; + +var GET_COVID_DRIVETHRU_PAYMENT_INFO = 'Services/Doctors.svc/REST/COVID19_GetPatientPaymentInormation'; + +var GET_COVID_DRIVETHRU_FREE_SLOTS = 'Services/Doctors.svc/REST/COVID19_GetFreeSlots'; + +var GET_COVID_DRIVETHRU_PROCEDURES_LIST = 'Services/Doctors.svc/REST/COVID19_GetTestProcedures'; + +///Smartwatch Integration Services +var GET_PATIENT_LAST_RECORD = 'Services/Patients.svc/REST/Med_GetPatientLastRecord'; +var INSERT_PATIENT_HEALTH_DATA = 'Services/Patients.svc/REST/Med_InsertTransactions'; + +///My Trackers +var GET_DIABETIC_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetDiabeticResultAverage'; +var GET_DIABTEC_RESULT = 'Services/Patients.svc/REST/Patient_GetDiabtecResults'; +var ADD_DIABTEC_RESULT = 'Services/Patients.svc/REST/Patient_AddDiabtecResult'; + +var GET_BLOOD_PRESSURE_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetBloodPressureResultAverage'; +var GET_BLOOD_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_GetBloodPressureResult'; +var ADD_BLOOD_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_AddBloodPressureResult'; + +var GET_WEIGHT_PRESSURE_RESULT_AVERAGE = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResultAverage'; +var GET_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResult'; +var ADD_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_AddWeightMeasurementResult'; + +var ADD_ACTIVE_PRESCRIPTIONS_REPORT_BY_PATIENT_ID = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID'; + +var GET_CALL_INFO_HOURS_RESULT = 'Services/Doctors.svc/REST/GetCallInfoHoursResult'; +var GET_CALL_REQUEST_TYPE_LOV = 'Services/Doctors.svc/REST/GetCallRequestType_LOV'; + +var GET_QUESTION_TYPES = 'Services/OUTPs.svc/REST/getQuestionsTypes'; + +var UPDATE_DIABETIC_RESULT = 'Services/Patients.svc/REST/Patient_UpdateDiabeticResult'; + +var SEND_AVERAGE_BLOOD_SUGAR_REPORT = 'Services/Notifications.svc/REST/SendAverageBloodSugarReport'; +var DEACTIVATE_DIABETIC_STATUS = 'services/Patients.svc/REST/Patient_DeactivateDiabeticStatus'; +var DEACTIVATE_BLOOD_PRESSURES_STATUS = 'services/Patients.svc/REST/Patient_DeactivateBloodPressureStatus'; + +var UPDATE_BLOOD_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_UpdateBloodPressureResult'; +var SEND_AVERAGE_BLOOD_WEIGHT_REPORT = 'Services/Notifications.svc/REST/SendAverageBodyWeightReport'; +var SEND_AVERAGE_BLOOD_PRESSURE_REPORT = 'Services/Notifications.svc/REST/SendAverageBloodPressureReport'; + +var UPDATE_WEIGHT_PRESSURE_RESULT = 'Services/Patients.svc/REST/Patient_UpdateWeightMeasurementResult'; +var DEACTIVATE_WEIGHT_PRESSURE_RESULT = 'services/Patients.svc/REST/Patient_DeactivateWeightMeasurementStatus'; +var GET_DOCTOR_RESPONSE = 'Services/Patients.svc/REST/GetDoctorResponse'; +var UPDATE_READ_STATUS = 'Services/Patients.svc/REST/UpdateReadStatus'; + +var INSERT_CALL_INFO = 'Services/Doctors.svc/REST/InsertCallInfo'; +var INSERT_APPOINTMENT_QUESTION = 'Services/OUTPs.svc/REST/insertAppointmentQuestion'; +var RATE_DOCTOR_RESPONSE = 'Services/OUTPs.svc/REST/insertAppointmentQuestionRating'; + +var GET_PATIENT_ALLERGIES = 'Services/Patients.svc/REST/GetPatientAllergies'; + +// H2O +var H2O_GET_USER_PROGRESS = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress"; +var H2O_INSERT_USER_ACTIVITY = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity"; +var H2O_GET_USER_DETAIL = "Services/H2ORemainder.svc/REST/H2O_GetUserDetails_New"; +var H2O_UPDATE_USER_DETAIL = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetails_New"; +var H2O_UNDO_USER_ACTIVITY = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity"; +//E_Referral Services + +var GET_ALL_RELATIONSHIP_TYPES = "Services/Patients.svc/REST/GetAllRelationshipTypes"; +var SEND_ACTIVATION_CODE_FOR_E_REFERRAL = 'Services/Authentication.svc/REST/SendActivationCodeForEReferral'; +var CHECK_ACTIVATION_CODE_FOR_E_REFERRAL = 'Services/Authentication.svc/REST/CheckActivationCodeForEReferral'; +var GET_ALL_CITIES = 'services/Lists.svc/rest/GetAllCities'; +var CREATE_E_REFERRAL = "Services/Patients.svc/REST/CreateEReferral"; +var GET_E_REFERRALS = "Services/Patients.svc/REST/GetEReferrals"; + +// Encillary Orders + +var GET_ANCILLARY_ORDERS = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList'; + +var GET_ANCILLARY_ORDERS_DETAILS = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderProcList'; + +//Pharmacy wishlist +// var GET_WISHLIST = "http://swd-pharapp-01:7200/api/shopping_cart_items/"; + +var GET_DOCTOR_LIST_BY_TIME = "Services/Doctors.svc/REST/SearchDoctorsByTime"; + +// pharmacy +var PHARMACY_AUTORZIE_CUSTOMER = "AutorizeCustomer"; +var PHARMACY_VERIFY_CUSTOMER = "VerifyCustomer"; +var PHARMACY_GET_COUNTRY = "countries"; +// var PHARMACY_CREATE_CUSTOMER = "epharmacy/api/CreateCustomer"; +var PHARMACY_CREATE_CUSTOMER = "getorcreateCustomer"; +var GET_PHARMACY_BANNER = "promotionbanners"; +var GET_PHARMACY_TOP_MANUFACTURER = "topmanufacturer"; +var GET_PHARMACY_BEST_SELLER_PRODUCT = "bestsellerproducts"; +var GET_PHARMACY_PRODUCTs_BY_IDS = "productsbyids/"; +var GET_PHARMACY_PRODUCTs_BY_SKU = "productbysku/"; +var GET_CUSTOMERS_ADDRESSES = "Customers/"; +var SUBSCRIBE_PRODUCT = "subscribe?"; +var GET_ORDER = "orders?"; +var GET_ORDER_DETAILS = "orders/"; +var ADD_CUSTOMER_ADDRESS = "addcustomeraddress"; +var EDIT_CUSTOMER_ADDRESS = "editcustomeraddress"; +var DELETE_CUSTOMER_ADDRESS = "deletecustomeraddress"; +var GET_ADDRESS = "Customers/"; +var GET_Cancel_ORDER = "cancelorder/"; +var WRITE_REVIEW = "Content-Type" + "text/plain; charset=utf-8"; +var GET_SHOPPING_CART = "shopping_cart_items/"; +var GET_SHIPPING_OPTIONS = "get_shipping_option/"; +var DELETE_SHOPPING_CART = "delete_shopping_cart_items/"; +var DELETE_SHOPPING_CART_ALL = "delete_shopping_cart_item_by_customer/"; +var ORDER_SHOPPING_CART = "orders"; +var GET_LACUM_ACCOUNT_INFORMATION = "Services/Patients.svc/REST/GetLakumAccountInformation"; +var GET_LACUM_GROUP_INFORMATION = "Services/Patients.svc/REST/GetlakumInQueryInfoGrouping"; +var LACUM_ACCOUNT_ACTIVATE = "Services/Patients.svc/REST/LakumAccountActivation"; +var LACUM_ACCOUNT_DEACTIVATE = "Services/Patients.svc/REST/LakumAccountDeactivation"; +var CREATE_LAKUM_ACCOUNT = "Services/Patients.svc/REST/PHR_CreateLakumAccount"; +var TRANSFER_YAHALA_LOYALITY_POINTS = "Services/Patients.svc/REST/TransferYaHalaLoyaltyPoints"; +var LAKUM_GET_USER_TERMS_AND_CONDITIONS = "Services/ERP.svc/REST/GetUserTermsAndConditionsForEPharmcy"; + +// var PRESCRIPTION = 'Services/Patients.svc/REST/GetPrescriptionApptList'; +var PRESCRIPTION = 'Services/Patients.svc/REST/GetPrescriptionApptList_Async'; + +var GET_RECOMMENDED_PRODUCT = 'alsoProduct/'; +var GET_MOST_VIEWED_PRODUCTS = "mostview"; +var GET_NEW_PRODUCTS = "newproducts"; + +// Home Health Care +var HHC_GET_ALL_SERVICES = "Services/Patients.svc/REST/PatientER_HHC_GetAllServices"; +var HHC_GET_ALL_CMC_SERVICES = "Services/Patients.svc/REST/PatientER_CMC_GetAllServices"; +var PATIENT_ER_UPDATE_PRES_ORDER = "Services/Patients.svc/REST/PatientER_UpdatePresOrder"; +var GET_ORDER_DETAIL_BY_ID = "Services/Patients.svc/REST/PatientER_HHC_GetTransactionsForOrder"; +var GET_CMC_ORDER_DETAIL_BY_ID = "Services/Patients.svc/REST/PatientER_CMC_GetTransactionsForOrder"; +var GET_CHECK_UP_ITEMS = "Services/Patients.svc/REST/GetCheckUpItems"; +var PUSH_NOTIFICATION_GET_ALL_NOTIFICATIONS = 'Services/MobileNotifications.svc/REST/PushNotification_GetAllNotifications'; +var PUSH_NOTIFICATION_SET_MESSAGES_FROM_POOL_AS_READ = 'Services/MobileNotifications.svc/REST/PushNotification_SetMessagesFromPoolAsRead'; +var GET_PATIENT_ALL_PRES_ORD = 'Services/Patients.svc/REST/PatientER_GetPatientAllPresOrders'; +var PATIENT_ER_INSERT_PRES_ORDER = 'Services/Patients.svc/REST/PatientER_InsertPresOrder'; +var BLOOD_DONATION_REGISTER_BLOOD_TYPE = 'Services/PatientVarification.svc/REST/BloodDonation_RegisterBloodType'; +var ADD_USER_AGREEMENT_FOR_BLOOD_DONATION = 'Services/PatientVarification.svc/REST/AddUserAgreementForBloodDonation'; + +// HHC RC SERVICES +var HHC_GET_ALL_SERVICES_RC = "api/HHC/getallhhc"; +var ADD_HHC_ORDER_RC = "api/HHC/add"; +var GET_ALL_HHC_ORDERS_RC = 'api/hhc/list'; +var UPDATE_HHC_ORDER_RC = 'api/hhc/update'; + +// CMC RC SERVICES +var GET_ALL_CMC_SERVICES_RC = 'api/cmc/getallcmc'; +var ADD_CMC_ORDER_RC = 'api/cmc/add'; +var GET_ALL_CMC_ORDERS_RC = 'api/cmc/list'; +var UPDATE_CMC_ORDER_RC = 'api/cmc/update'; + +// RRT RC SERVICES +var ADD_RRT_ORDER_RC = "api/rrt/add"; +var GET_ALL_RRT_ORDERS_RC = "api/rrt/list"; +var UPDATE_RRT_ORDER_RC = 'api/rrt/update'; + +// PRESCRIPTION RC SERVICES +var ADD_PRESCRIPTION_ORDER_RC = "api/prescription/add"; +var GET_ALL_PRESCRIPTION_ORDERS_RC = "api/prescription/list"; +var GET_ALL_PRESCRIPTION_INFO_RC = "api/Prescription/info"; +var UPDATE_PRESCRIPTION_ORDER_RC = 'api/prescription/update'; + +//Pharmacy wishlist +var GET_WISHLIST = "shopping_cart_items/"; +var DELETE_WISHLIST = "delete_shopping_cart_item_by_product?customer_id="; +var GET_REVIEW = "customerreviews/"; +var GET_BRANDS = "manufacturer"; +var GET_TOP_BRANDS = "topmanufacturer?page=1&limit=8"; +var GET_PRODUCT_DETAIL = "products/"; +var GET_LOCATION = "Services/Patients.svc/REST/GetPharmcyListBySKU"; +var GET_SPECIFICATION = "productspecification/"; +var GET_BRAND_ITEMS = "products"; +var PHARMACY_MAKE_REVIEW = 'insertreviews'; + +// External API +var ADD_ADDRESS_INFO = "addcustomeraddress"; +var GET_CUSTOMER_ADDRESSES = "Customers/"; +var GET_CUSTOMER_INFO = "VerifyCustomer"; + +//Pharmacy + +var GET_PHARMACY_CATEGORISE = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=0'; +var GET_OFFERS_CATEGORISE = 'discountcategories'; +var GET_OFFERS_PRODUCTS = 'offerproducts/'; +var GET_CATEGORISE_PARENT = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id='; +var GET_PARENT_PRODUCTS = 'products?categoryid='; +var GET_SUB_CATEGORISE = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id='; +var GET_SUB_PRODUCTS = 'products?categoryid='; +var GET_FINAL_PRODUCTS = + 'products?fields=id,reviews,discount_ids,name,namen,localized_names,display_order,short_description,full_description,full_descriptionn,sku,order_minimum_quantity,order_maximum_quantity,price,old_price,images,is_rx,rx_message,rx_messagen,discount_name,discount_namen,approved_rating_sum,approved_total_reviews,allow_back_in_stock_subscriptions,stock_quantity,stock_availability,stock_availabilityn,discount_percentage&CategoryId='; +var GET_CLINIC_CATEGORY = 'Services/Doctors.svc/REST/DP_GetClinicCategory'; +var GET_DISEASE_BY_CLINIC_ID = 'Services/Doctors.svc/REST/DP_GetDiseasesByClinicID'; +var SEARCH_DOCTOR_BY_TIME = 'Services/Doctors.svc/REST/SearchDoctorsByTime'; + +var TIMER_MIN = 10; + +var GOOGLE_API_KEY = "AIzaSyCmevVlr2Bh-c8W1VUzo8gt8JRY7n5PANw"; + +var GET_BRANDS_LIST = 'categoryManufacturer?categoryids='; + +var GET_SEARCH_PRODUCTS = + 'searchproducts?fields=id,discount_ids,reviews,name,namen,localized_names,display_order,short_description,full_description,full_descriptionn,sku,order_minimum_quantity,order_maximum_quantity,price,old_price,images,is_rx,rx_message,rx_messagen,discount_name,discount_namen,approved_rating_sum,approved_total_reviews,allow_back_in_stock_subscriptions,stock_quantity,stock_availability,stock_availabilityn,discount_percentage&search_key='; + +var SCAN_QR_CODE = 'productbysku/'; + +var FILTERED_PRODUCTS = 'products?categoryids='; + +var GET_DOCTOR_LIST_CALCULATION = "Services/Doctors.svc/REST/GetCallculationDoctors"; + +var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = "Services/Patients.svc/REST/GetDentalAppointments"; + +var GET_DENTAL_APPOINTMENT_INVOICE = "Services/Patients.svc/REST/HIS_eInvoiceForDentalByAppointmentNo"; + +var SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL = "Services/Notifications.svc/REST/SendInvoiceForDental"; + +var GET_TAMARA_PLAN = 'https://mdlaboratories.com/tamaralive/Home/GetInstallments'; + +var GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; + +var UPDATE_TAMARA_STATUS = 'Services/PayFort_Serv.svc/REST/Tamara_UpdateRequestStatus'; + +var MARK_APPOINTMENT_TAMARA_STATUS = 'Services/Patients.svc/REST/MarkAppointmentForTamaraPayment_FromVida'; + +var AUTO_GENERATE_INVOICE_TAMARA = 'Services/PayFort_Serv.svc/REST/Tamara_GetinfoByAppointmentNo_AutoGenerateInvoice'; + +var GET_ONESIGNAL_VOIP_TOKEN = 'https://onesignal.com/api/v1/players'; + +var CANCEL_PHARMA_LIVECARE_REQUEST = 'https://vcallapi.hmg.com/api/PharmaLiveCare/SendPaymentStatus'; + +var INSERT_FREE_SLOTS_LOGS = 'Services/Doctors.svc/Rest/InsertDoctorFreeSlotsLogs'; + +var GET_NATIONALITY = 'Services/Lists.svc/REST/GetNationality'; + +var PAYFORT_TEST_URL = 'https://sbpaymentservices.payfort.com/FortAPI/paymentApi'; +var PAYFORT_PROD_URL = 'https://paymentservices.payfort.com/FortAPI/paymentApi'; + +// Check If InPatient API +var CHECK_IF_INPATIENT = 'Services/Patients.svc/REST/GetInPatientAdmissionInfo'; + +var CHECK_IF_PATIENT_ADMITTED = 'Services/Inps.svc/REST/checkIsPatientAdmittedOrAdmissionRequest'; + +// Get General Instructions API +var GET_GENERAL_INSTRUCTIONS = 'Services/INPs.svc/REST/getGeneralInstructions'; + +// Get Medical Instructions API +var GET_MEDICAL_INSTRUCTIONS = 'Services/INPs.svc/REST/getPatientAdmissionRequest'; + +var GET_INPATIENT_ADVANCE_PAYMENT_REQUESTS = 'Services/INPs.svc/REST/getInpatientAdvancePendingPayment'; + +var GET_INPATIENT_PAID_ADVANCE_PAYMENT = 'Services/INPs.svc/REST/getInpatientAdvanceHistory'; + +var GET_INPATIENT_ADVANCE_PAYMENT_LINK = 'Services/PayFort_Serv.svc/REST/InsertInPatientAdvanceDetails'; + +var INSERT_INPATIENT_ORDER = 'Services/INPs.svc/REST/Inpcp_insertOrder'; + +var INPATIENT_DISCHARGE_MEDICATIONS = 'Services/INPs.svc/REST/chekPatientHasDischargeMedicine'; + +var GET_BIRTH_NOTIFICATION = 'Services/INPs.svc/REST/getBirthNotification_bymothermrn'; + +var SAVE_BIRTH_NOTIFICATION = 'Services/INPs.svc/REST/SaveBirthNotification'; + +var INSERT_GENERAL_ADMISSION_CONSENT = 'Services/INPs.svc/REST/Inp_insertAAForGeneralAdmissionConsent'; + +//Meal Plan APIs +var GET_ADMITTED_PATIENTS = 'Services/MOP.svc/REST/GetAdmittedPatients'; +var GET_CURRENT_WEEKID_WEEKDAY = 'Services/MOP.svc/REST/GetCurrentWeekAndDayHMGMP'; +var GET_MEALS_OF_SCHEDULE_ID = 'Services/MOP.svc/REST/GetMealsOfScheduleID_Mobile'; +var GET_MEAL_ITEMS_OF_SCHEDULE_ID = 'Services/MOP.svc/REST/GetDefaultItemsOfScheduleID'; +var PLACE_MEAL_PLAN_ORDER = 'Services/MOP.svc/REST/UpdateOrMakeNewOrder'; + +var CHECK_PATIENT_NPHIES_ELIGIBILITY = 'Services/Doctors.svc/REST/checkPatientInsuranceCompanyValidity'; +var CONVERT_PATIENT_TO_CASH = 'Services/Doctors.svc/REST/deActivateInsuranceCompany'; + +var GET_BLOOD_DONATION_PROJECTS_LIST = 'Services/OUTPs.svc/REST/BD_getProjectsHaveBDClinics'; + +var GET_BLOOD_DONATION_FREE_SLOTS = 'Services/OUTPs.svc/REST/BD_GetFreeSlots'; + +var GET_WE_CARE_TOUR_URL = 'Services/Consent.svc/Rest/Consent_VirtualJurny_Url_GetByProjectID'; + +var GET_DENTAL_INSTRUCTIONS = 'Services/OUTPs.svc/Rest/getProcedureNotification'; + +var INSERT_WALKIN_APPOINTMENT = "Services/Doctors.svc/REST/InsertWalkinAppointment"; + +//Usage Agreement APIs +var CHECK_USAGE_AGREEMENT = "Services/Patients.svc/REST/CheckForUsageAgreement"; +var GET_USAGE_AGREEMENT = "Services/Patients.svc/REST/GetUsageAgreementText"; +var ADD_USAGE_AGREEMENT = "Services/Patients.svc/REST/AddUsageAgreement"; + +var GET_ER_ONLINE_PAYMENT_DETAILS = 'Services/OUTPs.svc/Rest/Outp_GetPatientPaymentInformationForERClinic'; + +var AUTO_GENERATE_INVOICE_ER = 'Services/OUTPs.svc/Rest/Outp_AutoGenerateInvoiceForER'; + +var CHECK_IF_PATIENT_ARRIVED_ER_ONLINE_CHECKIN = 'Services/OUTPs.svc/Rest/IsPatientArrived'; + +var CHECK_PATIENT_ER_ADVANCE_BALANCE = 'Services/OUTPs.svc/Rest/getPatientAdvanceBalanceAmountByClinic'; + +var GET_PROJECT_FROM_NFC = 'Services/OUTPs.svc/Rest/GetProjectByNFC'; + +var GET_PATIENT_OCCUPATION_LIST = 'Services/Authentication.svc/REST/GetPatientOccupation'; + +var IS_DOCTOR_AVAILABLE_BY_CALENDAR_SCHEDULE = 'Services/OUTPs.svc/REST/HIS_IsDoctorAvailableByCalendarSchedule'; + +//PAYFORT +var getPayFortProjectDetails = "Services/PayFort_Serv.svc/REST/GetPayFortProjectDetails"; +var addPayFortApplePayResponse = "Services/PayFort_Serv.svc/REST/AddResponse"; +// var payFortEnvironment = FortEnvironment.production; +var applePayMerchantId = "merchant.com.hmgwebservices"; +// var payFortEnvironment = FortEnvironment.test; +// var applePayMerchantId = "merchant.com.hmgwebservices.uat"; + + + +// Auth Provider Consts + + +const String INSERT_DEVICE_IMEI = 'Services/Patients.svc/REST/Patient_INSERTDeviceIMEI'; +const String SELECT_DEVICE_IMEI = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI'; +const String CHECK_PATIENT_AUTH = 'Services/Authentication.svc/REST/CheckPatientAuthentication'; +const GET_MOBILE_INFO = 'Services/Authentication.svc/REST/GetMobileLoginInfo'; +const SEND_ACTIVATION_CODE = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationType'; + +const SEND_ACTIVATION_CODE_REGISTER = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationTypeForRegistration'; +const CHECK_ACTIVATION_CODE = 'Services/Authentication.svc/REST/CheckActivationCode'; +const CHECK_ACTIVATION_CODE_REGISTER = 'Services/Authentication.svc/REST/CheckActivationCodeForRegistration'; + +const FORGOT_PASSWORD = 'Services/Authentication.svc/REST/CheckActivationCodeForSendFileNo'; +const CHECK_PATIENT_FOR_REGISTRATION = "Services/Authentication.svc/REST/CheckPatientForRegisteration"; + +const CHECK_USER_STATUS = "Services/NHIC.svc/REST/GetPatientInfo"; +const REGISTER_USER = 'Services/Authentication.svc/REST/PatientRegistration'; +const LOGGED_IN_USER_URL = 'Services/MobileNotifications.svc/REST/Insert_PatientMobileDeviceInfo'; + +const FORGOT_PATIENT_ID = 'Services/Authentication.svc/REST/SendPatientIDSMSByMobileNumber'; +const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard'; +const PROFILE_SETTING = 'Services/Patients.svc/REST/GetPateintInfoForUpdate'; +const SAVE_SETTING = 'Services/Patients.svc/REST/UpdatePateintInfo'; + +const DEACTIVATE_ACCOUNT = 'Services/Patients.svc/REST/PatientAppleActivation_InsertUpdate'; + + + +class ApiConsts { + static const maxSmallScreen = 660; + + static bool isDevelopment = true; + + // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT + static String baseUrl = 'https://hmgwebservices.com/'; // HIS API URL PROD + + static String SELECT_DEVICE_IMEI = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI'; + + static num VERSION_ID = 18.9; + + static final String selectDeviceImei = 'https://hmgwebservices.com/Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI'; + static final String sendActivationCode = 'https://hmgwebservices.com/Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationType'; + + + + static setBackendURLs() { + if (isDevelopment) { + baseUrl = "https://uat.hmgwebservices.com/"; + } else { + baseUrl = "https://hmgwebservices.com/"; + } + } + + + + static final Map packagesAuthHeader = {}; + +} \ No newline at end of file diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 684c6003..71fb6a9f 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -1,7 +1,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:hmg_patient_app_new/core/post_params_model.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/authenticated_user_model.dart'; import 'package:hmg_patient_app_new/main.dart'; +import 'api_consts.dart' as ApiConsts; import 'consts.dart'; class AppState { @@ -21,8 +23,13 @@ class AppState { set setUserLong(v) => userLong = v; - final PostParamsModel _postParamsInitConfig = PostParamsModel(channel: 3, versionID: ApiConsts.VERSION_ID); - + final PostParamsModel _postParamsInitConfig = PostParamsModel( + channel: 3, + versionID: ApiConsts.VERSION_ID, + ipAddress: '10.20.10.20', + generalId: 'Cs2020@2016\$2958', + deviceTypeID: "2", + sessionID: 'TMRhVmkGhOsvamErw'); void setPostParamsInitConfig() { isAuthenticated = false; @@ -30,8 +37,12 @@ class AppState { } PostParamsModel? _postParams; + PostParamsModel? get postParamsObject => _postParams; - Map get postParamsJson => isAuthenticated ? (_postParams?.toJsonAfterLogin() ?? {}) : (_postParams?.toJson() ?? {}); + + Map get postParamsJson => isAuthenticated + ? (_postParams?.toJsonAfterLogin() ?? {}) + : (_postParams?.toJson() ?? {}); void setPostParamsModel(PostParamsModel _postParams) { this._postParams = _postParams; @@ -40,7 +51,18 @@ class AppState { double userLat = 0.0; double userLong = 0.0; - bool isArabic() => EasyLocalization.of(navigatorKey.currentContext!)?.locale.languageCode == "ar"; + bool isArabic() => + EasyLocalization.of(navigatorKey.currentContext!)?.locale.languageCode == + "ar"; + + int getLanguageID(context) => + EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2; + + AuthenticatedUser? _authenticatedUser; + + void setAuthenticatedUser(AuthenticatedUser authenticatedUser) { + _authenticatedUser = authenticatedUser; + } - int getLanguageID(context) => EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2; + AuthenticatedUser? get getAuthenticatedUser => _authenticatedUser; } diff --git a/lib/core/consts.dart b/lib/core/consts.dart index 5818b1e7..216b9a71 100644 --- a/lib/core/consts.dart +++ b/lib/core/consts.dart @@ -1,23 +1,4 @@ -class ApiConsts { - static const maxSmallScreen = 660; - static bool isDevelopment = true; - - // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT - static String baseUrl = 'https://hmgwebservices.com/'; // HIS API URL PROD - - static String SELECT_DEVICE_IMEI = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI'; - - static num VERSION_ID = 18.9; - - static setBackendURLs() { - if (isDevelopment) { - baseUrl = "https://uat.hmgwebservices.com/"; - } else { - baseUrl = "https://hmgwebservices.com/"; - } - } -} class SharedPrefsConsts { static String isRememberMe = "remember_me"; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 82e3fe77..a789da41 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -19,7 +19,7 @@ class AppDependencies { final sharedPreferences = await SharedPreferences.getInstance(); getIt.registerLazySingleton(() => CacheServiceImp(sharedPreferences: sharedPreferences)); getIt.registerSingleton(AppState()); - getIt.registerLazySingleton(() => ApiClient(loggerService: getIt())); + getIt.registerLazySingleton(() => ApiClientImp(loggerService: getIt())); // Repositories getIt.registerLazySingleton(() => CommonRepoImp(loggerService: getIt())); diff --git a/lib/core/exceptions/api_exception.dart b/lib/core/exceptions/api_exception.dart index 5076cc32..eb11b71e 100644 --- a/lib/core/exceptions/api_exception.dart +++ b/lib/core/exceptions/api_exception.dart @@ -16,12 +16,11 @@ class APIException implements Exception { static const String UNKNOWN = 'unexpected_error'; final String message; - final APIError? error; final arguments; - const APIException(this.message, {this.arguments, this.error}); + const APIException(this.message, {this.arguments}); - Map toJson() => {'message': message, 'error': error, 'arguments': '$arguments'}; + Map toJson() => {'message': message, 'arguments': '$arguments'}; @override String toString() { diff --git a/lib/core/post_params_model.dart b/lib/core/post_params_model.dart index 218f29f9..2f981f64 100644 --- a/lib/core/post_params_model.dart +++ b/lib/core/post_params_model.dart @@ -14,7 +14,7 @@ class PostParamsModel { String? sessionID; String? setupID; - PostParamsModel({this.versionID, this.channel, this.languageID, this.logInTokenID, this.tokenID, this.language, this.ipAddress, this.generalId, this.latitude, this.longitude, this.deviceTypeID}); + PostParamsModel({this.versionID, this.channel, this.languageID, this.logInTokenID, this.tokenID, this.language, this.ipAddress, this.generalId, this.latitude, this.longitude, this.deviceTypeID, this.sessionID}); PostParamsModel.fromJson(Map json) { versionID = json['VersionID']; @@ -22,25 +22,28 @@ class PostParamsModel { languageID = json['LanguageID']; logInTokenID = json['LogInTokenID']; tokenID = json['TokenID']; + sessionID = json['SessionID']; } Map toJson() { Map data = {}; - data['versionID'] = versionID; - data['channel'] = channel; - data['languageID'] = languageID; - data['logInTokenID'] = logInTokenID ?? ""; - data['tokenID'] = tokenID ?? ""; + data['VersionID'] = versionID; + data['Channel'] = channel; + data['LanguageID'] = languageID; + data['LogInTokenID'] = logInTokenID ?? ""; + data['TokenID'] = tokenID ?? ""; + data['SessionID'] = sessionID ?? ""; return data; } Map toJsonAfterLogin() { Map data = {}; - data['versionID'] = versionID; - data['channel'] = channel; - data['languageID'] = languageID; - data['logInTokenID'] = logInTokenID; - data['tokenID'] = tokenID; + data['VersionID'] = versionID; + data['Channel'] = channel; + data['LanguageID'] = languageID; + data['LogInTokenID'] = logInTokenID; + data['TokenID'] = tokenID; + data['SessionID'] = sessionID; return data; } diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart new file mode 100644 index 00000000..1eb3d9f4 --- /dev/null +++ b/lib/core/utils/date_util.dart @@ -0,0 +1,487 @@ +import 'package:device_calendar/device_calendar.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class DateUtil { + /// convert String To Date function + /// [date] String we want to convert + static DateTime convertStringToDate(String? date) { + if (date != null) { + const start = "/Date("; + const end = "+0300)"; + final startIndex = date.indexOf(start); + final endIndex = date.indexOf(end, startIndex + start.length); + return DateTime.fromMillisecondsSinceEpoch(int.parse( + date.substring(startIndex + start.length, endIndex), + )); + } else { + return DateTime.now(); + } + } + + static DateTime convertStringToDateSaudiTimezone(String date, int projectId) { + if (date != null) { + const start = "/Date("; + const end = "+0300)"; + final startIndex = date.indexOf(start); + final endIndex = date.indexOf(end, startIndex + start.length); + // if (projectId == 2 || projectId == 3) { + // return DateTime.fromMillisecondsSinceEpoch( + // int.parse( + // date.substring(startIndex + start.length, endIndex), + // ), + // isUtc: true) + // .add(Duration(hours: 4)); + // } else { + return DateTime.fromMillisecondsSinceEpoch( + int.parse( + date.substring(startIndex + start.length, endIndex), + ), + isUtc: true) + .add(Duration(hours: 3)); + // } + } else { + return DateTime.now(); + } + } + + static DateTime convertStringToDateNoTimeZone(String date) { + // /Date(1585774800000+0300)/ + if (date != null) { + const start = "/Date("; + const end = ")"; + final startIndex = date.indexOf(start); + final endIndex = date.indexOf(end, startIndex + start.length); + return DateTime.fromMillisecondsSinceEpoch( + int.parse( + date.substring(startIndex + start.length, endIndex), + ), + ); + } else { + return DateTime.now(); + } + } + + static DateTime convertStringToDateTime(String? date) { + if (date != null) { + try { + var dateT = date.split('/'); + var year = dateT[2].substring(0, 4); + var dateP = DateTime(int.parse(year), int.parse(dateT[1]), int.parse(dateT[0])); + return dateP; + } catch (e) { + print(e); + } + + return DateTime.now(); + } else { + return DateTime.now(); + } + } + + static String convertDateToString(DateTime date) { + const start = "/Date("; + const end = "+0300)/"; + int milliseconds = date.millisecondsSinceEpoch; + + return start + "$milliseconds" + end; + } + + static String convertDateToStringLocation(DateTime date) { + const start = "/Date("; + const end = ")/"; + int milliseconds = date.millisecondsSinceEpoch; + + return start + "$milliseconds" + end; + } + + static String convertTime(String timeStr) { + TimeOfDay time = TimeOfDay(hour: int.parse(timeStr.split(":")[0]), minute: int.parse(timeStr.split(":")[1])); // 24-hour format time + + int hour = time.hourOfPeriod; // get hour in 12-hour format + String meridiem = time.period == DayPeriod.am ? "AM" : "PM"; // get AM/PM + + String convertedTime = '$hour:${time.minute == 0 ? "00" : time.minute} $meridiem'; // create the new time string + + return convertedTime; + } + + static String formatDateToDate(DateTime date, bool isArabic) { + return DateFormat('dd MMM yyy', isArabic ? "ar_SA" : "en_US").format(date); + } + + static String formatDateToTime(DateTime date) { + return DateFormat('hh:mm a').format(date); + } + + static String yearMonthDay(DateTime dateTime) { + String dateFormat = '${dateTime.year}-${dateTime.month}-${dateTime.day}'; + return dateFormat; + } + + static String time(DateTime dateTime) { + String dateFormat = '${dateTime.hour}:${dateTime.minute}:00'; + return dateFormat; + } + + static String convertDateMSToJsonDate(utc) { + var dt = new DateTime.fromMicrosecondsSinceEpoch(utc); + + return "/Date(" + (dt.millisecondsSinceEpoch * 1000).toString() + '+0300' + ")/"; + } + + /// check Date + /// [dateString] String we want to convert + static String checkDate(DateTime checkedTime) { + DateTime currentTime = DateTime.now(); + if ((currentTime.year == checkedTime.year) && (currentTime.month == checkedTime.month) && (currentTime.day == checkedTime.day)) { + return "Today"; + } else if ((currentTime.year == checkedTime.year) && (currentTime.month == checkedTime.month)) { + if ((currentTime.day - checkedTime.day) == 1) { + return "YESTERDAY"; + } else if ((currentTime.day - checkedTime.day) == -1) { + return "Tomorrow"; + } + + if ((currentTime.day - checkedTime.day) <= -2) { + return "Next Week"; + } else { + return "Old Date"; + } + } + return "Old Date"; + } + + static String getDateFormatted(String date) { + DateTime dateObj = DateUtil.convertStringToDate(date); + return DateUtil.getWeekDay(dateObj.weekday) + ", " + dateObj.day.toString() + " " + DateUtil.getMonth(dateObj.month) + " " + dateObj.year.toString(); + } + + static String getISODateFormat(DateTime dateTime) { + // 2020-04-30T00:00:00.000 + return dateTime.toIso8601String(); + } + + /// get month by + /// [month] convert month number in to month name + static getMonth(int month) { + switch (month) { + case 1: + return "January"; + case 2: + return "February"; + case 3: + return "March"; + case 4: + return "April"; + case 5: + return "May"; + case 6: + return "June"; + case 7: + return "July"; + case 8: + return "August"; + case 9: + return "September"; + case 10: + return "October"; + case 11: + return "November"; + case 12: + return "December"; + } + } + + /// get month by + /// [month] convert month number in to month name in Arabic + static getMonthArabic(int month) { + switch (month) { + case 1: + return "يناير"; + case 2: + return " فبراير"; + case 3: + return "مارس"; + case 4: + return "أبريل"; + case 5: + return "مايو"; + case 6: + return "يونيو"; + case 7: + return "يوليو"; + case 8: + return "أغسطس"; + case 9: + return "سبتمبر"; + case 10: + return " اكتوبر"; + case 11: + return " نوفمبر"; + case 12: + return "ديسمبر"; + } + } + + static getMonthByName(String month) { + switch (month.toLowerCase()) { + case 'january': + return 1; + case 'february': + return 2; + case 'march': + return 3; + case 'april': + return 4; + case 'may': + return 5; + case 'june': + return 6; + case 'july': + return 7; + case 'august': + return 8; + case 'september': + return 9; + case 'october': + return 10; + case 'november': + return 11; + case 'december': + return 12; + } + } + + static getMonthDateTime(String month, yearName) { + DateTime? date; + try { + date = DateTime(int.parse(yearName), getMonthByName(month)); + } catch (e) { + print(e); + } + return date ?? DateTime.now(); + } + + /// get month by + /// [weekDay] convert week day in int to week day name + static getWeekDay(int weekDay) { + switch (weekDay) { + case 1: + return "Monday"; + case 2: + return "Tuesday"; + case 3: + return "Wednesday"; + case 4: + return "Thursday"; + case 5: + return "Friday"; + case 6: + return "Saturday "; + case 7: + return "Sunday"; + } + } + + /// get month by + /// [weekDay] convert week day in int to week day name arabic + static getWeekDayArabic(int weekDay) { + switch (weekDay) { + case 1: + return "الاثنين"; + case 2: + return "الثلاثاء"; + case 3: + return "الاربعاء"; + case 4: + return "الخميس"; + case 5: + return "الجمعه"; + case 6: + return "السبت "; + case 7: + return "الاحد"; + } + } + + static getWeekDayEnglish(int weekDay) { + switch (weekDay) { + case 1: + return "Monday"; + case 2: + return "Tuesday"; + case 3: + return "Wednesday"; + case 4: + return "Thursday"; + case 5: + return "Friday"; + case 6: + return "Saturday "; + case 7: + return "Sunday"; + } + } + + /// get data formatted like Apr 26,2020 + /// [dateTime] convert DateTime to data formatted + static String getMonthDayYearDateFormatted(DateTime dateTime) { + if (dateTime != null) { + return getMonth(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString(); + } else { + return ""; + } + } + + /// get data formatted like Apr 26,2020 + /// [dateTime] convert DateTime to data formatted Arabic + static String getMonthDayYearDateFormattedAr(DateTime dateTime) { + if (dateTime != null) { + return getMonthArabic(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString(); + } else { + return ""; + } + } + + /// get data formatted like Thursday, Apr 26,2020 + /// [dateTime] convert DateTime to date formatted + static String getWeekDayMonthDayYearDateFormatted(DateTime dateTime, String lang) { + if (dateTime != null) { + return lang == 'en' + ? getWeekDayEnglish(dateTime.weekday) + ", " + getMonth(dateTime.month) + " " + dateTime.day.toString() + " " + dateTime.year.toString() + : getWeekDayArabic(dateTime.weekday) + ", " + dateTime.day.toString() + " " + getMonthArabic(dateTime.month) + " " + dateTime.year.toString(); + } else { + return ""; + } + } + + static String getMonthDayYearLangDateFormatted(DateTime dateTime, String lang) { + if (dateTime != null) { + return lang == 'en' + ? getMonth(dateTime.month) + " " + dateTime.day.toString() + " " + dateTime.year.toString() + : dateTime.day.toString() + " " + getMonthArabic(dateTime.month) + " " + dateTime.year.toString(); + } else { + return ""; + } + } + + /// get data formatted like 26/4/2020 + static String getDayMonthYearLangDateFormatted(DateTime dateTime, String lang) { + if (dateTime != null) { + return lang == 'en' + ? dateTime.day.toString() + " " + getMonth(dateTime.month) + " " + dateTime.year.toString() + : dateTime.day.toString() + " " + getMonthArabic(dateTime.month) + " " + dateTime.year.toString(); + } else { + return ""; + } + } + + static String getMonthYearLangDateFormatted(DateTime dateTime, String lang) { + if (dateTime != null) { + return lang == 'en' ? getMonth(dateTime.month) + " " + dateTime.year.toString() : getMonthArabic(dateTime.month) + " " + dateTime.year.toString(); + } else { + return ""; + } + } + + /// get data formatted like 26/4/2020 + /// [dateTime] convert DateTime to data formatted + static String getDayMonthYearDateFormatted(DateTime dateTime) { + if (dateTime != null) { + return dateTime.day.toString() + "/" + dateTime.month.toString() + "/" + dateTime.year.toString(); + } else { + return ""; + } + } + + /// get data formatted like 26/4/2020 + /// [dateTime] convert DateTime to data formatted + static String getDayMonthDateFormatted(DateTime dateTime) { + if (dateTime != null) { + return DateFormat('dd/MM').format(dateTime); + } else { + return ""; + } + } + + /// get data formatted like 26/4/2020 + /// [dateTime] convert DateTime to data formatted according to language + static String getDayMonthYearDateFormattedLang(DateTime dateTime, bool isArabic) { + if (dateTime != null) { + return DateFormat('dd/MM/yyyy', isArabic ? "ar_SA" : "en_US").format(dateTime); + } else { + return ""; + } + } + + /// get data formatted like 10:30 according to lang + static String formatDateToTimeLang(DateTime date, bool isArabic) { + return DateFormat('HH:mm', isArabic ? "ar_SA" : "en_US").format(date); + } + + /// get data formatted like 26/4/2020 10:30 + /// [dateTime] convert DateTime to data formatted + static String getDayMonthYearHourMinuteDateFormatted(DateTime dateTime) { + if (dateTime != null) { + return dateTime.day.toString() + "/" + dateTime.month.toString() + "/" + dateTime.year.toString() + " " + DateFormat('HH:mm').format(dateTime); + } else { + return ""; + } + } + + /// get data formatted like 2020-8-13 09:43:00 + /// [dateTime] convert DateTime to data formatted + static String getYearMonthDayHourMinSecDateFormatted(DateTime dateTime) { + if (dateTime != null) { + return dateTime.year.toString() + + "-" + + dateTime.month.toString() + + "-" + + dateTime.day.toString() + + " " + + dateTime.hour.toString() + + ":" + + dateTime.minute.toString() + + ":" + + dateTime.second.toString(); + } else { + return ""; + } + } + + static String getFormattedDate(DateTime dateTime, String formattedString) { + return DateFormat(formattedString).format(dateTime); + } + + static convertISODateToJsonDate(String isoDate) { + return "/Date(" + DateFormat('mm-dd-yyy').parse(isoDate).millisecondsSinceEpoch.toString() + ")/"; + } + + static String getDay(DayOfWeek dayOfWeek) { + switch (dayOfWeek) { + case DayOfWeek.Monday: + return "Monday"; + break; + case DayOfWeek.Tuesday: + return "Tuesday"; + break; + case DayOfWeek.Wednesday: + return "Wednesday"; + break; + case DayOfWeek.Thursday: + return "Thursday"; + break; + case DayOfWeek.Friday: + return "Friday"; + break; + case DayOfWeek.Saturday: + return "Saturday"; + break; + case DayOfWeek.Sunday: + return "Sunday"; + break; + } + return ""; + } +} diff --git a/lib/core/utils/size_config.dart b/lib/core/utils/size_config.dart index 65ff3741..9c09b095 100644 --- a/lib/core/utils/size_config.dart +++ b/lib/core/utils/size_config.dart @@ -1,4 +1,5 @@ import 'package:flutter/cupertino.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/consts.dart'; class SizeConfig { diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 2df35f14..4f7b398c 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -1,3 +1,8 @@ +import 'dart:convert'; + +import 'package:crypto/crypto.dart' as crypto; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:google_api_availability/google_api_availability.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -454,4 +459,39 @@ class Utils { } + + + static Future isGoogleServicesAvailable() async { + GooglePlayServicesAvailability availability = await GoogleApiAvailability + .instance + .checkGooglePlayServicesAvailability(); + String status = availability.toString().split('.').last; + if (status == "success") { + return true; + } + return false; + } + + + + + + static Future checkConnection( + {bool bypassConnectionCheck = false}) async { + if (bypassConnectionCheck) return true; + List connectivityResult = + await (Connectivity().checkConnectivity()); + if (connectivityResult.contains(ConnectivityResult.mobile) || + connectivityResult.contains(ConnectivityResult.wifi)) { + return true; + } else { + return false; + } + } + + + static String generateMd5Hash(String input) { + return crypto.md5.convert(utf8.encode(input)).toString(); + } + } diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index a5e9ae33..8122a522 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -1,5 +1,8 @@ +import 'dart:convert'; + import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/authentication/models/select_device_by_imei.dart'; @@ -7,7 +10,7 @@ import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:http/http.dart'; abstract class AuthenticationRepo { - Future> selectDeviceByImei({required String deviceIMEI}); + Future> selectDeviceByImei({required String firebaseToken}); } class AuthenticationRepoImp implements AuthenticationRepo { @@ -17,23 +20,22 @@ class AuthenticationRepoImp implements AuthenticationRepo { AuthenticationRepoImp({required this.loggerService, required this.apiClient}); @override - Future> selectDeviceByImei({required String deviceIMEI}) async { - final mapDevice = {"": deviceIMEI}; + Future> selectDeviceByImei({required String firebaseToken}) async { + final mapDevice = {"IMEI": firebaseToken}; - final String apiUrl = "https://hmgwebservices.com/Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI" ; try { - final Response result = await apiClient.postJsonForResponse(apiUrl, mapDevice); + final Response result = await apiClient.postJsonForResponse(ApiConsts.selectDeviceImei, mapDevice); + final SelectDeviceByImeiRespModelElement selectDeviceByImeiRespModelElement = SelectDeviceByImeiRespModelElement.fromJson(jsonDecode(result.body)['Patient_SELECTDeviceIMEIbyIMEIList'][0]); + loggerService.logInfo(result.body); + if (result !=null) { - return Right(null); + return Right(selectDeviceByImeiRespModelElement); } else { loggerService.errorLogs(result.toString()); return Left(ServerFailure(result.toString())); } } on APIException catch (e) { - APIError? apiError; - if (e.error is APIError) { - apiError = e.error as APIError; - } + loggerService.errorLogs(e.toString()); return Left(ServerFailure(apiError?.errorMessage ?? e.message)); } catch (e) { diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 5b457901..8269c72c 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1,44 +1,44 @@ +import 'dart:developer'; +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/check_activation_code_request_register.dart'; class AuthenticationViewModel extends ChangeNotifier { AuthenticationRepo authenticationRepo; + AppState appState; - AuthenticationViewModel({required this.authenticationRepo}); + AuthenticationViewModel({ + required this.appState, + required this.authenticationRepo, + }); + final TextEditingController nationalIdController = TextEditingController(); + final TextEditingController phoneNumberController = TextEditingController(); - - Future signUp({ - required String phone, - required String password, + Future selectDeviceImei({ Function(dynamic)? onSuccess, - Function(String)? onError, + Function(String)? onError }) async { - Utils.showLoading(); - final String deviceIMEI = + final String firebaseToken = "cIkkB7h7Q7uoFkC4Qv82xG:APA91bEb53Z9XzqymCIctaLxCoMX6bm9fuKlWILQ59uUqfwhCoD42AOP1-jWGB1WYd9BVN5PT2pUUFxrT07vcNg1KH9OH39mrPgCl0m21XVIgWrzNnCkufg"; - final resultEither = await authenticationRepo.selectDeviceByImei(deviceIMEI: deviceIMEI); - - if (resultEither.isLeft()) { - - } - - if (resultEither.isRight()) { - - - } - + final resultEither = + await authenticationRepo.selectDeviceByImei(firebaseToken: firebaseToken); resultEither.fold( (failure) { - Utils.hideLoading(); notifyListeners(); if (onError != null) onError(failure.message); }, (data) { + log("resultEither: ${data.toString()} "); + notifyListeners(); if (onSuccess != null) onSuccess(data); }, diff --git a/lib/features/authentication/models/authenticated_user_model.dart b/lib/features/authentication/models/authenticated_user_model.dart new file mode 100644 index 00000000..49987f3a --- /dev/null +++ b/lib/features/authentication/models/authenticated_user_model.dart @@ -0,0 +1,292 @@ +import 'package:hmg_patient_app/uitl/date_uitl.dart'; + +class AuthenticatedUser { + String? setupID; + int? patientType; + int? patientID; + String? firstName; + String? middleName; + String? lastName; + String? firstNameN; + String? middleNameN; + String? lastNameN; + int? relationshipID; + int? gender; + String? dateofBirth; + DateTime? dateofBirthDataTime; + dynamic dateofBirthN; + String? nationalityID; + dynamic phoneResi; + dynamic phoneOffice; + String? mobileNumber; + dynamic faxNumber; + String? emailAddress; + dynamic bloodGroup; + dynamic rHFactor; + bool? isEmailAlertRequired; + bool? isSMSAlertRequired; + String? preferredLanguage; + bool? isPrivilegedMember; + dynamic memberID; + dynamic expiryDate; + dynamic isHmgEmployee; + dynamic employeeID; + dynamic emergencyContactName; + dynamic emergencyContactNo; + int? patientPayType; + dynamic dHCCPatientRefID; + bool? isPatientDummy; + int? status; + dynamic isStatusCleared; + int? patientIdentificationType; + String? patientIdentificationNo; + int? projectID; + int? infoSourceID; + dynamic address; + int? age; + String? ageDesc; + int? areaID; + int? createdBy; + String? genderDescription; + dynamic iR; + dynamic iSOCityID; + dynamic iSOCountryID; + List? listPrivilege; + dynamic marital; + int? outSA; + dynamic pOBox; + bool? receiveHealthSummaryReport; + int? sourceType; + dynamic strDateofBirth; + dynamic tempAddress; + dynamic zipCode; + dynamic isFamily; + dynamic cRSVerificationStatus; + // dynamic patientPayType; + // dynamic patientType; + // dynamic status; + + AuthenticatedUser( + {this.setupID, + this.patientType, + this.patientID, + this.firstName, + this.middleName, + this.lastName, + this.firstNameN, + this.middleNameN, + this.lastNameN, + this.relationshipID, + this.gender, + this.dateofBirth, + this.dateofBirthN, + this.nationalityID, + this.phoneResi, + this.phoneOffice, + this.mobileNumber, + this.faxNumber, + this.emailAddress, + this.bloodGroup, + this.rHFactor, + this.isEmailAlertRequired, + this.isSMSAlertRequired, + this.preferredLanguage, + this.isPrivilegedMember, + this.memberID, + this.expiryDate, + this.isHmgEmployee, + this.employeeID, + this.emergencyContactName, + this.emergencyContactNo, + this.patientPayType, + this.dHCCPatientRefID, + this.isPatientDummy, + this.status, + this.isStatusCleared, + this.patientIdentificationType, + this.patientIdentificationNo, + this.projectID, + this.infoSourceID, + this.address, + this.age, + this.ageDesc, + this.areaID, + this.createdBy, + this.genderDescription, + this.iR, + this.iSOCityID, + this.iSOCountryID, + this.listPrivilege, + this.marital, + this.outSA, + this.pOBox, + this.receiveHealthSummaryReport, + this.sourceType, + this.strDateofBirth, + this.tempAddress, + this.zipCode, + this.isFamily, + this.cRSVerificationStatus}); + + AuthenticatedUser.fromJson(Map json) { + setupID = json['SetupID']; + patientType = json['PatientType']; + patientID = json['PatientID']; + firstName = json['FirstName']; + middleName = json['MiddleName']; + lastName = json['LastName']; + firstNameN = json['FirstNameN']; + middleNameN = json['MiddleNameN']; + lastNameN = json['LastNameN']; + relationshipID = json['RelationshipID']; + gender = json['Gender']; + dateofBirth = json['DateofBirth']; + dateofBirthDataTime = DateUtil.convertStringToDate(json['DateofBirth']); + dateofBirthN = json['DateofBirthN']; + nationalityID = json['NationalityID']; + phoneResi = json['PhoneResi']; + phoneOffice = json['PhoneOffice']; + mobileNumber = json['MobileNumber']; + faxNumber = json['FaxNumber']; + emailAddress = json['EmailAddress']; + bloodGroup = json['BloodGroup']; + rHFactor = json['RHFactor']; + isEmailAlertRequired = json['IsEmailAlertRequired']; + isSMSAlertRequired = json['IsSMSAlertRequired']; + preferredLanguage = json['PreferredLanguage']; + isPrivilegedMember = json['IsPrivilegedMember']; + memberID = json['MemberID']; + expiryDate = json['ExpiryDate']; + isHmgEmployee = json['IsHmgEmployee']; + employeeID = json['EmployeeID']; + emergencyContactName = json['EmergencyContactName']; + emergencyContactNo = json['EmergencyContactNo']; + patientPayType = json['PatientPayType']; + dHCCPatientRefID = json['DHCCPatientRefID']; + isPatientDummy = json['IsPatientDummy']; + status = json['Status']; + isStatusCleared = json['IsStatusCleared']; + patientIdentificationType = json['PatientIdentificationType']; + patientIdentificationNo = json['PatientIdentificationNo']; + projectID = json['ProjectID']; + infoSourceID = json['InfoSourceID']; + address = json['Address']; + age = json['Age']; + ageDesc = json['AgeDesc']; + areaID = json['AreaID']; + createdBy = json['CreatedBy']; + genderDescription = json['GenderDescription']; + iR = json['IR']; + iSOCityID = json['ISOCityID']; + iSOCountryID = json['ISOCountryID']; + if (json['ListPrivilege'] != null) { + listPrivilege = []; + json['ListPrivilege'].forEach((v) { + listPrivilege!.add(new ListPrivilege.fromJson(v)); + }); + } + marital = json['Marital']; + outSA = json['OutSA']; + pOBox = json['POBox']; + receiveHealthSummaryReport = json['ReceiveHealthSummaryReport']; + sourceType = json['SourceType']; + strDateofBirth = json['StrDateofBirth']; + tempAddress = json['TempAddress']; + zipCode = json['ZipCode']; + isFamily = json['IsFamily']; + cRSVerificationStatus = json['CRSVerificationStatus']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['PatientType'] = this.patientType; + data['PatientID'] = this.patientID; + data['FirstName'] = this.firstName; + data['MiddleName'] = this.middleName; + data['LastName'] = this.lastName; + data['FirstNameN'] = this.firstNameN; + data['MiddleNameN'] = this.middleNameN; + data['LastNameN'] = this.lastNameN; + data['RelationshipID'] = this.relationshipID; + data['Gender'] = this.gender; + data['DateofBirth'] = this.dateofBirth; + data['DateofBirthN'] = this.dateofBirthN; + data['NationalityID'] = this.nationalityID; + data['PhoneResi'] = this.phoneResi; + data['PhoneOffice'] = this.phoneOffice; + data['MobileNumber'] = this.mobileNumber; + data['FaxNumber'] = this.faxNumber; + data['EmailAddress'] = this.emailAddress; + data['BloodGroup'] = this.bloodGroup; + data['RHFactor'] = this.rHFactor; + data['IsEmailAlertRequired'] = this.isEmailAlertRequired; + data['IsSMSAlertRequired'] = this.isSMSAlertRequired; + data['PreferredLanguage'] = this.preferredLanguage; + data['IsPrivilegedMember'] = this.isPrivilegedMember; + data['MemberID'] = this.memberID; + data['ExpiryDate'] = this.expiryDate; + data['IsHmgEmployee'] = this.isHmgEmployee; + data['EmployeeID'] = this.employeeID; + data['EmergencyContactName'] = this.emergencyContactName; + data['EmergencyContactNo'] = this.emergencyContactNo; + data['PatientPayType'] = this.patientPayType; + data['DHCCPatientRefID'] = this.dHCCPatientRefID; + data['IsPatientDummy'] = this.isPatientDummy; + data['Status'] = this.status; + data['IsStatusCleared'] = this.isStatusCleared; + data['PatientIdentificationType'] = this.patientIdentificationType; + data['PatientIdentificationNo'] = this.patientIdentificationNo; + data['ProjectID'] = this.projectID; + data['InfoSourceID'] = this.infoSourceID; + data['Address'] = this.address; + data['Age'] = this.age; + data['AgeDesc'] = this.ageDesc; + data['AreaID'] = this.areaID; + data['CreatedBy'] = this.createdBy; + data['GenderDescription'] = this.genderDescription; + data['IR'] = this.iR; + data['ISOCityID'] = this.iSOCityID; + data['ISOCountryID'] = this.iSOCountryID; + if (this.listPrivilege != null) { + data['ListPrivilege'] = + this.listPrivilege!.map((v) => v.toJson()).toList(); + } + data['Marital'] = this.marital; + data['OutSA'] = this.outSA; + data['POBox'] = this.pOBox; + data['ReceiveHealthSummaryReport'] = this.receiveHealthSummaryReport; + data['SourceType'] = this.sourceType; + data['StrDateofBirth'] = this.strDateofBirth; + data['TempAddress'] = this.tempAddress; + data['ZipCode'] = this.zipCode; + data['IsFamily'] = this.isFamily; + data['CRSVerificationStatus'] = this.cRSVerificationStatus; + return data; + } +} + +class ListPrivilege { + int? iD; + String? serviceName; + bool? previlege; + dynamic region; + + ListPrivilege({this.iD, this.serviceName, this.previlege, this.region}); + + ListPrivilege.fromJson(Map json) { + iD = json['ID']; + serviceName = json['ServiceName']; + previlege = json['Previlege']; + region = json['Region']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['ServiceName'] = this.serviceName; + data['Previlege'] = this.previlege; + data['Region'] = this.region; + return data; + } +} diff --git a/lib/features/authentication/models/check_activation_code_request_register.dart b/lib/features/authentication/models/check_activation_code_request_register.dart new file mode 100644 index 00000000..85e1f56f --- /dev/null +++ b/lib/features/authentication/models/check_activation_code_request_register.dart @@ -0,0 +1,121 @@ +class CheckActivationCodeRegisterReq { + int? patientMobileNumber; + String? mobileNo; + String? deviceToken; + bool? projectOutSA; + int? loginType; + String? zipCode; + bool? isRegister; + String? logInTokenID; + int? searchType; + int? patientID; + String? nationalID; + String? patientIdentificationID; + String? activationCode; + bool? isSilentLogin; + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + int? patientOutSA; + dynamic sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + bool? forRegisteration; + String? dob; + int? isHijri; + String? healthId; + + CheckActivationCodeRegisterReq({ + this.patientMobileNumber, + this.mobileNo, + this.deviceToken, + this.projectOutSA, + this.loginType, + this.zipCode, + this.isRegister, + this.logInTokenID, + this.searchType, + this.patientID, + this.nationalID, + this.patientIdentificationID, + this.activationCode, + this.isSilentLogin, + this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.patientOutSA, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID, + this.forRegisteration, + this.dob, + this.isHijri, + this.healthId, + }); + + CheckActivationCodeRegisterReq.fromJson(Map json) { + patientMobileNumber = json['PatientMobileNumber']; + mobileNo = json['MobileNo']; + deviceToken = json['DeviceToken']; + projectOutSA = json['ProjectOutSA']; + loginType = json['LoginType']; + zipCode = json['ZipCode']; + isRegister = json['isRegister']; + logInTokenID = json['LogInTokenID']; + searchType = json['SearchType']; + patientID = json['PatientID']; + nationalID = json['NationalID']; + patientIdentificationID = json['PatientIdentificationID']; + activationCode = json['activationCode']; + isSilentLogin = json['IsSilentLogin']; + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + patientOutSA = json['PatientOutSA']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + forRegisteration = json['ForRegisteration']; + dob = json['DOB']; + isHijri = json['IsHijri']; + healthId = json['HealthId']; + } + + Map toJson() { + final Map data = new Map(); + data['PatientMobileNumber'] = this.patientMobileNumber; + data['MobileNo'] = this.mobileNo; + data['DeviceToken'] = this.deviceToken; + data['ProjectOutSA'] = this.projectOutSA; + data['LoginType'] = this.loginType; + data['ZipCode'] = this.zipCode; + data['isRegister'] = this.isRegister; + data['LogInTokenID'] = this.logInTokenID; + data['SearchType'] = this.searchType; + data['PatientID'] = this.patientID; + data['NationalID'] = this.nationalID; + data['PatientIdentificationID'] = this.patientIdentificationID; + data['activationCode'] = this.activationCode; + data['IsSilentLogin'] = this.isSilentLogin; + data['VersionID'] = this.versionID; + data['Channel'] = this.channel; + data['LanguageID'] = this.languageID; + data['IPAdress'] = this.iPAdress; + data['generalid'] = this.generalid; + data['PatientOutSA'] = this.patientOutSA; + data['SessionID'] = this.sessionID; + data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; + data['DeviceTypeID'] = this.deviceTypeID; + data['ForRegisteration'] = this.forRegisteration; + data['DOB'] = dob; + data['IsHijri'] = isHijri; + data['HealthId'] = healthId; + return data; + } +} diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index b994b35e..5b0bf107 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -1,6 +1,5 @@ import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; -import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -41,13 +40,6 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { loggerService.errorLogs(result.toString()); return Left(ServerFailure(result.toString())); } - } on APIException catch (e) { - APIError? apiError; - if (e.error is APIError) { - apiError = e.error as APIError; - } - loggerService.errorLogs(e.toString()); - return Left(ServerFailure(apiError?.errorMessage ?? e.message)); } catch (e) { loggerService.errorLogs(e.toString()); return Left(ServerFailure(e.toString())); diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 7ac0c47a..28b9b383 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -63,13 +63,6 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { loggerService.errorLogs(result.toString()); return Left(ServerFailure(result.toString())); } - } on APIException catch (e) { - APIError? apiError; - if (e.error is APIError) { - apiError = e.error as APIError; - } - loggerService.errorLogs(e.toString()); - return Left(ServerFailure(apiError?.errorMessage ?? e.message)); } catch (e) { loggerService.errorLogs(e.toString()); return Left(ServerFailure(e.toString())); diff --git a/lib/main.dart b/lib/main.dart index 8d0f7fa5..446f8daf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -45,7 +45,7 @@ Future callInitializations() async { } void main() async { - callInitializations(); + await callInitializations(); runApp( EasyLocalization( supportedLocales: const [ @@ -56,7 +56,7 @@ void main() async { fallbackLocale: Locale('en', 'US'), child: MultiProvider(providers: [ ChangeNotifierProvider( - create: (_) => AuthenticationViewModel(authenticationRepo: getIt()), + create: (_) => AuthenticationViewModel(authenticationRepo: getIt(), appState: getIt()), ), ], child: MyApp()), ), diff --git a/lib/presentation/authantication/login.dart b/lib/presentation/authentication/login.dart similarity index 92% rename from lib/presentation/authantication/login.dart rename to lib/presentation/authentication/login.dart index 150d4464..2699a91a 100644 --- a/lib/presentation/authantication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -6,29 +6,16 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; -import 'package:hmg_patient_app_new/widgets/language_switcher.dart'; -import 'package:sizer/sizer.dart'; // Import sizer +import 'package:sizer/sizer.dart'; -class LoginScreen extends StatefulWidget { - @override - _LoginScreen createState() => _LoginScreen(); -} - -class _LoginScreen extends State { - @override - void initState() { - super.initState(); - } - - @override - void dispose() { - super.dispose(); - } +class LoginScreen extends StatelessWidget { + const LoginScreen({super.key}); @override Widget build(BuildContext context) { @@ -37,7 +24,6 @@ class _LoginScreen extends State { return Scaffold( appBar: CustomAppBar( onBackPressed: () { - // Navigator.of(context).pop(); }, onLanguageChanged: (String value) { print(value); diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 9163a7e4..d371213a 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:provider/provider.dart'; class LandingPage extends StatefulWidget { const LandingPage({super.key}); @@ -10,16 +11,22 @@ class LandingPage extends StatefulWidget { } class _LandingPageState extends State { - late AuthenticationViewModel authenticationViewModel; @override Widget build(BuildContext context) { + AuthenticationViewModel authenticationViewModel = context.read();; + return Scaffold( body: Column( children: [ - Padding( - padding: const EdgeInsets.all(64.0), - child: "Login".toText18(color: Colors.black), + InkWell( + onTap: () { + authenticationViewModel.selectDeviceImei(); + }, + child: Padding( + padding: const EdgeInsets.all(64.0), + child: "Login".toText18(color: Colors.black), + ), ), ], ), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 8aafc82d..d16cc62d 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/presentation/authantication/login.dart'; +import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; class AppRoutes { @@ -10,5 +10,4 @@ class AppRoutes { initialRoute: (context) => SplashPage(), login: (context) => LoginScreen(), }; - static Map get routes => {initialRoute: (context) => SplashPage()}; } diff --git a/lib/services/analytics/analytics_service.dart b/lib/services/analytics/analytics_service.dart new file mode 100644 index 00000000..d4f83a4a --- /dev/null +++ b/lib/services/analytics/analytics_service.dart @@ -0,0 +1,143 @@ + +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/authenticated_user_model.dart'; +import 'package:hmg_patient_app_new/services/analytics/flows/advance_payments.dart'; +import 'package:hmg_patient_app_new/services/analytics/flows/app_nav.dart'; +import 'package:hmg_patient_app_new/services/analytics/flows/appointments.dart'; +import 'package:hmg_patient_app_new/services/analytics/flows/error_tracking.dart'; +import 'package:hmg_patient_app_new/services/analytics/flows/hamburger_menu.dart'; +import 'package:hmg_patient_app_new/services/analytics/flows/hmg_services.dart'; +import 'package:hmg_patient_app_new/services/analytics/flows/live_care.dart'; +import 'package:hmg_patient_app_new/services/analytics/flows/login_registration.dart'; +import 'package:hmg_patient_app_new/services/analytics/flows/offers_promotions.dart'; +import 'package:hmg_patient_app_new/services/analytics/flows/todo_list.dart'; +import 'package:http/http.dart' as AnalyticEvents; + + +typedef GALogger = Function(String name, {Map parameters}); + +var _analytics = FirebaseAnalytics.instance; + +_logger(String name, {Map? parameters}) async { + // return; + if (name.isNotEmpty) { + if (name.contains(' ')) name = name.replaceAll(' ', '_'); + + // To LowerCase + if (parameters != null && parameters.isNotEmpty) { + parameters = parameters.map((key, value) { + final key_ = key.toLowerCase(); + var value_ = value; + if (value is String) value_ = value.toLowerCase(); + return MapEntry(key_, value_); + }); + } + + try { + final safeParameters = parameters?.map((key, value) => MapEntry(key, value as Object)); + await _analytics.logEvent( + name: name.trim().toLowerCase(), + parameters: safeParameters, + ); + } catch (e) { + print('Error sending analytics event: $e'); + } + + } +} + +class GAnalytics { + static String? TREATMENT_TYPE; + static String? APPOINTMENT_DETAIL_FLOW_TYPE; + static String? PAYMENT_TYPE; + + setUser(AuthenticatedUser user) async { + try { + _analytics.setUserProperty(name: 'user_language', value: user.preferredLanguage == '1' ? 'arabic' : 'english'); + _analytics.setUserProperty(name: 'userid', value: Utils.generateMd5Hash(user.emailAddress!)); + _analytics.setUserProperty(name: 'login_status', value: user == null ? 'guest' : 'loggedin'); + // if (await PermissionService.isLocationEnabled()) { + // final location = await Geolocator.getCurrentPosition(); + // if (location != null && !location.isMocked) { + // final places = await placemarkFromCoordinates(location.latitude, location.longitude,); + // final countryCode = places.first.isoCountryCode; + // _analytics.setUserProperty(name: 'user_country', value: countryCode); + // } + // } else { + // _analytics.setUserProperty(name: 'user_country', value: "N/A"); + // } + } catch (e) {} + } + + // NavObserver navObserver() => NavObserver(); + final hamburgerMenu = HamburgerMenu(_logger); + final bottomTabNavigation = AppNav(_logger); + final hmgServices = HMGServices(_logger); + final loginRegistration = LoginRegistration(_logger); + final appointment = Appointment(_logger); + final liveCare = LiveCare(_logger); + final todoList = TodoList(_logger); + final advancePayments = AdvancePayments(_logger); + final offerPackages = OfferAndPromotion(_logger); + final errorTracking = ErrorTracking(_logger); +} + +// // adb shell setprop debug.firebase.analytics.app com.ejada.hmg -> Android +// class NavObserver extends RouteObserver> { +// _sendScreenView(PageRoute route) async { +// log(String className) { +// var event = AnalyticEvents.get(className); +// if (event.active != null) { +// _analytics.setCurrentScreen(screenName: event.flutterName(), screenClassOverride: className).catchError( +// (Object error) { +// print('$FirebaseAnalyticsObserver: $error'); +// }, +// test: (Object error) { +// return error is PlatformException; +// }, +// ); +// } +// } +// +// if (route.settings.name != null && route.settings.name!.isNotEmpty && route.settings.name != "null") { +// var class_ = routes[route.settings.name]!(0); +// if (class_ != null) log(class_.toStringShort()); +// } else if (route is FadePage) { +// var class_ = route.page; +// if (class_ != null) log(class_.toStringShort()); +// } else if (route is MaterialPageRoute) { +// var class_ = route.builder(route.subtreeContext!); +// log(class_.toStringShort()); +// } else { +// print(""); +// } +// } +// +// @override +// void didPush(Route route, Route? previousRoute) { +// super.didPush(route, previousRoute); +// if (route is PageRoute) { +// _sendScreenView(route); +// } +// } +// +// @override +// void didReplace({Route? newRoute, Route? oldRoute}) { +// super.didReplace(newRoute: newRoute, oldRoute: oldRoute); +// if (newRoute is PageRoute) { +// _sendScreenView(newRoute); +// } +// } +// +// @override +// void didPop(Route route, Route? previousRoute) { +// super.didPop(route, previousRoute); +// // if (previousRoute is PageRoute && route is PageRoute) { +// // _sendScreenView(previousRoute); +// // } +// } +// } diff --git a/lib/services/analytics/flows/advance_payments.dart b/lib/services/analytics/flows/advance_payments.dart new file mode 100644 index 00000000..9c8baa14 --- /dev/null +++ b/lib/services/analytics/flows/advance_payments.dart @@ -0,0 +1,91 @@ +import 'package:flutter/cupertino.dart'; +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; + +class AdvancePayments{ + + final GALogger logger; + AdvancePayments(this.logger); + + // R038 + payment_services({required String service_type}){ + logger('payment_services', parameters: { + 'service_type' : service_type + }); + } + + // R039 + wallet_recharge({required String service_type}){ + logger('wallet_recharge', parameters: { + 'service_type' : service_type + }); + } + + // R040 + wallet_payment_details(){ + logger('wallet_payment_details'); + } + + // R041 + payment_method({required String method,type}){ + logger('payment_method', parameters: { + 'payment_method' : method, + 'payment_type' : type + }); + } + + // R042 + payment_confirm({required String method,type}){ + logger('payment_confirm', parameters: { + 'payment_method' : method, + 'payment_type' : type + }); + } + + // R043 + payment_otp_confirmation({required String method,type}){ + logger('payment_otp_confirmation', parameters: { + 'payment_method' : method, + 'payment_type' : type + }); + } + + // R044 + payment_confirm_card_details({required String method,type}){ + logger('payment_confirm_card_details', parameters: { + 'payment_method' : method, + 'payment_type' : type + }); + } + + // R045 + payment_pay({required String method,type}){ + logger('payment_pay', parameters: { + 'payment_method' : method, + 'payment_type' : type + }); + } + + // R046 + payment_success({required String hospital, payment_method, payment_type, txn_number, txn_amount, txn_currency}){ + logger('payment_success', parameters: { + 'payment_method' : payment_method, + 'payment_type' : payment_type, + 'hospital_name' : hospital, + 'transaction_number' : txn_number, + 'transaction_amount' : txn_amount, + 'transaction_currency' : txn_currency + }); + } + + payment_fail({required String hospital, payment_method, payment_type, txn_amount, txn_currency, error_type}){ + logger('payment_fail', parameters: { + 'payment_method' : payment_method, + 'payment_type' : payment_type, + 'hospital_name' : hospital, + 'transaction_amount' : txn_amount, + 'transaction_currency' : txn_currency, + 'error_type' : error_type + }); + } + +} \ No newline at end of file diff --git a/lib/services/analytics/flows/app_nav.dart b/lib/services/analytics/flows/app_nav.dart new file mode 100644 index 00000000..bd9186c9 --- /dev/null +++ b/lib/services/analytics/flows/app_nav.dart @@ -0,0 +1,30 @@ +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; + +class AppNav{ + final name = 'app_nav'; + final GALogger logger; + AppNav(this.logger); + + logNavName(String value){ + logger(name, parameters: { + 'nav_name' : value + }); + } + + log({int? tabIndex, bool? isLoggedIn}){ + var nav_name = ""; + if(tabIndex == 1) + nav_name = "medical file"; + if(tabIndex == 3) + nav_name = "my family"; + if(tabIndex == 4) + nav_name = "todo list"; + if(tabIndex == 5) + nav_name = "help"; + + if(nav_name.isNotEmpty) + logger(name, parameters: { + 'nav_name' : nav_name + }); + } +} \ No newline at end of file diff --git a/lib/services/analytics/flows/appointments.dart b/lib/services/analytics/flows/appointments.dart new file mode 100644 index 00000000..57c45e6b --- /dev/null +++ b/lib/services/analytics/flows/appointments.dart @@ -0,0 +1,268 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; +import 'package:intl/intl.dart'; + +class Appointment { + final GALogger logger; + + Appointment(this.logger); + + // R015 + book_appointment() { + logger('book_appointment'); + } + + // R016.1, R017.2 + book_appointment_by_clinic() { + logger('book_appointment_by_clinic'); + } + + // R016.2, R018.2 + book_appointment_by_doctor() { + logger('book_appointment_by_doctor'); + } + +// // R017.1 +// book_appointment_doctor_search({required String query}) { +// // doctor_search_query : value +// logger('book_appointment_doctor_search', parameters: {'doctor_search_query': query}); +// } +// +// // R018.1 +// book_appointment_select_clinic({required String appointment_type, clinic}) { +// // appointment_type: regular | livecare +// // clinic_type : $clinic_type +// logger('book_appointment_select_clinic', parameters: {'appointment_type': appointment_type, 'clinic_type': clinic}); +// } +// +// // R019.1 +// book_appointment_livecare_accept() { +// logger('book_appointment_livecare_accept'); +// } +// +// // R019.2 +// book_appointment_livecare_decline() { +// logger('book_appointment_livecare_decline'); +// } +// +// // R020 +// book_appointment_chief_complaints({ +// required String appointment_type, +// ListClinicCentralized? clinic, +// HospitalsModel? hospital, +// ListDentalChiefComplain? treatment, +// }) { +// GAnalytics.TREATMENT_TYPE = treatment!.name; +// logger('book_appointment_chief_complaints', parameters: { +// 'appointment_type': appointment_type, +// 'clinic_type': clinic!.clinicDescription, +// 'hospital_name':hospital!=null ? hospital!.name :"", +// 'treatment_type': treatment.name, +// }); +// } +// +// // R021 +// book_appointment_select_doctor({required String appointment_type, required DoctorList doctor}) { +// // appointment_type, clinic_type, hospital_name, treatment_type, doctor_name, doctor_nationality, doctor_gender +// logger('book_appointment_select_doctor', parameters: { +// 'appointment_type': appointment_type, +// 'clinic_type': doctor.clinicName, +// 'hospital_name': doctor.projectName, +// 'treatment_type': GAnalytics.TREATMENT_TYPE ?? '', +// 'doctor_name': doctor.name, +// 'doctor_nationality': doctor.nationalityName, +// 'doctor_gender': doctor.genderDescription, +// }); +// } +// +// // R022 +// book_appointment_schedule({required String appointment_type, required DoctorList doctor}) { +// // appointment_type, clinic_type, hospital_name, treatment_type, doctor_name, doctor_nationality, doctor_gender +// logger('book_appointment_schedule', parameters: { +// 'appointment_type': appointment_type, +// 'clinic_type': doctor.clinicName, +// 'hospital_name': doctor.projectName, +// 'treatment_type': GAnalytics.TREATMENT_TYPE ?? '', +// 'doctor_name': doctor.name, +// 'doctor_nationality': doctor.nationalityName, +// 'doctor_gender': doctor.genderDescription, +// }); +// } +// +// // R023 +// book_appointment_date_selection({required String appointment_type, required day, required DoctorList doctor}) { +// logger('book_appointment_date_selection', parameters: { +// 'appointment_type': appointment_type, +// 'clinic_type': doctor.clinicName, +// 'hospital_name': doctor.projectName, +// 'treatment_type': GAnalytics.TREATMENT_TYPE ?? '', +// 'doctor_name': doctor.name, +// 'doctor_nationality': doctor.nationalityName, +// 'doctor_gender': doctor.genderDescription, +// 'appointment_day': day +// }); +// } +// +// // R024.1 +// book_appointment_time_selection({required String appointment_type, required DateTime? dateTime, required DoctorList doctor}) { +// final day = DateUtil.getWeekDay(dateTime!.weekday); +// final hour = DateFormat('HH').format(dateTime); +// logger('book_appointment_time_selection', parameters: { +// 'appointment_type': appointment_type, +// 'clinic_type': doctor.clinicName, +// 'hospital_name': doctor.projectName, +// 'treatment_type': GAnalytics.TREATMENT_TYPE ?? '', +// 'doctor_name': doctor.name, +// 'doctor_nationality': doctor.nationalityName, +// 'doctor_gender': doctor.genderDescription, +// 'appointment_day': day, +// 'appointment_hour': hour +// }); +// } +// +// // R024.2 +// book_appointment_review({required String appointment_type, required DateTime dateTime, required DoctorList doctor}) { +// final day = DateUtil.getWeekDay(dateTime.weekday); +// final hour = DateFormat('HH').format(dateTime); +// logger('book_appointment_review', parameters: { +// 'appointment_type': appointment_type, +// 'clinic_type': doctor.clinicName, +// 'hospital_name': doctor.projectName, +// 'treatment_type': GAnalytics.TREATMENT_TYPE ?? '', +// 'doctor_name': doctor.name, +// 'doctor_nationality': doctor.nationalityName, +// 'doctor_gender': doctor.genderDescription, +// 'appointment_day': day, +// 'appointment_hour': hour +// }); +// } +// +// // R025 +// book_appointment_click_confirm({required String appointment_type, required DateTime dateTime, required DoctorList doctor}) { +// final day = DateUtil.getWeekDay(dateTime.weekday); +// final hour = DateFormat('HH').format(dateTime); +// logger('book_appointment_click_confirm', parameters: { +// 'appointment_type': appointment_type, +// 'clinic_type': doctor.clinicName, +// 'hospital_name': doctor.projectName, +// 'treatment_type': GAnalytics.TREATMENT_TYPE ?? '', +// 'doctor_name': doctor.name, +// 'doctor_nationality': doctor.nationalityName, +// 'doctor_gender': doctor.genderDescription, +// 'appointment_day': day, +// 'appointment_hour': hour // '5-6' +// }); +// } +// +// // R026 +// book_appointment_confirmation_success({required String appointment_type, required DateTime dateTime, required DoctorList doctor}) { +// final day = DateUtil.getWeekDay(dateTime.weekday); +// final hour = DateFormat('HH').format(dateTime); +// logger('book_appointment_confirmation_success', parameters: { +// 'appointment_type': appointment_type, +// 'clinic_type': doctor.clinicName, +// 'hospital_name': doctor.projectName, +// 'treatment_type': GAnalytics.TREATMENT_TYPE ?? '', +// 'doctor_name': doctor.name, +// 'doctor_nationality': doctor.nationalityName, +// 'doctor_gender': doctor.genderDescription, +// 'appointment_day': day, +// 'appointment_hour': hour +// }); +// } +// +// // R049.1 // should be for appointment flow +// appointment_actions(AppoitmentAllHistoryResultList appointment, String action) { +// logger('appointment_actions', parameters: { +// 'action_type': action, +// 'flow_type': GAnalytics.APPOINTMENT_DETAIL_FLOW_TYPE, +// 'appointment_type': appointment.appointmentType, +// 'clinic_type_online': appointment.clinicName, +// 'hospital_name': appointment.projectName, +// 'doctor_name': (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName, +// 'payment_type': 'appointment', +// }); +// } +// +// // R027 +// appointment_reminder(bool value) { +// logger('appointment_reminder', parameters: {'reminder': value ? 'yes' : 'no'}); +// } +// +// // R028 +// appointment_reminder_time({required String reminde_before}) { +// logger('appointment_reminder_time', parameters: {'reminder_before': reminde_before}); +// } +// +// // R053 +// // Note : - Payment flow beyond this step are same as listed under ‘Advance Payment’ section of this document +// pay_now_for_appointment({required String appointment_type, required DoctorList doctorDetail, bool payNow = true}) { +// logger('pay_now_for_appointment', parameters: { +// 'appointment_type': appointment_type, +// 'clinic_type': doctorDetail.clinicName, +// 'hospital_name': doctorDetail.projectName, +// 'doctor_name': doctorDetail.name, +// 'payment_type': 'appointment' +// }); +// } +// +// // R033 +// payment_method({required String appointment_type, clinic, payment_method, payment_type}) { +// logger('payment_method', parameters: {'appointment_type': appointment_type, 'clinic_type': clinic, 'payment_method': payment_method, 'payment_type': payment_type}); +// } +// +// // R036 +// payment_success({required String appointment_type, clinic, hospital, payment_method, payment_type, txn_number, txn_amount, txn_currency}) { +// logger('payment_success', parameters: { +// 'appointment_type': appointment_type, +// 'payment_method': payment_method, +// 'payment_type': payment_type, +// 'hospital_name': hospital, +// 'clinic_type_online': clinic, +// 'transaction_number': txn_number, +// 'transaction_amount': txn_amount, +// 'transaction_currency': txn_currency, +// }); +// } +// +// payment_fail({required String appointment_type, clinic, hospital, payment_method, payment_type, txn_amount, txn_currency, error_type}) { +// logger('payment_fail', parameters: { +// 'appointment_type': appointment_type, +// 'payment_method': payment_method, +// 'payment_type': payment_type, +// 'hospital_name': hospital, +// 'clinic_type_online': clinic, +// 'transaction_amount': txn_amount, +// 'transaction_currency': txn_currency, +// 'error_type': error_type +// }); +// } +// +// // Note : - Payment flow beyond this step are same as listed under ‘Advance Payment’ section of this document +// appointment_detail_action({required AppoitmentAllHistoryResultList appointment, required String action}) { +// logger('appointment_detail_action', parameters: { +// 'action_type': action, +// 'flow_type': GAnalytics.APPOINTMENT_DETAIL_FLOW_TYPE, +// }); +// } +// +// // Note : - Payment flow beyond this step are same as listed under ‘Advance Payment’ section of this document +// appointment_details_confirm({required AppoitmentAllHistoryResultList appointment}) { +// logger('appointment_details_confirm', parameters: {}); +// } +// +// // R053 +// // Note : - Payment flow beyond this step are same as listed under ‘Advance Payment’ section of this document +// appointment_details_cancel({required AppoitmentAllHistoryResultList appointment, appointment_type}) { +// logger('cancel_appointment', parameters: { +// 'flow_type': GAnalytics.APPOINTMENT_DETAIL_FLOW_TYPE, +// 'appointment_type': appointment_type, +// 'clinic_type_online': appointment.clinicName, +// 'hospital_name': appointment.projectName, +// 'doctor_name': (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName +// }); +// } +// } +} diff --git a/lib/services/analytics/flows/error_tracking.dart b/lib/services/analytics/flows/error_tracking.dart new file mode 100644 index 00000000..6cc28822 --- /dev/null +++ b/lib/services/analytics/flows/error_tracking.dart @@ -0,0 +1,16 @@ + +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; + +class ErrorTracking{ + + final GALogger logger; + ErrorTracking(this.logger); + + log(String type, {String? error}){ + logger('errors', parameters: { + 'error_type' : type ?? 'unknown', + 'error' : error ?? 'unknown', + }); + } + +} \ No newline at end of file diff --git a/lib/services/analytics/flows/hamburger_menu.dart b/lib/services/analytics/flows/hamburger_menu.dart new file mode 100644 index 00000000..22301a28 --- /dev/null +++ b/lib/services/analytics/flows/hamburger_menu.dart @@ -0,0 +1,16 @@ + +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; + +class HamburgerMenu{ + final hamburger_menu = 'hamburger_menu'; + + final GALogger logger; + HamburgerMenu(this.logger); + + logMenuItemClick(String value){ + logger(hamburger_menu, parameters: { + 'menu_item' : value + }); + } + +} \ No newline at end of file diff --git a/lib/services/analytics/flows/hmg_services.dart b/lib/services/analytics/flows/hmg_services.dart new file mode 100644 index 00000000..1a9a47eb --- /dev/null +++ b/lib/services/analytics/flows/hmg_services.dart @@ -0,0 +1,19 @@ + +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; + +class HMGServices{ + final hmg_services = 'hmg_services'; + + final GALogger logger; + HMGServices(this.logger); + logServiceName(String value){ + logger('hmg_services', parameters: { + 'services_name' : value + }); + } + viewAll(){ + logger('hmg_services', parameters: { + 'services_name' : 'view all services' + }); + } +} \ No newline at end of file diff --git a/lib/services/analytics/flows/live_care.dart b/lib/services/analytics/flows/live_care.dart new file mode 100644 index 00000000..5e7f3301 --- /dev/null +++ b/lib/services/analytics/flows/live_care.dart @@ -0,0 +1,102 @@ +import 'package:flutter/cupertino.dart'; +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; + +class LiveCare{ + + final GALogger logger; + LiveCare(this.logger); + + // R030.1 + livecare_immediate_consultation(){ + logger('livecare_immediate_consultation'); + } + + // R030.2 + livecare_schedule_video_call(){ + logger('livecare_schedule_video_call'); + } + + // R031.1 + livecare_clinic_schedule({required String clinic}){ + logger('livecare_clinic_schedule', parameters: { + 'clinic_type_online' : clinic + }); + } + + // R031.2 + livecare_immediate_consultation_clinic({required String clinic}){ + logger('livecare_immediate_consultation_clinic', parameters: { + 'clinic_type_online' : clinic + }); + } + + // R031.2 + livecare_schedule_video_call_clinic({required String clinic}){ + logger('livecare_schedule_video_call_clinic', parameters: { + 'clinic_type_online' : clinic + }); + } + + // R032 + livecare_immediate_consultation_TnC({required String clinic}){ + logger('livecare_immediate_consultation_tandc', parameters: { + 'clinic_type_online' : clinic + }); + } + + // R033 + payment_method({required String appointment_type, clinic, payment_method, payment_type}){ + logger('payment_method', parameters: { + 'appointment_type' : appointment_type, + 'clinic_type_online' : clinic, + 'payment_method' : payment_method, + 'payment_type' : payment_type + }); + } + + // R034 + payment_confirm({required String appointment_type, clinic, payment_method, payment_type}){ + logger('payment_confirm', parameters: { + 'appointment_type' : appointment_type, + 'clinic_type_online' : clinic, + 'payment_method' : payment_method, + 'payment_type' : payment_type + }); + } + + // R035 + payment_pay({required String appointment_type, clinic, hospital, payment_method, payment_type}){ + // logger('payment_pay', parameters: { + // 'appointment_type' : appointment_type, + // 'clinic_type_online' : clinic, + // 'payment_method' : payment_method, + // 'payment_type' : payment_type, + // 'hospital_name' : hospital + // }); + } + + // R036 + payment_success({required String appointment_type, clinic, hospital, payment_method, payment_type, txn_number, txn_amount, txn_currency}){ + // appointment_type + // clinic_type_online + // payment_method + // payment_type + // hospital_name + // transaction_number + // transaction_amount + // transaction_currency + } + + // R037 + livecare_immediate_consultation_payment_failed({required String appointment_type, clinic, payment_method, payment_type, txn_amount, txn_currency, error_message}){ + logger('livecare_immediate_consult_payment_fail', parameters: { + 'payment_method' : payment_method, + 'appointment_type' : appointment_type, + 'payment_type' : payment_type, + 'clinic_type_online' : clinic, + 'transaction_amount' : txn_amount, + 'transaction_currency' : txn_currency, + 'error_type' : error_message + }); + } +} \ No newline at end of file diff --git a/lib/services/analytics/flows/login_registration.dart b/lib/services/analytics/flows/login_registration.dart new file mode 100644 index 00000000..94883246 --- /dev/null +++ b/lib/services/analytics/flows/login_registration.dart @@ -0,0 +1,156 @@ +import 'package:flutter/cupertino.dart'; +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; + +class LoginRegistration{ + static int loginMethod = 0; + static int verificationMethod = 0; + + final GALogger logger; + LoginRegistration(this.logger); + // R004.1 + login_register_initiate(){ + logger('login_register_initiate'); + } + + // R005.1 + visited_alhabib_group(bool value){ + // selection_type: yes/no + logger('visited_alhabib_group', parameters: { + 'selection_type' : value ? 'yes' : 'no' + }); + } + + // R006.1, R007.1, R008.1, R009.1 + registration_cancel({required String step}){ + // registration_step : enter details + // registration_step : personal info + // registration_step : patient info + // fourth (verification) + logger('registration_cancel', parameters: { + 'registration_step' : step + }); + } + + // R006.2 + registration_enter_details(){ + logger('registration_enter_details'); + } + + // R007.2 + registration_personal_info(){ + logger('registration_personal_info'); + } + + // R008.2 + registration_patient_info(){ + logger('registration_patient_info'); + } + + // R009.2 + registration_verification_option(){ + logger('registration_verification_option'); + } + + // R010:registration_confirmation + registration_confirmation(){ + // verification_method: by + logger('registration_confirmation'); + } + + registration_fail({required String errorType}){ + // verification_method: by + logger('registration_fail', parameters: { + 'error_type' : errorType + }); + } + + // R011.1 + login_start({required String method}){ + logger('login_start', parameters: { + 'login_method' : method + }); + } + + // R011:login_verify_otp | R009:registration_verification_option + verify_otp_method({bool forRegistration = false}){ + if(forRegistration == false) + logger("login_verify_otp", parameters: { + 'login_method' : _getLoginMethod(), + 'verification_method' : _getVerificationMethod(), + }); + else + logger("registration_verification_option", parameters: { + 'verification_method' : _getVerificationMethod() + }); + } + + // R011:login_verify_otp | R009:registration_verification_option + login_verfication({bool forRegistration = false}){ + if(forRegistration == false) + logger("login_verfication", parameters: { + 'login_method' : _getLoginMethod(), + 'verification_method' : _getVerificationMethod(), + }); + else + logger("login_varification_register", parameters: { + 'login_method' : _getLoginMethod(), + 'verification_method' : _getVerificationMethod(), + }); + } + + // R011.2 + forget_file_number(){ + logger('forget_file_number'); + } + + // R011.3 + register_now(){ + logger('register_now'); + } + + // R012.1, R014.1 + login_successful(){ + logger('login_successful', parameters: { + 'login_method' : _getVerificationMethod() + }); + } + + // R012.4 + login_unsuccessful({required String method, error}){ + logger('login_unsuccessful', parameters: { + 'login_method' : method + }); + } + + login_fail({error}){ + logger('login_unsuccessful', parameters: { + 'login_method' : loginMethod, + 'error': error + }); + } + + // R013 + recover_file_number(){ + logger('recover_file_number'); + } + + // R014.2 + login_with_other_account(){ + logger('login_with_other_account'); + } + + + _getLoginMethod(){ + if(loginMethod == 1) return 'national id'; + if(loginMethod == 2) return 'file number'; + return 'otp'; + } + + String _getVerificationMethod(){ + if(verificationMethod == 1) return 'sms'; + if(verificationMethod == 2) return 'fingerprint'; + if(verificationMethod == 3) return 'face id'; + if(verificationMethod == 4) return 'whatsapp'; + return "unknown"; + } +} \ No newline at end of file diff --git a/lib/services/analytics/flows/offers_promotions.dart b/lib/services/analytics/flows/offers_promotions.dart new file mode 100644 index 00000000..bd2511f0 --- /dev/null +++ b/lib/services/analytics/flows/offers_promotions.dart @@ -0,0 +1,17 @@ + +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; + +class OfferAndPromotion{ + + final GALogger logger; + OfferAndPromotion(this.logger); + + final offers_and_promotion = 'offers_&_promotion'; + + log(){ + logger('offers_and_promotion', parameters: { + 'promotion_name' : "offer" + }); + } + +} \ No newline at end of file diff --git a/lib/services/analytics/flows/todo_list.dart b/lib/services/analytics/flows/todo_list.dart new file mode 100644 index 00000000..c1c874b2 --- /dev/null +++ b/lib/services/analytics/flows/todo_list.dart @@ -0,0 +1,111 @@ + + +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; + +class TodoList{ + + final GALogger logger; + TodoList(this.logger); + + // R047.1 + // to_do_list_pay_now(AppoitmentAllHistoryResultList appointment){ + // logger('to_do_list_pay_now', parameters: { + // 'appointment_type' : appointment.isLiveCareAppointment! ? 'livecare' : 'regular', + // 'clinic_type_online' : appointment.clinicName, + // 'hospital_name' : appointment.projectName, + // 'doctor_name' : (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName, + // 'payment_type' : 'appointment', + // }); + // } + + // // R047.2 + // to_do_list_more_details(AppoitmentAllHistoryResultList appointment){ + // logger('to_do_list_more_details', parameters: { + // 'appointment_type' : appointment.isLiveCareAppointment! ? 'livecare' : 'regular', + // 'clinic_type_online' : appointment.clinicName, + // 'hospital_name' : appointment.projectName, + // 'doctor_name' : (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName, + // 'payment_type' : 'appointment', + // }); + // } + // + // // R048 + // to_do_list_confirm_payment_details(AppoitmentAllHistoryResultList appointment){ + // logger('to_do_list_confirm_payment_details', parameters: { + // 'appointment_type' : appointment.isLiveCareAppointment! ? 'livecare' : 'regular', + // 'clinic_type_online' : appointment.clinicName, + // 'hospital_name' : appointment.projectName, + // 'doctor_name' : (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName, + // 'payment_type' : 'appointment', + // }); + // } + // + // // R048 + // to_do_list_cancel_payment_details(AppoitmentAllHistoryResultList appointment){ + // logger('to_do_list_cancel_payment_details', parameters: { + // 'appointment_type' : appointment.isLiveCareAppointment! ? 'livecare' : 'regular', + // 'clinic_type_online' : appointment.clinicName, + // 'hospital_name' : appointment.projectName, + // 'doctor_name' : (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName, + // 'payment_type' : 'appointment', + // }); + // } + // + // + // + // // R049.2 + // to_do_list_cancel_appointment(AppoitmentAllHistoryResultList appointment){ + // logger('to_do_list_cancel_appointment', parameters: { + // 'appointment_type' : appointment.isLiveCareAppointment! ? 'livecare' : 'regular', + // 'clinic_type_online' : appointment.clinicName, + // 'hospital_name' : appointment.projectName, + // 'doctor_name' : (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName, + // 'payment_type' : 'appointment', + // }); + // } + // + // // R049.3 + // to_do_list_confirm_appointment(AppoitmentAllHistoryResultList appointment){ + // logger('confirm_appointment', parameters: { + // 'appointment_type' : appointment.isLiveCareAppointment! ? 'livecare' : 'regular', + // 'flow_type' : 'todo list', + // 'clinic_type_online' : appointment.clinicName, + // 'hospital_name' : appointment.projectName, + // 'doctor_name' : (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName, + // 'payment_type' : 'appointment', + // }); + // } + // + // // R050 + // to_do_list_check_in(AppoitmentAllHistoryResultList appointment){ + // logger('to_do_list_check_in', parameters: { + // 'appointment_type' : appointment.isLiveCareAppointment! ? 'livecare' : 'regular', + // 'clinic_type_online' : appointment.clinicName, + // 'hospital_name' : appointment.projectName, + // 'doctor_name' : (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName, + // 'payment_type' : 'appointment', + // }); + // } + // + // // R051 + // to_do_list_nfc(AppoitmentAllHistoryResultList appointment){ + // logger('to_do_list_nfc', parameters: { + // 'appointment_type' : appointment.isLiveCareAppointment! ? 'livecare' : 'regular', + // 'clinic_type_online' : appointment.clinicName, + // 'hospital_name' : appointment.projectName, + // 'doctor_name' : (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName, + // 'payment_type' : 'appointment', + // }); + // } + // + // // R052 + // to_do_list_nfc_cancel(AppoitmentAllHistoryResultList appointment){ + // logger('to_do_list_nfc_cancel', parameters: { + // 'appointment_type' : appointment.isLiveCareAppointment != null ? appointment.isLiveCareAppointment! ? 'livecare' : 'regular' : 'regular', + // 'clinic_type_online' : appointment.clinicName, + // 'hospital_name' : appointment.projectName, + // 'doctor_name' : (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName, + // 'payment_type' : 'appointment', + // }); + // } +} \ No newline at end of file diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 72a9213e..85b2e807 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -6,9 +6,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; -import 'package:hmg_patient_app_new/presentation/authantication/login.dart'; +// import 'package:hmg_patient_app_new/presentation/authantication/login.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; @@ -24,10 +25,7 @@ class SplashPage extends StatefulWidget { } class _SplashScreenState extends State { - @override - void initState() { - super.initState(); - print("Splash init called............."); + Future initializeStuff() async { Timer( Duration(milliseconds: 500), () async { @@ -39,12 +37,11 @@ class _SplashScreenState extends State { LocalNotification.init(onNotificationClick: (payload) {}); Navigator.of(context).pushReplacement( FadePage( - // page: LandingPage(), - page: LoginScreen(), + page: LandingPage(), + // page: LoginScreen(), ), ); }); - var zoom = ZoomVideoSdk(); InitConfig initConfig = InitConfig( domain: "zoom.us", @@ -53,6 +50,8 @@ class _SplashScreenState extends State { zoom.initSdk(initConfig); } + + /// load the Privilege from service Future loadPrivilege() async { // ProjectViewModel projectProvider = Provider.of(context, listen: false); @@ -71,6 +70,13 @@ class _SplashScreenState extends State { PushNotificationHandler().init(context); // Asyncronously } + + @override + void initState() { + super.initState(); + initializeStuff(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -80,7 +86,8 @@ class _SplashScreenState extends State { children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 53), - child: Image.asset(AppAssets.hmg_logo, fit: BoxFit.fitWidth, width: MediaQuery.of(context).size.width), + child: Image.asset(AppAssets.hmg_logo, + fit: BoxFit.fitWidth, width: MediaQuery.of(context).size.width), ), Align( alignment: Alignment.bottomCenter, @@ -89,12 +96,18 @@ class _SplashScreenState extends State { children: [ Text( "Powered by", - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.textColor, letterSpacing: -0.56, height: 16 / 14), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColors.textColor, + letterSpacing: -0.56, + height: 16 / 14), ), SizedBox( height: 5, ), - Utils.buildSvgWithAssets(icon: AppAssets.cloud_logo, width: 40, height: 40), + Utils.buildSvgWithAssets( + icon: AppAssets.cloud_logo, width: 40, height: 40), SizedBox(height: 7), // Text( // "Version 1.1.0", diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 4987ffe4..a5374122 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; class AppColors { + static const transparent = Colors.transparent; static const mainPurple = Color(0xFF7954F7); static const purpleBg = Color(0xFFAEA4FC); static const deepPurple = Color(0xFF7C65E7); diff --git a/pubspec.lock b/pubspec.lock index 6ba3395e..98b07a4f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.12.0" audio_session: dependency: transitive description: @@ -161,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dartz: + dependency: "direct main" + description: + name: dartz + sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168 + url: "https://pub.dev" + source: hosted + version: "0.10.1" dbus: dependency: transitive description: @@ -219,7 +227,7 @@ packages: source: hosted version: "0.0.2" equatable: - dependency: transitive + dependency: "direct main" description: name: equatable sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" @@ -230,10 +238,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.2" ffi: dependency: transitive description: @@ -290,6 +298,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+4" + firebase_analytics: + dependency: "direct main" + description: + name: firebase_analytics + sha256: "4f85b161772e1d54a66893ef131c0a44bd9e552efa78b33d5f4f60d2caa5c8a3" + url: "https://pub.dev" + source: hosted + version: "11.6.0" + firebase_analytics_platform_interface: + dependency: transitive + description: + name: firebase_analytics_platform_interface + sha256: a44b6d1155ed5cae7641e3de7163111cfd9f6f6c954ca916dc6a3bdfa86bf845 + url: "https://pub.dev" + source: hosted + version: "4.4.3" + firebase_analytics_web: + dependency: transitive + description: + name: firebase_analytics_web + sha256: c7d1ed1f86ae64215757518af5576ff88341c8ce5741988c05cc3b2e07b0b273 + url: "https://pub.dev" + source: hosted + version: "0.5.10+16" firebase_core: dependency: "direct main" description: @@ -615,6 +647,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.5" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b + url: "https://pub.dev" + source: hosted + version: "8.2.0" + google_api_availability: + dependency: "direct main" + description: + name: google_api_availability + sha256: "2ffdc91e1e0cf4e7974fef6c2988a24cefa81f03526ff04b694df6dc0fcbca03" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + google_api_availability_android: + dependency: transitive + description: + name: google_api_availability_android + sha256: "4794147f43a8f3eee6b514d3ae30dbe6f7b9048cae8cd2a74cb4055cd28d74a8" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + google_api_availability_platform_interface: + dependency: transitive + description: + name: google_api_availability_platform_interface + sha256: "65b7da62fe5b582bb3d508628ad827d36d890710ea274766a992a56fa5420da6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" google_maps: dependency: transitive description: @@ -767,22 +831,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2" - injector: - dependency: "direct main" - description: - name: injector - sha256: d692c37788872bfd4bd7c01b864b0712190a25ae5a346431ff69949d5728a2e1 - url: "https://pub.dev" - source: hosted - version: "4.0.0" intl: dependency: transitive description: name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.20.2" + version: "0.19.0" json_annotation: dependency: transitive description: @@ -819,10 +875,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: @@ -1099,10 +1155,10 @@ packages: dependency: transitive description: name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "6.1.0" platform: dependency: transitive description: @@ -1296,10 +1352,10 @@ packages: dependency: transitive description: name: sqflite_common - sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" url: "https://pub.dev" source: hosted - version: "2.5.6" + version: "2.5.5" sqflite_darwin: dependency: transitive description: @@ -1384,10 +1440,10 @@ packages: dependency: transitive description: name: synchronized - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.3.1" term_glyph: dependency: transitive description: @@ -1536,10 +1592,10 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "14.3.1" web: dependency: "direct main" description: @@ -1552,10 +1608,10 @@ packages: dependency: transitive description: name: win32 - sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" url: "https://pub.dev" source: hosted - version: "5.14.0" + version: "5.13.0" win32_registry: dependency: transitive description: @@ -1576,10 +1632,10 @@ packages: dependency: transitive description: name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "6.5.0" sdks: - dart: ">=3.8.0 <4.0.0" + dart: ">=3.7.0 <4.0.0" flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index a6e3e499..6c79c929 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,6 +62,8 @@ dependencies: flutter_zoom_videosdk: ^2.1.10 dartz: ^0.10.1 equatable: ^2.0.7 + google_api_availability: ^5.0.1 + firebase_analytics: ^11.5.1 web: any smooth_corner: ^1.1.1 From 4e010bb2cf3ea496e8501d4e0095a73efb958323 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Tue, 2 Sep 2025 09:09:38 +0300 Subject: [PATCH 05/17] initial commit --- ios/Podfile.lock | 66 +++++ lib/core/api/api_client.dart | 241 ++++++------------ lib/core/api_consts.dart | 4 +- lib/core/dependencies.dart | 10 +- .../authentication/authentication_repo.dart | 41 +-- .../models/authenticated_user_model.dart | 2 +- 6 files changed, 180 insertions(+), 184 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index db4395f7..6794b89b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -42,11 +42,20 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter + - Firebase/Analytics (11.15.0): + - Firebase/Core + - Firebase/Core (11.15.0): + - Firebase/CoreOnly + - FirebaseAnalytics (~> 11.15.0) - Firebase/CoreOnly (11.15.0): - FirebaseCore (~> 11.15.0) - Firebase/Messaging (11.15.0): - Firebase/CoreOnly - FirebaseMessaging (~> 11.15.0) + - firebase_analytics (11.6.0): + - Firebase/Analytics (= 11.15.0) + - firebase_core + - Flutter - firebase_core (3.15.2): - Firebase/CoreOnly (= 11.15.0) - Flutter @@ -54,6 +63,24 @@ PODS: - Firebase/Messaging (= 11.15.0) - firebase_core - Flutter + - FirebaseAnalytics (11.15.0): + - FirebaseAnalytics/Default (= 11.15.0) + - FirebaseCore (~> 11.15.0) + - FirebaseInstallations (~> 11.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - FirebaseAnalytics/Default (11.15.0): + - FirebaseCore (~> 11.15.0) + - FirebaseInstallations (~> 11.0) + - GoogleAppMeasurement/Default (= 11.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) - FirebaseCore (11.15.0): - FirebaseCoreInternal (~> 11.15.0) - GoogleUtilities/Environment (~> 8.1) @@ -107,6 +134,32 @@ PODS: - Flutter - Google-Maps-iOS-Utils (< 7.0, >= 5.0) - GoogleMaps (< 10.0, >= 8.4) + - GoogleAdsOnDeviceConversion (2.1.0): + - GoogleUtilities/Logger (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/Core (11.15.0): + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/Default (11.15.0): + - GoogleAdsOnDeviceConversion (= 2.1.0) + - GoogleAppMeasurement/Core (= 11.15.0) + - GoogleAppMeasurement/IdentitySupport (= 11.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/IdentitySupport (11.15.0): + - GoogleAppMeasurement/Core (= 11.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) - GoogleDataTransport (10.1.0): - nanopb (~> 3.30910.0) - PromisesObjC (~> 2.4) @@ -125,6 +178,9 @@ PODS: - GoogleUtilities/Logger (8.1.0): - GoogleUtilities/Environment - GoogleUtilities/Privacy + - GoogleUtilities/MethodSwizzler (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy - GoogleUtilities/Network (8.1.0): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" @@ -216,6 +272,7 @@ DEPENDENCIES: - device_calendar (from `.symlinks/plugins/device_calendar/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) + - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - FLAnimatedImage @@ -249,12 +306,15 @@ SPEC REPOS: - DKImagePickerController - DKPhotoGallery - Firebase + - FirebaseAnalytics - FirebaseCore - FirebaseCoreInternal - FirebaseInstallations - FirebaseMessaging - FLAnimatedImage - Google-Maps-iOS-Utils + - GoogleAdsOnDeviceConversion + - GoogleAppMeasurement - GoogleDataTransport - GoogleMaps - GoogleUtilities @@ -288,6 +348,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/device_info_plus/ios" file_picker: :path: ".symlinks/plugins/file_picker/ios" + firebase_analytics: + :path: ".symlinks/plugins/firebase_analytics/ios" firebase_core: :path: ".symlinks/plugins/firebase_core/ios" firebase_messaging: @@ -345,8 +407,10 @@ SPEC CHECKSUMS: DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e + firebase_analytics: bf93e20703c95030404d6ddbb1adf05bf5c3885b firebase_core: 99a37263b3c27536063a7b601d9e2a49400a433c firebase_messaging: bf6697c61f31c7cc0f654131212ff04c0115c2c7 + FirebaseAnalytics: 6433dfd311ba78084fc93bdfc145e8cb75740eae FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843 @@ -362,6 +426,8 @@ SPEC CHECKSUMS: geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321 google_maps_flutter_ios: e31555a04d1986ab130f2b9f24b6cdc861acc6d3 + GoogleAdsOnDeviceConversion: 2be6297a4f048459e0ae17fad9bfd2844e10cf64 + GoogleAppMeasurement: 700dce7541804bec33db590a5c496b663fbe2539 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index d8b379de..203c6ef4 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:developer'; import 'dart:io' show Platform; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; @@ -10,7 +11,6 @@ import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:http/http.dart' as http; - abstract class ApiClient { Future post( String endPoint, { @@ -75,8 +75,10 @@ class ApiClientImp implements ApiClient { final _analytics = getIt(); final LoggerService loggerService; + ApiClientImp({required this.loggerService}); + @override post(String endPoint, {required Map body, required Function(dynamic response, int statusCode) onSuccess, @@ -98,21 +100,15 @@ class ApiClientImp implements ApiClient { } try { var user = appState.getAuthenticatedUser; - Map headers = { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }; + Map headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}; if (!isExternal) { String? token = appState.appAuthToken; - String? languageID = - (appState.postParamsObject?.languageID == 1 ? 'ar' : 'en') ?? 'ar'; + String? languageID = (appState.postParamsObject?.languageID == 1 ? 'ar' : 'en') ?? 'ar'; if (endPoint == ApiConsts.sendActivationCode) { languageID = 'en'; } if (body.containsKey('SetupID')) { - body['SetupID'] = body.containsKey('SetupID') - ? body['SetupID'] ?? body[''] - : SETUP_ID; + body['SetupID'] = body.containsKey('SetupID') ? body['SetupID'] ?? body[''] : SETUP_ID; } else {} if (body.containsKey('LanguageID')) { @@ -127,90 +123,71 @@ class ApiClientImp implements ApiClient { } if (body.containsKey('isDentalAllowedBackend')) { - body['isDentalAllowedBackend'] = - body.containsKey('isDentalAllowedBackend') - ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND - : IS_DENTAL_ALLOWED_BACKEND; + body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') + ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND + : IS_DENTAL_ALLOWED_BACKEND; } + + //Todo: I have converted it to string body['DeviceTypeID'] = Platform.isIOS - ? 1 + ? "1" : await Utils.isGoogleServicesAvailable() - ? 2 - : 3; + ? "2" + : "3"; if (!body.containsKey('IsPublicRequest')) { // if (!body.containsKey('PatientType')) { if (user != null && user.patientType != null) { body['PatientType'] = user.patientType; } else { - body['PatientType'] = PATIENT_TYPE; + body['PatientType'] = PATIENT_TYPE.toString(); } - // } - - // body['PatientType'] = body.containsKey('PatientType') - // ? body['PatientType'] != null - // ? body['PatientType'] - // : user['PatientType'] != null - // ? user['PatientType'] - // : PATIENT_TYPE - // : PATIENT_TYPE; - // if (!body.containsKey('PatientTypeID')) { if (user != null && user.patientType != null) { body['PatientTypeID'] = user.patientType; } else { - body['PatientType'] = PATIENT_TYPE_ID; + body['PatientType'] = PATIENT_TYPE_ID.toString(); } - // } - - // body['PatientTypeID'] = body.containsKey('PatientTypeID') - // ? body['PatientTypeID'] != null - // ? body['PatientTypeID'] - // : user['PatientType'] != null - // ? user['PatientType'] - // : PATIENT_TYPE_ID - // : PATIENT_TYPE_ID; if (user != null) { body['TokenID'] = body['TokenID'] ?? token; body['PatientID'] = body['PatientID'] ?? user.patientID; - body['PatientOutSA'] = body.containsKey('PatientOutSA') - ? body['PatientOutSA'] ?? user.outSA - : user.outSA; + body['PatientOutSA'] = body.containsKey('PatientOutSA') ? body['PatientOutSA'] ?? user.outSA : user.outSA; body['SessionID'] = getSessionId(body['TokenID'] ?? ""); //getSe } } } + + body['LanguageID'] = body['LanguageID'] ?? "1"; + body['VersionID'] = body['VersionID'] ?? "18.7"; + body['Channel'] = body['Channel'] ?? "3"; + body['IPAdress'] = body['IPAdress'] ?? "10.20.10.20"; + body['generalid'] = body['generalid'] ?? "Cs2020@2016\$2958"; + body['Latitude'] = body['Latitude'] ?? "0.0"; + body['Longitude'] = body['Longitude'] ?? "0.0"; + body['DeviceTypeID'] = body['DeviceTypeID'] ?? + (Platform.isIOS ? "1" : await Utils.isGoogleServicesAvailable() ? "2" : "3"); + //"LanguageID":1,"VersionID":18.7,"Channel":3,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","Latitude":0.0,"Longitude":0.0,"DeviceTypeID":2,"PatientType":1} body.removeWhere((key, value) => value == null); - debugPrint("URL : $url"); - final jsonBody = json.encode(body); - debugPrint(jsonBody); - // } - // return; - if (await Utils.checkConnection( - bypassConnectionCheck: bypassConnectionCheck)) { - final response = await http.post(Uri.parse(url.trim()), - body: json.encode(body), headers: headers); + log("bodi: ${json.encode(body)}"); + log("bodi: ${Uri.parse(url.trim())}"); + + if (await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck)) { + + final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers); + final int statusCode = response.statusCode; if (statusCode < 200 || statusCode >= 400) { onFailure('Error While Fetching data', statusCode); - logApiEndpointError( - endPoint, 'Error While Fetching data', statusCode); + logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); } else { - // var decoded = utf8.decode(response.bodyBytes); - var parsed; - // if (url.contains('Services/NHIC.svc/REST/GetPatientInfo')) { - // parsed = json.decode(sampleNHICResponse); - // } else { - parsed = json.decode(utf8.decode(response.bodyBytes)); - // } - - // print("Response: $parsed"); + var parsed = json.decode(utf8.decode(response.bodyBytes)); + log("parsed: ${parsed.toString()}"); if (isAllowAny) { onSuccess(parsed, statusCode); } else { @@ -219,10 +196,7 @@ class ApiClientImp implements ApiClient { } else { if (parsed['ErrorType'] == 4) { //TODO : handle app update - logApiEndpointError( - endPoint, - parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], - statusCode); + logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } if (parsed['ErrorType'] == 2) { // todo: handle Logout @@ -238,72 +212,42 @@ class ApiClientImp implements ApiClient { } else if (parsed['Result'] == 'OK') { onSuccess(parsed, statusCode); } else { - // if (parsed != null) { - // onSuccess(parsed, statusCode); - // } else { - onFailure( - parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], - statusCode); - logApiEndpointError( - endPoint, - parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], - statusCode); - // logout(); - // } + + onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + } - } else if (parsed['MessageStatus'] == 1 || - parsed['SMSLoginRequired'] == true) { + } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { onSuccess(parsed, statusCode); - } else if (parsed['MessageStatus'] == 2 && - parsed['IsAuthenticated']) { + } else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) { if (parsed['SameClinicApptList'] != null) { onSuccess(parsed, statusCode); } else { - if (parsed['message'] == null && - parsed['ErrorEndUserMessage'] == null) { + if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) { if (parsed['ErrorSearchMsg'] == null) { - onFailure("Server Error found with no available message", - statusCode); - logApiEndpointError( - endPoint, - "Server Error found with no available message", - statusCode); + onFailure("Server Error found with no available message", statusCode); + logApiEndpointError(endPoint, "Server Error found with no available message", statusCode); } else { onFailure(parsed['ErrorSearchMsg'], statusCode); - logApiEndpointError( - endPoint, parsed['ErrorSearchMsg'], statusCode); + logApiEndpointError(endPoint, parsed['ErrorSearchMsg'], statusCode); } } else { - onFailure( - parsed['message'] ?? - parsed['ErrorEndUserMessage'] ?? - parsed['ErrorMessage'], - statusCode); - logApiEndpointError(endPoint, - parsed['message'] ?? parsed['message'], statusCode); + onFailure(parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); } } } - // else if (!parsed['IsAuthenticated']) { - // await logout(); - // } + else { if (parsed['SameClinicApptList'] != null) { onSuccess(parsed, statusCode); } else { if (parsed['message'] != null) { - onFailure( - parsed['message'] ?? parsed['message'], statusCode); - logApiEndpointError(endPoint, - parsed['message'] ?? parsed['message'], statusCode); + onFailure(parsed['message'] ?? parsed['message'], statusCode); + logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); } else { - onFailure( - parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], - statusCode); - logApiEndpointError( - endPoint, - parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], - statusCode); + onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } } } @@ -312,20 +256,17 @@ class ApiClientImp implements ApiClient { } } else { onFailure('Please Check The Internet Connection 1', -1); - _analytics.errorTracking - .log("internet_connectivity", error: "no internet available"); + _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } } catch (e) { - print(e); + loggerService.errorLogs(e.toString()); if (e.toString().contains("ClientException")) { onFailure('Something went wrong, plase try again', -1); - _analytics.errorTracking - .log("internet_connectivity", error: "no internet available"); + _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } else { onFailure(e.toString(), -1); } - _analytics.errorTracking - .log(endPoint, error: "api exception: $e - API Path: $url"); + _analytics.errorTracking.log(endPoint, error: "api exception: $e - API Path: $url"); } } @@ -356,10 +297,7 @@ class ApiClientImp implements ApiClient { if (await Utils.checkConnection()) { final response = await http.get( Uri.parse(url.trim()), - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }, + headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}, ); final int statusCode = response.statusCode; // print("statusCode :$statusCode"); @@ -373,8 +311,7 @@ class ApiClientImp implements ApiClient { } } else { onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking - .log("internet_connectivity", error: "no internet available"); + _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } } @@ -390,8 +327,7 @@ class ApiClientImp implements ApiClient { // print("body: $body"); if (await Utils.checkConnection()) { - headers!.addAll( - {'Content-Type': 'application/json', 'Accept': 'application/json'}); + headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); final response = await http.post( Uri.parse(url.trim()), body: json.encode(body), @@ -400,11 +336,7 @@ class ApiClientImp implements ApiClient { final int statusCode = response.statusCode; // print("statusCode :$statusCode"); if (await handleUnauthorized(statusCode, forUrl: fullUrl)) - simplePost(fullUrl, - onFailure: onFailure, - onSuccess: onSuccess, - body: body, - headers: headers); + simplePost(fullUrl, onFailure: onFailure, onSuccess: onSuccess, body: body, headers: headers); // print(response.body.toString()); @@ -416,8 +348,7 @@ class ApiClientImp implements ApiClient { } } else { onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking - .log("internet_connectivity", error: "no internet available"); + _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } } @@ -437,8 +368,7 @@ class ApiClientImp implements ApiClient { } if (await Utils.checkConnection()) { - headers.addAll( - {'Content-Type': 'application/json', 'Accept': 'application/json'}); + headers.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); final response = await http.get( Uri.parse(url.trim()), headers: headers, @@ -447,11 +377,7 @@ class ApiClientImp implements ApiClient { final int statusCode = response.statusCode; // print("statusCode :$statusCode"); if (await handleUnauthorized(statusCode, forUrl: fullUrl)) - simpleGet(fullUrl, - onFailure: onFailure, - onSuccess: onSuccess, - headers: headers, - queryParams: queryParams); + simpleGet(fullUrl, onFailure: onFailure, onSuccess: onSuccess, headers: headers, queryParams: queryParams); if (statusCode < 200 || statusCode >= 400) { onFailure!('Error While Fetching data', statusCode); @@ -461,8 +387,7 @@ class ApiClientImp implements ApiClient { } } else { onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking - .log("internet_connectivity", error: "no internet available"); + _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } } @@ -475,8 +400,7 @@ class ApiClientImp implements ApiClient { // print("URL Query String: $url"); if (await Utils.checkConnection()) { - headers!.addAll( - {'Content-Type': 'application/json', 'Accept': 'application/json'}); + headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); final response = await http.put( Uri.parse(url.trim()), body: json.encode(body), @@ -486,11 +410,7 @@ class ApiClientImp implements ApiClient { final int statusCode = response.statusCode; // print("statusCode :$statusCode"); if (await handleUnauthorized(statusCode, forUrl: fullUrl)) - simplePut(fullUrl, - onFailure: onFailure, - onSuccess: onSuccess, - headers: headers, - body: body); + simplePut(fullUrl, onFailure: onFailure, onSuccess: onSuccess, headers: headers, body: body); if (statusCode < 200 || statusCode >= 400) { onFailure!('Error While Fetching data', statusCode); @@ -500,8 +420,7 @@ class ApiClientImp implements ApiClient { } } else { onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking - .log("internet_connectivity", error: "no internet available"); + _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } } @@ -521,8 +440,7 @@ class ApiClientImp implements ApiClient { } if (await Utils.checkConnection()) { - headers!.addAll( - {'Content-Type': 'application/json', 'Accept': 'application/json'}); + headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); final response = await http.delete( Uri.parse(url.trim()), headers: headers, @@ -531,11 +449,7 @@ class ApiClientImp implements ApiClient { final int statusCode = response.statusCode; // print("statusCode :$statusCode"); if (await handleUnauthorized(statusCode, forUrl: fullUrl)) - simpleDelete(fullUrl, - onFailure: onFailure, - onSuccess: onSuccess, - queryParams: queryParams, - headers: headers); + simpleDelete(fullUrl, onFailure: onFailure, onSuccess: onSuccess, queryParams: queryParams, headers: headers); if (statusCode < 200 || statusCode >= 400) { onFailure!('Error While Fetching data', statusCode); @@ -545,13 +459,11 @@ class ApiClientImp implements ApiClient { } } else { onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking - .log("internet_connectivity", error: "no internet available"); + _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } } - Future handleUnauthorized(int statusCode, - {required String forUrl}) async { + Future handleUnauthorized(int statusCode, {required String forUrl}) async { if (forUrl.startsWith(EXA_CART_API_BASE_URL) && statusCode == 401) { final token = await generatePackagesToken(); ApiConsts.packagesAuthHeader['Authorization'] = 'Bearer $token'; @@ -574,8 +486,7 @@ class ApiClientImp implements ApiClient { }; String? token; final completer = Completer(); - simplePost(url, body: body, headers: {}, - onSuccess: (dynamic stringResponse, int statusCode) { + simplePost(url, body: body, headers: {}, onSuccess: (dynamic stringResponse, int statusCode) { if (statusCode == 200) { var jsonResponse = json.decode(stringResponse); token = jsonResponse['auth_token']; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index dc952bf5..6f82e26a 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -745,8 +745,8 @@ class ApiConsts { static num VERSION_ID = 18.9; - static final String selectDeviceImei = 'https://hmgwebservices.com/Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI'; - static final String sendActivationCode = 'https://hmgwebservices.com/Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationType'; + static final String selectDeviceImei = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI'; + static final String sendActivationCode = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationType'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index a789da41..440cd265 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_repo. import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; +import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:logger/web.dart'; @@ -15,10 +16,17 @@ GetIt getIt = GetIt.instance; class AppDependencies { static Future addDependencies() async { // Services - getIt.registerLazySingleton(() => LoggerServiceImp(logger: Logger(printer: PrettyPrinter(lineLength: 0)))); + getIt.registerLazySingleton(() => LoggerServiceImp(logger: Logger(printer: PrettyPrinter( + methodCount: 2, // number of stack trace lines + errorMethodCount: 5, // number of stack trace lines for errors + lineLength: 100, // wrap width + colors: true, // colorful logs + printEmojis: true, // include emojis + ),))); final sharedPreferences = await SharedPreferences.getInstance(); getIt.registerLazySingleton(() => CacheServiceImp(sharedPreferences: sharedPreferences)); getIt.registerSingleton(AppState()); + getIt.registerSingleton(GAnalytics()); getIt.registerLazySingleton(() => ApiClientImp(loggerService: getIt())); // Repositories diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 8122a522..e544638d 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -1,4 +1,5 @@ -import 'dart:convert'; +import 'dart:async'; +import 'dart:developer'; import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; @@ -7,7 +8,6 @@ import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/authentication/models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; -import 'package:http/http.dart'; abstract class AuthenticationRepo { Future> selectDeviceByImei({required String firebaseToken}); @@ -20,24 +20,35 @@ class AuthenticationRepoImp implements AuthenticationRepo { AuthenticationRepoImp({required this.loggerService, required this.apiClient}); @override - Future> selectDeviceByImei({required String firebaseToken}) async { + Future> selectDeviceByImei({ + required String firebaseToken, + }) async { final mapDevice = {"IMEI": firebaseToken}; - try { - final Response result = await apiClient.postJsonForResponse(ApiConsts.selectDeviceImei, mapDevice); - final SelectDeviceByImeiRespModelElement selectDeviceByImeiRespModelElement = SelectDeviceByImeiRespModelElement.fromJson(jsonDecode(result.body)['Patient_SELECTDeviceIMEIbyIMEIList'][0]); - loggerService.logInfo(result.body); - if (result !=null) { - return Right(selectDeviceByImeiRespModelElement); - } else { - loggerService.errorLogs(result.toString()); - return Left(ServerFailure(result.toString())); - } + final completer = Completer>(); + await apiClient.post( + ApiConsts.selectDeviceImei, + body: mapDevice, + onSuccess: (response, statusCode) { + try { + final SelectDeviceByImeiRespModelElement model = + SelectDeviceByImeiRespModelElement.fromJson(response['Patient_SELECTDeviceIMEIbyIMEIList'][0]); + completer.complete(Right(model)); + } catch (e) { + completer.complete(Left(ServerFailure(e.toString()))); + } + }, + onFailure: (error, statusCode) { + loggerService.logInfo(("$error - $statusCode").toString()); + completer.complete(Left(ServerFailure(error))); + }, + ); + + return await completer.future; } on APIException catch (e) { - loggerService.errorLogs(e.toString()); - return Left(ServerFailure(apiError?.errorMessage ?? e.message)); + return Left(ServerFailure(e.message)); } catch (e) { loggerService.errorLogs(e.toString()); return Left(ServerFailure(e.toString())); diff --git a/lib/features/authentication/models/authenticated_user_model.dart b/lib/features/authentication/models/authenticated_user_model.dart index 49987f3a..2a071bbc 100644 --- a/lib/features/authentication/models/authenticated_user_model.dart +++ b/lib/features/authentication/models/authenticated_user_model.dart @@ -1,4 +1,4 @@ -import 'package:hmg_patient_app/uitl/date_uitl.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; class AuthenticatedUser { String? setupID; From c1221e4970b90417532c3b23a7b98aae63ebe4d1 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Tue, 2 Sep 2025 09:35:05 +0300 Subject: [PATCH 06/17] pushed device api --- ios/Podfile.lock | 4 ++-- lib/core/api/api_client.dart | 5 +++-- pubspec.lock | 30 +++++++++++++++--------------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6794b89b..e5a28a81 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -416,7 +416,7 @@ SPEC CHECKSUMS: FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843 FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09 FLAnimatedImage: bbf914596368867157cc71b38a8ec834b3eeb32b - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_callkit_incoming: 417dd1b46541cdd5d855ad795ccbe97d1c18155e flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_ios_voip_kit_karmm: 7ea37381a8841c92d186edf1f4604df5cc437579 @@ -464,4 +464,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 5df9d8aa8f2c105eacd5ad7a310503d93c68c86b -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 203c6ef4..fbf63b25 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -161,7 +161,7 @@ class ApiClientImp implements ApiClient { } - body['LanguageID'] = body['LanguageID'] ?? "1"; + body['LanguageID'] = body['LanguageID'] ?? "2"; body['VersionID'] = body['VersionID'] ?? "18.7"; body['Channel'] = body['Channel'] ?? "3"; body['IPAdress'] = body['IPAdress'] ?? "10.20.10.20"; @@ -169,6 +169,8 @@ class ApiClientImp implements ApiClient { body['Latitude'] = body['Latitude'] ?? "0.0"; body['Longitude'] = body['Longitude'] ?? "0.0"; body['DeviceTypeID'] = body['DeviceTypeID'] ?? + + (Platform.isIOS ? "1" : await Utils.isGoogleServicesAvailable() ? "2" : "3"); //"LanguageID":1,"VersionID":18.7,"Channel":3,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","Latitude":0.0,"Longitude":0.0,"DeviceTypeID":2,"PatientType":1} body.removeWhere((key, value) => value == null); @@ -186,7 +188,6 @@ class ApiClientImp implements ApiClient { logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); } else { var parsed = json.decode(utf8.decode(response.bodyBytes)); - log("parsed: ${parsed.toString()}"); if (isAllowAny) { onSuccess(parsed, statusCode); diff --git a/pubspec.lock b/pubspec.lock index 98b07a4f..0695fc8d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -238,10 +238,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -835,10 +835,10 @@ packages: dependency: transitive description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" json_annotation: dependency: transitive description: @@ -875,26 +875,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -1456,10 +1456,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" time: dependency: transitive description: @@ -1584,10 +1584,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -1637,5 +1637,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.7.0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.29.0" From 89e9323d9e389a64c925f8c0823d90252d037a26 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Tue, 2 Sep 2025 09:43:45 +0300 Subject: [PATCH 07/17] merged master and updated structure --- lib/core/app_state.dart | 4 +- lib/main.dart | 4 -- lib/presentation/home/landing_page.dart | 61 ++++++++++++------------- 3 files changed, 29 insertions(+), 40 deletions(-) diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index e58ba20c..91e200ae 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -4,14 +4,12 @@ import 'package:hmg_patient_app_new/features/authentication/models/authenticated import 'package:hmg_patient_app_new/main.dart'; import 'api_consts.dart' as ApiConsts; -import 'consts.dart'; + class AppState { // Simple constructor - let get_it handle the singleton behavior AppState(); - //Tokens - bool isAuthenticated = false; bool isAuthenticated = true; diff --git a/lib/main.dart b/lib/main.dart index 7b2e93c2..3d25c0ac 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,18 +5,14 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; -import 'package:hmg_patient_app_new/providers/authentication_view_model.dart'; import 'package:hmg_patient_app_new/providers/bottom_navigation_provider.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/theme/app_theme.dart'; -import 'package:logger/logger.dart'; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; - import 'core/utils/size_utils.dart'; import 'firebase_options.dart'; diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 03970568..feb7f69b 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -1,8 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -10,15 +12,11 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; -import 'package:hmg_patient_app_new/providers/authentication_view_model.dart'; import 'package:hmg_patient_app_new/providers/bottom_navigation_provider.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:provider/provider.dart'; -import '../../core/app_assets.dart'; -import '../../core/utils/utils.dart'; -import '../../widgets/buttons/custom_button.dart'; - class LandingPage extends StatefulWidget { const LandingPage({super.key}); @@ -27,7 +25,6 @@ class LandingPage extends StatefulWidget { } class _LandingPageState extends State { - late AuthenticationViewModel authenticationViewModel; @override Widget build(BuildContext context) { @@ -80,33 +77,31 @@ class _LandingPageState extends State { ), child: Padding( padding: EdgeInsets.all(12.h), - child: Container( - child: Column( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), - SizedBox(height: 12.h), - "You do not have any upcoming appointment. Please book an appointment".toText12(isCenter: true), - SizedBox(height: 12.h), - CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (BuildContext context) => LandingPage()), - ); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40, - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - ), - ], - ), + child: Column( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), + SizedBox(height: 12.h), + "You do not have any upcoming appointment. Please book an appointment".toText12(isCenter: true), + SizedBox(height: 12.h), + CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (BuildContext context) => LandingPage()), + ); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40, + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + ), + ], ), ), ), From 6bc7af00bc3346d2a7b029e184ce2875687c7616 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Tue, 2 Sep 2025 12:31:05 +0300 Subject: [PATCH 08/17] completed the deviceapi with new architecture --- ios/Podfile.lock | 35 +- lib/core/api/api_client.dart | 532 +++++++++--------- lib/core/app_state.dart | 18 +- lib/core/common_models/generic_api_model.dart | 34 ++ lib/core/dependencies.dart | 62 +- lib/core/exceptions/api_failure.dart | 21 +- lib/core/location_util.dart | 43 +- lib/core/utils/utils.dart | 85 +-- .../authentication/authentication_repo.dart | 62 +- .../authentication_view_model.dart | 45 +- .../models/select_device_by_imei.dart | 124 ++-- lib/main.dart | 15 +- lib/presentation/home/landing_page.dart | 15 +- lib/services/dialog_service.dart | 71 +++ lib/services/error_handler_service.dart | 52 ++ lib/services/navigation_service.dart | 15 + 16 files changed, 735 insertions(+), 494 deletions(-) create mode 100644 lib/core/common_models/generic_api_model.dart create mode 100644 lib/services/dialog_service.dart create mode 100644 lib/services/error_handler_service.dart create mode 100644 lib/services/navigation_service.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5249fe53..c62a44f5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -41,11 +41,20 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter + - Firebase/Analytics (11.15.0): + - Firebase/Core + - Firebase/Core (11.15.0): + - Firebase/CoreOnly + - FirebaseAnalytics (~> 11.15.0) - Firebase/CoreOnly (11.15.0): - FirebaseCore (~> 11.15.0) - Firebase/Messaging (11.15.0): - Firebase/CoreOnly - FirebaseMessaging (~> 11.15.0) + - firebase_analytics (11.6.0): + - Firebase/Analytics (= 11.15.0) + - firebase_core + - Flutter - firebase_core (3.15.2): - Firebase/CoreOnly (= 11.15.0) - Flutter @@ -53,6 +62,24 @@ PODS: - Firebase/Messaging (= 11.15.0) - firebase_core - Flutter + - FirebaseAnalytics (11.15.0): + - FirebaseAnalytics/Default (= 11.15.0) + - FirebaseCore (~> 11.15.0) + - FirebaseInstallations (~> 11.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - FirebaseAnalytics/Default (11.15.0): + - FirebaseCore (~> 11.15.0) + - FirebaseInstallations (~> 11.0) + - GoogleAppMeasurement/Default (= 11.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) - FirebaseCore (11.15.0): - FirebaseCoreInternal (~> 11.15.0) - GoogleUtilities/Environment (~> 8.1) @@ -371,14 +398,16 @@ SPEC CHECKSUMS: DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e + firebase_analytics: bf93e20703c95030404d6ddbb1adf05bf5c3885b firebase_core: 99a37263b3c27536063a7b601d9e2a49400a433c firebase_messaging: bf6697c61f31c7cc0f654131212ff04c0115c2c7 + FirebaseAnalytics: 6433dfd311ba78084fc93bdfc145e8cb75740eae FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843 FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09 FLAnimatedImage: bbf914596368867157cc71b38a8ec834b3eeb32b - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_ios_voip_kit_karmm: 7ea37381a8841c92d186edf1f4604df5cc437579 flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f @@ -387,6 +416,8 @@ SPEC CHECKSUMS: geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321 google_maps_flutter_ios: e31555a04d1986ab130f2b9f24b6cdc861acc6d3 + GoogleAdsOnDeviceConversion: 2be6297a4f048459e0ae17fad9bfd2844e10cf64 + GoogleAppMeasurement: 700dce7541804bec33db590a5c496b663fbe2539 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 @@ -423,4 +454,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 5df9d8aa8f2c105eacd5ad7a310503d93c68c86b -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index fbf63b25..c3862e2c 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -2,21 +2,25 @@ import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'dart:io' show Platform; + import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:http/http.dart' as http; +import '../exceptions/api_failure.dart'; + abstract class ApiClient { Future post( String endPoint, { required Map body, - required Function(dynamic response, int statusCode) onSuccess, - required Function(String error, int statusCode) onFailure, + required Function(dynamic response, int statusCode, {int? messageStatus}) onSuccess, + required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, bool isAllowAny, bool isExternal, bool isRCService, @@ -32,61 +36,69 @@ abstract class ApiClient { bool isRCService, }); - Future simplePost( - String fullUrl, { - required Map body, - required Map headers, - required Function(dynamic response, int statusCode) onSuccess, - required Function(String error, int statusCode) onFailure, - }); - - Future simpleGet( - String fullUrl, { - Function(dynamic response, int statusCode)? onSuccess, - Function(String error, int statusCode)? onFailure, - Map? queryParams, - Map? headers, - }); - - Future simplePut( - String fullUrl, { - Map? body, - Map? headers, - Function(dynamic response, int statusCode)? onSuccess, - Function(String error, int statusCode)? onFailure, - }); - - Future simpleDelete( - String fullUrl, { - Function(dynamic response, int statusCode)? onSuccess, - Function(String error, int statusCode)? onFailure, - Map? queryParams, - Map? headers, - }); - - Future handleUnauthorized(int statusCode, {required String forUrl}); - String getSessionId(String id); - Future generatePackagesToken(); +// Future simplePost( +// String fullUrl, { +// required Map body, +// required Map headers, +// required Function(dynamic response, int statusCode) onSuccess, +// required Function(String error, int statusCode) onFailure, +// }); + +// +// Future simpleGet( +// String fullUrl, { +// Function(dynamic response, int statusCode)? onSuccess, +// Function(String error, int statusCode)? onFailure, +// Map? queryParams, +// Map? headers, +// }); +// +// Future simplePut( +// String fullUrl, { +// Map? body, +// Map? headers, +// Function(dynamic response, int statusCode)? onSuccess, +// Function(String error, int statusCode)? onFailure, +// }); +// +// Future simpleDelete( +// String fullUrl, { +// Function(dynamic response, int statusCode)? onSuccess, +// Function(String error, int statusCode)? onFailure, +// Map? queryParams, +// Map? headers, +// }); + +// Future handleUnauthorized(int statusCode, {required String forUrl}); +// Future generatePackagesToken(); } class ApiClientImp implements ApiClient { final _analytics = getIt(); final LoggerService loggerService; + final AppState appState; + final DialogService dialogService; - ApiClientImp({required this.loggerService}); + ApiClientImp({ + required this.loggerService, + required this.dialogService, + required this.appState, + }); @override - post(String endPoint, - {required Map body, - required Function(dynamic response, int statusCode) onSuccess, - required Function(String error, int statusCode) onFailure, - bool isAllowAny = false, - bool isExternal = false, - bool isRCService = false, - bool bypassConnectionCheck = false}) async { + post( + String endPoint, { + required Map body, + required Function(dynamic response, int statusCode, {int? messageStatus}) onSuccess, + required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, + bool isAllowAny = false, + bool isExternal = false, + bool isRCService = false, + bool bypassConnectionCheck = false, + }) async { AppState appState = getIt.get(); String url; if (isExternal) { @@ -103,7 +115,7 @@ class ApiClientImp implements ApiClient { Map headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}; if (!isExternal) { String? token = appState.appAuthToken; - String? languageID = (appState.postParamsObject?.languageID == 1 ? 'ar' : 'en') ?? 'ar'; + String? languageID = (appState.postParamsObject?.languageID == 1 ? 'ar' : 'en'); if (endPoint == ApiConsts.sendActivationCode) { languageID = 'en'; } @@ -113,7 +125,6 @@ class ApiClientImp implements ApiClient { if (body.containsKey('LanguageID')) { if (body['LanguageID'] != null) { - //change this line because language issue happened on dental body['LanguageID'] = body['LanguageID'] == 'ar' ? 1 : body['LanguageID'] == 'en' @@ -128,7 +139,6 @@ class ApiClientImp implements ApiClient { : IS_DENTAL_ALLOWED_BACKEND; } - //Todo: I have converted it to string body['DeviceTypeID'] = Platform.isIOS ? "1" @@ -160,40 +170,38 @@ class ApiClientImp implements ApiClient { } } - - body['LanguageID'] = body['LanguageID'] ?? "2"; - body['VersionID'] = body['VersionID'] ?? "18.7"; - body['Channel'] = body['Channel'] ?? "3"; - body['IPAdress'] = body['IPAdress'] ?? "10.20.10.20"; - body['generalid'] = body['generalid'] ?? "Cs2020@2016\$2958"; - body['Latitude'] = body['Latitude'] ?? "0.0"; - body['Longitude'] = body['Longitude'] ?? "0.0"; + body['LanguageID'] = body['LanguageID'] ?? "2"; + body['VersionID'] = body['VersionID'] ?? "18.7"; + body['Channel'] = body['Channel'] ?? "3"; + body['IPAdress'] = body['IPAdress'] ?? "10.20.10.20"; + body['generalid'] = body['generalid'] ?? "Cs2020@2016\$2958"; + body['Latitude'] = body['Latitude'] ?? "0.0"; + body['Longitude'] = body['Longitude'] ?? "0.0"; body['DeviceTypeID'] = body['DeviceTypeID'] ?? - - - (Platform.isIOS ? "1" : await Utils.isGoogleServicesAvailable() ? "2" : "3"); - //"LanguageID":1,"VersionID":18.7,"Channel":3,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","Latitude":0.0,"Longitude":0.0,"DeviceTypeID":2,"PatientType":1} + (Platform.isIOS + ? "1" + : await Utils.isGoogleServicesAvailable() + ? "2" + : "3"); body.removeWhere((key, value) => value == null); - - log("bodi: ${json.encode(body)}"); - log("bodi: ${Uri.parse(url.trim())}"); + log("body: ${json.encode(body)}"); + log("uri: ${Uri.parse(url.trim())}"); if (await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck)) { - final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers); final int statusCode = response.statusCode; if (statusCode < 200 || statusCode >= 400) { - onFailure('Error While Fetching data', statusCode); + onFailure('Error While Fetching data', statusCode, failureType: ServerFailure("Error While Fetching data")); logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); } else { - var parsed = json.decode(utf8.decode(response.bodyBytes)); + var parsed = json.decode(utf8.decode(response.bodyBytes)); log("parsed: ${parsed.toString()}"); if (isAllowAny) { onSuccess(parsed, statusCode); } else { if (parsed['Response_Message'] != null) { - onSuccess(parsed, statusCode); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus']); } else { if (parsed['ErrorType'] == 4) { //TODO : handle app update @@ -204,19 +212,18 @@ class ApiClientImp implements ApiClient { logApiEndpointError(endPoint, "session logged out", statusCode); } if (isAllowAny) { - onSuccess(parsed, statusCode); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus']); } else if (parsed['IsAuthenticated'] == null) { if (parsed['isSMSSent'] == true) { - onSuccess(parsed, statusCode); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus']); } else if (parsed['MessageStatus'] == 1) { onSuccess(parsed, statusCode); } else if (parsed['Result'] == 'OK') { onSuccess(parsed, statusCode); } else { - - onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, + failureType: ServerFailure("Error While Fetching data")); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); - } } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { onSuccess(parsed, statusCode); @@ -226,28 +233,46 @@ class ApiClientImp implements ApiClient { } else { if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) { if (parsed['ErrorSearchMsg'] == null) { - onFailure("Server Error found with no available message", statusCode); + onFailure( + "Server Error found with no available message", + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); logApiEndpointError(endPoint, "Server Error found with no available message", statusCode); } else { - onFailure(parsed['ErrorSearchMsg'], statusCode); + onFailure( + parsed['ErrorSearchMsg'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); logApiEndpointError(endPoint, parsed['ErrorSearchMsg'], statusCode); } } else { - onFailure(parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + onFailure( + parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); } } - } - - else { + } else { if (parsed['SameClinicApptList'] != null) { onSuccess(parsed, statusCode); } else { if (parsed['message'] != null) { - onFailure(parsed['message'] ?? parsed['message'], statusCode); + onFailure( + parsed['message'] ?? parsed['message'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); } else { - onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + onFailure( + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } } @@ -256,13 +281,17 @@ class ApiClientImp implements ApiClient { } } } else { - onFailure('Please Check The Internet Connection 1', -1); + onFailure( + 'Please Check The Internet Connection 1', + -1, + failureType: ConnectivityFailure("Error While Fetching data"), + ); _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } } catch (e) { loggerService.errorLogs(e.toString()); if (e.toString().contains("ClientException")) { - onFailure('Something went wrong, plase try again', -1); + onFailure('Something went wrong, plase try again', -1, failureType: InvalidCredentials('Something went wrong, plase try again')); _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } else { onFailure(e.toString(), -1); @@ -271,6 +300,7 @@ class ApiClientImp implements ApiClient { } } + @override get(String endPoint, {required Function(dynamic response, int statusCode) onSuccess, required Function(String error, int statusCode) onFailure, @@ -304,203 +334,173 @@ class ApiClientImp implements ApiClient { // print("statusCode :$statusCode"); if (statusCode < 200 || statusCode >= 400) { - onFailure!('Error While Fetching data', statusCode); + onFailure('Error While Fetching data', statusCode); logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); } else { var parsed = json.decode(utf8.decode(response.bodyBytes)); - onSuccess!(parsed, statusCode); - } - } else { - onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); - } - } - - simplePost( - String fullUrl, { - required Map body, - required Map headers, - required Function(dynamic response, int statusCode) onSuccess, - required Function(String error, int statusCode) onFailure, - }) async { - String url = fullUrl; - // print("URL Query String: $url"); - // print("body: $body"); - - if (await Utils.checkConnection()) { - headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); - final response = await http.post( - Uri.parse(url.trim()), - body: json.encode(body), - headers: headers, - ); - final int statusCode = response.statusCode; - // print("statusCode :$statusCode"); - if (await handleUnauthorized(statusCode, forUrl: fullUrl)) - simplePost(fullUrl, onFailure: onFailure, onSuccess: onSuccess, body: body, headers: headers); - - // print(response.body.toString()); - - if (statusCode < 200 || statusCode >= 400) { - onFailure!('Error While Fetching data', statusCode); - logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); - } else { - onSuccess!(response.body.toString(), statusCode); + onSuccess(parsed, statusCode); } } else { - onFailure!('Please Check The Internet Connection', -1); + onFailure('Please Check The Internet Connection', -1); _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); } } - simpleGet(String fullUrl, - {Function(dynamic response, int statusCode)? onSuccess, - Function(String error, int statusCode)? onFailure, - Map? queryParams, - Map? headers}) async { - headers = headers ?? {}; - String url = fullUrl; - - var haveParams = (queryParams != null); - if (haveParams) { - String queryString = Uri(queryParameters: queryParams).query; - url += '?$queryString'; - // print("URL Query String: $url"); - } - - if (await Utils.checkConnection()) { - headers.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); - final response = await http.get( - Uri.parse(url.trim()), - headers: headers, - ); - - final int statusCode = response.statusCode; - // print("statusCode :$statusCode"); - if (await handleUnauthorized(statusCode, forUrl: fullUrl)) - simpleGet(fullUrl, onFailure: onFailure, onSuccess: onSuccess, headers: headers, queryParams: queryParams); - - if (statusCode < 200 || statusCode >= 400) { - onFailure!('Error While Fetching data', statusCode); - logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); - } else { - onSuccess!(response.body.toString(), statusCode); - } - } else { - onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); - } - } - - simplePut(String fullUrl, - {Map? body, - Map? headers, - Function(dynamic response, int statusCode)? onSuccess, - Function(String error, int statusCode)? onFailure}) async { - String url = fullUrl; - // print("URL Query String: $url"); - - if (await Utils.checkConnection()) { - headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); - final response = await http.put( - Uri.parse(url.trim()), - body: json.encode(body), - headers: headers, - ); - - final int statusCode = response.statusCode; - // print("statusCode :$statusCode"); - if (await handleUnauthorized(statusCode, forUrl: fullUrl)) - simplePut(fullUrl, onFailure: onFailure, onSuccess: onSuccess, headers: headers, body: body); - - if (statusCode < 200 || statusCode >= 400) { - onFailure!('Error While Fetching data', statusCode); - logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); - } else { - onSuccess!(response.body.toString(), statusCode); - } - } else { - onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); - } - } - - simpleDelete(String fullUrl, - {Function(dynamic response, int statusCode)? onSuccess, - Function(String error, int statusCode)? onFailure, - Map? queryParams, - Map? headers}) async { - String url = fullUrl; - // print("URL Query String: $url"); - - var haveParams = (queryParams != null); - if (haveParams) { - String queryString = Uri(queryParameters: queryParams).query; - url += '?$queryString'; - // print("URL Query String: $url"); - } - - if (await Utils.checkConnection()) { - headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); - final response = await http.delete( - Uri.parse(url.trim()), - headers: headers, - ); - - final int statusCode = response.statusCode; - // print("statusCode :$statusCode"); - if (await handleUnauthorized(statusCode, forUrl: fullUrl)) - simpleDelete(fullUrl, onFailure: onFailure, onSuccess: onSuccess, queryParams: queryParams, headers: headers); - - if (statusCode < 200 || statusCode >= 400) { - onFailure!('Error While Fetching data', statusCode); - logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); - } else { - onSuccess!(response.body.toString(), statusCode); - } - } else { - onFailure!('Please Check The Internet Connection', -1); - _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); - } - } - - Future handleUnauthorized(int statusCode, {required String forUrl}) async { - if (forUrl.startsWith(EXA_CART_API_BASE_URL) && statusCode == 401) { - final token = await generatePackagesToken(); - ApiConsts.packagesAuthHeader['Authorization'] = 'Bearer $token'; - return (token is String); - } - return false; - } + // @override + // simplePost( + // String fullUrl, { + // required Map body, + // required Map headers, + // required Function(dynamic response, int statusCode) onSuccess, + // required Function(String error, int statusCode) onFailure, + // }) async { + // String url = fullUrl; + // // print("URL Query String: $url"); + // // print("body: $body"); + // + // if (await Utils.checkConnection()) { + // headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); + // final response = await http.post( + // Uri.parse(url.trim()), + // body: json.encode(body), + // headers: headers, + // ); + // final int statusCode = response.statusCode; + // // print("statusCode :$statusCode"); + // if (await handleUnauthorized(statusCode, forUrl: fullUrl)) { + // simplePost(fullUrl, onFailure: onFailure, onSuccess: onSuccess, body: body, headers: headers); + // } + // + // // print(response.body.toString()); + // + // if (statusCode < 200 || statusCode >= 400) { + // onFailure!('Error While Fetching data', statusCode); + // logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); + // } else { + // onSuccess!(response.body.toString(), statusCode); + // } + // } else { + // onFailure!('Please Check The Internet Connection', -1); + // _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); + // } + // } + + // simpleGet(String fullUrl, + // {Function(dynamic response, int statusCode)? onSuccess, + // Function(String error, int statusCode)? onFailure, + // Map? queryParams, + // Map? headers}) async { + // headers = headers ?? {}; + // String url = fullUrl; + // + // var haveParams = (queryParams != null); + // if (haveParams) { + // String queryString = Uri(queryParameters: queryParams).query; + // url += '?$queryString'; + // // print("URL Query String: $url"); + // } + // + // if (await Utils.checkConnection()) { + // headers.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); + // final response = await http.get( + // Uri.parse(url.trim()), + // headers: headers, + // ); + // + // final int statusCode = response.statusCode; + // // print("statusCode :$statusCode"); + // if (await handleUnauthorized(statusCode, forUrl: fullUrl)) + // simpleGet(fullUrl, onFailure: onFailure, onSuccess: onSuccess, headers: headers, queryParams: queryParams); + // + // if (statusCode < 200 || statusCode >= 400) { + // onFailure!('Error While Fetching data', statusCode); + // logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); + // } else { + // onSuccess!(response.body.toString(), statusCode); + // } + // } else { + // onFailure!('Please Check The Internet Connection', -1); + // _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); + // } + // } + + // simplePut(String fullUrl, + // {Map? body, + // Map? headers, + // Function(dynamic response, int statusCode)? onSuccess, + // Function(String error, int statusCode)? onFailure}) async { + // String url = fullUrl; + // // print("URL Query String: $url"); + // + // if (await Utils.checkConnection()) { + // headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); + // final response = await http.put( + // Uri.parse(url.trim()), + // body: json.encode(body), + // headers: headers, + // ); + // + // final int statusCode = response.statusCode; + // // print("statusCode :$statusCode"); + // if (await handleUnauthorized(statusCode, forUrl: fullUrl)) + // simplePut(fullUrl, onFailure: onFailure, onSuccess: onSuccess, headers: headers, body: body); + // + // if (statusCode < 200 || statusCode >= 400) { + // onFailure!('Error While Fetching data', statusCode); + // logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); + // } else { + // onSuccess!(response.body.toString(), statusCode); + // } + // } else { + // onFailure!('Please Check The Internet Connection', -1); + // _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); + // } + // } + // + // simpleDelete(String fullUrl, + // {Function(dynamic response, int statusCode)? onSuccess, + // Function(String error, int statusCode)? onFailure, + // Map? queryParams, + // Map? headers}) async { + // String url = fullUrl; + // // print("URL Query String: $url"); + // + // var haveParams = (queryParams != null); + // if (haveParams) { + // String queryString = Uri(queryParameters: queryParams).query; + // url += '?$queryString'; + // // print("URL Query String: $url"); + // } + // + // if (await Utils.checkConnection()) { + // headers!.addAll({'Content-Type': 'application/json', 'Accept': 'application/json'}); + // final response = await http.delete( + // Uri.parse(url.trim()), + // headers: headers, + // ); + // + // final int statusCode = response.statusCode; + // // print("statusCode :$statusCode"); + // if (await handleUnauthorized(statusCode, forUrl: fullUrl)) + // simpleDelete(fullUrl, onFailure: onFailure, onSuccess: onSuccess, queryParams: queryParams, headers: headers); + // + // if (statusCode < 200 || statusCode >= 400) { + // onFailure!('Error While Fetching data', statusCode); + // logApiFullUrlError(fullUrl, 'Error While Fetching data', statusCode); + // } else { + // onSuccess!(response.body.toString(), statusCode); + // } + // } else { + // onFailure!('Please Check The Internet Connection', -1); + // _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); + // } + // } + @override String getSessionId(String id) { return id.replaceAll(RegExp('/[^a-zA-Z]'), ''); } - Future generatePackagesToken() async { - var url = EXA_CART_API_BASE_URL + PACKAGES_TOKEN; - var body = { - "api_client": { - "client_id": "a4ab6be4-424f-4836-b032-46caed88e184", - "client_secret": "3c1a3e07-4a40-4510-9fb0-ee5f0a72752c" - } - }; - String? token; - final completer = Completer(); - simplePost(url, body: body, headers: {}, onSuccess: (dynamic stringResponse, int statusCode) { - if (statusCode == 200) { - var jsonResponse = json.decode(stringResponse); - token = jsonResponse['auth_token']; - completer.complete(); - } - }, onFailure: (String error, int statusCode) { - completer.complete(); - logApiFullUrlError(url, error, statusCode); - }); - await completer.future; - return token!; - } - logApiFullUrlError(String fullUrl, error, code) { final endpoint = Uri.parse(fullUrl).pathSegments.last; logApiEndpointError(endpoint, error, code); diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 91e200ae..bdf8d257 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -1,15 +1,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:hmg_patient_app_new/core/post_params_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/authenticated_user_model.dart'; -import 'package:hmg_patient_app_new/main.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'api_consts.dart' as ApiConsts; - class AppState { - // Simple constructor - let get_it handle the singleton behavior - AppState(); + NavigationService navigationService; + AppState({required this.navigationService}); bool isAuthenticated = true; @@ -40,9 +39,7 @@ class AppState { PostParamsModel? get postParamsObject => _postParams; - Map get postParamsJson => isAuthenticated - ? (_postParams?.toJsonAfterLogin() ?? {}) - : (_postParams?.toJson() ?? {}); + Map get postParamsJson => isAuthenticated ? (_postParams?.toJsonAfterLogin() ?? {}) : (_postParams?.toJson() ?? {}); void setPostParamsModel(PostParamsModel _postParams) { this._postParams = _postParams; @@ -51,12 +48,9 @@ class AppState { double userLat = 0.0; double userLong = 0.0; - bool isArabic() => - EasyLocalization.of(navigatorKey.currentContext!)?.locale.languageCode == - "ar"; + bool isArabic() => EasyLocalization.of(navigationService.navigatorKey.currentContext!)?.locale.languageCode == "ar"; - int getLanguageID(context) => - EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2; + int getLanguageID(context) => EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2; AuthenticatedUser? _authenticatedUser; diff --git a/lib/core/common_models/generic_api_model.dart b/lib/core/common_models/generic_api_model.dart new file mode 100644 index 00000000..1ab11db2 --- /dev/null +++ b/lib/core/common_models/generic_api_model.dart @@ -0,0 +1,34 @@ +class GenericApiModel { + final int? messageStatus; + final String? errorMessage; + final int? statusCode; + final T? data; + + GenericApiModel({ + this.messageStatus, + this.errorMessage, + this.statusCode, + this.data, + }); + + factory GenericApiModel.fromJson( + Map json, + T Function(Object? json)? fromJsonT, + ) { + return GenericApiModel( + messageStatus: json['messageStatus'] as int?, + errorMessage: json['errorMessage'] as String?, + statusCode: json['statusCode'] as int?, + data: fromJsonT != null ? fromJsonT(json['data']) : json['data'] as T?, + ); + } + + Map toJson(Object Function(T value)? toJsonT) { + return { + 'messageStatus': messageStatus, + 'errorMessage': errorMessage, + 'statusCode': statusCode, + 'data': toJsonT != null && data != null ? toJsonT(data as T) : data, + }; + } +} diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 440cd265..5aba3e24 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -1,13 +1,18 @@ import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -15,25 +20,58 @@ GetIt getIt = GetIt.instance; class AppDependencies { static Future addDependencies() async { - // Services - getIt.registerLazySingleton(() => LoggerServiceImp(logger: Logger(printer: PrettyPrinter( - methodCount: 2, // number of stack trace lines - errorMethodCount: 5, // number of stack trace lines for errors - lineLength: 100, // wrap width - colors: true, // colorful logs - printEmojis: true, // include emojis - ),))); + Logger logger = Logger( + printer: PrettyPrinter( + methodCount: 2, + errorMethodCount: 5, + lineLength: 100, + colors: true, + printEmojis: true, + ), + ); + + // Core Services + getIt.registerLazySingleton(() => LoggerServiceImp(logger: logger)); + getIt.registerLazySingleton(() => NavigationService()); + getIt.registerLazySingleton(() => GAnalytics()); + getIt.registerLazySingleton(() => AppState(navigationService: getIt())); + getIt.registerLazySingleton(() => LocationUtils(isShowConfirmDialog: false, navigationService: getIt())); + getIt.registerLazySingleton(() => DialogServiceImp(navigationService: getIt())); + getIt.registerLazySingleton(() => ErrorHandlerServiceImp( + dialogService: getIt(), + loggerService: getIt(), + navigationService: getIt(), + )); + final sharedPreferences = await SharedPreferences.getInstance(); getIt.registerLazySingleton(() => CacheServiceImp(sharedPreferences: sharedPreferences)); - getIt.registerSingleton(AppState()); - getIt.registerSingleton(GAnalytics()); - getIt.registerLazySingleton(() => ApiClientImp(loggerService: getIt())); + getIt.registerLazySingleton(() => ApiClientImp(loggerService: getIt(), dialogService: getIt(), appState: getIt())); // Repositories getIt.registerLazySingleton(() => CommonRepoImp(loggerService: getIt())); getIt.registerLazySingleton(() => AuthenticationRepoImp(loggerService: getIt(), apiClient: getIt())); - getIt.registerLazySingleton(() => BookAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton( + () => BookAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MyAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); + // ViewModels + // Global/shared VMs → LazySingleton + getIt.registerLazySingleton( + () => AuthenticationViewModel( + authenticationRepo: getIt(), + dialogService: getIt(), + appState: getIt(), + errorHandlerService: getIt(), + ), + ); + + // Screen-specific VMs → Factory + // getIt.registerFactory( + // () => BookAppointmentsViewModel( + // bookAppointmentsRepo: getIt(), + // dialogService: getIt(), + // errorHandlerService: getIt(), + // ), + // ); } } diff --git a/lib/core/exceptions/api_failure.dart b/lib/core/exceptions/api_failure.dart index eaa434e3..d2a510f0 100644 --- a/lib/core/exceptions/api_failure.dart +++ b/lib/core/exceptions/api_failure.dart @@ -1,8 +1,8 @@ - import 'package:equatable/equatable.dart'; abstract class Failure extends Equatable implements Exception { final String message; + const Failure(this.message); } @@ -27,15 +27,30 @@ class LocalStorageFailure extends Failure { List get props => [message]; } +class DataParsingFailure extends Failure { + const DataParsingFailure(super.message); + + @override + List get props => [message]; +} + +class UnknownFailure extends Failure { + const UnknownFailure(super.message); + + @override + List get props => [message]; +} + + class DuplicateUsername extends Failure { - const DuplicateUsername({String? message}) : super(message ?? ''); + const DuplicateUsername(String? message) : super(message ?? ''); @override List get props => [message]; } class InvalidCredentials extends Failure { - const InvalidCredentials({String? message}) : super(message ?? ''); + const InvalidCredentials(String? message) : super(message ?? ''); @override List get props => [message]; diff --git a/lib/core/location_util.dart b/lib/core/location_util.dart index d9716a9b..c8e69afd 100644 --- a/lib/core/location_util.dart +++ b/lib/core/location_util.dart @@ -1,41 +1,40 @@ import 'dart:io'; +import 'package:geolocator/geolocator.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/consts.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:geolocator/geolocator.dart'; -import 'package:google_maps_flutter/google_maps_flutter.dart'; - +import 'package:hmg_patient_app_new/services/navigation_service.dart'; // import 'package:huawei_location/huawei_location.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:provider/provider.dart'; class LocationUtils { + NavigationService navigationService; bool isShowConfirmDialog; bool isShowLocationTimeoutDialog; - BuildContext context; bool isHuawei; final GeolocatorPlatform _geolocatorPlatform = GeolocatorPlatform.instance; - LocationUtils({required this.isShowConfirmDialog, required this.context, this.isHuawei = false, this.isShowLocationTimeoutDialog = true}); + LocationUtils({ + required this.isShowConfirmDialog, + required this.navigationService, + this.isHuawei = false, + this.isShowLocationTimeoutDialog = true, + }); + AppState appState = getIt.get(); void getCurrentLocation({Function(LatLng)? callBack}) async { Geolocator.isLocationServiceEnabled().then((value) async { if (value) { await Geolocator.checkPermission().then((permission) async { if (permission == LocationPermission.always || permission == LocationPermission.whileInUse) { - // Geolocator.getCurrentPosition(locationSettings: LocationSettings(accuracy: LocationAccuracy.medium, timeLimit: Duration(seconds: 5))).then((value) { Geolocator.getLastKnownPosition().then((value) { setLocation(value); if (callBack != null) callBack(LatLng(value?.latitude ?? 24.7101433, value?.longitude ?? 46.6757709)); }).catchError((err) { - print(err); - if (isShowConfirmDialog && isShowLocationTimeoutDialog) { - // showLocationTimeOutDialog(failureCallBack: () { - // Geolocator.openAppSettings(); - // }); - } + if (isShowConfirmDialog && isShowLocationTimeoutDialog) {} }); } @@ -62,15 +61,11 @@ class LocationUtils { } } } - }).catchError((err) { - print(err); - }); + }).catchError((err) {}); } else { if (isShowConfirmDialog) showErrorLocationDialog(false, failureCallBack: () {}); } - }).catchError((err) { - print(err); - }); + }).catchError((err) {}); } Future checkIfGPSIsEnabled() async { @@ -151,8 +146,8 @@ class LocationUtils { Utils.saveNumFromPrefs(SharedPrefsConsts.user_lat, position?.latitude ?? 0.0); Utils.saveNumFromPrefs(SharedPrefsConsts.user_lat, position?.longitude ?? 0.0); - AppState().setUserLat = position?.latitude ?? 0.0; - AppState().setUserLong = position?.longitude ?? 0.0; + appState.setUserLat = position?.latitude ?? 0.0; + appState.setUserLong = position?.longitude ?? 0.0; // projectViewModel.setLatitudeLongitude(position?.latitude ?? 0.0, position?.longitude ?? 0.0); } @@ -161,8 +156,8 @@ class LocationUtils { Utils.saveNumFromPrefs(SharedPrefsConsts.user_lat, 0.0); Utils.saveNumFromPrefs(SharedPrefsConsts.user_lat, 0.0); - AppState().setUserLat = 0.0; - AppState().setUserLong = 0.0; + appState.setUserLat = 0.0; + appState.setUserLong = 0.0; } Future requestPermissions() async { diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 4f7b398c..14e91f87 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -1,24 +1,28 @@ import 'dart:convert'; -import 'package:crypto/crypto.dart' as crypto; import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:crypto/crypto.dart' as crypto; +import 'package:easy_localization/easy_localization.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:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/main.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/dialogs/confirm_dialog.dart'; import 'package:hmg_patient_app_new/widgets/loading_dialog.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:lottie/lottie.dart'; import 'package:shared_preferences/shared_preferences.dart'; class Utils { + static AppState appState = getIt.get(); + static NavigationService navigationService = getIt.get(); + static bool _isLoadingVisible = false; static bool get isLoading => _isLoadingVisible; @@ -47,14 +51,16 @@ class Utils { } static String getFreeSlotsTimeText(String startTime, {bool isAddHours = false}) { - // return DateFormat('hh:mm a', AppState().isArabic() ? "ar_SA" : "en_US").format(DateTime.tryParse(startTime)!.add( + // return DateFormat('hh:mm a', appState.isArabic() ? "ar_SA" : "en_US").format(DateTime.tryParse(startTime)!.add( // Duration( // hours: isAddHours ? 3 : 0, // ), // )); return !isAddHours - ? DateFormat('hh:mm a', AppState().isArabic() ? "ar_SA" : "en_US").format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.toLocal()) - : DateFormat('hh:mm a', AppState().isArabic() ? "ar_SA" : "en_US").format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.add( + ? DateFormat('hh:mm a', appState.isArabic() ? "ar_SA" : "en_US") + .format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.toLocal()) + : DateFormat('hh:mm a', appState.isArabic() ? "ar_SA" : "en_US") + .format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.add( Duration( hours: isAddHours ? 3 : 0, ), @@ -82,12 +88,9 @@ class Utils { } static String getMonthDayYearDateFormatted(DateTime dateTime) { - if (dateTime != null) - return AppState().isArabic() - ? getMonthArabic(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString() - : getMonth(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString(); - else - return ""; + return appState.isArabic() + ? getMonthArabic(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString() + : getMonth(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString(); } /// get month by @@ -200,7 +203,7 @@ class Utils { static void showLoadingDialog() { _isLoadingVisible = true; showDialog( - context: navigatorKey.currentContext!, + context: navigationService.navigatorKey.currentContext!, barrierColor: Colors.black.withOpacity(0.5), builder: (BuildContext context) => LoadingDialog(), ) @@ -217,7 +220,7 @@ class Utils { try { if (_isLoadingVisible) { _isLoadingVisible = false; - Navigator.of(navigatorKey.currentContext!).pop(); + Navigator.of(navigationService.navigatorKey.currentContext!).pop(); } _isLoadingVisible = false; } catch (e) {} @@ -242,9 +245,6 @@ class Utils { static bool isSAUDIIDValid(String id, type) { if (type == 1) { - if (id == null) { - return false; - } try { id = id.toString(); id = id.trim(); @@ -304,13 +304,15 @@ class Utils { } static String removeHtmlTags(String htmlString) { - if (htmlString == null || htmlString.isEmpty) { + if (htmlString.isEmpty) { return ''; } // Replace HTML line breaks with newlines - var withLineBreaks = - htmlString.replaceAll(RegExp(r'', multiLine: true), '\n').replaceAll(RegExp(r'<\/p>', multiLine: true), '\n').replaceAll(RegExp(r'', multiLine: true), '\n'); + var withLineBreaks = htmlString + .replaceAll(RegExp(r'', multiLine: true), '\n') + .replaceAll(RegExp(r'<\/p>', multiLine: true), '\n') + .replaceAll(RegExp(r'', multiLine: true), '\n'); // Remove all other HTML tags var withoutTags = withLineBreaks.replaceAll(RegExp(r'<[^>]*>'), ''); @@ -376,7 +378,20 @@ class Utils { final year = parts[0]; // Map month number to short month name (Hijri months) - const hijriMonthNames = ['Muharram', 'Safar', 'Rabi I', 'Rabi II', 'Jumada I', 'Jumada II', 'Rajab', 'Sha\'ban', 'Ramadan', 'Shawwal', 'Dhu al-Qi\'dah', 'Dhu al-Hijjah']; + const hijriMonthNames = [ + 'Muharram', + 'Safar', + 'Rabi I', + 'Rabi II', + 'Jumada I', + 'Jumada II', + 'Rajab', + 'Sha\'ban', + 'Ramadan', + 'Shawwal', + 'Dhu al-Qi\'dah', + 'Dhu al-Hijjah' + ]; final monthIndex = int.tryParse(parts[1]) ?? 1; final month = hijriMonthNames[monthIndex - 1]; @@ -458,13 +473,8 @@ class Utils { ); } - - - static Future isGoogleServicesAvailable() async { - GooglePlayServicesAvailability availability = await GoogleApiAvailability - .instance - .checkGooglePlayServicesAvailability(); + GooglePlayServicesAvailability availability = await GoogleApiAvailability.instance.checkGooglePlayServicesAvailability(); String status = availability.toString().split('.').last; if (status == "success") { return true; @@ -472,26 +482,17 @@ class Utils { return false; } - - - - - static Future checkConnection( - {bool bypassConnectionCheck = false}) async { + static Future checkConnection({bool bypassConnectionCheck = false}) async { if (bypassConnectionCheck) return true; - List connectivityResult = - await (Connectivity().checkConnectivity()); - if (connectivityResult.contains(ConnectivityResult.mobile) || - connectivityResult.contains(ConnectivityResult.wifi)) { + List connectivityResult = await (Connectivity().checkConnectivity()); + if (connectivityResult.contains(ConnectivityResult.mobile) || connectivityResult.contains(ConnectivityResult.wifi)) { return true; } else { return false; } } - static String generateMd5Hash(String input) { return crypto.md5.convert(utf8.encode(input)).toString(); } - } diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index e544638d..04e3a9e2 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -1,16 +1,15 @@ import 'dart:async'; -import 'dart:developer'; import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; -import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/authentication/models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class AuthenticationRepo { - Future> selectDeviceByImei({required String firebaseToken}); + Future>> selectDeviceByImei({required String firebaseToken}); } class AuthenticationRepoImp implements AuthenticationRepo { @@ -20,38 +19,43 @@ class AuthenticationRepoImp implements AuthenticationRepo { AuthenticationRepoImp({required this.loggerService, required this.apiClient}); @override - Future> selectDeviceByImei({ + Future>> selectDeviceByImei({ required String firebaseToken, }) async { final mapDevice = {"IMEI": firebaseToken}; try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.selectDeviceImei, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus}) { + try { + final list = response['Patient_SELECTDeviceIMEIbyIMEIList'] as List?; + if (list == null || list.isEmpty) { + throw Exception("Device list is empty"); + } - final completer = Completer>(); - await apiClient.post( - ApiConsts.selectDeviceImei, - body: mapDevice, - onSuccess: (response, statusCode) { - try { - final SelectDeviceByImeiRespModelElement model = - SelectDeviceByImeiRespModelElement.fromJson(response['Patient_SELECTDeviceIMEIbyIMEIList'][0]); - completer.complete(Right(model)); - } catch (e) { - completer.complete(Left(ServerFailure(e.toString()))); - } - }, - onFailure: (error, statusCode) { - loggerService.logInfo(("$error - $statusCode").toString()); - completer.complete(Left(ServerFailure(error))); - }, - ); - - return await completer.future; - } on APIException catch (e) { - loggerService.errorLogs(e.toString()); - return Left(ServerFailure(e.message)); + final model = SelectDeviceByImeiRespModelElement.fromJson(list[0] as Map); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: model, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); } catch (e) { - loggerService.errorLogs(e.toString()); - return Left(ServerFailure(e.toString())); + return Left(UnknownFailure(e.toString())); } } } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 8269c72c..eaf3c950 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1,46 +1,37 @@ -import 'dart:developer'; -import 'dart:io'; - import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; -import 'package:hmg_patient_app_new/features/authentication/models/check_activation_code_request_register.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class AuthenticationViewModel extends ChangeNotifier { AuthenticationRepo authenticationRepo; AppState appState; + ErrorHandlerService errorHandlerService; + DialogService dialogService; AuthenticationViewModel({ required this.appState, required this.authenticationRepo, + required this.errorHandlerService, + required this.dialogService, }); final TextEditingController nationalIdController = TextEditingController(); final TextEditingController phoneNumberController = TextEditingController(); - Future selectDeviceImei({ - Function(dynamic)? onSuccess, - Function(String)? onError - }) async { - final String firebaseToken = - "cIkkB7h7Q7uoFkC4Qv82xG:APA91bEb53Z9XzqymCIctaLxCoMX6bm9fuKlWILQ59uUqfwhCoD42AOP1-jWGB1WYd9BVN5PT2pUUFxrT07vcNg1KH9OH39mrPgCl0m21XVIgWrzNnCkufg"; - - final resultEither = - await authenticationRepo.selectDeviceByImei(firebaseToken: firebaseToken); - - resultEither.fold( - (failure) { - notifyListeners(); - if (onError != null) onError(failure.message); - }, - (data) { - - log("resultEither: ${data.toString()} "); - - notifyListeners(); - if (onSuccess != null) onSuccess(data); + Future selectDeviceImei({Function(dynamic)? onSuccess, Function(String)? onError}) async { + String firebaseToken = + "dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc"; + final result = await authenticationRepo.selectDeviceByImei(firebaseToken: firebaseToken); + result.fold( + (failure) async => await errorHandlerService.handleError(failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + dialogService.showErrorDialog(apiResponse.errorMessage!); + } else if (apiResponse.messageStatus == 1) { + // move to next api call + } }, ); } diff --git a/lib/features/authentication/models/select_device_by_imei.dart b/lib/features/authentication/models/select_device_by_imei.dart index 2ab669e2..398f7918 100644 --- a/lib/features/authentication/models/select_device_by_imei.dart +++ b/lib/features/authentication/models/select_device_by_imei.dart @@ -1,77 +1,73 @@ -// To parse this JSON data, do -// -// final selectDeviceByImeiRespModel = selectDeviceByImeiRespModelFromJson(jsonString); - import 'dart:convert'; -Map selectDeviceByImeiRespModelFromJson(String str) => Map.from(json.decode(str)).map((k, v) => MapEntry(k, v)); +Map selectDeviceByImeiRespModelFromJson(String str) => Map.from(json.decode(str)); -String selectDeviceByImeiRespModelToJson(Map data) => json.encode(Map.from(data).map((k, v) => MapEntry(k, v))); +String selectDeviceByImeiRespModelToJson(Map data) => json.encode(Map.from(data)); class SelectDeviceByImeiRespModelElement { - int id; - String imei; - int logInType; - int patientId; - bool outSa; - String mobile; - String identificationNo; - String name; - String nameN; - String createdOn; - String editedOn; - bool biometricEnabled; - int patientType; - int preferredLanguage; + int? id; + String? imei; + int? logInType; + int? patientId; + bool? outSa; + String? mobile; + String? identificationNo; + String? name; + String? nameN; + String? createdOn; + String? editedOn; + bool? biometricEnabled; + int? patientType; + int? preferredLanguage; SelectDeviceByImeiRespModelElement({ - required this.id, - required this.imei, - required this.logInType, - required this.patientId, - required this.outSa, - required this.mobile, - required this.identificationNo, - required this.name, - required this.nameN, - required this.createdOn, - required this.editedOn, - required this.biometricEnabled, - required this.patientType, - required this.preferredLanguage, + this.id, + this.imei, + this.logInType, + this.patientId, + this.outSa, + this.mobile, + this.identificationNo, + this.name, + this.nameN, + this.createdOn, + this.editedOn, + this.biometricEnabled, + this.patientType, + this.preferredLanguage, }); factory SelectDeviceByImeiRespModelElement.fromJson(Map json) => SelectDeviceByImeiRespModelElement( - id: json["ID"], - imei: json["IMEI"], - logInType: json["LogInType"], - patientId: json["PatientID"], - outSa: json["OutSA"], - mobile: json["Mobile"], - identificationNo: json["IdentificationNo"], - name: json["Name"], - nameN: json["NameN"], - createdOn: json["CreatedOn"], - editedOn: json["EditedOn"], - biometricEnabled: json["BiometricEnabled"], - patientType: json["PatientType"], - preferredLanguage: json["PreferredLanguage"], - ); + id: json["ID"] as int?, + imei: json["IMEI"] as String?, + logInType: json["LogInType"] as int?, + patientId: json["PatientID"] as int?, + outSa: json["OutSA"] as bool?, + mobile: json["Mobile"] as String?, + identificationNo: json["IdentificationNo"] as String?, + name: json["Name"] as String?, + nameN: json["NameN"] as String?, + createdOn: json["CreatedOn"] as String?, + editedOn: json["EditedOn"] as String?, + biometricEnabled: json["BiometricEnabled"] as bool?, + patientType: json["PatientType"] as int?, + preferredLanguage: json["PreferredLanguage"] as int?, + ); Map toJson() => { - "ID": id, - "IMEI": imei, - "LogInType": logInType, - "PatientID": patientId, - "OutSA": outSa, - "Mobile": mobile, - "IdentificationNo": identificationNo, - "Name": name, - "NameN": nameN, - "CreatedOn": createdOn, - "EditedOn": editedOn, - "BiometricEnabled": biometricEnabled, - "PatientType": patientType, - "PreferredLanguage": preferredLanguage, - }; + "ID": id, + "IMEI": imei, + "LogInType": logInType, + "PatientID": patientId, + "OutSA": outSa, + "Mobile": mobile, + "IdentificationNo": identificationNo, + "Name": name, + "NameN": nameN, + "CreatedOn": createdOn, + "EditedOn": editedOn, + "BiometricEnabled": biometricEnabled, + "PatientType": patientType, + "PreferredLanguage": preferredLanguage, + }; } diff --git a/lib/main.dart b/lib/main.dart index 3d25c0ac..bb4332b2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,16 +10,14 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ import 'package:hmg_patient_app_new/providers/bottom_navigation_provider.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/app_theme.dart'; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; + import 'core/utils/size_utils.dart'; import 'firebase_options.dart'; -var globalMessengerKey = GlobalKey(); -final navigatorKey = GlobalKey(); - - @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); @@ -57,7 +55,12 @@ void main() async { create: (_) => BottomNavigationProvider(), ), ChangeNotifierProvider( - create: (_) => AuthenticationViewModel(authenticationRepo: getIt(), appState: getIt()), + create: (_) => AuthenticationViewModel( + authenticationRepo: getIt(), + appState: getIt(), + dialogService: getIt(), + errorHandlerService: getIt(), + ), ), ], child: MyApp()), ), @@ -94,7 +97,7 @@ class MyApp extends StatelessWidget { initialRoute: AppRoutes.initialRoute, routes: AppRoutes.routes, theme: AppTheme.getTheme(EasyLocalization.of(context)?.locale.languageCode == "ar"), - navigatorKey: navigatorKey, + navigatorKey: getIt.get().navigatorKey, ); }, ); diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index feb7f69b..22355cc7 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -3,11 +3,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; @@ -25,9 +27,10 @@ class LandingPage extends StatefulWidget { } class _LandingPageState extends State { - @override Widget build(BuildContext context) { + AppState appState = getIt.get(); + final AuthenticationViewModel authenticationViewModel = context.read(); return Consumer(builder: (context, navigationProvider, child) { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, @@ -43,10 +46,8 @@ class _LandingPageState extends State { children: [ CustomButton( text: LocaleKeys.loginOrRegister.tr(context: context), - onPressed: () { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (BuildContext context) => LandingPage()), - ); + onPressed: () async { + await authenticationViewModel.selectDeviceImei(); }, backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), @@ -66,7 +67,7 @@ class _LandingPageState extends State { ), ), SizedBox(height: 16.h), - AppState().isAuthenticated + appState.isAuthenticated ? Column( children: [ Container( @@ -257,7 +258,7 @@ class _LandingPageState extends State { ), ), SizedBox(height: 16.h), - AppState().isAuthenticated + appState.isAuthenticated ? Column( children: [ Row( diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart new file mode 100644 index 00000000..e931568e --- /dev/null +++ b/lib/services/dialog_service.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; + +abstract class DialogService { + Future showErrorDialog(String message); +} + +class DialogServiceImp implements DialogService { + final NavigationService navigationService; + + DialogServiceImp({required this.navigationService}); + + @override + Future showErrorDialog(String message) async { + final context = navigationService.navigatorKey.currentContext; + if (context == null) return; + + await showModalBottomSheet( + context: context, + isScrollControlled: false, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + builder: (_) => _ErrorBottomSheet(message: message), + ); + } +} + +class _ErrorBottomSheet extends StatelessWidget { + final String message; + + const _ErrorBottomSheet({required this.message}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.error_outline, color: Colors.red, size: 40), + const SizedBox(height: 12), + Text( + "Error", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Text( + message, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: const Text("OK"), + ), + ], + ), + ); + } +} diff --git a/lib/services/error_handler_service.dart b/lib/services/error_handler_service.dart new file mode 100644 index 00000000..d8d473ae --- /dev/null +++ b/lib/services/error_handler_service.dart @@ -0,0 +1,52 @@ +import 'dart:io'; + +import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; + +abstract class ErrorHandlerService { + Future handleError(Failure failure); +} + +class ErrorHandlerServiceImp implements ErrorHandlerService { + final DialogService dialogService; + final LoggerService loggerService; + final NavigationService navigationService; + + ErrorHandlerServiceImp({ + required this.dialogService, + required this.loggerService, + required this.navigationService, + }); + + @override + Future handleError(Failure failure) async { + if (failure is APIException) { + loggerService.errorLogs("API Exception: ${failure.message}"); + } else if (failure is ServerFailure) { + loggerService.errorLogs("Server Failure: ${failure.message}"); + await _showDialog(failure); + } else if (failure is DataParsingFailure) { + loggerService.errorLogs("Data Parsing Failure: ${failure.message}"); + await _showDialog(failure, title: "Data Error"); + } else if (failure is HttpException) { + loggerService.errorLogs("Http Exception: ${failure.message}"); + await _showDialog(failure, title: "Network Error"); + } else if (failure is UnknownFailure) { + loggerService.errorLogs("Unknown Failure: ${failure.message}"); + await _showDialog(failure, title: "Unknown Error"); + } else if (failure is InvalidCredentials) { + loggerService.errorLogs("Invalid Credentials : ${failure.message}"); + await _showDialog(failure, title: "Unknown Error"); + } else { + loggerService.errorLogs("Unhandled failure type: $failure"); + await _showDialog(failure, title: "Error"); + } + } + + Future _showDialog(Failure failure, {String title = "Error"}) async { + await dialogService.showErrorDialog(failure.message); + } +} diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart new file mode 100644 index 00000000..d814cf53 --- /dev/null +++ b/lib/services/navigation_service.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class NavigationService { + final GlobalKey navigatorKey = GlobalKey(); + + BuildContext? get context => navigatorKey.currentContext; + + Future push(Route route) { + return navigatorKey.currentState!.push(route); + } + + void pop([T? result]) { + navigatorKey.currentState!.pop(result); + } +} From 4ecbcb9332fe13a529a14f042733f52bd07436b8 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 2 Sep 2025 14:45:56 +0300 Subject: [PATCH 09/17] Homepage & medical implementation contd. --- assets/images/png/Apple_Pay.png | Bin 0 -> 6592 bytes assets/images/png/Mada.png | Bin 0 -> 10340 bytes assets/images/png/Mastercard.png | Bin 0 -> 14639 bytes assets/images/png/male_img.png | Bin 0 -> 19600 bytes assets/images/png/tamara_en.png | Bin 0 -> 46326 bytes assets/images/png/visa.png | Bin 0 -> 16450 bytes lib/core/app_assets.dart | 19 ++- lib/core/utils/utils.dart | 15 +- lib/presentation/home/landing_page.dart | 41 ++--- .../home/widgets/habib_wallet_card.dart | 127 ++++++++++++++ .../medical_file/medical_file_page.dart | 158 +++++++++++++++++- lib/theme/colors.dart | 1 + lib/widgets/buttons/custom_button.dart | 4 +- 13 files changed, 330 insertions(+), 35 deletions(-) create mode 100644 assets/images/png/Apple_Pay.png create mode 100644 assets/images/png/Mada.png create mode 100644 assets/images/png/Mastercard.png create mode 100644 assets/images/png/male_img.png create mode 100644 assets/images/png/tamara_en.png create mode 100644 assets/images/png/visa.png create mode 100644 lib/presentation/home/widgets/habib_wallet_card.dart diff --git a/assets/images/png/Apple_Pay.png b/assets/images/png/Apple_Pay.png new file mode 100644 index 0000000000000000000000000000000000000000..1dc75042f37de9d6b7629dc7e3f33e7c5d5671b4 GIT binary patch literal 6592 zcmV;x89(NUP)Px#IAvH#W=%~1DgXcg2mk?xX#fNO00031000^Q000000-yo_1ONa40RR92ET97b z1ONa40RR91djJ3c0J_IuqW}OIHc3Q5RCodHT?^P2RkhXtQ8BZB}_$yyo*X| z26-zl2g`eESF@9PEX+%3ob>4Ps70AKo&-$IFw&!`C2Ho4Kul8v0u?0V@~csxnCHCj z4->=u-}lX4Yi@gH)_R`z|GsZ#t-aQM_sp8T_w1Q}z4fXsy-~fhfC<10z&k)+z{D6Z z28;nO80ds3jsqS6{ssJB1cUM0#27FJjDbQKSRdX;1G9l>k_!JGRA`ju+!!zhYR|xq zVBk?8oS2$@->UX(83D$CF;Ex-r@-@apjm_A>dWO2S7L} zwfg>CZP~Jl8v_+!pmT}qFb=VQQ5gR-LVe%VH}oVRUKCaVyjYkR1GzC!)`WE_7?LI{ zlKL?JlvtJ__KiG>|DTAZ4Oj@w16~5A15W{q0TW}O_6&R$3`mB7!4Q!rSND>$$2tV{ z@%fX>NoE390DAx?#y~9@=!07Gimo|XCsly4vLvi%`#cZs*(3I;cUXbL!K&ap15*%S z^ki`Ty|3?;bBM332`gx$zoL%k0$m;IYAVJ+sTnvN(FX^<^z)HY*L-d8WlvZ^yS;)c z9lEw!_RScm9s|=*rQm>X~02?M&hoo-aErLBoUqu& zTx_~Si~8FxW59a`u0({AgvCcjPWQf6x&W)5u-MMCfF3$}%&O`Pl;P2wT?LK6H;R4^ zxL06P2UYf5n8=ez7Z`JRzHyR59BlcjpqqhX9du2_81RmPml2Um59?{@Red<2?O~<) zcnlL3UxLV?`XvCL&*W=+T-r#Ya|+5dF$PM<09Qx1I?$J3|2Y6(ja+r{Az?M*n9pdQ z2Jmedt=dYmkL|hzU}6lEih*VcYYu900l<@`F5+v#3iJO-{CE+NB&TM&*M|$X@5VqG z7`P8Xaw7Iw;7;IMz%ZbWQig=ZcG&>9641O=Bb>arG^H6^KY(Tjn#*=3m`8zA0p2oq73!W0JO*f!RHOK~ zO8y>TcfdC(Yr^7szvqBPe7kIWz_*65c*?^7S0P^ma6;7OV3X*yq0Acq4(A#TSGjHf zH9BwOQ&P56mx=2}AUvGJ&qAfF35%p13`pA`(C0d@53>*xV?e?HZ&|+%1P4-X_7= z0ZxXt5+hofI}26SngpfJYojPQ-5*5S8O+6%y7N3686QDRTWGlyxM)N56Ox%IX;A zyzfK6@jzP1;=%;+7Im>j$nC=ANZO@PpIK8zhmYad@;;DNBbK?`dEjN=4vgiqp$ZA> z&;-X-8vp%>BZtudpK{3iIQ$EIegWtNB$2)_*4_ZSHi_dDY>!468s53&scR$u%zz|T zQ)ty~?guYD8k|HfuP zU!g2e6GWM8ZUrxCb?KXNAGf^<32ShI<4b$
Tkad4vCmt&42n9BghrjgTOq^-*k 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 literal 0 HcmV?d00001 diff --git a/assets/images/png/Mada.png b/assets/images/png/Mada.png new file mode 100644 index 0000000000000000000000000000000000000000..905a5ba893f3e650656028f91f2f7833b5408ede GIT binary patch literal 10340 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

*x#nRP$)q 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 { CustomButton( text: LocaleKeys.loginOrRegister.tr(context: context), onPressed: () { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (BuildContext context) => LandingPage()), + Navigator.of(context).push( + MaterialPageRoute(builder: (BuildContext context) => LoginScreen()), ); }, backgroundColor: Color(0xffFEE9EA), @@ -64,7 +68,14 @@ class _LandingPageState extends State { icon: AppAssets.contact_icon, width: 24, height: 24, - ), + ).onPress(() { + Navigator.of(context).push( + FadePage( + page: MedicalFilePage(), + // page: LoginScreen(), + ), + ); + }), ], ), ), @@ -114,7 +125,7 @@ class _LandingPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Quick Links".toText14(isBold: true), + "Quick Links".toText16(isBold: true), Row( children: [ "View medical file".toText12(color: AppColors.primaryRedColor), @@ -217,7 +228,7 @@ class _LandingPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Services".toText14(isBold: true), + "Services".toText16(isBold: true), Row( children: [ "View all services".toText12(color: AppColors.primaryRedColor), @@ -262,25 +273,7 @@ class _LandingPageState extends State { ), ), SizedBox(height: 16.h), - AppState().isAuthenticated - ? Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "My Balance".toText14(isBold: true), - Row( - children: [ - "View all services".toText12(color: AppColors.primaryRedColor), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], - ), - ], - ), - ], - ) - : SizedBox(), + AppState().isAuthenticated ? HabibWalletCard() : SizedBox(), ], ), ), diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart new file mode 100644 index 00000000..9b513dbc --- /dev/null +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; + +class HabibWalletCard extends StatelessWidget { + const HabibWalletCard({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "My Balance".toText16(isBold: true), + Row( + children: [ + "View all services".toText12(color: AppColors.primaryRedColor), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ), + ], + ), + SizedBox(height: 16.h), + Container( + // height: 150.h, + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Stack(children: [ + Positioned( + right: 0, + child: ClipRRect(borderRadius: BorderRadius.circular(24.0), child: Utils.buildSvgWithAssets(icon: AppAssets.habib_background_icon, width: 150.h, height: 150.h)), + ), + Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Habib Wallet".toText15(isBold: true), + Container( + height: 40.h, + width: 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 8.h, + ), + child: Padding( + padding: EdgeInsets.all(8.h), + child: Utils.buildSvgWithAssets( + icon: AppAssets.show_icon, + width: 12.h, + height: 12.h, + fit: BoxFit.contain, + ), + ), + ), + ], + ), + SizedBox(height: 4.h), + Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.saudi_riyal_icon, + iconColor: AppColors.dividerColor, + width: 24.h, + height: 24.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.h), + "200.18".toText32(isBold: true), + ], + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 50.h), + child: Row( + children: [ + "View details".toText12(color: AppColors.primaryRedColor), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ), + ), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 150.h, + child: Utils.getPaymentMethods(), + ), + CustomButton( + icon: AppAssets.recharge_icon, + iconSize: 18.h, + text: "Recharge", + onPressed: () {}, + backgroundColor: AppColors.infoColor, + borderColor: AppColors.infoColor, + textColor: AppColors.whiteColor, + fontSize: 12, + fontWeight: FontWeight.bold, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 35.h, + ), + ], + ), + ], + ), + ), + ]), + ) + ], + ); + } +} diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index dff7f759..55ca84bf 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -1,5 +1,13 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; class MedicalFilePage extends StatelessWidget { const MedicalFilePage({super.key}); @@ -12,12 +20,152 @@ class MedicalFilePage extends StatelessWidget { title: const Text('Appointments'), backgroundColor: AppColors.bgScaffoldColor, ), - body: const Center( - child: Text( - 'Appointments Page', - style: TextStyle(fontSize: 24), + body: Padding( + padding: EdgeInsets.all(24.h), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.medicalFile.tr(context: context).toText22(isBold: true), + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: "Type any record", + controller: TextEditingController(), + keyboardType: TextInputType.number, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + leadingIcon: AppAssets.student_card, + hasError: true, + ), + SizedBox(height: 16.h), + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset( + AppAssets.male_img, + width: 56.h, + height: 56.h, + ), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Haroon Amjad".toText18(isBold: true), + SizedBox(height: 4.h), + Row( + children: [ + CustomButton( + icon: AppAssets.file_icon, + iconColor: AppColors.blackColor, + iconSize: 12.h, + text: "File no: 3628599", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.normal, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + SizedBox(width: 4.h), + CustomButton( + icon: AppAssets.checkmark_icon, + iconColor: AppColors.successColor, + iconSize: 13.h, + text: LocaleKeys.verified.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.normal, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + ], + ) + ], + ), + SizedBox(height: 16.h), + Divider(color: AppColors.dividerColor, height: 1.h), + SizedBox(height: 16.h), + Row( + children: [ + CustomButton( + text: "30 Years Old", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.normal, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + SizedBox(width: 4.h), + CustomButton( + icon: AppAssets.blood_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 13.h, + text: "Blood: A+", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.normal, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + SizedBox(width: 4.h), + CustomButton( + icon: AppAssets.insurance_active_icon, + iconColor: AppColors.successColor, + iconSize: 13.h, + text: "Insurance Active", + onPressed: () {}, + backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + borderColor: AppColors.bgGreenColor.withOpacity(0.0), + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.normal, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + SizedBox(height: 8.h), + ], + ), + ), + ) + ], + ), ), ), ); } -} \ No newline at end of file +} diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 3eacbd6b..ff310ee9 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -29,6 +29,7 @@ class AppColors { static const Color bgGreenColor = Color(0xFF18C273); static const Color textColor = Color(0xFF2E3039); static const Color borderOnlyColor = Color(0xFF2E3039); + static const Color dividerColor = Color(0xFFD2D2D2); //Chips static const Color successColor = Color(0xff18C273); diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index e08189ac..5413cea1 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -20,6 +20,7 @@ class CustomButton extends StatelessWidget { final bool isDisabled; final Color iconColor; final double height; + final double iconSize; CustomButton({ Key? key, @@ -38,6 +39,7 @@ class CustomButton extends StatelessWidget { this.icon, this.iconColor = Colors.white, this.height = 56, + this.iconSize = 24, }) : super(key: key); @override @@ -62,7 +64,7 @@ class CustomButton extends StatelessWidget { if (icon != null) Padding( padding: const EdgeInsets.only(right: 8.0), - child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled), + child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled, width: iconSize, height: iconSize), ), Text( text, From 9509d4a1921b9d07f4bed68c77862e9e155ab072 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Tue, 2 Sep 2025 15:15:16 +0300 Subject: [PATCH 10/17] completed the deviceapi with new architecture --- lib/core/api/api_client.dart | 10 +- lib/core/app_state.dart | 2 +- lib/core/dependencies.dart | 6 +- lib/core/enums.dart | 42 +- lib/core/exceptions/api_failure.dart | 9 + lib/core/location_util.dart | 6 +- ...CalendarUtils.dart => calendar_utils.dart} | 0 ...fication.dart => local_notifications.dart} | 0 ...er.dart => push_notification_handler.dart} | 12 +- lib/core/utils/request_utils.dart | 91 +++ lib/extensions/widget_extensions.dart | 22 +- .../authentication/authentication_repo.dart | 111 +++- .../authentication_view_model.dart | 103 +++- ...heck_activation_code_request_register.dart | 121 ---- ...heck_activation_code_request_register.dart | 121 ++++ ..._patient_authentication_request_model.dart | 88 +++ .../send_activation_request_model.dart | 132 +++++ .../authenticated_user_resp_model.dart} | 0 .../check_activation_code_resp_model.dart | 546 ++++++++++++++++++ ...ent_last_login_details_response_model.dart | 0 .../select_device_by_imei.dart | 0 lib/services/analytics/analytics_service.dart | 2 +- lib/services/dialog_service.dart | 18 +- lib/services/error_handler_service.dart | 11 +- lib/splashPage.dart | 4 +- lib/widgets/chip/custom_chip_widget.dart | 6 +- 26 files changed, 1286 insertions(+), 177 deletions(-) rename lib/core/utils/{CalendarUtils.dart => calendar_utils.dart} (100%) rename lib/core/utils/{LocalNotification.dart => local_notifications.dart} (100%) rename lib/core/utils/{push-notification-handler.dart => push_notification_handler.dart} (97%) create mode 100644 lib/core/utils/request_utils.dart delete mode 100644 lib/features/authentication/models/check_activation_code_request_register.dart create mode 100644 lib/features/authentication/models/request_models/check_activation_code_request_register.dart create mode 100644 lib/features/authentication/models/request_models/check_patient_authentication_request_model.dart create mode 100644 lib/features/authentication/models/request_models/send_activation_request_model.dart rename lib/features/authentication/models/{authenticated_user_model.dart => resp_models/authenticated_user_resp_model.dart} (100%) create mode 100644 lib/features/authentication/models/resp_models/check_activation_code_resp_model.dart rename lib/features/authentication/models/{ => resp_models}/get_patient_last_login_details_response_model.dart (100%) rename lib/features/authentication/models/{ => resp_models}/select_device_by_imei.dart (100%) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index c3862e2c..92a7e6c5 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -170,6 +170,14 @@ class ApiClientImp implements ApiClient { } } + // request.versionID = VERSION_ID; + // request.channel = CHANNEL; + // request.iPAdress = IP_ADDRESS; + // request.generalid = GENERAL_ID; + // request.languageID = (languageID == 'ar' ? 1 : 2); + // request.patientOutSA = (request.zipCode == '966' || request.zipCode == '+966') ? 0 : 1; + + // TODO : we will use all these from appState body['LanguageID'] = body['LanguageID'] ?? "2"; body['VersionID'] = body['VersionID'] ?? "18.7"; body['Channel'] = body['Channel'] ?? "3"; @@ -192,7 +200,7 @@ class ApiClientImp implements ApiClient { final int statusCode = response.statusCode; if (statusCode < 200 || statusCode >= 400) { - onFailure('Error While Fetching data', statusCode, failureType: ServerFailure("Error While Fetching data")); + onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); } else { var parsed = json.decode(utf8.decode(response.bodyBytes)); diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index bdf8d257..7c42042e 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -1,6 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:hmg_patient_app_new/core/post_params_model.dart'; -import 'package:hmg_patient_app_new/features/authentication/models/authenticated_user_model.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'api_consts.dart' as ApiConsts; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 5aba3e24..549eeed2 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -35,7 +35,11 @@ class AppDependencies { getIt.registerLazySingleton(() => NavigationService()); getIt.registerLazySingleton(() => GAnalytics()); getIt.registerLazySingleton(() => AppState(navigationService: getIt())); - getIt.registerLazySingleton(() => LocationUtils(isShowConfirmDialog: false, navigationService: getIt())); + getIt.registerLazySingleton(() => LocationUtils( + isShowConfirmDialog: false, + navigationService: getIt(), + appState: getIt(), + )); getIt.registerLazySingleton(() => DialogServiceImp(navigationService: getIt())); getIt.registerLazySingleton(() => ErrorHandlerServiceImp( dialogService: getIt(), diff --git a/lib/core/enums.dart b/lib/core/enums.dart index db758f3d..2fd58672 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -5,7 +5,7 @@ // unverified, // } -enum AuthMethodTypes { +enum AuthMethodTypesEnum { sms, whatsApp, fingerPrint, @@ -13,7 +13,7 @@ enum AuthMethodTypes { moreOptions, } -enum ViewState { +enum ViewStateEnum { hide, idle, busy, @@ -22,20 +22,44 @@ enum ViewState { errorLocal, } -enum LoginType { +enum LoginTypeEnum { fromLogin, silentLogin, silentWithOTP, } -enum OTPType { sms, whatsapp } +enum OTPTypeEnum { sms, whatsapp } -enum Country { saudiArabia, unitedArabEmirates } +enum CountryEnum { saudiArabia, unitedArabEmirates } -enum SelectionType { dropdown, calendar } +enum SelectionTypeEnum { dropdown, calendar } -enum GenderType { male, female } +enum GenderTypeEnum { male, female } -enum MaritalStatusType { single, married, divorced, widowed } +enum MaritalStatusTypeEnum { single, married, divorced, widowed } -enum ChipType { success, error, alert, info, warning } +enum ChipTypeEnum { success, error, alert, info, warning } + +extension OTPTypeEnumExtension on OTPTypeEnum { + /// Convert enum to int + int toInt() { + switch (this) { + case OTPTypeEnum.sms: + return 1; + case OTPTypeEnum.whatsapp: + return 2; + } + } + + /// Convert int to enum + static OTPTypeEnum fromInt(int value) { + switch (value) { + case 1: + return OTPTypeEnum.sms; + case 2: + return OTPTypeEnum.whatsapp; + default: + throw Exception("Invalid OTPTypeEnum value: $value"); + } + } +} diff --git a/lib/core/exceptions/api_failure.dart b/lib/core/exceptions/api_failure.dart index d2a510f0..4bc50974 100644 --- a/lib/core/exceptions/api_failure.dart +++ b/lib/core/exceptions/api_failure.dart @@ -13,6 +13,13 @@ class ServerFailure extends Failure { List get props => [message]; } +class StatusCodeFailure extends Failure { + const StatusCodeFailure(super.message); + + @override + List get props => [message]; +} + class ConnectivityFailure extends Failure { const ConnectivityFailure(super.message); @@ -55,3 +62,5 @@ class InvalidCredentials extends Failure { @override List get props => [message]; } + + diff --git a/lib/core/location_util.dart b/lib/core/location_util.dart index c8e69afd..dc6a0d61 100644 --- a/lib/core/location_util.dart +++ b/lib/core/location_util.dart @@ -4,14 +4,14 @@ import 'package:geolocator/geolocator.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/consts.dart'; -import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; -// import 'package:huawei_location/huawei_location.dart'; import 'package:permission_handler/permission_handler.dart'; class LocationUtils { NavigationService navigationService; + AppState appState; + bool isShowConfirmDialog; bool isShowLocationTimeoutDialog; bool isHuawei; @@ -20,10 +20,10 @@ class LocationUtils { LocationUtils({ required this.isShowConfirmDialog, required this.navigationService, + required this.appState, this.isHuawei = false, this.isShowLocationTimeoutDialog = true, }); - AppState appState = getIt.get(); void getCurrentLocation({Function(LatLng)? callBack}) async { Geolocator.isLocationServiceEnabled().then((value) async { diff --git a/lib/core/utils/CalendarUtils.dart b/lib/core/utils/calendar_utils.dart similarity index 100% rename from lib/core/utils/CalendarUtils.dart rename to lib/core/utils/calendar_utils.dart diff --git a/lib/core/utils/LocalNotification.dart b/lib/core/utils/local_notifications.dart similarity index 100% rename from lib/core/utils/LocalNotification.dart rename to lib/core/utils/local_notifications.dart diff --git a/lib/core/utils/push-notification-handler.dart b/lib/core/utils/push_notification_handler.dart similarity index 97% rename from lib/core/utils/push-notification-handler.dart rename to lib/core/utils/push_notification_handler.dart index d71bdcfd..b5a816ea 100644 --- a/lib/core/utils/push-notification-handler.dart +++ b/lib/core/utils/push_notification_handler.dart @@ -3,19 +3,17 @@ import 'dart:developer'; import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:firebase_messaging/firebase_messaging.dart' as fir; import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart' as fir; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; // import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:hmg_patient_app_new/core/utils/LocalNotification.dart'; +import 'package:hmg_patient_app_new/core/utils/local_notifications.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:uuid/uuid.dart'; import '../consts.dart'; @@ -365,7 +363,9 @@ class PushNotificationHandler { Future requestPermissions() async { try { if (Platform.isIOS) { - await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation()?.requestPermissions(alert: true, badge: true, sound: true); + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation() + ?.requestPermissions(alert: true, badge: true, sound: true); } else if (Platform.isAndroid) { Map statuses = await [ Permission.notification, diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart new file mode 100644 index 00000000..1869923c --- /dev/null +++ b/lib/core/utils/request_utils.dart @@ -0,0 +1,91 @@ +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/request_models/send_activation_request_model.dart'; + +class RequestUtils { + static dynamic getCommonRequestWelcome({ + required String phoneNumber, + required OTPTypeEnum otpTypeEnum, + required String? deviceToken, + required bool patientOutSA, + required String? loginTokenID, + required var registeredData, + required int? patientId, + required String nationIdText, + required String countryCode, + }) { + bool fileNo = false; + if (nationIdText.isNotEmpty) { + fileNo = nationIdText.length < 10; + } + var request = SendActivationRequest(); + request.patientMobileNumber = int.parse(phoneNumber); + request.mobileNo = '0$phoneNumber'; + request.deviceToken = deviceToken; + request.projectOutSA = patientOutSA; + request.loginType = otpTypeEnum.toInt(); + request.oTPSendType = otpTypeEnum.toInt(); // could map OTPTypeEnum if needed + request.zipCode = countryCode; // or countryCode if defined elsewhere + request.logInTokenID = loginTokenID ?? ""; + + if (registeredData != null) { + request.searchType = registeredData.searchType ?? 1; + request.patientID = registeredData.patientID ?? 0; + request.patientIdentificationID = request.nationalID = registeredData.patientIdentificationID ?? '0'; + request.dob = registeredData.dob; + request.isRegister = registeredData.isRegister; + } else { + if (fileNo) { + request.patientID = patientId ?? int.parse(nationIdText); + request.patientIdentificationID = request.nationalID = ""; + request.searchType = 2; + } else { + request.patientID = 0; + request.searchType = 1; + request.patientIdentificationID = request.nationalID = nationIdText.isNotEmpty ? nationIdText : '0'; + } + request.isRegister = false; + } + + request.deviceTypeID = request.searchType; + return request; + } + + static getCommonRequestAuthProvider({ + required OTPTypeEnum otpTypeEnum, + required registeredData, + required deviceToken, + required mobileNumber, + required zipCode, + required patientOutSA, + required loginTokenID, + required selectedOption, + required int? patientId, + }) { + var request = SendActivationRequest(); + request.patientMobileNumber = mobileNumber; + request.mobileNo = '0$mobileNumber'; + request.deviceToken = deviceToken; + request.projectOutSA = patientOutSA == true ? true : false; + request.loginType = selectedOption; + request.oTPSendType = otpTypeEnum.toInt(); //this.selectedOption == 1 ? 1 : 2; + request.zipCode = zipCode; + + request.logInTokenID = loginTokenID ?? ""; + + if (registeredData != null) { + request.searchType = registeredData.searchType ?? 1; + request.patientID = registeredData.patientID ?? 0; + request.patientIdentificationID = request.nationalID = registeredData.patientIdentificationID ?? '0'; + request.dob = registeredData.dob; + request.isRegister = registeredData.isRegister; + } else { + request.searchType = request.searchType ?? 2; + request.patientID = patientId ?? 0; + request.nationalID = request.nationalID ?? '0'; + request.patientIdentificationID = request.patientIdentificationID ?? '0'; + request.isRegister = false; + } + request.deviceTypeID = request.searchType; + return request; + } +} diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index 8b8c7965..e6913d2e 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -193,33 +193,33 @@ Widget widthSpacer5per() => SizedBox(height: 5.w); -extension ChipTypeExtension on ChipType { +extension ChipTypeEnumExtension on ChipTypeEnum { Color get color { switch (this) { - case ChipType.success: + case ChipTypeEnum.success: return AppColors.successColor; // Replace with your actual color - case ChipType.error: + case ChipTypeEnum.error: return AppColors.errorColor; // Replace with your actual color - case ChipType.alert: + case ChipTypeEnum.alert: return AppColors.alertColor; // Replace with your actual color - case ChipType.info: + case ChipTypeEnum.info: return AppColors.infoColor; // Replace with your actual color - case ChipType.warning: + case ChipTypeEnum.warning: return AppColors.warningColor; // Replace with your actual color } } Color get backgroundColor { switch (this) { - case ChipType.success: + case ChipTypeEnum.success: return AppColors.successLightColor; // Replace with your actual color - case ChipType.error: + case ChipTypeEnum.error: return AppColors.errorLightColor; // Replace with your actual color - case ChipType.alert: + case ChipTypeEnum.alert: return AppColors.alertLightColor; // Replace with your actual color - case ChipType.info: + case ChipTypeEnum.info: return AppColors.infoLightColor; // Replace with your actual color - case ChipType.warning: + case ChipTypeEnum.warning: return AppColors.warningLightColor; // Replace with your actual color } } diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 04e3a9e2..73070139 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -1,15 +1,27 @@ import 'dart:async'; +import 'dart:io'; import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; -import 'package:hmg_patient_app_new/features/authentication/models/select_device_by_imei.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_patient_authentication_request_model.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_activation_code_resp_model.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class AuthenticationRepo { - Future>> selectDeviceByImei({required String firebaseToken}); + Future>> selectDeviceByImei({ + required String firebaseToken, + }); + + Future>> checkPatientAuthentication({ + required CheckPatientAuthenticationReq checkPatientAuthenticationReq, + }); + + Future>> sendActivationCodeRegister( + {required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID}); } class AuthenticationRepoImp implements AuthenticationRepo { @@ -19,9 +31,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { AuthenticationRepoImp({required this.loggerService, required this.apiClient}); @override - Future>> selectDeviceByImei({ - required String firebaseToken, - }) async { + Future>> selectDeviceByImei({required String firebaseToken}) async { final mapDevice = {"IMEI": firebaseToken}; try { GenericApiModel? apiResponse; @@ -34,7 +44,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { }, onSuccess: (response, statusCode, {messageStatus}) { try { - final list = response['Patient_SELECTDeviceIMEIbyIMEIList'] as List?; + final list = response['Patient_SELECTDeviceIMEIbyIMEIList']; if (list == null || list.isEmpty) { throw Exception("Device list is empty"); } @@ -58,4 +68,93 @@ class AuthenticationRepoImp implements AuthenticationRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> checkPatientAuthentication({ + required CheckPatientAuthenticationReq checkPatientAuthenticationReq, + String? languageID, + }) async { + int isOutKsa = (checkPatientAuthenticationReq.zipCode == '966' || checkPatientAuthenticationReq.zipCode == '+966') ? 0 : 1; + //TODO : We will use all these from AppState directly in the ApiClient + + checkPatientAuthenticationReq.versionID = VERSION_ID; + checkPatientAuthenticationReq.channel = CHANNEL; + checkPatientAuthenticationReq.iPAdress = IP_ADDRESS; + checkPatientAuthenticationReq.generalid = GENERAL_ID; + checkPatientAuthenticationReq.languageID = (languageID == 'ar' ? 1 : 2); + checkPatientAuthenticationReq.patientOutSA = isOutKsa; + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.selectDeviceImei, + body: checkPatientAuthenticationReq.toJson(), + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> sendActivationCodeRegister( + {required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID}) async { + int isOutKsa = (checkPatientAuthenticationReq.zipCode == '966' || checkPatientAuthenticationReq.zipCode == '+966') ? 0 : 1; + //TODO : We will use all these from AppState directly in the ApiClient + + checkPatientAuthenticationReq.versionID = VERSION_ID; + checkPatientAuthenticationReq.channel = CHANNEL; + checkPatientAuthenticationReq.iPAdress = IP_ADDRESS; + checkPatientAuthenticationReq.generalid = GENERAL_ID; + checkPatientAuthenticationReq.languageID = (languageID == 'ar' ? 1 : 2); + checkPatientAuthenticationReq.deviceTypeID = Platform.isIOS ? 1 : 2; + checkPatientAuthenticationReq.patientOutSA = isOutKsa; + checkPatientAuthenticationReq.isDentalAllowedBackend = false; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + SEND_ACTIVATION_CODE_REGISTER, + body: checkPatientAuthenticationReq.toJson(), + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: CheckActivationCode.fromJson(response), + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index eaf3c950..573c528e 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_patient_authentication_request_model.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -24,15 +27,109 @@ class AuthenticationViewModel extends ChangeNotifier { String firebaseToken = "dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc"; final result = await authenticationRepo.selectDeviceByImei(firebaseToken: firebaseToken); + result.fold( - (failure) async => await errorHandlerService.handleError(failure), + (failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) { if (apiResponse.messageStatus == 2) { - dialogService.showErrorDialog(apiResponse.errorMessage!); + dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { - // move to next api call + //todo: move to next api call + } + }, + ); + } + + Future checkUserAuthentication({Function(dynamic)? onSuccess, Function(String)? onError}) async { + CheckPatientAuthenticationReq checkPatientAuthenticationReq = RequestUtils.getCommonRequestWelcome( + phoneNumber: '0567184134', + otpTypeEnum: OTPTypeEnum.sms, + deviceToken: 'dummyDeviceToken123', + patientOutSA: true, + loginTokenID: 'dummyLoginToken456', + registeredData: null, + patientId: 12345, + nationIdText: '1234567890', + countryCode: 'SA', + ); + + final result = await authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq); + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.data['isSMSSent']) { + // TODO: set this in AppState + // sharedPref.setString(LOGIN_TOKEN_ID, value['LogInTokenID']); + // loginTokenID = value['LogInTokenID'], + // sharedPref.setObject(REGISTER_DATA_FOR_LOGIIN, request), + sendActivationCode(type); + } else { + if (apiResponse.data['IsAuthenticated']) { + checkActivationCode(onWrongActivationCode: (String? message) {}); + } } }, ); } + + Future sendActivationCode({required OTPTypeEnum otpTypeEnum}) async { + var request = RequestUtils.getCommonRequestAuthProvider( + otpTypeEnum: otpTypeEnum, + registeredData: null, + deviceToken: "dummyLoginToken456", + mobileNumber: "0567184134", + zipCode: "SA", + patientOutSA: true, + loginTokenID: "dummyLoginToken456", + selectedOption: selectedOption, + patientId: 12345, + ); + + request.sMSSignature = await SMSOTP.getSignature(); + selectedOption = type; + // GifLoaderDialogUtils.showMyDialog(context); + if (healthId != null || isDubai) { + if (!isDubai) { + request.dob = dob; //isHijri == 1 ? dob : dateFormat2.format(dateFormat.parse(dob)); + } + request.healthId = healthId; + request.isHijri = isHijri; + await this.apiClient.sendActivationCodeRegister(request).then((result) { + // GifLoaderDialogUtils.hideDialog(context); + if (result != null && result['isSMSSent'] == true) { + this.startSMSService(type); + } + }).catchError((r) { + GifLoaderDialogUtils.hideDialog(context); + context.showBottomSheet( + child: ExceptionBottomSheet( + message: r.toString(), + onOkPressed: () { + Navigator.of(context).pop(); + }, + )); + // AppToast.showErrorToast(message: r); + }); + } else { + request.dob = ""; + request.healthId = ""; + request.isHijri = 0; + await this.authService.sendActivationCode(request).then((result) { + GifLoaderDialogUtils.hideDialog(context); + if (result != null && result['isSMSSent'] == true) { + this.startSMSService(type); + } + }).catchError((r) { + GifLoaderDialogUtils.hideDialog(context); + context.showBottomSheet( + child: ExceptionBottomSheet( + message: r.toString(), + onOkPressed: () { + Navigator.of(context).pop(); + }, + )); + // AppToast.showErrorToast(message: r.toString()); + }); + } + } } diff --git a/lib/features/authentication/models/check_activation_code_request_register.dart b/lib/features/authentication/models/check_activation_code_request_register.dart deleted file mode 100644 index 85e1f56f..00000000 --- a/lib/features/authentication/models/check_activation_code_request_register.dart +++ /dev/null @@ -1,121 +0,0 @@ -class CheckActivationCodeRegisterReq { - int? patientMobileNumber; - String? mobileNo; - String? deviceToken; - bool? projectOutSA; - int? loginType; - String? zipCode; - bool? isRegister; - String? logInTokenID; - int? searchType; - int? patientID; - String? nationalID; - String? patientIdentificationID; - String? activationCode; - bool? isSilentLogin; - double? versionID; - int? channel; - int? languageID; - String? iPAdress; - String? generalid; - int? patientOutSA; - dynamic sessionID; - bool? isDentalAllowedBackend; - int? deviceTypeID; - bool? forRegisteration; - String? dob; - int? isHijri; - String? healthId; - - CheckActivationCodeRegisterReq({ - this.patientMobileNumber, - this.mobileNo, - this.deviceToken, - this.projectOutSA, - this.loginType, - this.zipCode, - this.isRegister, - this.logInTokenID, - this.searchType, - this.patientID, - this.nationalID, - this.patientIdentificationID, - this.activationCode, - this.isSilentLogin, - this.versionID, - this.channel, - this.languageID, - this.iPAdress, - this.generalid, - this.patientOutSA, - this.sessionID, - this.isDentalAllowedBackend, - this.deviceTypeID, - this.forRegisteration, - this.dob, - this.isHijri, - this.healthId, - }); - - CheckActivationCodeRegisterReq.fromJson(Map json) { - patientMobileNumber = json['PatientMobileNumber']; - mobileNo = json['MobileNo']; - deviceToken = json['DeviceToken']; - projectOutSA = json['ProjectOutSA']; - loginType = json['LoginType']; - zipCode = json['ZipCode']; - isRegister = json['isRegister']; - logInTokenID = json['LogInTokenID']; - searchType = json['SearchType']; - patientID = json['PatientID']; - nationalID = json['NationalID']; - patientIdentificationID = json['PatientIdentificationID']; - activationCode = json['activationCode']; - isSilentLogin = json['IsSilentLogin']; - versionID = json['VersionID']; - channel = json['Channel']; - languageID = json['LanguageID']; - iPAdress = json['IPAdress']; - generalid = json['generalid']; - patientOutSA = json['PatientOutSA']; - sessionID = json['SessionID']; - isDentalAllowedBackend = json['isDentalAllowedBackend']; - deviceTypeID = json['DeviceTypeID']; - forRegisteration = json['ForRegisteration']; - dob = json['DOB']; - isHijri = json['IsHijri']; - healthId = json['HealthId']; - } - - Map toJson() { - final Map data = new Map(); - data['PatientMobileNumber'] = this.patientMobileNumber; - data['MobileNo'] = this.mobileNo; - data['DeviceToken'] = this.deviceToken; - data['ProjectOutSA'] = this.projectOutSA; - data['LoginType'] = this.loginType; - data['ZipCode'] = this.zipCode; - data['isRegister'] = this.isRegister; - data['LogInTokenID'] = this.logInTokenID; - data['SearchType'] = this.searchType; - data['PatientID'] = this.patientID; - data['NationalID'] = this.nationalID; - data['PatientIdentificationID'] = this.patientIdentificationID; - data['activationCode'] = this.activationCode; - data['IsSilentLogin'] = this.isSilentLogin; - data['VersionID'] = this.versionID; - data['Channel'] = this.channel; - data['LanguageID'] = this.languageID; - data['IPAdress'] = this.iPAdress; - data['generalid'] = this.generalid; - data['PatientOutSA'] = this.patientOutSA; - data['SessionID'] = this.sessionID; - data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; - data['DeviceTypeID'] = this.deviceTypeID; - data['ForRegisteration'] = this.forRegisteration; - data['DOB'] = dob; - data['IsHijri'] = isHijri; - data['HealthId'] = healthId; - return data; - } -} diff --git a/lib/features/authentication/models/request_models/check_activation_code_request_register.dart b/lib/features/authentication/models/request_models/check_activation_code_request_register.dart new file mode 100644 index 00000000..167c8ec5 --- /dev/null +++ b/lib/features/authentication/models/request_models/check_activation_code_request_register.dart @@ -0,0 +1,121 @@ +// class CheckActivationCodeRegisterReq { +// int? patientMobileNumber; +// String? mobileNo; +// String? deviceToken; +// bool? projectOutSA; +// int? loginType; +// String? zipCode; +// bool? isRegister; +// String? logInTokenID; +// int? searchType; +// int? patientID; +// String? nationalID; +// String? patientIdentificationID; +// String? activationCode; +// bool? isSilentLogin; +// double? versionID; +// int? channel; +// int? languageID; +// String? iPAdress; +// String? generalid; +// int? patientOutSA; +// dynamic sessionID; +// bool? isDentalAllowedBackend; +// int? deviceTypeID; +// bool? forRegisteration; +// String? dob; +// int? isHijri; +// String? healthId; +// +// CheckActivationCodeRegisterReq({ +// this.patientMobileNumber, +// this.mobileNo, +// this.deviceToken, +// this.projectOutSA, +// this.loginType, +// this.zipCode, +// this.isRegister, +// this.logInTokenID, +// this.searchType, +// this.patientID, +// this.nationalID, +// this.patientIdentificationID, +// this.activationCode, +// this.isSilentLogin, +// this.versionID, +// this.channel, +// this.languageID, +// this.iPAdress, +// this.generalid, +// this.patientOutSA, +// this.sessionID, +// this.isDentalAllowedBackend, +// this.deviceTypeID, +// this.forRegisteration, +// this.dob, +// this.isHijri, +// this.healthId, +// }); +// +// CheckActivationCodeRegisterReq.fromJson(Map json) { +// patientMobileNumber = json['PatientMobileNumber']; +// mobileNo = json['MobileNo']; +// deviceToken = json['DeviceToken']; +// projectOutSA = json['ProjectOutSA']; +// loginType = json['LoginType']; +// zipCode = json['ZipCode']; +// isRegister = json['isRegister']; +// logInTokenID = json['LogInTokenID']; +// searchType = json['SearchType']; +// patientID = json['PatientID']; +// nationalID = json['NationalID']; +// patientIdentificationID = json['PatientIdentificationID']; +// activationCode = json['activationCode']; +// isSilentLogin = json['IsSilentLogin']; +// versionID = json['VersionID']; +// channel = json['Channel']; +// languageID = json['LanguageID']; +// iPAdress = json['IPAdress']; +// generalid = json['generalid']; +// patientOutSA = json['PatientOutSA']; +// sessionID = json['SessionID']; +// isDentalAllowedBackend = json['isDentalAllowedBackend']; +// deviceTypeID = json['DeviceTypeID']; +// forRegisteration = json['ForRegisteration']; +// dob = json['DOB']; +// isHijri = json['IsHijri']; +// healthId = json['HealthId']; +// } +// +// Map toJson() { +// final Map data = new Map(); +// data['PatientMobileNumber'] = this.patientMobileNumber; +// data['MobileNo'] = this.mobileNo; +// data['DeviceToken'] = this.deviceToken; +// data['ProjectOutSA'] = this.projectOutSA; +// data['LoginType'] = this.loginType; +// data['ZipCode'] = this.zipCode; +// data['isRegister'] = this.isRegister; +// data['LogInTokenID'] = this.logInTokenID; +// data['SearchType'] = this.searchType; +// data['PatientID'] = this.patientID; +// data['NationalID'] = this.nationalID; +// data['PatientIdentificationID'] = this.patientIdentificationID; +// data['activationCode'] = this.activationCode; +// data['IsSilentLogin'] = this.isSilentLogin; +// data['VersionID'] = this.versionID; +// data['Channel'] = this.channel; +// data['LanguageID'] = this.languageID; +// data['IPAdress'] = this.iPAdress; +// data['generalid'] = this.generalid; +// data['PatientOutSA'] = this.patientOutSA; +// data['SessionID'] = this.sessionID; +// data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; +// data['DeviceTypeID'] = this.deviceTypeID; +// data['ForRegisteration'] = this.forRegisteration; +// data['DOB'] = dob; +// data['IsHijri'] = isHijri; +// data['HealthId'] = healthId; +// return data; +// } +// } diff --git a/lib/features/authentication/models/request_models/check_patient_authentication_request_model.dart b/lib/features/authentication/models/request_models/check_patient_authentication_request_model.dart new file mode 100644 index 00000000..483e416b --- /dev/null +++ b/lib/features/authentication/models/request_models/check_patient_authentication_request_model.dart @@ -0,0 +1,88 @@ +class CheckPatientAuthenticationReq { + int? patientMobileNumber; + String? zipCode; + bool? isRegister; + String? tokenID; + int? searchType; + String? patientIdentificationID; + int? patientID; + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + int? patientOutSA; + dynamic sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + String? dob; + int? isHijri; + String? healthId; + + CheckPatientAuthenticationReq( + {this.patientMobileNumber, + this.zipCode, + this.isRegister, + this.tokenID, + this.searchType, + this.patientIdentificationID, + this.patientID, + this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.patientOutSA, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID, + this.dob, + this.isHijri, + this.healthId}); + + CheckPatientAuthenticationReq.fromJson(Map json) { + patientMobileNumber = json['PatientMobileNumber']; + zipCode = json['ZipCode']; + isRegister = json['isRegister']; + tokenID = json['TokenID']; + searchType = json['SearchType']; + patientIdentificationID = json['PatientIdentificationID']; + patientID = json['PatientID']; + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + patientOutSA = json['PatientOutSA']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + dob = json['dob']; + isHijri = json['isHijri']; + healthId = json['HealthId']; + } + + Map toJson() { + final Map data = {}; + data['PatientMobileNumber'] = patientMobileNumber; + data['ZipCode'] = zipCode; + data['isRegister'] = isRegister; + data['TokenID'] = tokenID; + data['SearchType'] = searchType; + data['PatientIdentificationID'] = patientIdentificationID; + data['PatientID'] = patientID; + data['VersionID'] = versionID; + data['Channel'] = channel; + data['LanguageID'] = languageID; + data['IPAdress'] = iPAdress; + data['generalid'] = generalid; + data['PatientOutSA'] = patientOutSA; + data['SessionID'] = sessionID; + data['isDentalAllowedBackend'] = isDentalAllowedBackend; + data['DeviceTypeID'] = deviceTypeID; + data['dob'] = dob; + data['isHijri'] = isHijri; + data['HealthId'] = healthId; + return data; + } +} diff --git a/lib/features/authentication/models/request_models/send_activation_request_model.dart b/lib/features/authentication/models/request_models/send_activation_request_model.dart new file mode 100644 index 00000000..dc3d55dd --- /dev/null +++ b/lib/features/authentication/models/request_models/send_activation_request_model.dart @@ -0,0 +1,132 @@ +class SendActivationRequest { + int? patientMobileNumber; + String? mobileNo; + String? deviceToken; + bool? projectOutSA; + int? loginType; + String? zipCode; + bool? isRegister; + String? logInTokenID; + int? searchType; + int? patientID; + String? nationalID; + String? patientIdentificationID; + int? oTPSendType; + int? languageID; + double? versionID; + int? channel; + String? iPAdress; + String? generalid; + int? patientOutSA; + dynamic sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + String? sMSSignature; + String? dob; + int? isHijri; + String? healthId; + int? responseID; + int? status; + int? familyRegionID; + bool? isPatientExcluded; + SendActivationRequest( + {this.patientMobileNumber, + this.mobileNo, + this.deviceToken, + this.projectOutSA, + this.loginType, + this.zipCode, + this.isRegister, + this.logInTokenID, + this.searchType, + this.patientID, + this.nationalID, + this.patientIdentificationID, + this.oTPSendType, + this.languageID, + this.versionID, + this.channel, + this.iPAdress, + this.generalid, + this.patientOutSA, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID, + this.sMSSignature, + this.dob, + this.isHijri, + this.healthId, + this.responseID, + this.status, + this.familyRegionID, + this.isPatientExcluded + }); + + SendActivationRequest.fromJson(Map json) { + patientMobileNumber = json['PatientMobileNumber']; + mobileNo = json['MobileNo']; + deviceToken = json['DeviceToken']; + projectOutSA = json['ProjectOutSA']; + loginType = json['LoginType']; + zipCode = json['ZipCode']; + isRegister = json['isRegister']; + logInTokenID = json['LogInTokenID']; + searchType = json['SearchType']; + patientID = json['PatientID']; + nationalID = json['NationalID']; + patientIdentificationID = json['PatientIdentificationID']; + oTPSendType = json['OTP_SendType']; + languageID = json['LanguageID']; + versionID = json['VersionID']; + channel = json['Channel']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + patientOutSA = json['PatientOutSA']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + sMSSignature = json['SMSSignature']; + dob = json['DOB']; + isHijri = json['IsHijri']; + healthId = json['HealthId']; + responseID = json['ReponseID']; + status = json['Status']; + familyRegionID = json['FamilyRegionID']; + isPatientExcluded = json['IsPatientExcluded']; + } + + Map toJson() { + final Map data = new Map(); + data['PatientMobileNumber'] = patientMobileNumber; + data['MobileNo'] = mobileNo; + data['DeviceToken'] = deviceToken; + data['ProjectOutSA'] = projectOutSA; + data['LoginType'] = loginType; + data['ZipCode'] = zipCode; + data['isRegister'] = isRegister; + data['LogInTokenID'] = logInTokenID; + data['SearchType'] = searchType; + data['PatientID'] = patientID; + data['NationalID'] = nationalID; + data['PatientIdentificationID'] = patientIdentificationID; + data['OTP_SendType'] = oTPSendType; + data['LanguageID'] = languageID; + data['VersionID'] = versionID; + data['Channel'] = channel; + data['IPAdress'] = iPAdress; + data['generalid'] = generalid; + data['PatientOutSA'] = patientOutSA; + data['SessionID'] = sessionID; + data['isDentalAllowedBackend'] = isDentalAllowedBackend; + data['DeviceTypeID'] = deviceTypeID; + data['SMSSignature'] = sMSSignature; + data['DOB'] = dob; + data['IsHijri'] = isHijri; + data['HealthId'] = healthId; + data['ResponseID'] = responseID; + data['Status'] = status; + data['FamilyRegionID'] = familyRegionID; + data['IsPatientExcluded'] = isPatientExcluded; + return data; + } +} diff --git a/lib/features/authentication/models/authenticated_user_model.dart b/lib/features/authentication/models/resp_models/authenticated_user_resp_model.dart similarity index 100% rename from lib/features/authentication/models/authenticated_user_model.dart rename to lib/features/authentication/models/resp_models/authenticated_user_resp_model.dart diff --git a/lib/features/authentication/models/resp_models/check_activation_code_resp_model.dart b/lib/features/authentication/models/resp_models/check_activation_code_resp_model.dart new file mode 100644 index 00000000..8610e088 --- /dev/null +++ b/lib/features/authentication/models/resp_models/check_activation_code_resp_model.dart @@ -0,0 +1,546 @@ +import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; + +class CheckActivationCode { + dynamic date; + int? languageID; + int? serviceName; + dynamic time; + dynamic androidLink; + String? authenticationTokenID; + dynamic data; + bool? dataw; + int? dietType; + dynamic errorCode; + dynamic errorEndUserMessage; + dynamic errorEndUserMessageN; + dynamic errorMessage; + int? errorType; + int? foodCategory; + dynamic iOSLink; + bool? isAuthenticated; + int? mealOrderStatus; + int? mealType; + int? messageStatus; + int? numberOfResultRecords; + dynamic patientBlodType; + dynamic successMsg; + dynamic successMsgN; + dynamic doctorInformationList; + dynamic getAllPendingRecordsList; + dynamic getAllSharedRecordsByStatusList; + dynamic getResponseFileList; + bool? isHMGPatient; + bool? isLoginSuccessfully; + bool? isNeedUpdateIdintificationNo; + bool? kioskSendSMS; + AuthenticatedUser? list; + dynamic listAskHabibMobileLoginInfo; + dynamic listAskHabibPatientFile; + dynamic listMergeFiles; + dynamic listMobileLoginInfo; + dynamic listPatientCount; + dynamic logInTokenID; + dynamic mohemmPrivilegeList; + int? pateintID; + String? patientBloodType; + bool? patientHasFile; + dynamic patientMergedIDs; + bool? patientOutSA; + int? patientShareRequestID; + int? patientType; + int? projectIDOut; + dynamic returnMessage; + bool? sMSLoginRequired; + dynamic servicePrivilegeList; + dynamic sharePatientName; + dynamic verificationCode; + dynamic email; + dynamic errorList; + bool? hasFile; + bool? isActiveCode; + bool? isMerged; + bool? isNeedUserAgreement; + bool? isSMSSent; + dynamic memberList; + dynamic message; + int? statusCode; + + CheckActivationCode( + {this.date, + this.languageID, + this.serviceName, + this.time, + this.androidLink, + this.authenticationTokenID, + this.data, + this.dataw, + this.dietType, + this.errorCode, + this.errorEndUserMessage, + this.errorEndUserMessageN, + this.errorMessage, + this.errorType, + this.foodCategory, + this.iOSLink, + this.isAuthenticated, + this.mealOrderStatus, + this.mealType, + this.messageStatus, + this.numberOfResultRecords, + this.patientBlodType, + this.successMsg, + this.successMsgN, + this.doctorInformationList, + this.getAllPendingRecordsList, + this.getAllSharedRecordsByStatusList, + this.getResponseFileList, + this.isHMGPatient, + this.isLoginSuccessfully, + this.isNeedUpdateIdintificationNo, + this.kioskSendSMS, + this.list, + this.listAskHabibMobileLoginInfo, + this.listAskHabibPatientFile, + this.listMergeFiles, + this.listMobileLoginInfo, + this.listPatientCount, + this.logInTokenID, + this.mohemmPrivilegeList, + this.pateintID, + this.patientBloodType, + this.patientHasFile, + this.patientMergedIDs, + this.patientOutSA, + this.patientShareRequestID, + this.patientType, + this.projectIDOut, + this.returnMessage, + this.sMSLoginRequired, + this.servicePrivilegeList, + this.sharePatientName, + this.verificationCode, + this.email, + this.errorList, + this.hasFile, + this.isActiveCode, + this.isMerged, + this.isNeedUserAgreement, + this.isSMSSent, + this.memberList, + this.message, + this.statusCode}); + + CheckActivationCode.fromJson(Map json) { + date = json['Date']; + languageID = json['LanguageID']; + serviceName = json['ServiceName']; + time = json['Time']; + androidLink = json['AndroidLink']; + authenticationTokenID = json['AuthenticationTokenID']; + data = json['Data']; + dataw = json['Dataw']; + dietType = json['DietType']; + errorCode = json['ErrorCode']; + errorEndUserMessage = json['ErrorEndUserMessage']; + errorEndUserMessageN = json['ErrorEndUserMessageN']; + errorMessage = json['ErrorMessage']; + errorType = json['ErrorType']; + foodCategory = json['FoodCategory']; + iOSLink = json['IOSLink']; + isAuthenticated = json['IsAuthenticated']; + mealOrderStatus = json['MealOrderStatus']; + mealType = json['MealType']; + messageStatus = json['MessageStatus']; + numberOfResultRecords = json['NumberOfResultRecords']; + patientBlodType = json['PatientBlodType']; + successMsg = json['SuccessMsg']; + successMsgN = json['SuccessMsgN']; + doctorInformationList = json['DoctorInformation_List']; + getAllPendingRecordsList = json['GetAllPendingRecordsList']; + getAllSharedRecordsByStatusList = json['GetAllSharedRecordsByStatusList']; + getResponseFileList = json['GetResponseFileList']; + isHMGPatient = json['IsHMGPatient']; + isLoginSuccessfully = json['IsLoginSuccessfully']; + isNeedUpdateIdintificationNo = json['IsNeedUpdateIdintificationNo']; + kioskSendSMS = json['KioskSendSMS']; + if (json['List'] != null) { + list = AuthenticatedUser.fromJson(json['List'][0]); + } + listAskHabibMobileLoginInfo = json['List_AskHabibMobileLoginInfo']; + listAskHabibPatientFile = json['List_AskHabibPatientFile']; + listMergeFiles = json['List_MergeFiles']; + listMobileLoginInfo = json['List_MobileLoginInfo']; + listPatientCount = json['List_PatientCount']; + logInTokenID = json['LogInTokenID']; + mohemmPrivilegeList = json['MohemmPrivilege_List']; + pateintID = json['PateintID']; + patientBloodType = json['PatientBloodType']; + patientHasFile = json['PatientHasFile']; + patientMergedIDs = json['PatientMergedIDs']; + patientOutSA = json['PatientOutSA']; + patientShareRequestID = json['PatientShareRequestID']; + patientType = json['PatientType']; + projectIDOut = json['ProjectIDOut']; + returnMessage = json['ReturnMessage']; + sMSLoginRequired = json['SMSLoginRequired']; + servicePrivilegeList = json['ServicePrivilege_List']; + sharePatientName = json['SharePatientName']; + verificationCode = json['VerificationCode']; + email = json['email']; + errorList = json['errorList']; + hasFile = json['hasFile']; + isActiveCode = json['isActiveCode']; + isMerged = json['isMerged']; + isNeedUserAgreement = json['isNeedUserAgreement']; + isSMSSent = json['isSMSSent']; + memberList = json['memberList']; + message = json['message']; + statusCode = json['statusCode']; + } + + Map toJson() { + final Map data = {}; + data['Date'] = date; + data['LanguageID'] = languageID; + data['ServiceName'] = serviceName; + data['Time'] = time; + data['AndroidLink'] = androidLink; + data['AuthenticationTokenID'] = authenticationTokenID; + data['Data'] = this.data; + data['Dataw'] = dataw; + data['DietType'] = dietType; + data['ErrorCode'] = errorCode; + data['ErrorEndUserMessage'] = errorEndUserMessage; + data['ErrorEndUserMessageN'] = errorEndUserMessageN; + data['ErrorMessage'] = errorMessage; + data['ErrorType'] = errorType; + data['FoodCategory'] = foodCategory; + data['IOSLink'] = iOSLink; + data['IsAuthenticated'] = isAuthenticated; + data['MealOrderStatus'] = mealOrderStatus; + data['MealType'] = mealType; + data['MessageStatus'] = messageStatus; + data['NumberOfResultRecords'] = numberOfResultRecords; + data['PatientBlodType'] = patientBlodType; + data['SuccessMsg'] = successMsg; + data['SuccessMsgN'] = successMsgN; + data['DoctorInformation_List'] = doctorInformationList; + data['GetAllPendingRecordsList'] = getAllPendingRecordsList; + data['GetAllSharedRecordsByStatusList'] = getAllSharedRecordsByStatusList; + data['GetResponseFileList'] = getResponseFileList; + data['IsHMGPatient'] = isHMGPatient; + data['IsLoginSuccessfully'] = isLoginSuccessfully; + data['IsNeedUpdateIdintificationNo'] = isNeedUpdateIdintificationNo; + data['KioskSendSMS'] = kioskSendSMS; + if (list != null) { + data['List'] = list; + } + data['List_AskHabibMobileLoginInfo'] = listAskHabibMobileLoginInfo; + data['List_AskHabibPatientFile'] = listAskHabibPatientFile; + data['List_MergeFiles'] = listMergeFiles; + data['List_MobileLoginInfo'] = listMobileLoginInfo; + data['List_PatientCount'] = listPatientCount; + data['LogInTokenID'] = logInTokenID; + data['MohemmPrivilege_List'] = mohemmPrivilegeList; + data['PateintID'] = pateintID; + data['PatientBloodType'] = patientBloodType; + data['PatientHasFile'] = patientHasFile; + data['PatientMergedIDs'] = patientMergedIDs; + data['PatientOutSA'] = patientOutSA; + data['PatientShareRequestID'] = patientShareRequestID; + data['PatientType'] = patientType; + data['ProjectIDOut'] = projectIDOut; + data['ReturnMessage'] = returnMessage; + data['SMSLoginRequired'] = sMSLoginRequired; + data['ServicePrivilege_List'] = servicePrivilegeList; + data['SharePatientName'] = sharePatientName; + data['VerificationCode'] = verificationCode; + data['email'] = email; + data['errorList'] = errorList; + data['hasFile'] = hasFile; + data['isActiveCode'] = isActiveCode; + data['isMerged'] = isMerged; + data['isNeedUserAgreement'] = isNeedUserAgreement; + data['isSMSSent'] = isSMSSent; + data['memberList'] = memberList; + data['message'] = message; + data['statusCode'] = statusCode; + return data; + } +} + +class List { + String? setupID; + int? patientType; + int? patientID; + String? firstName; + String? middleName; + String? lastName; + String? firstNameN; + String? middleNameN; + String? lastNameN; + int? relationshipID; + int? gender; + String? dateofBirth; + dynamic dateofBirthN; + String? nationalityID; + dynamic phoneResi; + dynamic phoneOffice; + String? mobileNumber; + dynamic faxNumber; + String? emailAddress; + dynamic bloodGroup; + dynamic rHFactor; + bool? isEmailAlertRequired; + bool? isSMSAlertRequired; + String? preferredLanguage; + bool? isPrivilegedMember; + dynamic memberID; + dynamic expiryDate; + dynamic isHmgEmployee; + dynamic employeeID; + dynamic emergencyContactName; + dynamic emergencyContactNo; + int? patientPayType; + dynamic dHCCPatientRefID; + bool? isPatientDummy; + int? status; + dynamic isStatusCleared; + int? patientIdentificationType; + String? patientIdentificationNo; + int? projectID; + int? infoSourceID; + dynamic address; + int? age; + String? ageDesc; + int? areaID; + int? createdBy; + String? genderDescription; + dynamic iR; + dynamic iSOCityID; + dynamic iSOCountryID; + ListPrivilege? listPrivilege; + dynamic marital; + int? outSA; + dynamic pOBox; + bool? receiveHealthSummaryReport; + int? sourceType; + dynamic strDateofBirth; + dynamic tempAddress; + dynamic zipCode; + + List({ + this.setupID, + this.patientType, + this.patientID, + this.firstName, + this.middleName, + this.lastName, + this.firstNameN, + this.middleNameN, + this.lastNameN, + this.relationshipID, + this.gender, + this.dateofBirth, + this.dateofBirthN, + this.nationalityID, + this.phoneResi, + this.phoneOffice, + this.mobileNumber, + this.faxNumber, + this.emailAddress, + this.bloodGroup, + this.rHFactor, + this.isEmailAlertRequired, + this.isSMSAlertRequired, + this.preferredLanguage, + this.isPrivilegedMember, + this.memberID, + this.expiryDate, + this.isHmgEmployee, + this.employeeID, + this.emergencyContactName, + this.emergencyContactNo, + this.patientPayType, + this.dHCCPatientRefID, + this.isPatientDummy, + this.status, + this.isStatusCleared, + this.patientIdentificationType, + this.patientIdentificationNo, + this.projectID, + this.infoSourceID, + this.address, + this.age, + this.ageDesc, + this.areaID, + this.createdBy, + this.genderDescription, + this.iR, + this.iSOCityID, + this.iSOCountryID, + this.listPrivilege, + this.marital, + this.outSA, + this.pOBox, + this.receiveHealthSummaryReport, + this.sourceType, + this.strDateofBirth, + this.tempAddress, + this.zipCode, + }); + + List.fromJson(Map json) { + setupID = json['SetupID']; + patientType = json['PatientType']; + patientID = json['PatientID']; + firstName = json['FirstName']; + middleName = json['MiddleName']; + lastName = json['LastName']; + firstNameN = json['FirstNameN']; + middleNameN = json['MiddleNameN']; + lastNameN = json['LastNameN']; + relationshipID = json['RelationshipID']; + gender = json['Gender']; + dateofBirth = json['DateofBirth']; + dateofBirthN = json['DateofBirthN']; + nationalityID = json['NationalityID']; + phoneResi = json['PhoneResi']; + phoneOffice = json['PhoneOffice']; + mobileNumber = json['MobileNumber']; + faxNumber = json['FaxNumber']; + emailAddress = json['EmailAddress']; + bloodGroup = json['BloodGroup']; + rHFactor = json['RHFactor']; + isEmailAlertRequired = json['IsEmailAlertRequired']; + isSMSAlertRequired = json['IsSMSAlertRequired']; + preferredLanguage = json['PreferredLanguage']; + isPrivilegedMember = json['IsPrivilegedMember']; + memberID = json['MemberID']; + expiryDate = json['ExpiryDate']; + isHmgEmployee = json['IsHmgEmployee']; + employeeID = json['EmployeeID']; + emergencyContactName = json['EmergencyContactName']; + emergencyContactNo = json['EmergencyContactNo']; + patientPayType = json['PatientPayType']; + dHCCPatientRefID = json['DHCCPatientRefID']; + isPatientDummy = json['IsPatientDummy']; + status = json['Status']; + isStatusCleared = json['IsStatusCleared']; + patientIdentificationType = json['PatientIdentificationType']; + patientIdentificationNo = json['PatientIdentificationNo']; + projectID = json['ProjectID']; + infoSourceID = json['InfoSourceID']; + address = json['Address']; + age = json['Age']; + ageDesc = json['AgeDesc']; + areaID = json['AreaID']; + createdBy = json['CreatedBy']; + genderDescription = json['GenderDescription']; + iR = json['IR']; + iSOCityID = json['ISOCityID']; + iSOCountryID = json['ISOCountryID']; + if (json['ListPrivilege'] != null) { + listPrivilege = ListPrivilege.fromJson(json['ListPrivilege']); + } + marital = json['Marital']; + outSA = json['OutSA']; + pOBox = json['POBox']; + receiveHealthSummaryReport = json['ReceiveHealthSummaryReport']; + sourceType = json['SourceType']; + strDateofBirth = json['StrDateofBirth']; + tempAddress = json['TempAddress']; + zipCode = json['ZipCode']; + } + + Map toJson() { + final Map data = {}; + data['SetupID'] = setupID; + data['PatientType'] = patientType; + data['PatientID'] = patientID; + data['FirstName'] = firstName; + data['MiddleName'] = middleName; + data['LastName'] = lastName; + data['FirstNameN'] = firstNameN; + data['MiddleNameN'] = middleNameN; + data['LastNameN'] = lastNameN; + data['RelationshipID'] = relationshipID; + data['Gender'] = gender; + data['DateofBirth'] = dateofBirth; + data['DateofBirthN'] = dateofBirthN; + data['NationalityID'] = nationalityID; + data['PhoneResi'] = phoneResi; + data['PhoneOffice'] = phoneOffice; + data['MobileNumber'] = mobileNumber; + data['FaxNumber'] = faxNumber; + data['EmailAddress'] = emailAddress; + data['BloodGroup'] = bloodGroup; + data['RHFactor'] = rHFactor; + data['IsEmailAlertRequired'] = isEmailAlertRequired; + data['IsSMSAlertRequired'] = isSMSAlertRequired; + data['PreferredLanguage'] = preferredLanguage; + data['IsPrivilegedMember'] = isPrivilegedMember; + data['MemberID'] = memberID; + data['ExpiryDate'] = expiryDate; + data['IsHmgEmployee'] = isHmgEmployee; + data['EmployeeID'] = employeeID; + data['EmergencyContactName'] = emergencyContactName; + data['EmergencyContactNo'] = emergencyContactNo; + data['PatientPayType'] = patientPayType; + data['DHCCPatientRefID'] = dHCCPatientRefID; + data['IsPatientDummy'] = isPatientDummy; + data['Status'] = status; + data['IsStatusCleared'] = isStatusCleared; + data['PatientIdentificationType'] = patientIdentificationType; + data['PatientIdentificationNo'] = patientIdentificationNo; + data['ProjectID'] = projectID; + data['InfoSourceID'] = infoSourceID; + data['Address'] = address; + data['Age'] = age; + data['AgeDesc'] = ageDesc; + data['AreaID'] = areaID; + data['CreatedBy'] = createdBy; + data['GenderDescription'] = genderDescription; + data['IR'] = iR; + data['ISOCityID'] = iSOCityID; + data['ISOCountryID'] = iSOCountryID; + if (listPrivilege != null) { + data['ListPrivilege'] = listPrivilege; + } + data['Marital'] = marital; + data['OutSA'] = outSA; + data['POBox'] = pOBox; + data['ReceiveHealthSummaryReport'] = receiveHealthSummaryReport; + data['SourceType'] = sourceType; + data['StrDateofBirth'] = strDateofBirth; + data['TempAddress'] = tempAddress; + data['ZipCode'] = zipCode; + + return data; + } +} + +class ListPrivilege { + int? iD; + String? serviceName; + bool? previlege; + dynamic region; + + ListPrivilege({this.iD, this.serviceName, this.previlege, this.region}); + + ListPrivilege.fromJson(Map json) { + iD = json['ID']; + serviceName = json['ServiceName']; + previlege = json['Previlege']; + region = json['Region']; + } + + Map toJson() { + final Map data = {}; + data['ID'] = iD; + data['ServiceName'] = serviceName; + data['Previlege'] = previlege; + data['Region'] = region; + return data; + } +} diff --git a/lib/features/authentication/models/get_patient_last_login_details_response_model.dart b/lib/features/authentication/models/resp_models/get_patient_last_login_details_response_model.dart similarity index 100% rename from lib/features/authentication/models/get_patient_last_login_details_response_model.dart rename to lib/features/authentication/models/resp_models/get_patient_last_login_details_response_model.dart diff --git a/lib/features/authentication/models/select_device_by_imei.dart b/lib/features/authentication/models/resp_models/select_device_by_imei.dart similarity index 100% rename from lib/features/authentication/models/select_device_by_imei.dart rename to lib/features/authentication/models/resp_models/select_device_by_imei.dart diff --git a/lib/services/analytics/analytics_service.dart b/lib/services/analytics/analytics_service.dart index d4f83a4a..3411157a 100644 --- a/lib/services/analytics/analytics_service.dart +++ b/lib/services/analytics/analytics_service.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:geolocator/geolocator.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; -import 'package:hmg_patient_app_new/features/authentication/models/authenticated_user_model.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/services/analytics/flows/advance_payments.dart'; import 'package:hmg_patient_app_new/services/analytics/flows/app_nav.dart'; import 'package:hmg_patient_app_new/services/analytics/flows/appointments.dart'; diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index e931568e..af5af9ca 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; abstract class DialogService { - Future showErrorDialog(String message); + Future showErrorDialog({required String message, Function()? onOkPressed}); } class DialogServiceImp implements DialogService { @@ -11,7 +12,7 @@ class DialogServiceImp implements DialogService { DialogServiceImp({required this.navigationService}); @override - Future showErrorDialog(String message) async { + Future showErrorDialog({required String message, Function()? onOkPressed}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; @@ -21,15 +22,16 @@ class DialogServiceImp implements DialogService { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), - builder: (_) => _ErrorBottomSheet(message: message), + builder: (_) => _ErrorBottomSheet(message: message, onOkPressed: onOkPressed), ); } } class _ErrorBottomSheet extends StatelessWidget { final String message; + final Function()? onOkPressed; - const _ErrorBottomSheet({required this.message}); + const _ErrorBottomSheet({required this.message, this.onOkPressed}); @override Widget build(BuildContext context) { @@ -55,7 +57,13 @@ class _ErrorBottomSheet extends StatelessWidget { ), const SizedBox(height: 16), ElevatedButton( - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + if (onOkPressed != null) { + onOkPressed!(); + } else { + context.pop(); + } + }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red, shape: RoundedRectangleBorder( diff --git a/lib/services/error_handler_service.dart b/lib/services/error_handler_service.dart index d8d473ae..a26d264b 100644 --- a/lib/services/error_handler_service.dart +++ b/lib/services/error_handler_service.dart @@ -7,7 +7,7 @@ import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; abstract class ErrorHandlerService { - Future handleError(Failure failure); + Future handleError({required Failure failure, Function() onOkPressed}); } class ErrorHandlerServiceImp implements ErrorHandlerService { @@ -22,7 +22,7 @@ class ErrorHandlerServiceImp implements ErrorHandlerService { }); @override - Future handleError(Failure failure) async { + Future handleError({required Failure failure, Function()? onOkPressed}) async { if (failure is APIException) { loggerService.errorLogs("API Exception: ${failure.message}"); } else if (failure is ServerFailure) { @@ -31,6 +31,9 @@ class ErrorHandlerServiceImp implements ErrorHandlerService { } else if (failure is DataParsingFailure) { loggerService.errorLogs("Data Parsing Failure: ${failure.message}"); await _showDialog(failure, title: "Data Error"); + } else if (failure is StatusCodeFailure) { + loggerService.errorLogs("StatusCode Failure: ${failure.message}"); + await _showDialog(failure, title: "Status Code Failure Error"); } else if (failure is HttpException) { loggerService.errorLogs("Http Exception: ${failure.message}"); await _showDialog(failure, title: "Network Error"); @@ -46,7 +49,7 @@ class ErrorHandlerServiceImp implements ErrorHandlerService { } } - Future _showDialog(Failure failure, {String title = "Error"}) async { - await dialogService.showErrorDialog(failure.message); + Future _showDialog(Failure failure, {String title = "Error", Function()? onOkPressed}) async { + await dialogService.showErrorDialog(message: failure.message, onOkPressed: onOkPressed); } } diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 85b2e807..bc3e542b 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -16,8 +16,8 @@ import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'core/consts.dart'; -import 'core/utils/LocalNotification.dart'; -import 'core/utils/push-notification-handler.dart'; +import 'core/utils/local_notifications.dart'; +import 'core/utils/push_notification_handler.dart'; class SplashPage extends StatefulWidget { @override diff --git a/lib/widgets/chip/custom_chip_widget.dart b/lib/widgets/chip/custom_chip_widget.dart index 9afb9866..6c2f1e3e 100644 --- a/lib/widgets/chip/custom_chip_widget.dart +++ b/lib/widgets/chip/custom_chip_widget.dart @@ -5,7 +5,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; class CustomChipWidget extends StatelessWidget { - final ChipType chipType; + final ChipTypeEnum chipType; final String chipText; final String? iconAsset; final VoidCallback? onTap; @@ -14,7 +14,7 @@ class CustomChipWidget extends StatelessWidget { final EdgeInsetsGeometry padding; const CustomChipWidget({ - Key? key, + super.key, required this.chipType, required this.chipText, this.iconAsset, @@ -22,7 +22,7 @@ class CustomChipWidget extends StatelessWidget { this.isSelected = false, this.borderRadius = 12, this.padding = const EdgeInsets.all(8), - }) : super(key: key); + }); @override Widget build(BuildContext context) { From c6a95fe7d72737c988be1909a6bd760fb9d6a75c Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Tue, 2 Sep 2025 15:18:03 +0300 Subject: [PATCH 11/17] latest push after master merged --- .../authentication_view_model.dart | 182 +++++++++--------- lib/presentation/home/landing_page.dart | 8 +- 2 files changed, 92 insertions(+), 98 deletions(-) diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 573c528e..ed77aca0 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -40,96 +40,96 @@ class AuthenticationViewModel extends ChangeNotifier { ); } - Future checkUserAuthentication({Function(dynamic)? onSuccess, Function(String)? onError}) async { - CheckPatientAuthenticationReq checkPatientAuthenticationReq = RequestUtils.getCommonRequestWelcome( - phoneNumber: '0567184134', - otpTypeEnum: OTPTypeEnum.sms, - deviceToken: 'dummyDeviceToken123', - patientOutSA: true, - loginTokenID: 'dummyLoginToken456', - registeredData: null, - patientId: 12345, - nationIdText: '1234567890', - countryCode: 'SA', - ); - - final result = await authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq); - result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), - (apiResponse) { - if (apiResponse.data['isSMSSent']) { - // TODO: set this in AppState - // sharedPref.setString(LOGIN_TOKEN_ID, value['LogInTokenID']); - // loginTokenID = value['LogInTokenID'], - // sharedPref.setObject(REGISTER_DATA_FOR_LOGIIN, request), - sendActivationCode(type); - } else { - if (apiResponse.data['IsAuthenticated']) { - checkActivationCode(onWrongActivationCode: (String? message) {}); - } - } - }, - ); - } - - Future sendActivationCode({required OTPTypeEnum otpTypeEnum}) async { - var request = RequestUtils.getCommonRequestAuthProvider( - otpTypeEnum: otpTypeEnum, - registeredData: null, - deviceToken: "dummyLoginToken456", - mobileNumber: "0567184134", - zipCode: "SA", - patientOutSA: true, - loginTokenID: "dummyLoginToken456", - selectedOption: selectedOption, - patientId: 12345, - ); + // Future checkUserAuthentication({Function(dynamic)? onSuccess, Function(String)? onError}) async { + // CheckPatientAuthenticationReq checkPatientAuthenticationReq = RequestUtils.getCommonRequestWelcome( + // phoneNumber: '0567184134', + // otpTypeEnum: OTPTypeEnum.sms, + // deviceToken: 'dummyDeviceToken123', + // patientOutSA: true, + // loginTokenID: 'dummyLoginToken456', + // registeredData: null, + // patientId: 12345, + // nationIdText: '1234567890', + // countryCode: 'SA', + // ); + // + // final result = await authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq); + // result.fold( + // (failure) async => await errorHandlerService.handleError(failure: failure), + // (apiResponse) { + // if (apiResponse.data['isSMSSent']) { + // // TODO: set this in AppState + // // sharedPref.setString(LOGIN_TOKEN_ID, value['LogInTokenID']); + // // loginTokenID = value['LogInTokenID'], + // // sharedPref.setObject(REGISTER_DATA_FOR_LOGIIN, request), + // sendActivationCode(type); + // } else { + // if (apiResponse.data['IsAuthenticated']) { + // checkActivationCode(onWrongActivationCode: (String? message) {}); + // } + // } + // }, + // ); + // } - request.sMSSignature = await SMSOTP.getSignature(); - selectedOption = type; - // GifLoaderDialogUtils.showMyDialog(context); - if (healthId != null || isDubai) { - if (!isDubai) { - request.dob = dob; //isHijri == 1 ? dob : dateFormat2.format(dateFormat.parse(dob)); - } - request.healthId = healthId; - request.isHijri = isHijri; - await this.apiClient.sendActivationCodeRegister(request).then((result) { - // GifLoaderDialogUtils.hideDialog(context); - if (result != null && result['isSMSSent'] == true) { - this.startSMSService(type); - } - }).catchError((r) { - GifLoaderDialogUtils.hideDialog(context); - context.showBottomSheet( - child: ExceptionBottomSheet( - message: r.toString(), - onOkPressed: () { - Navigator.of(context).pop(); - }, - )); - // AppToast.showErrorToast(message: r); - }); - } else { - request.dob = ""; - request.healthId = ""; - request.isHijri = 0; - await this.authService.sendActivationCode(request).then((result) { - GifLoaderDialogUtils.hideDialog(context); - if (result != null && result['isSMSSent'] == true) { - this.startSMSService(type); - } - }).catchError((r) { - GifLoaderDialogUtils.hideDialog(context); - context.showBottomSheet( - child: ExceptionBottomSheet( - message: r.toString(), - onOkPressed: () { - Navigator.of(context).pop(); - }, - )); - // AppToast.showErrorToast(message: r.toString()); - }); - } - } + // Future sendActivationCode({required OTPTypeEnum otpTypeEnum}) async { + // var request = RequestUtils.getCommonRequestAuthProvider( + // otpTypeEnum: otpTypeEnum, + // registeredData: null, + // deviceToken: "dummyLoginToken456", + // mobileNumber: "0567184134", + // zipCode: "SA", + // patientOutSA: true, + // loginTokenID: "dummyLoginToken456", + // selectedOption: selectedOption, + // patientId: 12345, + // ); + // + // request.sMSSignature = await SMSOTP.getSignature(); + // selectedOption = type; + // // GifLoaderDialogUtils.showMyDialog(context); + // if (healthId != null || isDubai) { + // if (!isDubai) { + // request.dob = dob; //isHijri == 1 ? dob : dateFormat2.format(dateFormat.parse(dob)); + // } + // request.healthId = healthId; + // request.isHijri = isHijri; + // await this.apiClient.sendActivationCodeRegister(request).then((result) { + // // GifLoaderDialogUtils.hideDialog(context); + // if (result != null && result['isSMSSent'] == true) { + // this.startSMSService(type); + // } + // }).catchError((r) { + // GifLoaderDialogUtils.hideDialog(context); + // context.showBottomSheet( + // child: ExceptionBottomSheet( + // message: r.toString(), + // onOkPressed: () { + // Navigator.of(context).pop(); + // }, + // )); + // // AppToast.showErrorToast(message: r); + // }); + // } else { + // request.dob = ""; + // request.healthId = ""; + // request.isHijri = 0; + // await this.authService.sendActivationCode(request).then((result) { + // GifLoaderDialogUtils.hideDialog(context); + // if (result != null && result['isSMSSent'] == true) { + // this.startSMSService(type); + // } + // }).catchError((r) { + // GifLoaderDialogUtils.hideDialog(context); + // context.showBottomSheet( + // child: ExceptionBottomSheet( + // message: r.toString(), + // onOkPressed: () { + // Navigator.of(context).pop(); + // }, + // )); + // // AppToast.showErrorToast(message: r.toString()); + // }); + // } + // } } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 27e95de3..8421856f 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -11,23 +11,17 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/authantication/login.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; -import 'package:hmg_patient_app_new/providers/authentication_view_model.dart'; import 'package:hmg_patient_app_new/providers/bottom_navigation_provider.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; -import '../../core/app_assets.dart'; -import '../../core/utils/utils.dart'; -import '../../widgets/buttons/custom_button.dart'; -import '../../widgets/transitions/fade_page.dart'; - class LandingPage extends StatefulWidget { const LandingPage({super.key}); From 9b1c3fe920a577edb074d3bca6800122e683fda6 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 2 Sep 2025 15:22:42 +0300 Subject: [PATCH 12/17] medical file updates --- .../medical_file/medical_file_page.dart | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 55ca84bf..f4ba5076 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -161,7 +161,51 @@ class MedicalFilePage extends StatelessWidget { ], ), ), - ) + ), + SizedBox(height: 16.h), + //Insurance Tab Data + Container( + height: 150.h, + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Haroon Amjad".toText18(isBold: true), + "Policy: 223123345".toText12(isBold: true, color: AppColors.lightGrayColor), + ], + ), + CustomButton( + icon: AppAssets.cross_circle, + iconColor: AppColors.primaryRedColor, + iconSize: 13.h, + text: "Insurance Expired", + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + borderColor: AppColors.primaryRedColor.withOpacity(0.0), + textColor: AppColors.primaryRedColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + ], + ), + ), + ), ], ), ), From 22e9fb738b133c3ef8d7af07a9ff831637c96a34 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 2 Sep 2025 16:35:50 +0300 Subject: [PATCH 13/17] assets added --- assets/images/svg/Saudi_Riyal_Symbol.svg | 12 ++++++++++++ assets/images/svg/blood_icon.svg | 3 +++ assets/images/svg/checkmark_icon.svg | 3 +++ assets/images/svg/file_icon.svg | 3 +++ assets/images/svg/habib_logo_background.svg | 3 +++ assets/images/svg/insurance_active_icon.svg | 3 +++ assets/images/svg/recharge_icon.svg | 4 ++++ assets/images/svg/show_icon.svg | 4 ++++ 8 files changed, 35 insertions(+) create mode 100644 assets/images/svg/Saudi_Riyal_Symbol.svg create mode 100644 assets/images/svg/blood_icon.svg create mode 100644 assets/images/svg/checkmark_icon.svg create mode 100644 assets/images/svg/file_icon.svg create mode 100644 assets/images/svg/habib_logo_background.svg create mode 100644 assets/images/svg/insurance_active_icon.svg create mode 100644 assets/images/svg/recharge_icon.svg create mode 100644 assets/images/svg/show_icon.svg diff --git a/assets/images/svg/Saudi_Riyal_Symbol.svg b/assets/images/svg/Saudi_Riyal_Symbol.svg new file mode 100644 index 00000000..d57d83a1 --- /dev/null +++ b/assets/images/svg/Saudi_Riyal_Symbol.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/assets/images/svg/blood_icon.svg b/assets/images/svg/blood_icon.svg new file mode 100644 index 00000000..c83b20c4 --- /dev/null +++ b/assets/images/svg/blood_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/checkmark_icon.svg b/assets/images/svg/checkmark_icon.svg new file mode 100644 index 00000000..e5141063 --- /dev/null +++ b/assets/images/svg/checkmark_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/file_icon.svg b/assets/images/svg/file_icon.svg new file mode 100644 index 00000000..7bfc6553 --- /dev/null +++ b/assets/images/svg/file_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/habib_logo_background.svg b/assets/images/svg/habib_logo_background.svg new file mode 100644 index 00000000..597c7b2a --- /dev/null +++ b/assets/images/svg/habib_logo_background.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/insurance_active_icon.svg b/assets/images/svg/insurance_active_icon.svg new file mode 100644 index 00000000..33a25bb8 --- /dev/null +++ b/assets/images/svg/insurance_active_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/recharge_icon.svg b/assets/images/svg/recharge_icon.svg new file mode 100644 index 00000000..a6f26ce3 --- /dev/null +++ b/assets/images/svg/recharge_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/show_icon.svg b/assets/images/svg/show_icon.svg new file mode 100644 index 00000000..541fd55d --- /dev/null +++ b/assets/images/svg/show_icon.svg @@ -0,0 +1,4 @@ + + + + From 89456bc2aa76beaf080f906801d25ca49509bf67 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 2 Sep 2025 17:24:12 +0300 Subject: [PATCH 14/17] Medical File Insurance tab content done --- assets/images/svg/eye_results_icon.svg | 4 ++ lib/core/app_assets.dart | 1 + .../medical_file/medical_file_page.dart | 69 ++++++++++++++++++- .../widgets/medical_file_card.dart | 44 ++++++++++++ 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 assets/images/svg/eye_results_icon.svg create mode 100644 lib/presentation/medical_file/widgets/medical_file_card.dart diff --git a/assets/images/svg/eye_results_icon.svg b/assets/images/svg/eye_results_icon.svg new file mode 100644 index 00000000..fd9c7545 --- /dev/null +++ b/assets/images/svg/eye_results_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 0e7d9448..cba90a1d 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -59,6 +59,7 @@ class AppAssets { static const String habib_background_icon = '$svgBasePath/habib_logo_background.svg'; static const String show_icon = '$svgBasePath/show_icon.svg'; static const String recharge_icon = '$svgBasePath/recharge_icon.svg'; + static const String eye_result_icon = '$svgBasePath/eye_results_icon.svg'; // PNGS // static const String hmg_logo = '$pngBasePath/hmg_logo.png'; diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index f4ba5076..6635ef52 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; @@ -165,7 +166,7 @@ class MedicalFilePage extends StatelessWidget { SizedBox(height: 16.h), //Insurance Tab Data Container( - height: 150.h, + // height: 150.h, width: double.infinity, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -174,6 +175,7 @@ class MedicalFilePage extends StatelessWidget { child: Padding( padding: EdgeInsets.all(16.h), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -202,10 +204,75 @@ class MedicalFilePage extends StatelessWidget { ), ], ), + SizedBox(height: 12.h), + "NCCI".toText12(isBold: true), + "NC_Dr Sulaiman Al Habib Medical Group".toText12(isBold: true), + SizedBox(height: 8.h), + Row( + children: [ + CustomButton( + icon: AppAssets.cross_circle, + iconColor: AppColors.primaryRedColor, + iconSize: 13.h, + text: "Expiry: 18 Mar, 2025", + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + borderColor: AppColors.primaryRedColor.withOpacity(0.0), + textColor: AppColors.primaryRedColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + SizedBox(width: 5.h), + CustomButton( + text: "Patient Card ID: 3628599", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.normal, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + SizedBox(height: 10.h), + CustomButton( + icon: AppAssets.cross_circle, + iconColor: AppColors.primaryRedColor, + iconSize: 13.h, + text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", + onPressed: () {}, + backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + borderColor: AppColors.bgGreenColor.withOpacity(0.0), + textColor: AppColors.bgGreenColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), ], ), ), ), + SizedBox(height: 10.h), + GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(top: 12), + shrinkWrap: true, + children: [ + MedicalFileCard(label: "Update Insurance", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard(label: "Insurance Approvals", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard(label: "My Invoices List", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard(label: "Ancillary Orders List", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + ], + ), ], ), ), diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart new file mode 100644 index 00000000..92bb4eec --- /dev/null +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; + +class MedicalFileCard extends StatelessWidget { + final String label; + + // final Color svgColor; + final Color textColor; + final Color backgroundColor; + final String svgIcon; + + MedicalFileCard({ + required this.label, + // required this.svgColor, + required this.textColor, + required this.backgroundColor, + this.svgIcon = "", + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: backgroundColor, + borderRadius: 20, + ), + child: Padding( + padding: EdgeInsets.all(8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: svgIcon, width: 30.h, height: 30.h, fit: BoxFit.contain), + SizedBox(height: 12.h), + label.toText11(color: textColor, isBold: true), + ], + ), + ), + ); + } +} From e044fc8c5e1746ccd3e88fac881de26112de3ebb Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 2 Sep 2025 19:13:15 +0300 Subject: [PATCH 15/17] lab implementation contd. --- assets/images/svg/search_icon.svg | 3 + lib/core/api/api_client.dart | 2 +- lib/core/app_assets.dart | 1 + lib/features/lab/lab_repo.dart | 9 + lib/features/lab/lab_view_model.dart | 13 ++ lib/main.dart | 4 + .../home/data/landing_page_data.dart | 22 +++ .../home/data/service_card_data.dart | 2 + lib/presentation/home/landing_page.dart | 1 + .../home/widgets/small_service_card.dart | 25 ++- lib/presentation/lab/lab_orders_page.dart | 173 ++++++++++++++++++ .../medical_file/medical_file_page.dart | 3 - lib/theme/colors.dart | 1 + 13 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 assets/images/svg/search_icon.svg create mode 100644 lib/features/lab/lab_repo.dart create mode 100644 lib/features/lab/lab_view_model.dart create mode 100644 lib/presentation/lab/lab_orders_page.dart diff --git a/assets/images/svg/search_icon.svg b/assets/images/svg/search_icon.svg new file mode 100644 index 00000000..1a64ce5b --- /dev/null +++ b/assets/images/svg/search_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 92a7e6c5..53bd522b 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -179,7 +179,7 @@ class ApiClientImp implements ApiClient { // TODO : we will use all these from appState body['LanguageID'] = body['LanguageID'] ?? "2"; - body['VersionID'] = body['VersionID'] ?? "18.7"; + body['VersionID'] = body['VersionID'] ?? "50.0"; body['Channel'] = body['Channel'] ?? "3"; body['IPAdress'] = body['IPAdress'] ?? "10.20.10.20"; body['generalid'] = body['generalid'] ?? "Cs2020@2016\$2958"; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index cba90a1d..2032f56e 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -60,6 +60,7 @@ class AppAssets { static const String show_icon = '$svgBasePath/show_icon.svg'; static const String recharge_icon = '$svgBasePath/recharge_icon.svg'; static const String eye_result_icon = '$svgBasePath/eye_results_icon.svg'; + static const String search_icon = '$svgBasePath/search_icon.svg'; // PNGS // static const String hmg_logo = '$pngBasePath/hmg_logo.png'; diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart new file mode 100644 index 00000000..f651a715 --- /dev/null +++ b/lib/features/lab/lab_repo.dart @@ -0,0 +1,9 @@ +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:dartz/dartz.dart'; + +abstract class AuthenticationRepo { + Future>> getPatientLabOrders({ + required String firebaseToken, + }); +} diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart new file mode 100644 index 00000000..f0ebad3e --- /dev/null +++ b/lib/features/lab/lab_view_model.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class LabViewModel extends ChangeNotifier { + + bool isLabOrdersLoading = false; + bool isLabResultsLoading = false; + + initLabProvider() { + isLabOrdersLoading = true; + isLabResultsLoading = true; + notifyListeners(); + } +} diff --git a/lib/main.dart b/lib/main.dart index bb4332b2..1ea5d84b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/providers/bottom_navigation_provider.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -54,6 +55,9 @@ void main() async { ChangeNotifierProvider( create: (_) => BottomNavigationProvider(), ), + ChangeNotifierProvider( + create: (_) => LabViewModel(), + ), ChangeNotifierProvider( create: (_) => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index af43b9f5..84744225 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; class LandingPageData { static List getNotLoggedInServiceCardsList = [ ServiceCardData( + serviceName: "emergency", icon: AppAssets.emergency_services_icon, title: "Emergency", subtitle: "Services", @@ -17,6 +18,7 @@ class LandingPageData { isBold: true, ), ServiceCardData( + serviceName: "indoor_navigation", icon: AppAssets.indoor_nav_icon, title: "Indoor", subtitle: "Navigation", @@ -26,6 +28,7 @@ class LandingPageData { isBold: false, ), ServiceCardData( + serviceName: "search_doctor", icon: AppAssets.search_doctor_icon, title: "Search", subtitle: "Doctor", @@ -35,6 +38,7 @@ class LandingPageData { isBold: false, ), ServiceCardData( + serviceName: "health_calculators", icon: AppAssets.health_calculators_icon, title: "Health", subtitle: "Calculators", @@ -44,6 +48,7 @@ class LandingPageData { isBold: false, ), ServiceCardData( + serviceName: "health_converters", icon: AppAssets.health_calculators_icon, title: "Health", subtitle: "Converters", @@ -53,6 +58,7 @@ class LandingPageData { isBold: false, ), ServiceCardData( + serviceName: "parking_guide", icon: AppAssets.health_calculators_icon, title: "Parking", subtitle: "Guide", @@ -65,6 +71,7 @@ class LandingPageData { static List getLoggedInServiceCardsList = [ ServiceCardData( + serviceName: "emergency", icon: AppAssets.emergency_services_icon, title: "Emergency", subtitle: "Services", @@ -74,6 +81,7 @@ class LandingPageData { isBold: true, ), ServiceCardData( + serviceName: "lab_results", icon: AppAssets.lab_result_icon, title: "My Lab", subtitle: "Results", @@ -83,6 +91,17 @@ class LandingPageData { isBold: false, ), ServiceCardData( + serviceName: "radiology_results", + icon: AppAssets.lab_result_icon, + title: "My Radiology", + subtitle: "Results", + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), + ServiceCardData( + serviceName: "prescriptions", icon: AppAssets.my_prescription_icon, title: "My", subtitle: "Prescriptions", @@ -92,6 +111,7 @@ class LandingPageData { isBold: false, ), ServiceCardData( + serviceName: "insurance_update", icon: AppAssets.insurance_update_icon, title: "Insurance", subtitle: "Update", @@ -101,6 +121,7 @@ class LandingPageData { isBold: false, ), ServiceCardData( + serviceName: "my_doctors", icon: AppAssets.insurance_update_icon, title: "My", subtitle: "Doctors", @@ -110,6 +131,7 @@ class LandingPageData { isBold: false, ), ServiceCardData( + serviceName: "sick_leaves", icon: AppAssets.insurance_update_icon, title: "My Sick", subtitle: "Leaves", diff --git a/lib/presentation/home/data/service_card_data.dart b/lib/presentation/home/data/service_card_data.dart index 856a15ec..49e7e3d8 100644 --- a/lib/presentation/home/data/service_card_data.dart +++ b/lib/presentation/home/data/service_card_data.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:hmg_patient_app_new/theme/colors.dart'; class ServiceCardData { + final String serviceName; final String icon; final String title; final String subtitle; @@ -13,6 +14,7 @@ class ServiceCardData { final String largeCardIcon; ServiceCardData({ + this.serviceName = "", this.icon = "", this.title = "", this.subtitle = "", diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 8421856f..a1707fa6 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -155,6 +155,7 @@ class _LandingPageState extends State { horizontalOffset: 100.0, child: FadeInAnimation( child: SmallServiceCard( + serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, icon: LandingPageData.getLoggedInServiceCardsList[index].icon, title: LandingPageData.getLoggedInServiceCardsList[index].title, subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index a138db65..0983a067 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -2,11 +2,14 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import '../../../core/utils/utils.dart'; import '../../../theme/colors.dart'; class SmallServiceCard extends StatelessWidget { + final String serviceName; final String icon; final String title; final String subtitle; @@ -18,6 +21,7 @@ class SmallServiceCard extends StatelessWidget { SmallServiceCard({ super.key, this.icon = "", + this.serviceName = "", this.title = "", this.subtitle = "", this.backgroundColor = AppColors.whiteColor, @@ -48,6 +52,25 @@ class SmallServiceCard extends StatelessWidget { ), ), ), - ); + ).onPress(() { + switch (serviceName) { + case "lab_results": + Navigator.of(context).push( + FadePage( + page: LabOrdersPage(), + ), + ); + break; + case "radiology_results": + break; + case "prescriptions": + break; + case "insurance_update": + break; + default: + // Handle unknown service + break; + } + }); } } diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart new file mode 100644 index 00000000..bc7ff619 --- /dev/null +++ b/lib/presentation/lab/lab_orders_page.dart @@ -0,0 +1,173 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; + +class LabOrdersPage extends StatefulWidget { + const LabOrdersPage({super.key}); + + @override + State createState() => _LabOrdersPageState(); +} + +class _LabOrdersPageState extends State { + late LabViewModel labProvider; + + int? expandedIndex; + + // Sample data for demonstration + final List labOrders = [ + 'Blood Test', + 'Urine Test', + 'X-Ray', + 'MRI', + ]; + + @override + void initState() { + scheduleMicrotask(() { + labProvider.initLabProvider(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + labProvider = Provider.of(context); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: AppBar( + title: const Text('Lab Results'), + backgroundColor: AppColors.bgScaffoldColor, + ), + body: Padding( + padding: EdgeInsets.all(24.h), + child: SingleChildScrollView( + child: Consumer( + builder: (context, model, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.labResults.tr(context: context).toText24(isBold: true), + Utils.buildSvgWithAssets(icon: AppAssets.search_icon), + ], + ), + SizedBox(height: 16.h), + // Build Tab Bar + SizedBox(height: 16.h), + // Expandable list + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: model.isLabOrdersLoading ? 5 : labOrders.length, + itemBuilder: (context, index) { + final isExpanded = expandedIndex == index; + return model.isLabOrdersLoading + ? const MoviesShimmerWidget() + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + ), + child: InkWell( + onTap: () { + setState(() { + expandedIndex = isExpanded ? null : index; + }); + }, + child: Column( + children: [ + Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomButton( + text: LocaleKeys.pending.tr(context: context), + onPressed: () {}, + backgroundColor: getLabOrderStatusColor(44).withOpacity(0.15), + borderColor: getLabOrderStatusColor(44).withOpacity(0.01), + textColor: getLabOrderStatusColor(44), + fontSize: 10, + fontWeight: FontWeight.normal, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + Icon(isExpanded ? Icons.expand_less : Icons.expand_more), + ], + ), + SizedBox(height: 8.h), + Text(labOrders[index], style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + ], + ), + ), + AnimatedCrossFade( + firstChild: SizedBox.shrink(), + secondChild: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Text('Details for ${labOrders[index]}'), + ), + crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, + duration: Duration(milliseconds: 300), + ), + ], + ), + ), + ), + ), + ), + ); + }, + ), + ], + ); + }, + ), + ), + ), + ); + } + + Color getLabOrderStatusColor(num status) { + switch (status) { + case 44: + return AppColors.warningColorYellow; + case 45: + return AppColors.warningColorYellow; + case 16: + return AppColors.successColor; + case 17: + return AppColors.successColor; + default: + return AppColors.greyColor; + } + } +} diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 6635ef52..7fa4fe9f 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -87,9 +87,6 @@ class MedicalFilePage extends StatelessWidget { ), SizedBox(width: 4.h), CustomButton( - icon: AppAssets.checkmark_icon, - iconColor: AppColors.successColor, - iconSize: 13.h, text: LocaleKeys.verified.tr(context: context), onPressed: () {}, backgroundColor: AppColors.greyColor, diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index a6b33622..8b388f1f 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -31,6 +31,7 @@ class AppColors { static const Color textColor = Color(0xFF2E3039); static const Color borderOnlyColor = Color(0xFF2E3039); static const Color dividerColor = Color(0xFFD2D2D2); + static const Color warningColorYellow = Color(0xFFF4A308); //Chips static const Color successColor = Color(0xff18C273); From a77fc49c68824cbb2c140409637c660319696af9 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Tue, 2 Sep 2025 22:45:47 +0300 Subject: [PATCH 16/17] lab result changes --- lib/core/api/api_client.dart | 7 +- lib/core/api_consts.dart | 4 +- lib/core/dependencies.dart | 14 +- lib/features/lab/lab_repo.dart | 58 ++- lib/features/lab/lab_view_model.dart | 38 +- .../patient_lab_orders_response_model.dart | 126 +++++ lib/main.dart | 9 +- lib/presentation/home/landing_page.dart | 441 +++++++++--------- lib/presentation/lab/lab_orders_page.dart | 53 ++- lib/providers/bottom_navigation_provider.dart | 12 - 10 files changed, 501 insertions(+), 261 deletions(-) create mode 100644 lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart delete mode 100644 lib/providers/bottom_navigation_provider.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 53bd522b..b3ea4870 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -134,9 +134,7 @@ class ApiClientImp implements ApiClient { } if (body.containsKey('isDentalAllowedBackend')) { - body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') - ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND - : IS_DENTAL_ALLOWED_BACKEND; + body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; } //Todo: I have converted it to string @@ -229,8 +227,7 @@ class ApiClientImp implements ApiClient { } else if (parsed['Result'] == 'OK') { onSuccess(parsed, statusCode); } else { - onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, - failureType: ServerFailure("Error While Fetching data")); + onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, failureType: ServerFailure("Error While Fetching data")); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 6f82e26a..6e1e92a5 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -17,8 +17,8 @@ var PACKAGES_ORDERS = '/api/orders'; var PACKAGES_ORDER_HISTORY = '/api/orders/items'; var PACKAGES_TAMARA_OPT = '/api/orders/paymentoptions/tamara'; // var BASE_URL = 'http://10.50.100.198:2018/'; -var BASE_URL = 'https://uat.hmgwebservices.com/'; -// var BASE_URL = 'https://hmgwebservices.com/'; +// var BASE_URL = 'https://uat.hmgwebservices.com/'; +var BASE_URL = 'https://hmgwebservices.com/'; // var BASE_URL = 'http://10.201.204.103/'; // var BASE_URL = 'https://orash.cloudsolutions.com.sa/'; // var BASE_URL = 'https://vidauat.cloudsolutions.com.sa/'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 549eeed2..dca08fc3 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -6,6 +6,8 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_repo. import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; @@ -54,12 +56,20 @@ class AppDependencies { // Repositories getIt.registerLazySingleton(() => CommonRepoImp(loggerService: getIt())); getIt.registerLazySingleton(() => AuthenticationRepoImp(loggerService: getIt(), apiClient: getIt())); - getIt.registerLazySingleton( - () => BookAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => BookAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MyAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => LabRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton + + getIt.registerLazySingleton( + () => LabViewModel( + labRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + getIt.registerLazySingleton( () => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart index f651a715..061506a5 100644 --- a/lib/features/lab/lab_repo.dart +++ b/lib/features/lab/lab_repo.dart @@ -1,9 +1,59 @@ +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; -abstract class AuthenticationRepo { - Future>> getPatientLabOrders({ - required String firebaseToken, - }); +abstract class LabRepo { + Future>>> getPatientLabOrders({required num patientId}); +} + +class LabRepoImp implements LabRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + LabRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getPatientLabOrders({required num patientId}) async { + final mapDevice = {"PatientID": patientId}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_Patient_LAB_ORDERS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus}) { + try { + final list = response['ListPLO']; + if (list == null || list.isEmpty) { + throw Exception("lab list is empty"); + } + + final labOrders = list.map((item) => PatientLabOrdersResponseModel.fromJson(item as Map)).toList(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: labOrders, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index f0ebad3e..73f8b137 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -1,13 +1,45 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class LabViewModel extends ChangeNotifier { - bool isLabOrdersLoading = false; bool isLabResultsLoading = false; + LabRepo labRepo; + ErrorHandlerService errorHandlerService; + + List patientLabOrders = []; + + LabViewModel({required this.labRepo, required this.errorHandlerService}); + initLabProvider() { - isLabOrdersLoading = true; - isLabResultsLoading = true; + patientLabOrders.clear(); + // isLabOrdersLoading = true; + // isLabResultsLoading = true; + // getPatientLabOrders(); notifyListeners(); } + + Future getPatientLabOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await labRepo.getPatientLabOrders(patientId: 1231755); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientLabOrders = apiResponse.data!; + isLabOrdersLoading = false; + isLabResultsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart b/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart new file mode 100644 index 00000000..5cd8fbd7 --- /dev/null +++ b/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart @@ -0,0 +1,126 @@ +class PatientLabOrdersResponseModel { + String? description; + dynamic femaleInterpretativeData; + int? gender; + bool? isCertificateAllowed; + int? lineItemNo; + dynamic maleInterpretativeData; + dynamic notes; + int? orderLineItemNo; + int? orderNo; + String? packageID; + int? patientID; + String? projectID; + String? referanceRange; + String? resultValue; + int? resultValueBasedLineItemNo; + String? resultValueFlag; + String? sampleCollectedOn; + String? sampleReceivedOn; + String? setupID; + dynamic superVerifiedOn; + String? testCode; + String? uOM; + String? verifiedOn; + String? packageShortDescription; + String? testShortDescription; + dynamic verifiedOnDateTime; + num? percentage; + num? width; + num? resultTypeID; + + PatientLabOrdersResponseModel( + {this.description, + this.femaleInterpretativeData, + this.gender, + this.isCertificateAllowed, + this.lineItemNo, + this.maleInterpretativeData, + this.notes, + this.orderLineItemNo, + this.orderNo, + this.packageID, + this.patientID, + this.projectID, + this.referanceRange, + this.resultValue, + this.resultValueBasedLineItemNo, + this.resultValueFlag, + this.sampleCollectedOn, + this.sampleReceivedOn, + this.setupID, + this.superVerifiedOn, + this.testCode, + this.uOM, + this.verifiedOn, + this.verifiedOnDateTime}); + + PatientLabOrdersResponseModel.fromJson(Map json, {String? flag}) { + description = json['Description']; + femaleInterpretativeData = json['FemaleInterpretativeData']; + gender = json['Gender']; + isCertificateAllowed = json['IsCertificateAllowed']; + lineItemNo = json['LineItemNo']; + maleInterpretativeData = json['MaleInterpretativeData']; + notes = json['Notes']; + orderLineItemNo = json['OrderLineItemNo']; + orderNo = json['OrderNo']; + packageID = json['PackageID']; + patientID = json['PatientID']; + projectID = json['ProjectID']; + referanceRange = json['ReferanceRange']; + resultValue = json['ResultValue']; + resultValueBasedLineItemNo = json['ResultValueBasedLineItemNo']; + resultValueFlag = json['ResultValueFlag']; + sampleCollectedOn = json['SampleCollectedOn']; + sampleReceivedOn = json['SampleReceivedOn']; + setupID = json['SetupID']; + superVerifiedOn = json['SuperVerifiedOn']; + testCode = json['TestCode']; + uOM = json['UOM']; + verifiedOn = json['VerifiedOn']; + verifiedOnDateTime = json['VerifiedOnDateTime']; + packageShortDescription = json['PackageShortDescription']; + testShortDescription = json['TestShortDescription']; + resultTypeID = json['ResultTypeID']; + } + + Map toJson() { + final Map data = new Map(); + data['Description'] = this.description; + data['FemaleInterpretativeData'] = this.femaleInterpretativeData; + data['Gender'] = this.gender; + data['IsCertificateAllowed'] = this.isCertificateAllowed; + data['LineItemNo'] = this.lineItemNo; + data['MaleInterpretativeData'] = this.maleInterpretativeData; + data['Notes'] = this.notes; + data['OrderLineItemNo'] = this.orderLineItemNo; + data['OrderNo'] = this.orderNo; + data['PackageID'] = this.packageID; + data['PatientID'] = this.patientID; + data['ProjectID'] = this.projectID; + data['ReferanceRange'] = this.referanceRange; + data['ResultValue'] = this.resultValue; + data['ResultValueBasedLineItemNo'] = this.resultValueBasedLineItemNo; + data['ResultValueFlag'] = this.resultValueFlag; + data['SampleCollectedOn'] = this.sampleCollectedOn; + data['SampleReceivedOn'] = this.sampleReceivedOn; + data['SetupID'] = this.setupID; + data['SuperVerifiedOn'] = this.superVerifiedOn; + data['TestCode'] = this.testCode; + data['UOM'] = this.uOM; + data['VerifiedOn'] = this.verifiedOn; + data['VerifiedOnDateTime'] = this.verifiedOnDateTime; + data['PackageShortDescription'] = this.packageShortDescription; + data['TestShortDescription'] = this.testShortDescription; + + return data; + } + + bool shouldShowResultBarAndGraph() { + if (resultTypeID == null) return false; + if (resultTypeID == 6) return false; + + return true; + } +} diff --git a/lib/main.dart b/lib/main.dart index 1ea5d84b..3f096497 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,7 +7,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; -import 'package:hmg_patient_app_new/providers/bottom_navigation_provider.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -52,11 +51,11 @@ void main() async { path: 'assets/langs', fallbackLocale: Locale('en', 'US'), child: MultiProvider(providers: [ - ChangeNotifierProvider( - create: (_) => BottomNavigationProvider(), - ), ChangeNotifierProvider( - create: (_) => LabViewModel(), + create: (_) => LabViewModel( + labRepo: getIt(), + errorHandlerService: getIt(), + ), ), ChangeNotifierProvider( create: (_) => AuthenticationViewModel( diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index a1707fa6..f930dca0 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -16,7 +16,6 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card. import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; -import 'package:hmg_patient_app_new/providers/bottom_navigation_provider.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; @@ -34,247 +33,245 @@ class _LandingPageState extends State { Widget build(BuildContext context) { AppState appState = getIt.get(); final AuthenticationViewModel authenticationViewModel = context.read(); - return Consumer(builder: (context, navigationProvider, child) { - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: Padding( - padding: EdgeInsets.all(24.h), - child: SingleChildScrollView( - child: Column( - children: [ - Padding( - padding: EdgeInsets.only(top: 50.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomButton( - text: LocaleKeys.loginOrRegister.tr(context: context), - onPressed: () async { - await authenticationViewModel.selectDeviceImei(); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50, - ), - Utils.buildSvgWithAssets( - icon: AppAssets.contact_icon, - width: 24, - height: 24, - ).onPress(() { - Navigator.of(context).push( - FadePage( - page: MedicalFilePage(), - // page: LoginScreen(), - ), - ); - }), - ], - ), + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Padding( + padding: EdgeInsets.all(24.h), + child: SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: EdgeInsets.only(top: 50.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomButton( + text: LocaleKeys.loginOrRegister.tr(context: context), + onPressed: () async { + await authenticationViewModel.selectDeviceImei(); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50, + ), + Utils.buildSvgWithAssets( + icon: AppAssets.contact_icon, + width: 24, + height: 24, + ).onPress(() { + Navigator.of(context).push( + FadePage( + page: MedicalFilePage(), + // page: LoginScreen(), + ), + ); + }), + ], ), - SizedBox(height: 16.h), - appState.isAuthenticated - ? Column( - children: [ - Container( - width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Padding( - padding: EdgeInsets.all(12.h), - child: Column( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), - SizedBox(height: 12.h), - "You do not have any upcoming appointment. Please book an appointment".toText12(isCenter: true), - SizedBox(height: 12.h), - CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (BuildContext context) => LandingPage()), - ); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40, - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - ), - ], - ), - ), + ), + SizedBox(height: 16.h), + appState.isAuthenticated + ? Column( + children: [ + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, ), - SizedBox(height: 12.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "Quick Links".toText16(isBold: true), - Row( - children: [ - "View medical file".toText12(color: AppColors.primaryRedColor), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], - ), - ], + child: Padding( + padding: EdgeInsets.all(12.h), + child: Column( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), + SizedBox(height: 12.h), + "You do not have any upcoming appointment. Please book an appointment".toText12(isCenter: true), + SizedBox(height: 12.h), + CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (BuildContext context) => LandingPage()), + ); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40, + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + ), + ], + ), ), - SizedBox(height: 12.h), - Container( - height: 127.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, + ), + SizedBox(height: 12.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Quick Links".toText16(isBold: true), + Row( + children: [ + "View medical file".toText12(color: AppColors.primaryRedColor), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - Expanded( - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getLoggedInServiceCardsList.length, - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, - icon: LandingPageData.getLoggedInServiceCardsList[index].icon, - title: LandingPageData.getLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor, - textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, - ), + ], + ), + SizedBox(height: 12.h), + Container( + height: 127.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Expanded( + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getLoggedInServiceCardsList.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, + icon: LandingPageData.getLoggedInServiceCardsList[index].icon, + title: LandingPageData.getLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor, + textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, ), ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 0.width, - ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 0.width, ), - ], - ), + ), + ], ), - ) - ], - ) - : Container( - height: 127.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - Expanded( - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, - title: LandingPageData.getNotLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor, - textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, - ), + ), + ) + ], + ) + : Container( + height: 127.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Expanded( + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, + title: LandingPageData.getNotLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor, + textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, ), ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 0.width, - ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 0.width, ), - ], - ), + ), + ], ), ), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "Services".toText16(isBold: true), - Row( - children: [ - "View all services".toText12(color: AppColors.primaryRedColor), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], ), - ], - ), - SizedBox(height: 16.h), - SizedBox( - height: 325.h, - child: Column( + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Services".toText16(isBold: true), + Row( children: [ - Expanded( - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getServiceCardsList.length, - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: LargeServiceCard( - image: LandingPageData.getServiceCardsList[index].icon, - title: LandingPageData.getServiceCardsList[index].title, - subtitle: LandingPageData.getServiceCardsList[index].subtitle, - icon: LandingPageData.getServiceCardsList[index].largeCardIcon, - ), + "View all services".toText12(color: AppColors.primaryRedColor), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ), + ], + ), + SizedBox(height: 16.h), + SizedBox( + height: 325.h, + child: Column( + children: [ + Expanded( + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getServiceCardsList.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: LargeServiceCard( + image: LandingPageData.getServiceCardsList[index].icon, + title: LandingPageData.getServiceCardsList[index].title, + subtitle: LandingPageData.getServiceCardsList[index].subtitle, + icon: LandingPageData.getServiceCardsList[index].largeCardIcon, ), ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 0.width, - ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 0.width, ), - ], - ), + ), + ], ), - SizedBox(height: 16.h), - appState.isAuthenticated ? HabibWalletCard() : SizedBox(), - ], - ), + ), + SizedBox(height: 16.h), + appState.isAuthenticated ? HabibWalletCard() : SizedBox(), + ], ), ), - ); - }); + ), + ); } } diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index bc7ff619..e0143ca7 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -29,10 +29,10 @@ class _LabOrdersPageState extends State { // Sample data for demonstration final List labOrders = [ - 'Blood Test', - 'Urine Test', - 'X-Ray', - 'MRI', + 'Mohammad Al Harbi', + 'Mohammad Al Harbi', + 'Mohammad Al Harbi', + 'Mohammad Al Harbi', ]; @override @@ -116,7 +116,7 @@ class _LabOrdersPageState extends State { borderColor: getLabOrderStatusColor(44).withOpacity(0.01), textColor: getLabOrderStatusColor(44), fontSize: 10, - fontWeight: FontWeight.normal, + fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 30.h, @@ -125,7 +125,48 @@ class _LabOrdersPageState extends State { ], ), SizedBox(height: 8.h), - Text(labOrders[index], style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + Row( + children: [ + Image.network( + "https://hmgwebservices.com/Images/MobileImages/SUWAIDI/152305.png", + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 4.h), + Text(labOrders[index], style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + CustomButton( + text: "12th June, 2025", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 24.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: "Clinic: Cardiology", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 24.h, + ), + ], + ), ], ), ), diff --git a/lib/providers/bottom_navigation_provider.dart b/lib/providers/bottom_navigation_provider.dart deleted file mode 100644 index 58a0d4ec..00000000 --- a/lib/providers/bottom_navigation_provider.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter/material.dart'; - -class BottomNavigationProvider extends ChangeNotifier { - int _currentIndex = 0; - - int get currentIndex => _currentIndex; - - void setIndex(int index) { - _currentIndex = index; - notifyListeners(); - } -} \ No newline at end of file From f5e655fd35df6b230385e9110c2e819c1bce9f67 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Tue, 2 Sep 2025 23:25:45 +0300 Subject: [PATCH 17/17] Lab order API implemented --- lib/core/api/api_client.dart | 11 +- lib/extensions/widget_extensions.dart | 28 +- lib/features/lab/lab_repo.dart | 28 +- lib/features/lab/lab_view_model.dart | 8 +- .../patient_lab_orders_response_model.dart | 307 ++++++++++++------ lib/presentation/lab/lab_orders_page.dart | 24 +- 6 files changed, 277 insertions(+), 129 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index b3ea4870..d43ca936 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -189,6 +189,7 @@ class ApiClientImp implements ApiClient { : await Utils.isGoogleServicesAvailable() ? "2" : "3"); + body['TokenID'] = "@dm!n"; body.removeWhere((key, value) => value == null); log("body: ${json.encode(body)}"); log("uri: ${Uri.parse(url.trim())}"); @@ -223,18 +224,18 @@ class ApiClientImp implements ApiClient { if (parsed['isSMSSent'] == true) { onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus']); } else if (parsed['MessageStatus'] == 1) { - onSuccess(parsed, statusCode); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus']); } else if (parsed['Result'] == 'OK') { - onSuccess(parsed, statusCode); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus']); } else { onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, failureType: ServerFailure("Error While Fetching data")); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { - onSuccess(parsed, statusCode); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus']); } else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) { if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus']); } else { if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) { if (parsed['ErrorSearchMsg'] == null) { @@ -263,7 +264,7 @@ class ApiClientImp implements ApiClient { } } else { if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus']); } else { if (parsed['message'] != null) { onFailure( diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index e6913d2e..d1a3db4a 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -78,7 +78,14 @@ extension WidgetExtensions on Widget { } Widget objectContainerBorderView( - {String title = "", String note = "", bool disablePadding = false, double radius = 20, Color? color, Color borderColor = AppColors.buttonColor, bool disableWidth = false, bool isAlignment = false}) { + {String title = "", + String note = "", + bool disablePadding = false, + double radius = 20, + Color? color, + Color borderColor = AppColors.buttonColor, + bool disableWidth = false, + bool isAlignment = false}) { return Container( padding: disablePadding ? EdgeInsets.zero : const EdgeInsets.only(top: 15, bottom: 15, left: 14, right: 14), decoration: BoxDecoration( @@ -127,6 +134,7 @@ extension SmoothContainerExtension on ShapeBorder { bool isDisabled = false, Color? backgroundColor, BorderSide? side, + bool hasShadow = false, }) { final bgColor = backgroundColor ?? color; return ShapeDecoration( @@ -136,15 +144,19 @@ extension SmoothContainerExtension on ShapeBorder { smoothness: 1, side: side ?? BorderSide.none, ), + shadows: hasShadow + ? [ + BoxShadow( + color: const Color(0xff000000).withOpacity(.05), + blurRadius: 32, + offset: const Offset(0, 0), + ) + ] + : [], ); } } - - - - - //Height Spacers in percentages Widget heightSpacer02per() => SizedBox(height: 0.2.h); @@ -191,8 +203,6 @@ Widget widthSpacer4per() => SizedBox(height: 4.w); Widget widthSpacer5per() => SizedBox(height: 5.w); - - extension ChipTypeEnumExtension on ChipTypeEnum { Color get color { switch (this) { @@ -223,4 +233,4 @@ extension ChipTypeEnumExtension on ChipTypeEnum { return AppColors.warningLightColor; // Replace with your actual color } } -} \ No newline at end of file +} diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart index 061506a5..6d2711c9 100644 --- a/lib/features/lab/lab_repo.dart +++ b/lib/features/lab/lab_repo.dart @@ -7,7 +7,7 @@ import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_ import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class LabRepo { - Future>>> getPatientLabOrders({required num patientId}); + Future>>> getPatientLabOrders({required String patientId}); } class LabRepoImp implements LabRepo { @@ -17,8 +17,24 @@ class LabRepoImp implements LabRepo { LabRepoImp({required this.loggerService, required this.apiClient}); @override - Future>>> getPatientLabOrders({required num patientId}) async { - final mapDevice = {"PatientID": patientId}; + Future>>> getPatientLabOrders({required String patientId}) async { + final mapDevice = { + "isDentalAllowedBackend": false, + "VersionID": 50.0, + "Channel": 3, + "LanguageID": 2, + "IPAdress": "10.20.10.20", + "generalid": "Cs2020@2016\$2958", + "Latitude": 0.0, + "Longitude": 0.0, + "DeviceTypeID": 1, + "PatientType": 1, + "PatientTypeID": 1, + "TokenID": "@dm!n", + "PatientID": "2663907", + "PatientOutSA": "0", + "SessionID": "03478TYC02N80874CTYN04883475!?" + }; try { GenericApiModel>? apiResponse; @@ -36,7 +52,11 @@ class LabRepoImp implements LabRepo { throw Exception("lab list is empty"); } - final labOrders = list.map((item) => PatientLabOrdersResponseModel.fromJson(item as Map)).toList(); + // final labOrders = list.map((item) => PatientLabOrdersResponseModel.fromJson(item as Map)).toList(); + final labOrders = list + .map((item) => PatientLabOrdersResponseModel.fromJson(item as Map)) + .toList() + .cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 73f8b137..acd87eb8 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -16,14 +16,14 @@ class LabViewModel extends ChangeNotifier { initLabProvider() { patientLabOrders.clear(); - // isLabOrdersLoading = true; - // isLabResultsLoading = true; - // getPatientLabOrders(); + isLabOrdersLoading = true; + isLabResultsLoading = true; + getPatientLabOrders(); notifyListeners(); } Future getPatientLabOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await labRepo.getPatientLabOrders(patientId: 1231755); + final result = await labRepo.getPatientLabOrders(patientId: "1231755"); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), diff --git a/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart b/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart index 5cd8fbd7..8f6dc4ff 100644 --- a/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart +++ b/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart @@ -1,126 +1,245 @@ class PatientLabOrdersResponseModel { - String? description; - dynamic femaleInterpretativeData; + int? actualDoctorRate; + dynamic? admissionDate; + dynamic? admissionNumber; + dynamic? appointmentDate; + dynamic? appointmentNo; + dynamic? appointmentTime; + String? clinicDescription; + String? clinicDescriptionEnglish; + dynamic? clinicDescriptionN; + int? clinicID; + String? createdOn; + num? decimalDoctorRate; + int? doctorID; + String? doctorImageURL; + String? doctorName; + String? doctorNameEnglish; + dynamic? doctorNameN; + int? doctorRate; + num? doctorStarsRate; + String? doctorTitle; int? gender; - bool? isCertificateAllowed; - int? lineItemNo; - dynamic maleInterpretativeData; - dynamic notes; - int? orderLineItemNo; - int? orderNo; - String? packageID; - int? patientID; + String? genderDescription; + String? invoiceNo; + dynamic? invoiceNoVP; + String? invoiceType; + bool? isActiveDoctorProfile; + bool? isDoctorAllowVedioCall; + bool? isDrReviewReq; + bool? isExecludeDoctor; + bool? isInOutPatient; + String? isInOutPatientDescription; + String? isInOutPatientDescriptionN; + bool? isLiveCareAppointment; + bool? isRead; + bool? isSendEmail; + String? nationalityFlagURL; + int? noOfPatientsRate; + String? orderDate; + String? orderNo; + dynamic? orderProjectID; + String? patientID; String? projectID; - String? referanceRange; - String? resultValue; - int? resultValueBasedLineItemNo; - String? resultValueFlag; - String? sampleCollectedOn; - String? sampleReceivedOn; + String? projectName; + dynamic? projectNameN; + String? qR; String? setupID; - dynamic superVerifiedOn; - String? testCode; - String? uOM; - String? verifiedOn; - String? packageShortDescription; - String? testShortDescription; - dynamic verifiedOnDateTime; - num? percentage; - num? width; - num? resultTypeID; + List? speciality; + int? status; + String? statusDesc; + String? strOrderDate; + List? testDetails; PatientLabOrdersResponseModel( - {this.description, - this.femaleInterpretativeData, + {this.actualDoctorRate, + this.admissionDate, + this.admissionNumber, + this.appointmentDate, + this.appointmentNo, + this.appointmentTime, + this.clinicDescription, + this.clinicDescriptionEnglish, + this.clinicDescriptionN, + this.clinicID, + this.createdOn, + this.decimalDoctorRate, + this.doctorID, + this.doctorImageURL, + this.doctorName, + this.doctorNameEnglish, + this.doctorNameN, + this.doctorRate, + this.doctorStarsRate, + this.doctorTitle, this.gender, - this.isCertificateAllowed, - this.lineItemNo, - this.maleInterpretativeData, - this.notes, - this.orderLineItemNo, + this.genderDescription, + this.invoiceNo, + this.invoiceNoVP, + this.invoiceType, + this.isActiveDoctorProfile, + this.isDoctorAllowVedioCall, + this.isDrReviewReq, + this.isExecludeDoctor, + this.isInOutPatient, + this.isInOutPatientDescription, + this.isInOutPatientDescriptionN, + this.isLiveCareAppointment, + this.isRead, + this.isSendEmail, + this.nationalityFlagURL, + this.noOfPatientsRate, + this.orderDate, this.orderNo, - this.packageID, + this.orderProjectID, this.patientID, this.projectID, - this.referanceRange, - this.resultValue, - this.resultValueBasedLineItemNo, - this.resultValueFlag, - this.sampleCollectedOn, - this.sampleReceivedOn, + this.projectName, + this.projectNameN, + this.qR, this.setupID, - this.superVerifiedOn, - this.testCode, - this.uOM, - this.verifiedOn, - this.verifiedOnDateTime}); + this.speciality, + this.status, + this.statusDesc, + this.strOrderDate, + this.testDetails}); - PatientLabOrdersResponseModel.fromJson(Map json, {String? flag}) { - description = json['Description']; - femaleInterpretativeData = json['FemaleInterpretativeData']; + PatientLabOrdersResponseModel.fromJson(Map json) { + actualDoctorRate = json['ActualDoctorRate']; + admissionDate = json['AdmissionDate']; + admissionNumber = json['AdmissionNumber']; + appointmentDate = json['AppointmentDate']; + appointmentNo = json['AppointmentNo']; + appointmentTime = json['AppointmentTime']; + clinicDescription = json['ClinicDescription']; + clinicDescriptionEnglish = json['ClinicDescriptionEnglish']; + clinicDescriptionN = json['ClinicDescriptionN']; + clinicID = json['ClinicID']; + createdOn = json['CreatedOn']; + decimalDoctorRate = json['DecimalDoctorRate']; + doctorID = json['DoctorID']; + doctorImageURL = json['DoctorImageURL']; + doctorName = json['DoctorName']; + doctorNameEnglish = json['DoctorNameEnglish']; + doctorNameN = json['DoctorNameN']; + doctorRate = json['DoctorRate']; + doctorStarsRate = json['DoctorStarsRate']; + doctorTitle = json['DoctorTitle']; gender = json['Gender']; - isCertificateAllowed = json['IsCertificateAllowed']; - lineItemNo = json['LineItemNo']; - maleInterpretativeData = json['MaleInterpretativeData']; - notes = json['Notes']; - orderLineItemNo = json['OrderLineItemNo']; + genderDescription = json['GenderDescription']; + invoiceNo = json['InvoiceNo']; + invoiceNoVP = json['InvoiceNo_VP']; + invoiceType = json['InvoiceType']; + isActiveDoctorProfile = json['IsActiveDoctorProfile']; + isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall']; + isDrReviewReq = json['IsDrReviewReq']; + isExecludeDoctor = json['IsExecludeDoctor']; + isInOutPatient = json['IsInOutPatient']; + isInOutPatientDescription = json['IsInOutPatientDescription']; + isInOutPatientDescriptionN = json['IsInOutPatientDescriptionN']; + isLiveCareAppointment = json['IsLiveCareAppointment']; + isRead = json['IsRead']; + isSendEmail = json['IsSendEmail']; + nationalityFlagURL = json['NationalityFlagURL']; + noOfPatientsRate = json['NoOfPatientsRate']; + orderDate = json['OrderDate']; orderNo = json['OrderNo']; - packageID = json['PackageID']; + orderProjectID = json['OrderProjectID']; patientID = json['PatientID']; projectID = json['ProjectID']; - referanceRange = json['ReferanceRange']; - resultValue = json['ResultValue']; - resultValueBasedLineItemNo = json['ResultValueBasedLineItemNo']; - resultValueFlag = json['ResultValueFlag']; - sampleCollectedOn = json['SampleCollectedOn']; - sampleReceivedOn = json['SampleReceivedOn']; + projectName = json['ProjectName']; + projectNameN = json['ProjectNameN']; + qR = json['QR']; setupID = json['SetupID']; - superVerifiedOn = json['SuperVerifiedOn']; - testCode = json['TestCode']; - uOM = json['UOM']; - verifiedOn = json['VerifiedOn']; - verifiedOnDateTime = json['VerifiedOnDateTime']; - packageShortDescription = json['PackageShortDescription']; - testShortDescription = json['TestShortDescription']; - resultTypeID = json['ResultTypeID']; + speciality = json['Speciality'].cast(); + status = json['Status']; + statusDesc = json['StatusDesc']; + strOrderDate = json['StrOrderDate']; + if (json['TestDetails'] != dynamic) { + testDetails = []; + json['TestDetails'].forEach((v) { + testDetails!.add(new TestDetails.fromJson(v)); + }); + } } Map toJson() { final Map data = new Map(); - data['Description'] = this.description; - data['FemaleInterpretativeData'] = this.femaleInterpretativeData; + data['ActualDoctorRate'] = this.actualDoctorRate; + data['AdmissionDate'] = this.admissionDate; + data['AdmissionNumber'] = this.admissionNumber; + data['AppointmentDate'] = this.appointmentDate; + data['AppointmentNo'] = this.appointmentNo; + data['AppointmentTime'] = this.appointmentTime; + data['ClinicDescription'] = this.clinicDescription; + data['ClinicDescriptionEnglish'] = this.clinicDescriptionEnglish; + data['ClinicDescriptionN'] = this.clinicDescriptionN; + data['ClinicID'] = this.clinicID; + data['CreatedOn'] = this.createdOn; + data['DecimalDoctorRate'] = this.decimalDoctorRate; + data['DoctorID'] = this.doctorID; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorName'] = this.doctorName; + data['DoctorNameEnglish'] = this.doctorNameEnglish; + data['DoctorNameN'] = this.doctorNameN; + data['DoctorRate'] = this.doctorRate; + data['DoctorStarsRate'] = this.doctorStarsRate; + data['DoctorTitle'] = this.doctorTitle; data['Gender'] = this.gender; - data['IsCertificateAllowed'] = this.isCertificateAllowed; - data['LineItemNo'] = this.lineItemNo; - data['MaleInterpretativeData'] = this.maleInterpretativeData; - data['Notes'] = this.notes; - data['OrderLineItemNo'] = this.orderLineItemNo; + data['GenderDescription'] = this.genderDescription; + data['InvoiceNo'] = this.invoiceNo; + data['InvoiceNo_VP'] = this.invoiceNoVP; + data['InvoiceType'] = this.invoiceType; + data['IsActiveDoctorProfile'] = this.isActiveDoctorProfile; + data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall; + data['IsDrReviewReq'] = this.isDrReviewReq; + data['IsExecludeDoctor'] = this.isExecludeDoctor; + data['IsInOutPatient'] = this.isInOutPatient; + data['IsInOutPatientDescription'] = this.isInOutPatientDescription; + data['IsInOutPatientDescriptionN'] = this.isInOutPatientDescriptionN; + data['IsLiveCareAppointment'] = this.isLiveCareAppointment; + data['IsRead'] = this.isRead; + data['IsSendEmail'] = this.isSendEmail; + data['NationalityFlagURL'] = this.nationalityFlagURL; + data['NoOfPatientsRate'] = this.noOfPatientsRate; + data['OrderDate'] = this.orderDate; data['OrderNo'] = this.orderNo; - data['PackageID'] = this.packageID; + data['OrderProjectID'] = this.orderProjectID; data['PatientID'] = this.patientID; data['ProjectID'] = this.projectID; - data['ReferanceRange'] = this.referanceRange; - data['ResultValue'] = this.resultValue; - data['ResultValueBasedLineItemNo'] = this.resultValueBasedLineItemNo; - data['ResultValueFlag'] = this.resultValueFlag; - data['SampleCollectedOn'] = this.sampleCollectedOn; - data['SampleReceivedOn'] = this.sampleReceivedOn; + data['ProjectName'] = this.projectName; + data['ProjectNameN'] = this.projectNameN; + data['QR'] = this.qR; data['SetupID'] = this.setupID; - data['SuperVerifiedOn'] = this.superVerifiedOn; - data['TestCode'] = this.testCode; - data['UOM'] = this.uOM; - data['VerifiedOn'] = this.verifiedOn; - data['VerifiedOnDateTime'] = this.verifiedOnDateTime; - data['PackageShortDescription'] = this.packageShortDescription; - data['TestShortDescription'] = this.testShortDescription; - + data['Speciality'] = this.speciality; + data['Status'] = this.status; + data['StatusDesc'] = this.statusDesc; + data['StrOrderDate'] = this.strOrderDate; + if (this.testDetails != dynamic) { + data['TestDetails'] = this.testDetails!.map((v) => v.toJson()).toList(); + } return data; } +} + +class TestDetails { + String? description; + String? testCode; + String? testID; + + TestDetails({this.description, this.testCode, this.testID}); - bool shouldShowResultBarAndGraph() { - if (resultTypeID == null) return false; - if (resultTypeID == 6) return false; + TestDetails.fromJson(Map json) { + description = json['Description']; + testCode = json['TestCode']; + testID = json['TestID']; + } - return true; + Map toJson() { + final Map data = new Map(); + data['Description'] = this.description; + data['TestCode'] = this.testCode; + data['TestID'] = this.testID; + return data; } } diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index e0143ca7..7854ee8e 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -4,6 +4,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -74,7 +75,7 @@ class _LabOrdersPageState extends State { ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), - itemCount: model.isLabOrdersLoading ? 5 : labOrders.length, + itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.length, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading @@ -89,10 +90,7 @@ class _LabOrdersPageState extends State { duration: Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), child: InkWell( onTap: () { setState(() { @@ -112,9 +110,9 @@ class _LabOrdersPageState extends State { CustomButton( text: LocaleKeys.pending.tr(context: context), onPressed: () {}, - backgroundColor: getLabOrderStatusColor(44).withOpacity(0.15), - borderColor: getLabOrderStatusColor(44).withOpacity(0.01), - textColor: getLabOrderStatusColor(44), + backgroundColor: getLabOrderStatusColor(model.patientLabOrders[index].status!).withOpacity(0.15), + borderColor: getLabOrderStatusColor(model.patientLabOrders[index].status!).withOpacity(0.01), + textColor: getLabOrderStatusColor(model.patientLabOrders[index].status!), fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, @@ -128,20 +126,20 @@ class _LabOrdersPageState extends State { Row( children: [ Image.network( - "https://hmgwebservices.com/Images/MobileImages/SUWAIDI/152305.png", + model.patientLabOrders[index].doctorImageURL!, width: 24.h, height: 24.h, fit: BoxFit.fill, ).circle(100), SizedBox(width: 4.h), - Text(labOrders[index], style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + model.patientLabOrders[index].doctorName!.toText16(isBold: true) ], ), SizedBox(height: 8.h), Row( children: [ CustomButton( - text: "12th June, 2025", + text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(model.patientLabOrders[index].createdOn), false), onPressed: () {}, backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, @@ -154,7 +152,7 @@ class _LabOrdersPageState extends State { ), SizedBox(width: 8.h), CustomButton( - text: "Clinic: Cardiology", + text: model.patientLabOrders[index].clinicDescription!, onPressed: () {}, backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, @@ -174,7 +172,7 @@ class _LabOrdersPageState extends State { firstChild: SizedBox.shrink(), secondChild: Padding( padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Text('Details for ${labOrders[index]}'), + child: Text('Details for ${model.patientLabOrders[index].clinicDescription}'), ), crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, duration: Duration(milliseconds: 300),