Merge remote-tracking branch 'origin/master' into changes

# Conflicts:
#	assets/langs/ar-SA.json
#	lib/generated/codegen_loader.g.dart
#	lib/generated/locale_kesy.g.dart
pull/251/head
tahaalam 15 hours ago
commit a31c4c99df

@ -895,6 +895,7 @@
"monitor": "يتطلب متابعة",
"noSpecialResult": "لا توجد نتائج خاصة",
"setTheDateRange": "تعيين النطاق الزمني",
"selectDate": "اختر التاريخ",
"historyFlowchart": "السجل التاريخي",
"to": "إلى",
"startDate": "تاريخ البدء",

@ -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",

@ -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

@ -199,8 +199,8 @@ class ApiClientImp implements ApiClient {
body['TokenID'] = "@dm!n";
}
body['TokenID'] = "@dm!n";
body['PatientID'] = 1018977;
// body['TokenID'] = "@dm!n";
// body['PatientID'] = 1018977;
// body['PatientTypeID'] = 1;
// body['PatientOutSA'] = 0;
// body['SessionID'] = "45786230487560q";

@ -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<NotificationService>(() => NotificationServiceImp(
flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin,
@ -172,8 +172,12 @@ class AppDependencies {
getIt.registerLazySingleton<MonthlyReportRepo>(() => MonthlyReportRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<ActivePrescriptionsRepo>(() => ActivePrescriptionsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<TermsConditionsRepo>(() => TermsConditionsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerFactory<TermsConditionsViewModel>(() => TermsConditionsViewModel(termsConditionsRepo: getIt<TermsConditionsRepo>(), errorHandlerService: getIt<ErrorHandlerService>(),
),);
getIt.registerFactory<TermsConditionsViewModel>(
() => TermsConditionsViewModel(
termsConditionsRepo: getIt<TermsConditionsRepo>(),
errorHandlerService: getIt<ErrorHandlerService>(),
),
);
getIt.registerLazySingleton<MonthlyReportsRepo>(() => MonthlyReportsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<QrParkingRepo>(() => QrParkingRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<NotificationsRepo>(() => NotificationsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
@ -312,22 +316,12 @@ class AppDependencies {
getIt.registerLazySingleton<NotificationsViewModel>(() => NotificationsViewModel(notificationsRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<HealthTrackersViewModel>(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<ActivePrescriptionsViewModel>(
() => ActivePrescriptionsViewModel(
errorHandlerService: getIt(),
activePrescriptionsRepo: getIt()
),
);
getIt.registerFactory<QrParkingViewModel>(
() => QrParkingViewModel(
qrParkingRepo: getIt<QrParkingRepo>(),
errorHandlerService: getIt<ErrorHandlerService>(),
cacheService: getIt<CacheService>(),
),
);
getIt.registerLazySingleton<ActivePrescriptionsViewModel>(() => ActivePrescriptionsViewModel(errorHandlerService: getIt(), activePrescriptionsRepo: getIt()));
getIt.registerFactory<QrParkingViewModel>(() => QrParkingViewModel(qrParkingRepo: getIt<QrParkingRepo>(), errorHandlerService: getIt<ErrorHandlerService>(), cacheService: getIt<CacheService>()));
getIt.registerLazySingleton<AskDoctorViewModel>(() => AskDoctorViewModel(askDoctorRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<ServicesPriceListViewModel>(() => ServicesPriceListViewModel(servicesPriceListRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<DateRangCalenderModel>(() => DateRangCalenderModel(appState: getIt(), navigationService: getIt(), dialogService: getIt()));
}
}

@ -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({

@ -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("=========");
}
}
});

@ -259,7 +259,7 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo {
Failure? failure;
await apiClient.post(
body: {},
"$GET_ALL_TRANSPORTATIONS_ORDERS?patientID=1018977",
"$GET_ALL_TRANSPORTATIONS_ORDERS?patientID=$id",
isRCService: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;

@ -847,7 +847,7 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
// final queueList = list.map((item) => PatientQueueDetails.fromJson(item as Map<String, dynamic>)).toList().cast<PatientQueueDetails>();
final queueList = PatientQueueDetails.fromJson(queueData);
queueList.patientQueueData!.isClinicConfigured = 1;
// queueList.patientQueueData!.isClinicConfigured = 1;
apiResponse = GenericApiModel<PatientQueueDetails>(
messageStatus: messageStatus,

@ -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));
@ -13,10 +23,19 @@ class RadiologyAiAnalysisResponse {
factory RadiologyAiAnalysisResponse.fromJson(Map<String, dynamic> json) => RadiologyAiAnalysisResponse(
summary: json["summary"],
labResults: json["lab_results"],
aiPredictions: json["ai_predictions"],
riskFactors: json["riskFactors"],
prevention: json["prevention"],
languageId: json["language_id"],
);
Map<String, dynamic> toJson() => {
"summary": summary,
"lab_results": labResults,
"ai_predictions": aiPredictions,
"riskFactors": riskFactors,
"prevention": prevention,
"language_id": languageId,
};
}

@ -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<void> 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()),
);
}
}
},

@ -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';

@ -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<WeatherMonitorViewModel>(
create: (_) => getIt.get<WeatherMonitorViewModel>(),
), ChangeNotifierProvider<DateRangCalenderModel>(
create: (_) => getIt.get<DateRangCalenderModel>(),
)
], child: MyApp()),
), // Wrap your app

@ -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();

@ -125,7 +125,8 @@ class _RegisterNew extends State<RegisterNewStep2> {
),
)
],
).paddingAll(16.h),),
).paddingAll(16.h),
),
body: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();

