Merge branch 'master' into dev_aamir

# Conflicts:
#	lib/presentation/my_family/widget/family_cards.dart
pull/225/head
aamir-csol 2 days ago
commit b4ab3a112c

@ -41,10 +41,13 @@
tools:node="remove" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" tools:node="remove" />
<!-- Added by open_filex -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" tools:node="remove" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" tools:node="remove" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" tools:node="remove" />
<!-- Gallery/Photo permissions for Android 13+ (API 33+) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- Gallery/Photo permissions for Android 12 and below -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<uses-permission
android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
@ -90,8 +93,6 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />

@ -1,11 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30.996" height="28.414" viewBox="0 0 30.996 28.414">
<g id="image-gallery" transform="translate(-0.001 -2)">
<g id="Group_8635" data-name="Group 8635" transform="translate(0.001 5.875)">
<g id="Group_8634" data-name="Group 8634" transform="translate(0 5.078)">
<path id="Path_5018" data-name="Path 5018" d="M8.072,22.581A4.836,4.836,0,0,1,3.5,19.286l-.045-.149a4.711,4.711,0,0,1-.223-1.4V8.932L.1,19.391a2.933,2.933,0,0,0,2.056,3.558L22.123,28.3a2.962,2.962,0,0,0,.744.1,2.88,2.88,0,0,0,2.791-2.112l1.164-3.7Z" transform="translate(-0.001 -8.932)" fill="#2bb8a6"/>
</g>
<path id="Path_5019" data-name="Path 5019" d="M9.583,10.166A2.583,2.583,0,1,0,7,7.583,2.586,2.586,0,0,0,9.583,10.166Z" transform="translate(2.04 -5)" fill="#2bb8a6"/>
</g>
<path id="Path_5020" data-name="Path 5020" d="M26.6,2H7.229A3.233,3.233,0,0,0,4,5.229V19.436a3.233,3.233,0,0,0,3.229,3.229H26.6a3.233,3.233,0,0,0,3.229-3.229V5.229A3.233,3.233,0,0,0,26.6,2ZM7.229,4.583H26.6a.646.646,0,0,1,.646.646V14.4l-4.08-4.761a2.313,2.313,0,0,0-1.732-.794,2.258,2.258,0,0,0-1.726.815l-4.8,5.758-1.563-1.559a2.267,2.267,0,0,0-3.2,0L6.583,17.42V5.229a.646.646,0,0,1,.646-.646Z" transform="translate(1.166)" fill="#2bb8a6"/>
</g>
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
<path d="M21.2444 13.5C20.5571 13.5 20 14.0596 20 14.75C20 15.4404 20.5571 16 21.2444 16C21.9268 16 22.5 15.4401 22.5 14.75C22.5 14.0599 21.9268 13.5 21.2444 13.5Z" fill="#8F9AA3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.0732 9H17.9268C16.1034 8.99997 14.6336 8.99995 13.4777 9.15537C12.2775 9.31672 11.267 9.66191 10.4645 10.4645C9.66191 11.267 9.31672 12.2775 9.15537 13.4777C8.99995 14.6336 8.99997 16.1034 9 17.9268V18.0732C8.99997 19.8966 8.99995 21.3664 9.15537 22.5223C9.31672 23.7225 9.66191 24.733 10.4645 25.5355C11.1805 26.2516 12.062 26.6035 13.0961 26.7857C13.2742 27.8673 13.6274 28.7857 14.3689 29.5272C15.1752 30.3335 16.1906 30.6805 17.3977 30.8428C18.5606 30.9992 20.0394 30.9991 21.8748 30.9991H22.0212C23.8567 30.9991 25.3355 30.9992 26.4984 30.8428C27.7055 30.6805 28.7209 30.3335 29.5272 29.5272C30.3335 28.7209 30.6805 27.7055 30.8428 26.4984C30.9992 25.3355 30.9991 23.8567 30.9991 22.0212V21.8748C30.9991 20.0394 30.9992 18.5606 30.8428 17.3977C30.6805 16.1906 30.3335 15.1752 29.5272 14.3689C28.7857 13.6274 27.8673 13.2742 26.7857 13.0961C26.6035 12.062 26.2516 11.1805 25.5355 10.4645C24.733 9.66191 23.7225 9.31672 22.5223 9.15537C21.3664 8.99995 19.8966 8.99997 18.0732 9ZM26.9723 15.1769C27 15.9862 27 16.9005 27 17.9268V18.0732C27 19.8966 27.0001 21.3664 26.8446 22.5223C26.6833 23.7225 26.3381 24.733 25.5355 25.5355C24.733 26.3381 23.7225 26.6833 22.5223 26.8446C21.3664 27.0001 19.8966 27 18.0732 27H17.9268C16.9005 27 15.9862 27 15.1769 26.9723C15.3177 27.5048 15.5173 27.8471 15.7831 28.1129C16.1559 28.4857 16.6789 28.7282 17.6642 28.8606C18.6783 28.997 20.0221 28.9991 21.948 28.9991C23.874 28.9991 25.2178 28.997 26.2319 28.8606C27.2172 28.7282 27.7402 28.4857 28.1129 28.1129C28.4857 27.7402 28.7282 27.2172 28.8606 26.2319C28.997 25.2178 28.9991 23.874 28.9991 21.948C28.9991 20.0221 28.997 18.6783 28.8606 17.6642C28.7282 16.6789 28.4857 16.1559 28.1129 15.7831C27.8471 15.5173 27.5048 15.3177 26.9723 15.1769ZM11.8787 11.8787C12.2477 11.5097 12.7658 11.2691 13.7442 11.1375C14.7513 11.0021 16.0861 11 18 11C19.9139 11 21.2487 11.0021 22.2558 11.1375C23.2342 11.2691 23.7523 11.5097 24.1213 11.8787C24.4903 12.2477 24.7309 12.7658 24.8625 13.7442C24.9979 14.7513 25 16.0861 25 18C25 19.9139 24.9979 21.2487 24.8625 22.2558C24.7309 23.2342 24.4903 23.7523 24.1213 24.1213C23.9227 24.3199 23.6809 24.4813 23.3474 24.6086C22.5834 22.9592 21.3002 21.5131 19.6363 20.4162C17.4301 18.9618 14.6803 18.2023 11.8763 18.2523C11.5839 18.2518 11.2916 18.2596 11 18.2755L11 18C11 16.0861 11.0021 14.7513 11.1375 13.7442C11.2691 12.7658 11.5097 12.2477 11.8787 11.8787Z" fill="#8F9AA3"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

