From fd3e434b0caa2264ee336865ed729061a9bc291b Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Tue, 14 Apr 2026 11:50:34 +0300 Subject: [PATCH 1/4] no message --- ios/Podfile.lock | 626 ------------------ .../my_appointments/my_appointments_repo.dart | 2 +- .../widgets/appointment_doctor_card.dart | 3 +- 3 files changed, 3 insertions(+), 628 deletions(-) delete mode 100644 ios/Podfile.lock diff --git a/ios/Podfile.lock b/ios/Podfile.lock deleted file mode 100644 index deb3a53f..00000000 --- a/ios/Podfile.lock +++ /dev/null @@ -1,626 +0,0 @@ -PODS: - - amazon_payfort (1.1.4): - - Flutter - - PayFortSDK - - audio_session (0.0.1): - - Flutter - - barcode_scan2 (0.0.1): - - Flutter - - SwiftProtobuf (~> 1.33) - - connectivity_plus (0.0.1): - - Flutter - - CryptoSwift (1.8.4) - - device_calendar (0.0.1): - - Flutter - - device_calendar_plus_ios (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/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/Crashlytics (11.15.0): - - Firebase/CoreOnly - - FirebaseCrashlytics (~> 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 - - firebase_crashlytics (4.3.10): - - Firebase/Crashlytics (= 11.15.0) - - firebase_core - - Flutter - - firebase_messaging (15.2.10): - - 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) - - GoogleUtilities/Logger (~> 8.1) - - FirebaseCoreExtension (11.15.0): - - FirebaseCore (~> 11.15.0) - - FirebaseCoreInternal (11.15.0): - - "GoogleUtilities/NSData+zlib (~> 8.1)" - - FirebaseCrashlytics (11.15.0): - - FirebaseCore (~> 11.15.0) - - FirebaseInstallations (~> 11.0) - - FirebaseRemoteConfigInterop (~> 11.0) - - FirebaseSessions (~> 11.0) - - GoogleDataTransport (~> 10.0) - - GoogleUtilities/Environment (~> 8.1) - - nanopb (~> 3.30910.0) - - PromisesObjC (~> 2.4) - - 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) - - FirebaseRemoteConfigInterop (11.15.0) - - FirebaseSessions (11.15.0): - - FirebaseCore (~> 11.15.0) - - FirebaseCoreExtension (~> 11.15.0) - - FirebaseInstallations (~> 11.0) - - GoogleDataTransport (~> 10.0) - - GoogleUtilities/Environment (~> 8.1) - - GoogleUtilities/UserDefaults (~> 8.1) - - nanopb (~> 3.30910.0) - - PromisesSwift (~> 2.1) - - FLAnimatedImage (1.0.17) - - Flutter (1.0.0) - - flutter_callkit_incoming (0.0.1): - - CryptoSwift - - Flutter - - flutter_image_compress_common (1.0.0): - - Flutter - - Mantle - - SDWebImage - - SDWebImageWebPCoder - - 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_nfc_kit (3.6.0): - - 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 (< 11.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) - - 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/MethodSwizzler (8.1.0): - - GoogleUtilities/Logger - - 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.4): - - Flutter - - image_picker_ios (0.0.1): - - Flutter - - in_app_review (2.0.0): - - Flutter - - just_audio (0.0.1): - - Flutter - - FlutterMacOS - - libwebp (1.5.0): - - libwebp/demux (= 1.5.0) - - libwebp/mux (= 1.5.0) - - libwebp/sharpyuv (= 1.5.0) - - libwebp/webp (= 1.5.0) - - libwebp/demux (1.5.0): - - libwebp/webp - - libwebp/mux (1.5.0): - - libwebp/demux - - libwebp/sharpyuv (1.5.0) - - libwebp/webp (1.5.0): - - libwebp/sharpyuv - - local_auth_darwin (0.0.1): - - Flutter - - FlutterMacOS - - location (0.0.1): - - Flutter - - manage_calendar_events (0.0.1): - - Flutter - - Mantle (2.2.0): - - Mantle/extobjc (= 2.2.0) - - Mantle/extobjc (2.2.0) - - map_launcher (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.3): - - 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) - - network_info_plus (0.0.1): - - Flutter - - open_filex (0.0.2): - - Flutter - - OrderedSet (6.0.3) - - package_info_plus (0.4.5): - - Flutter - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - PayFortSDK (3.2.1) - - permission_handler_apple (9.3.0): - - Flutter - - Polyline (5.1.0) - - PromisesObjC (2.4.0) - - PromisesSwift (2.4.0): - - PromisesObjC (= 2.4.0) - - SDWebImage (5.21.5): - - SDWebImage/Core (= 5.21.5) - - SDWebImage/Core (5.21.5) - - SDWebImageWebPCoder (0.15.0): - - libwebp (~> 1.0) - - SDWebImage/Core (~> 5.17) - - 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 - - SwiftProtobuf (1.33.3) - - SwiftyGif (5.4.5) - - Turf (2.8.0) - - url_launcher_ios (0.0.1): - - Flutter - - video_player_avfoundation (0.0.1): - - Flutter - - FlutterMacOS - - wakelock_plus (0.0.1): - - Flutter - - webview_flutter_wkwebview (0.0.1): - - Flutter - - FlutterMacOS - - ZoomVideoSDK/CptShare (2.1.10) - - ZoomVideoSDK/zm_annoter_dynamic (2.1.10) - - ZoomVideoSDK/zoomcml (2.1.10) - - ZoomVideoSDK/ZoomVideoSDK (2.1.10) - -DEPENDENCIES: - - amazon_payfort (from `.symlinks/plugins/amazon_payfort/ios`) - - audio_session (from `.symlinks/plugins/audio_session/ios`) - - barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`) - - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - - device_calendar (from `.symlinks/plugins/device_calendar/ios`) - - device_calendar_plus_ios (from `.symlinks/plugins/device_calendar_plus_ios/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_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) - - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - - FLAnimatedImage - - Flutter (from `Flutter`) - - flutter_callkit_incoming (from `.symlinks/plugins/flutter_callkit_incoming/ios`) - - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/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_nfc_kit (from `.symlinks/plugins/flutter_nfc_kit/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`) - - in_app_review (from `.symlinks/plugins/in_app_review/ios`) - - just_audio (from `.symlinks/plugins/just_audio/darwin`) - - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - - location (from `.symlinks/plugins/location/ios`) - - manage_calendar_events (from `.symlinks/plugins/manage_calendar_events/ios`) - - map_launcher (from `.symlinks/plugins/map_launcher/ios`) - - MapboxMaps (= 10.19.0) - - MapboxNavigation (= 2.19.0) - - network_info_plus (from `.symlinks/plugins/network_info_plus/ios`) - - open_filex (from `.symlinks/plugins/open_filex/ios`) - - 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`) - - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) - -SPEC REPOS: - trunk: - - CryptoSwift - - DKImagePickerController - - DKPhotoGallery - - Firebase - - FirebaseAnalytics - - FirebaseCore - - FirebaseCoreExtension - - FirebaseCoreInternal - - FirebaseCrashlytics - - FirebaseInstallations - - FirebaseMessaging - - FirebaseRemoteConfigInterop - - FirebaseSessions - - FLAnimatedImage - - Google-Maps-iOS-Utils - - GoogleAdsOnDeviceConversion - - GoogleAppMeasurement - - GoogleDataTransport - - GoogleMaps - - GoogleUtilities - - libwebp - - Mantle - - MapboxCommon - - MapboxCoreMaps - - MapboxCoreNavigation - - MapboxDirections - - MapboxMaps - - MapboxMobileEvents - - MapboxNavigation - - MapboxNavigationNative - - MapboxSpeech - - nanopb - - OrderedSet - - PayFortSDK - - Polyline - - PromisesObjC - - PromisesSwift - - SDWebImage - - SDWebImageWebPCoder - - Solar-dev - - SwiftProtobuf - - SwiftyGif - - Turf - - ZoomVideoSDK - -EXTERNAL SOURCES: - amazon_payfort: - :path: ".symlinks/plugins/amazon_payfort/ios" - audio_session: - :path: ".symlinks/plugins/audio_session/ios" - barcode_scan2: - :path: ".symlinks/plugins/barcode_scan2/ios" - connectivity_plus: - :path: ".symlinks/plugins/connectivity_plus/ios" - device_calendar: - :path: ".symlinks/plugins/device_calendar/ios" - device_calendar_plus_ios: - :path: ".symlinks/plugins/device_calendar_plus_ios/ios" - device_info_plus: - :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_crashlytics: - :path: ".symlinks/plugins/firebase_crashlytics/ios" - firebase_messaging: - :path: ".symlinks/plugins/firebase_messaging/ios" - Flutter: - :path: Flutter - flutter_callkit_incoming: - :path: ".symlinks/plugins/flutter_callkit_incoming/ios" - flutter_image_compress_common: - :path: ".symlinks/plugins/flutter_image_compress_common/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_nfc_kit: - :path: ".symlinks/plugins/flutter_nfc_kit/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" - in_app_review: - :path: ".symlinks/plugins/in_app_review/ios" - just_audio: - :path: ".symlinks/plugins/just_audio/darwin" - local_auth_darwin: - :path: ".symlinks/plugins/local_auth_darwin/darwin" - location: - :path: ".symlinks/plugins/location/ios" - manage_calendar_events: - :path: ".symlinks/plugins/manage_calendar_events/ios" - map_launcher: - :path: ".symlinks/plugins/map_launcher/ios" - network_info_plus: - :path: ".symlinks/plugins/network_info_plus/ios" - open_filex: - :path: ".symlinks/plugins/open_filex/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" - video_player_avfoundation: - :path: ".symlinks/plugins/video_player_avfoundation/darwin" - wakelock_plus: - :path: ".symlinks/plugins/wakelock_plus/ios" - webview_flutter_wkwebview: - :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" - -SPEC CHECKSUMS: - amazon_payfort: 782d287860ac8962baf4e0a2a0fb2f81fdf27204 - audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe - barcode_scan2: 050b7b327e85954c8918715d42e83c5a35c3ed89 - connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d - CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90 - device_calendar: 9cb33f88a02e19652ec7b8b122ca778f751b1f7b - device_calendar_plus_ios: 34dca387f44fad29b840096a840f94edc55af858 - device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 - DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c - DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 - Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e - firebase_analytics: bf93e20703c95030404d6ddbb1adf05bf5c3885b - firebase_core: 99a37263b3c27536063a7b601d9e2a49400a433c - firebase_crashlytics: 986773c0c7ea4c3f5a4ead141b58522fcd9dfdb1 - firebase_messaging: bf6697c61f31c7cc0f654131212ff04c0115c2c7 - FirebaseAnalytics: 6433dfd311ba78084fc93bdfc145e8cb75740eae - FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e - FirebaseCoreExtension: edbd30474b5ccf04e5f001470bdf6ea616af2435 - FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 - FirebaseCrashlytics: e09d0bc19aa54a51e45b8039c836ef73f32c039a - FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843 - FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09 - FirebaseRemoteConfigInterop: 1c6135e8a094cc6368949f5faeeca7ee8948b8aa - FirebaseSessions: b9a92c1c51bbb81e78fc3142cda6d925d700f8e7 - FLAnimatedImage: bbf914596368867157cc71b38a8ec834b3eeb32b - Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_callkit_incoming: 417dd1b46541cdd5d855ad795ccbe97d1c18155e - flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e - flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 - flutter_ios_voip_kit_karmm: 7ea37381a8841c92d186edf1f4604df5cc437579 - flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f - flutter_nfc_kit: 3985c93f749b9cb4747479205c2f10bd2f877a11 - flutter_zoom_videosdk: 7ddc97d617b85aeace252165d2b5fc5638678c38 - fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f - geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd - Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321 - google_maps_flutter_ios: ffdd933832966673a1f57f5effd014dc8d4d43a2 - GoogleAdsOnDeviceConversion: 2be6297a4f048459e0ae17fad9bfd2844e10cf64 - GoogleAppMeasurement: 700dce7541804bec33db590a5c496b663fbe2539 - GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 - GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d - GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 - health: 9cd70a5c9d9ec559a12d639a1471091c0cbb6b48 - image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b - in_app_review: 436034b18594851a7328d7f1c2ed5ec235b79cfc - just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79 - libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 - local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 - location: d5cf8598915965547c3f36761ae9cc4f4e87d22e - manage_calendar_events: 9b2889799340398027b3e3f5c4891d41599ec257 - Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d - map_launcher: 8a4e484aaddff55680a7da9bc9e25f512067fb87 - MapboxCommon: 119f3759f7dc9457f0695848108ab323eb643cb4 - MapboxCoreMaps: ca17f67baced23f8c952166ac6314c35bad3f66c - MapboxCoreNavigation: 3be9990fae3ed732a101001746d0e3b4234ec023 - MapboxDirections: d9ad8452e8927d95ed21e35f733834dbca7e0eb1 - MapboxMaps: b7f29ec7c33f7dc6d2947c1148edce6db81db9a7 - MapboxMobileEvents: d044b9edbe0ec7df60f6c2c9634fe9a7f449266b - MapboxNavigation: da9cf3d773ed5b0fa0fb388fccdaa117ee681f31 - MapboxNavigationNative: 629e359f3d2590acd1ebbacaaf99e1a80ee57e42 - MapboxSpeech: cd25ef99c3a3d2e0da72620ff558276ea5991a77 - nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - network_info_plus: 6613d9d7cdeb0e6f366ed4dbe4b3c51c52d567a9 - open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4 - OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba - PayFortSDK: 233eabe9a45601fdbeac67fa6e5aae46ed8faf82 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 - Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1 - PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 - SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838 - SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377 - share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 - Solar-dev: 4612dc9878b9fed2667d23b327f1d4e54e16e8d0 - sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d - SwiftProtobuf: e1b437c8e31a4c5577b643249a0bb62ed4f02153 - SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e - url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa - video_player_avfoundation: 7993f492ae0bd77edaea24d9dc051d8bb2cd7c86 - wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e - webview_flutter_wkwebview: 29eb20d43355b48fe7d07113835b9128f84e3af4 - ZoomVideoSDK: 94e939820e57a075c5e712559f927017da0de06a - -PODFILE CHECKSUM: 8235407385ddd5904afc2563d65406117a51993e - -COCOAPODS: 1.16.2 diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index d4669767..541e8878 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -847,7 +847,7 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { // final queueList = list.map((item) => PatientQueueDetails.fromJson(item as Map)).toList().cast(); final queueList = PatientQueueDetails.fromJson(queueData); - queueList.patientQueueData!.isClinicConfigured = 1; + // queueList.patientQueueData!.isClinicConfigured = 1; apiResponse = GenericApiModel( messageStatus: messageStatus, diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 593746cb..0e4e2aeb 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -150,7 +150,8 @@ class AppointmentDoctorCard extends StatelessWidget { ], ), ), - patientAppointmentHistoryResponseModel.isLiveCareAppointment! || patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! ==false + + patientAppointmentHistoryResponseModel.isLiveCareAppointment! || patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! || patientAppointmentHistoryResponseModel.isActiveDoctor! == false ? SizedBox.shrink() : Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async { DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); From ce0eda744811ef0c96dafdd6fd87444d808c02bf Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Tue, 14 Apr 2026 11:58:47 +0300 Subject: [PATCH 2/4] no message --- pubspec.lock | 126 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 34 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 8395a120..f8ccd5ee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -237,35 +237,31 @@ packages: device_calendar_plus: dependency: "direct main" description: - name: device_calendar_plus - sha256: d11a70d98eb123e8eb09fdcfaf220ca4f1aa65a1512e12092f176f4b54983507 - url: "https://pub.dev" - source: hosted - version: "0.3.3" + path: "package/device_calendar_plus" + relative: true + source: path + version: "0.3.1" device_calendar_plus_android: dependency: transitive description: - name: device_calendar_plus_android - sha256: a341ef29fa0251251287d63c1d009dfd35c1459dc6a129fd5e03f5ac92d8d7ff - url: "https://pub.dev" - source: hosted - version: "0.3.3" + path: "package/device_calendar_plus/package/device_calendar_plus_android" + relative: true + source: path + version: "0.3.1" device_calendar_plus_ios: dependency: transitive description: - name: device_calendar_plus_ios - sha256: "3b2f84ce1ed002be8460e214a3229e66748bbaad4077603f2c734d67c42033ff" - url: "https://pub.dev" - source: hosted - version: "0.3.3" + path: "package/device_calendar_plus/package/device_calendar_plus_ios" + relative: true + source: path + version: "0.3.1" device_calendar_plus_platform_interface: dependency: transitive description: - name: device_calendar_plus_platform_interface - sha256: "0ce7511c094ca256831a48e16efe8f1e97e7bd00a5ff3936296ffd650a1d76b5" - url: "https://pub.dev" - source: hosted - version: "0.3.3" + path: "package/device_calendar_plus/package/device_calendar_plus_platform_interface" + relative: true + source: path + version: "0.3.1" device_info_plus: dependency: "direct main" description: @@ -519,6 +515,54 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.5" + flutter_image_compress: + dependency: "direct main" + description: + name: flutter_image_compress + sha256: "51d23be39efc2185e72e290042a0da41aed70b14ef97db362a6b5368d0523b27" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + flutter_image_compress_common: + dependency: transitive + description: + name: flutter_image_compress_common + sha256: c5c5d50c15e97dd7dc72ff96bd7077b9f791932f2076c5c5b6c43f2c88607bfb + url: "https://pub.dev" + source: hosted + version: "1.0.6" + flutter_image_compress_macos: + dependency: transitive + description: + name: flutter_image_compress_macos + sha256: "20019719b71b743aba0ef874ed29c50747461e5e8438980dfa5c2031898f7337" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + flutter_image_compress_ohos: + dependency: transitive + description: + name: flutter_image_compress_ohos + sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51 + url: "https://pub.dev" + source: hosted + version: "0.0.3" + flutter_image_compress_platform_interface: + dependency: transitive + description: + name: flutter_image_compress_platform_interface + sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + flutter_image_compress_web: + dependency: transitive + description: + name: flutter_image_compress_web + sha256: b9b141ac7c686a2ce7bb9a98176321e1182c9074650e47bb140741a44b6f5a96 + url: "https://pub.dev" + source: hosted + version: "0.1.5" flutter_inappwebview: dependency: "direct main" description: @@ -679,10 +723,9 @@ packages: flutter_swiper_view: dependency: "direct main" description: - name: flutter_swiper_view - sha256: "2a165b259e8a4c49d4da5626b967ed42a73dac2d075bd9e266ad8d23b9f01879" - url: "https://pub.dev" - source: hosted + path: "package/flutter_swiper_view" + relative: true + source: path version: "1.1.8" flutter_test: dependency: "direct dev" @@ -993,12 +1036,11 @@ packages: huawei_map: dependency: "direct main" description: - path: flutter-hms-map - ref: HEAD - resolved-ref: "9a16541e4016e3bf58a2571e6aa658a4751af399" - url: "https://github.com/fleoparra/hms-flutter-plugin.git" - source: git - version: "6.11.2+303" + name: huawei_map + sha256: "03bd37d565874281be10e7649d09990d88bc5bf724f45051f8cfaca7620fc273" + url: "https://pub.dev" + source: hosted + version: "6.12.0+301" image_picker: dependency: "direct main" description: @@ -1063,6 +1105,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2" + in_app_review: + dependency: "direct main" + description: + name: in_app_review + sha256: ab26ac54dbd802896af78c670b265eaeab7ecddd6af4d0751e9604b60574817f + url: "https://pub.dev" + source: hosted + version: "2.0.11" + in_app_review_platform_interface: + dependency: transitive + description: + name: in_app_review_platform_interface + sha256: fed2c755f2125caa9ae10495a3c163aa7fab5af3585a9c62ef4a6920c5b45f10 + url: "https://pub.dev" + source: hosted + version: "2.0.5" in_app_update: dependency: "direct main" description: @@ -1283,10 +1341,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -1816,10 +1874,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" time: dependency: transitive description: From 4cd51326277ba741ce7a1d7549c8dc1e844144c3 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Tue, 14 Apr 2026 12:25:53 +0300 Subject: [PATCH 3/4] radiology ai Analysis --- .../radiology_ai_analysis_response_model.dart | 29 +- .../radiology/radiology_view_model.dart | 12 +- .../radiology_ai_analysis_detailed_page.dart | 255 ++++++++++++++++++ .../radiology_ai_analysis_widget.dart | 63 ++++- .../radiology/radiology_result_page.dart | 39 +-- 5 files changed, 349 insertions(+), 49 deletions(-) create mode 100644 lib/presentation/radiology/radiology_ai_analysis_detailed_page.dart diff --git a/lib/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart b/lib/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart index e838f187..3f523428 100644 --- a/lib/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart +++ b/lib/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart @@ -2,9 +2,19 @@ import 'dart:convert'; class RadiologyAiAnalysisResponse { String? summary; + String? labResults; + String? aiPredictions; + String? riskFactors; + String? prevention; + int? languageId; RadiologyAiAnalysisResponse({ this.summary, + this.labResults, + this.aiPredictions, + this.riskFactors, + this.prevention, + this.languageId, }); factory RadiologyAiAnalysisResponse.fromRawJson(String str) => RadiologyAiAnalysisResponse.fromJson(json.decode(str)); @@ -12,11 +22,20 @@ class RadiologyAiAnalysisResponse { String toRawJson() => json.encode(toJson()); factory RadiologyAiAnalysisResponse.fromJson(Map json) => RadiologyAiAnalysisResponse( - summary: json["summary"], - ); + summary: json["summary"], + labResults: json["lab_results"], + aiPredictions: json["ai_predictions"], + riskFactors: json["riskFactors"], + prevention: json["prevention"], + languageId: json["language_id"], + ); Map toJson() => { - "summary": summary, - }; + "summary": summary, + "lab_results": labResults, + "ai_predictions": aiPredictions, + "riskFactors": riskFactors, + "prevention": prevention, + "language_id": languageId, + }; } - diff --git a/lib/features/radiology/radiology_view_model.dart b/lib/features/radiology/radiology_view_model.dart index 7386a5a0..cf795a1d 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -1,12 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/radiology/radiology_ai_analysis_detailed_page.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -216,12 +214,11 @@ class RadiologyViewModel extends ChangeNotifier { void setIsRadiologyAIAnalysisNeedsToBeShown(bool value) { isRadiologyAIAnalysisNeedsToBeShown = value; - notifyListeners(); } void closeRadiologyAIAnalysis() { radiologyAiAnalysisResponse = null; - notifyListeners(); + } Future getRadiologyAiAnalysis({required String reportData, required String loadingText, required int langId}) async { @@ -229,7 +226,6 @@ class RadiologyViewModel extends ChangeNotifier { loadingText: loadingText, showCloseButton: true, onCloseTap: () { - setIsRadiologyAIAnalysisNeedsToBeShown(false); }, ); @@ -248,6 +244,10 @@ class RadiologyViewModel extends ChangeNotifier { if (isRadiologyAIAnalysisNeedsToBeShown) { radiologyAiAnalysisResponse = apiResponse.data; notifyListeners(); + // Navigate to detailed page + navigationService.push( + CustomPageRoute(direction: AxisDirection.down, page: RadiologyAiAnalysisDetailedPage()), + ); } } }, diff --git a/lib/presentation/radiology/radiology_ai_analysis_detailed_page.dart b/lib/presentation/radiology/radiology_ai_analysis_detailed_page.dart new file mode 100644 index 00000000..78a3a097 --- /dev/null +++ b/lib/presentation/radiology/radiology_ai_analysis_detailed_page.dart @@ -0,0 +1,255 @@ +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/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/radiology/radiology_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/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/expandable_list_widget.dart'; +import 'package:provider/provider.dart'; + +class RadiologyAiAnalysisDetailedPage extends StatefulWidget { + const RadiologyAiAnalysisDetailedPage({super.key}); + + @override + State createState() => _RadiologyAiAnalysisDetailedPageState(); +} + +class _RadiologyAiAnalysisDetailedPageState extends State { + late RadiologyViewModel radiologyProvider; + + List _parseStringToList(String? text) { + if (text == null || text.isEmpty) return []; + // Split by newlines and filter out empty lines + return text.split('\n').where((line) => line.trim().isNotEmpty).map((line) => line.trim()).toList(); + } + + @override + Widget build(BuildContext context) { + radiologyProvider = Provider.of(context, listen: false); + return CollapsingListView( + isClose: true, + title: LocaleKeys.aiAnalysis.tr(context: context), + child: Consumer( + builder: (context, model, child) { + if (model.radiologyAiAnalysisResponse == null) { + return Utils.getNoDataWidget(context); + } + + final analysis = model.radiologyAiAnalysisResponse!; + List expandableItems = []; + + // Parse string fields into lists + final predictions = _parseStringToList(analysis.aiPredictions); + final preventions = _parseStringToList(analysis.prevention); + final riskFactorsText = _parseStringToList(analysis.riskFactors); + + // Summary Section + if (analysis.summary != null && analysis.summary!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor: Colors.transparent, + title: LocaleKeys.summary.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + initiallyExpanded: true, + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + side: BorderSide(width: 1, color: Color(0xFF0B85F7)), + ), + padding: EdgeInsets.all(16.r), + child: (analysis.summary ?? "").toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // Lab Results Section (Radiology Results) + if (analysis.labResults != null && analysis.labResults!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor: Colors.transparent, + title: LocaleKeys.labResults.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + side: BorderSide(width: 1, color: Color(0xFF0B85F7)), + ), + padding: EdgeInsets.all(16.r), + child: (analysis.labResults ?? "").toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // AI Predictions Section + if (predictions.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor: Colors.transparent, + title: LocaleKeys.prediction.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + side: BorderSide(width: 1, color: Color(0xFFFFAF15)), + ), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: predictions.map((prediction) => _buildBulletItem(prediction)).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // Risk Factors Section + if (riskFactorsText.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor: Colors.transparent, + title: LocaleKeys.riskFactors.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + ...riskFactorsText.asMap().entries.map((entry) { + final riskText = entry.value; + return Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Color(0xFFFBCB6E).withOpacity(0.10), + borderRadius: 12.r, + side: BorderSide(width: 1, color: Color(0xFFFBCB6E).withOpacity(0.10)), + ), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.guradIcon), + Padding( + padding: const EdgeInsets.all(8.0), + child: LocaleKeys.riskFactors.tr(context: context).toText16(weight: FontWeight.w700, color: AppColors.blackColor), + ), + ], + ), + SizedBox(height: 8.h), + riskText.toText14( + color: AppColors.textColorLight, + height: 1.5, + weight: FontWeight.w400, + ), + ], + ), + ), + SizedBox(height: 16.h), + ], + ); + }).toList(), + ], + ), + ); + } + + // Prevention Section + if (preventions.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor: Colors.transparent, + title: LocaleKeys.prevention.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + side: BorderSide(width: 1, color: Color(0xFF18C273)), + ), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: preventions.map((prevention) => _buildBulletItem(prevention)).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + return Column( + children: [ + if (expandableItems.isEmpty) + Utils.getNoDataWidget(context) + else + CustomExpandableList( + expansionMode: ExpansionMode.exactlyOne, + dividerColor: AppColors.dividerColor, + itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h), + items: expandableItems, + theme: ExpandableListTheme.custom( + defaultTrailingIcon: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_down, + height: 22.h, + width: 22.w, + iconColor: AppColors.textColor, + ), + ), + ).paddingSymmetrical(16.w, 0.0), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.info_outline, color: AppColors.greyTextColor, size: 20.r), + SizedBox(width: 8.w), + Expanded( + child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.thisAboveInfo.tr()}") + .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400), + ), + ], + ).paddingSymmetrical(16.w, 16.h) + ], + ); + }, + ), + ); + } + + Widget _buildBulletItem(String text) { + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: text.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + ], + ), + ); + } +} + diff --git a/lib/presentation/radiology/radiology_ai_analysis_widget.dart b/lib/presentation/radiology/radiology_ai_analysis_widget.dart index 97d35115..7eda4047 100644 --- a/lib/presentation/radiology/radiology_ai_analysis_widget.dart +++ b/lib/presentation/radiology/radiology_ai_analysis_widget.dart @@ -16,8 +16,18 @@ class RadiologyAiAnalysisWidget extends StatelessWidget { const RadiologyAiAnalysisWidget({super.key, required this.data}); + List _parseStringToList(String? text) { + if (text == null || text.isEmpty) return []; + // Split by newlines and filter out empty lines + return text.split('\n').where((line) => line.trim().isNotEmpty).map((line) => line.trim()).toList(); + } + @override Widget build(BuildContext context) { + // Parse the string fields into lists + final predictions = _parseStringToList(data.aiPredictions); + final preventions = _parseStringToList(data.prevention); + return Container( decoration: BoxDecoration(borderRadius: BorderRadius.circular(24.r), gradient: AppColors.aiLinearGradient), padding: EdgeInsets.all(1.5.r), // Border thickness @@ -43,7 +53,25 @@ class RadiologyAiAnalysisWidget extends StatelessWidget { ), SizedBox(height: 16.h), if (data.summary != null && data.summary!.isNotEmpty) ...[ + LocaleKeys.suggestion.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 8.h), data.summary!.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + SizedBox(height: 16.h), + Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 16.h), + ], + if (predictions.isNotEmpty) ...[ + LocaleKeys.prediction.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 12.h), + ...predictions.map((item) => _buildBulletItem(item)), + SizedBox(height: 16.h), + Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 16.h), + ], + if (preventions.isNotEmpty) ...[ + LocaleKeys.prevention.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 12.h), + ...preventions.map((item) => _buildBulletItem(item)), SizedBox(height: 20.h), ], Row( @@ -52,14 +80,45 @@ class RadiologyAiAnalysisWidget extends StatelessWidget { Icon(Icons.info_outline, color: AppColors.greyTextColor, size: 20.r), SizedBox(width: 8.w), Expanded( - child: ("${LocaleKeys.disclaimer.tr()}: ${LocaleKeys.thisAboveInfo.tr()}").toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400), + child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.thisAboveInfo.tr()}").toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400), ), ], ), - SizedBox(height: 8.h), + SizedBox(height: 16.h), + CustomButton( + height: 50.h, + text: LocaleKeys.close.tr(context: context), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + onPressed: () { + getIt.get().closeRadiologyAIAnalysis(); + }, + ), ], ), ), ); } + + Widget _buildBulletItem(String text) { + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 8.h, right: 12.w, left: 4.w), + child: Container( + width: 5.r, + height: 5.r, + decoration: BoxDecoration(color: AppColors.blackColor, shape: BoxShape.circle), + ), + ), + Expanded( + child: text.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + ], + ), + ); + } } diff --git a/lib/presentation/radiology/radiology_result_page.dart b/lib/presentation/radiology/radiology_result_page.dart index 445bbd45..5b7072e6 100644 --- a/lib/presentation/radiology/radiology_result_page.dart +++ b/lib/presentation/radiology/radiology_result_page.dart @@ -11,10 +11,8 @@ import 'package:hmg_patient_app_new/extensions/route_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/radiology/models/resp_models/patient_radiology_response_model.dart'; -import 'package:hmg_patient_app_new/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/radiology/radiology_ai_analysis_widget.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -37,8 +35,6 @@ class RadiologyResultPage extends StatefulWidget { class _RadiologyResultPageState extends State { late RadiologyViewModel radiologyViewModel; - final GlobalKey _aiAnalysisKey = GlobalKey(); - RadiologyAiAnalysisResponse? _previousAiData; @override void initState() { @@ -52,7 +48,7 @@ class _RadiologyResultPageState extends State { void dispose() { // Clear AI analysis data when leaving the screen radiologyViewModel.closeRadiologyAIAnalysis(); - radiologyViewModel.setIsRadiologyAIAnalysisNeedsToBeShown(true); + // radiologyViewModel.setIsRadiologyAIAnalysisNeedsToBeShown(true); super.dispose(); } @@ -63,29 +59,6 @@ class _RadiologyResultPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, radVM, child) { - // Detect changes in radiologyAiAnalysisResponse - WidgetsBinding.instance.addPostFrameCallback((_) { - if (radVM.radiologyAiAnalysisResponse != null && _previousAiData == null) { - // AI data just became available — scroll to it - final ctx = _aiAnalysisKey.currentContext; - if (ctx != null) { - Scrollable.ensureVisible( - ctx, - duration: const Duration(milliseconds: 400), - curve: Curves.easeOut, - ); - } - } else if (radVM.radiologyAiAnalysisResponse == null && _previousAiData != null) { - // AI data just became null — scroll to top - final scrollable = Scrollable.maybeOf(context); - scrollable?.position.animateTo( - 0, - duration: const Duration(milliseconds: 400), - curve: Curves.easeOut, - ); - } - _previousAiData = radVM.radiologyAiAnalysisResponse; - }); return Column( children: [ @@ -185,11 +158,6 @@ class _RadiologyResultPageState extends State { ).paddingSymmetrical(16.h, 0.h), ), SizedBox(height: 24.h), - if (radVM.radiologyAiAnalysisResponse != null) - RadiologyAiAnalysisWidget( - key: _aiAnalysisKey, - data: radVM.radiologyAiAnalysisResponse!, - ), ], ), ), @@ -229,8 +197,7 @@ class _RadiologyResultPageState extends State { iconSize: 20.h, ).paddingSymmetrical(24.h, 0.h) : SizedBox.shrink(), - Utils.havePrivilege(116) ? radVM.radiologyAiAnalysisResponse == null - ? Container( + Utils.havePrivilege(116) ? Container( height: 56.h, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12.r), @@ -274,7 +241,7 @@ class _RadiologyResultPageState extends State { context.pop(); }, ); - }) : SizedBox.shrink() : SizedBox(height: 24.h), + }) : SizedBox(height: 24.h), ], ), ), From db2ccd3be6a424df447626b6e3238f8832152119 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Tue, 14 Apr 2026 20:04:39 +0300 Subject: [PATCH 4/4] Date & Time New Widget For Whole App --- assets/langs/ar-SA.json | 1 + assets/langs/en-US.json | 1 + lib/core/dependencies.dart | 34 +- lib/core/utils/request_utils.dart | 2 +- .../authentication_view_model.dart | 19 +- lib/generated/codegen_loader.g.dart | 2 + lib/generated/locale_kesy.g.dart | 1 + lib/generated/locale_keys.g.dart | 1 + lib/main.dart | 3 + .../authentication/register_step2.dart | 87 +- .../date_range_calender.dart | 750 ++++++++++++++++-- .../models/calendar_design_type.dart | 5 + .../viewmodel/date_range_calendar_model.dart | 555 +++++++++++++ .../viewmodel/date_range_view_model.dart | 8 + lib/widgets/input_widget.dart | 55 +- pubspec.yaml | 3 +- 16 files changed, 1362 insertions(+), 165 deletions(-) create mode 100644 lib/widgets/date_range_selector/models/calendar_design_type.dart create mode 100644 lib/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 5a8df673..8cc2cba5 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -895,6 +895,7 @@ "monitor": "مراقبة", "noSpecialResult": "لا توجد نتائج خاصة", "setTheDateRange": "تعيين النطاق الزمني", + "selectDate": "اختر التاريخ", "historyFlowchart": "مخطط تدفق التاريخ", "to": "إلى", "startDate": "تاريخ البدء", diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 5a7f9e66..75a7bcab 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -878,6 +878,7 @@ "monitor": "Monitor", "noSpecialResult": "No Special Results", "setTheDateRange": "Set The Date Range", + "selectDate": "Select Date", "historyFlowchart": "History FlowChart", "to": "to", "startDate" : "Start Date", diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index f451f0d5..3b9eefc0 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -76,6 +76,7 @@ import 'package:hmg_patient_app_new/services/localauth_service.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/services/notification_service.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:local_auth/local_auth.dart'; import 'package:logger/web.dart'; @@ -133,7 +134,6 @@ class AppDependencies { navigationService: getIt(), )); - final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); getIt.registerLazySingleton(() => NotificationServiceImp( flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin, @@ -172,8 +172,12 @@ class AppDependencies { getIt.registerLazySingleton(() => MonthlyReportRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ActivePrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => TermsConditionsRepoImp(loggerService: getIt(), apiClient: getIt())); - getIt.registerFactory(() => TermsConditionsViewModel(termsConditionsRepo: getIt(), errorHandlerService: getIt(), - ),); + getIt.registerFactory( + () => TermsConditionsViewModel( + termsConditionsRepo: getIt(), + errorHandlerService: getIt(), + ), + ); getIt.registerLazySingleton(() => MonthlyReportsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => QrParkingRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => NotificationsRepoImp(loggerService: getIt(), apiClient: getIt())); @@ -233,10 +237,10 @@ class AppDependencies { ); getIt.registerLazySingleton(() => ProfileSettingsViewModel( - cacheService: getIt(), - profileSettingsRepo: getIt(), - errorHandlerService: getIt(), - )); + cacheService: getIt(), + profileSettingsRepo: getIt(), + errorHandlerService: getIt(), + )); getIt.registerLazySingleton(() => DateRangeSelectorRangeViewModel()); @@ -312,22 +316,12 @@ class AppDependencies { getIt.registerLazySingleton(() => NotificationsViewModel(notificationsRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton( - () => ActivePrescriptionsViewModel( - errorHandlerService: getIt(), - activePrescriptionsRepo: getIt() - ), - ); - getIt.registerFactory( - () => QrParkingViewModel( - qrParkingRepo: getIt(), - errorHandlerService: getIt(), - cacheService: getIt(), - ), - ); + getIt.registerLazySingleton(() => ActivePrescriptionsViewModel(errorHandlerService: getIt(), activePrescriptionsRepo: getIt())); + getIt.registerFactory(() => QrParkingViewModel(qrParkingRepo: getIt(), errorHandlerService: getIt(), cacheService: getIt())); getIt.registerLazySingleton(() => AskDoctorViewModel(askDoctorRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton(() => ServicesPriceListViewModel(servicesPriceListRepo: getIt(), errorHandlerService: getIt())); + getIt.registerLazySingleton(() => DateRangCalenderModel(appState: getIt(), navigationService: getIt(), dialogService: getIt())); } } diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index bb947398..4d4ae4b5 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -1,7 +1,6 @@ import 'dart:developer'; import 'package:easy_localization/easy_localization.dart'; -import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.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'; @@ -12,6 +11,7 @@ import 'package:hmg_patient_app_new/features/authentication/models/request_model import 'package:hmg_patient_app_new/features/authentication/models/request_models/send_activation_request_model.dart'; import 'package:hmg_patient_app_new/features/common/models/commong_authanticated_req_model.dart'; import 'package:hmg_patient_app_new/features/common/models/family_file_request.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart'; class RequestUtils { static dynamic getPatientAuthenticationRequest({ diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 573887e6..ca31b663 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -6,7 +6,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:get_it/get_it.dart'; -import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.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/cache_consts.dart'; @@ -42,6 +41,7 @@ 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/localauth_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart'; @@ -152,10 +152,12 @@ class AuthenticationViewModel extends ChangeNotifier { void onDobChange(String? date) { if (calenderType == CalenderEnum.hijri) { - var hijriDate = HijriGregConverter.gregorianToHijri(DateTime.parse(date!)); + // Use DateRangCalenderModel for accurate Umm al-Qura conversion + final calendarModel = DateRangCalenderModel(appState: _appState, navigationService: _errorHandlerService, dialogService: _dialogService); + var hijriDate = calendarModel.gregorianToHijri(DateTime.parse(date!)); DateTime hijriDateTimeForController = DateTime(hijriDate.year, hijriDate.month, hijriDate.day); dob = Utils.formatDateForApi(date); - dobController.text = Utils.formatHijriDateToDisplay(hijriDateTimeForController.toIso8601String()); // Or directly hijriDate.toString() if that's what your formatter expects + dobController.text = Utils.formatHijriDateToDisplay(hijriDateTimeForController.toIso8601String()); } else { dobController.text = Utils.formatDateToDisplay(date!); dob = Utils.formatDateForApi(date); @@ -897,7 +899,13 @@ class AuthenticationViewModel extends ChangeNotifier { } final resultEither = await _authenticationRepo.checkPatientForRegistration(commonAuthanticatedRequest: nRequest); - resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure, onUnHandledFailure: (failur) { + + LoaderBottomSheet.hideLoader(); + _dialogService.showExceptionBottomSheet(message: failur.message, onOkPressed: () { + _navigationService.pop(); + }); + }), (apiResponse) async { checkUserStatusForRegistration(response: apiResponse.data, request: request); }); } @@ -907,6 +915,7 @@ class AuthenticationViewModel extends ChangeNotifier { // LoadingUtils.showFullScreenLoader(loadingText: "Setting up your medical file.\nMay take a moment."); var request = RequestUtils.getUserSignupCompletionRequest(fullName: nameController.text, emailAddress: emailController.text, gender: genderType, maritalStatus: maritalStatus); + final resultEither = await _authenticationRepo.registerUser(registrationPayloadDataModelRequest: request); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.data is String) { @@ -926,6 +935,8 @@ class AuthenticationViewModel extends ChangeNotifier { _navigationService.pushAndRemoveUntil(CustomPageRoute(page: LandingNavigation()), (r) => false); _navigationService.push(CustomPageRoute(page: LoginScreen())); }); + } else { + print("========="); } } }); diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 486cb4da..03e2a341 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -907,6 +907,7 @@ class CodegenLoader extends AssetLoader{ "monitor": "مراقبة", "noSpecialResult": "لا توجد نتائج خاصة", "setTheDateRange": "تعيين النطاق الزمني", + "selectDate": "اختر التاريخ", "historyFlowchart": "مخطط تدفق التاريخ", "to": "إلى", "startDate": "تاريخ البدء", @@ -2627,6 +2628,7 @@ static const Map _en_US = { "monitor": "Monitor", "noSpecialResult": "No Special Results", "setTheDateRange": "Set The Date Range", + "selectDate": "Select Date", "historyFlowchart": "History FlowChart", "to": "to", "startDate": "Start Date", diff --git a/lib/generated/locale_kesy.g.dart b/lib/generated/locale_kesy.g.dart index 06f92632..3bf6d2d9 100644 --- a/lib/generated/locale_kesy.g.dart +++ b/lib/generated/locale_kesy.g.dart @@ -893,6 +893,7 @@ abstract class LocaleKeys { static const monitor = 'monitor'; static const noSpecialResult = 'noSpecialResult'; static const setTheDateRange = 'setTheDateRange'; + static const selectDate = 'selectDate'; static const historyFlowchart = 'historyFlowchart'; static const to = 'to'; static const startDate = 'startDate'; diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index f71483b9..058100e4 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -894,6 +894,7 @@ abstract class LocaleKeys { static const monitor = 'monitor'; static const noSpecialResult = 'noSpecialResult'; static const setTheDateRange = 'setTheDateRange'; + static const selectDate = 'selectDate'; static const historyFlowchart = 'historyFlowchart'; static const to = 'to'; static const startDate = 'startDate'; diff --git a/lib/main.dart b/lib/main.dart index 6f086c34..23f7c4d7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -53,6 +53,7 @@ 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:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; @@ -246,6 +247,8 @@ void main() async { ) , ChangeNotifierProvider( create: (_) => getIt.get(), + ), ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), // Wrap your app diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index f01dd036..4a628fd3 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -73,7 +73,7 @@ class _RegisterNew extends State { onLanguageChanged: (lang) {}, hideLogoAndLang: true, ), - bottomSheet: Container( + bottomSheet: Container( // height: 200.h, width: MediaQuery.of(context).size.width, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -81,51 +81,52 @@ class _RegisterNew extends State { customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), hasShadow: true, ), - child: Row( - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.cancel.tr(), - icon: AppAssets.cancel, - onPressed: () { - Navigator.of(context).pop(); - // authVM!.clearDefaultInputValues(); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - iconColor: AppColors.primaryRedColor, + child: Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + icon: AppAssets.cancel, + onPressed: () { + Navigator.of(context).pop(); + // authVM!.clearDefaultInputValues(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + iconColor: AppColors.primaryRedColor, + ), ), - ), - SizedBox(width: 16.w), - Expanded( - child: CustomButton( - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - text: LocaleKeys.confirm.tr(), - icon: AppAssets.confirm, - iconColor: AppColors.whiteColor, - onPressed: () { - if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) { - if (ValidationUtils.validateUaeRegistration( - name: authVM!.nameController.text, - gender: authVM!.genderType, - country: authVM!.pickedCountryByUAEUser, - maritalStatus: authVM!.maritalStatus, - onOkPress: () { - Navigator.of(context).pop(); - })) { + SizedBox(width: 16.w), + Expanded( + child: CustomButton( + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + text: LocaleKeys.confirm.tr(), + icon: AppAssets.confirm, + iconColor: AppColors.whiteColor, + onPressed: () { + if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) { + if (ValidationUtils.validateUaeRegistration( + name: authVM!.nameController.text, + gender: authVM!.genderType, + country: authVM!.pickedCountryByUAEUser, + maritalStatus: authVM!.maritalStatus, + onOkPress: () { + Navigator.of(context).pop(); + })) { + showModel(context: context); + } + } else { showModel(context: context); } - } else { - showModel(context: context); - } - }, - ), - ) - ], - ).paddingAll(16.h),), + }, + ), + ) + ], + ).paddingAll(16.h), + ), body: GestureDetector( onTap: () { FocusScope.of(context).unfocus(); diff --git a/lib/widgets/date_range_selector/date_range_calender.dart b/lib/widgets/date_range_selector/date_range_calender.dart index fd37d0cb..2bc5788b 100644 --- a/lib/widgets/date_range_selector/date_range_calender.dart +++ b/lib/widgets/date_range_selector/date_range_calender.dart @@ -4,6 +4,7 @@ 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/app_export.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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -12,31 +13,66 @@ 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/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/models/calendar_design_type.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_datepicker/datepicker.dart'; typedef OnRangeSelected = void Function(DateTime? start, DateTime? end); +typedef OnCalendarTypeChanged = void Function(bool isGregorian); class DateRangeSelector extends StatefulWidget { final OnRangeSelected onRangeSelected; + final CalendarDesignType designType; + final bool showCalendarToggle; + final OnCalendarTypeChanged? onCalendarTypeChanged; - const DateRangeSelector({super.key, required this.onRangeSelected}); + const DateRangeSelector({ + super.key, + required this.onRangeSelected, + this.designType = CalendarDesignType.defaultUI, + this.showCalendarToggle = true, + this.onCalendarTypeChanged, + }); @override State createState() => _DateRangeSelectorState(); } +enum PickerViewMode { + date, + month, + year, +} + class _DateRangeSelectorState extends State { late DateRangePickerController _calendarController; DateTime? start; DateTime? end; late DateRangeSelectorRangeViewModel model; + PickerViewMode _viewMode = PickerViewMode.date; + + // Cache for Hijri conversions to avoid repeated calculations + final Map _hijriCache = {}; + + // Track the current Hijri month/year being displayed (for header display accuracy) + HijriGregDate? _currentHijriDisplay; + @override void initState() { _calendarController = DateRangePickerController(); scheduleMicrotask(() { - _calendarController.selectedRange = PickerDateRange(model.fromDate,model.toDate); + if (widget.designType == CalendarDesignType.designV2) { + // For V2, select today's date by default + final today = DateTime.now(); + _calendarController.selectedDate = today; + model.updateSelectedDate(today); + } else { + // For default UI, use range selection + _calendarController.selectedRange = PickerDateRange(model.fromDate, model.toDate); + } }); super.initState(); } @@ -45,9 +81,12 @@ class _DateRangeSelectorState extends State { Widget build(BuildContext context) { model = Provider.of(context); + _calendarController.selectedRange = PickerDateRange(model.fromDate, model.toDate); - _calendarController.selectedRange = PickerDateRange(model.fromDate,model.toDate); + return widget.designType == CalendarDesignType.designV2 ? _buildDesignV2() : _buildDefaultUI(); + } + Widget _buildDefaultUI() { return Padding( padding: EdgeInsets.symmetric(horizontal: 0.h), child: Column( @@ -57,14 +96,8 @@ class _DateRangeSelectorState extends State { builder: (_, model, __) => selectionChip(model), ).paddingOnly(bottom: 16.h), Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - ), - padding: EdgeInsets.all( - 16.h - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false), + padding: EdgeInsets.all(16.h), child: Column( children: [ Row( @@ -72,13 +105,8 @@ class _DateRangeSelectorState extends State { fromDateComponent(), Text( LocaleKeys.to.tr(context: context), - style: TextStyle( - color: AppColors.calenderTextColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - letterSpacing: -.2 - ), - ).paddingSymmetrical(24.h,0.h), + style: TextStyle(color: AppColors.calenderTextColor, fontSize: 14.f, fontWeight: FontWeight.w600, letterSpacing: -.2), + ).paddingSymmetrical(24.h, 0.h), toDateComponent(), ], ), @@ -126,8 +154,7 @@ class _DateRangeSelectorState extends State { ), startRangeSelectionColor: AppColors.primaryRedColor, endRangeSelectionColor: AppColors.primaryRedColor, - rangeSelectionColor: - AppColors.primaryRedColor.withOpacity(0.1), + rangeSelectionColor: AppColors.primaryRedColor.withOpacity(0.1), rangeTextStyle: TextStyle( fontFamily: "Poppins", ), @@ -144,8 +171,7 @@ class _DateRangeSelectorState extends State { fontWeight: FontWeight.bold, ), ), - onSelectionChanged: - (DateRangePickerSelectionChangedArgs args) { + onSelectionChanged: (DateRangePickerSelectionChangedArgs args) { print("the value is ${args.value}"); if (args.value is PickerDateRange) { final PickerDateRange range = args.value; @@ -153,11 +179,10 @@ class _DateRangeSelectorState extends State { end = range.endDate; model.fromDate = start; model.toDate = end; - if(end == null) { - scheduleMicrotask((){ + if (end == null) { + scheduleMicrotask(() { model.resetCurrentlySelectedRange(); }); - } } }, @@ -169,7 +194,7 @@ class _DateRangeSelectorState extends State { Row( children: [ Consumer( - builder: (_, model, __) => Visibility( + builder: (_, model, __) => Visibility( visible: (model.fromDate != null || model.toDate != null), child: Expanded( child: Row( @@ -192,7 +217,9 @@ class _DateRangeSelectorState extends State { height: 56.h, ), ), - SizedBox(width: 16.h,) + SizedBox( + width: 16.h, + ) ], ), ), @@ -210,7 +237,6 @@ class _DateRangeSelectorState extends State { textColor: AppColors.textGreenColor, icon: AppAssets.reminder_bell, iconColor: AppColors.textGreenColor, - height: 56.h, ), ), @@ -221,11 +247,620 @@ class _DateRangeSelectorState extends State { ); } + Widget _buildDesignV2() { + return ChangeNotifierProvider( + create: (_) => DateRangCalenderModel(appState: getIt(), navigationService: getIt(), dialogService: getIt()), + child: Consumer( + builder: (context, calendarModel, child) { + // Check if current language is Arabic + final bool isArabic = context.locale.languageCode == 'ar'; + + return Padding( + padding: EdgeInsets.symmetric(horizontal: 0.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Conditionally show calendar toggle + if (widget.showCalendarToggle) + Selector( + selector: (_, model) => model.getSelectedTabIndex, + builder: (context, selectedIndex, child) => CustomTabBar( + activeBackgroundColor: AppColors.secondaryLightRedColor, + activeTextColor: AppColors.primaryRedColor, + initialIndex: selectedIndex, + tabs: [CustomTabBarModel(null, LocaleKeys.gregorianDate.tr()), CustomTabBarModel(null, LocaleKeys.hijriDate.tr())], + onTabChange: (index) { + final calendarModel = Provider.of(context, listen: false); + calendarModel.setTabIndex(index); + + // Notify parent widget about calendar type change (e.g., AuthenticationViewModel) + final isGregorian = index == 0; + widget.onCalendarTypeChanged?.call(isGregorian); + print('📅 Calendar type changed: ${isGregorian ? "Gregorian" : "Hijri"}'); + + // Reset view mode and Hijri display when switching calendar types + setState(() { + _viewMode = PickerViewMode.date; + _currentHijriDisplay = null; + }); + }, + ), + ), + if (widget.showCalendarToggle) + SizedBox( + height: 16.w, + ), + + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false), + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + // Show custom header for both calendar types + _buildCalendarHeader(isArabic, calendarModel), + // Wrap all views in SizedBox for consistent height + SizedBox( + height: 272.h, // Fixed height for calendar body only (no header) + child: Material( + color: AppColors.whiteColor, + // Rebuild when view mode or calendar type changes + key: ValueKey('${calendarModel.getSelectedTabIndex}-$_viewMode'), + child: _viewMode == PickerViewMode.month + ? _buildMonthPicker(isArabic, calendarModel) + : _viewMode == PickerViewMode.year + ? _buildYearPicker(isArabic, calendarModel) + : (calendarModel.isHijri ? _buildHijriCalendar(isArabic) : _buildGregorianCalendar(isArabic)), + ), + ), + ], + ), + ), + // Only Select button for V2 (no cancel button) + CustomButton( + text: LocaleKeys.select.tr(context: context), + onPressed: () { + // Don't reset calendar type - keep user's preference + Navigator.of(context).pop(); + // For single date selection, pass the same date as both start and end + widget.onRangeSelected(model.fromDate, model.fromDate); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: Colors.transparent, + textColor: AppColors.whiteColor, + height: 56.h, + ).paddingOnly(top: 24.h), + // Bottom image from ui2.png + SizedBox(height: 16.h), + ], + ), + ); + }, + ), + ); + } + + // Build Gregorian calendar (Design V2 - Single Date Selection) + Widget _buildGregorianCalendar(bool isArabic) { + return SfDateRangePicker( + controller: _calendarController, + selectionMode: DateRangePickerSelectionMode.single, + showNavigationArrow: false, + headerHeight: 0, + // Hide built-in header, use custom header + backgroundColor: AppColors.whiteColor, + monthViewSettings: DateRangePickerMonthViewSettings( + viewHeaderStyle: DateRangePickerViewHeaderStyle( + backgroundColor: AppColors.whiteColor, + textStyle: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w600, + letterSpacing: -0.46, + color: AppColors.textColor, + ), + ), + showTrailingAndLeadingDates: false, + dayFormat: "EEE", + ), + // Custom cell builder for square border selection + cellBuilder: (BuildContext context, DateRangePickerCellDetails cellDetails) { + if (cellDetails.date != DateTime(0)) { + final isToday = cellDetails.date.day == DateTime.now().day && cellDetails.date.month == DateTime.now().month && cellDetails.date.year == DateTime.now().year; + final isSelected = _calendarController.selectedDate != null && + cellDetails.date.day == _calendarController.selectedDate!.day && + cellDetails.date.month == _calendarController.selectedDate!.month && + cellDetails.date.year == _calendarController.selectedDate!.year; + + return Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.h), + border: isSelected ? Border.all(color: AppColors.primaryRedColor, width: 2) : null, + color: Colors.transparent, + ), + child: Text( + cellDetails.date.day.toString(), + style: TextStyle( + fontFamily: "Poppins", + fontSize: 12.f, + color: AppColors.textColor, + fontWeight: isToday ? FontWeight.bold : (isSelected ? FontWeight.w600 : FontWeight.normal), + ), + ), + ); + } + return Container(); + }, + selectionShape: DateRangePickerSelectionShape.rectangle, + selectionRadius: 8.h, + selectionColor: Colors.transparent, + selectionTextStyle: TextStyle( + fontFamily: "Poppins", + color: AppColors.textColor, + fontWeight: FontWeight.w600, + ), + todayHighlightColor: Colors.transparent, + monthCellStyle: DateRangePickerMonthCellStyle( + textStyle: TextStyle( + fontFamily: "Poppins", + fontSize: 12.f, + color: AppColors.textColor, + ), + todayTextStyle: TextStyle( + fontFamily: "Poppins", + color: AppColors.textColor, + fontWeight: FontWeight.bold, + ), + ), + onViewChanged: (DateRangePickerViewChangedArgs args) { + // Trigger rebuild when month changes + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() {}); + } + }); + }, + onSelectionChanged: (DateRangePickerSelectionChangedArgs args) { + if (args.value is DateTime) { + setState(() { + start = args.value; + end = args.value; + }); + model.updateSelectedDate(args.value); + } + }, + ); + } + + // Build Hijri calendar (Design V2 - Single Date Selection) + // Note: SfDateRangePicker doesn't have built-in Hijri support + // Using Gregorian calendar with Hijri date conversion in the model + Widget _buildHijriCalendar(bool isArabic) { + final calendarModel = Provider.of(context, listen: false); + + return SfDateRangePicker( + controller: _calendarController, + selectionMode: DateRangePickerSelectionMode.single, + showNavigationArrow: false, + headerHeight: 0, + backgroundColor: AppColors.whiteColor, + monthViewSettings: DateRangePickerMonthViewSettings( + viewHeaderStyle: DateRangePickerViewHeaderStyle( + backgroundColor: AppColors.whiteColor, + textStyle: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w600, + letterSpacing: -0.46, + color: AppColors.textColor, + ), + ), + showTrailingAndLeadingDates: false, + dayFormat: "EEE", + ), + cellBuilder: (BuildContext context, DateRangePickerCellDetails cellDetails) { + if (cellDetails.date != DateTime(0)) { + // Use cached Hijri conversion + final dateKey = '${cellDetails.date.year}-${cellDetails.date.month}-${cellDetails.date.day}'; + final hijriDate = _hijriCache.putIfAbsent(dateKey, () => calendarModel.gregorianToHijri(cellDetails.date)); + + final isToday = cellDetails.date.day == DateTime.now().day && cellDetails.date.month == DateTime.now().month && cellDetails.date.year == DateTime.now().year; + final isSelected = _calendarController.selectedDate != null && + cellDetails.date.day == _calendarController.selectedDate!.day && + cellDetails.date.month == _calendarController.selectedDate!.month && + cellDetails.date.year == _calendarController.selectedDate!.year; + + return Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.h), + border: isSelected ? Border.all(color: AppColors.primaryRedColor, width: 2) : null, + color: Colors.transparent, + ), + child: Text( + hijriDate.day.toString(), + style: TextStyle( + fontFamily: "Poppins", + fontSize: 12.f, + color: AppColors.textColor, + fontWeight: isToday ? FontWeight.bold : (isSelected ? FontWeight.w600 : FontWeight.normal), + ), + ), + ); + } + return Container(); + }, + selectionShape: DateRangePickerSelectionShape.rectangle, + selectionRadius: 8.h, + selectionColor: Colors.transparent, + selectionTextStyle: TextStyle( + fontFamily: "Poppins", + color: AppColors.textColor, + fontWeight: FontWeight.w600, + ), + todayHighlightColor: Colors.transparent, + monthCellStyle: DateRangePickerMonthCellStyle( + textStyle: TextStyle( + fontFamily: "Poppins", + fontSize: 12.f, + color: AppColors.textColor, + ), + todayTextStyle: TextStyle( + fontFamily: "Poppins", + color: AppColors.textColor, + fontWeight: FontWeight.bold, + ), + ), + onViewChanged: (DateRangePickerViewChangedArgs args) { + // Clear cache when month changes to avoid stale data + _hijriCache.clear(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() {}); + } + }); + }, + onSelectionChanged: (DateRangePickerSelectionChangedArgs args) { + if (args.value is DateTime) { + setState(() { + start = args.value; + end = args.value; + }); + model.updateSelectedDate(args.value); + } + }, + ); + } + + // Build unified calendar header for both Gregorian and Hijri + Widget _buildCalendarHeader(bool isArabic, DateRangCalenderModel calendarModel) { + final displayedDate = _calendarController.displayDate ?? DateTime.now(); + + String monthText; + String yearText; + + if (calendarModel.isHijri) { + // Use tracked Hijri display date if available, otherwise convert from displayed Gregorian date + final hijriDate = _currentHijriDisplay ?? calendarModel.gregorianToHijri(displayedDate); + final monthName = calendarModel.getMonthNameLocalized(hijriDate.month, isArabic); + monthText = monthName; + yearText = hijriDate.year.toString(); + } else { + final monthNames = isArabic + ? ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'] + : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + monthText = monthNames[displayedDate.month - 1]; + yearText = displayedDate.year.toString(); + } + + return Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: SizedBox( + height: 40.h, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + // Month text - clickable + GestureDetector( + onTap: () { + setState(() { + _viewMode = _viewMode == PickerViewMode.month ? PickerViewMode.date : PickerViewMode.month; + }); + }, + child: Text( + monthText, + style: TextStyle( + fontSize: 18.f, + fontWeight: FontWeight.w600, + letterSpacing: -0.46, + color: AppColors.primaryRedColor, + fontFamily: "Poppins", + ), + textAlign: isArabic ? TextAlign.right : TextAlign.left, + ), + ), + SizedBox(width: 8.h), + // Year text - clickable + GestureDetector( + onTap: () { + setState(() { + _viewMode = _viewMode == PickerViewMode.year ? PickerViewMode.date : PickerViewMode.year; + }); + }, + child: Text( + yearText, + style: TextStyle( + fontSize: 18.f, + fontWeight: FontWeight.w600, + letterSpacing: -0.46, + color: AppColors.primaryRedColor, + fontFamily: "Poppins", + ), + textAlign: isArabic ? TextAlign.right : TextAlign.left, + ), + ), + ], + ), + Row( + children: [ + IconButton( + icon: Icon(Icons.chevron_left, color: AppColors.primaryRedColor), + onPressed: () { + if (_viewMode == PickerViewMode.date) { + _calendarController.backward!(); + // Update tracked Hijri display when navigating months + if (calendarModel.isHijri) { + setState(() { + _currentHijriDisplay = null; // Will recalculate on next build + }); + } + } else if (_viewMode == PickerViewMode.year) { + // Navigate years backward by 12 + final currentDate = _calendarController.displayDate ?? DateTime.now(); + if (calendarModel.isHijri) { + // For Hijri, convert back and forth + final hijriDate = _currentHijriDisplay ?? calendarModel.gregorianToHijri(currentDate); + try { + final newHijriDate = HijriGregDate(day: 1, month: hijriDate.month, year: hijriDate.year - 12); + final newGregorianDate = calendarModel.hijriToGregorian(newHijriDate); + _calendarController.displayDate = DateTime(newGregorianDate.year, newGregorianDate.month, 1); + _currentHijriDisplay = newHijriDate; + } catch (e) { + // Fallback + _calendarController.displayDate = DateTime(currentDate.year - 10, currentDate.month, 1); + } + } else { + _calendarController.displayDate = DateTime(currentDate.year - 12, currentDate.month, 1); + } + setState(() {}); + } + }, + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + ), + SizedBox(width: 8.h), + IconButton( + icon: Icon(Icons.chevron_right, color: AppColors.primaryRedColor), + onPressed: () { + if (_viewMode == PickerViewMode.date) { + _calendarController.forward!(); + // Update tracked Hijri display when navigating months + if (calendarModel.isHijri) { + setState(() { + _currentHijriDisplay = null; // Will recalculate on next build + }); + } + } else if (_viewMode == PickerViewMode.year) { + // Navigate years forward by 12 + final currentDate = _calendarController.displayDate ?? DateTime.now(); + if (calendarModel.isHijri) { + // For Hijri, convert back and forth + final hijriDate = _currentHijriDisplay ?? calendarModel.gregorianToHijri(currentDate); + try { + final newHijriDate = HijriGregDate(day: 1, month: hijriDate.month, year: hijriDate.year + 12); + final newGregorianDate = calendarModel.hijriToGregorian(newHijriDate); + _calendarController.displayDate = DateTime(newGregorianDate.year, newGregorianDate.month, 1); + _currentHijriDisplay = newHijriDate; + } catch (e) { + // Fallback + _calendarController.displayDate = DateTime(currentDate.year + 10, currentDate.month, 1); + } + } else { + _calendarController.displayDate = DateTime(currentDate.year + 12, currentDate.month, 1); + } + setState(() {}); + } + }, + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + ), + ], + ), + ], + ), + ), + ); + } + + // Build month picker grid + Widget _buildMonthPicker(bool isArabic, DateRangCalenderModel calendarModel) { + final displayedDate = _calendarController.displayDate ?? DateTime.now(); + final currentDate = DateTime.now(); + + int currentMonth; + int currentYear; + int displayedYear; + + if (calendarModel.isHijri) { + // For Hijri calendar + final hijriCurrent = calendarModel.gregorianToHijri(currentDate); + final hijriDisplayed = calendarModel.gregorianToHijri(displayedDate); + currentMonth = hijriCurrent.month; + currentYear = hijriCurrent.year; + displayedYear = hijriDisplayed.year; + } else { + // For Gregorian calendar + currentMonth = currentDate.month; + currentYear = currentDate.year; + displayedYear = displayedDate.year; + } + + List monthNames; + if (calendarModel.isHijri) { + monthNames = isArabic ? DateRangCalenderModel.hijriMonthNamesArabic : DateRangCalenderModel.hijriMonthNamesEnglish; + } else { + monthNames = isArabic ? DateRangCalenderModel.gregorianMonthNamesArabic : DateRangCalenderModel.gregorianMonthNamesEnglish; + } + + return GridView.builder( + padding: EdgeInsets.all(8.h), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 2.5, crossAxisSpacing: 8.h, mainAxisSpacing: 8.h), + itemCount: 12, + itemBuilder: (context, index) { + final monthIndex = index + 1; + final isCurrentMonth = currentMonth == monthIndex && currentYear == displayedYear; + + return GestureDetector( + onTap: () { + // Update the displayed month based on calendar type + if (calendarModel.isHijri) { + // Convert Hijri month to Gregorian date + try { + final hijriDate = HijriGregDate(day: 1, month: monthIndex, year: displayedYear); + final gregorianDate = calendarModel.hijriToGregorian(hijriDate); + _calendarController.displayDate = DateTime(gregorianDate.year, gregorianDate.month, 1); + // Track the Hijri month/year for accurate header display + _currentHijriDisplay = hijriDate; + // Clear cache since we changed the month + _hijriCache.clear(); + } catch (e) { + // Fallback if conversion fails + _calendarController.displayDate = DateTime(displayedDate.year, monthIndex, 1); + } + } else { + // Gregorian month - direct update + _calendarController.displayDate = DateTime(displayedYear, monthIndex, 1); + } + + // Switch back to date view + setState(() { + _viewMode = PickerViewMode.date; + }); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.h), + border: isCurrentMonth ? Border.all(color: AppColors.primaryRedColor, width: 2) : null, + color: Colors.transparent, + ), + child: Center( + child: Text( + monthNames[index], + style: TextStyle( + fontFamily: "Poppins", + fontSize: 14.f, + color: AppColors.textColor, + fontWeight: isCurrentMonth ? FontWeight.w600 : FontWeight.normal, + ), + textAlign: TextAlign.center, + ), + ), + ), + ); + }, + ); + } + + // Build year picker grid + Widget _buildYearPicker(bool isArabic, DateRangCalenderModel calendarModel) { + final displayedDate = _calendarController.displayDate ?? DateTime.now(); + final currentYear = DateTime.now().year; + + int displayedYear; + int currentDisplayYear; + int startYear; + + if (calendarModel.isHijri) { + // For Hijri calendar + final hijriDisplayed = calendarModel.gregorianToHijri(displayedDate); + final hijriCurrent = calendarModel.gregorianToHijri(DateTime.now()); + displayedYear = hijriDisplayed.year; + currentDisplayYear = hijriCurrent.year; + startYear = (displayedYear ~/ 12) * 12; + } else { + // For Gregorian calendar + displayedYear = displayedDate.year; + currentDisplayYear = currentYear; + startYear = (displayedYear ~/ 12) * 12; + } + + return GridView.builder( + padding: EdgeInsets.all(8.h), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 2.5, + crossAxisSpacing: 8.h, + mainAxisSpacing: 8.h, + ), + itemCount: 12, + itemBuilder: (context, index) { + final year = startYear + index; + final isCurrentYear = currentDisplayYear == year; + + return GestureDetector( + onTap: () { + // Update the displayed year based on calendar type + if (calendarModel.isHijri) { + // Convert Hijri year to Gregorian date + try { + final currentHijriMonth = _currentHijriDisplay?.month ?? calendarModel.gregorianToHijri(displayedDate).month; + final hijriDate = HijriGregDate(day: 1, month: currentHijriMonth, year: year); + final gregorianDate = calendarModel.hijriToGregorian(hijriDate); + _calendarController.displayDate = DateTime(gregorianDate.year, gregorianDate.month, 1); + // Track the Hijri month/year for accurate header display + _currentHijriDisplay = hijriDate; + } catch (e) { + // Fallback if conversion fails + _calendarController.displayDate = DateTime(displayedDate.year, displayedDate.month, 1); + } + } else { + // Gregorian year - direct update + _calendarController.displayDate = DateTime(year, displayedDate.month, 1); + } + + // Switch back to date view + setState(() { + _viewMode = PickerViewMode.date; + }); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.h), + border: isCurrentYear ? Border.all(color: AppColors.primaryRedColor, width: 2) : null, + color: Colors.transparent, + ), + child: Center( + child: Text( + year.toString(), + style: TextStyle( + fontFamily: "Poppins", + fontSize: 14.f, + color: AppColors.textColor, + fontWeight: isCurrentYear ? FontWeight.w600 : FontWeight.normal, + ), + textAlign: TextAlign.center, + ), + ), + ), + ); + }, + ); + } + fromDateComponent() { return Consumer( builder: (_, model, __) { - return displayDate(LocaleKeys.startDate.tr(context: context), - model.getDateString(model.fromDate), model.fromDate == null); + return displayDate(LocaleKeys.startDate.tr(context: context), model.getDateString(model.fromDate), model.fromDate == null); }, ); } @@ -233,21 +868,16 @@ class _DateRangeSelectorState extends State { toDateComponent() { return Consumer( builder: (_, model, __) { - return displayDate(LocaleKeys.endDate.tr(context: context), - model.getDateString(model.toDate), model.toDate == null); + return displayDate(LocaleKeys.endDate.tr(context: context), model.getDateString(model.toDate), model.toDate == null); }, ); } displayDate(String label, String? date, bool isNotSelected) => Expanded( - child: Row( + child: Row( spacing: 12.h, children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.rangeCalendar, - iconColor: isNotSelected ? AppColors.borderOnlyColor: AppColors.blackColor , - height: 24, - width: 24), + Utils.buildSvgWithAssets(icon: AppAssets.rangeCalendar, iconColor: isNotSelected ? AppColors.borderOnlyColor : AppColors.blackColor, height: 24, width: 24), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -271,7 +901,7 @@ class _DateRangeSelectorState extends State { ) ], ), - ); + ); selectionChip(DateRangeSelectorRangeViewModel model) { return Row( @@ -279,59 +909,46 @@ class _DateRangeSelectorState extends State { children: [ AppCustomChipWidget( labelText: LocaleKeys.thisWeek.tr(context: context), - backgroundColor: model.currentlySelectedRange == Range.WEEKLY - ? AppColors.primaryRedColor.withOpacity(0.1) - : AppColors.whiteColor, + backgroundColor: model.currentlySelectedRange == Range.WEEKLY ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, shape: RoundedRectangleBorder( side: BorderSide( - color: model.currentlySelectedRange == Range.WEEKLY - ? AppColors.primaryRedBorderColor - : AppColors.chipBorderColorOpacity20, + color: model.currentlySelectedRange == Range.WEEKLY ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20, width: 1, ), borderRadius: BorderRadius.circular(10)), - ).onPress((){ + ).onPress(() { _calendarController.selectedRange = null; model.currentlySelectedRange = Range.WEEKLY; model.calculateDatesFromRange(); }), AppCustomChipWidget( labelText: LocaleKeys.lastMonth.tr(context: context), - backgroundColor: model.currentlySelectedRange == Range.LAST_MONTH - ? AppColors.primaryRedColor.withOpacity(0.1) - : AppColors.whiteColor, + backgroundColor: model.currentlySelectedRange == Range.LAST_MONTH ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, shape: RoundedRectangleBorder( side: BorderSide( - color: model.currentlySelectedRange == Range.LAST_MONTH - ? AppColors.primaryRedBorderColor - : AppColors.chipBorderColorOpacity20, + color: model.currentlySelectedRange == Range.LAST_MONTH ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20, width: 1, ), borderRadius: BorderRadius.circular(10)), - ).onPress((){ + ).onPress(() { _calendarController.selectedRange = null; model.currentlySelectedRange = Range.LAST_MONTH; model.calculateDatesFromRange(); }), AppCustomChipWidget( labelText: LocaleKeys.lastSixMonths.tr(context: context), - backgroundColor: model.currentlySelectedRange == Range.LAST_6MONTH - ? AppColors.primaryRedColor.withOpacity(0.1) - : AppColors.whiteColor, + backgroundColor: model.currentlySelectedRange == Range.LAST_6MONTH ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, shape: RoundedRectangleBorder( side: BorderSide( - color: model.currentlySelectedRange == Range.LAST_6MONTH - ? AppColors.primaryRedBorderColor - : AppColors.chipBorderColorOpacity20, + color: model.currentlySelectedRange == Range.LAST_6MONTH ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20, width: 1, ), borderRadius: BorderRadius.circular(10)), isEnglishOnly: true, - ).onPress((){ + ).onPress(() { _calendarController.selectedRange = null; model.currentlySelectedRange = Range.LAST_6MONTH; model.calculateDatesFromRange(); - }), AppCustomChipWidget( richText: Row( @@ -341,23 +958,18 @@ class _DateRangeSelectorState extends State { ], ), labelText: "${LocaleKeys.year.tr(context: context)} ${model.getCurrentYear}", - backgroundColor: model.currentlySelectedRange == Range.THIS_YEAR - ? AppColors.primaryRedColor.withOpacity(0.1) - : AppColors.whiteColor, + backgroundColor: model.currentlySelectedRange == Range.THIS_YEAR ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, shape: RoundedRectangleBorder( side: BorderSide( - color: model.currentlySelectedRange == Range.THIS_YEAR - ? AppColors.primaryRedBorderColor - : AppColors.chipBorderColorOpacity20, + color: model.currentlySelectedRange == Range.THIS_YEAR ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20, width: 1, ), borderRadius: BorderRadius.circular(10)), isEnglishOnly: true, - ).onPress((){ + ).onPress(() { _calendarController.selectedRange = null; model.currentlySelectedRange = Range.THIS_YEAR; model.calculateDatesFromRange(); - }), ], ); diff --git a/lib/widgets/date_range_selector/models/calendar_design_type.dart b/lib/widgets/date_range_selector/models/calendar_design_type.dart new file mode 100644 index 00000000..5cbc8129 --- /dev/null +++ b/lib/widgets/date_range_selector/models/calendar_design_type.dart @@ -0,0 +1,5 @@ +enum CalendarDesignType { + defaultUI, + designV2, +} + diff --git a/lib/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart b/lib/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart new file mode 100644 index 00000000..54c23a72 --- /dev/null +++ b/lib/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart @@ -0,0 +1,555 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +// ==================== Hijri Date Classes ==================== + +/// Represents a Hijri date with day, month, and year components. +class HijriGregDate { + final int day; + final int month; + final int year; + + HijriGregDate({required this.day, required this.month, required this.year}) + : assert(day >= 1 && day <= 30, 'Day must be between 1 and 30'), + assert(month >= 1 && month <= 12, 'Month must be between 1 and 12'), + assert(year > 0, 'Year must be positive'); + + @override + String toString() => '$day/$month/$year H'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is HijriGregDate && other.day == day && other.month == month && other.year == year; + } + + @override + int get hashCode => day.hashCode ^ month.hashCode ^ year.hashCode; +} + +/// Utility class for converting between Hijri and Gregorian calendars. +class HijriGregConverter { + static const int _hijriEpoch = 1948439; + + static HijriGregDate gregorianToHijri(DateTime gregorianDate) { + int julianDay = _gregorianToJulian(gregorianDate); + return _julianToHijri(julianDay); + } + + static DateTime hijriToGregorian(HijriGregDate hijriDate) { + int julianDay = _hijriToJulian(hijriDate); + return _julianToGregorian(julianDay); + } + + static int _gregorianToJulian(DateTime date) { + int year = date.year; + int month = date.month; + int day = date.day; + + if (month <= 2) { + year--; + month += 12; + } + + int a = year ~/ 100; + int b = 2 - a + (a ~/ 4); + + return (365.25 * (year + 4716)).floor() + (30.6001 * (month + 1)).floor() + day + b - 1524; + } + + static DateTime _julianToGregorian(int julianDay) { + int a = julianDay + 32044; + int b = (4 * a + 3) ~/ 146097; + int c = a - (146097 * b) ~/ 4; + int d = (4 * c + 3) ~/ 1461; + int e = c - (1461 * d) ~/ 4; + int m = (5 * e + 2) ~/ 153; + + int day = e - (153 * m + 2) ~/ 5 + 1; + int month = m + 3 - 12 * (m ~/ 10); + int year = 100 * b + d - 4800 + m ~/ 10; + + return DateTime(year, month, day); + } + + static HijriGregDate _julianToHijri(int julianDay) { + int daysSinceEpoch = julianDay - _hijriEpoch; + int hijriYear = (daysSinceEpoch * 33 ~/ 10631) + 1; + + int yearStartJulian = _hijriYearStartJulian(hijriYear); + while (yearStartJulian > julianDay) { + hijriYear--; + yearStartJulian = _hijriYearStartJulian(hijriYear); + } + while (yearStartJulian + _hijriYearLength(hijriYear) <= julianDay) { + hijriYear++; + yearStartJulian = _hijriYearStartJulian(hijriYear); + } + + int dayOfYear = julianDay - yearStartJulian + 1; + int month = 1; + int dayInMonth = dayOfYear; + + while (month <= 12) { + int monthLength = _hijriMonthLength(hijriYear, month); + if (dayInMonth <= monthLength) { + break; + } + dayInMonth -= monthLength; + month++; + } + + if (dayInMonth < 1) dayInMonth = 1; + if (dayInMonth > 30) dayInMonth = 30; + + return HijriGregDate(day: dayInMonth, month: month, year: hijriYear); + } + + static int _hijriToJulian(HijriGregDate hijriDate) { + int yearStart = _hijriYearStartJulian(hijriDate.year); + int dayOfYear = 0; + + for (int i = 1; i < hijriDate.month; i++) { + dayOfYear += _hijriMonthLength(hijriDate.year, i); + } + + dayOfYear += hijriDate.day - 1; + return yearStart + dayOfYear; + } + + static int _hijriYearStartJulian(int hijriYear) { + if (hijriYear <= 1) return _hijriEpoch; + + int totalDays = 0; + for (int year = 1; year < hijriYear; year++) { + totalDays += _hijriYearLength(year); + } + + return _hijriEpoch + totalDays; + } + + static int _hijriYearLength(int year) { + return _isHijriLeapYear(year) ? 355 : 354; + } + + static int _hijriMonthLength(int year, int month) { + if (month <= 0 || month > 12) return 30; + + const List monthLengths = [30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29]; + int length = monthLengths[month - 1]; + + if (month == 12 && _isHijriLeapYear(year)) { + length = 30; + } + + return length; + } + + static bool _isHijriLeapYear(int year) { + if (year <= 0) return false; + const leapYears = [2, 5, 7, 10, 13, 16, 18, 21, 24, 26, 29]; + int yearInCycle = ((year - 1) % 30) + 1; + return leapYears.contains(yearInCycle); + } + + static int getHijriMonthLength(int year, int month) { + return _hijriMonthLength(year, month); + } + + static bool isHijriLeapYear(int year) { + return _isHijriLeapYear(year); + } +} + +// ==================== Main Model ==================== + +enum CalendarType { + gregorian, + hijri, +} + +class DateRangCalenderModel extends ChangeNotifier { + final AppState appState; + final ErrorHandlerService navigationService; + final DialogService dialogService; + + // Static variable to persist calendar type preference across instances + static CalendarType _persistedCalendarType = CalendarType.gregorian; + static int _persistedTabIndex = 0; + + DateRangCalenderModel({required this.appState, required this.navigationService, required this.dialogService}) { + // Initialize from persisted state + _initializeFromPersistedState(); + } + + int _selectedTabIndex = 0; + CalendarType _calendarType = CalendarType.gregorian; + + void _initializeFromPersistedState() { + // Load from static persisted values + _selectedTabIndex = _persistedTabIndex; + _calendarType = _persistedCalendarType; + } + + // Umm al-Qura calendar adjustment (in days) + // This offset corrects the astronomical calculation to match the official Saudi calendar + static const int _ummAlQuraOffset = -1; + + // Hijri month names in English (short form) + static const List _hijriMonthNamesShort = [ + 'Muh', + 'Saf', + 'Rab I', + 'Rab II', + 'Jum I', + 'Jum II', + 'Raj', + 'Sha', + 'Ram', + 'Shaw', + 'Dhu Q', + 'Dhu H', + ]; + + // Hijri month names in English (full form) - public for month picker + static const List hijriMonthNamesEnglish = [ + 'Muharram', + 'Safar', + 'Rabi\' I', + 'Rabi\' II', + 'Jumada I', + 'Jumada II', + 'Rajab', + 'Sha\'ban', + 'Ramadan', + 'Shawwal', + 'Dhu al-Qi\'dah', + 'Dhu al-Hijjah', + ]; + + // Hijri month names in Arabic (short form) + static const List _hijriMonthNamesShortArabic = [ + 'محرم', + 'صفر', + 'ربيع الأول', + 'ربيع الثاني', + 'جمادى الأولى', + 'جمادى الثانية', + 'رجب', + 'شعبان', + 'رمضان', + 'شوال', + 'ذو القعدة', + 'ذو الحجة', + ]; + + // Hijri month names in Arabic (full form) - public for month picker + static const List hijriMonthNamesArabic = [ + 'محرم', + 'صفر', + 'ربيع الأول', + 'ربيع الثاني', + 'جمادى الأولى', + 'جمادى الثانية', + 'رجب', + 'شعبان', + 'رمضان', + 'شوال', + 'ذو القعدة', + 'ذو الحجة', + ]; + + // Gregorian month names (short form) + static const List _gregorianMonthNames = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + + // Gregorian month names in English (full form) - public for month picker + static const List gregorianMonthNamesEnglish = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + + // Gregorian month names in Arabic + static const List _gregorianMonthNamesArabic = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', + ]; + + // Gregorian month names in Arabic (full form) - public for month picker + static const List gregorianMonthNamesArabic = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', + ]; + + // Weekday names in English (short form) + static const List _weekdayNamesEnglish = [ + 'Sun', + 'Mon', + 'Tue', + 'Wed', + 'Thu', + 'Fri', + 'Sat', + ]; + + // Weekday names in Arabic (short form) + static const List _weekdayNamesArabic = [ + 'أحد', + 'اثنين', + 'ثلاثاء', + 'أربعاء', + 'خميس', + 'جمعة', + 'سبت', + ]; + + int get getSelectedTabIndex => _selectedTabIndex; + + CalendarType get calendarType => _calendarType; + + set selectedTabIndex(int value) { + _selectedTabIndex = value; + _calendarType = value == 0 ? CalendarType.gregorian : CalendarType.hijri; + notifyListeners(); + } + + void setTabIndex(int index) { + _selectedTabIndex = index; + _calendarType = index == 0 ? CalendarType.gregorian : CalendarType.hijri; + // Persist the selection for next time the widget opens + _persistCalendarTypePreference(); + notifyListeners(); + } + + void _persistCalendarTypePreference() { + // Store the preference in static variables so it persists when widget reopens + _persistedTabIndex = _selectedTabIndex; + _persistedCalendarType = _calendarType; + } + + bool get isGregorian => _calendarType == CalendarType.gregorian; + + bool get isHijri => _calendarType == CalendarType.hijri; + + /// Reset to Gregorian and clear persisted preference + /// Call this only when explicitly needed (e.g., logout) + void reset({bool force = false}) { + if (force) { + // Force reset - clear both instance and persisted state + _selectedTabIndex = 0; + _calendarType = CalendarType.gregorian; + _persistedTabIndex = 0; + _persistedCalendarType = CalendarType.gregorian; + notifyListeners(); + } + // If not forcing, keep the user's preference (persisted state remains) + } + + /// Clear persisted calendar type preference (reset to Gregorian on next open) + static void clearPersistedPreference() { + _persistedTabIndex = 0; + _persistedCalendarType = CalendarType.gregorian; + } + + // ==================== Conversion Methods ==================== + + /// Converts Gregorian DateTime to Hijri date with Umm al-Qura correction + HijriGregDate gregorianToHijri(DateTime gregorianDate) { + // Apply Umm al-Qura offset for accurate Saudi calendar + final adjustedDate = gregorianDate.add(Duration(days: _ummAlQuraOffset)); + return HijriGregConverter.gregorianToHijri(adjustedDate); + } + + /// Converts Hijri date to Gregorian DateTime with Umm al-Qura correction + DateTime hijriToGregorian(HijriGregDate hijriDate) { + final gregorianDate = HijriGregConverter.hijriToGregorian(hijriDate); + // Reverse the Umm al-Qura offset + return gregorianDate.subtract(Duration(days: _ummAlQuraOffset)); + } + + // ==================== Date Formatting Methods ==================== + + /// Formats a date based on the current calendar type + /// Returns formatted string like "14 Apr,26" for Gregorian or "14 Muh,1447" for Hijri + String formatDate(DateTime? date) { + if (date == null) return "-"; + + if (isGregorian) { + return formatGregorianDate(date); + } else { + return formatHijriDate(date); + } + } + + /// Formats a Gregorian date + /// Format: "dd MMM,yy" (e.g., "14 Apr,26") + String formatGregorianDate(DateTime date) { + String year = date.year.toString().substring(2); + String month = _gregorianMonthNames[date.month - 1]; + String day = date.day.toString(); + return '$day $month,$year'; + } + + /// Formats a date as Hijri + /// Format: "dd MMM,yyyy" (e.g., "14 Muh,1447") + String formatHijriDate(DateTime gregorianDate) { + HijriGregDate hijriDate = gregorianToHijri(gregorianDate); + String month = _hijriMonthNamesShort[hijriDate.month - 1]; + return '${hijriDate.day} $month,${hijriDate.year}'; + } + + /// Gets month name based on current calendar type + String getMonthName(int month) { + if (isGregorian) { + return _gregorianMonthNames[month - 1]; + } else { + return _hijriMonthNamesShort[month - 1]; + } + } + + /// Gets month name in Arabic based on current calendar type + String getMonthNameArabic(int month) { + if (isGregorian) { + return _gregorianMonthNamesArabic[month - 1]; + } else { + return _hijriMonthNamesShortArabic[month - 1]; + } + } + + /// Gets month name with language support + String getMonthNameLocalized(int month, bool isArabic) { + return isArabic ? getMonthNameArabic(month) : getMonthName(month); + } + + /// Gets weekday name in English + String getWeekdayName(int weekday) { + // weekday: 1 = Monday, 7 = Sunday in DateTime + // Convert to Sunday = 0 index + int index = weekday % 7; + return _weekdayNamesEnglish[index]; + } + + /// Gets weekday name in Arabic + String getWeekdayNameArabic(int weekday) { + // weekday: 1 = Monday, 7 = Sunday in DateTime + // Convert to Sunday = 0 index + int index = weekday % 7; + return _weekdayNamesArabic[index]; + } + + /// Gets weekday name with language support + String getWeekdayNameLocalized(int weekday, bool isArabic) { + return isArabic ? getWeekdayNameArabic(weekday) : getWeekdayName(weekday); + } + + /// Converts a date string to display format based on calendar type + String formatDateString(String? dateString) { + if (dateString == null || dateString.isEmpty) return "-"; + + try { + DateTime date = DateTime.parse(dateString); + return formatDate(date); + } catch (e) { + return "-"; + } + } + + // ==================== Date Range Methods ==================== + + /// Formats a date range based on current calendar type + /// Returns formatted string like "14 Apr,26 - 20 Apr,26" for Gregorian + String formatDateRange(DateTime? startDate, DateTime? endDate) { + if (startDate == null || endDate == null) return "-"; + + String start = formatDate(startDate); + String end = formatDate(endDate); + return '$start - $end'; + } + + /// Gets the current date in the selected calendar format + String getCurrentDateFormatted() { + return formatDate(DateTime.now()); + } + + // ==================== Calendar Type Specific Helpers ==================== + + /// Gets the year based on calendar type + int getCurrentYear() { + if (isGregorian) { + return DateTime.now().year; + } else { + return gregorianToHijri(DateTime.now()).year; + } + } + + /// Gets formatted year string based on calendar type + String getFormattedYear(DateTime date) { + if (isGregorian) { + return date.year.toString(); + } else { + return gregorianToHijri(date).year.toString(); + } + } + + /// Checks if a year is leap year based on calendar type + bool isLeapYear(int year) { + if (isGregorian) { + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + } else { + return HijriGregConverter.isHijriLeapYear(year); + } + } + + /// Gets the number of days in a month based on calendar type + int getDaysInMonth(int year, int month) { + if (isGregorian) { + return DateTime(year, month + 1, 0).day; + } else { + return HijriGregConverter.getHijriMonthLength(year, month); + } + } +} diff --git a/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart b/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart index 3f67b6a6..e92d0817 100644 --- a/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart +++ b/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart @@ -85,4 +85,12 @@ class DateRangeSelectorRangeViewModel extends ChangeNotifier { notifyListeners(); } + /// Update both fromDate and toDate (for single date selection in V2) + void updateSelectedDate(DateTime? date) { + _fromDate = date; + _toDate = date; + _currentlySelectedRange = null; + notifyListeners(); + } + } diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index d8a74d17..51948f01 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -1,6 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -9,9 +9,14 @@ import 'package:hmg_patient_app_new/core/enums.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_kesy.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/models/calendar_design_type.dart'; import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; import 'package:hmg_patient_app_new/widgets/time_picker_widget.dart'; +import 'dart:ui' as ui; class TextInputWidget extends StatelessWidget { final String labelText; @@ -222,26 +227,26 @@ class TextInputWidget extends StatelessWidget { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 12.r, color: AppColors.whiteColor), child: GestureDetector( onTap: () async { - bool isGregorian = true; - final picked = await showHijriGregBottomSheet(context, - isShowTimeSlots: true, - switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h), - language: appState.getLanguageCode()!, - initialDate: DateTime.now(), + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.selectDate.tr(context: context), + context, + child: DateRangeSelector( + onRangeSelected: (DateTime? start, DateTime? end) { + var picked = start.toString().split(" ").first; + if (picked != null && onChange != null) { + onChange!(picked.toString()); + } + }, + designType: CalendarDesignType.designV2, showCalendarToggle: isHideSwitcher == true ? false : true, - // fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "Poppins", - fontFamily: "Poppins", - okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), - cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), - onCalendarTypeChanged: (bool value) { - isGregorian = value; - }); - if (picked != null && onChange != null) { - if (onCalendarTypeChanged != null) { - onCalendarTypeChanged!.call(isGregorian); - } - onChange!(picked.toString()); - } + onCalendarTypeChanged: onCalendarTypeChanged, + ), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () { + // Don't reset calendar type - persist user's preference + }, + ); }, child: Utils.buildSvgWithAssets(icon: isArrowTrailing ? AppAssets.arrow_down : AppAssets.calendar), ), @@ -299,13 +304,11 @@ class TextInputWidget extends StatelessWidget { return Builder( builder: (context) { return Directionality( - textDirection: isArabic ? TextDirection.rtl : TextDirection.ltr, + textDirection: isArabic ? ui.TextDirection.rtl : ui.TextDirection.ltr, child: Localizations.override( - context: context, locale: const Locale('en', 'US'), // Force English locale for TextField child: TextField( - hintLocales: const [Locale('en', 'US')], enabled: isEnable, scrollPadding: EdgeInsets.zero, @@ -314,7 +317,7 @@ class TextInputWidget extends StatelessWidget { readOnly: isReadOnly, textAlignVertical: TextAlignVertical.top, textAlign: isArabic ? TextAlign.right : TextAlign.left, - textDirection: TextDirection.ltr, + textDirection: ui.TextDirection.ltr, onChanged: onChange, focusNode: focusNode ?? _focusNode, autofocus: autoFocus, @@ -346,7 +349,6 @@ class TextInputWidget extends StatelessWidget { isDense: true, hintText: hintText, hintStyle: TextStyle( - fontFamily: isArabic ? 'CairoArabic' : 'Poppins', fontSize: 14.f, height: 21 / 16, @@ -356,7 +358,7 @@ class TextInputWidget extends StatelessWidget { ), prefixIconConstraints: BoxConstraints(minWidth: 30.h), prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, isBold: true), - contentPadding: EdgeInsets.only(right:isArabic ? 10.w :0), + contentPadding: EdgeInsets.only(right: isArabic ? 10.w : 0), border: InputBorder.none, focusedBorder: InputBorder.none, enabledBorder: InputBorder.none, @@ -484,4 +486,3 @@ class _ThousandSeparatorInputFormatter extends TextInputFormatter { ); } } - diff --git a/pubspec.yaml b/pubspec.yaml index 7f14f57c..4d853184 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -71,7 +71,8 @@ dependencies: firebase_analytics: ^11.5.1 firebase_crashlytics: ^4.3.8 jiffy: ^6.4.3 - hijri_gregorian_calendar: ^0.1.1 +# hijri_gregorian_calendar: +# path: /Users/aamir/StudioProjects/hijri_gregorian_calendar sms_otp_auto_verify: ^2.2.0 web: any flutter_staggered_animations: ^1.1.1