@ -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<RadiologyAiAnalysisDetailedPage> createState() => _RadiologyAiAnalysisDetailedPageState();
}
class _RadiologyAiAnalysisDetailedPageState extends State<RadiologyAiAnalysisDetailedPage> {
late RadiologyViewModel radiologyProvider;
List<String> _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<RadiologyViewModel>(context, listen: false);
return CollapsingListView(
isClose: true,
title: LocaleKeys.aiAnalysis.tr(context: context),
child: Consumer<RadiologyViewModel>(
builder: (context, model, child) {
if (model.radiologyAiAnalysisResponse == null) {
return Utils.getNoDataWidget(context);
}
final analysis = model.radiologyAiAnalysisResponse!;
List<ExpandableListItem> 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),
),
],
),
);
}
}

@ -16,8 +16,18 @@ class RadiologyAiAnalysisWidget extends StatelessWidget {
const RadiologyAiAnalysisWidget({super.key, required this.data});
List<String> _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(
@ -56,10 +84,41 @@ class RadiologyAiAnalysisWidget extends StatelessWidget {
),
],
),
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<RadiologyViewModel>().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),
),
],
),
);
}
}

@ -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<RadiologyResultPage> {
late RadiologyViewModel radiologyViewModel;
final GlobalKey _aiAnalysisKey = GlobalKey();
RadiologyAiAnalysisResponse? _previousAiData;
@override
void initState() {
@ -52,7 +48,7 @@ class _RadiologyResultPageState extends State<RadiologyResultPage> {
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<RadiologyResultPage> {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<RadiologyViewModel>(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<RadiologyResultPage> {
).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<RadiologyResultPage> {
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<RadiologyResultPage> {
context.pop();
},
);
}) : SizedBox.shrink() : SizedBox(height: 24.h),
}) : SizedBox(height: 24.h),
],
),
),