@ -1,7 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="31.908" height="29.134" viewBox="0 0 31.908 29.134">
<g id="Group_8579" data-name="Group 8579" transform="translate(0 -100)">
<path id="Path_4556" data-name="Path 4556" d="M88.972,88.108h-4.4l-2.539-2.775H72.324v4.162H68.162v4.162H64V107.53a2.783,2.783,0,0,0,2.775,2.775h22.2a2.783,2.783,0,0,0,2.775-2.775V90.882A2.783,2.783,0,0,0,88.972,88.108Zm-11.1,18.035a6.937,6.937,0,1,1,6.937-6.937A6.939,6.939,0,0,1,77.873,106.143Z" transform="translate(-59.838 18.829)" fill="#2bb8a6"/>
<path id="Path_4557" data-name="Path 4557" d="M4.162,32.432H6.937V28.27H11.1V25.495H6.937V21.333H4.162v4.162H0V28.27H4.162Z" transform="translate(0 78.667)" fill="#2bb8a6"/>
<path id="Path_4558" data-name="Path 4558" d="M213.505,230.4a4.435,4.435,0,0,0-4.439,4.439h0a4.439,4.439,0,1,0,4.439-4.439Z" transform="translate(-195.47 -116.804)" fill="#2bb8a6"/>
</g>
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.9686 9.25H20.0314H20.0314C20.6355 9.24999 21.1356 9.24998 21.5485 9.28515C21.9812 9.32201 22.3723 9.40069 22.7494 9.58742C23.4214 9.92009 23.8461 10.4488 24.1443 10.979C24.3755 11.39 24.555 11.8518 24.7071 12.2432C24.743 12.3355 24.7774 12.424 24.8105 12.5069L24.9569 12.8728C25.0299 13.0554 25.0664 13.1466 25.1425 13.1982C25.2185 13.2498 25.317 13.2499 25.5141 13.2502C26.3986 13.2516 27.0637 13.2617 27.5921 13.3345C28.2753 13.4287 28.7694 13.6309 29.2829 14.0249C29.5429 14.2244 29.7756 14.4572 29.9751 14.7171C30.4019 15.2734 30.5828 15.922 30.6678 16.6762C30.75 17.4057 30.75 18.3204 30.75 19.4548V24.0549C30.75 25.4225 30.75 26.5248 30.6335 27.3918C30.5125 28.2919 30.2536 29.0497 29.6517 29.6517C29.0497 30.2536 28.2919 30.5125 27.3918 30.6335C26.5248 30.75 25.4225 30.75 24.0549 30.75L15.9451 30.75C14.5775 30.75 13.4752 30.75 12.6083 30.6335C11.7081 30.5125 10.9503 30.2536 10.3484 29.6517C9.74644 29.0497 9.48754 28.2919 9.36653 27.3918C9.24997 26.5248 9.24998 25.4225 9.25 24.0549V24.0549L9.25 19.4548V19.4548C9.24999 18.3204 9.24998 17.4057 9.33222 16.6762C9.41725 15.922 9.59808 15.2734 10.0249 14.7171C10.2244 14.4572 10.4572 14.2244 10.7171 14.0249C11.2306 13.6309 11.7247 13.4287 12.4079 13.3345C12.9363 13.2617 13.6014 13.2516 14.4859 13.2502C14.683 13.2499 14.7815 13.2498 14.8575 13.1982C14.9336 13.1466 14.9701 13.0554 15.0431 12.8728L15.1895 12.5069C15.2226 12.424 15.257 12.3355 15.2929 12.2432C15.445 11.8518 15.6245 11.39 15.8557 10.979C16.1539 10.4488 16.5787 9.92009 17.2506 9.58742C17.6278 9.40069 18.0188 9.32201 18.4515 9.28515C18.8644 9.24998 19.3645 9.24999 19.9686 9.25H19.9686ZM16 22C16 19.7909 17.7909 18 20 18C22.2091 18 24 19.7909 24 22C24 24.2091 22.2091 26 20 26C17.7909 26 16 24.2091 16 22ZM19.9955 13C19.4457 13 19 13.4477 19 14C19 14.5502 19.4567 15 20.0045 15C20.5543 15 21 14.5523 21 14C21 13.4498 20.5433 13 19.9955 13Z" fill="#8F9AA3"/>
</svg>

Before

Width:  |  Height:  |  Size: 910 B

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -1692,6 +1692,21 @@
"grantLocationPermission": "يرجى منح إذن الوصول إلى الموقع من إعدادات التطبيق للحصول على نتائج أفضل.",
"NoBookedAppointments": "لا توجد مواعيد محجوزة",
"aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟",
"imageSizeTooLarge": "حجم الصورة كبير جداً. الحد الأقصى للحجم هو 1 ميجابايت",
"profileImageUpdatedSuccessfully": "تم تحديث صورة الملف الشخصي بنجاح",
"failedToProcessImage": "فشل في معالجة الصورة. يرجى المحاولة مرة أخرى",
"cameraGalleryPermissionMessage": "مطلوب إذن الكاميرا والمعرض لتحميل صورة الملف الشخصي. يرجى منح الأذونات من إعدادات التطبيق.",
"cameraGalleryPermissionRequired": "مطلوب أذونات الكاميرا والمعرض",
"cameraPermissionMessage": "مطلوب إذن الكاميرا لالتقاط الصور. يرجى منح الإذن من إعدادات التطبيق.",
"cameraPermissionRequired": "مطلوب إذن الكاميرا",
"galleryPermissionMessage": "مطلوب إذن المعرض لتحديد الصور. يرجى منح الإذن من إعدادات التطبيق.",
"galleryPermissionRequired": "مطلوب إذن المعرض",
"failedToCheckPermissions": "فشل في التحقق من الأذونات. يرجى المحاولة مرة أخرى",
"takePhotoWithCamera": "التقط صورة بالكاميرا",
"selectFromPhotoLibrary": "اختر من مكتبة الصور",
"selectDocumentFiles": "اختر ملفات المستندات",
"profilePicture": "صورة الملف الشخصي",
"aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟",
"sendingAskDrRequest": "جارٍ إرسال الطلب...",
"askDrRequestSentSuccess": "تم إرسال الطلب بنجاح، وسيتواصل معك الطبيب قريباً."
}

@ -1684,6 +1684,21 @@
"grantLocationPermission": "Please grant location permission from app settings to see better results.",
"NoBookedAppointments": "No Booked Appointments",
"aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?",
"imageSizeTooLarge": "Image size is too large. Maximum size is 1MB",
"profileImageUpdatedSuccessfully": "Profile image updated successfully",
"failedToProcessImage": "Failed to process image. Please try again",
"cameraGalleryPermissionMessage": "Camera and Gallery permissions are required to upload profile picture. Please grant permissions from app settings.",
"cameraGalleryPermissionRequired": "Camera and Gallery permissions are required",
"cameraPermissionMessage": "Camera permission is required to take photos. Please grant permission from app settings.",
"cameraPermissionRequired": "Camera permission is required",
"galleryPermissionMessage": "Gallery permission is required to select photos. Please grant permission from app settings.",
"galleryPermissionRequired": "Gallery permission is required",
"failedToCheckPermissions": "Failed to check permissions. Please try again",
"takePhotoWithCamera": "Take a photo with your camera",
"selectFromPhotoLibrary": "Select from your photo library",
"selectDocumentFiles": "Select document files",
"profilePicture": "Profile Picture",
"aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?",
"sendingAskDrRequest": "Sending Request...",
"askDrRequestSentSuccess": "Request has been sent successfully, The doctor will get back to you soon."
}

