diff --git a/AuthKey_JG87SJ23A4.p8 b/AuthKey_JG87SJ23A4.p8 new file mode 100644 index 00000000..6a3c2a53 --- /dev/null +++ b/AuthKey_JG87SJ23A4.p8 @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgaPetsOq3tqUlswBN +BFUWIIdEg4dcTCGRUKbAEx8ViFygCgYIKoZIzj0DAQehRANCAAS6IKkZ4tCeBlN6 +DtXiBekCiIbgHxWk8AWppP0H3mJ+I2s8cIEaquL4/yeLDcHzAHf8tHcYhF/TvggT +nc30yDLv +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/android/app/libs/PenNavUI.aar b/android/app/libs/PenNavUI.aar index 7832df8c..a5658605 100644 Binary files a/android/app/libs/PenNavUI.aar and b/android/app/libs/PenNavUI.aar differ diff --git a/android/app/libs/PenguinRenderer.aar b/android/app/libs/PenguinRenderer.aar index 2926e9ad..b498726f 100644 Binary files a/android/app/libs/PenguinRenderer.aar and b/android/app/libs/PenguinRenderer.aar differ diff --git a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt index 6c7306d8..3925ca56 100644 --- a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt +++ b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt @@ -320,6 +320,7 @@ internal class PenguinView( if(clinicId.isEmpty()) return navigateTo(clinicId) +// navigateTo("3-1") } /** diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 3df60413..50b0c0c3 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1496,10 +1496,6 @@ "someRemarksAboutPrescription": "ستجدون هنا بعض الملاحظات حول الوصفة الطبية", "notifyMeBeforeConsumptionTime": "أبلغني قبل وقت الاستهلاك", "noMedicationsToday": "لا أدوية اليوم", - "route": "Route: {route}", - "frequency": "Frequency: {frequency}", - "instruction": "Instruction: {instruction}", - "duration": "Duration: {days}", "reminders": "تذكيرات", "reminderAddedToCalendar": "تمت إضافة تذكير إلى التقويم ✅", "errorWhileSettingCalendar": "حدث خطأ أثناء ضبط التقويم:{error}", @@ -1526,8 +1522,6 @@ "trackerAndOthers": "المتتبع وأخرى", "bmi": "مؤشر كتلة الجسم", "yesPleaseINeedHelp": "نعم، من فضلك أريد المساعدة", - "noThanksIKnowTheClinic": "لا شكراً، أعرف العيادة", - "unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة", "aiOverView": "AI ملخص", "generateAiAnalysis": "قم بإجراء تحليل لهذا الطلب AI", "aiAnalysis": "AI تحليل", @@ -1569,5 +1563,13 @@ - + "confirmYourLocation": "قم بتأكيد موقعك", + "confirmYourLocationDesc": "يرجى تأكيد موقعك الحالي لعرض الاتجاهات المناسبة.", + "insideHospital": "أنا داخل المستشفى", + "outsideHospital": "أنا خارج المستشفى", + "servicePriceList": "قائمة أسعار الخدمات", + "servicePriceListDesc": "توضح قائمة أسعار الخدمات أدناه رسوم الخدمات الصحية المقدمة للمرضى بنظام الدفع النقدي. أما فيما يتعلق بالخدمات المشمولة بالتأمين، فسيتم تطبيق التغطية التأمينية والتحقق من الأهلية واحتساب نسب التحمل وفقًا لشروط وثيقة التأمين وجدول المنافع المعتمد لكل شركة تأمين.", + "servicePriceListRights": "يحق للمريض الحصول على متابعة مجانية في غضون 14 يومًا من الزيارة الأولى", + "serviceName": "اسم الخدمة", + "noFreeSlot": "لا توجد مواعيد متاحة في التاريخ المحدد. يرجى اختيار تاريخ آخر أو إعادة المحاولة لاحقاً." } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 77998074..65b08f26 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -618,7 +618,7 @@ "general": "General", "profile": "ملفي", "notifications": "إشعارات", - "notificationDetails": "تفاصيل الاشعار", + "notificationDetails": "Notification Details", "notificationDetailsa": "تفاصيل الاشعار", "infoMyDoctorPoints": "", "infoMyDoctor": "This service allows you to see all the doctors you have visited in Al Habib Medical Group, and through this service:", @@ -1475,7 +1475,6 @@ "remindersSet": "Reminders Set!", "dailyWaterRemindersScheduledAt": "Daily water reminders scheduled at:", "waterConsumption": "Water Consumption", - "selectActivityLevel": "Select Activity Level", "selectNumberOfReminders": "Select Number of Reminders", "h2oSettings": "H20 Settings", "settingsSavedSuccessfully": "Settings saved successfully", @@ -1487,10 +1486,8 @@ "someRemarksAboutPrescription": "some remarks about the prescription will be here", "notifyMeBeforeConsumptionTime": "Notify me before the consumption time", "noMedicationsToday": "No medications today", - "route": "Route: {route}", - "frequency": "Frequency: {frequency}", - "instruction": "Instruction: {instruction}", - "duration": "Duration: {days}", + "route": "Route", + "frequency": "Frequency", "reminders": "Reminders", "reminderAddedToCalendar": "Reminder added to calendar ✅", "errorWhileSettingCalendar": "Error while setting calendar: {error}", @@ -1517,6 +1514,15 @@ "trackerAndOthers": "Tracker & Others", "bmi": "BMI", "yesPleaseINeedHelp": "Yes, Please I need help", + "confirmYourLocation": "Confirm Your Location", + "confirmYourLocationDesc": "Please confirm your present location to view appropriate directions.", + "insideHospital": "I am inside the hospital", + "outsideHospital": "I am outside the hospital", + "servicePriceList": "Services Price List", + "servicePriceListDesc": "Below is the services price list outline the healthcare services fees for cash payments, where the insurance coverage, eligibility, and co-payment deductions will be processed in accordance with the insurance policy terms and the table of benefits of each insurance providers:", + "servicePriceListRights": "The patient has the right to a free follow-up within 14 days of initial visit", + "serviceName": "Service Name", + "noFreeSlot": "No free slot available for the selected date. Please select another date or check back later." "noThanksIKnowTheClinic": "No, Thanks. I Know the clinic", "unableToSendOTP": "Unable to send Verification OTP", "aiOverView": "AI Overview", diff --git a/icon.jpg b/icon.jpg new file mode 100644 index 00000000..65ca5c05 Binary files /dev/null and b/icon.jpg differ diff --git a/ios/GoogleService-Info.plist b/ios/GoogleService-Info.plist index 633037cb..a9b1f53a 100644 --- a/ios/GoogleService-Info.plist +++ b/ios/GoogleService-Info.plist @@ -2,10 +2,6 @@ - CLIENT_ID - 815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt ANDROID_CLIENT_ID 815750722565-m14h8mkosm7cnq6uh6rhqr54dn02d705.apps.googleusercontent.com API_KEY @@ -15,11 +11,11 @@ PLIST_VERSION 1 BUNDLE_ID - com.HMG.HMG-Smartphone + com.cloudsolutions.HMGPatientApp PROJECT_ID api-project-815750722565 STORAGE_BUCKET - api-project-815750722565.appspot.com + api-project-815750722565.firebasestorage.app IS_ADS_ENABLED IS_ANALYTICS_ENABLED @@ -31,7 +27,7 @@ IS_SIGNIN_ENABLED GOOGLE_APP_ID - 1:815750722565:ios:328ec247a81a2ca23c186c + 1:815750722565:ios:34ec03c09e02e21f3c186c DATABASE_URL https://api-project-815750722565.firebaseio.com diff --git a/ios/Podfile b/ios/Podfile index 9249e54d..4c8bed13 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -54,6 +54,8 @@ post_install do |installer| 'PERMISSION_EVENTS_FULL_ACCESS=1', ## dart: PermissionGroup.reminders 'PERMISSION_REMINDERS=1', + ## dart: PermissionGroup.notification + 'PERMISSION_NOTIFICATIONS=1', ] build_configuration.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386' build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 7a41ae2c..dd2d96ef 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -18,12 +18,12 @@ 61243B562EC5FA3700D46FA0 /* PenguinNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */; }; 61243B572EC5FA3700D46FA0 /* PenguinViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 766D8CB62EC60BE600D05E07 /* Penguin.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */; }; - 766D8CB72EC60BE600D05E07 /* Penguin.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 766D8CB82EC60BE600D05E07 /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */; }; - 766D8CB92EC60BE600D05E07 /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 766D8CBA2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */; }; - 766D8CBB2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 765A5A8C2F35CD8B0003FF7D /* Penguin.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A802F35CD730003FF7D /* Penguin.xcframework */; }; + 765A5A8D2F35CD8B0003FF7D /* Penguin.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A802F35CD730003FF7D /* Penguin.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 765A5A8E2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */; }; + 765A5A8F2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 765A5A902F35CD8B0003FF7D /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */; }; + 765A5A912F35CD8B0003FF7D /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -41,15 +41,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + 765A5A922F35CD8B0003FF7D /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - 766D8CB72EC60BE600D05E07 /* Penguin.xcframework in Embed Frameworks */, - 766D8CBB2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Embed Frameworks */, - 766D8CB92EC60BE600D05E07 /* PenNavUI.xcframework in Embed Frameworks */, + 765A5A8F2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Embed Frameworks */, + 765A5A8D2F35CD8B0003FF7D /* Penguin.xcframework in Embed Frameworks */, + 765A5A912F35CD8B0003FF7D /* PenNavUI.xcframework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -73,9 +73,9 @@ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Penguin.xcframework; path = Frameworks/Penguin.xcframework; sourceTree = ""; }; - 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenNavUI.xcframework; path = Frameworks/PenNavUI.xcframework; sourceTree = ""; }; - 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenguinINRenderer.xcframework; path = Frameworks/PenguinINRenderer.xcframework; sourceTree = ""; }; + 765A5A802F35CD730003FF7D /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Penguin.xcframework; sourceTree = ""; }; + 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenguinINRenderer.xcframework; sourceTree = ""; }; + 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenNavUI.xcframework; sourceTree = ""; }; 769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 8E12CEEB8E334EE22D5259D7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; @@ -95,9 +95,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 766D8CB62EC60BE600D05E07 /* Penguin.xcframework in Frameworks */, - 766D8CBA2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Frameworks */, - 766D8CB82EC60BE600D05E07 /* PenNavUI.xcframework in Frameworks */, + 765A5A8C2F35CD8B0003FF7D /* Penguin.xcframework in Frameworks */, + 765A5A902F35CD8B0003FF7D /* PenNavUI.xcframework in Frameworks */, + 765A5A8E2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Frameworks */, DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -136,9 +136,9 @@ 766D8CB22EC60BE600D05E07 /* Frameworks */ = { isa = PBXGroup; children = ( - 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */, - 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */, - 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */, + 765A5A802F35CD730003FF7D /* Penguin.xcframework */, + 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */, + 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */, D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */, ); name = Frameworks; @@ -235,10 +235,10 @@ 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 8372B02399CDF54531650AD4 /* [CP] Embed Pods Frameworks */, 81DE7C26F41956799E954FCE /* [CP] Copy Pods Resources */, + 765A5A922F35CD8B0003FF7D /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); @@ -524,8 +524,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -710,8 +710,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -739,8 +739,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index 08f3422c..1eb27a20 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -109,7 +109,7 @@ "size" : "83.5x83.5" }, { - "filename" : "icon 1.jpg", + "filename" : "icon.jpg", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index ab9828e6..9c805ac5 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -13,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - Dr. Alhabib + Dr. Alhabib Beta CFBundlePackageType APPL CFBundleShortVersionString diff --git a/ios/Runner/Penguin/PenguinView.swift b/ios/Runner/Penguin/PenguinView.swift index 508fb74a..e683a35a 100644 --- a/ios/Runner/Penguin/PenguinView.swift +++ b/ios/Runner/Penguin/PenguinView.swift @@ -309,22 +309,13 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia PenNavUIManager.shared.eventsDelegate = self - - - print("====== after eventsDelegate onPenNavSuccess =========") - - PenNavUIManager.shared.navigate(to: "108") - - +// print("====== after onPenNavSuccess navigate =========") +// PenNavUIManager.shared.navigate(to: "3-1") // Present the Penguin UI on top of the Flutter view controller - + print("====== after onPenNavSuccess present =========") PenNavUIManager.shared.present(root: controller, view: _view) - - - - print("====== after present onPenNavSuccess =========") print(model?.clinicID) @@ -347,11 +338,21 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia let clientID = self.model?.clientID, !clientID.isEmpty else { - print("Error: Config Client ID is nil or empty") + print("Error: Config Clinic ID is nil or empty") return } + + print("====== after onPenNavSuccess navigate =========") +// PenNavUIManager.shared.navigate(to: "3-1") { success, error in + PenNavUIManager.shared.navigate(to: clinicID) { success, error in + if success { + print("Nav Success") + } else { + print("Nav Error") + } + } @@ -373,20 +374,20 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia - guard let token = token else { - - print("Token is nil") - - return - - } - - print("Token Generated") - - print(token); +// guard let token = token else { +// +// print("Token is nil") +// +// return +// +// } +// +// print("Token Generated") +// +// print(token); - +// // self?.handleNavigation(clinicID: clinicID, token: token) { success, errorMessage in // // if success { @@ -425,7 +426,8 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia PenNavUIManager.shared.setToken(token: token) - PenNavUIManager.shared.navigate(to: clinicID) +// PenNavUIManager.shared.navigate(to: clinicID) + PenNavUIManager.shared.navigate(to: "3-1") completion(true,nil) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index ba8f627a..ea8062d0 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -182,14 +182,15 @@ class ApiClientImp implements ApiClient { } // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 1018977; + // body['PatientID'] = 4768663; // body['PatientTypeID'] = 1; - // // body['PatientOutSA'] = 0; // body['SessionID'] = "45786230487560q"; } - // body['IsNewFlutterApp'] = true; + if(!url.contains("/paymentApi")) { + body['IsNewFlutterApp'] = true; + } body.removeWhere((key, value) => value == null); } @@ -208,10 +209,10 @@ class ApiClientImp implements ApiClient { // Handle body encoding based on isBodyPlainText flag final dynamic requestBody = isBodyPlainText ? body : json.encode(body); + debugPrint("uri: ${Uri.parse(url.trim())}"); + debugPrint("body: ${json.encode(body)}", wrapWidth: 2048); final response = await http.post(Uri.parse(url.trim()), body: requestBody, headers: headers); final int statusCode = response.statusCode; - log("uri: ${Uri.parse(url.trim())}"); - log("body: ${json.encode(body)}"); // log("response.body: ${response.body}"); if (statusCode < 200 || statusCode >= 400) { onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index e71629d4..c08cdda9 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -83,6 +83,7 @@ var PRESCRIPTIONS = 'Services/Patients.svc/REST/GetPrescriptionApptList_Async'; var GET_PRESCRIPTIONS_ALL_ORDERS = 'Services/Patients.svc/REST/PatientER_GetPatientAllPresOrders'; var GET_PRESCRIPTION_REPORT = 'Services/Patients.svc/REST/INP_GetPrescriptionReport'; var SEND_PRESCRIPTION_EMAIL = 'Services/Notifications.svc/REST/SendPrescriptionEmail'; +var SEND_PRESCRIPTION_EMAIL_NEW = 'Services/Notifications.svc/REST/SendPrescriptionEmail_New'; var GET_PRESCRIPTION_REPORT_ENH = 'Services/Patients.svc/REST/GetPrescriptionReport_enh'; ///Lab Order @@ -379,6 +380,8 @@ var GET_PATIENT_SICK_LEAVE = 'Services/Patients.svc/REST/GetPatientSickLeave'; var GET_PATIENT_SICK_LEAVE_STATUS = 'Services/Patients.svc/REST/GetPatientSickLeave_Status'; +var GET_SERVICES_PRICE_LIST = 'Services/OUTPs.svc/REST/GetServicesPriceList'; + var SendSickLeaveEmail = 'Services/Notifications.svc/REST/SendSickLeaveEmail'; var GET_PATIENT_AdVANCE_BALANCE_AMOUNT = 'Services/Patients.svc/REST/GetPatientAdvanceBalanceAmount'; @@ -680,7 +683,7 @@ const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard'; class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT @@ -905,7 +908,7 @@ class ApiConsts { static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; // ************ static values for Api **************** - static final double appVersionID = 50.3; + static final double appVersionID = 19.7; static final int appChannelId = 3; static final String appIpAddress = "10.20.10.20"; static final String appGeneralId = "Cs2020@2016\$2958"; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 0560ff9e..e7ac0515 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -54,6 +54,8 @@ import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_v import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_repo.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; @@ -169,6 +171,7 @@ class AppDependencies { getIt.registerLazySingleton(() => QrParkingRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => NotificationsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => AskDoctorRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => ServicesPriceListRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -311,5 +314,6 @@ class AppDependencies { getIt.registerLazySingleton(() => AskDoctorViewModel(askDoctorRepo: getIt(), errorHandlerService: getIt())); + getIt.registerLazySingleton(() => ServicesPriceListViewModel(servicesPriceListRepo: getIt(), errorHandlerService: getIt())); } } diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index 746d2a7a..2b134735 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -110,7 +110,7 @@ class DateUtil { } static String formatDateToTime(DateTime date) { - return DateFormat('hh:mm a').format(date); + return DateFormat('hh:mm a', "en-US").format(date); } static String yearMonthDay(DateTime dateTime) { @@ -487,7 +487,9 @@ class DateUtil { } static String getFormattedDate(DateTime dateTime, String formattedString) { - return DateFormat(formattedString).format(dateTime); + String formattedDate = DateFormat(formattedString, "en-US").format(dateTime); + print(formattedDate); + return formattedDate; } static convertISODateToJsonDate(String isoDate) { diff --git a/lib/core/utils/penguin_method_channel.dart b/lib/core/utils/penguin_method_channel.dart index 1f190376..69ef4be7 100644 --- a/lib/core/utils/penguin_method_channel.dart +++ b/lib/core/utils/penguin_method_channel.dart @@ -7,7 +7,7 @@ class PenguinMethodChannel { return await rootBundle.load("assets/images/progress-loading-red-crop-1.gif").then((data) => data.buffer.asUint8List()); } - Future launch(String storyboardName, String languageCode, String username, {NavigationClinicDetails? details}) async { + Future launch(String storyboardName, String languageCode, String username, bool isClinicNavigation, {NavigationClinicDetails? details}) async { // Uint8List image = await loadGif(); try { await _channel.invokeMethod('launchPenguin', { @@ -31,7 +31,7 @@ class PenguinMethodChannel { "isEnableReportIssue": true, "languageCode": languageCode, "mapBoxKey": "pk.eyJ1IjoicndhaWQiLCJhIjoiY2x5cGo4aHNjMGNsbTJyc2djempobGQxaSJ9.RCaC6WrUt4A4YnZNfxnONQ", - "clinicID": details?.clinicId ?? "", + "clinicID": isClinicNavigation ? "${details?.projectId}-${details?.clinicId}" : details?.clinicId ?? "", // "clinicID": "108", // 46 ,49, 133 "patientID": details?.patientId ?? "", "projectID": int.parse(details?.projectId ?? "-1"), diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart index 88e8cc84..aef18f73 100644 --- a/lib/core/utils/push_notification_handler.dart +++ b/lib/core/utils/push_notification_handler.dart @@ -372,6 +372,14 @@ class PushNotificationHandler { } }); + if (Platform.isIOS) { + FirebaseMessaging.instance.getAPNSToken().then((String? token) { + print("Push Notification getAPNSToken: ${token!}"); + }).catchError((err) { + print("Push Notification getAPNSToken ERR: ${err.toString()}"); + }); + } + FirebaseMessaging.instance.getToken().then((String? token) { print("Push Notification getToken: ${token!}"); onToken(token!); diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index e57039cb..dd81a13c 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -210,8 +210,8 @@ class RequestUtils { List names = fullName != null ? fullName.split(" ") : []; var dob = appState.getUserRegistrationPayload.dob; - final DateFormat dateFormat1 = DateFormat('MM/dd/yyyy'); - final DateFormat dateFormat2 = DateFormat('dd/MM/yyyy'); + final DateFormat dateFormat1 = DateFormat('MM/dd/yyyy', "en-US"); + final DateFormat dateFormat2 = DateFormat('dd/MM/yyyy', "en-US"); DateTime gregorianDate = dateFormat2.parse(dob!); HijriGregDate hijriDate = HijriGregConverter.gregorianToHijri(gregorianDate); String? date = "${hijriDate.day}/${hijriDate.month}/${hijriDate.year}"; diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 10d37675..60925bca 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -128,7 +128,7 @@ class Utils { static String convertStringToDateTime(String dateTimeString) { String timeString = dateTimeString; // Parse the time string using DateFormat - DateFormat format = DateFormat.Hms(); // 'Hms' = 'HH:mm:ss' + DateFormat format = DateFormat.Hms(["en-US"]); // 'Hms' = 'HH:mm:ss' DateTime time = format.parse(timeString); DateTime now = DateTime.now(); @@ -977,4 +977,27 @@ class Utils { return checkDate == today; } + + static bool isClinicAllowedForRebook(int clinicID) { + bool isAllowed = true; + + if (clinicID == 23 || clinicID == 47 || clinicID == 265 || clinicID == 253 || clinicID == 134) { + isAllowed = false; + } + + return isAllowed; + } + + static String toEnglishNumbers(String input) { + const english = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + const arabic = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩']; + const persian = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; + + String result = input; + for (int i = 0; i < 10; i++) { + result = result.replaceAll(arabic[i], english[i]); + result = result.replaceAll(persian[i], english[i]); + } + return result; + } } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 1df051ea..6a009428 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -23,8 +23,7 @@ extension CapExtension on String { extension EmailValidator on String { Widget get toWidget => Text(this); - Widget toText8({Color? color, FontWeight? fontWeight, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => - Text( + Widget toText8({Color? color, FontWeight? fontWeight, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( this, maxLines: maxlines, overflow: textOverflow, @@ -38,7 +37,8 @@ extension EmailValidator on String { ); Widget toText10( - {Color? color, + {bool isEnglishOnly = false, + Color? color, FontWeight? weight, bool isBold = false, bool isUnderLine = false, @@ -59,18 +59,36 @@ extension EmailValidator on String { color: color ?? AppColors.blackColor, letterSpacing: letterSpacing, decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', decorationColor: color ?? AppColors.blackColor), ); - Widget toText11( + Widget toText9( {Color? color, FontWeight? weight, + bool isBold = false, bool isUnderLine = false, bool isCenter = false, - bool isBold = false, - int maxLine = 0, + int? maxlines, + FontStyle? fontStyle, + TextOverflow? textOverflow, double letterSpacing = 0}) => Text( + this, + textAlign: isCenter ? TextAlign.center : null, + maxLines: maxlines, + overflow: textOverflow, + style: TextStyle( + fontSize: 9.f, + fontStyle: fontStyle ?? FontStyle.normal, + fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), + color: color ?? AppColors.blackColor, + letterSpacing: letterSpacing, + decoration: isUnderLine ? TextDecoration.underline : null, + decorationColor: color ?? AppColors.blackColor), + ); + + Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isCenter = false, bool isBold = false, int maxLine = 0, double letterSpacing = 0}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -85,7 +103,7 @@ extension EmailValidator on String { ); Widget toText12( - {Color? color, + {bool isEnglishOnly = false, Color? color, bool isUnderLine = false, TextAlign textAlignment = TextAlign.start, bool isBold = false, @@ -106,6 +124,7 @@ extension EmailValidator on String { height: height, decorationColor: isUnderLine ? AppColors.blackColor : null, decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', ), ); @@ -151,15 +170,7 @@ extension EmailValidator on String { ), ); - Widget toText13( - {Color? color, - bool isUnderLine = false, - bool isBold = false, - bool isCenter = false, - int maxLine = 0, - FontWeight? weight, - double? letterSpacing = 0}) => - Text( + Widget toText13({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0, FontWeight? weight, double? letterSpacing = 0}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -172,6 +183,7 @@ extension EmailValidator on String { ); Widget toText14({ + bool isEnglishOnly = false, Color? color, bool isUnderLine = false, bool isBold = false, @@ -194,18 +206,11 @@ extension EmailValidator on String { height: height, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', decorationColor: color ?? AppColors.blackColor), ); - Widget toText15( - {Color? color, - bool isUnderLine = false, - bool isBold = false, - bool isCenter = false, - FontWeight? weight, - int? maxlines, - double? letterSpacing = -1}) => - Text( + Widget toText15({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines, double? letterSpacing = -1}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, @@ -217,8 +222,7 @@ extension EmailValidator on String { decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText16( - {Color? color, + Widget toText16({bool isEnglishOnly = false, Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, @@ -233,6 +237,7 @@ extension EmailValidator on String { this, maxLines: maxlines, textAlign: isCenter ? TextAlign.center : null, + // locale: Locale('en', 'US'), style: TextStyle( color: color ?? AppColors.blackColor, fontSize: 16.f, @@ -241,14 +246,14 @@ extension EmailValidator on String { overflow: textOverflow, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', decorationColor: decorationColor), ); - Widget toText17({Color? color, bool isBold = false, bool isCenter = false}) => Text( + Widget toText17({bool isEnglishOnly = false, Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), ); Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCenter = false, int? maxlines, TextOverflow? textOverflow}) => Text( @@ -256,17 +261,12 @@ extension EmailValidator on String { textAlign: isCenter ? TextAlign.center : null, this, overflow: textOverflow, - style: TextStyle( - fontSize: 18.f, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), - color: color ?? AppColors.blackColor, - letterSpacing: -0.4), + style: TextStyle(fontSize: 18.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText19({Color? color, bool isBold = false}) => Text( this, - style: TextStyle( - fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), + style: TextStyle(fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText20({ @@ -276,86 +276,51 @@ extension EmailValidator on String { }) => Text( this, - style: TextStyle( - fontSize: 20.f, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), - color: color ?? AppColors.blackColor, - letterSpacing: -0.4), + style: TextStyle(fontSize: 20.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( this, maxLines: maxlines, - style: TextStyle( - color: color ?? AppColors.blackColor, - fontSize: 21.f, - letterSpacing: -1, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), + style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 21.f, letterSpacing: -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), ); Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - height: 1, - color: color ?? AppColors.blackColor, - fontSize: 22.f, - letterSpacing: -1, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 1, color: color ?? AppColors.blackColor, fontSize: 22.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText24({Color? color, bool isBold = false, bool isCenter = false, FontWeight? fontWeight, double? letterSpacing}) => Text( this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - height: 23 / 24, - color: color ?? AppColors.blackColor, - fontSize: 24.f, - letterSpacing: letterSpacing ?? -1, - fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.normal), + height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.normal), ); Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false, FontWeight? weight, double? letterSpacing}) => Text( this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - height: height ?? 23 / 26, - color: color ?? AppColors.blackColor, - fontSize: 26.f, - letterSpacing: letterSpacing ?? -1, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), + height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.f, letterSpacing: letterSpacing ?? -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), ); - Widget toText28({Color? color, bool isBold = false, double? height, bool isCenter = false, double? letterSpacing}) => Text( + Widget toText28({bool isEnglishOnly = false, Color? color, bool isBold = false, double? height, bool isCenter = false, double? letterSpacing}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - height: height ?? 23 / 28, - color: color ?? AppColors.blackColor, - fontSize: 28.f, - letterSpacing: letterSpacing ?? -1, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: height ?? 23 / 28, color: color ?? AppColors.blackColor, fontSize: 28.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), ); - Widget toText32({FontWeight? weight, Color? color, bool isBold = false, bool isCenter = false}) => Text( + Widget toText32({bool isEnglishOnly = false, FontWeight? weight, Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - height: 32 / 32, - color: color ?? AppColors.blackColor, - fontSize: 32.f, - letterSpacing: -1, - fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal), + height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal), ); Widget toText44({Color? color, bool isBold = false}) => Text( this, - style: TextStyle( - height: 32 / 32, - color: color ?? AppColors.blackColor, - fontSize: 44.f, - letterSpacing: -1, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) { @@ -391,9 +356,7 @@ extension EmailValidator on String { } bool isValidEmail() { - return RegExp( - r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$') - .hasMatch(this); + return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this); } String toFormattedDate() { diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index e93c2465..50bd1cae 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -7,6 +7,7 @@ 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'; import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; @@ -74,6 +75,8 @@ class AuthenticationViewModel extends ChangeNotifier { dobController = TextEditingController(), nameController = TextEditingController(), emailController = TextEditingController(); + + CountryEnum selectedCountrySignup = CountryEnum.saudiArabia; MaritalStatusTypeEnum? maritalStatus; GenderTypeEnum? genderType; @@ -581,8 +584,8 @@ class AuthenticationViewModel extends ChangeNotifier { if (!_appState.getIsChildLoggedIn) { await medicalVm.getFamilyFiles(status: 0); // await medicalVm.getAllPendingRecordsByResponseId(); + // _navigationService.replaceAllRoutesAndNavigateToLanding(); _navigationService.popUntilNamed(AppRoutes.landingScreen); - // _navigationService.replaceAllRoutesAndNavigateToLanding(); } } else { if (activation.list != null && activation.list!.isNotEmpty) { @@ -606,7 +609,7 @@ class AuthenticationViewModel extends ChangeNotifier { } // _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); _appState.setAppAuthToken = activation.authenticationTokenId; - myAppointmentsVM.getActiveAppointmentsCount(); + // myAppointmentsVM.getActiveAppointmentsCount(); final request = RequestUtils.getAuthanticatedCommonRequest().toJson(); bool isUserAgreedBefore = await checkIfUserAgreedBefore(request: request); @@ -1045,6 +1048,7 @@ class AuthenticationViewModel extends ChangeNotifier { } Future getServicePrivilege() async { + ApiConsts.setBackendURLs(); final resultEither = await _authenticationRepo.getServicePrivilege(); List privilegeModelList = []; List vidaPlusProjectListModel = []; diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index cfd473ee..fc541d2e 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -30,6 +30,8 @@ abstract class BookAppointmentsRepo { Future>> getDoctorProfile(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}); + Future>> getDoctorRatingDetails(int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}); + Future>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, {bool continueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}); @@ -183,7 +185,7 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { body: mapDevice, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; - onError!(error); + // onError!(error); }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { @@ -298,6 +300,52 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { } } + @override + Future>> getDoctorRatingDetails(int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map mapDevice = { + "DoctorID": doctorId, + "PatientID": 0, + "License": true, + "IsRegistered": true, + "isDentalAllowedBackend": false, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_DOCTOR_RATING_DETAILS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + if (onError != null) { + onError(error); + } + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + if (onSuccess != null) { + onSuccess(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())); + } + } + //TODO: Implement the logic for Dental & laser clinics @override Future>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 664f2440..9b53a133 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot. import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_rating_details_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_patient_dental_plan_response_model.dart'; @@ -92,6 +93,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { late DoctorsProfileResponseModel doctorsProfileResponseModel; + bool isDoctorRatingDetailsLoading = false; + List doctorDetailsList = []; + List slotsList = []; List docFreeSlots = []; List dayEvents = []; @@ -99,6 +103,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { String selectedAppointmentDate = ""; String selectedAppointmentTime = ""; + String selectedAppointmentDisplayDate = ""; dynamic freeSlotsResponse; @@ -340,6 +345,14 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsLiveCareDoctorsListLoading(bool value) { + if (value) { + liveCareDoctorsList.clear(); + } + isDoctorsListLoading = value; + notifyListeners(); + } + setIsClinicsListLoading(bool value) { if (value) { clinicsList.clear(); @@ -373,9 +386,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } - setSelectedAppointmentDateTime(String date, String time) { + setSelectedAppointmentDateTime(String date, String time, String displayDate) { selectedAppointmentDate = date; selectedAppointmentTime = time; + selectedAppointmentDisplayDate = displayDate; notifyListeners(); } @@ -467,10 +481,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { } Future getLiveCareDoctorsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { - doctorsList.clear(); + liveCareDoctorsList.clear(); + notifyListeners(); final result = await bookAppointmentsRepo.getLiveCareDoctorsList(selectedLiveCareClinic.serviceID!, _appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, onError: onError); - result.fold( (failure) async { onError!(LocaleKeys.noDoctorFound.tr()); @@ -569,7 +583,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { - onError?.call(LocaleKeys.noDoctorFound.tr()); + onError?.call(selectedClinic.clinicID == 23 ? failure.message : LocaleKeys.noDoctorFound.tr()); }, (apiResponse) async { if (apiResponse.messageStatus == 2) { @@ -614,6 +628,46 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } + Future getDoctorRatingDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isDoctorRatingDetailsLoading = true; + doctorDetailsList.clear(); + notifyListeners(); + + final result = await bookAppointmentsRepo.getDoctorRatingDetails(selectedDoctor.doctorID ?? 0, onSuccess: onSuccess, onError: onError); + + result.fold( + (failure) async { + if (onError != null) { + onError("Failed to load doctor rating details"); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + if (onError != null) { + onError(apiResponse.errorMessage ?? "Unknown error occurred"); + } + isDoctorRatingDetailsLoading = false; + } else if (apiResponse.messageStatus == 1) { + try { + apiResponse.data['DoctorRatingDetailsList'].forEach((v) { + doctorDetailsList.add(DoctorRateDetails.fromJson(v)); + }); + isDoctorRatingDetailsLoading = false; + // doctorRatingDetails = DoctorRateDetails.fromJson(apiResponse.data); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } catch (e) { + if (onError != null) { + onError("Failed to parse rating details: ${e.toString()}"); + } + } + } + }, + ); + } + Future getDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { docFreeSlots.clear(); DateTime date; @@ -669,7 +723,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { Future getLiveCareDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { docFreeSlots.clear(); DateTime date; - final DateFormat formatter = DateFormat('HH:mm'); + final DateFormat formatter = DateFormat('HH:mm', "en-US"); final DateFormat dateFormatter = DateFormat('yyyy-MM-dd'); Map _eventsParsed; @@ -754,6 +808,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { print(failure); + onError!(failure.message); }, (apiResponse) { if (apiResponse.messageStatus == 2) { diff --git a/lib/features/book_appointments/models/resp_models/doctor_rating_details_response_model.dart b/lib/features/book_appointments/models/resp_models/doctor_rating_details_response_model.dart new file mode 100644 index 00000000..58668856 --- /dev/null +++ b/lib/features/book_appointments/models/resp_models/doctor_rating_details_response_model.dart @@ -0,0 +1,36 @@ +class DoctorRateDetails { + dynamic doctorID; + dynamic projectID; + dynamic clinicID; + dynamic rate; + dynamic patientNumber; + dynamic ratio; + + DoctorRateDetails( + {this.doctorID, + this.projectID, + this.clinicID, + this.rate, + this.patientNumber, + this.ratio}); + + DoctorRateDetails.fromJson(Map json) { + doctorID = json['DoctorID']; + projectID = json['ProjectID']; + clinicID = json['ClinicID']; + rate = json['Rate']; + patientNumber = json['PatientNumber']; + ratio = json['Ratio']; + } + + Map toJson() { + final Map data = new Map(); + data['DoctorID'] = this.doctorID; + data['ProjectID'] = this.projectID; + data['ClinicID'] = this.clinicID; + data['Rate'] = this.rate; + data['PatientNumber'] = this.patientNumber; + data['Ratio'] = this.ratio; + return data; + } +} diff --git a/lib/features/habib_wallet/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart index 52916465..2f338957 100644 --- a/lib/features/habib_wallet/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -9,6 +9,7 @@ class HabibWalletViewModel extends ChangeNotifier { bool isWalletAmountLoading = false; num habibWalletAmount = 0; num walletRechargeAmount = 0; + String notesText = ""; bool isBottomSheetContentLoading = false; @@ -60,6 +61,11 @@ class HabibWalletViewModel extends ChangeNotifier { notifyListeners(); } + setNotesText(String notes) { + notesText = notes; + notifyListeners(); + } + setDepositorDetails(String fileNum, String depositor, String mobile) { fileNumber = fileNum; depositorName = depositor; diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index daddbff4..5bf450ba 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.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/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart'; @@ -17,6 +18,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/search_e_referral_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -31,8 +33,7 @@ class HmgServicesViewModel extends ChangeNotifier { final ErrorHandlerService errorHandlerService; final NavigationService navigationService; - HmgServicesViewModel( - {required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService, required this.navigationService}); + HmgServicesViewModel({required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService, required this.navigationService}); bool isCmcOrdersLoading = false; bool isCmcServicesLoading = false; @@ -55,9 +56,11 @@ class HmgServicesViewModel extends ChangeNotifier { // Vital Sign PageView Controller PageController _vitalSignPageController = PageController(); + PageController get vitalSignPageController => _vitalSignPageController; int _vitalSignCurrentPage = 0; + int get vitalSignCurrentPage => _vitalSignCurrentPage; void setVitalSignCurrentPage(int page) { @@ -65,7 +68,6 @@ class HmgServicesViewModel extends ChangeNotifier { notifyListeners(); } - // HHC specific lists List hhcOrdersList = []; List hhcServicesList = []; @@ -82,9 +84,8 @@ class HmgServicesViewModel extends ChangeNotifier { List searchReferralList = []; List covidTestProcedureList = []; Covid19GetPaymentInfo? covidPaymentInfo; - Future getOrdersList() async {} - + Future getOrdersList() async {} // HHC multiple services selection List selectedHhcServices = []; @@ -792,22 +793,20 @@ class HmgServicesViewModel extends ChangeNotifier { }, ); } + List getQuestionsFromJson() { - final String questionsJson = '''[ { "id": 1, "questionEN": "Is the test intended for travel?", "questionAR": "هل تجري التحليل بغرض السفر؟", "ans": 2 }, { "id": 2, "questionEN": "Coming from outside KSA within last 2 weeks?", "questionAR": "هل قدمت من خارج المملكة خلال الأسبوعين الماضيين؟", "ans": 2 }, { "id": 3, "questionEN": "Do you currently have fever?", "questionAR": "هل تعاني حاليا من حرارة؟", "ans": 2 }, { "id": 4, "questionEN": "Did you have fever in last 2 weeks?", "questionAR": "هل عانيت من حرارة في الأسبوعين الماضيين؟", "ans": 2 }, { "id": 5, "questionEN": "Do you have a sore throat?", "questionAR": "هل لديك التهاب في الحلق؟", "ans": 2 }, { "id": 6, "questionEN": "Do you have a runny nose?", "questionAR": "هل لديك سيلان بالأنف؟" }, { "id": 7, "questionEN": "Do you have a cough?", "questionAR": "هل لديك سعال؟", "ans": 2 }, { "id": 8, "questionEN": "Do you have shortness of breath?", "questionAR": "هل تعاني من ضيق في التنفس؟", "ans": 2 }, { "id": 9, "questionEN": "Do you have nausea?", "questionAR": "هل تعاني من غثيان؟", "ans": 2 }, { "id": 10, "questionEN": "Do you have vomiting?", "questionAR": "هل تعاني من القيء؟", "ans": 2 }, { "id": 11, "questionEN": "Do you have a headache?", "questionAR": "هل تعاني من صداع في الرأس؟", "ans": 2 }, { "id": 12, "questionEN": "Do you have muscle pain?", "questionAR": "هل تعانين من آلام عضلية؟", "ans": 2 }, { "id": 13, "questionEN": "Do you have joint pain?", "questionAR": "هل تعاني من آلام المفاصل؟", "ans": 2 }, { "id": 14, "questionEN": "Do you have diarrhea?", "questionAR": "هل لديك اسهال؟", "ans": 2 } ]'''; + final String questionsJson = + '''[ { "id": 1, "questionEN": "Is the test intended for travel?", "questionAR": "هل تجري التحليل بغرض السفر؟", "ans": 2 }, { "id": 2, "questionEN": "Coming from outside KSA within last 2 weeks?", "questionAR": "هل قدمت من خارج المملكة خلال الأسبوعين الماضيين؟", "ans": 2 }, { "id": 3, "questionEN": "Do you currently have fever?", "questionAR": "هل تعاني حاليا من حرارة؟", "ans": 2 }, { "id": 4, "questionEN": "Did you have fever in last 2 weeks?", "questionAR": "هل عانيت من حرارة في الأسبوعين الماضيين؟", "ans": 2 }, { "id": 5, "questionEN": "Do you have a sore throat?", "questionAR": "هل لديك التهاب في الحلق؟", "ans": 2 }, { "id": 6, "questionEN": "Do you have a runny nose?", "questionAR": "هل لديك سيلان بالأنف؟" }, { "id": 7, "questionEN": "Do you have a cough?", "questionAR": "هل لديك سعال؟", "ans": 2 }, { "id": 8, "questionEN": "Do you have shortness of breath?", "questionAR": "هل تعاني من ضيق في التنفس؟", "ans": 2 }, { "id": 9, "questionEN": "Do you have nausea?", "questionAR": "هل تعاني من غثيان؟", "ans": 2 }, { "id": 10, "questionEN": "Do you have vomiting?", "questionAR": "هل تعاني من القيء؟", "ans": 2 }, { "id": 11, "questionEN": "Do you have a headache?", "questionAR": "هل تعاني من صداع في الرأس؟", "ans": 2 }, { "id": 12, "questionEN": "Do you have muscle pain?", "questionAR": "هل تعانين من آلام عضلية؟", "ans": 2 }, { "id": 13, "questionEN": "Do you have joint pain?", "questionAR": "هل تعاني من آلام المفاصل؟", "ans": 2 }, { "id": 14, "questionEN": "Do you have diarrhea?", "questionAR": "هل لديك اسهال؟", "ans": 2 } ]'''; try { final parsed = json.decode(questionsJson) as List; - return parsed - .map((e) => CovidQuestionnaireModel.fromJson(Map.from(e))) - .toList(); + return parsed.map((e) => CovidQuestionnaireModel.fromJson(Map.from(e))).toList(); } catch (_) { return []; } - } - + } Future getCovidProcedureList({ - Function(dynamic)? onSuccess, Function(String)? onError, }) async { @@ -816,14 +815,14 @@ class HmgServicesViewModel extends ChangeNotifier { final result = await hmgServicesRepo.getCovidTestProcedures(); result.fold( - (failure) async { + (failure) async { notifyListeners(); await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 1) { covidTestProcedureList = apiResponse.data ?? []; notifyListeners(); @@ -840,7 +839,6 @@ class HmgServicesViewModel extends ChangeNotifier { ); } - Future getPaymentInfo({ String? procedureID, int? projectID, @@ -852,14 +850,14 @@ class HmgServicesViewModel extends ChangeNotifier { final result = await hmgServicesRepo.getCovidPaymentInfo(procedureID!, projectID!); result.fold( - (failure) async { + (failure) async { notifyListeners(); await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 1) { covidPaymentInfo = apiResponse.data; notifyListeners(); @@ -900,6 +898,8 @@ class HmgServicesViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 1) { vitalSignList = apiResponse.data ?? []; hasVitalSignDataLoaded = true; + getIt.get().setSelectedHeight(vitalSignList.first.heightCm); + getIt.get().setSelectedWeight(vitalSignList.first.weightKg); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/hospital/hospital_selection_view_model.dart b/lib/features/hospital/hospital_selection_view_model.dart index c0cab2ad..24c7b9b0 100644 --- a/lib/features/hospital/hospital_selection_view_model.dart +++ b/lib/features/hospital/hospital_selection_view_model.dart @@ -101,6 +101,7 @@ class HospitalSelectionBottomSheetViewModel extends ChangeNotifier { NavigationClinicDetails data = NavigationClinicDetails(); data.projectId = projectID.toString(); data.clinicId = clinicID; + // data.clinicId = "1"; final bool permited = await AppPermission.askPenguinPermissions(); if (!permited) { Map statuses = await [ @@ -110,7 +111,7 @@ class HospitalSelectionBottomSheetViewModel extends ChangeNotifier { Permission.bluetoothScan, Permission.activityRecognition, ].request().whenComplete(() { - PenguinMethodChannel().launch("penguin", appState.isArabic() ? "ar" : "en", appState.getAuthenticatedUser()?.patientId?.toString()??"", details: data); + PenguinMethodChannel().launch("penguin", appState.isArabic() ? "ar" : "en", appState.getAuthenticatedUser()?.patientId?.toString() ?? "", false, details: data); }); } } diff --git a/lib/features/insurance/insurance_repo.dart b/lib/features/insurance/insurance_repo.dart index 1aeb7696..2719035b 100644 --- a/lib/features/insurance/insurance_repo.dart +++ b/lib/features/insurance/insurance_repo.dart @@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patien import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_card_history.dart'; import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_update_response_model.dart'; +import 'package:hmg_patient_app_new/features/insurance/models/resp_models/upload_insurance_card_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class InsuranceRepo { @@ -17,6 +18,14 @@ abstract class InsuranceRepo { Future>> getPatientInsuranceDetailsForUpdate({required String patientId, required String identificationNo}); Future>>> getPatientInsuranceApprovalsList(); + + Future>> updatePatientInsuranceCard({ + required int patientID, + required int patientType, + required String mobileNo, + required String patientIdentificationID, + required String insuranceCardImage, + }); } class InsuranceRepoImp implements InsuranceRepo { @@ -183,4 +192,52 @@ class InsuranceRepoImp implements InsuranceRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> updatePatientInsuranceCard({ + required int patientID, + required int patientType, + required String mobileNo, + required String patientIdentificationID, + required String insuranceCardImage, + }) async { + Map mapDevice = { + "PatientID": patientID, + "PatientType": patientType, + "MobileNo": mobileNo, + "PatientIdentificationID": patientIdentificationID, + "InsuranceCardImage": insuranceCardImage, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + UPLOAD_INSURANCE_CARD, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // final uploadResponse = UploadInsuranceCardResponseModel.fromJson(response); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: apiResponse, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart index 0bbdda20..261bda29 100644 --- a/lib/features/insurance/insurance_view_model.dart +++ b/lib/features/insurance/insurance_view_model.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patien import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_card_history.dart'; import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_update_response_model.dart'; +import 'package:hmg_patient_app_new/features/insurance/models/resp_models/upload_insurance_card_response_model.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class InsuranceViewModel extends ChangeNotifier { @@ -171,4 +172,44 @@ class InsuranceViewModel extends ChangeNotifier { }, ); } + + Future updatePatientInsuranceCard({ + required int patientID, + required int patientType, + required String mobileNo, + required String patientIdentificationID, + required String insuranceCardImage, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + final result = await insuranceRepo.updatePatientInsuranceCard( + patientID: patientID, + patientType: patientType, + mobileNo: mobileNo, + patientIdentificationID: patientIdentificationID, + insuranceCardImage: insuranceCardImage, + ); + + result.fold( + (failure) async { + notifyListeners(); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? "Error updating insurance card"); + } + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/insurance/models/resp_models/upload_insurance_card_response_model.dart b/lib/features/insurance/models/resp_models/upload_insurance_card_response_model.dart new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/lib/features/insurance/models/resp_models/upload_insurance_card_response_model.dart @@ -0,0 +1 @@ + diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 9b4e941a..04c945df 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -231,6 +231,9 @@ class LabViewModel extends ChangeNotifier { model: item)) }; uniqueTestsList = uniqueTests.toList(); + + uniqueTestsList = Utils.uniqueBy(uniqueTestsList, (p) => p.testCode); + uniqueTestsList.sort((a, b) { return a.description!.toLowerCase().compareTo(b.description!.toLowerCase()); }); diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 17cbb94e..b26a87a1 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -41,6 +41,7 @@ class MedicalFileViewModel extends ChangeNotifier { List patientSickLeavesViewList = []; bool isSickLeavesSortByClinic = true; + bool isSickLeavesDataNeedsReloading = true; List patientAllergiesList = []; @@ -163,15 +164,15 @@ class MedicalFileViewModel extends ChangeNotifier { } setIsPatientSickLeaveListLoading(bool val) { - if (val) { + if (val && isSickLeavesDataNeedsReloading) { patientSickLeaveList.clear(); patientSickLeavesByClinic.clear(); patientSickLeavesByHospital.clear(); patientSickLeavesViewList.clear(); patientSickLeavePDFBase64 = ""; isSickLeavesSortByClinic = true; + isPatientSickLeaveListLoading = val; } - isPatientSickLeaveListLoading = val; notifyListeners(); } @@ -267,6 +268,10 @@ class MedicalFileViewModel extends ChangeNotifier { } Future getPatientSickLeaveList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (!isSickLeavesDataNeedsReloading) { + return; + } + patientSickLeaveList.clear(); final result = await medicalFileRepo.getPatientSickLeavesList(); @@ -309,6 +314,7 @@ class MedicalFileViewModel extends ChangeNotifier { } } patientSickLeavesViewList = patientSickLeavesByClinic; + isSickLeavesDataNeedsReloading = false; notifyListeners(); if (onSuccess != null) { diff --git a/lib/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart b/lib/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart index 1dc1cbe7..b412a2e4 100644 --- a/lib/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart +++ b/lib/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart @@ -5,14 +5,14 @@ class PatientAppointmentShareResponseModel { int? appointmentNo; dynamic cardNumber; dynamic cardType; - int? cashPrice; - int? cashPriceTax; - int? cashPriceWithTax; + num? cashPrice; + num? cashPriceTax; + num? cashPriceWithTax; int? clinicID; String? clinicName; int? companyId; String? companyName; - int? companyShareWithTax; + num? companyShareWithTax; int? doctorID; String? doctorImageURL; String? doctorNameObj; @@ -39,7 +39,7 @@ class PatientAppointmentShareResponseModel { int? patientStatusType; num? patientTaxAmount; String? patientType; - int? paymentAmount; + num? paymentAmount; String? paymentDate; dynamic paymentMethodName; dynamic paymentReferenceNumber; @@ -57,7 +57,7 @@ class PatientAppointmentShareResponseModel { int? statusCode; dynamic statusDesc; String? subPolicyNo; - int? tax; + num? tax; int? userID; PatientAppointmentShareResponseModel({ diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index f2878037..ce2b7b91 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -220,9 +220,23 @@ class MyAppointmentsViewModel extends ChangeNotifier { } Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async { - isPatientHasQueueAppointment = false; + if (patientArrivedAppointmentsHistoryList.isNotEmpty) { + isPatientHasQueueAppointment = false; + notifyListeners(); + if (Utils.isDateToday(DateUtil.convertStringToDate(patientArrivedAppointmentsHistoryList.first.appointmentDate))) { + // getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID); + getPatientAppointmentQueueDetails(); + } + } if (!isAppointmentDataToBeLoaded) return; + debugPrint('getPatientAppointments CALLED!!!'); + + patientAppointmentsByClinic.clear(); + patientAppointmentsByHospital.clear(); + patientAppointmentsViewList.clear(); + + patientTimelineAppointmentsList.clear(); filteredAppointmentList.clear(); patientAppointmentsHistoryList.clear(); patientUpcomingAppointmentsHistoryList.clear(); @@ -276,6 +290,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { _groupAppointmentsByClinicAndHospital(); if (patientArrivedAppointmentsHistoryList.isNotEmpty) { + isPatientHasQueueAppointment = false; + notifyListeners(); if (Utils.isDateToday(DateUtil.convertStringToDate(patientArrivedAppointmentsHistoryList.first.appointmentDate))) { // getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID); getPatientAppointmentQueueDetails(); @@ -494,7 +510,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { } Future getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async { - if (!isAppointmentDataToBeLoaded) return; + // if (!isAppointmentDataToBeLoaded) return; isPatientMyDoctorsLoading = true; notifyListeners(); diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart index c6e7150b..2eb5d43e 100644 --- a/lib/features/prescriptions/prescriptions_repo.dart +++ b/lib/features/prescriptions/prescriptions_repo.dart @@ -167,29 +167,44 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { @override Future> getPrescriptionPDF( {required PatientPrescriptionsResponseModel prescriptionsResponseModel, required List prescriptionDetailsList}) async { + // Map mapDevice = { + // "AppointmentDate": prescriptionsResponseModel.appointmentDate, + // "ClinicName": prescriptionsResponseModel.clinicDescription, + // "DoctorName": prescriptionsResponseModel.doctorName, + // "ProjectID": prescriptionsResponseModel.projectID, + // "DoctorID": prescriptionsResponseModel.doctorID, + // "ClinicID": prescriptionsResponseModel.clinicID, + // "DateofBirth": Utils.appState.getAuthenticatedUser()!.dateofBirth, + // "ListPrescriptions": prescriptionDetailsList, + // "PatientIditificationNum": Utils.appState.getAuthenticatedUser()!.patientIdentificationNo, + // "PatientMobileNumber": Utils.appState.getAuthenticatedUser()!.mobileNumber, + // "PatientName": "${Utils.appState.getAuthenticatedUser()!.firstName!} ${Utils.appState.getAuthenticatedUser()!.lastName!}", + // "To": Utils.appState.getAuthenticatedUser()!.emailAddress, + // "SetupID": prescriptionsResponseModel.setupID, + // "IsDownload": true, + // "isDentalAllowedBackend": false, + // }; + Map mapDevice = { - "AppointmentDate": prescriptionsResponseModel.appointmentDate, - "ClinicName": prescriptionsResponseModel.clinicDescription, - "DoctorName": prescriptionsResponseModel.doctorName, - "ProjectID": prescriptionsResponseModel.projectID, - "DoctorID": prescriptionsResponseModel.doctorID, - "ClinicID": prescriptionsResponseModel.clinicID, - "DateofBirth": Utils.appState.getAuthenticatedUser()!.dateofBirth, - "ListPrescriptions": prescriptionDetailsList, - "PatientIditificationNum": Utils.appState.getAuthenticatedUser()!.patientIdentificationNo, - "PatientMobileNumber": Utils.appState.getAuthenticatedUser()!.mobileNumber, - "PatientName": "${Utils.appState.getAuthenticatedUser()!.firstName!} ${Utils.appState.getAuthenticatedUser()!.lastName!}", - "To": Utils.appState.getAuthenticatedUser()!.emailAddress, + "AppointmentNo": prescriptionsResponseModel.appointmentNo, "SetupID": prescriptionsResponseModel.setupID, + "EpisodeID": prescriptionsResponseModel.episodeID, + "ClinicID": prescriptionsResponseModel.clinicID, + "ProjectID": prescriptionsResponseModel.projectID, + "DischargeNo": prescriptionsResponseModel.dischargeNo, "IsDownload": true, - "isDentalAllowedBackend": false, + // "To": Utils.appState.getAuthenticatedUser()!.emailAddress, + "To": "amjad.haroon@cloudsolutions.com.sa", + "ClinicName": prescriptionsResponseModel.clinicDescription, + "DoctorName": prescriptionsResponseModel.doctorName, }; try { GenericApiModel? apiResponse; Failure? failure; await apiClient.post( - SEND_PRESCRIPTION_EMAIL, + // SEND_PRESCRIPTION_EMAIL, + SEND_PRESCRIPTION_EMAIL_NEW, body: mapDevice, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index 3f9af346..a7cd42f6 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -47,20 +47,24 @@ class PrescriptionsViewModel extends ChangeNotifier { late GeocodeResponse locationGeocodeResponse; bool isPrescriptionsDeliveryOrdersLoading = false; + + bool isPrescriptionsDataNeedsReloading = true; List prescriptionsOrderList = []; PrescriptionsViewModel({required this.prescriptionsRepo, required this.errorHandlerService, required this.navServices}); initPrescriptionsViewModel() { - patientPrescriptionOrders.clear(); - patientPrescriptionOrdersByClinic.clear(); - patientPrescriptionOrdersByHospital.clear(); - patientPrescriptionOrdersViewList.clear(); - prescriptionsOrderList.clear(); - isPrescriptionsOrdersLoading = true; + if (isPrescriptionsDataNeedsReloading) { + patientPrescriptionOrders.clear(); + patientPrescriptionOrdersByClinic.clear(); + patientPrescriptionOrdersByHospital.clear(); + patientPrescriptionOrdersViewList.clear(); + prescriptionsOrderList.clear(); + isPrescriptionsOrdersLoading = true; + getPatientPrescriptionOrders(); + } isSortByClinic = true; isPrescriptionsDeliveryOrdersLoading = true; - getPatientPrescriptionOrders(); notifyListeners(); } @@ -98,6 +102,10 @@ class PrescriptionsViewModel extends ChangeNotifier { } Future getPatientPrescriptionOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (!isPrescriptionsDataNeedsReloading) { + return; + } + final result = await prescriptionsRepo.getPatientPrescriptionOrders(patientId: "1231755"); result.fold( @@ -131,6 +139,7 @@ class PrescriptionsViewModel extends ChangeNotifier { } } patientPrescriptionOrdersViewList = patientPrescriptionOrdersByClinic; + isPrescriptionsDataNeedsReloading = false; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/services_price_list/models/resp_models/services_price_list_response_model.dart b/lib/features/services_price_list/models/resp_models/services_price_list_response_model.dart new file mode 100644 index 00000000..bb60094a --- /dev/null +++ b/lib/features/services_price_list/models/resp_models/services_price_list_response_model.dart @@ -0,0 +1,54 @@ +class ServicesPriceListResponseModel { + int? createdBy; + String? createdOn; + int? editedBy; + String? editedOn; + int? id; + bool? isEnabled; + String? nameAR; + String? nameEN; + num? price; + int? rowID; + + ServicesPriceListResponseModel({ + this.createdBy, + this.createdOn, + this.editedBy, + this.editedOn, + this.id, + this.isEnabled, + this.nameAR, + this.nameEN, + this.price, + this.rowID, + }); + + ServicesPriceListResponseModel.fromJson(Map json) { + createdBy = json['CreatedBy']; + createdOn = json['CreatedOn']; + editedBy = json['EditedBy']; + editedOn = json['EditedOn']; + id = json['ID']; + isEnabled = json['IsEnabled']; + nameAR = json['NameAR']; + nameEN = json['NameEN']; + price = json['Price']; + rowID = json['RowID']; + } + + Map toJson() { + final Map data = {}; + data['CreatedBy'] = createdBy; + data['CreatedOn'] = createdOn; + data['EditedBy'] = editedBy; + data['EditedOn'] = editedOn; + data['ID'] = id; + data['IsEnabled'] = isEnabled; + data['NameAR'] = nameAR; + data['NameEN'] = nameEN; + data['Price'] = price; + data['RowID'] = rowID; + return data; + } +} + diff --git a/lib/features/services_price_list/services_price_list_repo.dart b/lib/features/services_price_list/services_price_list_repo.dart new file mode 100644 index 00000000..4267d77f --- /dev/null +++ b/lib/features/services_price_list/services_price_list_repo.dart @@ -0,0 +1,80 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/models/resp_models/services_price_list_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class ServicesPriceListRepo { + Future>>> getServicesPriceList({ + String searchKey = "", + int pageIndex = 0, + int pageSize = 0, + }); +} + +class ServicesPriceListRepoImp implements ServicesPriceListRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + ServicesPriceListRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getServicesPriceList({ + String searchKey = "", + int pageIndex = 0, + int pageSize = 0, + }) async { + Map mapDevice = { + "ID": 1, + "SearchKey": searchKey, + "PageIndex": pageIndex, + "PageSize": pageSize, + "RowCount": 0, + "TokenID": "@dm!n" + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + GET_SERVICES_PRICE_LIST, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['getServicesPriceList']; + if (list == null || list.isEmpty) { + throw Exception("Services price list is empty"); + } + + final servicesList = list + .map((item) => ServicesPriceListResponseModel.fromJson(item as Map)) + .toList() + .cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: servicesList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} + diff --git a/lib/features/services_price_list/services_price_list_view_model.dart b/lib/features/services_price_list/services_price_list_view_model.dart new file mode 100644 index 00000000..85b96802 --- /dev/null +++ b/lib/features/services_price_list/services_price_list_view_model.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/models/resp_models/services_price_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class ServicesPriceListViewModel extends ChangeNotifier { + bool isServicesPriceListLoading = false; + + ServicesPriceListRepo servicesPriceListRepo; + ErrorHandlerService errorHandlerService; + + List servicesPriceList = []; + List filteredServicesPriceList = []; + + String searchKey = ""; + + ServicesPriceListViewModel({ + required this.servicesPriceListRepo, + required this.errorHandlerService, + }); + + initServicesPriceListProvider() { + servicesPriceList.clear(); + filteredServicesPriceList.clear(); + isServicesPriceListLoading = true; + getServicesPriceList(); + notifyListeners(); + } + + setIsServicesPriceListLoading(bool val) { + isServicesPriceListLoading = val; + notifyListeners(); + } + + setSearchKey(String val) { + searchKey = val; + filterServicesList(val); + } + + void filterServicesList(String query) { + if (query.isEmpty) { + filteredServicesPriceList = List.from(servicesPriceList); + } else { + filteredServicesPriceList = servicesPriceList.where((service) { + final nameEN = service.nameEN?.toLowerCase() ?? ''; + final nameAR = service.nameAR?.toLowerCase() ?? ''; + final searchLower = query.toLowerCase(); + return nameEN.contains(searchLower) || nameAR.contains(searchLower); + }).toList(); + } + notifyListeners(); + } + + Future getServicesPriceList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await servicesPriceListRepo.getServicesPriceList( + searchKey: searchKey, + ); + + result.fold( + (failure) async { + isServicesPriceListLoading = false; + notifyListeners(); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + isServicesPriceListLoading = false; + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? "Error loading services price list"); + } + } else if (apiResponse.messageStatus == 1) { + servicesPriceList = apiResponse.data!; + servicesPriceList.removeWhere((element) => element.isEnabled == false); + filteredServicesPriceList = List.from(servicesPriceList); + isServicesPriceListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index ed82451f..668b302e 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -84,9 +84,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier { String? _selectedGender; DateTime? _dateOfBirth; int? _selectedAge; - double _selectedHeight = 170; + num _selectedHeight = 170; bool _isHeightCm = true; - double _selectedWeight = 60; + num _selectedWeight = 60; bool _isWeightKg = true; // Getters @@ -104,11 +104,25 @@ class SymptomsCheckerViewModel extends ChangeNotifier { int? get selectedAge => _selectedAge; - double? get selectedHeight => _selectedHeight; + setSelectedAge(int age) { + _selectedAge = age; + } + + setSelectedHeight(num height) { + _selectedHeight = height; + notifyListeners(); + } + + setSelectedWeight(num weight) { + _selectedWeight = weight; + notifyListeners(); + } + + num? get selectedHeight => _selectedHeight; bool get isHeightCm => _isHeightCm; - double? get selectedWeight => _selectedWeight; + num? get selectedWeight => _selectedWeight; bool get isWeightKg => _isWeightKg; diff --git a/lib/features/todo_section/todo_section_view_model.dart b/lib/features/todo_section/todo_section_view_model.dart index 92d893ad..0f6cc873 100644 --- a/lib/features/todo_section/todo_section_view_model.dart +++ b/lib/features/todo_section/todo_section_view_model.dart @@ -13,17 +13,20 @@ class TodoSectionViewModel extends ChangeNotifier { String? notificationsCount = "0"; initializeTodoSectionViewModel() async { - patientAncillaryOrdersList.clear(); - isAncillaryOrdersLoading = true; - isAncillaryDetailsProceduresLoading = true; - notificationsCount = "0"; - getPatientOnlineAncillaryOrderList(); + if (isAncillaryOrdersNeedReloading) { + patientAncillaryOrdersList.clear(); + isAncillaryOrdersLoading = true; + isAncillaryDetailsProceduresLoading = true; + notificationsCount = "0"; + getPatientOnlineAncillaryOrderList(); + } getPatientDashboard(); } bool isAncillaryOrdersLoading = false; bool isAncillaryDetailsProceduresLoading = false; bool isProcessingPayment = false; + bool isAncillaryOrdersNeedReloading = true; List patientAncillaryOrdersList = []; List patientAncillaryOrderProceduresList = []; @@ -32,6 +35,11 @@ class TodoSectionViewModel extends ChangeNotifier { notifyListeners(); } + void setIsAncillaryOrdersNeedReloading(bool value) { + isAncillaryOrdersNeedReloading = value; + notifyListeners(); + } + Future getPatientDashboard({Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await todoSectionRepo.getPatientDashboard(); @@ -55,6 +63,10 @@ class TodoSectionViewModel extends ChangeNotifier { } Future getPatientOnlineAncillaryOrderList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (!isAncillaryOrdersNeedReloading) { + return; + } + patientAncillaryOrdersList.clear(); isAncillaryOrdersLoading = true; notifyListeners(); @@ -71,6 +83,7 @@ class TodoSectionViewModel extends ChangeNotifier { } else if (apiResponse.messageStatus == 1) { patientAncillaryOrdersList = apiResponse.data!; isAncillaryOrdersLoading = false; + isAncillaryOrdersNeedReloading = false; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 05563f7c..72297264 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1553,5 +1553,14 @@ abstract class LocaleKeys { static const contextDependent = 'contextDependent'; static const calculatedValue = 'calculatedValue'; static const calculationFormula = 'calculationFormula'; + static const confirmYourLocation = 'confirmYourLocation'; + static const confirmYourLocationDesc = 'confirmYourLocationDesc'; + static const insideHospital = 'insideHospital'; + static const outsideHospital = 'outsideHospital'; + static const servicePriceList = 'servicePriceList'; + static const servicePriceListDesc = 'servicePriceListDesc'; + static const servicePriceListRights = 'servicePriceListRights'; + static const serviceName = 'serviceName'; + static const noFreeSlot = 'noFreeSlot'; } diff --git a/lib/main.dart b/lib/main.dart index 6010a05d..e034cf92 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -36,6 +36,7 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; @@ -202,6 +203,9 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), // Wrap your app @@ -224,7 +228,7 @@ class MyApp extends StatelessWidget { return MaterialApp( title: 'Dr. AlHabib', builder: (context, mchild) { - return MediaQuery(data: MediaQuery.of(context).copyWith(textScaler: TextScaler.linear(1.0)), child: mchild!); + return MediaQuery(data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling, alwaysUse24HourFormat: true,), child: mchild!, ); }, showSemanticsDebugger: false, debugShowCheckedModeBanner: false, diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 3f98e6d4..0e0194b5 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -8,6 +9,7 @@ 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/calender_utils_new.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/penguin_method_channel.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'; @@ -15,6 +17,8 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_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/hospital/AppPermission.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; @@ -37,7 +41,8 @@ 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/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import '../medical_file/widgets/medical_file_card.dart'; @@ -59,6 +64,8 @@ class _AppointmentDetailsPageState extends State { late LabViewModel labViewModel; late RadiologyViewModel radiologyViewModel; + String mapURL = ""; + @override void initState() { scheduleMicrotask(() async { @@ -90,6 +97,7 @@ class _AppointmentDetailsPageState extends State { title: LocaleKeys.appointmentDetails.tr(context: context), report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? () { + contactUsViewModel.setSelectedFeedbackType(FeedbackType(id: 1, nameEN: "Complaint for appointment", nameAR: 'شكوى على موعد')); contactUsViewModel.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); Navigator.of(context).push( CustomPageRoute( @@ -103,6 +111,9 @@ class _AppointmentDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ AppointmentDoctorCard( + renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || + (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)), patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onAskDoctorTap: () async { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); @@ -180,15 +191,57 @@ class _AppointmentDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.appointmentStatus.tr(context: context).toText16(isBold: true), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.appointmentStatus.tr(context: context).toText16(isBold: true), + SizedBox(height: 4.h), + (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) + ? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) + : LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), + SizedBox(height: 16.h), + ], + ), + ((!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) && widget.patientAppointmentHistoryResponseModel.nextAction != 10) + ? CustomButton( + text: LocaleKeys.confirm.tr(), + onPressed: () async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingAppointmentPleaseWait.tr(context: context)); + await myAppointmentsViewModel.confirmAppointment( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + LoaderBottomSheet.hideLoader(); + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + showCommonBottomSheetWithoutHeight( + title: "", + context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), + callBackFunc: () { + Navigator.of(context).pop(); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + backgroundColor: AppColors.successColor, + borderColor: AppColors.successColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + height: 40.h, + icon: AppAssets.confirm_appointment_icon, + iconColor: AppColors.whiteColor, + iconSize: 16.h, + ) + : SizedBox.shrink()) ], ), - SizedBox(height: 4.h), - (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) - ? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) - : LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), - SizedBox(height: 16.h), //TODO Add countdown timer in case of LiveCare Appointment widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false ? Row( @@ -212,7 +265,7 @@ class _AppointmentDetailsPageState extends State { borderRadius: BorderRadius.circular(24.r), // Todo: what is this???? Api Key??? 😲 child: Image.network( - "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=${ApiKeyConstants.googleMapsApiKey}", + "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=${(MediaQuery.of(context).size.width * 1.5).toInt()}x${(MediaQuery.of(context).size.height * 0.35).toInt()}&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=${ApiKeyConstants.googleMapsApiKey}", fit: BoxFit.contain, ), ), @@ -221,9 +274,24 @@ class _AppointmentDetailsPageState extends State { child: SizedBox( width: MediaQuery.of(context).size.width * 0.785, child: CustomButton( - onPressed: () { - MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), - double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); + onPressed: () async { + if (widget.patientAppointmentHistoryResponseModel.projectID == 130 || widget.patientAppointmentHistoryResponseModel.projectID == 120) { + showDirectionsBottomSheet(); + } else { + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", + ); + }); + } }, text: LocaleKeys.getDirections.tr(context: context), backgroundColor: AppColors.textColor.withValues(alpha: 0.8), @@ -751,11 +819,15 @@ class _AppointmentDetailsPageState extends State { ], ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - ? CustomButton( - text: LocaleKeys.rebookAppointment.tr(context: context), - onPressed: () { - openDoctorScheduleCalendar(); - }, + ? + (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! || + widget.patientAppointmentHistoryResponseModel.isExecludeDoctor! || + Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)) + ? CustomButton( + text: LocaleKeys.rebookAppointment.tr(context: context), + onPressed: () { + openDoctorScheduleCalendar(); + }, backgroundColor: AppColors.successColor, borderColor: AppColors.successColor, textColor: AppColors.whiteColor, @@ -767,11 +839,11 @@ class _AppointmentDetailsPageState extends State { icon: AppAssets.add_icon, iconColor: AppColors.whiteColor, iconSize: 18.h, - ).paddingSymmetrical(16.h, 24.h) + ).paddingSymmetrical(16.h, 24.h) + : SizedBox.shrink() : CustomButton( text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), onPressed: () { - myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction); }, backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), @@ -796,6 +868,89 @@ class _AppointmentDetailsPageState extends State { ); } + void showDirectionsBottomSheet() { + showCommonBottomSheetWithoutHeight( + context, + title: "", + isDismissible: true, + isCloseButtonVisible: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.confirmYourLocation.tr(context: context).toText28(color: AppColors.textColor, isBold: true, height: 1.5), + SizedBox(height: 4.h), + LocaleKeys.confirmYourLocationDesc.tr(context: context).toText12( + color: AppColors.greyTextColor, + fontWeight: FontWeight.w500, + ), + SizedBox(height: 24.h), + CustomButton( + text: LocaleKeys.insideHospital.tr(context: context), + onPressed: () { + Navigator.pop(context); + initPenguinSDK(widget.patientAppointmentHistoryResponseModel.projectID == 130 ? 1 : (widget.patientAppointmentHistoryResponseModel.projectID == 120 ? 3 : -1), + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID.toString()); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 56.h, + ), + SizedBox(height: 8.h), + CustomButton( + text: LocaleKeys.outsideHospital.tr(context: context), + onPressed: () async { + Navigator.pop(context); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", + ); + }); + }, + backgroundColor: AppColors.chipSecondaryLightRedColor, + borderColor: AppColors.chipSecondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 56.h, + ), + ], + ).paddingSymmetrical(24.w, 20.h), + callBackFunc: () {}, + ); + } + + initPenguinSDK(int projectID, {String clinicID = ""}) async { + NavigationClinicDetails data = NavigationClinicDetails(); + data.projectId = projectID.toString(); + data.clinicId = clinicID; + // data.clinicId = "1"; + final bool permited = await AppPermission.askPenguinPermissions(); + if (!permited) { + Map statuses = await [ + Permission.location, + Permission.bluetooth, + Permission.bluetoothConnect, + Permission.bluetoothScan, + Permission.activityRecognition, + ].request().whenComplete(() { + PenguinMethodChannel().launch("penguin", getIt.get().isArabic() ? "ar" : "en", getIt.get().getAuthenticatedUser()?.patientId?.toString() ?? "", true, details: data); + }); + } + } + openDoctorScheduleCalendar() async { DoctorsListResponseModel doctor = DoctorsListResponseModel( clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, @@ -849,6 +1004,9 @@ class _AppointmentDetailsPageState extends State { myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.getPatientAppointments(true, false); showCommonBottomSheet(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), callBackFunc: (str) { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); Navigator.of(context).pop(); }, title: "", diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index f817a898..097efaf6 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -403,6 +403,9 @@ class _AppointmentPaymentPageState extends State { patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); Navigator.pushAndRemoveUntil( context, @@ -421,6 +424,9 @@ class _AppointmentPaymentPageState extends State { isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); Navigator.pushAndRemoveUntil( context, @@ -459,7 +465,6 @@ class _AppointmentPaymentPageState extends State { await payfortViewModel.checkPaymentStatus( transactionID: transID, onSuccess: (apiResponse) async { - print(apiResponse.data); if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") { await myAppointmentsViewModel.createAdvancePayment( paymentMethodName: selectedPaymentMethod, @@ -486,6 +491,8 @@ class _AppointmentPaymentPageState extends State { patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); Navigator.pushAndRemoveUntil( context, @@ -504,6 +511,8 @@ class _AppointmentPaymentPageState extends State { isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); Navigator.pushAndRemoveUntil( context, @@ -517,6 +526,7 @@ class _AppointmentPaymentPageState extends State { }); }); } else { + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), @@ -564,14 +574,7 @@ class _AppointmentPaymentPageState extends State { } startApplePay() async { - showCommonBottomSheet(context, - child: Utils.getLoadingWidget(), - callBackFunc: (str) {}, - title: "", - height: ResponsiveExtension.screenHeight * 0.3, - isCloseButtonVisible: false, - isDismissible: false, - isFullScreen: false); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); transID = Utils.getAppointmentTransID( widget.patientAppointmentHistoryResponseModel.projectID, widget.patientAppointmentHistoryResponseModel.clinicID, @@ -642,7 +645,7 @@ class _AppointmentPaymentPageState extends State { currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", onFailed: (failureResult) async { log("failureResult: ${failureResult.message.toString()}"); - Navigator.of(context).pop(); + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), @@ -652,6 +655,7 @@ class _AppointmentPaymentPageState extends State { ); }, onSucceeded: (successResult) async { + LoaderBottomSheet.hideLoader(); log("successResult: ${successResult.responseMessage.toString()}"); selectedPaymentMethod = successResult.paymentOption ?? "VISA"; checkPaymentStatus(); diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index e1748c3a..49aad51f 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -26,6 +26,7 @@ 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/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'dart:ui' as ui; class AppointmentCard extends StatelessWidget { final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; @@ -39,20 +40,20 @@ class AppointmentCard extends StatelessWidget { final ContactUsViewModel? contactUsViewModel; final BookAppointmentsViewModel bookAppointmentsViewModel; final bool isForRate; - const AppointmentCard({ - super.key, - required this.patientAppointmentHistoryResponseModel, - required this.myAppointmentsViewModel, - required this.bookAppointmentsViewModel, - this.isLoading = false, - this.isFromHomePage = false, - this.isFromMedicalReport = false, - this.isForEyeMeasurements = false, - this.isForFeedback = false, - this.medicalFileViewModel, - this.contactUsViewModel, - this.isForRate =false - }); + + const AppointmentCard( + {super.key, + required this.patientAppointmentHistoryResponseModel, + required this.myAppointmentsViewModel, + required this.bookAppointmentsViewModel, + this.isLoading = false, + this.isFromHomePage = false, + this.isFromMedicalReport = false, + this.isForEyeMeasurements = false, + this.isForFeedback = false, + this.medicalFileViewModel, + this.contactUsViewModel, + this.isForRate = false}); @override Widget build(BuildContext context) { @@ -64,11 +65,11 @@ class AppointmentCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - isForRate ? SizedBox(): _buildHeader(context, appState), + isForRate ? SizedBox() : _buildHeader(context, appState), SizedBox(height: 16.h), _buildDoctorRow(context), SizedBox(height: 16.h), - isForRate ? SizedBox(): _buildActionArea(context, appState), + isForRate ? SizedBox() : _buildActionArea(context, appState), ], ), ), @@ -101,18 +102,13 @@ class AppointmentCard extends StatelessWidget { textColor: isLoading ? AppColors.textColor : (isLiveCare ? AppColors.whiteColor : AppColors.textColor), ).toShimmer2(isShow: isLoading), AppCustomChipWidget( - labelText: isLoading - ? 'OutPatient' - : (appState.isArabic() - ? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! - : patientAppointmentHistoryResponseModel.isInOutPatientDescription!), + labelText: + isLoading ? 'OutPatient' : (appState.isArabic() ? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : patientAppointmentHistoryResponseModel.isInOutPatientDescription!), backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), textColor: AppColors.primaryRedColor, ).toShimmer2(isShow: isLoading), AppCustomChipWidget( - labelText: isLoading - ? 'Booked' - : AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!), + labelText: isLoading ? 'Booked' : AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!), backgroundColor: AppColors.successColor.withValues(alpha: 0.1), textColor: AppColors.successColor, ).toShimmer2(isShow: isLoading), @@ -151,7 +147,9 @@ class AppointmentCard extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h), SizedBox(height: 2.h), - "${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor), + (isFoldable || isTablet) + ? "${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText9(isBold: true, color: AppColors.textColor) + : "${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}".toText11(isBold: true, color: AppColors.textColor), ], ), ).circle(100).toShimmer2(isShow: isLoading), @@ -182,17 +180,21 @@ class AppointmentCard extends StatelessWidget { AppCustomChipWidget( labelText: isLoading ? 'Olaya' - : patientAppointmentHistoryResponseModel.projectName!.length > 15 - ? '${patientAppointmentHistoryResponseModel.projectName!.substring(0, 15)}...' - : patientAppointmentHistoryResponseModel.projectName!) + : (patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").length > 15 + ? '${(patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").substring(0, 15)}...' + : patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital") .toShimmer2(isShow: isLoading), - AppCustomChipWidget( - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), - icon: AppAssets.appointment_calendar_icon, - labelText: isLoading - ? 'Cardiology' - : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}", - ).toShimmer2(isShow: isLoading), + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + icon: AppAssets.appointment_calendar_icon, + richText: isLoading + ? 'Cardiology'.toText10().toShimmer2(isShow: isLoading) + : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}" + .toText10(isEnglishOnly: true), + ), + ), // AppCustomChipWidget( // labelPadding: EdgeInsetsDirectional.only(start: -2.w, end: 6.w), @@ -224,83 +226,123 @@ class AppointmentCard extends StatelessWidget { } Widget _buildActionArea(BuildContext context, AppState appState) { - if (isFromMedicalReport) { - if (isForEyeMeasurements) { - return SizedBox.shrink(); - } else { - return CustomButton( - text: LocaleKeys.selectAppointment.tr(context: context), - onPressed: () { + if ((((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || + (patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID ?? 0))) && + AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) { + return CustomButton( + text: isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context), + onPressed: () { + if (isFromMedicalReport) { if (isForFeedback) { contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel); } else { medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); } Navigator.pop(context, false); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - // height: isTablet || isFoldable ? 46.h : 40.h, - height: 40.h, - icon: AppAssets.checkmark_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ); + } else { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + ), + ) + .then((_) { + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + }); + } + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + // height: isTablet || isFoldable ? 46.h : 40.h, + height: 40.h, + icon: isFromMedicalReport ? AppAssets.checkmark_icon : null, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ); + } else { + if (isFromMedicalReport) { + if (isForEyeMeasurements) { + return SizedBox.shrink(); + } else { + return CustomButton( + text: LocaleKeys.selectAppointment.tr(context: context), + onPressed: () { + if (isForFeedback) { + contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel); + } else { + medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); + } + Navigator.pop(context, false); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + // height: isTablet || isFoldable ? 46.h : 40.h, + height: 40.h, + icon: AppAssets.checkmark_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ); + } } - } - return Row( - children: [ - Expanded( - flex: 6, - child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel) - ? _getArrivedButton(context) - : CustomButton( - text: AppointmentType.getNextActionText(patientAppointmentHistoryResponseModel.nextAction), - onPressed: () => _goToDetails(context), - backgroundColor: - AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), - borderColor: - AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), - textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - icon: AppointmentType.getNextActionIcon(patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), - iconSize: 15.h, - )) - .toShimmer2(isShow: isLoading), - ), - SizedBox(width: 8.h), - Expanded( - flex: 1, - child: Container( - height: (isFoldable || isTablet) ? 50.h : 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.textColor, borderRadius: 10.h), - child: Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.whiteColor, - width: 24.w, - height: 24.h, - fit: BoxFit.contain, + return Row( + children: [ + Expanded( + flex: 6, + child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel) + ? _getArrivedButton(context) + : CustomButton( + text: AppointmentType.getNextActionText(patientAppointmentHistoryResponseModel.nextAction), + onPressed: () => _goToDetails(context), + backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), + borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), + textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + icon: AppointmentType.getNextActionIcon(patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), + iconSize: 15.h, + )) + .toShimmer2(isShow: isLoading), + ), + SizedBox(width: 8.h), + Expanded( + flex: 1, + child: Container( + height: (isFoldable || isTablet) ? 50.h : 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.textColor, borderRadius: 10.h), + child: Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.whiteColor, + width: 24.w, + height: 24.h, + fit: BoxFit.contain, + ), ), - ), - ).toShimmer2(isShow: isLoading).onPress(() { - _goToDetails(context); - }), - ), - ], - ); + ).toShimmer2(isShow: isLoading).onPress(() { + _goToDetails(context); + }), + ), + ], + ); + } } Widget _getArrivedButton(BuildContext context) { @@ -340,7 +382,7 @@ class AppointmentCard extends StatelessWidget { backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, - fontSize: 14.f, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w500, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), @@ -358,7 +400,7 @@ class AppointmentCard extends StatelessWidget { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 14.f, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w500, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), @@ -386,8 +428,8 @@ class AppointmentCard extends StatelessWidget { ), ) .then((_) { - myAppointmentsViewModel.initAppointmentsViewModel(); - myAppointmentsViewModel.getPatientAppointments(true, false); + // myAppointmentsViewModel.initAppointmentsViewModel(); + // myAppointmentsViewModel.getPatientAppointments(true, false); }); } } diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index c5139f17..f07b9a4f 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -36,6 +36,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { + locationUtils = getIt.get(); appState = getIt.get(); FlutterNfcKit.nfcAvailability.then((value) { _supportsNFC = (value == NFCAvailability.available); @@ -149,6 +150,9 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { scannedCode: scannedCode, checkInType: checkInType, onSuccess: (apiResponse) { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () async { await myAppointmentsViewModel.getPatientAppointmentQueueDetails(); diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 0c4aec14..7e430694 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -12,6 +12,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 'dart:ui' as ui; class AppointmentDoctorCard extends StatelessWidget { const AppointmentDoctorCard( @@ -89,25 +90,28 @@ class AppointmentDoctorCard extends StatelessWidget { runSpacing: 6.h, children: [ AppCustomChipWidget( - labelText: (patientAppointmentHistoryResponseModel.clinicName!.length > 15 - ? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 12)}...' + labelText: (patientAppointmentHistoryResponseModel.clinicName!.length > 20 + ? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 20)}...' : patientAppointmentHistoryResponseModel.clinicName!), labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), ), AppCustomChipWidget( - labelText: patientAppointmentHistoryResponseModel.projectName!, + labelText: patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), ), - AppCustomChipWidget( - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), - icon: AppAssets.doctor_calendar_icon, - labelText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang( - DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), - false, - )}", + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), + icon: AppAssets.doctor_calendar_icon, + richText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang( + DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), + false, + )}".toText10(isEnglishOnly: true), + ), ), AppCustomChipWidget( - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon, @@ -159,8 +163,9 @@ class AppointmentDoctorCard extends StatelessWidget { icon: AppAssets.ask_doctor_icon, iconColor: AppColors.primaryRedColor, ) - : CustomButton( - text: LocaleKeys.rebookSameDoctor.tr(), + : !patientAppointmentHistoryResponseModel.isLiveCareAppointment! + ? CustomButton( + text: LocaleKeys.rebookSameDoctor.tr(), onPressed: () { onRescheduleTap(); }, @@ -175,7 +180,8 @@ class AppointmentDoctorCard extends StatelessWidget { icon: AppAssets.rebook_appointment_icon, iconColor: AppColors.blackColor, iconSize: 14.h, - ); + ) + : SizedBox.shrink(); } else { return patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false ? CustomButton( diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index 6dcf15fb..9f64bde4 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -73,6 +73,7 @@ class LoginScreenState extends State { SizedBox(height: 130.h), // Adjusted to sizer unit LocaleKeys.welcomeToDrSulaiman.tr(context: context).toText32(isBold: true, color: AppColors.textColor), SizedBox(height: 32.h), + Localizations.override(context: context, locale: Locale('en', 'US'), child: Container()), // Force English locale for this widget TextInputWidget( labelText: "${LocaleKeys.nationalId.tr(context: context)} / ${LocaleKeys.fileNo.tr(context: context)}", hintText: "xxxxxxxxx", @@ -89,6 +90,7 @@ class LoginScreenState extends State { leadingIcon: AppAssets.student_card, errorMessage: LocaleKeys.enterValidIDorIqama.tr(context: context), hasError: false, + fontFamily: "Poppins", ), SizedBox(height: 16.h), CustomButton( diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index 71bddb38..2a26a726 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -100,6 +100,7 @@ class _RegisterNew extends State { hintText: "xxxxxxxxx", controller: authVm.nationalIdController, focusNode: _nationalIdFocusNode, + keyboardType: TextInputType.number, isEnable: true, prefix: null, isAllowRadius: true, @@ -108,6 +109,7 @@ class _RegisterNew extends State { autoFocus: true, padding: EdgeInsets.symmetric(vertical: 8.h), leadingIcon: AppAssets.student_card, + fontFamily: "Poppins", ).withVerticalPadding(8), Divider(height: 1), TextInputWidget( @@ -125,6 +127,7 @@ class _RegisterNew extends State { selectionType: SelectionTypeEnum.calendar, onCalendarTypeChanged: authVm.onCalenderTypeChange, onChange: authVm.onDobChange, + fontFamily: "Poppins", ).withVerticalPadding(8), ], ), diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index 22fc4673..231d7d14 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dar import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; +import 'dart:ui' as ui; class SavedLogin extends StatefulWidget { const SavedLogin({super.key}); @@ -93,14 +94,15 @@ class _SavedLogin extends State { ("${LocaleKeys.lastLoginBy.tr()} ${loginType.displayName}") .toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1), - appState.getSelectDeviceByImeiRespModelElement != null - ? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null - ? DateUtil.getFormattedDate( - DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), - "d MMMM, y 'at' HH:mm") - : '--') - .toText16(isBold: true, color: AppColors.textColor) - : SizedBox(), + Directionality( + textDirection: ui.TextDirection.ltr, + child: appState.getSelectDeviceByImeiRespModelElement != null + ? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null + ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") + : '--') + .toText16(isBold: true, color: AppColors.textColor, isEnglishOnly: true) + : SizedBox(), + ), appState.getSelectDeviceByImeiRespModelElement != null ? Container( diff --git a/lib/presentation/blood_donation/blood_donation_page.dart b/lib/presentation/blood_donation/blood_donation_page.dart index 26da5995..7c6c625f 100644 --- a/lib/presentation/blood_donation/blood_donation_page.dart +++ b/lib/presentation/blood_donation/blood_donation_page.dart @@ -263,13 +263,20 @@ class _BloodDonationPageState extends State { Row( children: [ Text( - LocaleKeys.iAcceptThe.tr(), + "${LocaleKeys.iAcceptThe.tr()} ", style: context.dynamicTextStyle(fontSize: 14.f, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), ), GestureDetector( onTap: () { // Navigate to terms and conditions page - Navigator.of(context).pushNamed('/terms'); + // Navigator.of(context).pushNamed('/terms'); + appState.isArabic() + ? Utils.openWebView( + url: 'https://hmg.com/ar/Pages/Terms.aspx', + ) + : Utils.openWebView( + url: 'https://hmg.com/en/Pages/Terms.aspx', + ); }, child: Text( LocaleKeys.termsConditoins.tr(), diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index cf5eef34..fa239797 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -458,8 +458,8 @@ class _BookAppointmentPageState extends State { ), SizedBox(width: 16.w), CustomButton( - height: 40.h, - width: 40.w, + height: 42.h, + width: 42.w, text: "", onPressed: () => context.navigateWithName(AppRoutes.userInfoSelection), icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index 72f242d2..c62404ab 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_rating_details.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -108,9 +109,21 @@ class DoctorProfilePage extends StatelessWidget { ), SizedBox(height: 16.h), "Ratings".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), - bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate.toString().toText16(isBold: true, color: AppColors.textColor), + bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate + .toString() + .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor), ], - ), + ).onPress(() { + bookAppointmentsViewModel.getDoctorRatingDetails(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.doctorRating.tr(context: context), + context, + child: DoctorRatingDetails(), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }), SizedBox(width: 36.w), Column( children: [ @@ -122,15 +135,28 @@ class DoctorProfilePage extends StatelessWidget { ), SizedBox(height: 16.h), "Reviews".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), - bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate.toString().toText16(isBold: true, color: AppColors.textColor), + bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate + .toString() + .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor), ], - ), + ).onPress(() { + bookAppointmentsViewModel.getDoctorRatingDetails(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.doctorRating.tr(context: context), + context, + child: DoctorRatingDetails(), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }), ], ), SizedBox(height: 16.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), SizedBox(height: 16.h), - "Biography".toText14(weight: FontWeight.w600, color: AppColors.textColor), + LocaleKeys.docInfo.tr(context: context).toText14(weight: FontWeight.w600, color: AppColors.textColor), + SizedBox(height: 6.h), bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo!.toText12(fontWeight: FontWeight.w600, color: AppColors.greyTextColor), ], ).paddingSymmetrical(24.h, 0.h), diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart index 3e48f8bc..4992d850 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -1,4 +1,3 @@ - import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; @@ -18,6 +17,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immed import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; +import 'package:hmg_patient_app_new/services/livecare_permission_service.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/buttons/custom_button.dart'; @@ -32,71 +32,30 @@ import 'package:smooth_corner/smooth_corner.dart'; class ImmediateLiveCarePaymentDetails extends StatelessWidget { ImmediateLiveCarePaymentDetails({super.key}); - late ImmediateLiveCareViewModel immediateLiveCareViewModel; + // late ImmediateLiveCareViewModel immediateLiveCareViewModel; late AppState appState; @override Widget build(BuildContext context) { - immediateLiveCareViewModel = Provider.of(context, listen: false); + // immediateLiveCareViewModel = Provider.of(context, listen: false); appState = getIt.get(); return Scaffold( backgroundColor: AppColors.scaffoldBgColor, - body: Column( - children: [ - Expanded( - child: CollapsingListView( - title: LocaleKeys.reviewLiveCareRequest.tr(context: context), - child: SingleChildScrollView( - padding: EdgeInsets.symmetric(horizontal: 24.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 24.h), - LocaleKeys.patientInfo.tr(context: context).toText16(isBold: true), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Image.asset( - appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, - width: 52.h, - height: 52.h, - ), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true), - SizedBox(height: 8.h), - Wrap( - direction: Axis.horizontal, - spacing: 3.h, - runSpacing: 4.h, - children: [ - AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), - AppCustomChipWidget( - labelText: - "${LocaleKeys.clinic.tr()}: ${(appState.isArabic() ? immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceNameN : immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceName)!}"), - ], - ), - ], - ), - ], - ), - ), - ), - SizedBox(height: 24.h), - LocaleKeys.selectedLiveCareType.tr(context: context).toText16(isBold: true), - SizedBox(height: 16.h), - Consumer(builder: (context, bookAppointmentsVM, child) { - return Container( + body: Consumer(builder: (context, immediateLiveCareVM, child) { + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.reviewLiveCareRequest.tr(context: context), + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + LocaleKeys.patientInfo.tr(context: context).toText16(isBold: true), + SizedBox(height: 16.h), + Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.h, @@ -105,248 +64,371 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { child: Padding( padding: EdgeInsets.all(16.h), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( + Image.asset( + appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, + width: 52.h, + height: 52.h, + ), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain), - SizedBox(width: 8.h), - getLiveCareType(context, immediateLiveCareViewModel.liveCareSelectedCallType).toText16(isBold: true), + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), + AppCustomChipWidget( + labelText: + "${LocaleKeys.clinic.tr()}: ${(appState.isArabic() ? immediateLiveCareVM.immediateLiveCareSelectedClinic.serviceNameN : immediateLiveCareVM.immediateLiveCareSelectedClinic.serviceName)!}"), + ], + ), ], ), - Utils.buildSvgWithAssets(icon: AppAssets.edit_icon, width: 24.h, height: 24.h, fit: BoxFit.contain), ], ), ), - ).onPress(() { - showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareViewModel), callBackFunc: () async { - debugPrint("Selected Call Type: ${immediateLiveCareViewModel.liveCareSelectedCallType}"); - }, title: LocaleKeys.selectLiveCareCallType.tr(context: context), isCloseButtonVisible: true, isFullScreen: false); - }); - }), - SizedBox(height: 24.h) - ], + ), + SizedBox(height: 24.h), + LocaleKeys.selectedLiveCareType.tr(context: context).toText16(isBold: true), + SizedBox(height: 16.h), + Consumer(builder: (context, bookAppointmentsVM, child) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain), + SizedBox(width: 8.h), + getLiveCareType(context, immediateLiveCareVM.liveCareSelectedCallType).toText16(isBold: true), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.edit_icon, width: 24.h, height: 24.h, fit: BoxFit.contain), + ], + ), + ), + ).onPress(() { + showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareVM), callBackFunc: () async { + debugPrint("Selected Call Type: ${immediateLiveCareVM.liveCareSelectedCallType}"); + }, title: LocaleKeys.selectLiveCareCallType.tr(context: context), isCloseButtonVisible: true, isFullScreen: false); + }); + }), + SizedBox(height: 24.h) + ], + ), ), ), ), - ), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: false, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - (immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.isCash ?? true) - ? Container( - height: 50.h, - decoration: ShapeDecoration( - color: AppColors.secondaryLightRedBorderColor, - shape: SmoothRectangleBorder( - borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), - smoothness: 1, + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.isCash ?? true) + ? Container( + height: 50.h, + decoration: ShapeDecoration( + color: AppColors.secondaryLightRedBorderColor, + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + smoothness: 1, + ), ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), - CustomButton( - text: LocaleKeys.updateInsurance.tr(context: context), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: InsuranceHomePage(), - ), - ); - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.secondaryLightRedBorderColor, - textColor: AppColors.whiteColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(15, 0, 15, 0), - height: 30.h, - ).paddingSymmetrical(24.h, 0.h), - ], - ), - ) - : const SizedBox(), - SizedBox(height: 24.h), - LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 17.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), - Utils.getPaymentAmountWithSymbol(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.amount!.toText16(isBold: true), AppColors.blackColor, 13, - isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), - ], - ).paddingSymmetrical(24.h, 0.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), - Utils.getPaymentAmountWithSymbol( - immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.tax!.toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, - isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), - ], - ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 17.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(width: 150.h, child: Utils.getPaymentMethods()), - Utils.getPaymentAmountWithSymbol(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!.toText24(isBold: true), AppColors.blackColor, 17, - isSaudiCurrency: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), - ], - ).paddingSymmetrical(24.h, 0.h), - (immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total == "0.0") - ? CustomButton( - text: LocaleKeys.confirmLiveCare.tr(context: context), - onPressed: () async { - await askVideoCallPermission().then((val) async { - if (val) { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context)); + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), + CustomButton( + text: LocaleKeys.updateInsurance.tr(context: context), + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + page: InsuranceHomePage(), + ), + ); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.secondaryLightRedBorderColor, + textColor: AppColors.whiteColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(15, 0, 15, 0), + height: 30.h, + ).paddingSymmetrical(24.h, 0.h), + ], + ), + ) + : const SizedBox(), + SizedBox(height: 24.h), + LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), + Utils.getPaymentAmountWithSymbol(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.amount!.toText16(isBold: true), AppColors.blackColor, 13, + isSaudiCurrency: immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" || + immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال"), + ], + ).paddingSymmetrical(24.h, 0.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), + Utils.getPaymentAmountWithSymbol( + immediateLiveCareVM.liveCareImmediateAppointmentFeesList.tax!.toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, + isSaudiCurrency: (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" || + immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال")), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 150.h, child: Utils.getPaymentMethods()), + Utils.getPaymentAmountWithSymbol(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total!.toText24(isBold: true), AppColors.blackColor, 17, + isSaudiCurrency: (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" || + immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال")), + ], + ).paddingSymmetrical(24.h, 0.h), + (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0") + ? CustomButton( + text: LocaleKeys.confirmLiveCare.tr(context: context), + onPressed: () async { + await askVideoCallPermission(context).then((val) async { + if (val) { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context)); - await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}"); - await immediateLiveCareViewModel.getPatientLiveCareHistory(); - LoaderBottomSheet.hideLoader(); - if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { - Navigator.pushAndRemoveUntil( - context, + await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}"); + await immediateLiveCareVM.getPatientLiveCareHistory(); + LoaderBottomSheet.hideLoader(); + if (immediateLiveCareVM.patientHasPendingLiveCareRequest) { + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + Navigator.of(context).push( CustomPageRoute( - page: LandingNavigation(), + page: ImmediateLiveCarePendingRequestPage(), ), - (r) => false); - Navigator.of(context).push( - CustomPageRoute( - page: ImmediateLiveCarePendingRequestPage(), - ), - ); + ); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.unknownErrorOccurred.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } } else { showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), context, - child: Utils.getErrorWidget(loadingText: LocaleKeys.unknownErrorOccurred.tr(context: context)), + child: Utils.getWarningWidget( + loadingText: LocaleKeys.liveCarePermissionsMessage.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + openAppSettings(); + }), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); } - } else { - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.notice.tr(context: context), - context, - child: Utils.getWarningWidget( - loadingText: LocaleKeys.liveCarePermissionsMessage.tr(context: context), - isShowActionButtons: true, - onCancelTap: () { - Navigator.pop(context); - }, - onConfirmTap: () async { - openAppSettings(); - }), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - } - }); - }, - backgroundColor: AppColors.successColor, - borderColor: AppColors.successColor, - textColor: AppColors.whiteColor, - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, - icon: AppAssets.livecare_book_icon, - iconColor: AppColors.whiteColor, - iconSize: 18.h, - ).paddingSymmetrical(24.h, 24.h) - : CustomButton( - text: LocaleKeys.payNow.tr(context: context), - onPressed: () async { - await askVideoCallPermission().then((val) { - if (val) { - Navigator.of(context).push( - CustomPageRoute( - page: ImmediateLiveCarePaymentPage(), - ), - ); - } else { - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.notice.tr(context: context), - context, - child: Utils.getWarningWidget( - loadingText: LocaleKeys.liveCarePermissionsMessage.tr(context: context), - isShowActionButtons: true, - onCancelTap: () { - Navigator.pop(context); - }, - onConfirmTap: () async { - openAppSettings(); - }), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - } - }); - }, - backgroundColor: AppColors.infoColor, - borderColor: AppColors.infoColor, - textColor: AppColors.whiteColor, - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, - icon: AppAssets.appointment_pay_icon, - iconColor: AppColors.whiteColor, - iconSize: 18.h, - ).paddingSymmetrical(24.h, 24.h), - ], + }); + }, + backgroundColor: AppColors.successColor, + borderColor: AppColors.successColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.livecare_book_icon, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(24.h, 24.h) + : CustomButton( + text: LocaleKeys.payNow.tr(context: context), + onPressed: () async { + await askVideoCallPermission(context).then((val) { + if (val) { + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePaymentPage(), + ), + ); + } + // else { + // showCommonBottomSheetWithoutHeight( + // title: LocaleKeys.notice.tr(context: context), + // context, + // child: Utils.getWarningWidget( + // loadingText: LocaleKeys.liveCarePermissionsMessage.tr(context: context), + // isShowActionButtons: true, + // onCancelTap: () { + // Navigator.pop(context); + // }, + // onConfirmTap: () async { + // openAppSettings(); + // }), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // } + }); + }, + backgroundColor: AppColors.infoColor, + borderColor: AppColors.infoColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.appointment_pay_icon, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(24.h, 24.h), + ], + ), ), - ), - ], - ), + ], + ); + }), ); } - Future askVideoCallPermission() async { - bool hasPermission = false; - if (Platform.isIOS) { - Map statuses = await [Permission.camera, Permission.microphone].request(); + Future askVideoCallPermission(BuildContext context) async { + final statuses = await LiveCarePermissionService.instance.requestCameraMicAndNotification(context); + + // If service returned nothing (error), treat as not granted + if (statuses.isEmpty) return false; + + bool cameraGranted = statuses[Permission.camera]?.isGranted ?? false; + bool micGranted = statuses[Permission.microphone]?.isGranted ?? false; + bool notifGranted = statuses[Permission.notification]?.isGranted ?? false; + + // If all required permissions are already granted + if (cameraGranted && micGranted && notifGranted) return true; + + // Collect only the missing permissions + final missing = []; + if (!cameraGranted) missing.add(Permission.camera); + if (!micGranted) missing.add(Permission.microphone); + if (!notifGranted) missing.add(Permission.notification); + + // If any of the missing permissions are permanently denied/restricted -> open settings (single dialog) + final permanent = missing.where((p) => (statuses[p]?.isPermanentlyDenied ?? false) || (statuses[p]?.isRestricted ?? false)).toList(); + if (permanent.isNotEmpty) { + final names = permanent.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and '); + final message = permanent.length == 1 ? '$names permission is permanently denied. Open app settings to allow it.' : '$names permissions are permanently denied. Open app settings to allow them.'; + await LiveCarePermissionService.instance.showOpenSettingsDialog( + context, + title: "Permissions Required", + message: message, + ); + return false; + } + + // Show one combined retry dialog for all missing permissions + final missingNames = missing.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and '); + final retry = await showDialog( + context: context, + builder: (c) => AlertDialog( + title: const Text('Permissions required'), + content: Text('$missingNames permission${missing.length > 1 ? 's are' : ' is'} required to start the video call. Retry now?'), + actions: [ + TextButton(onPressed: () => Navigator.of(c).pop(false), child: const Text('Cancel')), + TextButton(onPressed: () => Navigator.of(c).pop(true), child: const Text('Retry')), + ], + ), + ); - if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted) { - // Camera permission granted - hasPermission = true; - } else { - hasPermission = false; - } - } else { - Map statuses = await [Permission.camera, Permission.microphone, Permission.notification].request(); + if (retry != true) return false; - if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted && statuses[Permission.notification] == PermissionStatus.granted) { - // Camera permission granted - hasPermission = true; - } else { - hasPermission = false; - } + // Request only the missing permissions + final newStatuses = {}; + for (final p in missing) { + newStatuses[p] = await p.request(); } - return hasPermission; + // Update grant state (preserve previously granted ones) + cameraGranted = newStatuses[Permission.camera]?.isGranted ?? cameraGranted; + micGranted = newStatuses[Permission.microphone]?.isGranted ?? micGranted; + notifGranted = newStatuses[Permission.notification]?.isGranted ?? notifGranted; + + // If any requested permission is now permanently denied -> open settings + final newlyPermanent = missing.where((p) => (newStatuses[p]?.isPermanentlyDenied ?? false) || (newStatuses[p]?.isRestricted ?? false)).toList(); + if (newlyPermanent.isNotEmpty) { + final names = newlyPermanent.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and '); + final message = + newlyPermanent.length == 1 ? '$names permission is permanently denied. Open app settings to allow it.' : '$names permissions are permanently denied. Open app settings to allow them.'; + await LiveCarePermissionService.instance.showOpenSettingsDialog( + context, + title: "Permissions Required", + message: message, + ); + return false; + } - // if (!(await Permission.camera.request().isGranted) || !(await Permission.microphone.request().isGranted)) { - // return false; - // } + return cameraGranted && micGranted && notifGranted; } + // Future askVideoCallPermission() async { + // bool hasPermission = false; + // if (Platform.isIOS) { + // Map statuses = await [Permission.camera, Permission.microphone].request(); + // + // if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted) { + // // Camera permission granted + // hasPermission = true; + // } else { + // hasPermission = false; + // } + // } else { + // Map statuses = await [Permission.camera, Permission.microphone, Permission.notification].request(); + // + // if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted && statuses[Permission.notification] == PermissionStatus.granted) { + // // Camera permission granted + // hasPermission = true; + // } else { + // hasPermission = false; + // } + // } + // + // return hasPermission; + // + // // if (!(await Permission.camera.request().isGranted) || !(await Permission.microphone.request().isGranted)) { + // // return false; + // // } + // } + String getLiveCareType(BuildContext context, int callType) { switch (callType) { case 1: diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart index 9ae7ee30..73da69f5 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -432,6 +432,7 @@ class _ImmediateLiveCarePaymentPageState extends State { ), AppCustomChipWidget( labelText: - "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDate}", + "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDisplayDate}", ), AppCustomChipWidget( labelText: @@ -178,7 +178,7 @@ class _ReviewAppointmentPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - bookAppointmentsViewModel.selectedDoctor.projectName!.toText16(isBold: true), + (bookAppointmentsViewModel.selectedDoctor.projectName ?? "Habib Hospital").toText16(isBold: true), SizedBox(height: 8.h), bookAppointmentsViewModel.appointmentNearestGateResponseModel != null ? Wrap( @@ -286,8 +286,20 @@ class _ReviewAppointmentPageState extends State { } else { //TODO: Add patient Derma package check API Here await bookAppointmentsViewModel.insertSpecificAppointment(onError: (err) { - print(err.data["ErrorEndUserMessage"]); + // print(err.data["ErrorEndUserMessage"]); LoadingUtils.hideFullScreenLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () { + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + // Navigator.pop(context); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); }, onSuccess: (apiResp) async { LoadingUtils.hideFullScreenLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 9d2c2921..cad8e2e7 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -988,7 +988,7 @@ class _SelectClinicPageState extends State { void onLiveCareClinicSelected(GetLiveCareClinicsResponseModel clinic) { bookAppointmentsViewModel.setLiveCareSelectedClinic(clinic); - bookAppointmentsViewModel.setIsDoctorsListLoading(true); + bookAppointmentsViewModel.setIsLiveCareDoctorsListLoading(true); Navigator.of(context).push( CustomPageRoute( page: SelectDoctorPage(), diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 3723bee0..1fc9d9ad 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -75,7 +75,7 @@ class _SelectDoctorPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: LocaleKeys.chooseDoctor.tr(), + title: LocaleKeys.chooseDoctor.tr(context: context), // bottomChild: Container( // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))), // padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h), @@ -95,9 +95,59 @@ class _SelectDoctorPageState extends State { child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.h), child: Consumer(builder: (context, bookAppointmentsVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + return bookAppointmentsViewModel.isLiveCareSchedule + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListView.separated( + padding: EdgeInsets.only(top: 16.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : (bookAppointmentsVM.liveCareDoctorsList.isNotEmpty ? bookAppointmentsVM.liveCareDoctorsList.length : 1), + itemBuilder: (context, index) { + // final isExpanded = bookAppointmentsVM.expandedGroupIndex == index; + final isExpanded = true; + return bookAppointmentsVM.isDoctorsListLoading + ? DoctorCard( + doctorsListResponseModel: DoctorsListResponseModel(), + isLoading: true, + bookAppointmentsViewModel: bookAppointmentsViewModel, + ) + : bookAppointmentsVM.liveCareDoctorsList.isEmpty + ? Utils.getNoDataWidget(context, noDataText: LocaleKeys.noDoctorFound.tr()) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: Padding( + padding: EdgeInsets.all(16.h), + child: DoctorCard( + isLoading: bookAppointmentsVM.isClinicsListLoading, + doctorsListResponseModel: bookAppointmentsVM.liveCareDoctorsList[index], + bookAppointmentsViewModel: bookAppointmentsViewModel, + ).onPress(() { + // onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]); + }), + ), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 24.h), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ SizedBox(height: 16.h), Row( spacing: 8.h, diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index 8fa72d4c..3d12f033 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -7,6 +7,7 @@ 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/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; @@ -144,13 +145,14 @@ class _AppointmentCalendarState extends State { child: selectedDateDisplay.toText16(weight: FontWeight.w500), ), //TODO: Add Next Day Span here - SizedBox( - height: 100.h, - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.start, + dayEvents.isNotEmpty + ? SizedBox( + height: 100.h, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.start, spacing: 6.h, runSpacing: 6.h, children: List.generate( @@ -168,10 +170,13 @@ class _AppointmentCalendarState extends State { ), ), ), - ), + ) + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noFreeSlot.tr(context: context)), + SizedBox(height: 16.h), CustomButton( text: LocaleKeys.select.tr(context: context), + isDisabled: dayEvents.isEmpty, onPressed: () async { if (appState.isAuthenticated) { if(selectedTime == LocaleKeys.waitingAppointment.tr(context: context)){ @@ -186,7 +191,7 @@ class _AppointmentCalendarState extends State { ); } else { bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!); - bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime); + bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay); Navigator.of(context).pop(); Navigator.of(context).push( CustomPageRoute( @@ -304,11 +309,14 @@ class _AppointmentCalendarState extends State { Map timeSlot = {"isoTime": dayEvents[i].isoTime, "start": dayEvents[i].start.toString(), "end": dayEvents[i].end.toString(), "vidaDate": dayEvents[i].vidaDate}; timeList.add(timeSlot); } - selectedTime = dayEvents[selectedButtonIndex].isoTime!; + if (dayEvents.isNotEmpty) { + selectedTime = dayEvents[selectedButtonIndex].isoTime!; + } + setState(() {}); } void _onDaySelected(DateTime day) { - final DateFormat formatter = DateFormat('yyyy-MM-dd'); + final DateFormat formatter = DateFormat('yyyy-MM-dd', "en-US"); setState(() { selectedDateDisplay = DateUtil.getMonthDayYearDateFormatted(day); selectedNextDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day.add(Duration(days: 1)), "en"); diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index 97a941dd..ddb43e9d 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -38,7 +38,7 @@ class DoctorCard extends StatelessWidget { hasShadow: false, ), child: Padding( - padding: EdgeInsets.only(top: 14.h,bottom: 20.h), + padding: EdgeInsets.only(top: 14.h, bottom: 20.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -90,23 +90,21 @@ class DoctorCard extends StatelessWidget { Row( children: [ SizedBox( - width: MediaQuery.of(context).size.width * 0.49, - child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}") - .toString() - .toText16(isBold: true, maxlines: 1), + width: MediaQuery.of(context).size.width * 0.55, + child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}").toString().toText16(isBold: true, maxlines: 1), ).toShimmer2(isShow: isLoading), ], ), SizedBox(height: 2.h), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox( - width: MediaQuery.of(context).size.width * 0.45, child: (isLoading ? "Consultant Cardiologist" : doctorsListResponseModel.speciality!.isNotEmpty - ? doctorsListResponseModel.speciality!.first + ? (doctorsListResponseModel.speciality!.first.length > 32 + ? '${doctorsListResponseModel.speciality!.first.substring(0, 32)}...' + : doctorsListResponseModel.speciality!.first) : "") .toString() .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 2) @@ -121,38 +119,40 @@ class DoctorCard extends StatelessWidget { ).toShimmer2(isShow: isLoading), ], ), + SizedBox(height: 6.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: "${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}", + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + labelText: "${isLoading ? "Olaya Hospital" : doctorsListResponseModel.projectName}", + ).toShimmer2(isShow: isLoading), + bookAppointmentsViewModel.isNearestAppointmentSelected + ? doctorsListResponseModel.nearestFreeSlot != null + ? AppCustomChipWidget( + // labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)), + richText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)) + .toText10(isEnglishOnly: true, color: AppColors.whiteColor), + backgroundColor: AppColors.successColor, + textColor: AppColors.whiteColor, + ).toShimmer2(isShow: isLoading) + : SizedBox.shrink() + : SizedBox.shrink(), + ], + ), ], ), ), Expanded( flex: 1, - child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown) - .toShimmer2(isShow: isLoading), + child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading), ), ], ), - Wrap( - direction: Axis.horizontal, - spacing: 3.h, - runSpacing: 4.h, - children: [ - AppCustomChipWidget( - labelText: "${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}", - ).toShimmer2(isShow: isLoading), - AppCustomChipWidget( - labelText: "${isLoading ? "Olaya Hospital" : doctorsListResponseModel.projectName}", - ).toShimmer2(isShow: isLoading), - bookAppointmentsViewModel.isNearestAppointmentSelected - ? doctorsListResponseModel.nearestFreeSlot != null - ? AppCustomChipWidget( - labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)), - backgroundColor: AppColors.successColor, - textColor: AppColors.whiteColor, - ).toShimmer2(isShow: isLoading) - : SizedBox.shrink() - : SizedBox.shrink(), - ], - ), SizedBox(height: 12.h), CustomButton( text: LocaleKeys.bookAppo.tr(context: context), diff --git a/lib/presentation/book_appointment/widgets/doctor_rating_details.dart b/lib/presentation/book_appointment/widgets/doctor_rating_details.dart new file mode 100644 index 00000000..0ed54862 --- /dev/null +++ b/lib/presentation/book_appointment/widgets/doctor_rating_details.dart @@ -0,0 +1,195 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.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/features/book_appointments/book_appointments_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:provider/provider.dart'; + +class DoctorRatingDetails extends StatelessWidget { + const DoctorRatingDetails({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer(builder: (context, bookAppointmentsVM, child) { + return bookAppointmentsVM.isDoctorRatingDetailsLoading + ? Utils.getLoadingWidget() + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + bookAppointmentsVM.doctorsProfileResponseModel.actualDoctorRate!.ceilToDouble().toString().toText44(isBold: true), + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${bookAppointmentsVM.doctorsProfileResponseModel.noOfPatientsRate} ${LocaleKeys.reviews.tr(context: context)}" + .toText16(weight: FontWeight.w500, color: AppColors.greyInfoTextColor), + RatingBar( + initialRating: bookAppointmentsVM.doctorsProfileResponseModel.actualDoctorRate!.toDouble(), + direction: Axis.horizontal, + allowHalfRating: true, + itemCount: 5, + itemSize: 20.h, + ignoreGestures: true, + ratingWidget: RatingWidget( + full: Icon( + Icons.star, + color: AppColors.ratingColorYellow, + size: 24.h, + ), + half: Icon( + Icons.star_half, + color: AppColors.ratingColorYellow, + ), + empty: Icon( + Icons.star, + color: AppColors.ratingColorYellow, + ), + ), + tapOnlyMode: true, + unratedColor: Colors.grey[500], + itemPadding: EdgeInsets.symmetric(horizontal: 4.0), + onRatingUpdate: (rating) { + print(rating); + }, + ), + ], + ), + SizedBox(height: 8.h), + Container( + margin: EdgeInsets.only(top: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 100.0, + margin: EdgeInsets.only(top: 10.0, left: 15.0, right: 15.0), + child: Text(LocaleKeys.excellent.tr(context: context), style: TextStyle(fontSize: 13.0, color: Colors.black, fontWeight: FontWeight.w600))), + getRatingLine(bookAppointmentsVM.doctorDetailsList[0].ratio, Colors.green[700]!), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), + child: Text("${getRatingWidth(bookAppointmentsVM.doctorDetailsList[0].ratio).round()}%", style: TextStyle(fontSize: 14.0, color: Colors.black, fontWeight: FontWeight.w600)), + ), + ], + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 100.0, + margin: EdgeInsets.only(top: 10.0, left: 15.0, right: 15.0), + child: Text(LocaleKeys.vGood.tr(context: context), style: TextStyle(fontSize: 13.0, color: Colors.black, fontWeight: FontWeight.w600))), + getRatingLine(bookAppointmentsVM.doctorDetailsList[1].ratio, Color(0xffB7B723)), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), + child: Text("${bookAppointmentsVM.doctorDetailsList[1].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: Colors.black, fontWeight: FontWeight.w600)), + ), + ], + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 100.0, + margin: EdgeInsets.only(top: 10.0, left: 15.0, right: 15.0), + child: Text(LocaleKeys.good.tr(context: context), style: TextStyle(fontSize: 13.0, color: Colors.black, fontWeight: FontWeight.w600))), + getRatingLine(bookAppointmentsVM.doctorDetailsList[2].ratio, Color(0xffEBA727)), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), + child: Text("${bookAppointmentsVM.doctorDetailsList[2].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: Colors.black, fontWeight: FontWeight.w600)), + ), + ], + ), + ), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 100.0, + margin: EdgeInsets.only(top: 10.0, left: 15.0, right: 15.0), + child: Text(LocaleKeys.average.tr(context: context), style: TextStyle(fontSize: 13.0, color: Colors.black, fontWeight: FontWeight.w600))), + getRatingLine(bookAppointmentsVM.doctorDetailsList[3].ratio, Color(0xffEB7227)), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), + child: Text("${bookAppointmentsVM.doctorDetailsList[3].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: Colors.black, fontWeight: FontWeight.w600)), + ), + ], + ), + ), + Container( + margin: EdgeInsets.only(bottom: 30.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 100.0, + margin: EdgeInsets.only(top: 10.0, left: 15.0, right: 15.0), + child: Text(LocaleKeys.average.tr(context: context), style: TextStyle(fontSize: 13.0, color: Colors.black, fontWeight: FontWeight.w600))), + getRatingLine(bookAppointmentsVM.doctorDetailsList[4].ratio, Color(0xffE20C0C)), + ], + ), + Container( + margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), + child: Text("${bookAppointmentsVM.doctorDetailsList[4].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: Colors.black, fontWeight: FontWeight.w600)), + ), + ], + ), + ), + ], + ); + }); + } + + double getRatingWidth(num patientNumber) { + var width = patientNumber; + return width.roundToDouble(); + } + + Widget getRatingLine(double patientNumber, Color color) { + return Container( + margin: EdgeInsets.only(top: 10.0), + child: Stack(children: [ + SizedBox( + width: 150.0, + height: 7.h, + child: Container( + color: Colors.grey[300], + ), + ), + SizedBox( + width: patientNumber * 1.55, + height: 7.h, + child: Container( + color: color, + ), + ), + ]), + ); + } +} diff --git a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart index 9b78ecad..840d38cf 100644 --- a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart +++ b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart @@ -1,4 +1,5 @@ import 'dart:developer'; +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -22,7 +23,7 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/map/location_map_widget.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; import 'package:provider/provider.dart'; class CmcSelectionReviewPage extends StatefulWidget { @@ -319,16 +320,27 @@ class _CmcSelectionReviewPageState extends State { CmcUiSelectionHelper.showHospitalSelectionBottomSheet(context: context, onHospitalSelected: (hospital) => context.pop()); } - void _launchDirections(HospitalsModel selectedHospital) { + void _launchDirections(HospitalsModel selectedHospital) async { final double lat = double.parse(selectedHospital.latitude ?? "0.0"); final double lng = double.parse(selectedHospital.longitude ?? "0.0"); if (lat != 0.0 && lng != 0.0) { - MapsLauncher.launchCoordinates( - lat, - lng, - selectedHospital.name ?? "Hospital", - ); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(lat, lng), + title: selectedHospital.name ?? "Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(lat, lng), + title: selectedHospital.name ?? "Hospital", + ); + }); + // MapsLauncher.launchCoordinates( + // lat, + // lng, + // selectedHospital.name ?? "Hospital", + // ); } } diff --git a/lib/presentation/contact_us/widgets/find_us_item_card.dart b/lib/presentation/contact_us/widgets/find_us_item_card.dart index 6e295e08..1774404d 100644 --- a/lib/presentation/contact_us/widgets/find_us_item_card.dart +++ b/lib/presentation/contact_us/widgets/find_us_item_card.dart @@ -1,3 +1,5 @@ +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'; @@ -10,7 +12,7 @@ import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_h 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/chip/app_custom_chip_widget.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; import 'package:url_launcher/url_launcher.dart'; class FindUsItemCard extends StatelessWidget { @@ -82,8 +84,18 @@ class FindUsItemCard extends StatelessWidget { iconColor: AppColors.whiteColor, backgroundColor: AppColors.textColor.withValues(alpha: 0.8), textColor: AppColors.whiteColor, - onChipTap: () { - MapsLauncher.launchCoordinates(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0"), getHMGLocationsModel.locationName!); + onChipTap: () async { + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0")), + title: getHMGLocationsModel.locationName ?? "Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0")), + title: getHMGLocationsModel.locationName ?? "Hospital", + ); + }); }, ), SizedBox(width: 4.w), diff --git a/lib/presentation/e_referral/widget/e_referral_patient_info.dart b/lib/presentation/e_referral/widget/e_referral_patient_info.dart index 0cc5c7ee..61b92c76 100644 --- a/lib/presentation/e_referral/widget/e_referral_patient_info.dart +++ b/lib/presentation/e_referral/widget/e_referral_patient_info.dart @@ -140,6 +140,7 @@ class PatientInformationStepState extends State { controller: _phoneController, padding: const EdgeInsets.all(8), keyboardType: TextInputType.number, + fontFamily: "Poppins", onChange: (value) { formManager.updatePatientPhone(value ?? ''); }, diff --git a/lib/presentation/e_referral/widget/search_e_referral_form.dart b/lib/presentation/e_referral/widget/search_e_referral_form.dart index fa3ae197..33697d5e 100644 --- a/lib/presentation/e_referral/widget/search_e_referral_form.dart +++ b/lib/presentation/e_referral/widget/search_e_referral_form.dart @@ -73,6 +73,7 @@ class SearchEReferralFormFormState extends State { hintText: formManager.searchCriteria == 0 ? "Enter Identification Number" : "Enter Referral Number", labelText: formManager.searchCriteria == 0 ? "Identification Number" : "Referral Number", keyboardType: TextInputType.number, + fontFamily: "Poppins", errorMessage: formManager.errors.searchValue, hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.searchValue), onChange: (value) { @@ -94,6 +95,7 @@ class SearchEReferralFormFormState extends State { controller: _phoneController, padding: const EdgeInsets.all(8), keyboardType: TextInputType.number, + fontFamily: "Poppins", onChange: (value) { formManager.updateSearchPhone(value ?? ''); // _validateForm(formManager); diff --git a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart index e86104b2..5a368a3b 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart @@ -526,8 +526,8 @@ class CallAmbulancePage extends StatelessWidget { textPlaceInput(context) { return Consumer(builder: (_, vm, __) { - print("the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); - return SizedBox( + // print("the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); + return (vm.geocodeResponse != null && vm.geocodeResponse!.results.isNotEmpty) ? SizedBox( width: MediaQuery.sizeOf(context).width, child: TextInputWidget( labelText: LocaleKeys.enterPickupLocationManually.tr(context: context), @@ -549,7 +549,7 @@ class CallAmbulancePage extends StatelessWidget { ).onPress(() { openLocationInputBottomSheet(context); }), - ); + ) : SizedBox.shrink(); }); } diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart index 3850c1f4..999a43ee 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart @@ -159,6 +159,15 @@ class ErOnlineCheckinHome extends StatelessWidget { await vm.getPatientERPaymentInformation(onSuccess: (response) { LoaderBottomSheet.hideLoader(); vm.navigateToEROnlineCheckInPaymentPage(); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); }); }, onHospitalSearch: (value) { diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart index 05348544..463121d9 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart @@ -62,25 +62,22 @@ class ErOnlineCheckinPaymentDetailsPage extends StatelessWidget { SizedBox(height: 12.h), Wrap( direction: Axis.horizontal, - spacing: 6.w, + spacing: 4.w, runSpacing: 6.h, children: [ AppCustomChipWidget( - labelText: "File No.: ${appState.getAuthenticatedUser()!.patientId!.toString()}", - labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + labelText: "${LocaleKeys.fileno.tr(context: context)}.: ${appState.getAuthenticatedUser()!.patientId!.toString()}", ), AppCustomChipWidget( labelText: LocaleKeys.erClinic.tr(), - labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), ), AppCustomChipWidget( labelText: emergencyServicesViewModel.selectedHospital!.name, - labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), ), AppCustomChipWidget( icon: AppAssets.calendar, labelText: DateUtil.formatDateToDate(DateTime.now(), false), - labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 8.w), ), ], ), diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart index 70a5b08b..35101978 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart @@ -337,8 +337,6 @@ class _ErOnlineCheckinPaymentPageState extends State } startApplePay() async { - // showCommonBottomSheet(context, - // child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); LoaderBottomSheet.showLoader(); transID = Utils.getAdvancePaymentTransID( emergencyServicesViewModel.selectedHospital!.iD, @@ -405,8 +403,8 @@ class _ErOnlineCheckinPaymentPageState extends State applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", onFailed: (failureResult) async { + LoaderBottomSheet.hideLoader(); log("failureResult: ${failureResult.message.toString()}"); - Navigator.of(context).pop(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), @@ -416,6 +414,7 @@ class _ErOnlineCheckinPaymentPageState extends State ); }, onSucceeded: (successResult) async { + LoaderBottomSheet.hideLoader(); log("successResult: ${successResult.responseMessage.toString()}"); selectedPaymentMethod = successResult.paymentOption ?? "VISA"; checkPaymentStatus(); diff --git a/lib/presentation/habib_wallet/recharge_wallet_page.dart b/lib/presentation/habib_wallet/recharge_wallet_page.dart index b8e2d01f..78608e8b 100644 --- a/lib/presentation/habib_wallet/recharge_wallet_page.dart +++ b/lib/presentation/habib_wallet/recharge_wallet_page.dart @@ -36,6 +36,7 @@ class _RechargeWalletPageState extends State { late HabibWalletViewModel habibWalletVM; late AppState appState; final TextEditingController amountTextController = TextEditingController(); + final TextEditingController notesTextController = TextEditingController(); @override void initState() { @@ -97,11 +98,12 @@ class _RechargeWalletPageState extends State { isBorderAllowed: false, isAllowLeadingIcon: true, autoFocus: true, - fontSize: 40, + fontSize: 40.f, padding: EdgeInsets.symmetric(horizontal: 8.h, vertical: 0.h), focusNode: textFocusNode, isWalletAmountInput: true, keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), + fontFamily: "Poppins", // leadingIcon: AppAssets.student_card, ), ), @@ -209,23 +211,19 @@ class _RechargeWalletPageState extends State { SizedBox(height: 16.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.notes_icon, width: 40.h, height: 40.h), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.notes.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), - "Lorem Ipsum".toText14(color: AppColors.textColor, weight: FontWeight.w500, letterSpacing: -0.2), - ], - ), - ], - ), - ], + TextInputWidget( + labelText: LocaleKeys.notes.tr(context: context), + hintText: "", + controller: notesTextController, + keyboardType: TextInputType.text, + isEnable: true, + prefix: null, + autoFocus: false, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + leadingIcon: AppAssets.notes_icon, + hasError: false, ), SizedBox(height: 8.h), ], @@ -271,6 +269,7 @@ class _RechargeWalletPageState extends State { ); } else { habibWalletVM.setWalletRechargeAmount(num.parse(amountTextController.text)); + habibWalletVM.setNotesText(notesTextController.text); // habibWalletVM.setDepositorDetails(appState.getAuthenticatedUser()!.patientId.toString(), "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", // appState.getAuthenticatedUser()!.mobileNumber!); Navigator.of(context).push( diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index 3dec5aa8..4585b7d0 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -316,6 +316,8 @@ class _WalletPaymentConfirmPageState extends State { context, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), callBackFunc: () { + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); Navigator.of(context).pop(); Navigator.of(context).pop(); }, diff --git a/lib/presentation/habib_wallet/widgets/select-medical_file.dart b/lib/presentation/habib_wallet/widgets/select-medical_file.dart index aaa00367..6fed6522 100644 --- a/lib/presentation/habib_wallet/widgets/select-medical_file.dart +++ b/lib/presentation/habib_wallet/widgets/select-medical_file.dart @@ -28,6 +28,8 @@ 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/habib_wallet/habib_wallet_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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -145,6 +147,10 @@ class _MultiPageBottomSheetState extends State { ], ).paddingAll(16.h), ).onPress(() { + habibWalletVM.setDepositorDetails(appState.getAuthenticatedUser()!.patientId!.toString(), + "${appState.getAuthenticatedUser()!.firstName.toString()} ${appState.getAuthenticatedUser()!.lastName.toString()}", appState.getAuthenticatedUser()!.mobileNumber!.toString()); + habibWalletVM.setCurrentIndex(0); + habibWalletVM.setSelectedRechargeType(1); Navigator.of(context).pop(); }), SizedBox(height: 16.h), @@ -167,7 +173,24 @@ class _MultiPageBottomSheetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), ], ).paddingAll(16.h), - ), + ).onPress(() { + DialogService dialogService = getIt.get(); + dialogService.showFamilyBottomSheetWithoutH( + label: LocaleKeys.familyTitle.tr(context: context), + message: "", + isShowManageButton: false, + isForWalletRecharge: true, + onSwitchPress: (FamilyFileResponseModelLists profile) { + habibWalletVM.setDepositorDetails(profile.responseId.toString(), profile.patientName.toString(), profile.mobileNumber.toString()); + habibWalletVM.setCurrentIndex(0); + habibWalletVM.setSelectedRechargeType(2); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + + // medicalFileViewModel.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); + }, + profiles: getIt.get().patientFamilyFiles); + }), SizedBox(height: 16.h), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( diff --git a/lib/presentation/health_calculators_and_converts/health_calculators_page.dart b/lib/presentation/health_calculators_and_converts/health_calculators_page.dart index 95e9aec6..aae25b03 100644 --- a/lib/presentation/health_calculators_and_converts/health_calculators_page.dart +++ b/lib/presentation/health_calculators_and_converts/health_calculators_page.dart @@ -204,7 +204,7 @@ class _HealthCalculatorsPageState extends State { crossAxisCount: 3, // 4 icons per row crossAxisSpacing: 16.w, mainAxisSpacing: 16.w, - childAspectRatio: 0.80), + childAspectRatio: 0.85), physics: NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: type == HealthCalculatorEnum.general ? generalHealthServices.length : womenHealthServices.length, @@ -214,6 +214,7 @@ class _HealthCalculatorsPageState extends State { icon: type == HealthCalculatorEnum.general ? generalHealthServices[index].icon : womenHealthServices[index].icon, labelText: type == HealthCalculatorEnum.general ? generalHealthServices[index].title : womenHealthServices[index].title, onTap: () { + Navigator.pop(context); Navigator.of(context).push( CustomPageRoute( page: HealthCalculatorDetailedPage( diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 10b93423..9aaf1591 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -29,6 +29,7 @@ import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dar import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart'; +import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -150,7 +151,7 @@ class ServicesPage extends StatelessWidget { }), HmgServicesComponentModel( 3, - "Blood Donation".needTranslation, + LocaleKeys.bloodDonation.tr(context: getIt.get().navigatorKey.currentContext!), "".needTranslation, AppAssets.blood_donation_icon, bgColor: AppColors.bloodDonationCardColor, @@ -158,14 +159,28 @@ class ServicesPage extends StatelessWidget { route: AppRoutes.bloodDonationPage, ), HmgServicesComponentModel( - 3, - "My Child Vaccine".needTranslation, + 113, + LocaleKeys.servicePriceList.tr(context: getIt.get().navigatorKey.currentContext!), "".needTranslation, - AppAssets.my_child_vaccine_icon, - bgColor: AppColors.myChildVaccineCardColor, - true, - route: AppRoutes.myChildVaccine, - ), + AppAssets.saudi_riyal_icon, + bgColor: AppColors.textColor, + false, + route: null, + onTap: () { + Navigator.of(getIt.get().navigatorKey.currentContext!).push( + CustomPageRoute(page: ServicesPriceListPage()), + ); + }, + ) + // HmgServicesComponentModel( + // 3, + // "My Child Vaccine".needTranslation, + // "".needTranslation, + // AppAssets.my_child_vaccine_icon, + // bgColor: AppColors.myChildVaccineCardColor, + // true, + // route: AppRoutes.myChildVaccine, + // ), // HmgServicesComponentModel( // 11, // "Covid 19 Test".needTranslation, @@ -302,6 +317,9 @@ class ServicesPage extends StatelessWidget { Widget build(BuildContext context) { bloodDonationViewModel = Provider.of(context); medicalFileViewModel = Provider.of(context); + + hmgServices.removeWhere((element) => Utils.havePrivilege(element.action) == false); + return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( @@ -382,7 +400,7 @@ class ServicesPage extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h), LocaleKeys.habibWallet.tr().toText14(weight: FontWeight.w600, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), ], ), Spacer(), @@ -442,7 +460,7 @@ class ServicesPage extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h), LocaleKeys.medicalFile.tr().toText16(weight: FontWeight.w600, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), ], ), Spacer(), @@ -667,7 +685,7 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.w), - LocaleKeys.hmgContact.tr().toText14(weight: FontWeight.w500) + Expanded(child: LocaleKeys.hmgContact.tr().toText14(weight: FontWeight.w500)) ], ), ), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index e0c15d0f..a8a64fa6 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -652,7 +652,7 @@ class _LandingPageState extends State { ], ).paddingSymmetrical(24.w, 0.h), SizedBox( - height: 411.h, + height: 431.h, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: LandingPageData.getServiceCardsList.length, diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index e09b1621..72565d03 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -87,7 +87,7 @@ class HabibWalletCard extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.h), - habibWalletVM.habibWalletAmount.toString().toText32(isBold: true).toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), + habibWalletVM.habibWalletAmount.toString().toText32(isBold: true, isEnglishOnly: true).toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), ], ); }), diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index cf49a263..84ef6fa2 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -65,11 +65,11 @@ class SmallServiceCard extends StatelessWidget { Utils.buildSvgWithAssets(icon: icon, iconColor: iconColor, width: 32.h, height: 32.h), SizedBox(height: 6.h), if (isFoldable || isTablet) ...[ - title.tr(context: context).toText11(color: textColor, weight: FontWeight.w500, isCenter: true, letterSpacing: -0.2), - subtitle.tr(context: context).toText11(color: textColor, weight: FontWeight.w500, isCenter: true, letterSpacing: -0.2), + title.tr(context: context).toText10(color: textColor, weight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxlines: 1), + subtitle.tr(context: context).toText10(color: textColor, weight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxlines: 1), ] else ...[ - title.tr(context: context).toText12(color: textColor, fontWeight: FontWeight.w500, isCenter: true, letterSpacing: -0.2), - subtitle.tr(context: context).toText12(color: textColor, fontWeight: FontWeight.w500, isCenter: true, letterSpacing: -0.2), + title.tr(context: context).toText12(color: textColor, fontWeight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxLine: 1), + subtitle.tr(context: context).toText12(color: textColor, fontWeight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxLine: 1), ] ], ), diff --git a/lib/presentation/home_health_care/hhc_selection_review_page.dart b/lib/presentation/home_health_care/hhc_selection_review_page.dart index ae2430af..9a9342d7 100644 --- a/lib/presentation/home_health_care/hhc_selection_review_page.dart +++ b/lib/presentation/home_health_care/hhc_selection_review_page.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -19,7 +21,7 @@ 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/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/map/location_map_widget.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; import 'package:provider/provider.dart'; class HhcSelectionReviewPage extends StatefulWidget { @@ -142,9 +144,19 @@ class _HhcSelectionReviewPageState extends State { ); } - void _launchDirectionsToLocation(double lat, double lng, String locationName) { + void _launchDirectionsToLocation(double lat, double lng, String locationName) async { if (lat != 0.0 && lng != 0.0) { - MapsLauncher.launchCoordinates(lat, lng, locationName); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(lat, lng), + title: locationName, + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(lat, lng), + title: locationName, + ); + }); } } diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index c737732a..35be7f9e 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -1,6 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -11,16 +13,20 @@ import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.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/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:provider/provider.dart'; class PatientInsuranceCardUpdateCard extends StatelessWidget { PatientInsuranceCardUpdateCard({super.key}); late InsuranceViewModel insuranceViewModel; + late AppState appState; @override Widget build(BuildContext context) { insuranceViewModel = Provider.of(context); + appState = getIt.get(); return Column( mainAxisSize: MainAxisSize.min, children: [ @@ -49,7 +55,7 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Haroon Amjad".toText16(weight: FontWeight.w600), + insuranceViewModel.patientInsuranceUpdateResponseModel!.memberName!.toText16(weight: FontWeight.w600), "Policy: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.policyNumber}".toText12(isBold: true, color: AppColors.lightGrayColor), SizedBox(height: 8.h), Row( @@ -99,7 +105,41 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { iconColor: AppColors.whiteColor, iconSize: 20.w, text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", - onPressed: () {}, + onPressed: () { + LoaderBottomSheet.showLoader(); + insuranceViewModel.updatePatientInsuranceCard( + patientID: appState.getAuthenticatedUser()!.patientId!, + patientType: appState.getAuthenticatedUser()!.patientType!, + patientIdentificationID: appState.getAuthenticatedUser()!.patientIdentificationNo!, + mobileNo: appState.getAuthenticatedUser()!.mobileNumber!, + insuranceCardImage: "", + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.success.tr(context: context), + context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr(context: context)), + callBackFunc: () { + Navigator.pop(context); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () { + Navigator.pop(context); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, backgroundColor: insuranceViewModel.patientInsuranceUpdateResponseModel != null ? AppColors.successColor : AppColors.lightGrayBGColor, borderColor: AppColors.successColor.withOpacity(0.01), textColor: AppColors.whiteColor, diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 84fdee4b..c9ec7909 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -75,10 +75,11 @@ class PatientInsuranceCard extends StatelessWidget { children: [ AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, - labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", + // labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", + richText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}".toText10(isEnglishOnly: true), labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), ), - AppCustomChipWidget(labelText: LocaleKeys.patientCardID.tr(namedArgs: {'id': insuranceCardDetailsModel.patientCardID ?? ''}, context: context)), + AppCustomChipWidget(richText: LocaleKeys.patientCardID.tr(namedArgs: {'id': insuranceCardDetailsModel.patientCardID ?? ''}, context: context).toText10(isEnglishOnly: true)), ], ), SizedBox(height: 10.h), diff --git a/lib/presentation/lab/alphabeticScroll.dart b/lib/presentation/lab/alphabeticScroll.dart index 29ded695..95d9da45 100644 --- a/lib/presentation/lab/alphabeticScroll.dart +++ b/lib/presentation/lab/alphabeticScroll.dart @@ -203,7 +203,6 @@ class _AlphabetScrollPageState extends State { verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( - appState: getIt(), onTap: () { if (items[itemIndex].model != null) { diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index a8460ca2..ddac1bb8 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -250,7 +250,7 @@ class _MedicalFilePageState extends State { children: [ AppCustomChipWidget( icon: AppAssets.file_icon, - labelText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}", + richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isEnglishOnly: true), labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), ), AppCustomChipWidget( @@ -1579,6 +1579,7 @@ class _MedicalFilePageState extends State { weight: FontWeight.w600, ), ), + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, width: 18.w, height: 18.h), ], ), SizedBox(height: 14.h), @@ -1601,6 +1602,7 @@ class _MedicalFilePageState extends State { child: value.toText17( isBold: true, color: AppColors.textColor, + isEnglishOnly: true ), ), if (unit.isNotEmpty) ...[ diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart index fcdb12e4..93cf17ff 100644 --- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart +++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/loader/bottomsheet_loader.dart'; import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; @@ -197,49 +198,23 @@ class _PatientSickleavesListPageState extends State { ], ), SizedBox(height: 8.h), - Row( + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, children: [ - CustomButton( - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(sickLeave.appointmentDate), false), - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, + AppCustomChipWidget( + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(sickLeave.appointmentDate), false), ), - SizedBox(width: 8.h), - CustomButton( - text: model.isSickLeavesSortByClinic ? sickLeave.projectName! : sickLeave.clinicName!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, + AppCustomChipWidget( + labelText: model.isSickLeavesSortByClinic ? sickLeave.projectName! : sickLeave.clinicName!, ), - SizedBox(width: 8.h), - CustomButton( - text: "${sickLeave.sickLeaveDays} Days", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, + AppCustomChipWidget( + labelText: "${sickLeave.sickLeaveDays} Days", ), ], ), - SizedBox(height: 8.h), + SizedBox(height: 12.h), Row( children: [ Expanded( diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart index 3d3624d0..73dd1652 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -18,6 +18,8 @@ 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/routes/custom_page_route.dart'; +import 'dart:ui' as ui; + class MedicalFileAppointmentCard extends StatelessWidget { final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; final MyAppointmentsViewModel myAppointmentsViewModel; @@ -39,11 +41,12 @@ class MedicalFileAppointmentCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ AppCustomChipWidget( - richText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false) - .toText12( - color: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, - fontWeight: FontWeight.w500) - .paddingOnly(left: 8.w), + richText: Directionality( + textDirection: ui.TextDirection.ltr, + child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false) + .toText12(color: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, fontWeight: FontWeight.w500, isEnglishOnly: true) + .paddingSymmetrical(8.w, 0), + ), icon: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppAssets.appointment_calendar_icon : AppAssets.alarm_clock_icon, iconColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, iconSize: 16.w, @@ -71,9 +74,7 @@ class MedicalFileAppointmentCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (patientAppointmentHistoryResponseModel.doctorNameObj ?? "") - .toText14(isBold: true, maxlines: 1) - .toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), (patientAppointmentHistoryResponseModel.clinicName ?? "") .toText12(maxLine: 1, fontWeight: FontWeight.w500, color: AppColors.greyTextColor) .toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), @@ -103,10 +104,8 @@ class MedicalFileAppointmentCard extends StatelessWidget { // widget.myAppointmentsViewModel.getPatientAppointments(true, false); }); }, - backgroundColor: - AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), - borderColor: - AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), + borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), fontSize: 14.f, fontWeight: FontWeight.w500, diff --git a/lib/presentation/monthly_report/monthly_report.dart b/lib/presentation/monthly_report/monthly_report.dart index 474863ba..43c7fc1d 100644 --- a/lib/presentation/monthly_report/monthly_report.dart +++ b/lib/presentation/monthly_report/monthly_report.dart @@ -80,7 +80,10 @@ class MonthlyReport extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), - "${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, weight: FontWeight.w500), + SizedBox( + width: MediaQuery.of(context).size.width * 0.7, + child: "${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, weight: FontWeight.w500, maxlines: 2), + ), ], ), ], diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 175cfb18..ea305b0e 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -8,6 +8,7 @@ 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/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -25,6 +26,7 @@ class FamilyCards extends StatefulWidget { final bool isRequestDesign; final bool isLeftAligned; final bool isShowRemoveButton; + final bool isForWalletRecharge; const FamilyCards( {super.key, @@ -35,6 +37,7 @@ class FamilyCards extends StatefulWidget { this.isBottomSheet = false, this.isRequestDesign = false, this.isLeftAligned = false, + this.isForWalletRecharge = false, this.isShowRemoveButton = false}); @override @@ -206,7 +209,21 @@ class _FamilyCardsState extends State { height: 4.h, ), Spacer(), - CustomButton( + widget.isForWalletRecharge ? CustomButton( + height: 40.h, + onPressed: () { + widget.onSelect(profile); + // if (canSwitch) widget.onSelect(profile); + }, + text: LocaleKeys.select.tr(context: context), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 13.h, + icon: AppAssets.activeCheck, + iconColor: isActive || !canSwitch ? (isActive ? null : AppColors.greyTextColor) : AppColors.primaryRedColor, + padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), + ).paddingOnly(top: 0, bottom: 0) : CustomButton( height: 40.h, onPressed: () { if (canSwitch) widget.onSelect(profile); diff --git a/lib/presentation/notifications/notification_details_page.dart b/lib/presentation/notifications/notification_details_page.dart index 949b8728..1607e8f5 100644 --- a/lib/presentation/notifications/notification_details_page.dart +++ b/lib/presentation/notifications/notification_details_page.dart @@ -20,26 +20,18 @@ class NotificationDetailsPage extends StatelessWidget { @override Widget build(BuildContext context) { - // Debug logging - print('=== Notification Details ==='); - print('Message: ${notification.message}'); - print('MessageType: ${notification.messageType}'); - print('MessageTypeData: ${notification.messageTypeData}'); - print('VideoURL: ${notification.videoURL}'); - print('========================'); - return CollapsingListView( title: LocaleKeys.notificationDetails.tr(), - trailing: IconButton( - icon: Icon( - Icons.share_outlined, - size: 24.h, - color: AppColors.textColor, - ), - onPressed: () { - _shareNotification(); - }, - ), + // trailing: IconButton( + // icon: Icon( + // Icons.share_outlined, + // size: 24.h, + // color: AppColors.textColor, + // ), + // onPressed: () { + // _shareNotification(); + // }, + // ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -230,21 +222,21 @@ class NotificationDetailsPage extends StatelessWidget { ), // Additional notification info - if (notification.notificationType != null && notification.notificationType!.isNotEmpty) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.typeNotification.tr().toText14( - weight: FontWeight.w600, - color: AppColors.greyTextColor, - ), - SizedBox(height: 8.h), - notification.notificationType!.toText16( - weight: FontWeight.w400, - color: AppColors.textColor, - ), - ], - ), + // if (notification.notificationType != null && notification.notificationType!.isNotEmpty) + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // LocaleKeys.typeNotification.tr().toText14( + // weight: FontWeight.w600, + // color: AppColors.greyTextColor, + // ), + // SizedBox(height: 8.h), + // notification.notificationType!.toText16( + // weight: FontWeight.w400, + // color: AppColors.textColor, + // ), + // ], + // ), ], ), ); diff --git a/lib/presentation/notifications/notifications_list_page.dart b/lib/presentation/notifications/notifications_list_page.dart index c5f5d120..6c344b2e 100644 --- a/lib/presentation/notifications/notifications_list_page.dart +++ b/lib/presentation/notifications/notifications_list_page.dart @@ -23,7 +23,7 @@ class NotificationsListPage extends StatelessWidget { @override Widget build(BuildContext context) { return CollapsingListView( - title: LocaleKeys.notifications.tr(context: context), + title: LocaleKeys.notification.tr(context: context), child: SingleChildScrollView( child: Consumer(builder: (context, notificationsVM, child) { return Container( diff --git a/lib/presentation/parking/parking_slot.dart b/lib/presentation/parking/parking_slot.dart index 52ab181d..e8c4f097 100644 --- a/lib/presentation/parking/parking_slot.dart +++ b/lib/presentation/parking/parking_slot.dart @@ -1,4 +1,4 @@ - +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -7,12 +7,12 @@ 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/qr_parking/models/qr_parking_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:map_launcher/map_launcher.dart'; import '../../features/qr_parking/qr_parking_view_model.dart'; import '../../theme/colors.dart'; import '../../widgets/appbar/app_bar_widget.dart'; import '../../widgets/buttons/custom_button.dart'; import '../../widgets/chip/app_custom_chip_widget.dart'; -import 'package:maps_launcher/maps_launcher.dart'; import 'package:provider/provider.dart'; import '../../widgets/routes/custom_page_route.dart'; @@ -29,7 +29,7 @@ class ParkingSlot extends StatefulWidget { } class _ParkingSlotState extends State { - void _openDirection() { + void _openDirection() async { final lat = widget.model.latitude; final lng = widget.model.longitude; @@ -48,7 +48,17 @@ class _ParkingSlotState extends State { return; } - MapsLauncher.launchCoordinates(lat, lng); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(lat, lng), + title: "Parking Location", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(lat, lng), + title: "Parking Location", + ); + }); } diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 9b1ae6df..5bb9cbd7 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -139,7 +139,7 @@ class _PrescriptionDetailPageState extends State { labelText: LocaleKeys.ratingValue.tr(namedArgs: {'rating': widget.prescriptionsResponseModel.decimalDoctorRate.toString()}, context: context), ), AppCustomChipWidget( - labelText: widget.prescriptionsResponseModel.name!, + labelText: widget.prescriptionsResponseModel.name ?? "", ), ], ), @@ -148,7 +148,7 @@ class _PrescriptionDetailPageState extends State { text: LocaleKeys.downloadPrescription.tr(context: context), onPressed: () async { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionPDFPleaseWait.tr(context: context)); - await prescriptionVM.getPrescriptionPDFBase64(widget.prescriptionsResponseModel).then((val) async { + await prescriptionVM.getPrescriptionPDFBase64(widget.prescriptionsResponseModel, onSuccess: (value) async { LoaderBottomSheet.hideLoader(); if (prescriptionVM.prescriptionPDFBase64Data.isNotEmpty) { String path = await Utils.createFileFromString(prescriptionVM.prescriptionPDFBase64Data, "pdf"); @@ -164,6 +164,15 @@ class _PrescriptionDetailPageState extends State { ); } } + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); }); }, backgroundColor: AppColors.successColor.withValues(alpha: 0.15), diff --git a/lib/presentation/prescriptions/prescription_item_view.dart b/lib/presentation/prescriptions/prescription_item_view.dart index a2a3e7fb..a41b53ff 100644 --- a/lib/presentation/prescriptions/prescription_item_view.dart +++ b/lib/presentation/prescriptions/prescription_item_view.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo 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/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; @@ -56,74 +57,18 @@ class PrescriptionItemView extends StatelessWidget { spacing: 6.h, runSpacing: 6.h, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.route.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].route}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ).toShimmer2(isShow: isLoading), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.frequency.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].frequency}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ).toShimmer2(isShow: isLoading), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ).toShimmer2(isShow: isLoading), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.days.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].days}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ).toShimmer2(isShow: isLoading), - ], - ), + AppCustomChipWidget( + labelText: "${LocaleKeys.route.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].route}", + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + labelText: "${LocaleKeys.frequency.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].frequency}", + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + labelText: "${LocaleKeys.dailyDoses.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + labelText: "${LocaleKeys.days.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].days}", + ).toShimmer2(isShow: isLoading), ], ).paddingSymmetrical(16.h, 0.h), SizedBox(height: 8.h), diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index e2a862bb..cd507d41 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -19,6 +19,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.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/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; @@ -164,7 +165,7 @@ class _PrescriptionsListPageState extends State { ], ), SizedBox(height: 8.h), - model.patientPrescriptionOrdersViewList[index].filterName!.toText16(isBold: true) + (model.patientPrescriptionOrdersViewList[index].filterName ?? "").toText16(isBold: true) ], ), ), @@ -207,32 +208,16 @@ class _PrescriptionsListPageState extends State { ], ), SizedBox(height: 8.h), - Row( + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, children: [ - CustomButton( - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescription.appointmentDate), false), - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, + AppCustomChipWidget( + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescription.appointmentDate), false), ), - SizedBox(width: 8.h), - CustomButton( - text: model.isSortByClinic ? prescription.name! : prescription.clinicDescription!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, + AppCustomChipWidget( + labelText: model.isSortByClinic ? prescription.name ?? "" : prescription.clinicDescription!, ), ], ), diff --git a/lib/presentation/servicesPriceList/services_price_list_page.dart b/lib/presentation/servicesPriceList/services_price_list_page.dart new file mode 100644 index 00000000..0ee81ae3 --- /dev/null +++ b/lib/presentation/servicesPriceList/services_price_list_page.dart @@ -0,0 +1,199 @@ +import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_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:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class ServicesPriceListPage extends StatefulWidget { + const ServicesPriceListPage({super.key}); + + @override + State createState() => _ServicesPriceListPageState(); +} + +class _ServicesPriceListPageState extends State { + late ServicesPriceListViewModel servicesPriceListViewModel; + late AppState appState; + + @override + void initState() { + scheduleMicrotask(() { + servicesPriceListViewModel.initServicesPriceListProvider(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + servicesPriceListViewModel = Provider.of(context, listen: false); + appState = getIt.get(); + + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.servicePriceList.tr(context: context), + child: SingleChildScrollView( + padding: EdgeInsets.all(24.h), + child: Consumer(builder: (context, model, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: LocaleKeys.servicePriceListDesc.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + ), + ), + SizedBox(height: 16.h), + model.isServicesPriceListLoading + ? ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: NeverScrollableScrollPhysics(), + itemCount: 5, + separatorBuilder: (context, index) => SizedBox(height: 12.h), + itemBuilder: (context, index) => _buildLoadingCard(), + ) + : model.filteredServicesPriceList.isEmpty + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(40.h), + child: Center( + child: LocaleKeys.noDataAvailable.tr(context: context).toText16( + color: AppColors.textColorLight, + ), + ), + ) + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.serviceName.tr(context: context).toText18(weight: FontWeight.bold, color: AppColors.textColor), + SizedBox(height: 16.h), + ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: NeverScrollableScrollPhysics(), + itemCount: model.filteredServicesPriceList.length, + separatorBuilder: (context, index) => Divider(height: 1.h, color: AppColors.dividerColor).withVerticalPadding(), + itemBuilder: (context, index) { + final service = model.filteredServicesPriceList[index]; + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: (appState.isArabic() ? service.nameAR ?? service.nameEN ?? '' : service.nameEN ?? service.nameAR ?? '').toText16( + weight: FontWeight.w500, + color: AppColors.textColor, + ), + ), + SizedBox(width: 12.w), + Utils.getPaymentAmountWithSymbol('${service.price ?? 0}'.toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), + ], + ), + ], + ), + ), + ), + ); + }, + ), + ], + ), + ), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: LocaleKeys.servicePriceListRights.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + ), + ), + ], + ); + }), + ), + ), + ); + } + + Widget _buildLoadingCard() { + return Shimmer.fromColors( + baseColor: AppColors.greyColor.withValues(alpha: 0.3), + highlightColor: AppColors.greyColor.withValues(alpha: 0.1), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + height: 20.h, + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(4.r), + ), + ), + ), + SizedBox(width: 12.w), + Container( + width: 80.w, + height: 32.h, + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(12.r), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart b/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart index 7c537ba7..f30d87a9 100644 --- a/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart +++ b/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart @@ -24,8 +24,8 @@ class HealthDashboard extends StatefulWidget { class _HealthDashboardState extends State with SingleTickerProviderStateMixin { late TabController _tabController; - final dateFormat = DateFormat('MMM dd, yyyy'); - final timeFormat = DateFormat('hh:mm a'); + final dateFormat = DateFormat('MMM dd, yyyy', "en-US"); + final timeFormat = DateFormat('hh:mm a', "en-US"); @override void initState() { diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart index c8d8cde9..f1e8413d 100644 --- a/lib/presentation/symptoms_checker/user_info_selection.dart +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; 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/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -39,13 +40,18 @@ class _UserInfoSelectionPageState extends State { if (appState.isAuthenticated) { final user = appState.getAuthenticatedUser(); + getIt.get().getPatientVitalSign(); if (user == null) return; // Populate gender (gender is int: 1=Male, 2=Female) // Use internal keys (male/female) for storage if (user.gender != null) { - String genderKey = user.gender == 1 ? "male" : user.gender == 2 ? "female" : "other"; + String genderKey = user.gender == 1 + ? "male" + : user.gender == 2 + ? "female" + : "other"; viewModel.setGender(genderKey); } @@ -64,13 +70,14 @@ class _UserInfoSelectionPageState extends State { // If not authenticated or fields are empty, user will fill them manually } - _buildEditInfoTile({ + Widget _buildEditInfoTile({ required String leadingIcon, required String title, required String subTitle, required VoidCallback onTap, required String trailingIcon, required BuildContext context, + required HmgServicesViewModel hmgServicesVM, Color? iconColor, }) { return InkWell( @@ -92,7 +99,9 @@ class _UserInfoSelectionPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ title.toText14(weight: FontWeight.w500), - subTitle.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + subTitle + .toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) + .toShimmer2(isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading), ], ), ], @@ -144,6 +153,7 @@ class _UserInfoSelectionPageState extends State { age--; } userAgeFromDOB = age; + getIt.get().setSelectedAge(userAgeFromDOB); } catch (e) { // If date parsing fails, ignore } @@ -154,25 +164,20 @@ class _UserInfoSelectionPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: Consumer( - builder: (context, viewModel, child) { + body: Consumer2( + builder: (context, viewModel, hmgServicesVM, child) { // Check if any field is empty - bool hasEmptyFields = viewModel.selectedGender == null || - viewModel.selectedAge == null || - viewModel.selectedHeight == null || - viewModel.selectedWeight == null; + bool hasEmptyFields = viewModel.selectedGender == null || viewModel.selectedAge == null || viewModel.selectedHeight == null || viewModel.selectedWeight == null; // Get display values String genderText = _getLocalizedGender(viewModel.selectedGender, context); // Show age calculated from DOB (prefer viewModel's age, fallback to calculated from user's DOB) int? displayAge = viewModel.selectedAge ?? userAgeFromDOB; String ageText = displayAge != null ? "$displayAge ${LocaleKeys.years.tr(context: context)}" : LocaleKeys.notSet.tr(context: context); - String heightText = viewModel.selectedHeight != null - ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" - : LocaleKeys.notSet.tr(context: context); - String weightText = viewModel.selectedWeight != null - ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" - : LocaleKeys.notSet.tr(context: context); + + String heightText = viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : LocaleKeys.notSet.tr(context: context); + + String weightText = viewModel.selectedWeight != null ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" : LocaleKeys.notSet.tr(context: context); return Column( children: [ @@ -195,53 +200,53 @@ class _UserInfoSelectionPageState extends State { ), SizedBox(height: 32.h), _buildEditInfoTile( - context: context, - leadingIcon: AppAssets.genderIcon, - title: LocaleKeys.gender.tr(context: context), - subTitle: genderText, - onTap: () { - viewModel.setUserInfoPage(0, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); - }, - trailingIcon: AppAssets.edit_icon, - ), + context: context, + leadingIcon: AppAssets.genderIcon, + title: LocaleKeys.gender.tr(context: context), + subTitle: genderText, + onTap: () { + viewModel.setUserInfoPage(0, isSinglePageEdit: true); + context.navigateWithName(AppRoutes.userInfoFlowManager); + }, + trailingIcon: AppAssets.edit_icon, + hmgServicesVM: hmgServicesVM), _getDivider(), _buildEditInfoTile( - context: context, - leadingIcon: AppAssets.calendarGrey, - title: LocaleKeys.age.tr(context: context), - subTitle: ageText, - iconColor: AppColors.greyTextColor, - onTap: () { - viewModel.setUserInfoPage(1, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); - }, - trailingIcon: AppAssets.edit_icon, - ), + context: context, + leadingIcon: AppAssets.calendarGrey, + title: LocaleKeys.age.tr(context: context), + subTitle: ageText, + iconColor: AppColors.greyTextColor, + onTap: () { + viewModel.setUserInfoPage(1, isSinglePageEdit: true); + context.navigateWithName(AppRoutes.userInfoFlowManager); + }, + trailingIcon: AppAssets.edit_icon, + hmgServicesVM: hmgServicesVM), _getDivider(), _buildEditInfoTile( - context: context, - leadingIcon: AppAssets.rulerIcon, - title: LocaleKeys.height.tr(context: context), - subTitle: heightText, - onTap: () { - viewModel.setUserInfoPage(2, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); - }, - trailingIcon: AppAssets.edit_icon, - ), + context: context, + leadingIcon: AppAssets.rulerIcon, + title: LocaleKeys.height.tr(context: context), + subTitle: heightText, + onTap: () { + viewModel.setUserInfoPage(2, isSinglePageEdit: true); + context.navigateWithName(AppRoutes.userInfoFlowManager); + }, + trailingIcon: AppAssets.edit_icon, + hmgServicesVM: hmgServicesVM), _getDivider(), _buildEditInfoTile( - context: context, - leadingIcon: AppAssets.weightScale, - title: LocaleKeys.weight.tr(context: context), - subTitle: weightText, - onTap: () { - viewModel.setUserInfoPage(3, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); - }, - trailingIcon: AppAssets.edit_icon, - ), + context: context, + leadingIcon: AppAssets.weightScale, + title: LocaleKeys.weight.tr(context: context), + subTitle: weightText, + onTap: () { + viewModel.setUserInfoPage(3, isSinglePageEdit: true); + context.navigateWithName(AppRoutes.userInfoFlowManager); + }, + trailingIcon: AppAssets.edit_icon, + hmgServicesVM: hmgServicesVM), ], ), ), @@ -291,6 +296,7 @@ class _UserInfoSelectionPageState extends State { text: LocaleKeys.yesItIs.tr(context: context), icon: AppAssets.tickIcon, iconColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor, + isDisabled: getIt.get().isVitalSignLoading || hasEmptyFields, onPressed: hasEmptyFields ? () {} // Empty function for disabled state : () => context.navigateWithName(AppRoutes.organSelectorPage), diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart index 65cf5a5b..f9f2d000 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart @@ -158,7 +158,7 @@ class HeightSelectionPage extends StatelessWidget { enableSound: true, minValue: minValue, maxValue: maxValue, - initialHeight: viewModel.selectedHeight ?? 100, + initialHeight: (viewModel.selectedHeight ?? 100).toDouble(), isCm: viewModel.isHeightCm, onHeightChanged: (newHeight) { log("height: $newHeight"); diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart index 8c837964..6e68eef9 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart @@ -150,7 +150,7 @@ class WeightSelectionPage extends StatelessWidget { enableSound: true, minValue: minValue, maxValue: maxValue, - initialWeight: viewModel.selectedWeight!, + initialWeight: (viewModel.selectedWeight ?? 70).toDouble(), isKg: isKg, onWeightChanged: (newWeight) { log("weight: $newWeight"); diff --git a/lib/presentation/todo_section/ancillary_procedures_details_page.dart b/lib/presentation/todo_section/ancillary_procedures_details_page.dart index f6046737..eba80cd4 100644 --- a/lib/presentation/todo_section/ancillary_procedures_details_page.dart +++ b/lib/presentation/todo_section/ancillary_procedures_details_page.dart @@ -153,7 +153,7 @@ class _AncillaryOrderDetailsListState extends State { ), ), ), - if (orderData != null) _buildStickyPaymentButton(orderData), + if (orderData != null && !viewModel.isAncillaryDetailsProceduresLoading) _buildStickyPaymentButton(orderData), ], ); }), @@ -164,6 +164,7 @@ class _AncillaryOrderDetailsListState extends State { return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, itemCount: 3, itemBuilder: (context, index) { return AncillaryOrderCard( @@ -657,6 +658,7 @@ class _AncillaryOrderDetailsListState extends State { text: LocaleKeys.proceedToPayment.tr(context: context), onPressed: () { // Navigate to payment page with selected procedures + todoSectionViewModel.setIsAncillaryOrdersNeedReloading(true); Navigator.of(context).push( CustomPageRoute( page: AncillaryOrderPaymentPage( diff --git a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart index 71d2cf03..58ee6d46 100644 --- a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart +++ b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart @@ -71,19 +71,12 @@ class AncillaryOrdersList extends StatelessWidget { return Center( child: Padding( padding: EdgeInsets.symmetric(vertical: 40.h), - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.r, - hasShadow: false, - ), - child: Utils.getNoDataWidget( - context, - noDataText: LocaleKeys.youDontHaveAnyAncillaryOrdersYet.tr(context: context), - isSmallWidget: true, - width: 62.w, - height: 62.h, - ), + child: Utils.getNoDataWidget( + context, + noDataText: LocaleKeys.youDontHaveAnyAncillaryOrdersYet.tr(context: context), + isSmallWidget: true, + width: 62.w, + height: 62.h, ), ), ); @@ -215,9 +208,7 @@ class AncillaryOrderCard extends StatelessWidget { ).toShimmer2(isShow: isLoading), ], ), - - SizedBox(height: 12.h), - + SizedBox(height: 6.h), // Action Buttons Row( children: [ @@ -236,7 +227,7 @@ class AncillaryOrderCard extends StatelessWidget { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - fontSize: 14.f, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w500, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), @@ -260,7 +251,7 @@ class AncillaryOrderCard extends StatelessWidget { backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), - fontSize: 14.f, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w500, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart index 980357de..75b6ec9d 100644 --- a/lib/presentation/vital_sign/vital_sign_details_page.dart +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -148,6 +148,7 @@ class _VitalSignDetailsPageState extends State { isBold: true, color: scheme.iconFg, letterSpacing: -2, + isEnglishOnly: true ), ), SizedBox(width: 4.h), diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart index e457023b..af8f5ab9 100644 --- a/lib/presentation/vital_sign/vital_sign_page.dart +++ b/lib/presentation/vital_sign/vital_sign_page.dart @@ -3,6 +3,8 @@ import 'dart:ui'; 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_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -330,6 +332,9 @@ class _VitalSignPageState extends State { weight: FontWeight.w600, ), ), + Utils.buildSvgWithAssets( + icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, + width: 18.w, height: 18.h), ], ), SizedBox(height: 14.h), @@ -353,6 +358,7 @@ class _VitalSignPageState extends State { value.toText17( isBold: true, color: AppColors.textColor, + isEnglishOnly: true ), if (unit.isNotEmpty) ...[ SizedBox(width: 3.w), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 9cf0ed91..c09fab67 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; +import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart'; import 'package:hmg_patient_app_new/presentation/smartwatches/huawei_health_example.dart'; import 'package:hmg_patient_app_new/presentation/smartwatches/smartwatch_home_page.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart'; @@ -88,6 +89,9 @@ class AppRoutes { // Emergency Services static const String emergencyServicesPage = '/emergencyServicesPage'; + // Services Price List + static const String servicesPriceListPage = '/servicesPriceListPage'; + static Map get routes => { initialRoute: (context) => SplashPage(), loginScreen: (context) => LoginScreen(), @@ -120,6 +124,7 @@ class AppRoutes { healthTrackersPage: (context) => HealthTrackersPage(), vitalSign: (context) => VitalSignPage(), emergencyServicesPage: (context) => EmergencyServicesPage(), + servicesPriceListPage: (context) => ServicesPriceListPage(), addHealthTrackerEntryPage: (context) { final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?; return AddHealthTrackerEntryPage( diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 502afac9..48f4159e 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -32,7 +32,8 @@ abstract class DialogService { required String message, required Function(FamilyFileResponseModelLists response) onSwitchPress, required List profiles, - bool isShowManageButton = false}); + bool isShowManageButton = false, + bool isForWalletRecharge = false}); Future showFamilyBottomSheetWithoutHWithChild({String? label, required String message, Widget? child, required Function() onOkPressed, Function()? onCancelPressed}); @@ -150,7 +151,8 @@ class DialogServiceImp implements DialogService { required String message, required Function(FamilyFileResponseModelLists response) onSwitchPress, required List profiles, - bool isShowManageButton = false}) async { + bool isShowManageButton = false, + bool isForWalletRecharge = false}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; showCommonBottomSheetWithoutHeight(context, @@ -168,6 +170,7 @@ class DialogServiceImp implements DialogService { }, onRemove: (FamilyFileResponseModelLists profile) {}, isShowDetails: false, + isForWalletRecharge: isForWalletRecharge, ), SizedBox(height: isShowManageButton ? 15.h : 24.h), if (isShowManageButton) diff --git a/lib/services/livecare_permission_service.dart b/lib/services/livecare_permission_service.dart new file mode 100644 index 00000000..a9a5d294 --- /dev/null +++ b/lib/services/livecare_permission_service.dart @@ -0,0 +1,92 @@ +import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class LiveCarePermissionService { + LiveCarePermissionService._(); + + static final LiveCarePermissionService instance = LiveCarePermissionService._(); + + // Guard to prevent concurrent permission requests + Completer>? _ongoingRequest; + + /// Requests camera, microphone and notification permissions together and + /// returns the final map of permission statuses. + /// This method does NOT show any dialogs — UI decisions must be done by caller. + Future> requestCameraMicAndNotification(BuildContext context) async { + if (_ongoingRequest != null) { + return _ongoingRequest!.future; + } + + _ongoingRequest = Completer>(); + final completer = _ongoingRequest!; + + final permissions = [ + Permission.camera, + Permission.microphone, + Permission.notification, + ]; + + try { + final statuses = await permissions.request(); + + // Ensure all requested keys exist in the returned map (some platforms may omit) + final result = {}; + for (final p in permissions) { + result[p] = statuses[p] ?? await p.status; + } + + if (!completer.isCompleted) completer.complete(result); + return completer.future; + } catch (e) { + if (!completer.isCompleted) completer.complete({}); + return completer.future; + } finally { + _ongoingRequest = null; + } + } + + /// Show an "open settings" dialog (kept as helper for the UI layer). + Future showOpenSettingsDialog(BuildContext context, {required String title, required String message}) async { + return showCommonBottomSheetWithoutHeight( + title: title, + context, + child: Utils.getWarningWidget( + loadingText: message, + isShowActionButtons: true, + onCancelTap: () { + Navigator.of(context).pop(); + }, + onConfirmTap: () async { + openAppSettings(); + Navigator.of(context).pop(); + }), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () {}, + ); + // return showDialog( + // context: context, + // builder: (c) => ConfirmDialog( + // title: '$title Permission', + // message: message, + // onTap: () { + // openAppSettings(); + // Navigator.of(c).pop(); + // }, + // okTitle: LocaleKeys.ok.tr(context: c), + // ), + // ); + } + + String friendlyName(Permission permission) { + if (permission == Permission.camera) return 'Camera'; + if (permission == Permission.microphone) return 'Microphone'; + if (permission == Permission.notification) return 'Notifications'; + return permission.toString(); + } +} diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 33d3d23d..49680a2a 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -56,7 +56,7 @@ class _SplashScreenState extends State { await notificationService.initialize(onNotificationClick: (payload) { // Handle notification click here }); - await ZoomService().initializeZoomSDK(); + ZoomService().initializeZoomSDK(); if (isAppOpenedFromCall) { navigateToTeleConsult(); } else { diff --git a/lib/widgets/bottomsheet/generic_bottom_sheet.dart b/lib/widgets/bottomsheet/generic_bottom_sheet.dart index b6be36c5..fd79b7c1 100644 --- a/lib/widgets/bottomsheet/generic_bottom_sheet.dart +++ b/lib/widgets/bottomsheet/generic_bottom_sheet.dart @@ -158,9 +158,10 @@ class GenericBottomSheetState extends State { prefix: widget.isForEmail ? null : widget.countryCode, isBorderAllowed: false, isAllowLeadingIcon: true, - fontSize: 13, + fontSize: 18.f, isCountryDropDown: widget.isEnableCountryDropdown, leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone, + fontFamily: "Poppins", ) : SizedBox(), ], diff --git a/lib/widgets/dropdown/country_dropdown_widget.dart b/lib/widgets/dropdown/country_dropdown_widget.dart index b6191edd..1cd387f9 100644 --- a/lib/widgets/dropdown/country_dropdown_widget.dart +++ b/lib/widgets/dropdown/country_dropdown_widget.dart @@ -100,7 +100,7 @@ class CustomCountryDropdownState extends State { children: [ Text( selectedCountry!.countryCode, - style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5), + style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"), ), SizedBox(width: 4.h), if (widget.isEnableTextField) @@ -111,7 +111,7 @@ class CustomCountryDropdownState extends State { alignment: Alignment.centerLeft, child: TextField( focusNode: textFocusNode, - style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5), + style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"), decoration: InputDecoration(hintText: "", isDense: true, border: InputBorder.none, contentPadding: EdgeInsets.zero), keyboardType: TextInputType.phone, onChanged: widget.onPhoneNumberChanged, diff --git a/lib/widgets/family_files/family_file_add_widget.dart b/lib/widgets/family_files/family_file_add_widget.dart index abc529bd..cd20c5f0 100644 --- a/lib/widgets/family_files/family_file_add_widget.dart +++ b/lib/widgets/family_files/family_file_add_widget.dart @@ -54,6 +54,7 @@ class FamilyFileAddWidget extends StatelessWidget { isAllowLeadingIcon: true, autoFocus: true, keyboardType: TextInputType.number, + fontFamily: "Poppins", padding: EdgeInsets.symmetric(vertical: 8.h), leadingIcon: AppAssets.student_card, ).paddingOnly(top: 8.h, bottom: 8.h), @@ -69,6 +70,7 @@ class FamilyFileAddWidget extends StatelessWidget { isAllowLeadingIcon: true, autoFocus: true, keyboardType: TextInputType.number, + fontFamily: "Poppins", padding: EdgeInsets.symmetric(vertical: 8.h), leadingIcon: AppAssets.smart_phone, ).paddingOnly(top: 8.h, bottom: 4.h), diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index cf1b132f..aae196cf 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -1,4 +1,5 @@ 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'; @@ -11,6 +12,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/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; @@ -48,6 +50,8 @@ class TextInputWidget extends StatelessWidget { final bool? isHideSwitcher; final bool? isArrowTrailing; + final String? fontFamily; + // final List countryList; // final Function(Country)? onCountryChange; @@ -86,6 +90,7 @@ class TextInputWidget extends StatelessWidget { this.maxLines = 6, this.isHideSwitcher, this.isArrowTrailing, + this.fontFamily, // this.countryList = const [], // this.onCountryChange, }); @@ -226,11 +231,8 @@ class TextInputWidget extends StatelessWidget { initialDate: DateTime.now(), showCalendarToggle: isHideSwitcher == true ? false : true, fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "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)), + 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; }); @@ -300,50 +302,103 @@ class TextInputWidget extends StatelessWidget { Widget _buildTextField(BuildContext context) { double fontS = fontSize ?? 14.f; - return TextField( - enabled: isEnable, - scrollPadding: EdgeInsets.zero, - keyboardType: isMultiline ? TextInputType.multiline : keyboardType, - controller: controller, - readOnly: isReadOnly, - textAlignVertical: TextAlignVertical.top, - textAlign: TextAlign.left, - textDirection: TextDirection.ltr, - onChanged: onChange, - focusNode: focusNode ?? _focusNode, - autofocus: autoFocus, - textInputAction: TextInputAction.done, - cursorHeight: isWalletAmountInput! ? 40.h : 20.h, - onTapOutside: (event) { - FocusManager.instance.primaryFocus?.unfocus(); + return Builder( + builder: (context) { + return 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, + keyboardType: isMultiline ? TextInputType.multiline : keyboardType, + controller: controller, + readOnly: isReadOnly, + textAlignVertical: TextAlignVertical.top, + textAlign: TextAlign.left, + textDirection: TextDirection.ltr, + onChanged: onChange, + focusNode: focusNode ?? _focusNode, + autofocus: autoFocus, + textInputAction: TextInputAction.done, + cursorHeight: isWalletAmountInput! ? 40.h : 20.h, + onTapOutside: (event) { + FocusManager.instance.primaryFocus?.unfocus(); + }, + onSubmitted: onSubmitted, + minLines: isMultiline ? minLines : 1, + maxLines: isMultiline ? maxLines : 1, + style: TextStyle( + fontSize: fontS, + height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), + fontWeight: FontWeight.w500, + color: AppColors.textColor, + letterSpacing: -1, + // fontFamily: keyboardType == TextInputType.number ? getIt.get().isArabic() ? 'GESSTwo' : 'Poppins' : 'Poppins', + fontFamily: fontFamily, + locale: const Locale('en', 'US'), // Force English locale for text style + ), + decoration: InputDecoration( + isDense: true, + hintText: hintText, + hintStyle: TextStyle(fontSize: 14.f, height: 21 / 16, fontWeight: FontWeight.w500, color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), letterSpacing: -0.75), + prefixIconConstraints: BoxConstraints(minWidth: 30.h), + prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500), + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + ), + ); }, - onSubmitted: onSubmitted, - minLines: isMultiline ? minLines : 1, - maxLines: isMultiline ? maxLines : 1, - style: TextStyle( - fontSize: fontS, - height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), - fontWeight: FontWeight.w500, - color: AppColors.textColor, - letterSpacing: -1, - ), - decoration: InputDecoration( - isDense: true, - hintText: hintText, - hintStyle: TextStyle( - fontSize: 14.f, - height: 21 / 16, - fontWeight: FontWeight.w500, - color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), - letterSpacing: -0.75), - prefixIconConstraints: BoxConstraints(minWidth: 30.h), - prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500), - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - ), ); + + // TextField( + // hintLocales: const [Locale('en', 'US')], + // enabled: isEnable, + // scrollPadding: EdgeInsets.zero, + // keyboardType: isMultiline ? TextInputType.multiline : keyboardType, + // controller: controller, + // readOnly: isReadOnly, + // textAlignVertical: TextAlignVertical.top, + // textAlign: TextAlign.left, + // textDirection: TextDirection.ltr, + // onChanged: onChange, + // focusNode: focusNode ?? _focusNode, + // autofocus: autoFocus, + // textInputAction: TextInputAction.done, + // cursorHeight: isWalletAmountInput! ? 40.h : 20.h, + // onTapOutside: (event) { + // FocusManager.instance.primaryFocus?.unfocus(); + // }, + // onSubmitted: onSubmitted, + // minLines: isMultiline ? minLines : 1, + // maxLines: isMultiline ? maxLines : 1, + // style: TextStyle( + // fontSize: fontS, + // height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), + // fontWeight: FontWeight.w500, + // color: AppColors.textColor, + // letterSpacing: -1, + // ), + // decoration: InputDecoration( + // isDense: true, + // hintText: hintText, + // hintStyle: TextStyle( + // fontSize: 14.f, + // height: 21 / 16, + // fontWeight: FontWeight.w500, + // color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), + // letterSpacing: -0.75), + // prefixIconConstraints: BoxConstraints(minWidth: 30.h), + // prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500), + // contentPadding: EdgeInsets.zero, + // border: InputBorder.none, + // focusedBorder: InputBorder.none, + // enabledBorder: InputBorder.none, + // ), + // ); } _buildTrailingIconForSearch(BuildContext context) { diff --git a/lib/widgets/map/location_map_widget.dart b/lib/widgets/map/location_map_widget.dart index 2a1ea657..e3139a93 100644 --- a/lib/widgets/map/location_map_widget.dart +++ b/lib/widgets/map/location_map_widget.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; @@ -8,7 +10,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; /// A reusable location map widget that displays a static map with address and directions button /// Can be used in any review screen (HHC, CMC, etc.) @@ -56,9 +58,19 @@ class LocationMapWidget extends StatelessWidget { this.padding, }); - void _defaultLaunchDirections() { + void _defaultLaunchDirections() async { if (latitude != 0.0 && longitude != 0.0) { - MapsLauncher.launchCoordinates(latitude, longitude, address); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(latitude, longitude), + title: address, + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(latitude, longitude), + title: address, + ); + }); } } diff --git a/pubspec.yaml b/pubspec.yaml index 02acec84..19acdf31 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,7 +73,7 @@ dependencies: web: any flutter_staggered_animations: ^1.1.1 smooth_corner: ^1.1.1 - maps_launcher: ^3.0.0+1 + map_launcher: ^4.4.3 amazon_payfort: ^1.1.4 network_info_plus: ^6.1.4 flutter_nfc_kit: ^3.6.0