@ -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<DateRangeSelector> createState() => _DateRangeSelectorState();
}
enum PickerViewMode {
date,
month,
year,
}
class _DateRangeSelectorState extends State<DateRangeSelector> {
late DateRangePickerController _calendarController;
DateTime? start;
DateTime? end;
late DateRangeSelectorRangeViewModel model;
PickerViewMode _viewMode = PickerViewMode.date;
// Cache for Hijri conversions to avoid repeated calculations
final Map<String, HijriGregDate> _hijriCache = {};
// Track the current Hijri month/year being displayed (for header display accuracy)
HijriGregDate? _currentHijriDisplay;
@override
void initState() {
_calendarController = DateRangePickerController();
scheduleMicrotask(() {
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<DateRangeSelector> {
Widget build(BuildContext context) {
model = Provider.of<DateRangeSelectorRangeViewModel>(context);
_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<DateRangeSelector> {
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,12 +105,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
fromDateComponent(),
Text(
LocaleKeys.to.tr(context: context),
style: TextStyle(
color: AppColors.calenderTextColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
letterSpacing: -.2
),
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<DateRangeSelector> {
),
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<DateRangeSelector> {
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;
@ -157,7 +183,6 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
scheduleMicrotask(() {
model.resetCurrentlySelectedRange();
});
}
}
},
@ -192,7 +217,9 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
height: 56.h,
),
),
SizedBox(width: 16.h,)
SizedBox(
width: 16.h,
)
],
),
),
@ -210,7 +237,6 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
textColor: AppColors.textGreenColor,
icon: AppAssets.reminder_bell,
iconColor: AppColors.textGreenColor,
height: 56.h,
),
),
@ -221,11 +247,620 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
);
}
Widget _buildDesignV2() {
return ChangeNotifierProvider(
create: (_) => DateRangCalenderModel(appState: getIt(), navigationService: getIt(), dialogService: getIt()),
child: Consumer<DateRangCalenderModel>(
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<DateRangCalenderModel, int>(
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<DateRangCalenderModel>(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<DateRangCalenderModel>(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<String> 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<DateRangeSelectorRangeViewModel>(
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,8 +868,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
toDateComponent() {
return Consumer<DateRangeSelectorRangeViewModel>(
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);
},
);
}
@ -243,11 +877,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
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: [
@ -279,14 +909,10 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
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)),
@ -297,14 +923,10 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
}),
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)),
@ -315,14 +937,10 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
}),
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)),
@ -331,7 +949,6 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
_calendarController.selectedRange = null;
model.currentlySelectedRange = Range.LAST_6MONTH;
model.calculateDatesFromRange();
}),
AppCustomChipWidget(
richText: Row(
@ -341,14 +958,10 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
],
),
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)),
@ -357,7 +970,6 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
_calendarController.selectedRange = null;
model.currentlySelectedRange = Range.THIS_YEAR;
model.calculateDatesFromRange();
}),
],
);

@ -0,0 +1,5 @@
enum CalendarDesignType {
defaultUI,
designV2,
}

@ -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<int> 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<String> _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<String> 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<String> _hijriMonthNamesShortArabic = [
'محرم',
'صفر',
'ربيع الأول',
'ربيع الثاني',
'جمادى الأولى',
'جمادى الثانية',
'رجب',
'شعبان',
'رمضان',
'شوال',
'ذو القعدة',
'ذو الحجة',
];
// Hijri month names in Arabic (full form) - public for month picker
static const List<String> hijriMonthNamesArabic = [
'محرم',
'صفر',
'ربيع الأول',
'ربيع الثاني',
'جمادى الأولى',
'جمادى الثانية',
'رجب',
'شعبان',
'رمضان',
'شوال',
'ذو القعدة',
'ذو الحجة',
];
// Gregorian month names (short form)
static const List<String> _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<String> gregorianMonthNamesEnglish = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
// Gregorian month names in Arabic
static const List<String> _gregorianMonthNamesArabic = [
'يناير',
'فبراير',
'مارس',
'أبريل',
'مايو',
'يونيو',
'يوليو',
'أغسطس',
'سبتمبر',
'أكتوبر',
'نوفمبر',
'ديسمبر',
];
// Gregorian month names in Arabic (full form) - public for month picker
static const List<String> gregorianMonthNamesArabic = [
'يناير',
'فبراير',
'مارس',
'أبريل',
'مايو',
'يونيو',
'يوليو',
'أغسطس',
'سبتمبر',
'أكتوبر',
'نوفمبر',
'ديسمبر',
];
// Weekday names in English (short form)
static const List<String> _weekdayNamesEnglish = [
'Sun',
'Mon',
'Tue',
'Wed',
'Thu',
'Fri',
'Sat',
];
// Weekday names in Arabic (short form)
static const List<String> _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);
}
}
}

@ -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();
}
}

@ -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,27 +227,27 @@ 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(),
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;
});
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) {
if (onCalendarTypeChanged != null) {
onCalendarTypeChanged!.call(isGregorian);
}
onChange!(picked.toString());
}
},
designType: CalendarDesignType.designV2,
showCalendarToggle: isHideSwitcher == true ? false : true,
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,
@ -484,4 +486,3 @@ class _ThousandSeparatorInputFormatter extends TextInputFormatter {
);
}
}

@ -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:

@ -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

Loading…
Cancel
Save