@ -57,6 +57,9 @@ PODS:
- 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)
@ -67,6 +70,10 @@ PODS:
- 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
@ -93,8 +100,19 @@ PODS:
- 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)
@ -109,11 +127,26 @@ PODS:
- 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)
@ -209,9 +242,23 @@ PODS:
- 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
@ -219,6 +266,9 @@ PODS:
- 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)
@ -264,9 +314,14 @@ PODS:
- 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):
@ -305,10 +360,12 @@ DEPENDENCIES:
- 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`)
@ -319,6 +376,7 @@ DEPENDENCIES:
- 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`)
@ -347,9 +405,13 @@ SPEC REPOS:
- Firebase
- FirebaseAnalytics
- FirebaseCore
- FirebaseCoreExtension
- FirebaseCoreInternal
- FirebaseCrashlytics
- FirebaseInstallations
- FirebaseMessaging
- FirebaseRemoteConfigInterop
- FirebaseSessions
- FLAnimatedImage
- Google-Maps-iOS-Utils
- GoogleAdsOnDeviceConversion
@ -357,6 +419,8 @@ SPEC REPOS:
- GoogleDataTransport
- GoogleMaps
- GoogleUtilities
- libwebp
- Mantle
- MapboxCommon
- MapboxCoreMaps
- MapboxCoreNavigation
@ -371,7 +435,9 @@ SPEC REPOS:
- PayFortSDK
- Polyline
- PromisesObjC
- PromisesSwift
- SDWebImage
- SDWebImageWebPCoder
- Solar-dev
- SwiftProtobuf
- SwiftyGif
@ -399,12 +465,16 @@ EXTERNAL SOURCES:
: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:
@ -425,6 +495,8 @@ EXTERNAL SOURCES:
: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:
@ -461,50 +533,59 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
SPEC CHECKSUMS:
amazon_payfort: 4ad7a3413acc1c4c4022117a80d18fee23c572d3
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
barcode_scan2: 4e4b850b112f4e29017833e4715f36161f987966
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
amazon_payfort: 782d287860ac8962baf4e0a2a0fb2f81fdf27204
audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe
barcode_scan2: 050b7b327e85954c8918715d42e83c5a35c3ed89
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90
device_calendar: b55b2c5406cfba45c95a59f9059156daee1f74ed
device_calendar_plus_ios: 2c04ad7643c6e697438216e33693b84e8ca45ded
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
device_calendar: 9cb33f88a02e19652ec7b8b122ca778f751b1f7b
device_calendar_plus_ios: 34dca387f44fad29b840096a840f94edc55af858
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
firebase_analytics: 0e25ca1d4001ccedd40b4e5b74c0ec34e18f6425
firebase_core: 995454a784ff288be5689b796deb9e9fa3601818
firebase_messaging: f4a41dd102ac18b840eba3f39d67e77922d3f707
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: cb8138af67cda6dd981f7101a5d709003af21502
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_ios_voip_kit_karmm: 371663476722afb631d5a13a39dee74c56c1abd0
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
flutter_nfc_kit: e1b71583eafd2c9650bc86844a7f2d185fb414f6
flutter_zoom_videosdk: 0f59e71685a03ddb0783ecc43bf3155b8599a7f5
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
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: 3213e1e5f5588b6134935cb8fc59acb4e6d88377
google_maps_flutter_ios: ffdd933832966673a1f57f5effd014dc8d4d43a2
GoogleAdsOnDeviceConversion: 2be6297a4f048459e0ae17fad9bfd2844e10cf64
GoogleAppMeasurement: 700dce7541804bec33db590a5c496b663fbe2539
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
health: 32d2fbc7f26f9a2388d1a514ce168adbfa5bda65
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
location: 155caecf9da4f280ab5fe4a55f94ceccfab838f8
manage_calendar_events: fe1541069431af035ced925ebd9def8b4b271254
map_launcher: 8051ad5783913cafce93f2414c6858f2904fd8df
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
@ -515,27 +596,29 @@ SPEC CHECKSUMS:
MapboxNavigationNative: 629e359f3d2590acd1ebbacaaf99e1a80ee57e42
MapboxSpeech: cd25ef99c3a3d2e0da72620ff558276ea5991a77
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc
open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1
network_info_plus: 6613d9d7cdeb0e6f366ed4dbe4b3c51c52d567a9
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
PayFortSDK: 233eabe9a45601fdbeac67fa6e5aae46ed8faf82
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
Solar-dev: 4612dc9878b9fed2667d23b327f1d4e54e16e8d0
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
SwiftProtobuf: e1b437c8e31a4c5577b643249a0bb62ed4f02153
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
video_player_avfoundation: 7993f492ae0bd77edaea24d9dc051d8bb2cd7c86
wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
webview_flutter_wkwebview: 29eb20d43355b48fe7d07113835b9128f84e3af4
ZoomVideoSDK: 94e939820e57a075c5e712559f927017da0de06a
PODFILE CHECKSUM: 8235407385ddd5904afc2563d65406117a51993e

@ -89,6 +89,8 @@
<string>This app requires motion detection access to function properly.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app requires photo library access to select image as document &amp; upload it.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app requires photo library access to upload profile picture and documents.</string>
<key>NSRemindersUsageDescription</key>
<string>This app requires calendar access to set reminders for Virtual &amp; Normal Appointments.</string>
<key>NSSpeechRecognitionUsageDescription</key>

@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts {
static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.preProd;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT
@ -947,6 +947,10 @@ const PROFILE_SETTING = 'Services/Patients.svc/REST/GetPateintInfoForUpdate';
const SAVE_SETTING = 'Services/Patients.svc/REST/UpdatePateintInfo';
const GET_PROFILE_IMAGE = 'Services/Patients.svc/REST/Patient_GetProfileImageData';
const UPLOAD_PROFILE_IMAGE = 'Services/Patients.svc/REST/Patient_InsertProfileImageData';
const DOWNLOAD_INVOICE_PDF = 'Services/Notifications.svc/REST/DownloadInvoiceReport';
class ApiKeyConstants {

@ -358,6 +358,9 @@ class AppAssets {
static const String imageIcon = '$svgBasePath/image_icon.svg';
static const String appointmentRatingIcon = '$svgBasePath/appointment_rating_icon.svg';
static const String invoiceListIcon = '$svgBasePath/invoice_list_icon.svg';
static const String open_camera = '$svgBasePath/open_camera.svg';
static const String gallery = '$svgBasePath/gallery.svg';
static const String files = '$svgBasePath/files.svg';
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';

@ -10,14 +10,19 @@ import 'package:hmg_patient_app_new/features/authentication/models/resp_models/a
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_user_staus_nhic_response_model.dart';
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import '../features/authentication/models/request_models/registration_payload_model.dart';
class AppState {
NavigationService navigationService;
CacheService cacheService;
AppState({required this.navigationService}){
static const String _profileImageKey = 'profile_image_data';
AppState({required this.navigationService, required this.cacheService}){
checkIfGMSAvailableOrNot();
_loadProfileImageFromCache();
}
double userLat = 0.0;
@ -84,6 +89,31 @@ class AppState {
set setUserBloodGroup(String value) => _userBloodGroup = value;
String? _profileImageData;
String? get getProfileImageData => _profileImageData;
set setProfileImageData(String? value) {
_profileImageData = value;
// Persist to cache
if (value != null && value.isNotEmpty) {
cacheService.saveString(key: _profileImageKey, value: value);
} else {
cacheService.remove(key: _profileImageKey);
}
}
/// Load profile image from cache on app initialization
void _loadProfileImageFromCache() {
_profileImageData = cacheService.getString(key: _profileImageKey);
}
/// Clear profile image from cache (e.g., on logout)
void clearProfileImageCache() {
_profileImageData = null;
cacheService.remove(key: _profileImageKey);
}
SelectDeviceByImeiRespModelElement? _selectDeviceByImeiRespModelElement;
void setSelectDeviceByImeiRespModelElement(SelectDeviceByImeiRespModelElement? value) {

@ -113,7 +113,12 @@ class AppDependencies {
getIt.registerLazySingleton<LocalAuthentication>(() => LocalAuthentication());
getIt.registerLazySingleton<GAnalytics>(() => GAnalytics());
getIt.registerLazySingleton<AppState>(() => AppState(navigationService: getIt()));
// Register CacheService before AppState so it can be injected
final sharedPreferences = await SharedPreferences.getInstance();
getIt.registerLazySingleton<CacheService>(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt()));
getIt.registerLazySingleton<AppState>(() => AppState(navigationService: getIt(), cacheService: getIt()));
getIt.registerLazySingleton<LocationUtils>(() => LocationUtils(
isShowConfirmDialog: false,
navigationService: getIt(),
@ -126,8 +131,6 @@ class AppDependencies {
navigationService: getIt(),
));
final sharedPreferences = await SharedPreferences.getInstance();
getIt.registerLazySingleton<CacheService>(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt()));
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
getIt.registerLazySingleton<NotificationService>(() => NotificationServiceImp(

@ -0,0 +1,103 @@
import 'dart:io';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
class ImageCompressionHelper {
/// Compress and resize image to max 200x200 and ensure file size < 1MB
/// Returns the compressed image file
static Future<File?> compressAndResizeImage(File imageFile) async {
try {
// Check if file exists
if (!await imageFile.exists()) {
print('Image file does not exist');
return null;
}
print('Original file size: ${await imageFile.length() / 1024} KB');
// Get temporary directory
final tempDir = await getTemporaryDirectory();
final targetPath = path.join(
tempDir.path,
'compressed_${DateTime.now().millisecondsSinceEpoch}.jpg',
);
// Start with quality 85
int quality = 85;
const int maxFileSizeBytes = 1048576; // 1MB
// Compress the image
var result = await FlutterImageCompress.compressWithFile(
imageFile.absolute.path,
quality: quality,
format: CompressFormat.jpeg,
);
if (result == null || result.isEmpty) {
print('Compression returned null or empty');
return null;
}
// Write to file
File resultFile = File(targetPath);
await resultFile.writeAsBytes(result);
int fileSize = await resultFile.length();
print('First compression: ${fileSize / 1024} KB, quality: $quality');
// If file size is still > 1MB, reduce quality progressively
int attempts = 0;
while (fileSize > maxFileSizeBytes && quality > 10 && attempts < 10) {
quality -= 10;
attempts++;
result = await FlutterImageCompress.compressWithFile(
imageFile.absolute.path,
quality: quality,
format: CompressFormat.jpeg,
);
if (result == null || result.isEmpty) {
print('Compression returned null on attempt $attempts');
break;
}
await resultFile.writeAsBytes(result);
fileSize = await resultFile.length();
print('Compression attempt $attempts: ${fileSize / 1024} KB, quality: $quality');
}
// Final validation
if (await resultFile.exists()) {
final finalSize = await resultFile.length();
print('Final compressed image size: ${finalSize / 1024} KB');
if (finalSize > maxFileSizeBytes) {
print('Warning: Final file size (${finalSize / 1024} KB) exceeds 1MB limit');
}
return resultFile;
}
return null;
} catch (e, stackTrace) {
print('Error compressing image: $e');
print('Stack trace: $stackTrace');
return null;
}
}
/// Get file size in KB
static Future<double> getFileSizeInKB(File file) async {
int bytes = await file.length();
return bytes / 1024;
}
/// Get file size in MB
static Future<double> getFileSizeInMB(File file) async {
int bytes = await file.length();
return bytes / (1024 * 1024);
}
}

@ -47,6 +47,7 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart';
import '../insurance/insurance_view_model.dart';
import '../profile_settings/profile_settings_repo.dart';
import 'models/request_models/get_user_mobile_device_data.dart';
import 'models/request_models/insert_patient_mobile_deviceinfo.dart';
import 'models/request_models/patient_insert_device_imei_request.dart';
@ -630,6 +631,10 @@ class AuthenticationViewModel extends ChangeNotifier {
} else {
if (activation.list != null && activation.list!.isNotEmpty) {
if (isSwitchUser) {
// Clear profile image cache when switching users
_appState.clearProfileImageCache();
log("🔄 Switching user - cleared profile image cache");
if (_appState.getIsChildLoggedIn) {
_appState.setSuperUserID = null;
// _appState.setIsChildLoggedIn = false;
@ -646,9 +651,15 @@ class AuthenticationViewModel extends ChangeNotifier {
activation.list!.first.isParentUser = true;
_appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true);
}
// Fetch profile image after switching user
await _fetchProfileImage(activation.list!.first.patientId);
} else {
activation.list!.first.isParentUser = true;
_appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true);
// Fetch profile image after login
await _fetchProfileImage(activation.list!.first.patientId);
}
activation.list!.first.bloodGroup = activation.patientBloodType;
activation.list!.first.zipCode = selectedCountrySignup == CountryEnum.others ? '0' : selectedCountrySignup.countryCode;
@ -656,6 +667,7 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.setPrivilegeModelList(activation.list!.first.listPrivilege!);
_appState.setUserBloodGroup = activation.patientBlodType ?? "N/A";
// Fetch patient blood type from new API
// await getPatientBloodTypeNew();
}
@ -1172,6 +1184,7 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.setIsAuthenticated = false;
_appState.setAppAuthToken = "";
_appState.setUserBloodGroup = "";
_appState.clearProfileImageCache(); // Clear profile image cache
_appState.setSuperUserID = null;
_appState.setIsChildLoggedIn = false;
_appState.setFamilyFileTokenID = "";
@ -1215,4 +1228,42 @@ class AuthenticationViewModel extends ChangeNotifier {
log("Error calling getPatientBloodType: $e");
}
}
/// Fetch profile image after login and store in AppState
Future<void> _fetchProfileImage(int? patientID) async {
if (patientID == null) return;
try {
log("Fetching profile image for patient ID: $patientID");
final profileSettingsRepo = GetIt.instance<ProfileSettingsRepo>();
final result = await profileSettingsRepo.getProfileImage(patientID: patientID);
result.fold(
(failure) {
// Silently fail - profile image is optional
log("Failed to fetch profile image: ${failure.message}");
},
(apiResponse) {
if (apiResponse.data != null && apiResponse.data['Patient_GetProfileImageDataList'] != null) {
var imageList = apiResponse.data['Patient_GetProfileImageDataList'];
if (imageList is List && imageList.isNotEmpty) {
String? imageData = imageList[0]['ImageData'];
if (imageData != null && imageData.isNotEmpty) {
_appState.setProfileImageData = imageData;
log("✅ Profile image loaded and cached successfully");
} else {
log("⚠️ Profile image data is empty");
}
} else {
log("⚠️ Profile image list is empty");
}
} else {
log("⚠️ No profile image data in response");
}
},
);
} catch (e) {
log("❌ Error fetching profile image: $e");
}
}
}

@ -16,6 +16,18 @@ abstract class ProfileSettingsRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> deactivateAccount();
Future<Either<Failure, GenericApiModel<dynamic>>> getProfileSettings();
/// Get patient profile image
Future<Either<Failure, GenericApiModel<dynamic>>> getProfileImage({
required int patientID,
});
/// Upload patient profile image
Future<Either<Failure, GenericApiModel<dynamic>>> uploadProfileImage({
required int patientID,
required String imageData,
required bool isActive,
});
}
class ProfileSettingsRepoImp implements ProfileSettingsRepo {
@ -137,5 +149,89 @@ class ProfileSettingsRepoImp implements ProfileSettingsRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getProfileImage({
required int patientID,
}) async {
final Map<String, dynamic> body = {
'PatientID': patientID,
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
GET_PROFILE_IMAGE,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> uploadProfileImage({
required int patientID,
required String imageData,
required bool isActive,
}) async {
final Map<String, dynamic> body = {
'PatientID': patientID,
'PatientsProfileImageData': imageData,
'IsActive': isActive,
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
UPLOAD_PROFILE_IMAGE,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/features/profile_settings/models/get_patient_info_response_model.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_repo.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
@ -41,6 +42,12 @@ class ProfileSettingsViewModel extends ChangeNotifier {
late GetPatientInfoForUpdate getPatientInfoForUpdate;
bool isPatientProfileLoading = false;
// Profile image state
bool isProfileImageLoading = false;
bool isUploadingProfileImage = false;
String? profileImageData;
String? profileImageError;
ProfileSettingsViewModel({
required CacheService cacheService,
required this.profileSettingsRepo,
@ -172,4 +179,116 @@ class ProfileSettingsViewModel extends ChangeNotifier {
isCloseButtonVisible: true,
);
}
// Profile image
Future<void> getProfileImage({
required int patientID,
Function(dynamic)? onSuccess,
Function(String)? onError,
bool forceRefresh = false, // Add flag to force refresh
}) async {
// Skip API call if image is already loaded and not forcing refresh
if (!forceRefresh && GetIt.instance<AppState>().getProfileImageData != null) {
// Use cached data
profileImageData = GetIt.instance<AppState>().getProfileImageData;
if (onSuccess != null) {
onSuccess(profileImageData);
}
return;
}
isProfileImageLoading = true;
profileImageError = null;
notifyListeners();
final result = await profileSettingsRepo.getProfileImage(patientID: patientID);
result.fold(
(failure) {
isProfileImageLoading = false;
profileImageError = failure.message;
notifyListeners();
if (onError != null) {
onError(failure.message);
}
},
(response) {
isProfileImageLoading = false;
// Extract image data from response
if (response.data != null && response.data['Patient_GetProfileImageDataList'] != null) {
var imageList = response.data['Patient_GetProfileImageDataList'];
if (imageList is List && imageList.isNotEmpty) {
profileImageData = imageList[0]['PatientsProfileImageData'];
// Store in AppState for global access
GetIt.instance<AppState>().setProfileImageData = profileImageData;
} else {
profileImageData = null;
GetIt.instance<AppState>().setProfileImageData = null;
}
} else {
profileImageData = null;
GetIt.instance<AppState>().setProfileImageData = null;
}
notifyListeners();
onSuccess?.call(response.data);
},
);
}
/// Clear cached profile image data
void clearProfileImageCache() {
profileImageData = null;
GetIt.instance<AppState>().setProfileImageData = null;
notifyListeners();
}
/// Sync profile image data from AppState
void syncProfileImageFromAppState() {
final appStateImageData = GetIt.instance<AppState>().getProfileImageData;
if (appStateImageData != null && appStateImageData.isNotEmpty) {
if (profileImageData != appStateImageData) {
profileImageData = appStateImageData;
notifyListeners();
}
}
}
Future<void> uploadProfileImage({
required int patientID,
required String imageData,
Function(dynamic)? onSuccess,
Function(String)? onError,
}) async {
isUploadingProfileImage = true;
profileImageError = null;
notifyListeners();
final result = await profileSettingsRepo.uploadProfileImage(
patientID: patientID,
imageData: imageData,
isActive: true,
);
result.fold(
(failure) {
isUploadingProfileImage = false;
profileImageError = failure.message;
notifyListeners();
if (onError != null) {
onError(failure.message);
} else {
errorHandlerService.handleError(failure: failure);
}
},
(response) {
isUploadingProfileImage = false;
profileImageData = imageData;
// Store in AppState for global access
GetIt.instance<AppState>().setProfileImageData = imageData;
notifyListeners();
onSuccess?.call(response.data);
},
);
}
}

@ -1683,6 +1683,20 @@ abstract class LocaleKeys {
static const grantLocationPermission = 'grantLocationPermission';
static const NoBookedAppointments = 'NoBookedAppointments';
static const aiDisclaimerRad = 'aiDisclaimerRad';
static const imageSizeTooLarge = 'imageSizeTooLarge';
static const profileImageUpdatedSuccessfully = 'profileImageUpdatedSuccessfully';
static const failedToProcessImage = 'failedToProcessImage';
static const cameraGalleryPermissionMessage = 'cameraGalleryPermissionMessage';
static const cameraGalleryPermissionRequired = 'cameraGalleryPermissionRequired';
static const cameraPermissionMessage = 'cameraPermissionMessage';
static const cameraPermissionRequired = 'cameraPermissionRequired';
static const galleryPermissionMessage = 'galleryPermissionMessage';
static const galleryPermissionRequired = 'galleryPermissionRequired';
static const failedToCheckPermissions = 'failedToCheckPermissions';
static const takePhotoWithCamera = 'takePhotoWithCamera';
static const selectFromPhotoLibrary = 'selectFromPhotoLibrary';
static const selectDocumentFiles = 'selectDocumentFiles';
static const profilePicture = 'profilePicture';
static const sendingAskDrRequest = 'sendingAskDrRequest';
static const askDrRequestSentSuccess = 'askDrRequestSentSuccess';

@ -48,6 +48,7 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card
import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart';
@ -228,6 +229,8 @@ class _LandingPageState extends State<LandingPage> {
child: Column(
spacing: 16.h,
children: [
Row(
spacing: 8.h,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -235,15 +238,7 @@ class _LandingPageState extends State<LandingPage> {
appState.isAuthenticated
? WelcomeWidget(
onTap: () {
// DialogService dialogService = getIt.get<DialogService>();
// dialogService.showFamilyBottomSheetWithoutH(
// label: LocaleKeys.familyTitle.tr(context: context),
// message: "",
// isShowManageButton: true,
// onSwitchPress: (FamilyFileResponseModelLists profile) {
// getIt.get<MedicalFileViewModel>().switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber);
// },
// profiles: getIt.get<MedicalFileViewModel>().patientFamilyFiles);
// ...existing navigation code...
Navigator.of(context).push(
CustomPageRoute(
@ -253,29 +248,18 @@ class _LandingPageState extends State<LandingPage> {
);
},
name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'),
// imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg,
imageUrl: appState
.getAuthenticatedUser()
?.gender == 1
? ((appState
.getAuthenticatedUser()
?.age ?? 0) < 7 ? AppAssets.babyBoyImg : AppAssets.maleImg)
: ((appState
.getAuthenticatedUser()
?.age ?? 0) < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg),
imageWidget: UserAvatarWidget(
width: 42.w,
height: 42.h,
fit: BoxFit.cover,
isCircular: true,
),
).expanded
: CustomButton(
text: LocaleKeys.loginOrRegister.tr(context: context),
onPressed: () async {
await authVM.onLoginPressed();
// Navigator.pushReplacementNamed(
// // context,
// context,
// AppRoutes.zoomCallPage,
// // arguments: CallArguments(appointmentID, "111", "Patient", "40", "1", true, 1),
// arguments: CallArguments("test123", "123", "Patient", "40", "0", true, 1),
// // arguments: CallArguments("SmallDailyStandup9875", "123", "Patient", "40", "0", false, int.parse(widget.incomingCallData!.appointmentNo!)),
// );
// ...existing commented code...
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
@ -291,6 +275,8 @@ class _LandingPageState extends State<LandingPage> {
mainAxisSize: MainAxisSize.min,
// spacing: 18.h,
children: [
Stack(clipBehavior: Clip.none, children: [
if (appState.isAuthenticated)
Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 24.h, width: 24.h).onPress(() async {

@ -20,13 +20,15 @@ import 'package:smooth_corner/smooth_corner.dart';
class WelcomeWidget extends StatelessWidget {
final String name;
final String imageUrl;
final String? imageUrl;
final Widget? imageWidget;
final VoidCallback? onTap;
const WelcomeWidget({
super.key,
required this.name,
required this.imageUrl,
this.imageUrl,
this.imageWidget,
this.onTap,
});
@ -50,7 +52,19 @@ class WelcomeWidget extends StatelessWidget {
),
);
}),
Image.asset(imageUrl, width: 40, height: 40),
Container(
height: 32.h,
width: 1.5.w,
margin: EdgeInsets.only(right: 10.w,left: 10.w),
decoration: BoxDecoration(
color: AppColors.greyTextColor.withOpacity(.3),
borderRadius: BorderRadius.circular(1.r),
),
),
imageWidget ??
(imageUrl != null
? Image.asset(imageUrl!, width: 40, height: 40)
: SizedBox(width: 40, height: 40)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4.h,

@ -78,6 +78,7 @@ import 'package:hmg_patient_app_new/widgets/input_widget.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:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart';
import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
@ -242,12 +243,12 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(
(appState.getAuthenticatedUser()?.gender == 1
? ((appState.getAuthenticatedUser()?.age ?? 0) < 7 ? AppAssets.babyBoyImg : AppAssets.maleImg)
: ((appState.getAuthenticatedUser()?.age ?? 0) < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg)),
width: 56.w,
height: 56.h),
UserAvatarWidget(
width: 56.w,
height: 56.h,
fit: BoxFit.cover,
isCircular: true,
),
SizedBox(width: 8.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,

@ -12,6 +12,7 @@ class MedicalFileCard extends StatelessWidget {
final double? iconSize;
final bool isLargeText;
final bool isLocked;
final Color? iconColor;
const MedicalFileCard({
super.key,
@ -22,6 +23,7 @@ class MedicalFileCard extends StatelessWidget {
this.iconSize,
this.isLargeText = false,
this.isLocked = false,
this.iconColor,
});
@override
@ -44,7 +46,14 @@ class MedicalFileCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(icon: svgIcon, width: iconS, height: iconS, fit: BoxFit.contain, applyThemeColor: false),
Utils.buildSvgWithAssets(
icon: svgIcon,
width: iconS,
height: iconS,
fit: BoxFit.contain,
applyThemeColor: false,
iconColor: iconColor,
),
SizedBox(height: 8.h),
isLargeText ? label.toText13(color: textColor, isBold: true, maxLine: 2) : label.toText11(color: textColor, isBold: true, maxLine: 2),
],

@ -23,6 +23,7 @@ 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/chip/custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart';
import 'package:provider/provider.dart';
class FamilyCards extends StatefulWidget {
@ -197,12 +198,12 @@ class _FamilyCardsState extends State<FamilyCards> {
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(
(appState.getAuthenticatedUser()?.gender == 1
? ((appState.getAuthenticatedUser()?.age ?? 0) < 7 ? AppAssets.babyBoyImg : AppAssets.maleImg)
: ((appState.getAuthenticatedUser()?.age ?? 0) < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg)),
width: 56.w,
height: 56.h),
UserAvatarWidget(
width: 56.w,
height: 56.h,
fit: BoxFit.cover,
isCircular: true,
),
SizedBox(width: 8.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -353,14 +354,14 @@ class _FamilyCardsState extends State<FamilyCards> {
Column(
mainAxisSize: MainAxisSize.min,
children: [
Utils.buildImgWithAssets(
icon: profile.gender == null
? AppAssets.dummyUser
: profile.gender == 1
? ((profile.age ?? 0) < 7 ? AppAssets.babyBoyImg : AppAssets.maleImg)
: (profile.age! < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg),
UserAvatarWidget(
width: 72.h,
height: 70.h,
gender: profile.gender,
age: profile.age,
fit: BoxFit.cover,
customProfileImageData: "", // Prevent showing logged-in user's image for family members
isCircular: true,
),
SizedBox(height: 8.h),
(profile.patientName ?? "Unknown").toText14(isBold: true, isCenter: true, maxlines: 1),

@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -8,8 +7,6 @@ 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';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/int_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -17,28 +14,26 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart';
import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart';
import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart';
import 'package:hmg_patient_app_new/presentation/profile_settings/widgets/profile_picture_widget.dart';
import 'package:hmg_patient_app_new/presentation/profile_settings/widgets/update_email_widget.dart';
import 'package:hmg_patient_app_new/presentation/profile_settings/widgets/update_emergency_contact_widget.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/app_language_change.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:in_app_review/in_app_review.dart';
@ -120,6 +115,9 @@ class ProfileSettingsState extends State<ProfileSettings> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Profile Picture Section
ProfilePictureWidget(),
// SizedBox(
// height: dynamicItemHeight(context) + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space
// child: Swiper(
@ -455,7 +453,14 @@ class FamilyCardWidget extends StatelessWidget {
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(profile.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, width: 56.w, height: 56.h),
UserAvatarWidget(
width: 56.w,
height: 56.h,
gender: profile.gender,
age: profile.age,
fit: BoxFit.cover,
isCircular: true,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
@ -560,10 +565,10 @@ class FamilyCardWidget extends StatelessWidget {
}
},
backgroundColor: insuranceVM.isInsuranceExpired
? AppColors.primaryRedColor.withOpacity(0.1)
? AppColors.primaryRedColor.withValues(alpha: 0.1)
: insuranceVM.isInsuranceActive
? AppColors.successColor.withOpacity(0.1)
: AppColors.warningColorYellow.withOpacity(0.1),
? AppColors.successColor.withValues(alpha: 0.1)
: AppColors.warningColorYellow.withValues(alpha: 0.1),
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceVM.isInsuranceActive ? 6.w : 0.w),
).toShimmer2(isShow: insuranceVM.isInsuranceLoading)
: SizedBox.shrink();

@ -14,6 +14,7 @@ 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/user_avatar_widget.dart';
import 'package:provider/provider.dart';
class FamilyCardWidget extends StatelessWidget {
@ -49,7 +50,15 @@ class FamilyCardWidget extends StatelessWidget {
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(profile.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, width: 56.w, height: 56.h),
UserAvatarWidget(
width: 56.w,
height: 56.h,
gender: profile.gender,
age: profile.age,
fit: BoxFit.cover,
customProfileImageData: "",
isCircular: true,// Prevent showing logged-in user's image for family members
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,

@ -0,0 +1,511 @@
import 'dart:convert';
import 'dart:io';
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/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/core/utils/image_compression_helper.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_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/image_picker.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
class ProfilePictureWidget extends StatefulWidget {
const ProfilePictureWidget({super.key});
@override
State<ProfilePictureWidget> createState() => _ProfilePictureWidgetState();
}
class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
final AppState _appState = getIt.get<AppState>();
File? _selectedImage;
@override
void initState() {
super.initState();
// Use addPostFrameCallback to ensure widget is built before loading
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
final profileVm = context.read<ProfileSettingsViewModel>();
// Only sync if AppState has data and ViewModel doesn't
if (_appState.getProfileImageData != null &&
_appState.getProfileImageData!.isNotEmpty &&
(profileVm.profileImageData == null || profileVm.profileImageData!.isEmpty)) {
profileVm.syncProfileImageFromAppState();
}
// Only load if no data exists in both AppState and ViewModel
if ((_appState.getProfileImageData == null || _appState.getProfileImageData!.isEmpty) &&
(profileVm.profileImageData == null || profileVm.profileImageData!.isEmpty)) {
_loadProfileImage();
}
});
}
void _loadProfileImage() {
// Check if profile image is already loaded in AppState
if (_appState.getProfileImageData != null && _appState.getProfileImageData!.isNotEmpty) {
// Image already loaded, no need to call API
return;
}
final profileVm = context.read<ProfileSettingsViewModel>();
final patientID = _appState.getAuthenticatedUser()?.patientId;
if (patientID != null) {
profileVm.getProfileImage(
patientID: patientID,
onSuccess: (data) {
// Image loaded successfully
},
onError: (error) {
// Error loading image
},
);
}
}
void _pickImage() {
// Show image picker options without checking permissions first
ImageOptions.showImageOptionsNew(
context,
false, // Don't show files option, only camera and gallery
(base64String, file) async {
try {
print('=== Starting image processing ===');
print('File path: ${file.path}');
print('File exists: ${await file.exists()}');
print('Original file size: ${await file.length() / 1024} KB');
// Compress and resize the image
print('Calling compressAndResizeImage...');
final compressedFile = await ImageCompressionHelper.compressAndResizeImage(file);
File finalFile;
String finalBase64;
if (compressedFile == null) {
print('⚠️ Compression failed - using original file as fallback');
// Fallback: use original image if compression fails
final originalSize = await file.length();
final maxSize = 1048576; // 1MB
if (originalSize > maxSize) {
print('❌ Original file is too large: ${originalSize / 1024} KB');
if (mounted) {
Utils.showToast(
LocaleKeys.imageSizeTooLarge.tr(context: context),
);
}
return;
}
print('✅ Using original file (${originalSize / 1024} KB)');
finalFile = file;
var bytes = await file.readAsBytes();
finalBase64 = base64.encode(bytes);
} else {
// Check compressed file size
final fileSize = await compressedFile.length();
final maxSize = 1048576; // 1MB
print('✅ Compression successful: ${fileSize / 1024} KB');
if (fileSize > maxSize) {
print('❌ Compressed file still too large');
if (mounted) {
Utils.showToast(
LocaleKeys.imageSizeTooLarge.tr(context: context),
);
}
return;
}
finalFile = compressedFile;
var bytes = await compressedFile.readAsBytes();
finalBase64 = base64.encode(bytes);
}
print('Converting to base64... Length: ${finalBase64.length}');
if (mounted) {
setState(() {
_selectedImage = finalFile;
});
print('📤 Starting upload...');
// Upload the image
_uploadImage(finalBase64);
}
print('=== Image processing complete ===');
} catch (e, stackTrace) {
print('❌ Error in _pickImage: $e');
print('Stack trace: $stackTrace');
if (mounted) {
Utils.showToast(
LocaleKeys.failedToProcessImage.tr(context: context),
);
}
}
},
checkCameraPermission: _checkCameraPermission,
checkGalleryPermission: _checkGalleryPermission,
);
}
Future<bool> _checkCameraPermission() async {
try {
print('=== Checking camera permission ===');
// First check current status
PermissionStatus currentStatus = await Permission.camera.status;
print('Current camera permission status: $currentStatus');
// If already granted, return true
if (currentStatus.isGranted) {
print('✅ Camera permission already granted');
return true;
}
// If denied or permanently denied, show settings dialog
if (currentStatus.isDenied || currentStatus.isPermanentlyDenied) {
// Request permission first
PermissionStatus newStatus = await Permission.camera.request();
print('Camera permission after request: $newStatus');
if (newStatus.isGranted) {
print('✅ Camera permission granted');
return true;
}
// Still denied - show settings dialog
print('⚠️ Camera permission denied - showing settings dialog');
if (mounted) {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: LocaleKeys.cameraPermissionMessage.tr(context: context),
isShowActionButtons: true,
onCancelTap: () {
Navigator.pop(context);
},
onConfirmTap: () async {
openAppSettings();
},
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
return false;
}
// Request permission for the first time
PermissionStatus newStatus = await Permission.camera.request();
print('Camera permission after request: $newStatus');
if (newStatus.isGranted) {
print('✅ Camera permission granted');
return true;
}
// Denied - show settings dialog
print('❌ Camera permission denied - showing settings dialog');
if (mounted) {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: LocaleKeys.cameraPermissionMessage.tr(context: context),
isShowActionButtons: true,
onCancelTap: () {
Navigator.pop(context);
},
onConfirmTap: () async {
openAppSettings();
},
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
return false;
} catch (e) {
print('❌ Error checking camera permission: $e');
if (mounted) {
Utils.showToast(
LocaleKeys.failedToCheckPermissions.tr(context: context),
);
}
return false;
}
}
Future<bool> _checkGalleryPermission() async {
try {
print('=== Checking gallery permission ===');
// Determine which permission to check based on platform and Android version
Permission galleryPermission;
if (Platform.isIOS) {
galleryPermission = Permission.photos;
} else {
// Android: use photos permission which handles API level differences automatically
galleryPermission = Permission.photos;
}
// First check current status
PermissionStatus currentStatus = await galleryPermission.status;
print('Current gallery permission status: $currentStatus');
// If already granted, return true
if (currentStatus.isGranted || currentStatus.isLimited) {
print('✅ Gallery permission already granted');
return true;
}
// If denied or permanently denied, request permission first
if (currentStatus.isDenied || currentStatus.isPermanentlyDenied) {
// Request permission first
PermissionStatus newStatus = await galleryPermission.request();
print('Gallery permission after request: $newStatus');
if (newStatus.isGranted || newStatus.isLimited) {
print('✅ Gallery permission granted');
return true;
}
// Still denied - show settings dialog
print('⚠️ Gallery permission denied - showing settings dialog');
if (mounted) {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: LocaleKeys.galleryPermissionMessage.tr(context: context),
isShowActionButtons: true,
onCancelTap: () {
Navigator.pop(context);
},
onConfirmTap: () async {
openAppSettings();
},
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
return false;
}
// Request permission for the first time
PermissionStatus newStatus = await galleryPermission.request();
print('Gallery permission after request: $newStatus');
if (newStatus.isGranted || newStatus.isLimited) {
print('✅ Gallery permission granted');
return true;
}
// Denied - show settings dialog
print('❌ Gallery permission denied - showing settings dialog');
if (mounted) {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: LocaleKeys.galleryPermissionMessage.tr(context: context),
isShowActionButtons: true,
onCancelTap: () {
Navigator.pop(context);
},
onConfirmTap: () async {
openAppSettings();
},
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
return false;
} catch (e) {
print('❌ Error checking gallery permission: $e');
if (mounted) {
Utils.showToast(
LocaleKeys.failedToCheckPermissions.tr(context: context),
);
}
return false;
}
}
void _uploadImage(String base64String) {
final profileVm = context.read<ProfileSettingsViewModel>();
final patientID = _appState.getAuthenticatedUser()?.patientId;
if (patientID != null) {
profileVm.uploadProfileImage(
patientID: patientID,
imageData: base64String,
onSuccess: (data) {
if (mounted) {
setState(() {
_selectedImage = null; // Clear selected image after successful upload
});
print(
LocaleKeys.profileImageUpdatedSuccessfully.tr(context: context),
);
}
},
onError: (error) {
Utils.showToast(error);
},
);
}
}
Widget _buildProfileImage(ProfileSettingsViewModel profileVm) {
final gender = _appState.getAuthenticatedUser()?.gender ?? 1;
final age = _appState.getAuthenticatedUser()?.age ?? 0;
// Determine the default image based on gender and age
final String defaultImage;
if (gender == 1) {
// Male
defaultImage = age < 7 ? AppAssets.babyBoyImg : AppAssets.maleImg;
} else {
// Female
defaultImage = age < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg;
}
// Show selected image if available
if (_selectedImage != null) {
return ClipOval(
child: Image.file(
_selectedImage!,
width: 136.w,
height: 136.h,
fit: BoxFit.cover,
),
);
}
// Check both ViewModel and AppState for profile image
final String? imageData = profileVm.profileImageData ?? _appState.getProfileImageData;
// Show uploaded image if available
if (imageData != null && imageData.isNotEmpty) {
try {
final bytes = base64Decode(imageData);
return ClipOval(
child: Image.memory(
bytes,
width: 136.w,
height: 136.h,
fit: BoxFit.cover,
),
);
} catch (e) {
print('Error decoding profile image: $e');
// If decoding fails, show default image
return Image.asset(
defaultImage,
width: 136.w,
height: 136.h,
);
}
}
// Show default image
return Image.asset(
defaultImage,
width: 136.w,
height: 136.h,
);
}
@override
Widget build(BuildContext context) {
return Consumer<ProfileSettingsViewModel>(
builder: (context, profileVm, _) {
return Center(
child: Stack(
children: [
// Profile Image
profileVm.isProfileImageLoading
? Container(
width: 136.w,
height: 136.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.greyTextColor.withValues(alpha: 0.2),
),
).toShimmer2(isShow: true)
: _buildProfileImage(profileVm),
// Edit button
Positioned(
right: 0,
top: 0,
child: GestureDetector(
onTap: () {
if (!profileVm.isUploadingProfileImage) {
_pickImage();
}
},
child: Container(
width: 32.w,
height: 32.h,
decoration: BoxDecoration(
color: AppColors.primaryRedColor,
shape: BoxShape.circle,
border: Border.all(
color: AppColors.whiteColor,
width: 2.w,
),
),
child: profileVm.isUploadingProfileImage
? SizedBox(
width: 16.w,
height: 16.h,
child: CircularProgressIndicator(
strokeWidth: 2.w,
valueColor: AlwaysStoppedAnimation<Color>(
AppColors.whiteColor,
),
).paddingAll(8.w),
)
: Icon(
Icons.camera_alt,
color: AppColors.whiteColor,
size: 16.w,
),
),
),
),
],
).paddingOnly(bottom: 24.h),
);
},
);
}
}

@ -9,68 +9,71 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class AttachmentOptions extends StatelessWidget {
VoidCallback onCameraTap;
VoidCallback onGalleryTap;
VoidCallback onFilesTap;
bool showFilesOption;
final Future<void> Function() onCameraTap;
final Future<void> Function() onGalleryTap;
final VoidCallback onFilesTap;
final bool showFilesOption;
AttachmentOptions({Key? key, required this.onCameraTap, required this.onGalleryTap, required this.onFilesTap, this.showFilesOption = true}) : super(key: key);
late AppState? appState;
const AttachmentOptions({
Key? key,
required this.onCameraTap,
required this.onGalleryTap,
required this.onFilesTap,
this.showFilesOption = true
}) : super(key: key);
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
return SizedBox(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// "Upload Attachment".toSectionHeading(),
// "Select from gallery or open camera".toText11(isBold: true),
checkInOptionCard(
AppAssets.ask_doctor_icon,
LocaleKeys.openCamera.tr(context: context),
"",
).onPress(() {
onCameraTap();
}),
SizedBox(height: 16.h),
checkInOptionCard(
AppAssets.ask_doctor_icon,
LocaleKeys.openGallery.tr(context: context),
"",
).onPress(() {
onGalleryTap();
}),
SizedBox(height: 16.h),
final appState = getIt.get<AppState>();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
checkInOptionCard(
appState,
AppAssets.open_camera,
LocaleKeys.openCamera.tr(context: context),
LocaleKeys.takePhotoWithCamera.tr(context: context),
).onPress(() async {
await onCameraTap();
}),
SizedBox(height: 16.h),
checkInOptionCard(
appState,
AppAssets.gallery,
LocaleKeys.openGallery.tr(context: context),
LocaleKeys.selectFromPhotoLibrary.tr(context: context),
).onPress(() async {
await onGalleryTap();
}),
if (showFilesOption) SizedBox(height: 16.h),
if (showFilesOption)
checkInOptionCard(
AppAssets.ask_doctor_icon,
appState,
AppAssets.files,
LocaleKeys.openFiles.tr(context: context),
"",
).onPress(() {
LocaleKeys.selectDocumentFiles.tr(context: context),
).onPress(() async {
onFilesTap();
}),
],
).paddingOnly(),
],
);
}
Widget checkInOptionCard(String icon, String title, String subTitle) {
Widget checkInOptionCard(AppState appState, String icon, String title, String subTitle) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.r,
borderRadius: 20.h,
hasShadow: false,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Utils.buildSvgWithAssets(icon: icon, width: 24.h, height: 24.h, fit: BoxFit.fill, iconColor: AppColors.textColor),
// SizedBox(height: 16.h),
Utils.buildSvgWithAssets(icon: icon, width: 40.h, height: 40.h, fit: BoxFit.fill),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -79,12 +82,12 @@ class AttachmentOptions extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title.toText16(isBold: true, color: AppColors.textColor),
// subTitle.toText12(isBold: true, color: AppColors.greyTextColor),
subTitle.toText12(isBold: true, color: AppColors.greyTextColor),
],
),
),
Transform.flip(
flipX: appState!.isArabic(),
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
@ -99,25 +102,4 @@ class AttachmentOptions extends StatelessWidget {
).paddingAll(16.h),
);
}
Widget itemView(String icon, String title, VoidCallback onTap) {
return InkWell(
onTap: onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SvgPicture.asset(
"assets/images/svg/$icon",
),
title.toText11(isBold: true),
],
).paddingOnly(left: 13, right: 13, top: 16, bottom: 12).expanded.objectContainerBorderView(
disablePadding: true,
radius: 10,
color: AppColors.greyF7Color.withOpacity(.48),
borderColor: AppColors.lightGreyEFColor.withOpacity(.48),
),
);
}
}

@ -6,7 +6,6 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/attachment_options.dart';
import 'package:hmg_patient_app_new/widgets/bottom_sheet.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
@ -15,37 +14,64 @@ import 'package:image_picker/image_picker.dart';
final ImagePicker picker = ImagePicker();
class ImageOptions {
static void showImageOptionsNew(BuildContext context, bool showFilesOption, Function(String, File) image) {
static void showImageOptionsNew(
BuildContext context,
bool showFilesOption,
Function(String, File) image, {
Future<bool> Function()? checkCameraPermission,
Future<bool> Function()? checkGalleryPermission,
}) {
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.feedback.tr(),
title: LocaleKeys.profilePicture.tr(context: context),
child: AttachmentOptions(
showFilesOption: showFilesOption,
onCameraTap: () async {
// Check camera permission if callback provided
if (checkCameraPermission != null) {
final hasPermission = await checkCameraPermission();
if (!hasPermission) {
return; // Permission denied, don't proceed
}
}
Navigator.pop(context); // Close bottom sheet
if (Platform.isAndroid) {
cameraImageAndroid(image);
} else {
File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.camera, imageQuality: 20))?.path ?? "");
// XFile? media = await picker.pickMedia();
String? fileName = _image.path;
var bytes = File(fileName!).readAsBytesSync();
XFile? pickedFile = await picker.pickImage(source: ImageSource.camera, imageQuality: 20);
if (pickedFile == null) return;
File _image = File(pickedFile.path);
String fileName = _image.path;
var bytes = File(fileName).readAsBytesSync();
String base64Encode = base64.encode(bytes);
if (base64Encode != null) {
image(base64Encode, _image);
}
image(base64Encode, _image);
}
},
onGalleryTap: () async {
// Check gallery permission if callback provided
if (checkGalleryPermission != null) {
final hasPermission = await checkGalleryPermission();
if (!hasPermission) {
return; // Permission denied, don't proceed
}
}
Navigator.pop(context); // Close bottom sheet
if (Platform.isAndroid) {
galleryImageAndroid(image);
} else {
File _image = File((await picker.pickMedia())?.path ?? "");
XFile? pickedFile = await picker.pickImage(source: ImageSource.gallery, imageQuality: 20);
if (pickedFile == null) return;
File _image = File(pickedFile.path);
String fileName = _image.path;
var bytes = File(fileName).readAsBytesSync();
String base64Encode = base64.encode(bytes);
if (base64Encode != null) {
image(base64Encode, _image);
}
image(base64Encode, _image);
}
},
onFilesTap: () async {
@ -188,25 +214,25 @@ class ImageOptions {
}
void galleryImageAndroid(Function(String, File) image) async {
File _image = File((await picker.pickMedia())?.path ?? "");
XFile? pickedFile = await picker.pickImage(source: ImageSource.gallery, imageQuality: 20);
if (pickedFile == null) return;
File _image = File(pickedFile.path);
String fileName = _image.path;
var bytes = File(fileName).readAsBytesSync();
String base64Encode = base64.encode(bytes);
if (base64Encode != null) {
image(base64Encode, _image);
}
image(base64Encode, _image);
}
void cameraImageAndroid(Function(String, File) image) async {
File _image = File((await picker.pickMedia())?.path ?? "");
XFile? pickedFile = await picker.pickImage(source: ImageSource.camera, imageQuality: 20);
if (pickedFile == null) return;
File _image = File(pickedFile.path);
String fileName = _image.path;
var bytes = File(fileName).readAsBytesSync();
String base64Encode = base64.encode(bytes);
if (base64Encode != null) {
image(base64Encode, _image);
}
image(base64Encode, _image);
}
class _BottomSheet extends StatelessWidget {

@ -0,0 +1,94 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
class UserAvatarWidget extends StatelessWidget {
final double width;
final double height;
final int? gender;
final int? age;
final BoxFit? fit;
final bool isCircular;
final double? borderRadius;
final String? customProfileImageData; // For family members or other users
const UserAvatarWidget({
Key? key,
required this.width,
required this.height,
this.gender,
this.age,
this.fit,
this.isCircular = false,
this.borderRadius,
this.customProfileImageData,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final appState = getIt.get<AppState>();
final userGender = gender ?? appState.getAuthenticatedUser()?.gender ?? 1;
final userAge = age ?? appState.getAuthenticatedUser()?.age ?? 0;
// Use custom profile image data if provided, otherwise use AppState data
final profileImageData = customProfileImageData ?? appState.getProfileImageData;
// Determine the default image based on gender and age
final String defaultImage;
if (userGender == 1) {
// Male
defaultImage = userAge < 7 ? AppAssets.babyBoyImg : AppAssets.maleImg;
} else {
// Female
defaultImage = userAge < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg;
}
// Calculate border radius - default to 12.r if not specified and not circular
final effectiveBorderRadius = isCircular ? width / 2 : (borderRadius ?? 12.r);
// Show uploaded profile image if available
if (profileImageData != null && profileImageData.isNotEmpty) {
try {
final bytes = base64Decode(profileImageData);
final imageWidget = Image.memory(
bytes,
width: width,
height: height,
fit: fit ?? BoxFit.cover,
);
return ClipRRect(
borderRadius: BorderRadius.circular(effectiveBorderRadius),
child: imageWidget,
);
} catch (e) {
// If decoding fails, show default image
return ClipRRect(
borderRadius: BorderRadius.circular(effectiveBorderRadius),
child: Image.asset(
defaultImage,
width: width,
height: height,
fit: fit,
),
);
}
}
// Show default image with rounded corners
return ClipRRect(
borderRadius: BorderRadius.circular(effectiveBorderRadius),
child: Image.asset(
defaultImage,
width: width,
height: height,
fit: fit,
),
);
}
}

@ -97,6 +97,7 @@ dependencies:
intl: ^0.20.2
flutter_widget_from_html: ^0.17.1
huawei_map: ^6.12.0+301
flutter_image_compress: ^2.3.0
scrollable_positioned_list: ^0.3.8

Loading…
Cancel
Save