diff --git a/android/app/agconnect-services.json b/android/app/agconnect-services.json index 66f04d15..91aad2c7 100644 --- a/android/app/agconnect-services.json +++ b/android/app/agconnect-services.json @@ -1 +1,74 @@ -{"agcgw":{"url":"connect-drcn.dbankcloud.cn","backurl":"connect-drcn.hispace.hicloud.com","websocketurl":"connect-ws-drcn.hispace.dbankcloud.cn","websocketbackurl":"connect-ws-drcn.hispace.dbankcloud.com"},"agcgw_all":{"SG":"connect-dra.dbankcloud.cn","SG_back":"connect-dra.hispace.hicloud.com","CN":"connect-drcn.dbankcloud.cn","CN_back":"connect-drcn.hispace.hicloud.com","RU":"connect-drru.hispace.dbankcloud.ru","RU_back":"connect-drru.hispace.dbankcloud.cn","DE":"connect-dre.dbankcloud.cn","DE_back":"connect-dre.hispace.hicloud.com"},"websocketgw_all":{"SG":"connect-ws-dra.hispace.dbankcloud.cn","SG_back":"connect-ws-dra.hispace.dbankcloud.com","CN":"connect-ws-drcn.hispace.dbankcloud.cn","CN_back":"connect-ws-drcn.hispace.dbankcloud.com","RU":"connect-ws-drru.hispace.dbankcloud.ru","RU_back":"connect-ws-drru.hispace.dbankcloud.cn","DE":"connect-ws-dre.hispace.dbankcloud.cn","DE_back":"connect-ws-dre.hispace.dbankcloud.com"},"client":{"cp_id":"2640966000002322881","product_id":"736430079244816567","client_id":"563735388191982656","client_secret":"650C7C799812AFFD53A10C7756CF05FB9F215A7E49032ABA8EBF3E14B77535CF","project_id":"736430079244816567","app_id":"116912301","api_key":"CgB6e3x9DJzMgRCmnT6dyUEkp6UsIfddb6l3w0ZEXzeiRMHEFi3400Z5fJ5qaHneU0OrAI/JRpk+DMGVs3QpUxlI","package_name":"com.cloudsolutions.HMGPatientApp"},"oauth_client":{"client_id":"116912301","client_type":1},"app_info":{"app_id":"116912301","package_name":"com.cloudsolutions.HMGPatientApp"},"service":{"analytics":{"collector_url":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn","collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn","resource_id":"p1","channel_id":""},"ml":{"mlservice_url":"ml-api-drcn.ai.dbankcloud.com,ml-api-drcn.ai.dbankcloud.cn"},"cloudstorage":{"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg":"https://ops-dra.agcstorage.link","storage_url_de":"https://ops-dre.agcstorage.link","storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia","storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu","storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn"},"search":{"url":"https://search-drcn.cloud.huawei.com"},"edukit":{"edu_url":"edukit.cloud.huawei.com.cn","dh_url":"edukit.cloud.huawei.com.cn"}},"region":"CN","configuration_version":"3.0","appInfos":[{"package_name":"com.cloudsolutions.HMGPatientApp","client":{"app_id":"116912301"},"app_info":{"package_name":"com.cloudsolutions.HMGPatientApp","app_id":"116912301"},"oauth_client":{"client_type":1,"client_id":"116912301"}},{"package_name":"com.ejada.hmg","client":{"app_id":"102857389"},"app_info":{"package_name":"com.ejada.hmg","app_id":"102857389"},"oauth_client":{"client_type":1,"client_id":"102857389"}}]} \ No newline at end of file +{ + "agcgw":{ + "backurl":"connect-drcn.hispace.hicloud.com", + "url":"connect-drcn.dbankcloud.cn", + "websocketbackurl":"connect-ws-drcn.hispace.dbankcloud.com", + "websocketurl":"connect-ws-drcn.hispace.dbankcloud.cn" + }, + "agcgw_all":{ + "CN":"connect-drcn.dbankcloud.cn", + "CN_back":"connect-drcn.hispace.hicloud.com", + "DE":"connect-dre.dbankcloud.cn", + "DE_back":"connect-dre.hispace.hicloud.com", + "RU":"connect-drru.dbankcloud.cn", + "RU_back":"connect-drru.hispace.hicloud.com", + "SG":"connect-dra.dbankcloud.cn", + "SG_back":"connect-dra.hispace.hicloud.com" + }, + "client":{ + "cp_id":"2640966000002322881", + "product_id":"736430079244816567", + "client_id":"563735388191982656", + "client_secret":"650C7C799812AFFD53A10C7756CF05FB9F215A7E49032ABA8EBF3E14B77535CF", + "project_id":"736430079244816567", + "app_id":"102857389", + "api_key":"CgB6e3x9DJzMgRCmnT6dyUEkp6UsIfddb6l3w0ZEXzeiRMHEFi3400Z5fJ5qaHneU0OrAI/JRpk+DMGVs3QpUxlI", + "package_name":"com.ejada.hmg" + }, + "oauth_client":{ + "client_id":"102857389", + "client_type":1 + }, + "app_info":{ + "app_id":"102857389", + "package_name":"com.ejada.hmg" + }, + "service":{ + "analytics":{ + "collector_url":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn", + "collector_url_ru":"datacollector-drru.dt.hicloud.com,datacollector-drru.dt.dbankcloud.cn", + "collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn", + "collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn", + "collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn", + "resource_id":"p1", + "channel_id":"" + }, + "search":{ + "url":"https://search-drcn.cloud.huawei.com" + }, + "cloudstorage":{ + "storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn" + }, + "ml":{ + "mlservice_url":"ml-api-drcn.ai.dbankcloud.com,ml-api-drcn.ai.dbankcloud.cn" + } + }, + "region":"CN", + "configuration_version":"3.0", + "appInfos":[ + { + "package_name":"com.ejada.hmg", + "client":{ + "app_id":"102857389" + }, + "app_info":{ + "package_name":"com.ejada.hmg", + "app_id":"102857389" + }, + "oauth_client":{ + "client_type":1, + "client_id":"102857389" + } + } + ] +} diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 13f778ff..e115ebef 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -19,7 +19,8 @@ if (keystorePropertiesFile.exists()) { } android { - namespace = "com.cloudsolutions.HMGPatientApp" +// namespace = "com.cloudsolutions.HMGPatientApp" + namespace = "com.ejada.hmg" compileSdk = 36 // Using NDK 26 for better compatibility with Zoom SDK native libraries // ndkVersion = "27.0.12077973" @@ -33,7 +34,8 @@ android { } defaultConfig { - applicationId = "com.cloudsolutions.HMGPatientApp" +// applicationId = "com.cloudsolutions.HMGPatientApp" + applicationId = "com.ejada.hmg" // minSdk = 24 minSdk = 29 targetSdk = 36 @@ -141,6 +143,10 @@ flutter { source = "../.." } +configurations.all { + exclude(group = "org.jetbrains.kotlin", module = "kotlin-android-extensions-runtime") +} + dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.20") implementation("com.google.firebase:firebase-messaging:24.1.2") diff --git a/android/app/google-services.json b/android/app/google-services.json index 49bf1ace..3707617c 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -3,45 +3,9 @@ "project_number": "815750722565", "firebase_url": "https://api-project-815750722565.firebaseio.com", "project_id": "api-project-815750722565", - "storage_bucket": "api-project-815750722565.firebasestorage.app" + "storage_bucket": "api-project-815750722565.appspot.com" }, "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:815750722565:android:fa20b3a2401e08633c186c", - "android_client_info": { - "package_name": "com.cloudsolutions.HMGPatientApp" - } - }, - "oauth_client": [ - { - "client_id": "815750722565-3a0gc7neins0eoahdrimrfksk0sqice8.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyDZDeWcBlRE3YfJWYt_DCiToVnANfaj8qg" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "815750722565-3a0gc7neins0eoahdrimrfksk0sqice8.apps.googleusercontent.com", - "client_type": 3 - }, - { - "client_id": "815750722565-0cq9366orvsk5ipivq6lijcj56u03fr7.apps.googleusercontent.com", - "client_type": 2, - "ios_info": { - "bundle_id": "com.void.demo" - } - } - ] - } - } - }, { "client_info": { "mobilesdk_app_id": "1:815750722565:android:62281cd3e5df4063", diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index e4e65227..68b506a1 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -3,7 +3,7 @@ -keep class com.twilio.common.** { *; } -keepattributes InnerClasses --keep class com.ejada.** { *; } +#-keep class com.ejada.** { *; } -keep class org.webrtc.** { *; } -keep class com.builttoroam.devicecalendar.** { *; } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 70a68707..b6188224 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ + package="com.ejada.hmg"> + + + + + + + + Gradle Configuration Cache + + + +
+ +
+ Loading... +
+ + + + + + diff --git a/android/gradle.properties b/android/gradle.properties index f47e3b3a..0e253cc0 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,10 +1,18 @@ #org.gradle.jvmargs=-xmx4608m android.enableR8=true +android.enableR8.fullMode=true android.enableJetifier=true android.useDeprecatedNdk=true -org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.daemon=true org.gradle.parallel=true org.gradle.configureondemand=true android.useAndroidX=true android.enableImpeller=false +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false + +# Enabled parallel sync for Gradle 9.4+ +org.gradle.tooling.parallel=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 2299155e..4ee62d8f 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists #distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 1c91fb7c..434a4219 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -16,14 +16,34 @@ pluginManagement { } } +dependencyResolutionManagement { +// repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositoriesMode.set(RepositoriesMode.PREFER_PROJECT) + repositories { + google() + mavenCentral() + maven { + url = uri("https://storage.googleapis.com/download.flutter.io") + } + // Huawei HMS repo + maven { + url = uri("https://developer.huawei.com/repo/") + } + // Mapbox repo + maven { + url = uri("https://api.mapbox.com/downloads/v2/releases/maven") + } + } +} + plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" // id("com.android.application") version "8.9.3" apply false // START: FlutterFire Configuration id("com.google.gms.google-services") version("4.4.1") apply false // END: FlutterFire Configuration - id("com.android.application") version "8.9.3" apply false - id("org.jetbrains.kotlin.android") version "2.1.0" apply false + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false } include(":app") diff --git a/assets/images/svg/Icon-migrate.svg b/assets/images/svg/Icon-migrate.svg new file mode 100644 index 00000000..b8dc647f --- /dev/null +++ b/assets/images/svg/Icon-migrate.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/json/default_occasion_config.json b/assets/json/default_occasion_config.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/assets/json/default_occasion_config.json @@ -0,0 +1 @@ +[] diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 911856ab..f0e3c730 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1533,6 +1533,7 @@ "vatPercent": "ضريبة القيمة المضافة (15%)", "proceedToPayment": "المتابعة للدفع", "supportedSmartWatches": "كيف تعمل؟", + "smartwatchHowItWorks": "كيف تعمل الساعة الذكية", "pleaseMakeSureSamsungWatchConnected": "يرجى التأكد من أن ساعة Samsung الخاصة بك متصلة بهاتفك، ومتزامنة ومحدثة بشكل نشط.", "beforeSyncingDataFollowInstructions": "قبل مزامنة البيانات، يرجى التأكد من اتباع التعليمات بشكل صحيح.", "viewWatchInstructions": "عرض تعليمات الساعة", @@ -1544,6 +1545,18 @@ "seconds": "ثواني", "secs": "ثواني", "noAllergiesDataFound": "لم يتم العثور على بيانات الحساسية...", + "allHealthData": "جميع البيانات الصحية", + "activityCalories": "سعرات النشاط", + "activityCaloriesDescription": "توفر حلقات النشاط مرجعًا بصريًا سريعًا لمدى نشاطك خلال كل يوم.", + "steps": "الخطوات", + "distanceCovered": "المسافة المقطوعة", + "distanceCoveredDescription": "المسافة المقطوعة على مدار اليوم", + "sleepScore": "مؤشر النوم", + "sleepScoreDescription": "يتتبع هذا عدد ساعات نومك خلال اليوم", + "bloodOxygen": "أكسجين الدم", + "bloodOxygenTrackingDescription": "يتتبع هذا مستوى أكسجين الدم لمراقبة السجل الصحي وتحديثه", + "bodyTemperature": "درجة حرارة الجسم", + "bodyTemperatureTrackingDescription": "يتتبع هذا درجة حرارة الجسم لمراقبة السجل الصحي وتحديثه", "heartRateDescription": "معدل ضربات قلبك يشير إلى عدد المرات التي ينبض فيها قلبك في الدقيقة", "bloodOxygenDescription": "مستوى الأكسجين في الدم يشير إلى كمية الأكسجين التي تحملها خلايا الدم الحمراء", "stepsDescription": "عدد الخطوات المتخذة على مدار اليوم", @@ -2050,5 +2063,12 @@ "liveCareTermsConditions46": "13-3\tإذا تبين أن أي بند أو جزء من أي بند من هذه الشروط والأحكام باطل أو غير قابل للإنفاذ أو غير صالح، فسيتم فصله عن هذه الشروط والأحكام، تاركاً باقي الشروط سارية المفعول بكامل الأثر والنفاذ، بشرط ألا يغير فصل ذلك البند من الطبيعة الأساسية لهذه الشروط والأحكام.", "liveCareTermsHeading16": "14.\tالشكاوى", "liveCareTermsConditions47": "خدمات الرعاية الصحية عن بُعد الخاصة بنا سوف تكون لتخصصات طبية محددة أو لمواعيد المتابعة أو إعادة صرف الدواء. ", - "liveCareTermsConditions48": "إذا كانت لديك أي شكاوى أو مخاوف بشأن التطبيق و/أو موقع الويب أو خدماتنا أو كيفية تعاملنا مع معلوماتك الشخصية، فيرجى التواصل معنا على: EServices.HMG@drsulaimanalhabib.com أو الاتصال على الرقم: 9553 525 011" + "liveCareTermsConditions48": "إذا كانت لديك أي شكاوى أو مخاوف بشأن التطبيق و/أو موقع الويب أو خدماتنا أو كيفية تعاملنا مع معلوماتك الشخصية، فيرجى التواصل معنا على: EServices.HMG@drsulaimanalhabib.com أو الاتصال على الرقم: 9553 525 011", + "importantNotice": "تنبيه هام", + "appDecommissionedText": "تم إيقاف هذا الإصدار من تطبيق الحبيب الطبي.\nللاستمرار في الاستفادة من الخدمات الصحية والسجلات الطبية، يرجى تحميل التطبيق الجديد.", + "appDecommissionedSubText": "نعتذر بشدة عن الإزعاج الناتج عن هذا الانتقال.", + "downloadApp": "حمل التطبيق", + "unread": "غير مقروء", + "read": "مقروءة", + "pendingMedicalReportRequest": "لديك بالفعل طلب معلّق للحصول على تقرير طبي خاص بهذا الموعد. يرجى الانتظار حتى اكتمال الطلب قبل تقديم طلب جديد." } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index aaafb124..06a2742c 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1526,6 +1526,7 @@ "vatPercent": "VAT (15%)", "proceedToPayment": "Proceed to Payment", "supportedSmartWatches": "Supported Smart Watches", + "smartwatchHowItWorks": "How does it work", "pleaseMakeSureSamsungWatchConnected": "Please make sure that your Samsung Watch is connected to your Phone, is actively synced & updated.", "beforeSyncingDataFollowInstructions": "Before syncing data, please make sure that you have followed the instructions properly.", "viewWatchInstructions": "View watch instructions", @@ -1537,6 +1538,18 @@ "seconds": "Seconds", "secs": "Secs", "noAllergiesDataFound": "No allergies data found...", + "allHealthData": "All Health Data", + "activityCalories": "Activity Calories", + "activityCaloriesDescription": "Activity rings give you a quick visual reference of how active you are each day.", + "steps": "Steps", + "distanceCovered": "Distance Covered", + "distanceCoveredDescription": "Distance covered throughout the day", + "sleepScore": "Sleep Score", + "sleepScoreDescription": "This keeps track of how many hours you sleep in a day", + "bloodOxygen": "Blood Oxygen", + "bloodOxygenTrackingDescription": "This tracks your blood oxygen to monitor and update your health history", + "bodyTemperature": "Body Temperature", + "bodyTemperatureTrackingDescription": "This tracks your body temperature to monitor and update your health history", "heartRateDescription": "Your heart rate indicates how many times your heart beats per minute", "bloodOxygenDescription": "Blood oxygen level indicates how much oxygen your red blood cells are carrying", "stepsDescription": "Number of steps taken throughout the day", @@ -2040,7 +2053,14 @@ "liveCareTermsConditions46": "13.3 If any clause or part of any clause of these Terms & Conditions is found to be void, unenforceable or invalid, then it will be severed from these Terms & Conditions, leaving the remainder in full force and effect, provided that the severance has not altered the basic nature of these Terms & Conditions. ", "liveCareTermsHeading16": "14. COMPLAINTS", "liveCareTermsConditions47": "Our Telehealth Services will be for specific medical specialties or follow-up or medication refill appointments.", - "liveCareTermsConditions48": "If you have any complaints or concerns about the Application and or the Website, Our Services, or how we handle your personal information please contact us on: EServices.HMG@drsulaimanalhabib.com or call 011 525 9553" + "liveCareTermsConditions48": "If you have any complaints or concerns about the Application and or the Website, Our Services, or how we handle your personal information please contact us on: EServices.HMG@drsulaimanalhabib.com or call 011 525 9553", + "importantNotice": "Important Notice", + "appDecommissionedText": "This version of our healthcare application has been officially discontinued. To ensure uninterrupted access to your clinical records, we are transitioning all patients back to our main platform.", + "appDecommissionedSubText": "We sincerely apologize for the inconvenience of this transition.", + "downloadApp": "Download App", + "unread": "Unread", + "read": "Read", + "pendingMedicalReportRequest": "You already have a pending medical report request for this appointment. Please wait for the request to be completed before submitting a new one." } diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 1dc6cf76..391a902b 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 13.0 diff --git a/ios/GoogleService-Info.plist b/ios/GoogleService-Info.plist index a9b1f53a..633037cb 100644 --- a/ios/GoogleService-Info.plist +++ b/ios/GoogleService-Info.plist @@ -2,6 +2,10 @@ + 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 @@ -11,11 +15,11 @@ PLIST_VERSION 1 BUNDLE_ID - com.cloudsolutions.HMGPatientApp + com.HMG.HMG-Smartphone PROJECT_ID api-project-815750722565 STORAGE_BUCKET - api-project-815750722565.firebasestorage.app + api-project-815750722565.appspot.com IS_ADS_ENABLED IS_ANALYTICS_ENABLED @@ -27,7 +31,7 @@ IS_SIGNIN_ENABLED GOOGLE_APP_ID - 1:815750722565:ios:34ec03c09e02e21f3c186c + 1:815750722565:ios:328ec247a81a2ca23c186c DATABASE_URL https://api-project-815750722565.firebaseio.com diff --git a/ios/Podfile b/ios/Podfile index a74c7fb2..e0283052 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -59,7 +59,7 @@ post_install do |installer| 'PERMISSION_NOTIFICATIONS=1', ] build_configuration.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386' - build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' + build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' build_configuration.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES' xcconfig_path = build_configuration.base_configuration_reference.real_path xcconfig = File.read(xcconfig_path) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index f52d12fc..c0d2bb74 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,15 +3,15 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; - 37D27FAF2C38AC83B852680C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 478CFA942E638C8E0064F3D7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */; }; + 5D5A1F80D389375644C4D0DC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56E413AB2A2E9765749CACD1 /* Pods_Runner.framework */; }; 61243B492EC5FA3700D46FA0 /* PenguinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */; }; 61243B4C2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */; }; 61243B502EC5FA3700D46FA0 /* PenguinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B452EC5FA3700D46FA0 /* PenguinView.swift */; }; @@ -26,6 +26,7 @@ 76B1D2542F91BF8000DBBD1B /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 76B1D24F2F91BF8000DBBD1B /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 76B1D2552F91BF8000DBBD1B /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76B1D2502F91BF8000DBBD1B /* PenNavUI.xcframework */; }; 76B1D2562F91BF8000DBBD1B /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 76B1D2502F91BF8000DBBD1B /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 */; }; @@ -55,18 +56,27 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + 76B714E4300E58B2004DE37A /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 05E1B2A0E6844572ADA81174 /* 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 = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3EAE1F31D8D0AA94CA6C11EB /* 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 = ""; }; 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 478CFA952E6E20A60064F3D7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 56E413AB2A2E9765749CACD1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HMGPenguinInPlatformBridge.swift; sourceTree = ""; }; 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinModel.swift; sourceTree = ""; }; 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinNavigator.swift; sourceTree = ""; }; @@ -80,8 +90,10 @@ 76B1D24E2F91BF8000DBBD1B /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Penguin.xcframework; path = Frameworks/Penguin.xcframework; sourceTree = ""; }; 76B1D24F2F91BF8000DBBD1B /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenguinINRenderer.xcframework; path = Frameworks/PenguinINRenderer.xcframework; sourceTree = ""; }; 76B1D2502F91BF8000DBBD1B /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenNavUI.xcframework; path = Frameworks/PenNavUI.xcframework; sourceTree = ""; }; + 76B714CF300E58B1004DE37A /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 76B714D1300E58B1004DE37A /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7BEF44ABFEF585D64D3F266C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -89,7 +101,9 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BCE5BC40273DCC009E2FACB7 /* 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 = ""; }; + C6416282AC56269448D7DA99 /* 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 = ""; }; + C6FD350374F47AFA34911C58 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -97,10 +111,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 76B1D2512F91BF8000DBBD1B /* Penguin.xcframework in Frameworks */, 76B1D2552F91BF8000DBBD1B /* PenNavUI.xcframework in Frameworks */, 76B1D2532F91BF8000DBBD1B /* PenguinINRenderer.xcframework in Frameworks */, - 37D27FAF2C38AC83B852680C /* Pods_Runner.framework in Frameworks */, + 5D5A1F80D389375644C4D0DC /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -141,7 +156,9 @@ 76B1D24E2F91BF8000DBBD1B /* Penguin.xcframework */, 76B1D24F2F91BF8000DBBD1B /* PenguinINRenderer.xcframework */, 76B1D2502F91BF8000DBBD1B /* PenNavUI.xcframework */, - A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */, + 76B714CF300E58B1004DE37A /* WidgetKit.framework */, + 76B714D1300E58B1004DE37A /* SwiftUI.framework */, + 56E413AB2A2E9765749CACD1 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -149,9 +166,9 @@ 79DD2093A1D9674C94359FC8 /* Pods */ = { isa = PBXGroup; children = ( - 3EAE1F31D8D0AA94CA6C11EB /* Pods-Runner.debug.xcconfig */, - 05E1B2A0E6844572ADA81174 /* Pods-Runner.release.xcconfig */, - 7BEF44ABFEF585D64D3F266C /* Pods-Runner.profile.xcconfig */, + BCE5BC40273DCC009E2FACB7 /* Pods-Runner.debug.xcconfig */, + C6416282AC56269448D7DA99 /* Pods-Runner.release.xcconfig */, + C6FD350374F47AFA34911C58 /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -159,6 +176,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -233,21 +251,25 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - EA10FC42B68E41E75A052FAA /* [CP] Check Pods Manifest.lock */, + 511FFA76807DE636962DBF91 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 765A5A922F35CD8B0003FF7D /* Embed Frameworks */, + 76B714E4300E58B2004DE37A /* Embed Foundation Extensions */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 5D78F615DF2D966539B34DE2 /* [CP] Embed Pods Frameworks */, - 4AA9AE39AD56A64A1F16E548 /* [CP] Copy Pods Resources */, + C7E5B2C65EE0A4D64ECC826B /* [CP] Embed Pods Frameworks */, + DF2C78ED8C8F648520DF8C43 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -259,6 +281,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 2610; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -281,6 +304,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -329,85 +355,77 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; }; - 4AA9AE39AD56A64A1F16E548 /* [CP] Copy Pods Resources */ = { + 511FFA76807DE636962DBF91 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 5D78F615DF2D966539B34DE2 /* [CP] Embed Pods Frameworks */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); inputPaths = ( ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); + name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + C7E5B2C65EE0A4D64ECC826B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputPaths = ( + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "Run Script"; - outputPaths = ( + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; - EA10FC42B68E41E75A052FAA /* [CP] Check Pods Manifest.lock */ = { + DF2C78ED8C8F648520DF8C43 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -509,7 +527,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -537,8 +555,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.0.12; - PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; + MARKETING_VERSION = 4.6.044; + PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -643,7 +661,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -694,7 +712,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -724,8 +742,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.0.12; - PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; + MARKETING_VERSION = 4.6.044; + PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -754,8 +772,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.0.12; - PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; + MARKETING_VERSION = 4.6.044; + PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -798,6 +816,20 @@ defaultConfigurationName = Debug; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..6cf27c0f --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,194 @@ +{ + "pins" : [ + { + "identity" : "abseil-cpp-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/abseil-cpp-binary.git", + "state" : { + "revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5", + "version" : "1.2024072200.0" + } + }, + { + "identity" : "app-check", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/app-check.git", + "state" : { + "revision" : "3e33dd27dd4c69bd81c7c81fe61d8ccf58846902", + "version" : "11.3.1" + } + }, + { + "identity" : "dkcamera", + "kind" : "remoteSourceControl", + "location" : "https://github.com/zhangao0086/DKCamera", + "state" : { + "branch" : "master", + "revision" : "5c691d11014b910aff69f960475d70e65d9dcc96" + } + }, + { + "identity" : "dkimagepickercontroller", + "kind" : "remoteSourceControl", + "location" : "https://github.com/zhangao0086/DKImagePickerController", + "state" : { + "branch" : "4.3.9", + "revision" : "0bdfeacefa308545adde07bef86e349186335915" + } + }, + { + "identity" : "dkphotogallery", + "kind" : "remoteSourceControl", + "location" : "https://github.com/zhangao0086/DKPhotoGallery", + "state" : { + "branch" : "master", + "revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d" + } + }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk", + "state" : { + "revision" : "fdc352fabaf5916e7faa1f96ad02b1957e93e5a5", + "version" : "11.15.0" + } + }, + { + "identity" : "flutterfire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/flutterfire", + "state" : { + "revision" : "dadb0fd27bc9afe4dee4f23326a4a9ba238258ac", + "version" : "3.15.2-firebase-core-swift" + } + }, + { + "identity" : "google-ads-on-device-conversion-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk", + "state" : { + "revision" : "a2d0f1f1666de591eb1a811f40b1706f5c63a2ed", + "version" : "2.3.0" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "45ce435e9406d3c674dd249a042b932bee006f60", + "version" : "11.15.0" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "ba3358d3c3dbae8ef230b58a46b97ad65e84e974", + "version" : "10.1.1" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "9f183ae842be978784f2963a343682e0c46d8fb3", + "version" : "8.1.2" + } + }, + { + "identity" : "grpc-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/grpc-binary.git", + "state" : { + "revision" : "75b31c842f664a0f46a2e590a570e370249fd8f6", + "version" : "1.69.1" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "c756a29784521063b6a1202907e2cc47f41b667c", + "version" : "4.5.0" + } + }, + { + "identity" : "interop-ios-for-google-sdks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/interop-ios-for-google-sdks.git", + "state" : { + "revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe", + "version" : "101.0.0" + } + }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1", + "version" : "1.22.5" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "3851d94a41890dea16dc3db34caf60e585cb4163", + "version" : "2.30910.1" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "f4a19a3c313dc2616c70bb49d29a799fb16be837", + "version" : "2.4.1" + } + }, + { + "identity" : "sdwebimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImage", + "state" : { + "revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0", + "version" : "5.21.7" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "55d7a1cc5666b85c13464aea1c4b4a90feccb4c8", + "version" : "1.38.1" + } + }, + { + "identity" : "swiftygif", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kirualex/SwiftyGif.git", + "state" : { + "revision" : "4430cbc148baa3907651d40562d96325426f409a", + "version" : "5.4.5" + } + }, + { + "identity" : "tocropviewcontroller", + "kind" : "remoteSourceControl", + "location" : "https://github.com/TimOliver/TOCropViewController", + "state" : { + "revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e", + "version" : "2.8.0" + } + } + ], + "version" : 2 +} diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e3773d42..c3fedb29 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + Bool { GMSServices.provideAPIKey("AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng") - FirebaseApp.configure() +// FirebaseApp.configure() initializePlatformChannels() - GeneratedPluginRegistrant.register(with: self) +// GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } func initializePlatformChannels(){ - if let mainViewController = window?.rootViewController as? FlutterViewController{ // platform initialization suppose to be in foreground - HMGPenguinInPlatformBridge.initialize(flutterViewController: mainViewController) - } - } + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + // 1. Try accessing window via AppDelegate (legacy/single-window setup) + var controller = self.window?.rootViewController as? FlutterViewController + + // 2. Fallback to UIWindowScene active window (modern iOS architecture) + if controller == nil { + controller = UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap { $0.windows } + .first(where: { $0.isKeyWindow })? + .rootViewController as? FlutterViewController + } + + // 3. Perform initialization once FlutterViewController is available + if let mainViewController = controller { + HMGPenguinInPlatformBridge.initialize(flutterViewController: mainViewController) + } else { + print("⚠️ FlutterViewController not ready yet for initializePlatformChannels.") + } + } + + +// if let mainViewController = window?.rootViewController as? FlutterViewController{ // platform initialization suppose to be in foreground +// HMGPenguinInPlatformBridge.initialize(flutterViewController: mainViewController) +// } + + } + override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken:Data){ // Messaging.messaging().apnsToken = deviceToken super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index 65b74d7e..1eb27a20 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1 +1,128 @@ -{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]} \ No newline at end of file +{ + "images" : [ + { + "filename" : "Icon-App-20x20@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-20x20@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-29x29@1x.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-40x40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-40x40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-60x60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "Icon-App-60x60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "Icon-App-20x20@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-20x20@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-29x29@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-40x40@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-40x40@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-76x76@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "Icon-App-76x76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "Icon-App-83.5x83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "icon.jpg", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "filename" : "Icon-App-76x76@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "76x76" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 0e65243a..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 2e4c7963..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 59435c76..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 20855aad..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index 11d35da4..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index 93523cbe..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2e4c7963..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 00bb8dbd..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index 3eaa1fb6..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 3eaa1fb6..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index fd80aa41..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index 39c7958d..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 47a96a43..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 8c4ca7b0..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist index a9b1f53a..633037cb 100644 --- a/ios/Runner/GoogleService-Info.plist +++ b/ios/Runner/GoogleService-Info.plist @@ -2,6 +2,10 @@ + 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 @@ -11,11 +15,11 @@ PLIST_VERSION 1 BUNDLE_ID - com.cloudsolutions.HMGPatientApp + com.HMG.HMG-Smartphone PROJECT_ID api-project-815750722565 STORAGE_BUCKET - api-project-815750722565.firebasestorage.app + api-project-815750722565.appspot.com IS_ADS_ENABLED IS_ANALYTICS_ENABLED @@ -27,7 +31,7 @@ IS_SIGNIN_ENABLED GOOGLE_APP_ID - 1:815750722565:ios:34ec03c09e02e21f3c186c + 1:815750722565:ios:328ec247a81a2ca23c186c DATABASE_URL https://api-project-815750722565.firebaseio.com diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index caa84333..85d84646 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -35,6 +35,7 @@ comgooglemaps baidumap iosamap + whatsapp LSRequiresIPhoneOS @@ -86,17 +87,38 @@ NSMicrophoneUsageDescription This app requires microphone access to enable virtual consultation between patient & doctor NSMotionUsageDescription - This app requires access to motion detection to count your daily steps. - NSPhotoLibraryUsageDescription - This app requires photo library access to select image as document & upload it. + This app requires motion detection access to function properly. NSPhotoLibraryAddUsageDescription This app requires photo library access to upload profile picture and documents. + NSPhotoLibraryUsageDescription + This app requires photo library access to select image as document & upload it. NSRemindersUsageDescription This app requires calendar access to set reminders for Virtual & Normal Appointments. NSSpeechRecognitionUsageDescription This app requires speech recognition access to access voice command features. NSUserActivityTypes + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + FlutterSceneDelegate + UISceneStoryboardFile + Main + + + + UIApplicationSupportsIndirectInputEvents UIBackgroundModes diff --git a/lib/app_closed_page.dart b/lib/app_closed_page.dart new file mode 100644 index 00000000..2cda8935 --- /dev/null +++ b/lib/app_closed_page.dart @@ -0,0 +1,104 @@ +import 'dart:io'; + +import 'package:clarity_flutter/clarity_flutter.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:freerasp/freerasp.dart'; +import 'package:hmg_patient_app_new/core/app_assets.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/services/security_service.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +import 'generated/locale_keys.g.dart'; +import 'widgets/buttons/custom_button.dart'; + +class AppClosedPage extends StatefulWidget { + const AppClosedPage({super.key}); + + @override + State createState() => _AppClosedPageState(); +} + +class _AppClosedPageState extends State { + @override + void initState() { + // Clarity.setCurrentScreenName('Unsafe Device Page'); + Talsec.instance.detachListener(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.whiteColor, + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Logo + Utils.buildImgWithAssets(icon: AppAssets.hmgLogo, width: MediaQuery.of(context).size.width * 0.7, height: 90.h, fit: BoxFit.contain), + SizedBox(height: 32.h), + Text( + LocaleKeys.importantNotice.tr(context: context), + style: TextStyle( + fontSize: 28.f, + fontWeight: FontWeight.bold, + color: AppColors.textColor, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 16.h), + // Description + Text( + LocaleKeys.appDecommissionedText.tr(context: context), + style: TextStyle( + fontSize: 14.f, + color: Colors.black87, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 24.h), + Text( + LocaleKeys.appDecommissionedSubText.tr(context: context), + style: TextStyle(fontSize: 14.f, color: AppColors.textColorLight, fontStyle: FontStyle.italic), + textAlign: TextAlign.center, + ), + SizedBox(height: 48.h), + CustomButton( + text: LocaleKeys.downloadApp.tr(context: context), + onPressed: () { + if (Platform.isAndroid) { + Utils.openWebView( + url: "https://play.google.com/store/apps/details?id=com.ejada.hmg", + ); + } + if (Platform.isIOS) { + Utils.openWebView( + url: "https://itunes.apple.com/app/id733503978", + ); + } + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + // height: isTablet || isFoldable ? 46.h : 40.h, + height: 56.h, + iconColor: AppColors.primaryRedColor, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 4c961843..1bccb154 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -138,8 +138,7 @@ class ApiClientImp implements ApiClient { } else {} if (body.containsKey('isDentalAllowedBackend')) { - body['isDentalAllowedBackend'] = - body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; + body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; } if (!body.containsKey('IsPublicRequest')) { @@ -211,7 +210,7 @@ class ApiClientImp implements ApiClient { } // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 5422188; + // body['PatientID'] = 1009156; // body['PatientID'] = 53320; // body['PatientTypeID'] = 1; // body['PatientOutSA'] = 0; @@ -224,9 +223,9 @@ class ApiClientImp implements ApiClient { // body['PatientID'] = 809289; } - if (!url.contains("/paymentApi")) { - body['IsNewFlutterApp'] = true; - } + // if (!url.contains("/paymentApi")) { + // body['IsNewFlutterApp'] = true; + // } body.removeWhere((key, value) => value == null); } @@ -271,11 +270,11 @@ class ApiClientImp implements ApiClient { Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); Clarity.setCustomTag('api_err_method', 'POST'); - + if (_appState.isAuthenticated) { Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } - + Clarity.sendCustomEvent('api_socket_exception'); return; @@ -283,19 +282,19 @@ class ApiClientImp implements ApiClient { final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr(); onFailure(message, -1, failureType: ConnectivityFailure(message)); _analytics.errorTracking.log(endPoint, error: "ClientException: $e"); - + // Enhanced Clarity logging Clarity.setCustomTag('api_err_type', 'client_exception'); Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); Clarity.setCustomTag('api_err_method', 'POST'); - + if (_appState.isAuthenticated) { Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } - + Clarity.sendCustomEvent('api_client_exception'); - + return; } on TimeoutException catch (_) { final message = LocaleKeys.networkTimeout.tr(); @@ -307,36 +306,36 @@ class ApiClientImp implements ApiClient { Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); Clarity.setCustomTag('api_err_method', 'POST'); - + if (_appState.isAuthenticated) { Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } - + Clarity.sendCustomEvent('api_timeout'); - + return; } catch (e) { final message = LocaleKeys.networkUnknownError.tr(); onFailure(message, -1, failureType: ConnectivityFailure(message)); _analytics.errorTracking.log(endPoint, error: "UnknownException: $e"); - + // Enhanced Clarity logging for unknown errors Clarity.setCustomTag('api_err_type', 'unknown_exception'); Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); Clarity.setCustomTag('api_err_method', 'POST'); - + // Truncate exception message for Clarity final exceptionMsg = e.toString(); final truncatedMsg = exceptionMsg.length > 252 ? '...${exceptionMsg.substring(exceptionMsg.length - 252)}' : exceptionMsg; Clarity.setCustomTag('api_err_exception', truncatedMsg); - + if (_appState.isAuthenticated) { Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } - + Clarity.sendCustomEvent('api_unknown_exception'); - + return; } @@ -384,24 +383,19 @@ class ApiClientImp implements ApiClient { onFailure( parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, - failureType: - UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url), + failureType: UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url), ); // logApiEndpointError(endPoint, "session logged out", statusCode); } if (isAllowAny) { - onSuccess(parsed, statusCode, - messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else if (parsed['IsAuthenticated'] == null) { if (parsed['isSMSSent'] == true) { - onSuccess(parsed, statusCode, - messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else if (parsed['MessageStatus'] == 1) { - onSuccess(parsed, statusCode, - messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else if (parsed['Result'] == 'OK') { - onSuccess(parsed, statusCode, - messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { onFailure( parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], @@ -411,8 +405,7 @@ class ApiClientImp implements ApiClient { logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { - onSuccess(parsed, statusCode, - messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } // else if (parsed['IsAuthenticated'] == false) { // onFailure( @@ -424,8 +417,7 @@ class ApiClientImp implements ApiClient { // } else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) { if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode, - messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) { if (parsed['ErrorSearchMsg'] == null) { @@ -459,8 +451,7 @@ class ApiClientImp implements ApiClient { } } else { if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode, - messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { if (parsed['message'] != null) { onFailure( @@ -537,74 +528,74 @@ class ApiClientImp implements ApiClient { final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr(); onFailure(message, -1); _analytics.errorTracking.log(endPoint, error: "SocketException: $e"); - + // Clarity logging for GET requests Clarity.setCustomTag('api_err_type', 'socket_exception'); Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); Clarity.setCustomTag('api_err_method', 'GET'); - + if (_appState.isAuthenticated) { Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } - + Clarity.sendCustomEvent('api_get_socket_exception'); - + return; } on http.ClientException catch (e) { final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr(); onFailure(message, -1); _analytics.errorTracking.log(endPoint, error: "ClientException: $e"); - + Clarity.setCustomTag('api_err_type', 'client_exception'); Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); Clarity.setCustomTag('api_err_method', 'GET'); - + if (_appState.isAuthenticated) { Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } - + Clarity.sendCustomEvent('api_get_client_exception'); - + return; } on TimeoutException catch (_) { final message = LocaleKeys.networkTimeout.tr(); onFailure(message, -1); _analytics.errorTracking.log(endPoint, error: "TimeoutException"); - + Clarity.setCustomTag('api_err_type', 'timeout'); Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); Clarity.setCustomTag('api_err_method', 'GET'); - + if (_appState.isAuthenticated) { Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } - + Clarity.sendCustomEvent('api_get_timeout'); - + return; } catch (e) { final message = LocaleKeys.networkUnknownError.tr(); onFailure(message, -1); _analytics.errorTracking.log(endPoint, error: "UnknownException: $e"); - + Clarity.setCustomTag('api_err_type', 'unknown_exception'); Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); Clarity.setCustomTag('api_err_method', 'GET'); - + final exceptionMsg = e.toString(); final truncatedMsg = exceptionMsg.length > 252 ? '...${exceptionMsg.substring(exceptionMsg.length - 252)}' : exceptionMsg; Clarity.setCustomTag('api_err_exception', truncatedMsg); - + if (_appState.isAuthenticated) { Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } - + Clarity.sendCustomEvent('api_get_unknown_exception'); - + return; } @@ -614,18 +605,18 @@ class ApiClientImp implements ApiClient { if (statusCode < 200 || statusCode >= 400) { onFailure(LocaleKeys.networkErrorWhileFetching.tr(), statusCode); logApiEndpointError(endPoint, LocaleKeys.networkErrorWhileFetching.tr(), statusCode); - + // Clarity logging for GET status code errors Clarity.setCustomTag('api_err_type', 'status_code_error'); Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint)); Clarity.setCustomTag('api_err_method', 'GET'); Clarity.setCustomTag('api_err_status', statusCode.toString()); - + if (_appState.isAuthenticated) { Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } - + Clarity.sendCustomEvent('api_get_status_error'); } else { var parsed = json.decode(utf8.decode(response.bodyBytes)); @@ -813,7 +804,7 @@ class ApiClientImp implements ApiClient { // Extract the last part of the endpoint path for cleaner tracking const int clarityLimit = 100; final cleanEndpoint = endpoint.split('/').last; - + if (cleanEndpoint.length <= clarityLimit) return cleanEndpoint; return '...${cleanEndpoint.substring(cleanEndpoint.length - (clarityLimit - 3))}'; } diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 45d6f48c..72a97884 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -1,6 +1,7 @@ import 'package:amazon_payfort/amazon_payfort.dart'; import 'package:flutter_amazonpaymentservices/environment_type.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:myfatoorah_flutter/myfatoorah_flutter.dart'; class ApiConsts { static const maxSmallScreen = 660; @@ -44,12 +45,17 @@ class ApiConsts { // var payFortEnvironment = FortEnvironment.test; // var applePayMerchantId = "merchant.com.hmgwebservices.uat"; + static var myFatoorahEnvironmentType = MFEnvironment.TEST; + static var myFatoorahCountry = MFCountry.SAUDIARABIA; + static setBackendURLs() { switch (appEnvironmentType) { case AppEnvironmentTypeEnum.prod: baseUrl = "https://hmgwebservices.com/"; payFortEnvironment = FortEnvironment.production; payFortEnvironmentType = EnvironmentType.production; + myFatoorahEnvironmentType = MFEnvironment.LIVE; + myFatoorahCountry = MFCountry.SAUDIARABIA; applePayMerchantId = "merchant.com.hmgwebservices"; SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx"; TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout"; @@ -66,6 +72,8 @@ class ApiConsts { baseUrl = "https://uat.hmgwebservices.com/"; payFortEnvironment = FortEnvironment.test; payFortEnvironmentType = EnvironmentType.sandbox; + myFatoorahEnvironmentType = MFEnvironment.TEST; + myFatoorahCountry = MFCountry.KUWAIT; applePayMerchantId = "merchant.com.hmgwebservices.uat"; SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx'; TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; @@ -77,12 +85,13 @@ class ApiConsts { symptomsCheckerApi = symptomsCheckerApiUAT; symptomsCheckerUsername = 'guest_user'; symptomsCheckerPassword = '123456'; - break; case AppEnvironmentTypeEnum.uat: baseUrl = "https://uat.hmgwebservices.com/"; payFortEnvironment = FortEnvironment.test; payFortEnvironmentType = EnvironmentType.sandbox; + myFatoorahEnvironmentType = MFEnvironment.TEST; + myFatoorahCountry = MFCountry.KUWAIT; applePayMerchantId = "merchant.com.hmgwebservices.uat"; SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx'; TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; @@ -100,6 +109,8 @@ class ApiConsts { baseUrl = "https://webservices.hmg.com/"; payFortEnvironment = FortEnvironment.production; payFortEnvironmentType = EnvironmentType.production; + myFatoorahEnvironmentType = MFEnvironment.LIVE; + myFatoorahCountry = MFCountry.SAUDIARABIA; applePayMerchantId = "merchant.com.hmgwebservices"; SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx"; TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; @@ -117,6 +128,8 @@ class ApiConsts { baseUrl = "https://uat.hmgwebservices.com/"; payFortEnvironment = FortEnvironment.test; payFortEnvironmentType = EnvironmentType.sandbox; + myFatoorahEnvironmentType = MFEnvironment.TEST; + myFatoorahCountry = MFCountry.KUWAIT; applePayMerchantId = "merchant.com.hmgwebservices.uat"; SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx'; TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; @@ -134,6 +147,8 @@ class ApiConsts { baseUrl = "https://uat.hmgwebservices.com/"; payFortEnvironment = FortEnvironment.test; payFortEnvironmentType = EnvironmentType.sandbox; + myFatoorahEnvironmentType = MFEnvironment.TEST; + myFatoorahCountry = MFCountry.KUWAIT; applePayMerchantId = "merchant.com.hmgwebservices.uat"; SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx'; TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; @@ -301,13 +316,18 @@ class ApiConsts { static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations"; // ************ static values for Api **************** - static final double appVersionID = 22.1; + static final double appVersionID = 21.6; - // static final double appVersionID = 50.7; static final int appChannelId = 3; static final String appIpAddress = "10.20.10.20"; static final String appGeneralId = "Cs2020@2016\$2958"; static final String sessionID = 'TMRhVmkGhOsvamErw'; + + // Notification Center GUIDs - Configure these values as per your notification center setup + static final String notificationCenterAppGUID = "SzFwWjI0TjhmSjZreDVsNmc3SDRSdWlxUG1h"; + static final String notificationServiceGUID_Login = "D8BD5C76-4B50-4122-81D0-C3AA25DB08A2"; + static final String notificationServiceGUID_Registration = "690D3595-A1E1-489A-B9AB-6270E83AD6ED"; + static final String notificationServiceGUID_FamilyFile = "318285E4-6221-4A66-8FAF-2265D2B4B24C"; } // PACKAGES and OFFERS @@ -371,8 +391,6 @@ var DRIVER_LOCATION = 'Services/Patients.svc/REST/PatientER_GetDriverLocation'; //weather var WEATHER_INDICATOR = 'Services/Weather.svc/REST/GetCityInfo'; -var GET_PRIVILEGE = 'Services/Patients.svc/REST/Service_Privilege'; - // Wifi Credentials var WIFI_CREDENTIALS = "Services/Patients.svc/Hmg_SMS_Get_By_ProjectID_And_PatientID"; @@ -864,8 +882,7 @@ var GET_CUSTOMER_INFO = "VerifyCustomer"; //Pharmacy -var GET_PHARMACY_CATEGORISE = - 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=0'; +var GET_PHARMACY_CATEGORISE = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=0'; var GET_OFFERS_CATEGORISE = 'discountcategories'; var GET_OFFERS_PRODUCTS = 'offerproducts/'; var GET_CATEGORISE_PARENT = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id='; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index bd850985..bd6d7f87 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -385,6 +385,7 @@ class AppAssets { static const String hmg_pharmacy_logo = '$svgBasePath/hmg_pharmacy_logo.svg'; static const String redeem_icon = '$svgBasePath/redeem_icon.svg'; static const String expired_icon = '$svgBasePath/expired_icon.svg'; + static const String icon_migrate = '$svgBasePath/Icon-migrate.svg'; //Health Calculators static const String bodyfatcalc = '$svgBasePath/bfc.svg'; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 0d015922..25753e1a 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -48,12 +48,19 @@ class AppState { bool isPaytabsEnabled = false; bool isPayfortHostedPageEnabled = true; + bool isMyFatoorahEnabled = false; + // bool isMyFatoorahEnabled = true; void setIsPaytabsEnabled(bool value) { - isPaytabsEnabled = value; + isPaytabsEnabled = false; // isPaytabsEnabled = true; } + void setIsMyFatoorahEnabled(bool value) { + isMyFatoorahEnabled = false; + // isMyFatoorahEnabled = true; + } + void setIsPayfortHostedPageEnabled(bool value) { isPayfortHostedPageEnabled = value; // isPayfortHostedPageEnabled = false; @@ -63,7 +70,7 @@ class AppState { bool isChildLoggedIn = false; bool isGMSAvailable = true; bool isAndroid = true; - bool isRatedVisible =false; + bool isRatedVisible = false; void setAuthenticatedUser(AuthenticatedUser? authenticatedUser, {bool isFamily = false}) { if (isFamily) { _authenticatedChildUser = authenticatedUser; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index b6c7fb38..97458e1b 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -44,6 +44,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_rep import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart'; import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; +import 'package:hmg_patient_app_new/features/myfatoorah/myfatoorah_view_model.dart'; import 'package:hmg_patient_app_new/features/notifications/notifications_repo.dart'; import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_discounts_repo.dart'; @@ -84,7 +85,9 @@ import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/notification_service.dart'; import 'package:hmg_patient_app_new/services/permission_service.dart'; +import 'package:hmg_patient_app_new/services/remote_config_service.dart'; import 'package:hmg_patient_app_new/services/security_service.dart'; +import 'package:hmg_patient_app_new/features/occasion_theme/occasion_theme_view_model.dart'; import 'package:hmg_patient_app_new/core/services/turnstile_service.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; @@ -380,8 +383,22 @@ class AppDependencies { errorHandlerService: getIt(), )); + getIt.registerLazySingleton(() => MyFatoorahViewModel( + appState: getIt(), + errorHandlerService: getIt(), + payfortRepo: getIt(), + )); + getIt.registerLazySingleton( () => ClarityService(appState: getIt.get()), ); + + // Remote-config-driven "occasion" theming (National Day, Eid, etc.). + getIt.registerLazySingleton(() => RemoteConfigServiceImp(loggerService: getIt())); + getIt.registerLazySingleton(() => OccasionThemeViewModel( + remoteConfigService: getIt(), + cacheService: getIt(), + loggerService: getIt(), + )); } } diff --git a/lib/core/myFatoorah_config.dart b/lib/core/myFatoorah_config.dart new file mode 100644 index 00000000..582ced3c --- /dev/null +++ b/lib/core/myFatoorah_config.dart @@ -0,0 +1,5 @@ +class MyFatoorahConfig { + + static const String configAPIKey = "SK_KWT_0auB76h3Frhj7aUBSZa6H5ei792eZoGAPh0bBxKsrgtyjqKgf38MPnZqzGqagErb"; + +} \ No newline at end of file diff --git a/lib/core/talsec_config.dart b/lib/core/talsec_config.dart index 1603d419..d29d0dbb 100644 --- a/lib/core/talsec_config.dart +++ b/lib/core/talsec_config.dart @@ -2,12 +2,13 @@ import 'package:freerasp/freerasp.dart'; final talsecConfig = TalsecConfig( androidConfig: AndroidConfig( - packageName: 'com.cloudsolutions.HMGPatientApp', - signingCertHashes: ['6tvWaoN5coG4SnfxGbdQlcLmM0J4ePQwDjrKIg+QkV0=', 'j6VEqVhrypHMIiXiFdRLDdGwjGaMGWY7KAdBJA+Z4Pc='], // Must be the release cert hash + packageName: 'com.ejada.hmg', + signingCertHashes: ['6tvWaoN5coG4SnfxGbdQlcLmM0J4ePQwDjrKIg+QkV0=', 'j6VEqVhrypHMIiXiFdRLDdGwjGaMGWY7KAdBJA+Z4Pc=', 'EKw2Bd3VYz//DYjDnJLxEh3uGqhNDIWubLmpwAx/A7U='], // Must be the release cert hash supportedStores: ['com.android.vending'], // Google Play Store ), iosConfig: IOSConfig( - bundleIds: ['com.cloudsolutions.HMGPatientApp'], // iOS Bundle ID + // bundleIds: ['com.cloudsolutions.HMGPatientApp'], // iOS Bundle ID + bundleIds: ['com.HMG.HMG-Smartphone'], // iOS Bundle ID teamId: '3A359E86ZF', // Found in Apple Developer portal ), watcherMail: '', // Required to receive security alerts diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index 7510786d..b57f912d 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -28,21 +28,21 @@ class DateUtil { const end = "+0300)"; final startIndex = date.indexOf(start); final endIndex = date.indexOf(end, startIndex + start.length); - if (projectId == 2 || projectId == 3) { - return DateTime.fromMillisecondsSinceEpoch( - int.parse( - date.substring(startIndex + start.length, endIndex), - ), - isUtc: true) - .add(Duration(hours: 4)); - } else { - return DateTime.fromMillisecondsSinceEpoch( + // if (projectId == 2 || projectId == 3 || projectId == 4) { + // return DateTime.fromMillisecondsSinceEpoch( + // int.parse( + // date.substring(startIndex + start.length, endIndex), + // ), + // isUtc: true) + // .add(Duration(hours: 4)); + // } else { + return DateTime.fromMillisecondsSinceEpoch( int.parse( date.substring(startIndex + start.length, endIndex), ), isUtc: true) .add(Duration(hours: 3)); - } + // } } else { return DateTime.now(); } diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart index 89f6969a..30e251e1 100644 --- a/lib/core/utils/push_notification_handler.dart +++ b/lib/core/utils/push_notification_handler.dart @@ -27,6 +27,7 @@ import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:uuid/uuid.dart'; +import 'package:huawei_push/huawei_push.dart' as h_push; // Global tracking variables for call state bool _isCallInProgress = false; @@ -425,10 +426,30 @@ class PushNotificationHandler { if (Platform.isAndroid) { try { - final fcmToken = await FirebaseMessaging.instance.getToken().catchError((err) { - log(err); - }); - if (fcmToken != null) onToken(fcmToken); + if (!(await Utils.isGoogleServicesAvailable())) { + h_push.Push.enableLogger(); + final result = await h_push.Push.setAutoInitEnabled(true); + + h_push.Push.onNotificationOpenedApp.listen((message) { + // newMessage(toFirebaseRemoteMessage(message)); + }, onError: (e) => print(e.toString())); + + h_push.Push.onMessageReceivedStream.listen((message) { + // newMessage(toFirebaseRemoteMessage(message)); + }, onError: (e) => print(e.toString())); + + h_push.Push.getTokenStream.listen((token) { + onToken(token); + }, onError: (e) => print(e.toString())); + h_push.Push.getToken(''); + + h_push.Push.registerBackgroundMessageHandler(backgroundMessageHandler); + } else { + final fcmToken = await FirebaseMessaging.instance.getToken().catchError((err) { + log(err); + }); + if (fcmToken != null) onToken(fcmToken); + } // } } catch (ex) { log("Notification Exception: $ex"); @@ -587,7 +608,6 @@ class PushNotificationHandler { // Permission.audio, // Permission.microphone, ].request(); - log("=-=-=-=-=-=-=-=-=-=-"); log(statuses[Permission.notification].toString()); } } catch (_) { diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index 62399abd..b7691eb5 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -75,6 +75,12 @@ class RequestUtils { } request.isRegister = false; } + + request.notificationCenterAppGUID = ApiConsts.notificationCenterAppGUID; + request.notificationCenterAppServiceGUID = isForRegister + ? ApiConsts.notificationServiceGUID_Registration + : ApiConsts.notificationServiceGUID_Login; + return request; } @@ -148,6 +154,12 @@ class RequestUtils { } request.deviceTypeID = request.searchType; + + request.notificationCenterAppGUID = ApiConsts.notificationCenterAppGUID; + request.notificationCenterAppServiceGUID = (registeredData != null && registeredData.isRegister == true) + ? ApiConsts.notificationServiceGUID_Registration + : ApiConsts.notificationServiceGUID_Login; + return request; } @@ -220,6 +232,13 @@ class RequestUtils { request.familyRegionID = zipCode == CountryEnum.saudiArabia.countryCode ? 1 : 2; } + request.notificationCenterAppGUID = ApiConsts.notificationCenterAppGUID; + request.notificationCenterAppServiceGUID = isFormFamilyFile + ? ApiConsts.notificationServiceGUID_FamilyFile + : isForRegister + ? ApiConsts.notificationServiceGUID_Registration + : ApiConsts.notificationServiceGUID_Login; + return request; } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index d3107d72..0b5049cf 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -135,7 +135,7 @@ class Utils { static String convertStringToDateTime(String dateTimeString) { String timeString = dateTimeString; // Parse the time string using DateFormat - DateFormat format = DateFormat.Hms(["en-US"]); // 'Hms' = 'HH:mm:ss' + DateFormat format = DateFormat.Hms("en-US"); // 'Hms' = 'HH:mm:ss' DateTime time = format.parse(timeString); DateTime now = DateTime.now(); @@ -437,8 +437,8 @@ class Utils { onCancelTap(); } }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, + backgroundColor: AppColors.criticalActionColor, + borderColor: AppColors.criticalActionColor, textColor: AppColors.whiteColor, // icon: AppAssets.cancel, // iconColor: AppColors.whiteColor, @@ -516,8 +516,8 @@ class Utils { onCancelTap(); } }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, + backgroundColor: AppColors.criticalActionColor, + borderColor: AppColors.criticalActionColor, textColor: AppColors.whiteColor, // icon: AppAssets.cancel, // iconColor: AppColors.whiteColor, diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 291e4a9b..1c95d99a 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -222,6 +222,8 @@ class AuthenticationRepoImp implements AuthenticationRepo { if (isRegister) { newRequest["activationCode"] = activationCode ?? "0000"; newRequest["isSilentLogin"] = activationCode == null; + newRequest.remove("NotificationCenterAppGUID"); + newRequest.remove("NotificationCenterAppSeviceGUID"); } else { newRequest.activationCode = activationCode ?? "0000"; newRequest.isSilentLogin = activationCode == null; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index a8719f29..e5324525 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:get_it/get_it.dart'; +import 'package:hmg_patient_app_new/app_closed_page.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'; @@ -1248,6 +1249,7 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setUserBloodGroup = activation.patientBlodType ?? "N/A"; _appState.setIsPaytabsEnabled(Utils.havePrivilege(120)); _appState.setIsPayfortHostedPageEnabled(Utils.havePrivilege(121)); + _appState.setIsMyFatoorahEnabled(Utils.havePrivilege(123)); // Refresh privileges after user switching to ensure correct privileges are loaded if (isSwitchUser) { @@ -1419,7 +1421,8 @@ class AuthenticationViewModel extends ChangeNotifier { if (value) { LoaderBottomSheet.showLoader(); success(); - loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint); + // loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint); + loginTypeEnum = (Platform.isIOS ? LoginTypeEnum.face : LoginTypeEnum.fingerprint); if (!_appState.isAuthenticated) { //commenting this api to check either the same flow working or not because this api does not needed further if work fine we will remove this await getPatientDeviceData(loginTypeEnum.toInt); @@ -1516,7 +1519,9 @@ class AuthenticationViewModel extends ChangeNotifier { LoaderBottomSheet.hideLoader(); if (apiResponse.data["MessageStatus"] == 1) { LoaderBottomSheet.showLoader(loadingText: "Your medical file has been created successfully. \nPlease proceed to login."); - //TODO: Here We Need to Show a Dialog Of Something in the case of Success. + // Reset registration payload and clear healthId so subsequent logins hit the login endpoint + _appState.getUserRegistrationPayload.healthId = null; + _appState.setUserRegistrationPayload = RegistrationDataModelPayload(); await clearDefaultInputValues(); // This will Clear All Default Values Of User. Future.delayed(Duration(seconds: 1), () { LoaderBottomSheet.hideLoader(); @@ -1796,6 +1801,7 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setProjectsDetailList(projectDetailListModel); _appState.setIsPaytabsEnabled(Utils.havePrivilege(120)); _appState.setIsPayfortHostedPageEnabled(Utils.havePrivilege(121)); + _appState.setIsMyFatoorahEnabled(Utils.havePrivilege(123)); } }, ); @@ -1824,11 +1830,13 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setSelectDeviceByImeiRespModelElement(null); _appState.setNHICUserData = CheckUserStatusResponseNHIC(); _appState.setUserRegistrationPayload = RegistrationDataModelPayload(); + // Clear privilege lists - _appState.setPrivilegeModelList([]); - _appState.setVidaPlusProjectList([]); - _appState.setHMCProjectList([]); - _appState.setProjectsDetailList([]); + // _appState.setPrivilegeModelList([]); + // _appState.setVidaPlusProjectList([]); + // _appState.setHMCProjectList([]); + // _appState.setProjectsDetailList([]); + _appState.clearUserSwitchPayloads(); await clearDefaultInputValues(); _navigationService.pushAndReplace(AppRoutes.landingScreen); diff --git a/lib/features/authentication/models/request_models/send_activation_request_model.dart b/lib/features/authentication/models/request_models/send_activation_request_model.dart index 6e2ddb93..840227ea 100644 --- a/lib/features/authentication/models/request_models/send_activation_request_model.dart +++ b/lib/features/authentication/models/request_models/send_activation_request_model.dart @@ -31,6 +31,8 @@ class SendActivationRequest { int? familyRegionID; bool? isPatientExcluded; String? turnstileToken; + String? notificationCenterAppGUID; + String? notificationCenterAppServiceGUID; SendActivationRequest( {this.patientMobileNumber, @@ -64,7 +66,10 @@ class SendActivationRequest { this.status, this.familyRegionID, this.isPatientExcluded, - this.turnstileToken}); + this.turnstileToken, + this.notificationCenterAppGUID, + this.notificationCenterAppServiceGUID +}); SendActivationRequest.fromJson(Map json) { patientMobileNumber = json['PatientMobileNumber']; @@ -99,6 +104,8 @@ class SendActivationRequest { familyRegionID = json['FamilyRegionID']; isPatientExcluded = json['IsPatientExcluded']; turnstileToken = json['TurnstileToken']; + notificationCenterAppGUID = json['NotificationCenterAppGUID']; + notificationCenterAppServiceGUID = json['NotificationCenterAppSeviceGUID']; } Map toJson() { @@ -141,6 +148,8 @@ class SendActivationRequest { if (turnstileToken != null) { data['TurnstileToken'] = turnstileToken; } + data['NotificationCenterAppGUID'] = notificationCenterAppGUID; + data['NotificationCenterAppSeviceGUID'] = notificationCenterAppServiceGUID; return data; } } diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index a705d84a..caf97cb8 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -37,7 +37,7 @@ abstract class BookAppointmentsRepo { Future>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, {bool continueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}); - Future>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); + Future>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, bool isReschedule = false}); Future>> insertSpecificAppointment( {required int docID, @@ -483,12 +483,12 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { } @override - Future> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}) async { + Future> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, bool isReschedule = false}) async { Map requestBody = { "AppointmentID": patientAppointmentHistoryResponseModel.appointmentNo, "ClinicID": patientAppointmentHistoryResponseModel.clinicID, "ProjectID": patientAppointmentHistoryResponseModel.projectID, - "CancelToReschadual": false, + "CancelToReschadual": isReschedule, "EndTime": patientAppointmentHistoryResponseModel.endTime, "StartTime": patientAppointmentHistoryResponseModel.startTime, "DoctorID": patientAppointmentHistoryResponseModel.doctorID, diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index a4530eee..5865a829 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -948,8 +948,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } - Future cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await bookAppointmentsRepo.cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); + Future cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, bool isReschedule = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await bookAppointmentsRepo.cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, isReschedule: isReschedule); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), @@ -1011,7 +1011,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { appointmentDate: apiResponse.data["SameClinicApptList"][0]['AppointmentDate'], ); - await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { + await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, isReschedule: true).then((val) async { // LoaderBottomSheet.hideLoader(); Future.delayed(Duration(milliseconds: 50)).then((value) async {}); // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingYourAppointment.tr()); @@ -1069,7 +1069,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { // loadingText: LocaleKeys.reschedulingAppo.tr(context: navigationService.navigatorKey.currentContext!), loadingText: LocaleKeys.loadingText.tr(context: navigationService.navigatorKey.currentContext!), ); - await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { + await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, isReschedule: true).then((val) async { LoaderBottomSheet.hideLoader(); Future.delayed(Duration(milliseconds: 50)).then((value) async {}); LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingYourAppointment.tr()); @@ -1174,7 +1174,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); - await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { + await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, isReschedule: true).then((val) async { navigationService.pop(); Future.delayed(Duration(milliseconds: 50)).then((value) async {}); LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingYourAppointment.tr()); diff --git a/lib/features/common/models/family_file_request.dart b/lib/features/common/models/family_file_request.dart index 047b9af0..e3802bd5 100644 --- a/lib/features/common/models/family_file_request.dart +++ b/lib/features/common/models/family_file_request.dart @@ -47,7 +47,7 @@ class FamilyFileRequest { "SharedPatientIdentificationID": sharedPatientIdentificationId, "SearchType": searchType, "SharedPatientMobileNumber": sharedPatientMobileNumber, - "zipCode": zipCode, + "ZipCode": zipCode, "isRegister": isRegister, "PatientStatus": patientStatus, "isDentalAllowedBackend": isDentalAllowedBackend, diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 1c25ef9d..90ceacfe 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -209,12 +209,12 @@ class LabViewModel extends ChangeNotifier { final result = await labRepo.getLabResultReportPDF(labOrder: labOrder); result.fold( - (failure) async => await errorHandlerService.handleError( - failure: failure, - onOkPressed: () { - onError!(failure.message); - }, - ), + // (failure) async => await errorHandlerService.handleError( + (failure) async { + if (onError != null) { + onError(failure.message); + } + }, (apiResponse) { if (apiResponse.messageStatus == 2) { onError!(apiResponse.errorMessage!); @@ -363,6 +363,7 @@ class LabViewModel extends ChangeNotifier { labOrder.doctorName = doctorName; labOrder.projectName = projectName; labOrder.orderDate = appointmentDate; + // labOrder.testDetails = apiResponse.data; currentlySelectedPatientOrder = labOrder; @@ -881,8 +882,9 @@ class LabViewModel extends ChangeNotifier { List> results = []; Map orderData = {"order_date": labOrder.orderDate ?? "", "clinic": labOrder.clinicDescription ?? "", "doctor": labOrder.doctorName ?? "", "results": []}; List> testResults = []; - if (labOrder.testDetails != null && labOrder.testDetails!.isNotEmpty) { - for (var testDetail in labOrder.testDetails!) { + // if (labOrder.testDetails != null && labOrder.testDetails!.isNotEmpty) { + // for (var testDetail in labOrder.testDetails!) { + for (var testDetail in mainLabResultsByHospitalsList!) { LabResult? matchingResult = mainLabResultsByHospitalsList.firstWhere((labResult) => labResult.testCode == testDetail.testCode || labResult.description == testDetail.description, orElse: () => LabResult()); testResults.add({ @@ -893,7 +895,7 @@ class LabViewModel extends ChangeNotifier { "flag": matchingResult.calculatedResultFlag ?? "N" }); } - } + // } orderData["results"] = testResults; results.add(orderData); diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 29f653ec..f3a4e2e8 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -864,4 +864,17 @@ class MedicalFileViewModel extends ChangeNotifier { print("❌ Error updating family member profile image in cache: $e"); } } + + bool checkExistingMedicalReportStatus(PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel) { + bool hasExistingRequest = false; + + for (var report in patientMedicalReportRequestedList) { + if (report.clinicID == patientAppointmentHistoryResponseModel.clinicID && report.doctorID == patientAppointmentHistoryResponseModel.doctorID) { + hasExistingRequest = true; + break; + } + } + + return hasExistingRequest; + } } diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 87fb2ec2..ef3cd283 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -44,7 +44,7 @@ abstract class MyAppointmentsRepo { Future>> generateAppointmentQR({required int clinicID, required int projectID, required String appointmentNo, required int isFollowUp}); - Future>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); + Future>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, bool isReschedule = false}); Future>> confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); @@ -341,12 +341,12 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { } @override - Future> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}) async { + Future> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, bool isReschedule = false}) async { Map requestBody = { "AppointmentID": patientAppointmentHistoryResponseModel.appointmentNo, "ClinicID": patientAppointmentHistoryResponseModel.clinicID, "ProjectID": patientAppointmentHistoryResponseModel.projectID, - "CancelToReschadual": false, + "CancelToReschadual": isReschedule, "EndTime": patientAppointmentHistoryResponseModel.endTime, "StartTime": patientAppointmentHistoryResponseModel.startTime, "DoctorID": patientAppointmentHistoryResponseModel.doctorID, diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index b153c556..f040fd82 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -639,8 +639,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { ); } - Future cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await myAppointmentsRepo.cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); + Future cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, bool isReschedule = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, isReschedule: isReschedule); result.fold( // (failure) async => await errorHandlerService.handleError(failure: failure), @@ -772,9 +772,9 @@ class MyAppointmentsViewModel extends ChangeNotifier { patientMyDoctorsList = apiResponse.data!; isPatientMyDoctorsLoading = false; - if (!isTop8) { - isMyDoctorsDataToBeLoaded = false; - } + // if (!isTop8) { + isMyDoctorsDataToBeLoaded = false; + // } notifyListeners(); if (onSuccess != null) { diff --git a/lib/features/myfatoorah/myfatoorah_view_model.dart b/lib/features/myfatoorah/myfatoorah_view_model.dart new file mode 100644 index 00000000..b76b251e --- /dev/null +++ b/lib/features/myfatoorah/myfatoorah_view_model.dart @@ -0,0 +1,197 @@ +import 'dart:developer'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/cache_consts.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/myFatoorah_config.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart'; +import 'package:hmg_patient_app_new/features/payfort/models/payfort_response_insert_model.dart'; +import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; +import 'package:hmg_patient_app_new/generated/codegen_loader.g.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:myfatoorah_flutter/myfatoorah_flutter.dart'; + +class MyFatoorahViewModel extends ChangeNotifier { + final AppState appState; + final ErrorHandlerService errorHandlerService; + final PayfortRepo payfortRepo; + + MyFatoorahViewModel({ + required this.appState, + required this.errorHandlerService, + required this.payfortRepo, + }); + + // Payment Method ID + // 2: Credit/Debit Card + // 6: Mada + + initiateCardPayment({ + num amount = 10.0, + paymentDescription = "", + String displayCurrencyIso = MFCurrencyISO.KUWAIT_KWD, + required String paymentOption, + required String liveServiceID, + required String isScheduleLiveCare, + required String appointmentDate, + required String appointmentNo, + required String doctorID, + required String fileNumber, + required String serviceID, + required String projectID, + Function(MFGetPaymentStatusResponse)? onSuccess, + Function(String)? onError, + }) async { + LoaderBottomSheet.showLoader(); + await MFSDK.init(MyFatoorahConfig.configAPIKey, ApiConsts.myFatoorahCountry, ApiConsts.myFatoorahEnvironmentType); + + MFSDK.setUpActionBar(toolBarTitle: paymentDescription, toolBarTitleColor: "#ffffff", toolBarBackgroundColor: '#ED1C2B', isShowToolBar: true); + + var request = MFExecutePaymentRequest(paymentMethodId: paymentOption.toLowerCase() == "mada" ? 6 : 2, invoiceValue: amount); + request.displayCurrencyIso = displayCurrencyIso; + request.customerName = ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? "Test test" : "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}"; + request.customerEmail = "CustID_${appState.getAuthenticatedUser()!.patientId}@HMG.com"; + + await MFSDK.executePayment(request, appState.isArabic() ? MFLanguage.ARABIC : MFLanguage.ENGLISH, (invoiceId) async { + PayfortRequestInsertModel payfortRequestInsertModel = PayfortRequestInsertModel(); + + payfortRequestInsertModel.commandType = "SALE"; + payfortRequestInsertModel.amount = amount; + payfortRequestInsertModel.customerEmail = getIt.get().getAuthenticatedUser()!.emailAddress; + payfortRequestInsertModel.orderDescription = paymentDescription; + payfortRequestInsertModel.serviceID = serviceID; + payfortRequestInsertModel.paymentOption = paymentOption; + payfortRequestInsertModel.clientRequestID = invoiceId; + payfortRequestInsertModel.projectID = projectID; + payfortRequestInsertModel.fileNumber = getIt.get().getAuthenticatedUser()!.patientId.toString(); + payfortRequestInsertModel.customerName = "${getIt.get().getAuthenticatedUser()!.firstName} ${getIt.get().getAuthenticatedUser()!.lastName}"; + payfortRequestInsertModel.currency = getIt.get().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED"; + payfortRequestInsertModel.liveServiceID = liveServiceID; + payfortRequestInsertModel.latitude = getIt.get().userLat.toString(); + payfortRequestInsertModel.longitude = getIt.get().userLong.toString(); + payfortRequestInsertModel.merchantReference = invoiceId; + payfortRequestInsertModel.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken); + payfortRequestInsertModel.language = getIt.get().isArabic() ? "ar" : "en"; + payfortRequestInsertModel.integrationId = "6"; // PayTabs integration ID + payfortRequestInsertModel.isSchedule = isScheduleLiveCare; + payfortRequestInsertModel.appointmentDate = appointmentDate; + payfortRequestInsertModel.appointmentNo = appointmentNo; + payfortRequestInsertModel.doctorID = doctorID; + + // Call request insert API before payment + await paytabsRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel); + + LoaderBottomSheet.hideLoader(); + }).then((value) async { + var valJson = value.toString(); + print(valJson); + + // // Create PayfortResponseInsertModel (same as Payfort implementation) + PayfortResponseInsertModel payfortResponseInsertModel = PayfortResponseInsertModel(); + + payfortResponseInsertModel.fortId = value.invoiceTransactions!.first.transactionId ?? ""; + payfortResponseInsertModel.commandType = "SALE"; + // payfortResponseInsertModel.amount = paytabsTransactionResponseModel.tranTotal ?? 0; + payfortResponseInsertModel.amount = amount; + payfortResponseInsertModel.sadadOLP = ""; + payfortResponseInsertModel.paymentOption = value.invoiceTransactions!.first.paymentGateway ?? ""; + payfortResponseInsertModel.customerIP = ""; + payfortResponseInsertModel.eCI = ""; + payfortResponseInsertModel.responseMessage = value.invoiceTransactions!.first.transactionStatus ?? ""; + payfortResponseInsertModel.cardNumber = value.invoiceTransactions!.first.cardNumber ?? ""; + payfortResponseInsertModel.cardBin = ""; + payfortResponseInsertModel.installmentsNumber = 0; + payfortResponseInsertModel.status = (value.invoiceStatus ?? "").toLowerCase() == "paid" ? "14" : "00"; + payfortResponseInsertModel.merchantRef = value.invoiceId.toString(); + payfortResponseInsertModel.isRefund = false; + payfortResponseInsertModel.remmeberMe = true; + payfortResponseInsertModel.reconciliationReference = ""; + + // Call response insert API after payment + await paytabsResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel); + + if ((value.invoiceStatus ?? "").toLowerCase() == "paid") { + if (onSuccess != null) { + onSuccess(value); + } + } else { + if (onError != null) { + onError(LocaleKeys.paymentFailedPleaseTryAgain.tr()); + } + } + }).catchError((error) { + if (onError != null) { + onError(error.message); + } + }); + } + + executeApplePayPayment() { + MFApplepay.executeApplePayPayment(onInvoiceCreated: (invoiceID) { + log( + invoiceID.toString(), + ); + }).then((value) { + log( + value.toString(), + ); + }).catchError((error) { + log(error.message); + }); + } + + initApplePayPayment(num invoiceValue) async { + await MFSDK.init(MyFatoorahConfig.configAPIKey, ApiConsts.myFatoorahCountry, ApiConsts.myFatoorahEnvironmentType); + + await MFSDK.initSession(MFInitiateSessionRequest(), MFLanguage.ENGLISH).then((value) async { + var executePaymentRequest = MFExecutePaymentRequest(invoiceValue: invoiceValue, sessionId: value.sessionId, displayCurrencyIso: MFCurrencyISO.SAUDIARABIA_SAR); + + MFApplepay.setupApplePay(value, executePaymentRequest, MFLanguage.ENGLISH, merchantName: "Dr Sulaiman Al Habib"); + }).catchError((error) { + log(error.message); + }); + } + + Future paytabsRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await payfortRepo.payfortRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future paytabsResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await payfortRepo.payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/notifications/notifications_view_model.dart b/lib/features/notifications/notifications_view_model.dart index 85049ec9..0f3cc160 100644 --- a/lib/features/notifications/notifications_view_model.dart +++ b/lib/features/notifications/notifications_view_model.dart @@ -13,11 +13,16 @@ class NotificationsViewModel extends ChangeNotifier { ErrorHandlerService errorHandlerService; List notificationsList = []; + List readdNotificationsList = []; + List unreadNotificationsList = []; + List allNotificationsList = []; int currentPage = 0; int pagingSize = 50; int notificationStatusID = 2; // Default to status 2 (e.g., unread) + int selectedNotificationsTabIndex = 0; + NotificationsViewModel({ required this.notificationsRepo, required this.errorHandlerService, @@ -35,6 +40,18 @@ class NotificationsViewModel extends ChangeNotifier { notifyListeners(); } + void onNotificationTabChange(int index) { + selectedNotificationsTabIndex = index; + if (index == 0) { + notificationsList = allNotificationsList; + } else if (index == 1) { + notificationsList = unreadNotificationsList; + } else if (index == 2) { + notificationsList = readdNotificationsList; + } + notifyListeners(); + } + setNotificationStatusID(int statusID) { notificationStatusID = statusID; notificationsList.clear(); @@ -49,6 +66,9 @@ class NotificationsViewModel extends ChangeNotifier { }) async { isNotificationsLoading = true; unreadNotificationsCount = 0; + + notificationsList.clear(); + notifyListeners(); final result = await notificationsRepo.getAllNotifications( @@ -80,17 +100,37 @@ class NotificationsViewModel extends ChangeNotifier { hasMoreNotifications = false; } - notificationsList.addAll(newNotifications); - for (var notification in notificationsList) { - if (notification.isRead == false) { - unreadNotificationsCount++; + try { + notificationsList.addAll(newNotifications); + + // allNotificationsList.clear(); + // readdNotificationsList.clear(); + // unreadNotificationsList.clear(); + + // for (var notification in notificationsList) { + // if (notification.isRead == false) { + // unreadNotificationsCount++; + // unreadNotificationsList.add(notification); + // } else { + // readdNotificationsList.add(notification); + // } + // } + + // Filter out the items directly without a loop + unreadNotificationsList = notificationsList.where((n) => n.isRead == false).toList(); + readdNotificationsList = notificationsList.where((n) => n.isRead == true).toList(); + + // Calculate count based on the new list length + unreadNotificationsCount = unreadNotificationsList.length; + + currentPage++; + allNotificationsList = notificationsList; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); } - } - currentPage++; - - notifyListeners(); - if (onSuccess != null) { - onSuccess(apiResponse); + } catch (ex) { + print(ex.toString()); } } }, diff --git a/lib/features/occasion_theme/occasion_config_model.dart b/lib/features/occasion_theme/occasion_config_model.dart new file mode 100644 index 00000000..26ddf733 --- /dev/null +++ b/lib/features/occasion_theme/occasion_config_model.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; + +/// Model representing a remotely-configurable "occasion" theme +/// (e.g. Saudi National Day, Founding Day, Eid, Ramadan, ...). +/// +/// Everything here is fully data-driven — nothing about a specific +/// occasion (name, color, dates, banner asset) is hardcoded in the app. +/// All values are supplied via Firebase Remote Config (or the bundled +/// default JSON fallback used before the first successful fetch). +@immutable +class OccasionConfigModel { + /// Unique identifier of the occasion (e.g. "national_day"). Used only + /// for logging/analytics — no app logic branches on this value. + final String key; + + /// Master kill-switch. When false, the app must look exactly like the + /// default/current theme, regardless of any other field below. + final bool enabled; + + /// Activation window. Both must be present and `DateTime.now()` must fall + /// within [startDate, endDate] for this occasion to be eligible — a + /// missing date on either side makes the occasion inactive (dates are + /// mandatory, not an optional override of [enabled]). This lets several + /// occasions be scheduled in advance in the same list (see + /// [OccasionConfigModel.resolveActive]) without colliding. + final DateTime? startDate; + final DateTime? endDate; + + /// Hex string (e.g. "#00894A") for the occasion's primary brand color. + /// When null, the app's default primary color is preserved. + final String? primaryColorHex; + + /// Remote (or bundled asset path) image rendered as a decorative + /// background/watermark, drawn using [backgroundImageOpacity]. + final String? backgroundOpacityImage; + + /// Opacity (0.0 - 1.0) applied to [backgroundOpacityImage]. + final double backgroundImageOpacity; + + /// Remote (or bundled asset path) image rendered as the occasion's main + /// header/banner (e.g. the National Day greeting banner), drawn at full + /// opacity on top of [backgroundOpacityImage]. + final String? headerImage; + + const OccasionConfigModel({ + required this.key, + required this.enabled, + this.startDate, + this.endDate, + this.primaryColorHex, + this.backgroundOpacityImage, + this.backgroundImageOpacity = 1.0, + this.headerImage, + }); + + /// Fully inert config — identical to the app's current/default look. + factory OccasionConfigModel.disabled() => const OccasionConfigModel( + key: 'none', + enabled: false, + startDate: null, + endDate: null, + primaryColorHex: null, + backgroundOpacityImage: null, + backgroundImageOpacity: 1.0, + headerImage: null, + ); + + factory OccasionConfigModel.fromJson(Map json) { + return OccasionConfigModel( + key: (json['key'] as String?) ?? 'none', + enabled: (json['enabled'] as bool?) ?? false, + // enabled: true, + startDate: _parseDate(json['start_date']), + endDate: _parseDate(json['end_date']), + primaryColorHex: json['primary_color'] as String?, + backgroundOpacityImage: json['background_opacity_image'] as String?, + backgroundImageOpacity: (json['background_image_opacity'] as num?)?.toDouble() ?? 1.0, + headerImage: json['header_image'] as String?, + ); + } + + /// Parses the remote-config value as a JSON array of occasions — lets + /// several occasions be scheduled in advance (e.g. National Day, Founding + /// Day, Eid), each with its own non-overlapping [startDate]/[endDate] + /// window, all under the same `occasion_config` remote-config key. + static List listFromJson(List jsonList) { + return jsonList.map((e) => OccasionConfigModel.fromJson(e as Map)).toList(); + } + + /// Picks whichever scheduled occasion is enabled and whose + /// [startDate]/[endDate] window contains `now` — first match in list + /// order wins, so entries are expected not to overlap. Returns + /// [OccasionConfigModel.disabled] when nothing matches. + static OccasionConfigModel resolveActive(List configs, [DateTime? now]) { + for (final config in configs) { + if (config.enabled && config.isWithinActiveWindow(now)) return config; + } + return OccasionConfigModel.disabled(); + } + + static DateTime? _parseDate(dynamic value) { + if (value == null) return null; + if (value is String && value.isNotEmpty) { + return DateTime.tryParse(value); + } + return null; + } + + /// Whether "now" falls inside the configured activation window. + /// Both [startDate] and [endDate] are required — if either is missing, + /// this occasion is never considered active (see field doc above). + bool isWithinActiveWindow([DateTime? now]) { + if (startDate == null || endDate == null) return false; + final current = now ?? DateTime.now(); + if (current.isBefore(startDate!)) return false; + if (current.isAfter(endDate!)) return false; + return true; + } + + /// Parsed [Color] from [primaryColorHex], or null if not configured. + Color? get primaryColor => _hexToColor(primaryColorHex); + + /// Lighter/translucent variant of [primaryColor] — 11% opacity of the same + /// hue (e.g. for chip/badge backgrounds). Derived automatically from + /// [primaryColor]; not an independent remote-config field. Null unless + /// [primaryColor] is configured. + Color? get secondaryColor => primaryColor?.withValues(alpha: 0.11); + + static Color? _hexToColor(String? hex) { + if (hex == null || hex.isEmpty) return null; + var value = hex.trim().replaceFirst('#', ''); + if (value.length == 6) value = 'FF$value'; + final parsed = int.tryParse(value, radix: 16); + return parsed != null ? Color(parsed) : null; + } + + Map toJson() => { + 'key': key, + 'enabled': enabled, + 'start_date': startDate?.toIso8601String(), + 'end_date': endDate?.toIso8601String(), + 'primary_color': primaryColorHex, + 'background_opacity_image': backgroundOpacityImage, + 'background_image_opacity': backgroundImageOpacity, + 'header_image': headerImage, + }; +} diff --git a/lib/features/occasion_theme/occasion_theme_view_model.dart b/lib/features/occasion_theme/occasion_theme_view_model.dart new file mode 100644 index 00000000..73f94ec7 --- /dev/null +++ b/lib/features/occasion_theme/occasion_theme_view_model.dart @@ -0,0 +1,125 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/painting.dart' show Color; +import 'package:hmg_patient_app_new/features/occasion_theme/occasion_config_model.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; +import 'package:hmg_patient_app_new/services/remote_config_service.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +/// Drives the fully remote-config-based "occasion" theming (National Day, +/// Founding Day, Eid, Ramadan, ...). Nothing about a specific occasion is +/// hardcoded here — every color/asset/date comes from [OccasionConfigModel], +/// which is parsed from Firebase Remote Config. +/// +/// Mirrors the existing dark-mode pattern in ProfileSettingsViewModel: +/// a static flag on [AppColors] is kept in sync so every widget using +/// AppColors.primaryRedColor (even ones with no BuildContext/Provider +/// access) reflects the active occasion instantly. +class OccasionThemeViewModel extends ChangeNotifier { + static const String _cachedConfigKey = 'cached_occasion_config_json'; + + final RemoteConfigService _remoteConfigService; + final CacheService _cacheService; + final LoggerService _loggerService; + + OccasionThemeViewModel({ + required RemoteConfigService remoteConfigService, + required CacheService cacheService, + required LoggerService loggerService, + }) : _remoteConfigService = remoteConfigService, + _cacheService = cacheService, + _loggerService = loggerService; + + OccasionConfigModel _config = OccasionConfigModel.disabled(); + OccasionConfigModel get config => _config; + + /// Testing-only override for "now" when resolving which scheduled + /// occasion is active — set this instead of changing the device clock to + /// preview each occasion in the list. Only ever takes effect in debug + /// builds (see [_now]); always ignored in profile/release, so it can + /// never leak into production even if accidentally left set. + static DateTime? debugNowOverride; + + DateTime? get _now => kDebugMode ? debugNowOverride : null; + + /// True only when enabled AND (if a window is configured) within it. + bool get isOccasionActive => _config.enabled && _config.isWithinActiveWindow(_now); + + String? get backgroundOpacityImage => isOccasionActive ? _config.backgroundOpacityImage : null; + + double get backgroundImageOpacity => _config.backgroundImageOpacity; + + /// The occasion's main header/banner image (e.g. National Day greeting + /// banner), rendered at full opacity. Null unless an occasion is active. + String? get headerImage => isOccasionActive ? _config.headerImage : null; + + /// Lighter/translucent variant of the active occasion's primary color + /// (11% opacity) — e.g. for chip/badge backgrounds. Null unless an + /// occasion is active. + Color? get secondaryColor => isOccasionActive ? _config.secondaryColor : null; + + /// Call once at app startup (before the first frame) to restore the last + /// known-good config instantly (offline-safe), then kick off a remote + /// fetch in the background. + Future initialize() async { + _restoreCachedConfig(); + _applyToAppColors(); + + try { + await _remoteConfigService.initialize(); + await refresh(); + } catch (ex) { + _loggerService.logError('OccasionThemeViewModel.initialize error: $ex'); + } + } + + /// Fetches the latest occasion schedule from Firebase Remote Config and + /// applies whichever entry is currently active. Safe to call again later + /// (e.g. on app resume) to pick up changes without requiring an app update. + Future refresh() async { + try { + await _remoteConfigService.fetchAndActivate(); + final rawJson = _remoteConfigService.occasionConfigRawJson; + final configs = OccasionConfigModel.listFromJson(json.decode(rawJson) as List); + _config = OccasionConfigModel.resolveActive(configs, _now); + _loggerService.logInfo('OccasionThemeViewModel: chosen theme = "${_config.key}" (enabled=${_config.enabled}, active=$isOccasionActive)'); + _loggerService.logInfo('OccasionThemeViewModel: chosen theme object = ${_config.toJson()}'); + // Cache the full schedule (not just the resolved entry) so offline + // restore at next startup re-resolves against that day's date instead + // of replaying whichever occasion happened to be active last time. + await _cacheService.saveString(key: _cachedConfigKey, value: rawJson); + _applyToAppColors(); + notifyListeners(); + } catch (ex) { + _loggerService.logError('OccasionThemeViewModel.refresh error: $ex'); + // Keep whatever config was already applied (cached/disabled) — never + // let a remote-config failure change the app's appearance unexpectedly. + } + } + + void _restoreCachedConfig() { + try { + final cachedJson = _cacheService.getString(key: _cachedConfigKey); + if (cachedJson == null || cachedJson.isEmpty) { + _config = OccasionConfigModel.disabled(); + return; + } + final configs = OccasionConfigModel.listFromJson(json.decode(cachedJson) as List); + _config = OccasionConfigModel.resolveActive(configs, _now); + } catch (ex) { + _loggerService.logError('OccasionThemeViewModel._restoreCachedConfig error: $ex'); + _config = OccasionConfigModel.disabled(); + } + } + + /// Keeps AppColors in sync so non-Provider call sites (AppColors.primaryRedColor) + /// pick up the occasion color exactly like the existing dark-mode flag does. + void _applyToAppColors() { + AppColors.isOccasionThemeActive = isOccasionActive; + AppColors.occasionPrimaryColor = _config.primaryColor; + AppColors.occasionSecondaryColor = _config.secondaryColor; + } +} + diff --git a/lib/features/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart index c4f96857..7b6b8b7d 100644 --- a/lib/features/payfort/payfort_view_model.dart +++ b/lib/features/payfort/payfort_view_model.dart @@ -578,4 +578,27 @@ class PayfortViewModel extends ChangeNotifier { ); } + Future addApplePayPayfortResponse(dynamic paymentMap) async { + PayfortResponseInsertModel payfortResponseInsertModel = PayfortResponseInsertModel(); + + payfortResponseInsertModel.fortId = paymentMap['fort_id'] ?? ""; + payfortResponseInsertModel.commandType = paymentMap['command'] ?? ""; + payfortResponseInsertModel.amount = num.tryParse(paymentMap['amount'] ?? "0") ?? 0; + payfortResponseInsertModel.sadadOLP = ""; + payfortResponseInsertModel.paymentOption = paymentMap['payment_option'] ?? ""; + payfortResponseInsertModel.customerIP = paymentMap['customer_ip'] ?? ""; + payfortResponseInsertModel.eCI = paymentMap['eci'] ?? ""; + payfortResponseInsertModel.responseMessage = paymentMap['response_message'] ?? ""; + payfortResponseInsertModel.cardNumber = paymentMap['card_number'] ?? ""; + payfortResponseInsertModel.cardBin = paymentMap['card_bin'] ?? ""; + payfortResponseInsertModel.installmentsNumber = int.tryParse(paymentMap['installments_number'] ?? "0") ?? 0; + payfortResponseInsertModel.status = paymentMap['status'] ?? ""; + payfortResponseInsertModel.merchantRef = paymentMap['merchant_reference'] ?? ""; + payfortResponseInsertModel.isRefund = false; + payfortResponseInsertModel.remmeberMe = true; + payfortResponseInsertModel.reconciliationReference = ""; + + payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel); + } + } diff --git a/lib/features/smartwatch_health_data/health_provider.dart b/lib/features/smartwatch_health_data/health_provider.dart index c296f717..1397703f 100644 --- a/lib/features/smartwatch_health_data/health_provider.dart +++ b/lib/features/smartwatch_health_data/health_provider.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; import 'package:health/health.dart'; import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart'; @@ -119,15 +120,22 @@ class HealthProvider with ChangeNotifier { void initDevice() async { LoaderBottomSheet.showLoader(); notifyListeners(); + error = null; final result = await _healthService.initDevice(); isLoading = false; - LoaderBottomSheet.hideLoader(); + // LoaderBottomSheet.hideLoader(); if (result.isError) { - error = 'Error initializing device: ${result.asError}'; + LoaderBottomSheet.hideLoader(); + final rawError = result.asError?.error; + if (rawError is PlatformException) { + error = 'Error initializing device: ${rawError.message ?? rawError.toString()}'; + } else { + error = 'Error initializing device: ${rawError?.toString() ?? 'Unknown error'}'; + } } else { - LoaderBottomSheet.showLoader(); + // LoaderBottomSheet.showLoader(); await getVitals(); - // LoaderBottomSheet.hideLoader(); + LoaderBottomSheet.hideLoader(); // await Future.delayed(Duration(seconds: 5)); getIt.get().pushPage(page: SmartWatchesHealthDataScreen()); } @@ -286,4 +294,9 @@ class HealthProvider with ChangeNotifier { } return formattedString; } + + void clearError(){ + error = null; + notifyListeners(); + } } diff --git a/lib/features/smartwatch_health_data/model/vitals_data_model.dart b/lib/features/smartwatch_health_data/model/vitals_data_model.dart index 83cd4ea6..819e3773 100644 --- a/lib/features/smartwatch_health_data/model/vitals_data_model.dart +++ b/lib/features/smartwatch_health_data/model/vitals_data_model.dart @@ -22,6 +22,7 @@ class Vitals { if (timestamp.isEmpty) { timestamp = DateTime.now().toIso8601String(); } + timestamp = DateTime.parse(timestamp).toLocal().toIso8601String(); return Vitals( value: map['value'] ?? "", timestamp: timestamp, diff --git a/lib/features/smartwatch_health_data/watch_connectors/IllegalWatchTypeException.dart b/lib/features/smartwatch_health_data/watch_connectors/IllegalWatchTypeException.dart new file mode 100644 index 00000000..c3bfe4fc --- /dev/null +++ b/lib/features/smartwatch_health_data/watch_connectors/IllegalWatchTypeException.dart @@ -0,0 +1 @@ +class IllegalWatchTypeException implements Exception {} \ No newline at end of file diff --git a/lib/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart b/lib/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart index 8abb2136..2a4711b0 100644 --- a/lib/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart +++ b/lib/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart @@ -6,6 +6,8 @@ import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connec import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/samsung_health.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart'; +import 'IllegalWatchTypeException.dart'; + class CreateWatchHelper { static WatchHelper getWatchName(SmartWatchTypes watchType) { /// if running device is ios @@ -16,7 +18,7 @@ class CreateWatchHelper { case SmartWatchTypes.huawei: return HuaweiHealthDataConnector(); default: - return SamsungHealth(); + throw IllegalWatchTypeException(); } } } \ No newline at end of file diff --git a/lib/features/smartwatch_health_data/watch_connectors/huawei_watch_connecter.dart b/lib/features/smartwatch_health_data/watch_connectors/huawei_watch_connecter.dart index a8300e2d..689bb517 100644 --- a/lib/features/smartwatch_health_data/watch_connectors/huawei_watch_connecter.dart +++ b/lib/features/smartwatch_health_data/watch_connectors/huawei_watch_connecter.dart @@ -1,86 +1,403 @@ import 'dart:async'; +import 'dart:convert'; import 'package:async/src/result/result.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/model/vitals_data_model.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart'; +import 'package:http/http.dart'; import 'package:huawei_health/huawei_health.dart'; + class HuaweiHealthDataConnector extends WatchHelper{ final MethodChannel _channel = MethodChannel('huawei_watch'); - @override - Future> initDevice() async{ - try{ - await _channel.invokeMethod('init'); - }catch(e){ + DataController? _dataController; + final Map> mappedData = >{}; + DateTime get _defaultEndDate => DateTime.now(); + DateTime get _defaultStartDate => _defaultEndDate.subtract(const Duration(days: 365)); + + @override + Future> initDevice() async { + try { + await _channel.invokeMethod('init'); + } catch (e) { + debugPrint('Huawei channel init failed: $e'); } - // List of scopes to ask for authorization. - // Note: These scopes should also be authorized on the Huawei Developer Console. - List scopes = [ - Scope.HEALTHKIT_STEP_READ, Scope.HEALTHKIT_OXYGEN_SATURATION_READ, // View and store height and weight data in Health Service Kit. - Scope.HEALTHKIT_HEARTRATE_READ, Scope.HEALTHKIT_SLEEP_READ, - Scope.HEALTHKIT_BODYTEMPERATURE_READ, Scope.HEALTHKIT_CALORIES_READ + + final List scopes = [ + Scope.HEALTHKIT_ACTIVITY_READ, + Scope.HEALTHKIT_ACTIVITY_WRITE, + Scope.HEALTHKIT_BLOODGLUCOSE_READ, + Scope.HEALTHKIT_BLOODGLUCOSE_WRITE, + Scope.HEALTHKIT_CALORIES_READ, + Scope.HEALTHKIT_CALORIES_WRITE, + Scope.HEALTHKIT_DISTANCE_READ, + Scope.HEALTHKIT_DISTANCE_WRITE, + Scope.HEALTHKIT_HEARTRATE_READ, + Scope.HEALTHKIT_HEARTRATE_WRITE, + Scope.HEALTHKIT_HEIGHTWEIGHT_READ, + Scope.HEALTHKIT_HEIGHTWEIGHT_WRITE, + Scope.HEALTHKIT_LOCATION_READ, + Scope.HEALTHKIT_LOCATION_WRITE, + Scope.HEALTHKIT_PULMONARY_READ, + Scope.HEALTHKIT_PULMONARY_WRITE, + Scope.HEALTHKIT_SLEEP_READ, + Scope.HEALTHKIT_SLEEP_WRITE, + Scope.HEALTHKIT_SPEED_READ, + Scope.HEALTHKIT_SPEED_WRITE, + Scope.HEALTHKIT_STEP_READ, + Scope.HEALTHKIT_STEP_WRITE, + Scope.HEALTHKIT_STRENGTH_READ, + Scope.HEALTHKIT_STRENGTH_WRITE, + Scope.HEALTHKIT_BODYFAT_READ, + Scope.HEALTHKIT_BODYFAT_WRITE, + Scope.HEALTHKIT_NUTRITION_READ, + Scope.HEALTHKIT_NUTRITION_WRITE, + Scope.HEALTHKIT_BLOODPRESSURE_READ, + Scope.HEALTHKIT_BLOODPRESSURE_WRITE, + Scope.HEALTHKIT_BODYTEMPERATURE_READ, + Scope.HEALTHKIT_BODYTEMPERATURE_WRITE, + Scope.HEALTHKIT_OXYGENSTATURATION_READ, + Scope.HEALTHKIT_OXYGENSTATURATION_WRITE, + Scope.HEALTHKIT_REPRODUCTIVE_READ, + Scope.HEALTHKIT_REPRODUCTIVE_WRITE, + Scope.HEALTHKIT_ACTIVITY_RECORD_READ, + Scope.HEALTHKIT_ACTIVITY_RECORD_WRITE, + Scope.HEALTHKIT_HEARTRATE_REALTIME, + Scope.HEALTHKIT_STEP_REALTIME, + Scope.HEALTHKIT_HEARTHEALTH_WRITE, + Scope.HEALTHKIT_HEARTHEALTH_READ, + Scope.HEALTHKIT_STRESS_WRITE, + Scope.HEALTHKIT_STRESS_READ, + Scope.HEALTHKIT_OXYGEN_SATURATION_WRITE, + Scope.HEALTHKIT_OXYGEN_SATURATION_READ, + Scope.HEALTHKIT_HISTORYDATA_OPEN_WEEK, + Scope.HEALTHKIT_HISTORYDATA_OPEN_MONTH, + Scope.HEALTHKIT_HISTORYDATA_OPEN_YEAR, ]; + try { - bool? result = await SettingController.getHealthAppAuthorization(); - debugPrint( - 'Granted Scopes for result == is $result}', - ); + await HealthAuth.signIn(scopes); + _dataController = await DataController.init(); + if (_dataController == null) { + return Result.error(false); + } return Result.value(true); + } on PlatformException catch (e) { + debugPrint('Error on Huawei authorization: $e'); + return Result.error(false); } catch (e) { - debugPrint('Error on authorization, Error:${e.toString()}'); + debugPrint('Unexpected Huawei init error: $e'); return Result.error(false); } } @override Future getActivity() async { - DataType dataTypeResult = await SettingController.readDataType( - DataType.DT_CONTINUOUS_STEPS_DELTA.name - ); - - + try { + final ReadReply? data = await readDataType( + DataType.DT_CONTINUOUS_CALORIES_BURNT, + _defaultStartDate, + _defaultEndDate, + ); + addDataToMap('activity', data, preferredKeys: const ['calories', 'calories_total']); + } catch (e) { + debugPrint('Error getting activity data: $e'); + mappedData['activity'] = []; + } } @override - Future getBloodOxygen() { - throw UnimplementedError(); + Future getBloodOxygen() async { + try { + final ReadReply? data = await readDataType( + HealthDataTypes.DT_INSTANTANEOUS_SPO2, + _defaultStartDate, + _defaultEndDate, + ); + addDataToMap( + 'bloodOxygen', + data, + preferredKeys: const ['saturation', 'saturation_last', 'saturation_avg'], + transform: _normalizeSpo2, + ); + } catch (e) { + debugPrint('Error getting blood oxygen data: $e'); + mappedData['bloodOxygen'] = []; + } + } + @override + Future getBodyTemperature() async { + try { + final ReadReply? data = await readDataType( + HealthDataTypes.DT_INSTANTANEOUS_BODY_TEMPERATURE, + _defaultStartDate, + _defaultEndDate, + ); + addDataToMap('bodyTemperature', data, preferredKeys: const ['temperature']); + } catch (e) { + debugPrint('Error getting body temperature data: $e'); + mappedData['bodyTemperature'] = []; + } } @override - Future getBodyTemperature() { + FutureOr getHeartRate() async { + try { + final ReadReply? data = await readDataType( + DataType.DT_INSTANTANEOUS_HEART_RATE, + _defaultStartDate, + _defaultEndDate, + ); + addDataToMap('heartRate', data, preferredKeys: const ['bpm']); + } catch (e) { + debugPrint('Error getting heart rate data: $e'); + mappedData['heartRate'] = []; + } + } - throw UnimplementedError(); + @override + FutureOr getSleep() async { + try { + final ReadReply? data = await readDataType( + DataType.DT_CONTINUOUS_SLEEP, + _defaultStartDate, + _defaultEndDate, + ); + addSleepDataToMap(data); + } catch (e) { + debugPrint('Error getting sleep data: $e'); + mappedData['sleep'] = []; + } } @override - FutureOr getHeartRate() { - throw UnimplementedError(); + FutureOr getSteps() async { + try { + final ReadReply? data = await readDataType( + DataType.DT_CONTINUOUS_STEPS_DELTA, + _defaultStartDate, + _defaultEndDate, + ); + addDataToMap('steps', data, preferredKeys: const ['steps_delta', 'steps', 'step_rate']); + } catch (e) { + debugPrint('Error getting steps data: $e'); + mappedData['steps'] = []; + } } @override - FutureOr getSleep() { - throw UnimplementedError(); + Future> retrieveData() async { + return Result.value(getMappedData()); } @override - FutureOr getSteps() { - throw UnimplementedError(); + FutureOr getDistance() async { + try { + final ReadReply? data = await readDataType( + DataType.DT_CONTINUOUS_DISTANCE_DELTA, + _defaultStartDate, + _defaultEndDate, + ); + addDataToMap('distance', data, preferredKeys: const ['distance_delta', 'distance']); + } catch (e) { + debugPrint('Error getting distance data: $e'); + mappedData['distance'] = []; + } } + Future readDataType( + DataType dataType, + DateTime startTime, + DateTime endTime, + ) async { + final DataCollector dataCollector = DataCollector( + dataType: dataType, + dataGenerateType: DataGenerateType.DATA_TYPE_RAW, + ); - @override - Future retrieveData() { - throw UnimplementedError(); + final ReadOptions readOptions = ReadOptions( + dataCollectors: [dataCollector], + startTime: startTime, + endTime: endTime, + allowRemoteInquiry: true, + )..groupByTime(1, timeUnit: TimeUnit.HOURS); + + try { + return await _dataController?.read(readOptions); + } on PlatformException catch (e) { + debugPrint('Huawei read error for ${dataType.name}: $e'); + return null; + } } - @override - FutureOr getDistance() { - // TODO: implement getDistance - throw UnimplementedError(); + void addDataToMap( + String key, + ReadReply? data, { + List preferredKeys = const [], + double Function(double value)? transform, + }) { + mappedData[key] = []; + if (data == null) { + return; + } + + if (data.groups.isNotEmpty) { + for (final Group group in data.groups) { + for (final SampleSet sampleSet in group.sampleSets ?? []) { + for (final SamplePoint samplePoint in sampleSet.samplePoints) { + _addNumericSampleToMappedData( + key: key, + samplePoint: samplePoint, + preferredKeys: preferredKeys, + groupStartTime: group.startTime, + transform: transform, + ); + } + } + } + return; + } + + for (final SampleSet sampleSet in data.sampleSets) { + for (final SamplePoint samplePoint in sampleSet.samplePoints) { + _addNumericSampleToMappedData( + key: key, + samplePoint: samplePoint, + preferredKeys: preferredKeys, + transform: transform, + ); + } + } + } + + void addSleepDataToMap(ReadReply? data) { + mappedData['sleep'] = []; + if (data == null) { + return; + } + + void appendSleepPoint(SamplePoint samplePoint, {DateTime? groupStart, DateTime? groupEnd}) { + final DateTime? start = samplePoint.startTime ?? groupStart; + final DateTime? end = samplePoint.endTime ?? groupEnd; + + double? value; + if (start != null && end != null && end.isAfter(start)) { + value = end.difference(start).inMinutes / 60; + } + + value ??= _extractNumericValue( + samplePoint, + const ['sleep_state', 'all_sleep_time', 'deep_sleep_time', 'light_sleep_time'], + ); + + if (value == null) { + return; + } + + final String timestamp = + (start ?? samplePoint.samplingTime ?? DateTime.now()).toIso8601String(); + + mappedData['sleep']?.add( + Vitals( + value: value.toStringAsFixed(2), + timestamp: timestamp, + ), + ); + } + + if (data.groups.isNotEmpty) { + for (final Group group in data.groups) { + for (final SampleSet sampleSet in group.sampleSets ?? []) { + for (final SamplePoint samplePoint in sampleSet.samplePoints) { + appendSleepPoint( + samplePoint, + groupStart: group.startTime, + groupEnd: group.endTime, + ); + } + } + } + return; + } + + for (final SampleSet sampleSet in data.sampleSets) { + for (final SamplePoint samplePoint in sampleSet.samplePoints) { + appendSleepPoint(samplePoint); + } + } + } + + void _addNumericSampleToMappedData({ + required String key, + required SamplePoint samplePoint, + required List preferredKeys, + DateTime? groupStartTime, + double Function(double value)? transform, + }) { + final double? numericValue = _extractNumericValue(samplePoint, preferredKeys); + if (numericValue == null) { + return; + } + + final double transformedValue = transform?.call(numericValue) ?? numericValue; + final String timestamp = + (samplePoint.samplingTime ?? samplePoint.startTime ?? groupStartTime ?? DateTime.now()) + .toIso8601String(); + + mappedData[key]?.add( + Vitals( + value: transformedValue.toStringAsFixed(2), + timestamp: timestamp, + ), + ); + } + + double? _extractNumericValue(SamplePoint samplePoint, List preferredKeys) { + final Map values = { + ...samplePoint.pairs, + ...?samplePoint.fieldValues, + }; + + for (final String key in preferredKeys) { + final dynamic value = values[key]; + if (value is num) { + return value.toDouble(); + } + } + + for (final dynamic value in values.values) { + if (value is num) { + return value.toDouble(); + } + } + + return null; + } + + double _normalizeSpo2(double value) { + if (value <= 1) { + return value * 100; + } + return value; + } + + String getMappedData() { + return jsonEncode({ + 'heartRate': (mappedData['heartRate'] ?? []).map((Vitals v) => v.toJson()).toList(), + 'sleep': (mappedData['sleep'] ?? []).map((Vitals v) => v.toJson()).toList(), + 'steps': (mappedData['steps'] ?? []).map((Vitals v) => v.toJson()).toList(), + 'activity': (mappedData['activity'] ?? []).map((Vitals v) => v.toJson()).toList(), + 'bloodOxygen': + (mappedData['bloodOxygen'] ?? []).map((Vitals v) => v.toJson()).toList(), + 'bodyTemperature': + (mappedData['bodyTemperature'] ?? []).map((Vitals v) => v.toJson()).toList(), + 'distance': (mappedData['distance'] ?? []).map((Vitals v) => v.toJson()).toList(), + }); } +} -} \ No newline at end of file diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index 9658edbe..ea64dfaa 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -57,7 +57,8 @@ class DefaultFirebaseOptions { databaseURL: 'https://api-project-815750722565.firebaseio.com', storageBucket: 'api-project-815750722565.firebasestorage.app', androidClientId: '815750722565-m14h8mkosm7cnq6uh6rhqr54dn02d705.apps.googleusercontent.com', - iosBundleId: 'com.cloudsolutions.HMGPatientApp', + // iosBundleId: 'com.cloudsolutions.HMGPatientApp', + iosBundleId: 'com.HMG.HMG-Smartphone', ); } \ No newline at end of file diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 7dbcfd75..30e3bc0a 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -2032,5 +2032,12 @@ abstract class LocaleKeys { static const liveCareTermsHeading16 = 'liveCareTermsHeading16'; static const liveCareTermsConditions47 = 'liveCareTermsConditions47'; static const liveCareTermsConditions48 = 'liveCareTermsConditions48'; + static const importantNotice = 'importantNotice'; + static const appDecommissionedText = 'appDecommissionedText'; + static const appDecommissionedSubText = 'appDecommissionedSubText'; + static const downloadApp = 'downloadApp'; + static const unread = 'unread'; + static const read = 'read'; + static const pendingMedicalReportRequest = 'pendingMedicalReportRequest'; } diff --git a/lib/main.dart b/lib/main.dart index 4e60c2a0..47416b49 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; @@ -35,7 +36,9 @@ import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_ import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; +import 'package:hmg_patient_app_new/features/myfatoorah/myfatoorah_view_model.dart'; import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; +import 'package:hmg_patient_app_new/features/occasion_theme/occasion_theme_view_model.dart'; import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_discounts_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; @@ -70,7 +73,6 @@ import 'core/utils/size_utils.dart'; import 'features/monthly_reports/terms_conditions_view_model.dart'; import 'firebase_options.dart'; - @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); @@ -129,6 +131,7 @@ Future callInitializations() async { } catch (e) { // If it doesn't exist, initialize it. await Firebase.initializeApp( + name: "api-project-815750722565", options: DefaultFirebaseOptions.currentPlatform, ); } @@ -140,7 +143,11 @@ Future callInitializations() async { // Initialize Security Service early to catch threats before app logic runs if (kReleaseMode) { - await getIt.get().initialize(); + try { + await getIt.get().initialize(); + } catch (e) { + getIt.get().logError('SecurityService.initialize failed, continuing app startup: $e'); + } } // Set up critical threat callback to navigate to unsafe device page @@ -166,6 +173,16 @@ Future callInitializations() async { // Restore persisted accessibility settings before the first frame. getIt.get().loadAccessibilitySettings(); + + // Restore last-known occasion theme instantly (offline-safe, synchronous) + // so the very first frame is already correct, then let the Remote Config + // fetch happen in the background without blocking app startup. Fully + // disabled by default — the app looks exactly as it does today until + // Remote Config enables an occasion (e.g. National Day) on the server side. + final occasionThemeViewModel = getIt.get(); + OccasionThemeViewModel.debugNowOverride = DateTime.now(); + // OccasionThemeViewModel.debugNowOverride = DateTime.parse('2027-03-09T00:00:00Z'); + unawaited(occasionThemeViewModel.initialize()); } void main() async { @@ -303,9 +320,15 @@ void main() async { ChangeNotifierProvider( create: (_) => getIt.get(), ), + ChangeNotifierProvider( + create: (_) => getIt.get(), + ), ChangeNotifierProvider( create: (_) => getIt.get(), - ) + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), + ), ], child: MyApp()), ), // Wrap your app ); @@ -373,11 +396,11 @@ class MyApp extends StatelessWidget { return Sizer( builder: (context, orientation, deviceType) { // SizeConfig().init(constraints, orientation); - return Consumer( - builder: (context, profileVm, _) { + return Consumer2( + builder: (context, profileVm, occasionThemeVm, _) { final isArabic = EasyLocalization.of(context)?.locale.languageCode == "ar"; return MaterialApp( - key: ValueKey('app_theme_${profileVm.isDarkMode}'), + key: ValueKey('app_theme_${profileVm.isDarkMode}_${occasionThemeVm.isOccasionActive}_${occasionThemeVm.config.key}'), // title: 'Dr. AlHabib', title: 'Dr. AlHabib Beta', builder: (context, mchild) { diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index dc147981..874c97c6 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -338,6 +338,7 @@ class _AppointmentDetailsPageState extends State { myAppointmentsViewModel.updateListWRTTab(0); await myAppointmentsViewModel.cancelAppointment( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + isReschedule: false, onSuccess: (apiResponse) async { LoaderBottomSheet.hideLoader(); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); @@ -989,7 +990,7 @@ class _AppointmentDetailsPageState extends State { openDoctorScheduleCalendar(); }, backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, + borderColor: Colors.transparent, textColor: Colors.white, fontSize: 16.f, fontWeight: FontWeight.w600, @@ -1097,7 +1098,7 @@ class _AppointmentDetailsPageState extends State { openDoctorScheduleCalendar(); }, backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, + borderColor: Colors.transparent, textColor: Colors.white, fontSize: 16.f, fontWeight: FontWeight.w600, diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index ffa9b723..992f512e 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -748,7 +748,8 @@ class _AppointmentPaymentPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (myAppointmentsVM.patientAppointmentShareResponseModel!.isCash ?? true) + // (myAppointmentsVM.patientAppointmentShareResponseModel!.isCash ?? true) + (true) ? Container( height: 50.h, decoration: ShapeDecoration( @@ -857,7 +858,7 @@ class _AppointmentPaymentPageState extends State { print("onBrowserLoadStart"); print(url); - if (selectedPaymentMethod == "tamara") { + if (selectedPaymentMethod.toLowerCase() == "tamara") { if (Platform.isAndroid) { Uri uri = new Uri.dataFromString(url); tamaraPaymentStatus = uri.queryParameters['status']!; @@ -896,7 +897,7 @@ class _AppointmentPaymentPageState extends State { void checkPaymentStatus() async { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); - if (selectedPaymentMethod == "TAMARA") { + if (selectedPaymentMethod.toLowerCase() == "tamara") { await payfortViewModel.checkTamaraPaymentStatus( transactionID: transID, onSuccess: (apiResponse) async { @@ -1282,6 +1283,7 @@ class _AppointmentPaymentPageState extends State { currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", onFailed: (failureResult) async { log("failureResult: ${failureResult.message.toString()}"); + payfortViewModel.addApplePayPayfortResponse(failureResult.response); LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, @@ -1292,10 +1294,94 @@ class _AppointmentPaymentPageState extends State { ); }, onSucceeded: (successResult) async { + payfortViewModel.addApplePayPayfortResponse(successResult); LoaderBottomSheet.hideLoader(); log("successResult: ${successResult.responseMessage.toString()}"); selectedPaymentMethod = successResult.paymentOption ?? "VISA"; checkPaymentStatus(); + + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); + // await myAppointmentsViewModel.createAdvancePayment( + // paymentMethodName: selectedPaymentMethod, + // projectID: widget.patientAppointmentHistoryResponseModel.projectID, + // clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + // appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + // payedAmount: num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + // paymentReference: successResult.fortId!, + // patientID: appState.getAuthenticatedUser()!.patientId.toString(), + // patientType: appState.getAuthenticatedUser()!.patientType!, + // onSuccess: (value) async { + // print(value); + // await myAppointmentsViewModel.addAdvanceNumberRequest( + // advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID) + // ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() + // : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), + // paymentReference: successResult.fortId!, + // appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + // onSuccess: (value) async { + // if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) { + // //TODO: Implement LiveCare Check-In API Call + // await myAppointmentsViewModel.insertLiveCareVIDARequest( + // clientRequestID: transID, + // patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + // onSuccess: (apiResponse) { + // Future.delayed(Duration(milliseconds: 500), () { + // myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + // myAppointmentsViewModel.initAppointmentsViewModel(); + // myAppointmentsViewModel.getPatientAppointments(true, false); + // + // getIt.get().setIsWalletAmountToBeLoaded(true); + // getIt.get().initHabibWalletProvider(); + // getIt.get().getPatientBalanceAmount(); + // + // LoaderBottomSheet.hideLoader(); + // Navigator.pushAndRemoveUntil( + // context, + // CustomPageRoute( + // page: LandingNavigation(), + // ), + // (r) => false); + // }); + // }, + // onError: (error) {}); + // } else { + // await myAppointmentsViewModel.generateAppointmentQR( + // clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + // projectID: widget.patientAppointmentHistoryResponseModel.projectID, + // appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + // isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, + // onSuccess: (apiResponse) { + // Future.delayed(Duration(milliseconds: 500), () { + // myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + // myAppointmentsViewModel.initAppointmentsViewModel(); + // myAppointmentsViewModel.getPatientAppointments(true, false); + // + // getIt.get().setIsWalletAmountToBeLoaded(true); + // getIt.get().initHabibWalletProvider(); + // getIt.get().getPatientBalanceAmount(); + // + // LoaderBottomSheet.hideLoader(); + // Navigator.pushAndRemoveUntil( + // context, + // CustomPageRoute( + // page: LandingNavigation(), + // ), + // (r) => false); + // }); + // }); + // } + // }); + // }, + // onError: (err) { + // LoaderBottomSheet.hideLoader(); + // showCommonBottomSheetWithoutHeight( + // context, + // child: Utils.getErrorWidget(loadingText: err.toString()), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // }); }, // projectId: appo.projectID, // serviceTypeEnum: ServiceTypeEnum.appointmentPayment, diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index da20e183..e15edb47 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -67,8 +67,8 @@ class _MyAppointmentsPageState extends State { SizedBox(height: 16.h), CustomTabBar( initialIndex: myAppointmentsVM.selectedTabIndex, - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.secondaryLightRedColor, tabs: [ // CustomTabBarModel(null, LocaleKeys.allAppt.tr(context: context)), CustomTabBarModel(null, LocaleKeys.upcoming.tr(context: context)), @@ -269,9 +269,9 @@ class _MyAppointmentsPageState extends State { ), ); }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, fontSize: 14.f, isBold: true, borderRadius: 12.r, diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 1eeb842c..b9a2f26f 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -448,7 +448,17 @@ class _AppointmentCardState extends State { if (widget.isForFeedback) { widget.contactUsViewModel!.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); } else { - widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel); + if(!widget.medicalFileViewModel!.checkExistingMedicalReportStatus(widget.patientAppointmentHistoryResponseModel)) { + widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.pendingMedicalReportRequest.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } } Navigator.pop(context, false); } else { diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 4dd838ea..a3b13259 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -84,8 +84,7 @@ class AppointmentDoctorCard extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.h, height: 15.h, iconColor: AppColors.ratingColorYellow), SizedBox(height: 2.h), - "${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? 0.0}" - .toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true), + "${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? 0.0}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true), ], ), ).circle(100), @@ -102,11 +101,7 @@ class AppointmentDoctorCard extends StatelessWidget { children: [ Expanded( child: (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText16( - isBold: true, - isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""), - textOverflow: TextOverflow.ellipsis, - maxlines: 2 - ), + isBold: true, isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""), textOverflow: TextOverflow.ellipsis, maxlines: 2), ), SizedBox(width: 12.w), // (patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && @@ -142,18 +137,17 @@ class AppointmentDoctorCard extends StatelessWidget { child: AppCustomChipWidget( labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), icon: AppAssets.doctor_calendar_icon, - richText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${(patientAppointmentHistoryResponseModel.startTime != null && patientAppointmentHistoryResponseModel.startTime!.isNotEmpty) ? patientAppointmentHistoryResponseModel.startTime!.substring(0, 5) : DateUtil.formatDateToTimeLang(DateUtil.convertStringToDateSaudiTimezone(patientAppointmentHistoryResponseModel.appointmentDate!, patientAppointmentHistoryResponseModel.projectID), false)}" - .toText10(isBold: true), + richText: + "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${(patientAppointmentHistoryResponseModel.startTime != null && patientAppointmentHistoryResponseModel.startTime!.isNotEmpty) ? patientAppointmentHistoryResponseModel.startTime!.substring(0, 5) : DateUtil.formatDateToTimeLang(DateUtil.convertStringToDateSaudiTimezone(patientAppointmentHistoryResponseModel.appointmentDate!, patientAppointmentHistoryResponseModel.projectID), false)}" + .toText10(isBold: true), ), ), ), AppCustomChipWidget( labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), - icon: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppAssets.walkin_appointment_icon - : AppAssets.small_livecare_icon, + icon: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon, iconColor: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppColors.textColor : Colors.white, - labelText: (patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? LocaleKeys.livecare.tr(context: context) - : LocaleKeys.walkin.tr(context: context), + labelText: (patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context), backgroundColor: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppColors.greyColor : AppColors.successColor, textColor: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppColors.textColor : Colors.white, ), @@ -163,13 +157,12 @@ class AppointmentDoctorCard extends StatelessWidget { ), ), (patientAppointmentHistoryResponseModel.isLiveCareAppointment! || - patientAppointmentHistoryResponseModel.isExecludeDoctor! || + patientAppointmentHistoryResponseModel.isExecludeDoctor! || patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! == false || patientAppointmentHistoryResponseModel.isActiveDoctor! == false || - !Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID)) + !Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID)) ? SizedBox.shrink() - : Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown) - .onPress(() async { + : Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async { // DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); // selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID; // selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL; @@ -270,7 +263,7 @@ class AppointmentDoctorCard extends StatelessWidget { onAskDoctorTap(); }, backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, + borderColor: Colors.transparent, textColor: AppColors.primaryRedColor, fontSize: 14.f, isBold: true, @@ -310,7 +303,7 @@ class AppointmentDoctorCard extends StatelessWidget { onCancelTap(); }, backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, + borderColor: Colors.transparent, textColor: AppColors.whiteColor, fontSize: 14.f, isBold: true, @@ -329,7 +322,7 @@ class AppointmentDoctorCard extends StatelessWidget { onRescheduleTap(); }, backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, + borderColor: Colors.transparent, textColor: AppColors.primaryRedColor, fontSize: 14.f, isBold: true, diff --git a/lib/presentation/appointments/widgets/appointment_rating_widget.dart b/lib/presentation/appointments/widgets/appointment_rating_widget.dart index 9b13c6cd..ae70da1d 100644 --- a/lib/presentation/appointments/widgets/appointment_rating_widget.dart +++ b/lib/presentation/appointments/widgets/appointment_rating_widget.dart @@ -142,9 +142,9 @@ class _AppointmentRatingWidgetState extends State { Expanded( child: CustomButton( text: LocaleKeys.later.tr(context: context), - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, onPressed: () { Navigator.pop(context); }, diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index 133e38ef..00e872a3 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -94,17 +94,18 @@ class QuickLoginState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ Expanded( - child: CustomButton( - text: LocaleKeys.enableQuickLoginBtn.tr(context: context), - onPressed: () { - widget.onPressed(); - }, - backgroundColor: Color(0xffED1C2B), - borderColor: Color(0xffED1C2B), - textColor: Colors.white, - icon: AppAssets.apple_finder, - height: 56.h, - )), + child: CustomButton( + text: LocaleKeys.enableQuickLoginBtn.tr(context: context), + onPressed: () { + widget.onPressed(); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + icon: AppAssets.apple_finder, + height: 56.h, + ), + ), ], ), SizedBox( @@ -120,9 +121,9 @@ class QuickLoginState extends State { cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: false); Navigator.pop(context, "true"); }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Colors.red, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: Colors.transparent, + textColor: AppColors.primaryRedColor, height: 56.h, // icon: "assets/images/svg/apple-finder.svg", )), diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index bea93d36..4de4bb64 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -327,9 +327,9 @@ class _SavedLogin extends State with RouteAware { // MaterialPageRoute(builder: (BuildContext context) => LandingNavigation()) // ); }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: Colors.transparent, + textColor: AppColors.primaryRedColor, fontSize: 16.f, fontWeight: FontWeight.w600, borderRadius: 12.r, @@ -352,9 +352,9 @@ class _SavedLogin extends State with RouteAware { MaterialPageRoute(builder: (BuildContext context) => LoginScreen()), ); }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: Colors.transparent, + textColor: AppColors.primaryRedColor, fontSize: 15.f, fontWeight: FontWeight.w600, borderRadius: 12.r, diff --git a/lib/presentation/blood_donation/blood_donation_page.dart b/lib/presentation/blood_donation/blood_donation_page.dart index f3f0782d..b4bdb6df 100644 --- a/lib/presentation/blood_donation/blood_donation_page.dart +++ b/lib/presentation/blood_donation/blood_donation_page.dart @@ -232,7 +232,7 @@ class _BloodDonationPageState extends State { Container( padding: EdgeInsets.all(16.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.bgRedLightColor.withValues(alpha: 0.1), + color: AppColors.secondaryLightRedColorBase.withValues(alpha: 0.1), borderRadius: 12.r, hasShadow: false, ), diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index c2560ecd..57b3d541 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -106,8 +106,8 @@ class _BookAppointmentPageState extends State { children: [ SizedBox(height: 16.h), CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.secondaryLightRedColor, initialIndex: bookAppointmentsVM.selectedTabIndex, tabs: [ CustomTabBarModel(null, LocaleKeys.general.tr(context: context)), @@ -326,9 +326,9 @@ class _BookAppointmentPageState extends State { onPressed: () { Navigator.of(context).push(CustomPageRoute(page: SearchDoctorByName())); }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, fontSize: 14.f, isBold: true, padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index 5b6ad09d..ee4d859b 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -60,9 +60,7 @@ class DoctorProfilePage extends StatelessWidget { doctorID: viewModel.doctorsProfileResponseModel.doctorID ?? 0, isActive: viewModel.isFavouriteDoctor, onSuccess: (response) { - Utils.showToast(viewModel.isFavouriteDoctor - ? LocaleKeys.doctorAddedToFavourite.tr(context: context) - : LocaleKeys.doctorRemovedFromFavourite.tr(context: context)); + Utils.showToast(viewModel.isFavouriteDoctor ? LocaleKeys.doctorAddedToFavourite.tr(context: context) : LocaleKeys.doctorRemovedFromFavourite.tr(context: context)); // Successfully added/removed favorite - refresh the favorites list getIt.get().refreshFavouriteDoctors(); }, @@ -101,14 +99,11 @@ class DoctorProfilePage extends StatelessWidget { children: [ SizedBox( width: isFoldable ? 250.w : 220.w, - child: - ("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}") - .toString() - .toText24(isBold: true), + child: ("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}") + .toString() + .toText24(isBold: true), ), - (bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.isNotEmpty - ? bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.first - : "") + (bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.isNotEmpty ? bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.first : "") .toString() .toText18(isBold: true, color: AppColors.primaryRedColor), ], @@ -148,16 +143,12 @@ class DoctorProfilePage extends StatelessWidget { children: [ Column( children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.doctor_profile_rating_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), + Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_rating_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), SizedBox(height: 16.h), LocaleKeys.ratings.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor), - bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate.toString().toText16( - isBold: true, - color: AppColors.textColor, - isUnderLine: true, - decorationColor: AppColors.textColor, - fontFamily: "Poppins"), + bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate + .toString() + .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"), ], ).onPress(() { bookAppointmentsViewModel.getDoctorRatingDetails(); @@ -173,18 +164,12 @@ class DoctorProfilePage extends StatelessWidget { SizedBox(width: 36.w), Column( children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.doctor_profile_reviews_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), + Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_reviews_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), SizedBox(height: 16.h), LocaleKeys.reviews.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor), NumberFormat.decimalPattern("en_US") .format(bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate ?? 0) - .toText16( - isBold: true, - color: AppColors.textColor, - isUnderLine: true, - decorationColor: AppColors.textColor, - fontFamily: "Poppins"), + .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"), ], ).onPress(() { bookAppointmentsViewModel.getDoctorRatingDetails(); @@ -215,9 +200,7 @@ class DoctorProfilePage extends StatelessWidget { SizedBox(height: 16.h), LocaleKeys.information.tr(context: context).toText14(isBold: true, color: AppColors.textColor), SizedBox(height: 6.h), - (bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo ?? "") - .trim() - .toText12(isBold: true, color: AppColors.greyTextColor), + (bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo ?? "").trim().toText12(isBold: true, color: AppColors.greyTextColor), SizedBox(height: 24.h), ], ).paddingSymmetrical(24.h, 0.h), @@ -238,8 +221,7 @@ class DoctorProfilePage extends StatelessWidget { bookAppointmentsViewModel.selectedDoctor.specialityN = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty; bookAppointmentsViewModel.selectedDoctor.name = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName; bookAppointmentsViewModel.selectedDoctor.doctorImageURL = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorImageURL; - bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL = - bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL; + bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL = bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL; bookAppointmentsViewModel.selectedDoctor.clinicName = bookAppointmentsViewModel.doctorsProfileResponseModel.clinicDescription; bookAppointmentsViewModel.selectedDoctor.projectName = bookAppointmentsViewModel.doctorsProfileResponseModel.projectName; diff --git a/lib/presentation/book_appointment/laser/laser_appointment.dart b/lib/presentation/book_appointment/laser/laser_appointment.dart index c8773933..affd5423 100644 --- a/lib/presentation/book_appointment/laser/laser_appointment.dart +++ b/lib/presentation/book_appointment/laser/laser_appointment.dart @@ -80,8 +80,8 @@ class LaserAppointment extends StatelessWidget { child: Column( children: [ CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.secondaryLightRedColor, tabs: [ CustomTabBarModel(null, LocaleKeys.malE.tr()), CustomTabBarModel(null, LocaleKeys.femaleGender.tr()), 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 31defa56..b9df0744 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -11,8 +11,10 @@ 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/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/myFatoorah_config.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; +import 'package:hmg_patient_app_new/features/myfatoorah/myfatoorah_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -33,6 +35,8 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/in_app_browser/InAppBrowser.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:myfatoorah_flutter/MFEnums.dart'; +import 'package:myfatoorah_flutter/myfatoorah_flutter.dart'; import 'package:provider/provider.dart'; import 'package:smooth_corner/smooth_corner.dart'; @@ -49,6 +53,9 @@ class _ImmediateLiveCarePaymentPageState extends State= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && @@ -80,11 +92,15 @@ class _ImmediateLiveCarePaymentPageState extends State(); myAppointmentsViewModel = Provider.of(context, listen: false); immediateLiveCareViewModel = Provider.of(context, listen: false); payfortViewModel = Provider.of(context, listen: false); paytabsViewModel = Provider.of(context, listen: false); + myFatoorahViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, myAppointmentsVM, child) { @@ -131,7 +147,62 @@ class _ImmediateLiveCarePaymentPageState extends State false); + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePendingRequestPage(), + ), + ); + } else { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, + onError: (errMessage) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: errMessage.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else if (appState.isPaytabsEnabled) { transID = Utils.getAppointmentTransID( immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!, ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12, @@ -298,7 +369,62 @@ class _ImmediateLiveCarePaymentPageState extends State false); + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePendingRequestPage(), + ), + ); + } else { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, + onError: (errMessage) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: errMessage.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else if (appState.isPaytabsEnabled) { transID = Utils.getAppointmentTransID( immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!, ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12, @@ -610,16 +736,31 @@ class _ImmediateLiveCarePaymentPageState extends State SizedBox( height: 16.h, ), diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index 52cea368..ee40ae0c 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -7,7 +7,6 @@ import 'package:clarity_flutter/clarity_flutter.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_amazonpaymentservices/flutter_amazonpaymentservices.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -668,6 +667,7 @@ class _WalletPaymentConfirmPageState extends State { currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", onFailed: (failureResult) async { log("failureResult: ${failureResult.message.toString()}"); + payfortViewModel.addApplePayPayfortResponse(failureResult.response); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), @@ -678,6 +678,7 @@ class _WalletPaymentConfirmPageState extends State { }, onSucceeded: (successResult) async { log("successResult: ${successResult.responseMessage.toString()}"); + payfortViewModel.addApplePayPayfortResponse(successResult); selectedPaymentMethod = successResult.paymentOption ?? "VISA"; checkPaymentStatus(); }, @@ -830,28 +831,4 @@ class _WalletPaymentConfirmPageState extends State { "", "3"); } - - Future runSafePayment(Map requestParam, {String sdkToken = ""}) async { - dynamic result; - - try { - // Force a 100ms microtask buffer to isolate the UI rendering channel - await Future.delayed(const Duration(milliseconds: 350)); - - result = await FlutterAmazonpaymentservices.normalPay(requestParam, ApiConsts.payFortEnvironmentType, isShowResponsePage: true); - - print("Payment Success Data: ${result.toString()}"); - } on PlatformException catch (e) { - // If the error is the MethodChannel duplicate reply bug, handle it safely - if (e.message != null && e.message!.contains("Reply already submitted")) { - print("Swallowed native channel race-condition crash safely."); - return; - } - - // Process real payment rejection errors here - print("Actual Payment Failure: ${e.details.toString()}"); - } catch (error) { - print("Generic Error: $error"); - } - } } diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 582fd6fc..34fc268f 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -72,7 +72,7 @@ class _ServicesPageState extends State { LocaleKeys.emergencyServices.tr(), "", AppAssets.emergency_services_icon, - bgColor: AppColors.primaryRedColor, + bgColor: AppColors.primaryRedColorBase, true, route: null, onTap: () async { // if (getIt.get().isAuthenticated) { diff --git a/lib/presentation/home/app_update_page.dart b/lib/presentation/home/app_update_page.dart index 37dab487..e59a7e8a 100644 --- a/lib/presentation/home/app_update_page.dart +++ b/lib/presentation/home/app_update_page.dart @@ -76,14 +76,15 @@ class AppUpdatePage extends StatelessWidget { } if (Platform.isIOS) { Utils.openWebView( - // url: "https://itunes.apple.com/app/id733503978", - url: "https://itunes.apple.com/app/id6758851027", + url: "https://itunes.apple.com/app/id733503978", + // url: "https://itunes.apple.com/app/id6758851027", ); } } Future _handleAndroidAppUpdate() async { - const fallbackUrl = "https://play.google.com/store/apps/details?id=com.cloudsolutions.HMGPatientApp"; + // const fallbackUrl = "https://play.google.com/store/apps/details?id=com.cloudsolutions.HMGPatientApp"; + const fallbackUrl = "https://play.google.com/store/apps/details?id=com.ejada.hmg"; try { final info = await InAppUpdate.checkForUpdate(); print("InAppUpdate.checkForUpdate -> $info"); diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index 3d98e5a1..026c1725 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -9,192 +9,190 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; class LandingPageData { static List get getNotLoggedInServiceCardsList => [ - ServiceCardData( - serviceName: "emergency", - icon: AppAssets.emergency_services_icon, - title: getIt.get().isArabic()?LocaleKeys.services3 :LocaleKeys.emergency2, - subtitle: getIt.get().isArabic()?LocaleKeys.emergency2:LocaleKeys.services3, - backgroundColor: AppColors.primaryRedColor, - iconColor: Colors.white, - textColor: Colors.white, - isBold: true, - ), - ServiceCardData( - serviceName: "search_doctor", - icon: AppAssets.search_doctor_icon, - title: LocaleKeys.finda, - subtitle: LocaleKeys.doctor, - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, - isBold: false, - ), - ServiceCardData( - serviceName: "health_calculators_and_converts", - icon: AppAssets.health_calculators_icon, - title: getIt.get().isArabic()? LocaleKeys.calculators:LocaleKeys.health , - subtitle: getIt.get().isArabic()? LocaleKeys.health:LocaleKeys.calculators, - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, - isBold: false, - ), - ServiceCardData( - serviceName: "health_converters", - icon: AppAssets.health_converters_icon, - title: getIt.get().isArabic()? LocaleKeys.converters:LocaleKeys.health, - subtitle: getIt.get().isArabic()? LocaleKeys.health:LocaleKeys.converters, - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, - isBold: false, - ), - // ServiceCardData( - // serviceName: "parking_guide", - // icon: AppAssets.car_parking_icon, - // title: getIt.get().isArabic()? LocaleKeys.guide:LocaleKeys.parking2, - // subtitle: getIt.get().isArabic()? LocaleKeys.parking2:LocaleKeys.guide, - // backgroundColor: AppColors.whiteColor, - // iconColor: AppColors.blackColor, - // textColor: AppColors.blackColor, - // isBold: false, - // ), - ServiceCardData( - serviceName: "indoor_navigation", - icon: AppAssets.indoor_nav_icon, - title: LocaleKeys.indoor, - subtitle: LocaleKeys.thehospital, - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, - isBold: false, - ), - ]; + ServiceCardData( + serviceName: "emergency", + icon: AppAssets.emergency_services_icon, + title: getIt.get().isArabic() ? LocaleKeys.services3 : LocaleKeys.emergency2, + subtitle: getIt.get().isArabic() ? LocaleKeys.emergency2 : LocaleKeys.services3, + backgroundColor: AppColors.primaryRedColorBase, + iconColor: Colors.white, + textColor: Colors.white, + isBold: true, + ), + ServiceCardData( + serviceName: "search_doctor", + icon: AppAssets.search_doctor_icon, + title: LocaleKeys.finda, + subtitle: LocaleKeys.doctor, + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), + ServiceCardData( + serviceName: "health_calculators_and_converts", + icon: AppAssets.health_calculators_icon, + title: getIt.get().isArabic() ? LocaleKeys.calculators : LocaleKeys.health, + subtitle: getIt.get().isArabic() ? LocaleKeys.health : LocaleKeys.calculators, + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), + ServiceCardData( + serviceName: "health_converters", + icon: AppAssets.health_converters_icon, + title: getIt.get().isArabic() ? LocaleKeys.converters : LocaleKeys.health, + subtitle: getIt.get().isArabic() ? LocaleKeys.health : LocaleKeys.converters, + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), + // ServiceCardData( + // serviceName: "parking_guide", + // icon: AppAssets.car_parking_icon, + // title: getIt.get().isArabic()? LocaleKeys.guide:LocaleKeys.parking2, + // subtitle: getIt.get().isArabic()? LocaleKeys.parking2:LocaleKeys.guide, + // backgroundColor: AppColors.whiteColor, + // iconColor: AppColors.blackColor, + // textColor: AppColors.blackColor, + // isBold: false, + // ), + ServiceCardData( + serviceName: "indoor_navigation", + icon: AppAssets.indoor_nav_icon, + title: LocaleKeys.indoor, + subtitle: LocaleKeys.thehospital, + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), + ]; List get getLoggedInServiceCardsList => [ - ServiceCardData( - serviceName: "emergency", - icon: AppAssets.emergency_services_icon, - title: getIt.get().isArabic()?LocaleKeys.services3 :LocaleKeys.emergency2, - subtitle: getIt.get().isArabic()?LocaleKeys.emergency2:LocaleKeys.services3, - backgroundColor: AppColors.primaryRedColor, - iconColor: Colors.white, - textColor: Colors.white, - isBold: true, - ), - ServiceCardData( - serviceName: "lab_results", - icon: AppAssets.home_lab_result_icon, - title: getIt.get().isArabic()?LocaleKeys.labSubtitle:LocaleKeys.lab, - subtitle: getIt.get().isArabic()?LocaleKeys.lab:LocaleKeys.labSubtitle , - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, - isBold: false, - ), - ServiceCardData( - serviceName: "radiology_results", - icon: AppAssets.my_radiology_icon, - title: getIt.get().isArabic()?LocaleKeys.labSubtitle:LocaleKeys.radiology, - subtitle: getIt.get().isArabic()?LocaleKeys.radiology:LocaleKeys.labSubtitle , - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, - isBold: false, - ), - ServiceCardData( - serviceName: "prescriptions", - icon: AppAssets.my_prescription_icon, - title: getIt.get().isArabic()?LocaleKeys.medicinesSubtitle:LocaleKeys.medicalKey, - subtitle: getIt.get().isArabic()?LocaleKeys.medicalKey:LocaleKeys.medicinesSubtitle, - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, - isBold: false, - ), - ServiceCardData( - serviceName: "insurance_update", - icon: AppAssets.insurance_update_icon, - title: getIt.get().isArabic()?LocaleKeys.insurance :LocaleKeys.insurance, - subtitle: getIt.get().isArabic()?LocaleKeys.medical: LocaleKeys.details2, - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, - isBold: false, - ), - ServiceCardData( - serviceName: "my_doctors", - icon: AppAssets.my_doctors_icon, - title: getIt.get().isArabic() ? LocaleKeys.myDoctorSubtitle2.tr() : "My", - subtitle: getIt.get().isArabic() ? LocaleKeys.myDoctor.tr() : "Doctors", - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, - isBold: false, - ), - ServiceCardData( - serviceName: "sick_leaves", - icon: AppAssets.my_sick_leave_icon, - title: getIt.get().isArabic()?LocaleKeys.sickSubtitle:LocaleKeys.sick2, - subtitle: getIt.get().isArabic()?LocaleKeys.sick2:LocaleKeys.sickSubtitle, - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, - isBold: false, - ), - ServiceCardData( - serviceName: "my_invoice", - icon: AppAssets.invoiceListIcon, - title: getIt.get().isArabic()?LocaleKeys.myDoctorSubtitle2:LocaleKeys.invoiceList2, - subtitle: getIt.get().isArabic()?LocaleKeys.invoiceList2:LocaleKeys.myDoctorSubtitle2, - backgroundColor: AppColors.whiteColor, - iconColor: AppColors.blackColor, - textColor: AppColors.blackColor, - isBold: false, - ), - ]; + ServiceCardData( + serviceName: "emergency", + icon: AppAssets.emergency_services_icon, + title: getIt.get().isArabic() ? LocaleKeys.services3 : LocaleKeys.emergency2, + subtitle: getIt.get().isArabic() ? LocaleKeys.emergency2 : LocaleKeys.services3, + backgroundColor: AppColors.primaryRedColorBase, + iconColor: Colors.white, + textColor: Colors.white, + isBold: true, + ), + ServiceCardData( + serviceName: "lab_results", + icon: AppAssets.home_lab_result_icon, + title: getIt.get().isArabic() ? LocaleKeys.labSubtitle : LocaleKeys.lab, + subtitle: getIt.get().isArabic() ? LocaleKeys.lab : LocaleKeys.labSubtitle, + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), + ServiceCardData( + serviceName: "radiology_results", + icon: AppAssets.my_radiology_icon, + title: getIt.get().isArabic() ? LocaleKeys.labSubtitle : LocaleKeys.radiology, + subtitle: getIt.get().isArabic() ? LocaleKeys.radiology : LocaleKeys.labSubtitle, + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), + ServiceCardData( + serviceName: "prescriptions", + icon: AppAssets.my_prescription_icon, + title: getIt.get().isArabic() ? LocaleKeys.medicinesSubtitle : LocaleKeys.medicalKey, + subtitle: getIt.get().isArabic() ? LocaleKeys.medicalKey : LocaleKeys.medicinesSubtitle, + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), + ServiceCardData( + serviceName: "insurance_update", + icon: AppAssets.insurance_update_icon, + title: getIt.get().isArabic() ? LocaleKeys.insurance : LocaleKeys.insurance, + subtitle: getIt.get().isArabic() ? LocaleKeys.medical : LocaleKeys.details2, + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), + ServiceCardData( + serviceName: "my_doctors", + icon: AppAssets.my_doctors_icon, + title: getIt.get().isArabic() ? LocaleKeys.myDoctorSubtitle2.tr() : "My", + subtitle: getIt.get().isArabic() ? LocaleKeys.myDoctor.tr() : "Doctors", + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), + ServiceCardData( + serviceName: "sick_leaves", + icon: AppAssets.my_sick_leave_icon, + title: getIt.get().isArabic() ? LocaleKeys.sickSubtitle : LocaleKeys.sick2, + subtitle: getIt.get().isArabic() ? LocaleKeys.sick2 : LocaleKeys.sickSubtitle, + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), + ServiceCardData( + serviceName: "my_invoice", + icon: AppAssets.invoiceListIcon, + title: getIt.get().isArabic() ? LocaleKeys.myDoctorSubtitle2 : LocaleKeys.invoiceList2, + subtitle: getIt.get().isArabic() ? LocaleKeys.invoiceList2 : LocaleKeys.myDoctorSubtitle2, + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), + ]; static List get getServiceCardsList => [ - ServiceCardData( - serviceName: "livecare", - icon: AppAssets.small_livecare_icon, - title: LocaleKeys.liveCare, - subtitle: LocaleKeys.liveCareServiceDesc, - largeCardIcon: AppAssets.liveCareService, - backgroundColor: AppColors.successColor, - iconColor: AppColors.whiteColor, - isBold: false, - isPNG: false - ), - // ServiceCardData( - // icon: AppAssets.homeBottom, - // title: "Dermatology", - // subtitle: "Explore our app, View our services and offers", - // largeCardIcon: AppAssets.homeBottom, - // backgroundColor: AppColors.primaryRedColor, - // isBold: false, - // ), - ServiceCardData( - serviceName: "home_health_care", - icon: AppAssets.homeBottom, - title: LocaleKeys.homeHealthCare, - subtitle: LocaleKeys.homeHealthCareText, - largeCardIcon: AppAssets.homeHealthCareService, - backgroundColor: AppColors.primaryRedColor, - iconColor: AppColors.whiteColor, - isBold: false, - isPNG: false - ), - ServiceCardData( - serviceName: "pharmacy", - icon: AppAssets.defaultMEP, //359846 - title: LocaleKeys.hmgPharmacy, - subtitle: LocaleKeys.hmgPharmacyText, - largeCardIcon: AppAssets.pharmacyService, - backgroundColor: AppColors.pharmacyBGColor, - iconColor: null, - isBold: true, - isPNG: true - ), - ]; + ServiceCardData( + serviceName: "livecare", + icon: AppAssets.small_livecare_icon, + title: LocaleKeys.liveCare, + subtitle: LocaleKeys.liveCareServiceDesc, + largeCardIcon: AppAssets.liveCareService, + backgroundColor: AppColors.successColor, + iconColor: AppColors.whiteColor, + isBold: false, + isPNG: false), + // ServiceCardData( + // icon: AppAssets.homeBottom, + // title: "Dermatology", + // subtitle: "Explore our app, View our services and offers", + // largeCardIcon: AppAssets.homeBottom, + // backgroundColor: AppColors.primaryRedColor, + // isBold: false, + // ), + ServiceCardData( + serviceName: "home_health_care", + icon: AppAssets.homeBottom, + title: LocaleKeys.homeHealthCare, + subtitle: LocaleKeys.homeHealthCareText, + largeCardIcon: AppAssets.homeHealthCareService, + backgroundColor: AppColors.primaryRedColor, + iconColor: AppColors.whiteColor, + isBold: false, + isPNG: false), + ServiceCardData( + serviceName: "pharmacy", + icon: AppAssets.defaultMEP, + //359846 + title: LocaleKeys.hmgPharmacy, + subtitle: LocaleKeys.hmgPharmacyText, + largeCardIcon: AppAssets.pharmacyService, + backgroundColor: AppColors.pharmacyBGColor, + iconColor: null, + isBold: true, + isPNG: true), + ]; } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index fdb8afcf..ec0b0ba9 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -5,6 +5,7 @@ import 'dart:ui' as ui; import 'package:clarity_flutter/clarity_flutter.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_swiper_view/flutter_swiper_view.dart'; import 'package:get_it/get_it.dart'; @@ -31,34 +32,30 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; +import 'package:hmg_patient_app_new/features/occasion_theme/occasion_theme_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; -import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_queue_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; -import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart'; import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart'; -import 'package:hmg_patient_app_new/presentation/home/app_update_page.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/lakum_wallet_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; +import 'package:hmg_patient_app_new/presentation/home/widgets/occasion_banner_widget.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart'; import 'package:hmg_patient_app_new/presentation/notifications/notifications_list_page.dart'; -import 'package:hmg_patient_app_new/presentation/offers_and_discounts/offers_and_discounts_page.dart'; -import 'package:hmg_patient_app_new/presentation/offers_and_discounts/widgets/offers_and_discounts.dart'; import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart'; -import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart'; @@ -126,10 +123,7 @@ class _LandingPageState extends State { authVM.savePushTokenToAppState(); if (mounted) { final user = appState.getAuthenticatedUser(); - final hasUserQuickLoginData = - user != null && - (user.mobileNumber?.isNotEmpty ?? false) && - (user.patientIdentificationNo?.isNotEmpty ?? false); + final hasUserQuickLoginData = user != null && (user.mobileNumber?.isNotEmpty ?? false) && (user.patientIdentificationNo?.isNotEmpty ?? false); if (hasUserQuickLoginData) { authVM.checkLastLoginStatus(() { @@ -191,7 +185,7 @@ class _LandingPageState extends State { onSuccess: ((response) { // Only open dialog if details are loaded AND widget is mounted if (!mounted || appointmentRatingViewModel.appointmentDetails == null) return; - + appointmentRatingViewModel.setClinicOrDoctor(false); appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr()); appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr()); @@ -227,585 +221,697 @@ class _LandingPageState extends State { child: Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, insuranceVM, child) { + // Only when an occasion theme is active AND it publishes a header + // banner do we switch to the sticky-header layout below. Otherwise + // (no occasion, or occasion active but no banner configured) the + // page behaves exactly as it always has — banner (if any) scrolls + // inline with the rest of the content. + final hasStickyBanner = context.watch().headerImage?.isNotEmpty ?? false; + + log("hasStickyBanner: $hasStickyBanner"); return Stack( children: [ - RefreshIndicator( - color: AppColors.primaryRedColor, - onRefresh: () async { - if (appState.isAuthenticated) { - // Refresh Appointments Data - myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); - myAppointmentsViewModel.initAppointmentsViewModel(); - myAppointmentsViewModel.getPatientAppointments(true, false, isForTimeLine: true); - - // Refresh Appointments Data - // habibWalletVM.initHabibWalletProvider(); - // habibWalletVM.getPatientBalanceAmount(); - // habibWalletVM.getLakumAccountInformation(); - - // Refresh Ancillary Orders - todoSectionViewModel.setIsAncillaryOrdersNeedReloading(true); - todoSectionViewModel.initializeTodoSectionViewModel(); - - // Refresh Immediate LiveCare Data - immediateLiveCareViewModel.initImmediateLiveCare(); - immediateLiveCareViewModel.getPatientLiveCareHistory(); - - // appointmentRatingViewModel.getLastRatingAppointment( - // onSuccess: (response) { - // if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { - // appointmentRatingViewModel.getAppointmentDetails( - // appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, - // appointmentRatingViewModel.appointmentRatedList.last.projectID!, - // onSuccess: ((response) { - // appointmentRatingViewModel.setClinicOrDoctor(false); - // appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr(context: context)); - // appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context)); - // openLastRating(); - // appState.setRatedVisible(true); - // }), - // ); - // } - // }, - // ); - - } - }, - child: SingleChildScrollView( - controller: _scrollController, - physics: const AlwaysScrollableScrollPhysics(), - padding: EdgeInsets.only( - top: (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) - ? (MediaQuery.paddingOf(context).top + 70.h) - : kToolbarHeight + 16.h, - bottom: 24.h), - child: Column( - spacing: 16.h, - children: [ - Row( - spacing: 8.h, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - appState.isAuthenticated - ? WelcomeWidget( - onTap: () { - // ...existing navigation code... - - Navigator.of(context).push( - CustomPageRoute( - direction: AxisDirection.down, - page: FamilyMedicalScreen(), - ), - ); - - // Navigator.pushReplacementNamed( - // // context, - // GetIt.instance().navigatorKey.currentContext!, - // AppRoutes.zoomCallPage, - // // arguments: CallArguments(appointmentID, "111", "Patient", "40", "1", true, 1), - // arguments: CallArguments("yosemite-338", "123", "Patient", "40", "0", true, 1), - // // arguments: CallArguments("SmallDailyStandup9875", "123", "Patient", "40", "0", false, int.parse(widget.incomingCallData!.appointmentNo!)), - // ); - }, - name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), - imageWidget: Selector( - selector: (_, profileVM) => profileVM.profileImageData ?? appState.getProfileImageData, - shouldRebuild: (previous, next) => previous != next, - builder: (context, profileImageData, child) { - // Only rebuild when profile image data changes - return UserAvatarWidget( - key: ValueKey('landing_avatar_${profileImageData?.hashCode ?? 0}'), - width: 42.w, - height: 42.h, - fit: BoxFit.cover, - isCircular: true, - customProfileImageData: profileImageData, - ); - }, - ), - ).expanded - : CustomButton( - text: LocaleKeys.loginOrRegister.tr(context: context), - onPressed: () async { - await authVM.onLoginPressed(); - - // Navigator.pushReplacementNamed( - // // context, - // GetIt.instance().navigatorKey.currentContext!, - // AppRoutes.zoomCallPage, - // arguments: CallArguments("lake-tahoe-289", "123", "Patient", "40", "0", true, 1), - // ); - - // Navigator.of(context).push( - // CustomPageRoute( - // // page: NotificationsListPage(), - // page: AppUpdatePage( - // appUpdateMessage: - // "A new update for Dr.Alhabib app is now available with new features and services.\r\nFor technical support:\r\neservices.hmg@drsulaimanalhabib.com"), - // ), - // ); - - // ...existing commented code... - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w700, - borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(12.h, 0, 12.h, 0), - height: 40.h, - ), - Consumer(builder: (context, todoSectionVM, child) { - return Row( - mainAxisSize: MainAxisSize.min, - // spacing: 18.h, + // Renders nothing unless a remote-config occasion theme (e.g. + // National Day) is currently active — fills the whole page, + // behind all other content below. + const OccasionBackgroundWidget(), + + if (hasStickyBanner) + // Android 15+ (targetSdk 36) enforces edge-to-edge and + // silently ignores SystemUiOverlayStyle.statusBarColor, so + // the status bar's background must be painted manually. + // AppColors.primaryRedColor already resolves to the active + // occasion's color, so this needs no extra plumbing. + Positioned( + top: 0, + left: 0, + right: 0, + child: Container( + height: MediaQuery.paddingOf(context).top, + color: AppColors.primaryRedColor, + ), + ), + + Column( + children: [ + // Pinned/sticky header — only rendered here when an + // occasion banner is active; scrollable content below + // fills the remaining space and scrolls independently. + // Only the banner itself is offset clear of the status + // bar — the scrollable content underneath intentionally + // keeps extending edge-to-edge behind it (unchanged from + // before), with the AnimatedOpacity gradient smoothing + // that transition as the user scrolls. + if (hasStickyBanner) + Padding( + padding: EdgeInsets.only(top: MediaQuery.paddingOf(context).top - 12.h), + child: const OccasionBannerWidget(), + ), + Expanded( + child: RefreshIndicator( + color: AppColors.primaryRedColor, + onRefresh: () async { + // Re-check Remote Config for occasion theme changes on + // pull-to-refresh (independent of auth state) — handy for + // testing without needing to restart the app. + unawaited(GetIt.instance().refresh()); + + if (appState.isAuthenticated) { + // Refresh Appointments Data + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false, isForTimeLine: true); + + // Refresh Appointments Data + // habibWalletVM.initHabibWalletProvider(); + // habibWalletVM.getPatientBalanceAmount(); + // habibWalletVM.getLakumAccountInformation(); + + // Refresh Ancillary Orders + todoSectionViewModel.setIsAncillaryOrdersNeedReloading(true); + todoSectionViewModel.initializeTodoSectionViewModel(); + + // Refresh Immediate LiveCare Data + immediateLiveCareViewModel.initImmediateLiveCare(); + immediateLiveCareViewModel.getPatientLiveCareHistory(); + + // appointmentRatingViewModel.getLastRatingAppointment( + // onSuccess: (response) { + // if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) { + // appointmentRatingViewModel.getAppointmentDetails( + // appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!, + // appointmentRatingViewModel.appointmentRatedList.last.projectID!, + // onSuccess: ((response) { + // appointmentRatingViewModel.setClinicOrDoctor(false); + // appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr(context: context)); + // appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context)); + // openLastRating(); + // appState.setRatedVisible(true); + // }), + // ); + // } + // }, + // ); + } + }, + child: SingleChildScrollView( + controller: _scrollController, + physics: const AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.only( + top: (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) + ? (MediaQuery.paddingOf(context).top + 70.h) + // When the occasion banner is pinned above (hasStickyBanner), it + // already occupies the toolbar-height space that this padding + // otherwise reserves — so only a small breathing gap is needed. + : (hasStickyBanner ? 16.h : kToolbarHeight + 16.h), + bottom: 24.h), + child: Column( + children: [ + if (!hasStickyBanner) const OccasionBannerWidget(), + Row( + spacing: 8.h, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Stack(clipBehavior: Clip.none, children: [ - if (appState.isAuthenticated) Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 24.h, width: 24.h), - (appState.isAuthenticated && todoSectionVM.notificationsCount != null && todoSectionVM.notificationsCount != "0") - ? Positioned( - // right: appState.isArabic() ? 8.w : -8.w, - top: -8.h, - // left: 4.h, - // bottom: 4.h, - child: Container( - width: 18.w, - height: 18.h, - padding: EdgeInsets.all(2), - decoration: BoxDecoration( - color: AppColors.primaryRedColor, - borderRadius: BorderRadius.circular(20.r), + appState.isAuthenticated + ? WelcomeWidget( + onTap: () { + // ...existing navigation code... + + Navigator.of(context).push( + CustomPageRoute( + direction: AxisDirection.down, + page: FamilyMedicalScreen(), ), - child: Text( - todoSectionVM.notificationsCount ?? "0", - style: TextStyle( - color: Colors.white, - fontFamily: "Poppins", - fontSize: 10.f, - fontWeight: FontWeight.w600, - ), - textAlign: TextAlign.center, + ); + + // Navigator.pushReplacementNamed( + // // context, + // GetIt.instance().navigatorKey.currentContext!, + // AppRoutes.zoomCallPage, + // // arguments: CallArguments(appointmentID, "111", "Patient", "40", "1", true, 1), + // arguments: CallArguments("yosemite-338", "123", "Patient", "40", "0", true, 1), + // // arguments: CallArguments("SmallDailyStandup9875", "123", "Patient", "40", "0", false, int.parse(widget.incomingCallData!.appointmentNo!)), + // ); + }, + name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), + imageWidget: Selector( + selector: (_, profileVM) => profileVM.profileImageData ?? appState.getProfileImageData, + shouldRebuild: (previous, next) => previous != next, + builder: (context, profileImageData, child) { + // Only rebuild when profile image data changes + return UserAvatarWidget( + key: ValueKey('landing_avatar_${profileImageData?.hashCode ?? 0}'), + width: 42.w, + height: 42.h, + fit: BoxFit.cover, + isCircular: true, + customProfileImageData: profileImageData, + ); + }, + ), + ).expanded + : CustomButton( + text: LocaleKeys.loginOrRegister.tr(context: context), + onPressed: () async { + await authVM.onLoginPressed(); + + // Navigator.pushReplacementNamed( + // // context, + // GetIt.instance().navigatorKey.currentContext!, + // AppRoutes.zoomCallPage, + // arguments: CallArguments("lake-tahoe-289", "123", "Patient", "40", "0", true, 1), + // ); + + // Navigator.of(context).push( + // CustomPageRoute( + // // page: NotificationsListPage(), + // page: AppUpdatePage( + // appUpdateMessage: + // "A new update for Dr.Alhabib app is now available with new features and services.\r\nFor technical support:\r\neservices.hmg@drsulaimanalhabib.com"), + // ), + // ); + + // ...existing commented code... + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: Colors.transparent, + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w700, + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(12.h, 0, 12.h, 0), + height: 40.h, + ), + Consumer(builder: (context, todoSectionVM, child) { + return Row( + mainAxisSize: MainAxisSize.min, + // spacing: 18.h, + children: [ + Stack(clipBehavior: Clip.none, children: [ + if (appState.isAuthenticated) Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 24.h, width: 24.h), + (appState.isAuthenticated && todoSectionVM.notificationsCount != null && todoSectionVM.notificationsCount != "0") + ? Positioned( + // right: appState.isArabic() ? 8.w : -8.w, + top: -8.h, + // left: 4.h, + // bottom: 4.h, + child: Container( + width: 18.w, + height: 18.h, + padding: EdgeInsets.all(2), + decoration: BoxDecoration( + color: AppColors.primaryRedColorBase, + borderRadius: BorderRadius.circular(20.r), + ), + child: Text( + todoSectionVM.notificationsCount ?? "0", + style: TextStyle( + color: Colors.white, + fontFamily: "Poppins", + fontSize: 10.f, + fontWeight: FontWeight.w600, + ), + textAlign: TextAlign.center, + ), + ), + ) + : SizedBox.shrink(), + ]).onPress(() async { + if (appState.isAuthenticated) { + notificationsViewModel.onNotificationTabChange(0); + notificationsViewModel.setNotificationStatusID(2); + notificationsViewModel.getAllNotifications(); + Navigator.of(context).push( + CustomPageRoute( + page: NotificationsListPage(), + // page: LoginScreen(), ), - ), - ) - : SizedBox.shrink(), - ]).onPress(() async { - if (appState.isAuthenticated) { - notificationsViewModel.setNotificationStatusID(2); - notificationsViewModel.getAllNotifications(); - Navigator.of(context).push( - CustomPageRoute( - page: NotificationsListPage(), - // page: LoginScreen(), + ); + } else { + await authVM.onLoginPressed(); + } + }), + SizedBox( + width: 24.w, ), - ); - } else { - await authVM.onLoginPressed(); - } - }), - SizedBox( - width: 24.w, - ), - Utils.buildSvgWithAssets(icon: AppAssets.location, height: 24.h, width: 24.w).onPress(() { - // openIndoorNavigationBottomSheet(context); - showCommonBottomSheetWithoutHeight( - context, - title: LocaleKeys.contactUs.tr(), - child: ContactUs(), - callBackFunc: () {}, - isFullScreen: false, + Utils.buildSvgWithAssets(icon: AppAssets.location, height: 24.h, width: 24.w).onPress(() { + // openIndoorNavigationBottomSheet(context); + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.contactUs.tr(), + child: ContactUs(), + callBackFunc: () {}, + isFullScreen: false, + ); + }), + + // Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 24.h, width: 24.h).onPress(() { + // showCommonBottomSheetWithoutHeight( + // context, + // title: LocaleKeys.contactUs.tr(), + // child: ContactUs(), + // callBackFunc: () {}, + // isFullScreen: false, + // ); + // }), + !appState.isAuthenticated + ? Row(children: [ + SizedBox(width: 24.w), + Utils.buildSvgWithAssets(icon: appState.isArabic() ? AppAssets.enLangIcon : AppAssets.arLangIcon, height: 24.h, width: 24.h).onPress(() { + context.setLocale(appState.isArabic() ? Locale('en', 'US') : Locale('ar', 'SA')); + }) + ]) + : SizedBox() + ], ); }), - - // Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 24.h, width: 24.h).onPress(() { - // showCommonBottomSheetWithoutHeight( - // context, - // title: LocaleKeys.contactUs.tr(), - // child: ContactUs(), - // callBackFunc: () {}, - // isFullScreen: false, - // ); - // }), - !appState.isAuthenticated - ? Row(children: [ - SizedBox(width: 24.w), - Utils.buildSvgWithAssets(icon: appState.isArabic() ? AppAssets.enLangIcon : AppAssets.arLangIcon, height: 24.h, width: 24.h).onPress(() { - context.setLocale(appState.isArabic() ? Locale('en', 'US') : Locale('ar', 'SA')); - }) - ]) - : SizedBox() ], - ); - }), - ], - ).paddingSymmetrical(24.h, 0.h), - !appState.isAuthenticated - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - width: 50.w, - height: 60.h, - icon: AppAssets.symptomCheckerIcon, - fit: BoxFit.contain, + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 8.h), + !appState.isAuthenticated + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, ), - SizedBox(width: 12.w), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.howAreYouFeelingToday.tr(context: context).toText14(isBold: true), - LocaleKeys.checkYourSymptomsWithScale.tr(context: context).toText12(isBold: true), - SizedBox(height: 14.h), - CustomButton( - text: LocaleKeys.checkYourSymptoms.tr(context: context), - onPressed: () async { - context.navigateWithName(AppRoutes.userInfoSelection); - }, - padding: EdgeInsetsGeometry.zero, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: Colors.white, - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - height: 40.h, - ), - ], - ).expanded - ], - ), - ), - ).paddingSymmetrical(24.w, 0.h) - : SizedBox.shrink(), - - // Offers And Discounts Carousel - Auto-scrolling from right to left - // appState.isAuthenticated && appState.isEnabledOffersAndDiscountsCarousel - // ? Column( - // children: [ - // SizedBox(height: 12.h), - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // ("${LocaleKeys.offers.tr(context: context)} & ${LocaleKeys.discount.tr(context: context)}").toText16(isBold: true), - // Row( - // children: [ - // LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), - // SizedBox(width: 2.h), - // Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), - // ], - // ), - // ], - // ).paddingSymmetrical(24.h, 0.h).onPress(() { - // Navigator.of(context).push(CustomPageRoute(page: OffersAndDiscountsPage())); - // }), - // SizedBox(height: 16.h), - // OffersAndDiscountsCarousel().paddingSymmetrical(24.h, 0.h), - // SizedBox(height: 18.h), - // ], - // ) - // : SizedBox.shrink(), - - appState.isAuthenticated - ? Column( - children: [ - SizedBox(height: 12.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(isBold: true), - Row( - children: [ - LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), - ], + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + width: 50.w, + height: 60.h, + icon: AppAssets.symptomCheckerIcon, + fit: BoxFit.contain, + ), + SizedBox(width: 12.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.howAreYouFeelingToday.tr(context: context).toText14(isBold: true), + LocaleKeys.checkYourSymptomsWithScale.tr(context: context).toText12(isBold: true), + SizedBox(height: 14.h), + CustomButton( + text: LocaleKeys.checkYourSymptoms.tr(context: context), + onPressed: () async { + context.navigateWithName(AppRoutes.userInfoSelection); + }, + padding: EdgeInsetsGeometry.zero, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + height: 40.h, + ), + ], + ).expanded + ], + ), ), - ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - myAppointmentsViewModel.onTabChange(0); - myAppointmentsViewModel.updateListWRTTab(0); - - Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); - // getIt.get().setPaymentConfiguration("Appointment Payment", 15.5); - // getIt.get().startCardPayment(); - }), - Consumer3( - builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) { - return myAppointmentsVM.isMyAppointmentsLoading - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: true, - isFromHomePage: true, - ), - ).paddingSymmetrical(24.h, 16.h) - : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 && !immediateLiveCareVM.patientHasPendingLiveCareRequest - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first, - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ).paddingSymmetrical(24.h, 0.h) - : isTablet - ? SizedBox( - height: isTablet ? 290.h : 255.h, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: () { - final int baseCount = - myAppointmentsVM.patientAppointmentsHistoryList.length + (immediateLiveCareVM.patientHasPendingLiveCareRequest ? 1 : 0); - return baseCount < 3 ? baseCount : 3; - }(), - shrinkWrap: true, - padding: EdgeInsets.only(left: 16.h, right: 16.h), - itemBuilder: (context, index) { - return SizedBox( - height: isTablet ? 290.h : 255.h, - width: 250.w, - child: getIndexSwiperCard(index), - ); - // return AnimationConfiguration.staggeredList( - // position: index, - // duration: const Duration(milliseconds: 1000), - // child: SlideAnimation( - // horizontalOffset: 100.0, - // child: FadeInAnimation( - // child: SizedBox( - // height: 255.h, - // width: 250.w, - // child: getIndexSwiperCard(index), - // ), - // ), - // ), - // ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox( - width: 10.w, + ).paddingSymmetrical(24.w, 0.h) + : SizedBox.shrink(), + SizedBox(height: 16.h), + + // Offers And Discounts Carousel - Auto-scrolling from right to left + // appState.isAuthenticated && appState.isEnabledOffersAndDiscountsCarousel + // ? Column( + // children: [ + // SizedBox(height: 12.h), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // ("${LocaleKeys.offers.tr(context: context)} & ${LocaleKeys.discount.tr(context: context)}").toText16(isBold: true), + // Row( + // children: [ + // LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), + // SizedBox(width: 2.h), + // Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + // ], + // ), + // ], + // ).paddingSymmetrical(24.h, 0.h).onPress(() { + // Navigator.of(context).push(CustomPageRoute(page: OffersAndDiscountsPage())); + // }), + // SizedBox(height: 16.h), + // OffersAndDiscountsCarousel().paddingSymmetrical(24.h, 0.h), + // SizedBox(height: 18.h), + // ], + // ) + // : SizedBox.shrink(), + + appState.isAuthenticated + ? Column( + children: [ + SizedBox(height: 12.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(isBold: true), + Row( + children: [ + LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + ], + ), + ], + ).paddingSymmetrical(24.h, 0.h).onPress(() { + myAppointmentsViewModel.onTabChange(0); + myAppointmentsViewModel.updateListWRTTab(0); + + Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); + // getIt.get().setPaymentConfiguration("Appointment Payment", 15.5); + // getIt.get().startCardPayment(); + }), + Consumer3( + builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) { + return myAppointmentsVM.isMyAppointmentsLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: true, + isFromHomePage: true, + ), + ).paddingSymmetrical(24.h, 16.h) + : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 && !immediateLiveCareVM.patientHasPendingLiveCareRequest + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, ), - ), - ) - : SizedBox( - height: 255.h + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space - child: Builder( - builder: (context) { - final int baseAppointmentCount = myAppointmentsVM.patientAppointmentsHistoryList.length; - final int liveCareCount = immediateLiveCareVM.patientHasPendingLiveCareRequest ? 1 : 0; - final int totalCount = baseAppointmentCount + liveCareCount; - - final int swiperItemCount = myAppointmentsVM.isMyAppointmentsLoading - ? 3 - : totalCount < 3 - ? totalCount - : 3; - final bool isArabic = appState.isArabic(); - return Swiper( - itemCount: swiperItemCount, - layout: SwiperLayout.STACK, - loop: false, - itemWidth: MediaQuery.of(context).size.width - 48.h, - indicatorLayout: PageIndicatorLayout.COLOR, - axisDirection: isArabic ? AxisDirection.right : AxisDirection.left, - controller: _controller, - index: isArabic ? swiperItemCount - 1 : 0, - itemHeight: 255.h + 20, - // extra space for shadow - pagination: SwiperPagination( - alignment: Alignment.bottomCenter, - margin: EdgeInsets.only(top: 220.h + 20 + 8 + 24), - builder: _DirectionalDotPaginationBuilder( - color: Color(0xffD9D9D9), - activeColor: AppColors.blackBgColor, - isRTL: isArabic, - totalCount: swiperItemCount, + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first, + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ).paddingSymmetrical(24.h, 0.h) + : isTablet + ? SizedBox( + height: isTablet ? 290.h : 255.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: () { + final int baseCount = + myAppointmentsVM.patientAppointmentsHistoryList.length + (immediateLiveCareVM.patientHasPendingLiveCareRequest ? 1 : 0); + return baseCount < 3 ? baseCount : 3; + }(), + shrinkWrap: true, + padding: EdgeInsets.only(left: 16.h, right: 16.h), + itemBuilder: (context, index) { + return SizedBox( + height: isTablet ? 290.h : 255.h, + width: 250.w, + child: getIndexSwiperCard(index), + ); + // return AnimationConfiguration.staggeredList( + // position: index, + // duration: const Duration(milliseconds: 1000), + // child: SlideAnimation( + // horizontalOffset: 100.0, + // child: FadeInAnimation( + // child: SizedBox( + // height: 255.h, + // width: 250.w, + // child: getIndexSwiperCard(index), + // ), + // ), + // ), + // ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox( + width: 10.w, ), ), - itemBuilder: (BuildContext context, int index) { - final int actualIndex = isArabic ? (swiperItemCount - 1 - index) : index; - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: getIndexSwiperCard(actualIndex), - ); - }, - ); - }, + ) + : SizedBox( + height: 255.h + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space + child: Builder( + builder: (context) { + final int baseAppointmentCount = myAppointmentsVM.patientAppointmentsHistoryList.length; + final int liveCareCount = immediateLiveCareVM.patientHasPendingLiveCareRequest ? 1 : 0; + final int totalCount = baseAppointmentCount + liveCareCount; + + final int swiperItemCount = myAppointmentsVM.isMyAppointmentsLoading + ? 3 + : totalCount < 3 + ? totalCount + : 3; + final bool isArabic = appState.isArabic(); + return Swiper( + itemCount: swiperItemCount, + layout: SwiperLayout.STACK, + loop: false, + itemWidth: MediaQuery.of(context).size.width - 48.h, + indicatorLayout: PageIndicatorLayout.COLOR, + axisDirection: isArabic ? AxisDirection.right : AxisDirection.left, + controller: _controller, + index: isArabic ? swiperItemCount - 1 : 0, + itemHeight: 255.h + 20, + // extra space for shadow + pagination: SwiperPagination( + alignment: Alignment.bottomCenter, + margin: EdgeInsets.only(top: 220.h + 20 + 8 + 24), + builder: _DirectionalDotPaginationBuilder( + color: Color(0xffD9D9D9), + activeColor: AppColors.blackBgColor, + isRTL: isArabic, + totalCount: swiperItemCount, + ), + ), + itemBuilder: (BuildContext context, int index) { + final int actualIndex = isArabic ? (swiperItemCount - 1 - index) : index; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: getIndexSwiperCard(actualIndex), + ); + }, + ); + }, + ), + ) + : immediateLiveCareVM.patientHasPendingLiveCareRequest + ? _buildLiveCareRequestCard().paddingSymmetrical(24.h, 16.h) + : Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), + SizedBox(height: 12.h), + LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), + SizedBox(height: 12.h), + CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () { + getIt.get().onTabChanged(0); + Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage())); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + height: 40.h, + ), + ], + ), + ), + ).paddingSymmetrical(24.h, 16.h); + }, + ), + + // Consumer for ER Online Check-In pending request + // Consumer( + // builder: (context, emergencyServicesVM, child) { + // return emergencyServicesVM.patientHasAdvanceERBalance + // ? Column( + // children: [ + // SizedBox(height: 16.h), + // Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.whiteColor, + // borderRadius: 20.r, + // hasShadow: false, + // side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), + // ), + // width: double.infinity, + // child: Padding( + // padding: EdgeInsets.all(16.h), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // // Row( + // // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // // children: [ + // // AppCustomChipWidget( + // // labelText: LocaleKeys.erOnlineCheckInRequest.tr(context: context), + // // backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), + // // textColor: AppColors.primaryRedColor, + // // ), + // // Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor), + // // ], + // // ), + // SizedBox(height: 8.h), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // LocaleKeys.youHaveEROnlineCheckInRequest.tr(context: context).toText12(isBold: true), + // Transform.flip( + // flipX: getIt.get().isArabic(), + // child: Utils.buildSvgWithAssets( + // icon: AppAssets.forward_arrow_icon_small, + // iconColor: AppColors.blackColor, + // width: 20.h, + // height: 15.h, + // fit: BoxFit.contain, + // ), + // ), + // ], + // ), + // ], + // ), + // ), + // ).paddingSymmetrical(24.h, 0.h).onPress(() { + // Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome())); + // // context.read().navigateToEROnlineCheckIn(); + // }), + // SizedBox(height: 12.h), + // ], + // ) + // : SizedBox(height: 0.h); + // }, + // ), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.quickLinks.tr(context: context).toText16(isBold: true), + Row( + children: [ + LocaleKeys.viewMedicalFileLandingPage.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + ], + ), + ], + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: MedicalFilePage())); + }), + SizedBox(height: 16.h), + Consumer(builder: (BuildContext context, TodoSectionViewModel todoSectionVM, Widget? child) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: Column( + children: [ + todoSectionVM.patientAncillaryOrdersList.isNotEmpty + ? Container( + height: 50.h, + decoration: ShapeDecoration( + color: AppColors.eReferralCardColor.withAlpha(50), + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + smoothness: 1, ), - ) - : immediateLiveCareVM.patientHasPendingLiveCareRequest - ? _buildLiveCareRequestCard().paddingSymmetrical(24.h, 16.h) - : Container( - width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), - SizedBox(height: 12.h), - LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), - SizedBox(height: 12.h), + LocaleKeys.pendingAncillaryOrders + .tr(context: context) + .toText14(color: AppColors.eReferralCardColor, isBold: true) + .paddingSymmetrical(24.h, 0.h), CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), + text: LocaleKeys.view.tr(context: context), onPressed: () { - getIt.get().onTabChanged(0); - Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage())); + todoSectionVM.setIsAncillaryOrdersNeedReloading(true); + Navigator.of(context).push(CustomPageRoute(page: ToDoPage())); }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14.f, + backgroundColor: AppColors.eReferralCardColor, + borderColor: AppColors.eReferralCardColor, + textColor: AppColors.whiteColor, + fontSize: 10.f, fontWeight: FontWeight.w600, - padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - height: 40.h, - ), + borderRadius: 8, + padding: EdgeInsets.fromLTRB(15, 0, 15, 0), + height: 30.h, + ).paddingSymmetrical(24.h, 0.h), ], ), - ), - ).paddingSymmetrical(24.h, 16.h); - }, - ), - - // Consumer for ER Online Check-In pending request - // Consumer( - // builder: (context, emergencyServicesVM, child) { - // return emergencyServicesVM.patientHasAdvanceERBalance - // ? Column( - // children: [ - // SizedBox(height: 16.h), - // Container( - // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - // color: AppColors.whiteColor, - // borderRadius: 20.r, - // hasShadow: false, - // side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), - // ), - // width: double.infinity, - // child: Padding( - // padding: EdgeInsets.all(16.h), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // // Row( - // // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // // children: [ - // // AppCustomChipWidget( - // // labelText: LocaleKeys.erOnlineCheckInRequest.tr(context: context), - // // backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), - // // textColor: AppColors.primaryRedColor, - // // ), - // // Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor), - // // ], - // // ), - // SizedBox(height: 8.h), - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // LocaleKeys.youHaveEROnlineCheckInRequest.tr(context: context).toText12(isBold: true), - // Transform.flip( - // flipX: getIt.get().isArabic(), - // child: Utils.buildSvgWithAssets( - // icon: AppAssets.forward_arrow_icon_small, - // iconColor: AppColors.blackColor, - // width: 20.h, - // height: 15.h, - // fit: BoxFit.contain, - // ), - // ), - // ], - // ), - // ], - // ), - // ), - // ).paddingSymmetrical(24.h, 0.h).onPress(() { - // Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome())); - // // context.read().navigateToEROnlineCheckIn(); - // }), - // SizedBox(height: 12.h), - // ], - // ) - // : SizedBox(height: 0.h); - // }, - // ), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.quickLinks.tr(context: context).toText16(isBold: true), - Row( - children: [ - LocaleKeys.viewMedicalFileLandingPage.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), - ], - ), - ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: MedicalFilePage())); - }), - SizedBox(height: 16.h), - Consumer(builder: (BuildContext context, TodoSectionViewModel todoSectionVM, Widget? child) { - return Container( + ) + : SizedBox.shrink(), + SizedBox( + height: 92.h + 32.h - 4.h, + child: RawScrollbar( + controller: _horizontalScrollController, + thumbVisibility: true, + radius: Radius.circular(10.0), + thumbColor: AppColors.primaryRedColor, + trackVisibility: true, + trackColor: Color(0xffD9D9D9), + trackBorderColor: Colors.transparent, + trackRadius: Radius.circular(10.0), + padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery.sizeOf(context).width / 2.5 - 10, right: MediaQuery.sizeOf(context).width / 2.5 - 10), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData().getLoggedInServiceCardsList.length, + shrinkWrap: true, + controller: _horizontalScrollController, + padding: EdgeInsets.only(left: 0.h, right: 0.h, top: 16.h, bottom: 12.h), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 200), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + icon: LandingPageData().getLoggedInServiceCardsList[index].icon, + title: LandingPageData().getLoggedInServiceCardsList[index].title, + subtitle: LandingPageData().getLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData().getLoggedInServiceCardsList[index].iconColor!, + textColor: LandingPageData().getLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData().getLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData().getLoggedInServiceCardsList[index].isBold, + serviceName: LandingPageData().getLoggedInServiceCardsList[index].serviceName, + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 10.width, + ).paddingSymmetrical(16.h, 0.h), + ), + ), + SizedBox(height: 16.h), + ], + ), + ).paddingSymmetrical(24.h, 0.h); + }), + ], + ) + : Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), child: Column( children: [ - todoSectionVM.patientAncillaryOrdersList.isNotEmpty - ? Container( - height: 50.h, - decoration: ShapeDecoration( - color: AppColors.eReferralCardColor.withAlpha(50), - shape: SmoothRectangleBorder( - borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), - smoothness: 1, - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.pendingAncillaryOrders.tr(context: context).toText14(color: AppColors.eReferralCardColor, isBold: true).paddingSymmetrical(24.h, 0.h), - CustomButton( - text: LocaleKeys.view.tr(context: context), - onPressed: () { - todoSectionVM.setIsAncillaryOrdersNeedReloading(true); - Navigator.of(context).push(CustomPageRoute(page: ToDoPage())); - }, - backgroundColor: AppColors.eReferralCardColor, - borderColor: AppColors.eReferralCardColor, - textColor: AppColors.whiteColor, - fontSize: 10.f, - fontWeight: FontWeight.w600, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(15, 0, 15, 0), - height: 30.h, - ).paddingSymmetrical(24.h, 0.h), - ], - ), - ) - : SizedBox.shrink(), SizedBox( height: 92.h + 32.h - 4.h, child: RawScrollbar( @@ -820,160 +926,112 @@ class _LandingPageState extends State { padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery.sizeOf(context).width / 2.5 - 10, right: MediaQuery.sizeOf(context).width / 2.5 - 10), child: ListView.separated( scrollDirection: Axis.horizontal, - itemCount: LandingPageData().getLoggedInServiceCardsList.length, + itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, shrinkWrap: true, controller: _horizontalScrollController, padding: EdgeInsets.only(left: 0.h, right: 0.h, top: 16.h, bottom: 12.h), itemBuilder: (context, index) { return AnimationConfiguration.staggeredList( position: index, - duration: const Duration(milliseconds: 200), + duration: const Duration(milliseconds: 1000), child: SlideAnimation( horizontalOffset: 100.0, child: FadeInAnimation( child: SmallServiceCard( - icon: LandingPageData().getLoggedInServiceCardsList[index].icon, - title: LandingPageData().getLoggedInServiceCardsList[index].title, - subtitle: LandingPageData().getLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData().getLoggedInServiceCardsList[index].iconColor!, - textColor: LandingPageData().getLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData().getLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData().getLoggedInServiceCardsList[index].isBold, - serviceName: LandingPageData().getLoggedInServiceCardsList[index].serviceName, + serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName, + icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, + title: LandingPageData.getNotLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor!, + textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, ), ), ), ); }, - separatorBuilder: (BuildContext cxt, int index) => 10.width, + separatorBuilder: (BuildContext cxt, int index) => 0.width, ).paddingSymmetrical(16.h, 0.h), ), ), SizedBox(height: 16.h), ], ), - ).paddingSymmetrical(24.h, 0.h); + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.services2.tr(context: context).toText18(isBold: true), + Row( + children: [ + LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + ], + ).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ServicesPage())); }), ], - ) - : Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), - child: Column( - children: [ - SizedBox( - height: 92.h + 32.h - 4.h, - child: RawScrollbar( - controller: _horizontalScrollController, - thumbVisibility: true, - radius: Radius.circular(10.0), - thumbColor: AppColors.primaryRedColor, - trackVisibility: true, - trackColor: Color(0xffD9D9D9), - trackBorderColor: Colors.transparent, - trackRadius: Radius.circular(10.0), - padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery.sizeOf(context).width / 2.5 - 10, right: MediaQuery.sizeOf(context).width / 2.5 - 10), - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, - shrinkWrap: true, - controller: _horizontalScrollController, - padding: EdgeInsets.only(left: 0.h, right: 0.h, top: 16.h, bottom: 12.h), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName, - icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, - title: LandingPageData.getNotLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor!, - textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, - ), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 0.width, - ).paddingSymmetrical(16.h, 0.h), + ).paddingSymmetrical(24.w, 0.h), + SizedBox(height: 16.h), + ConstrainedBox( + constraints: BoxConstraints(maxHeight: isFoldable ? 450.h : (isTablet ? 440.h : 431.h), minHeight: 411.h), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getServiceCardsList.length, + shrinkWrap: true, + padding: EdgeInsets.only(left: 24.w, right: 24.w), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: FadedLargeServiceCard( + serviceCardData: LandingPageData.getServiceCardsList[index], + image: LandingPageData.getServiceCardsList[index].icon, + title: LandingPageData.getServiceCardsList[index].title, + subtitle: LandingPageData.getServiceCardsList[index].subtitle, + icon: LandingPageData.getServiceCardsList[index].largeCardIcon, + isPNG: LandingPageData.getServiceCardsList[index].isPNG, + ), + ), ), - ), - SizedBox(height: 16.h), - ], - ), - ).paddingSymmetrical(24.h, 0.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.services2.tr(context: context).toText18(isBold: true), - Row( - children: [ - LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), - ], - ).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: ServicesPage())); - }), - ], - ).paddingSymmetrical(24.w, 0.h), - ConstrainedBox( - constraints: BoxConstraints(maxHeight: isFoldable ? 450.h : (isTablet ? 440.h : 431.h), minHeight: 411.h), - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getServiceCardsList.length, - shrinkWrap: true, - padding: EdgeInsets.only(left: 24.w, right: 24.w), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: FadedLargeServiceCard( - serviceCardData: LandingPageData.getServiceCardsList[index], - image: LandingPageData.getServiceCardsList[index].icon, - title: LandingPageData.getServiceCardsList[index].title, - subtitle: LandingPageData.getServiceCardsList[index].subtitle, - icon: LandingPageData.getServiceCardsList[index].largeCardIcon, - isPNG: LandingPageData.getServiceCardsList[index].isPNG, - ), - ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w), ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w), + ), + SizedBox(height: 16.h), + appState.isAuthenticated + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.myBalanceSubtitle.tr(context: context).toText16(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox( + height: 180.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: 2, + padding: EdgeInsetsDirectional.only(start: 24.h, end: 24.h), + itemBuilder: (BuildContext context, int index) { + return index == 0 ? HabibWalletCard() : LakumWalletCard(); + }, + separatorBuilder: (BuildContext context, int index) => SizedBox(width: 12.h), + ), + ), + ], + ) + : SizedBox(), + ], ), ), - appState.isAuthenticated - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.myBalanceSubtitle.tr(context: context).toText16(isBold: true).paddingSymmetrical(24.h, 0.h), - SizedBox( - height: 180.h, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: 2, - padding: EdgeInsetsDirectional.only(start: 24.h, end: 24.h), - itemBuilder: (BuildContext context, int index) { - return index == 0 ? HabibWalletCard() : LakumWalletCard(); - }, - separatorBuilder: (BuildContext context, int index) => SizedBox(width: 12.h), - ), - ), - ], - ) - : SizedBox(), - ], + ), ), - ), + ], ), AnimatedOpacity( opacity: _scrollOpacity, @@ -998,9 +1056,9 @@ class _LandingPageState extends State { ? Container( height: MediaQuery.paddingOf(context).top + 50.h, decoration: ShapeDecoration( - color: AppColors.secondaryLightRedBorderColor, + color: AppColors.secondaryLightRedColorBase, shape: SmoothRectangleBorder( - side: BorderSide(width: 1, color: AppColors.primaryRedColor.withAlpha(20)), + side: BorderSide(width: 1, color: AppColors.primaryRedColorBase.withAlpha(20)), // borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), smoothness: 1, ), @@ -1011,7 +1069,7 @@ class _LandingPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true).paddingSymmetrical(0.h, 0.h), + LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColorBase, isBold: true).paddingSymmetrical(0.h, 0.h), Row( children: [ CustomButton( @@ -1023,8 +1081,8 @@ class _LandingPageState extends State { showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.secondaryLightRedBorderColor, + backgroundColor: AppColors.primaryRedColorBase, + borderColor: AppColors.primaryRedColorBase, textColor: Colors.white, fontSize: 12.f, fontWeight: FontWeight.bold, @@ -1032,7 +1090,7 @@ class _LandingPageState extends State { padding: EdgeInsets.fromLTRB(15, 0, 15, 0), height: 36.h, ).paddingSymmetrical(12.h, 0.h), - Icon(Icons.close, color: AppColors.primaryRedColor).onPress(() { + Icon(Icons.close, color: AppColors.primaryRedColorBase).onPress(() { insuranceVM.setIsInsuranceExpiryBannerShown(false); }), ], diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index 803b9b5a..604ed5f5 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -92,7 +92,7 @@ class HabibWalletCard extends StatelessWidget { ), SizedBox(width: 8.h), (habibWalletVM.isWalletAmountHidden - ? "••••" + ? "****" : NumberFormat.decimalPattern().format(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/lakum_wallet_card.dart b/lib/presentation/home/widgets/lakum_wallet_card.dart index feea02ba..cddfd309 100644 --- a/lib/presentation/home/widgets/lakum_wallet_card.dart +++ b/lib/presentation/home/widgets/lakum_wallet_card.dart @@ -92,7 +92,7 @@ class LakumWalletCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ (habibWalletVM.isLakumPointsHidden - ? "••••" + ? "****" : NumberFormat.decimalPattern() .format(habibWalletVM.isLakumAccountInfoLoading ? 123 diff --git a/lib/presentation/home/widgets/occasion_banner_widget.dart b/lib/presentation/home/widgets/occasion_banner_widget.dart new file mode 100644 index 00000000..1f19b826 --- /dev/null +++ b/lib/presentation/home/widgets/occasion_banner_widget.dart @@ -0,0 +1,97 @@ +import 'dart:developer'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/occasion_theme/occasion_theme_view_model.dart'; +import 'package:provider/provider.dart'; + +/// Renders the occasion's main header/banner (`header_image`, e.g. the +/// National Day greeting banner) inline at the top of the landing page's +/// scrollable content, drawn at full opacity. +/// +/// Fully data-driven: when no occasion is active (the default), this widget +/// renders nothing and takes zero space — the existing layout/UI is +/// completely unaffected. +/// +/// See also [OccasionBackgroundWidget], which renders the decorative +/// backdrop image as a full-page background instead of inline content. +class OccasionBannerWidget extends StatelessWidget { + const OccasionBannerWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, occasionThemeVm, _) { + final headerImageUrl = occasionThemeVm.headerImage; + final hasHeaderImage = occasionThemeVm.isOccasionActive && (headerImageUrl?.isNotEmpty ?? false); + + if (!hasHeaderImage) { + return const SizedBox(height: 0); + } + + return buildOccasionImage(headerImageUrl!).paddingOnly(bottom: 16.h); + }, + ); + } +} + +/// Renders the occasion's decorative backdrop/watermark +/// (`background_opacity_image`) as a full-page background. Place this as +/// the first child of the landing page's outer [Stack] (behind the +/// scrollable content) so it fills the whole screen and sits behind +/// everything else, drawn at [OccasionThemeViewModel.backgroundImageOpacity]. +/// +/// Fully data-driven: when no occasion is active (the default), this widget +/// renders nothing — the existing layout/UI is completely unaffected. +class OccasionBackgroundWidget extends StatelessWidget { + const OccasionBackgroundWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, occasionThemeVm, _) { + final backgroundImageUrl = occasionThemeVm.backgroundOpacityImage; + final hasBackgroundImage = occasionThemeVm.isOccasionActive && (backgroundImageUrl?.isNotEmpty ?? false); + + if (!hasBackgroundImage) { + return const SizedBox.shrink(); + } + + return Positioned.fill( + child: Opacity( + opacity: occasionThemeVm.backgroundImageOpacity.clamp(0.0, 1.0), + child: buildOccasionImage(backgroundImageUrl!, isNeedPadding: true), + ), + ); + }, + ); + } +} + +/// Remote-config image URLs may point to either an SVG (e.g. the National +/// Day banner) or a raster image (PNG/JPG). [CachedNetworkImage] cannot +/// decode SVG — it silently renders nothing — so SVG URLs are routed to +/// [SvgPicture.network] instead, based on the URL's file extension. +Widget buildOccasionImage(String url, {bool isNeedPadding = false}) { + final isSvg = url.split('?').first.toLowerCase().endsWith('.svg'); + if (isSvg) { + return SvgPicture.network( + url, + width: double.infinity, + fit: BoxFit.contain, + placeholderBuilder: (_) => const SizedBox.shrink(), + ); + } + return CachedNetworkImage( + imageUrl: url, + width: double.infinity, + fit: BoxFit.contain, + placeholder: (_, __) => const SizedBox.shrink(), + errorWidget: (_, __, error) { + return const SizedBox.shrink(); + }, + ).paddingSymmetrical(isNeedPadding ? 24.w : 0, 0); +} diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 45023c3b..3c5ebbf7 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -38,7 +38,7 @@ class SmallServiceCard extends StatelessWidget { final Color? backgroundColor; final bool isBold; - SmallServiceCard({ + const SmallServiceCard({ super.key, this.icon = "", this.serviceName = "", diff --git a/lib/presentation/insurance/widgets/insurance_history.dart b/lib/presentation/insurance/widgets/insurance_history.dart index 0f5bde1e..f2019c91 100644 --- a/lib/presentation/insurance/widgets/insurance_history.dart +++ b/lib/presentation/insurance/widgets/insurance_history.dart @@ -69,8 +69,8 @@ class InsuranceHistory extends StatelessWidget { CustomButton( text: insuranceVM.patientInsuranceCardHistoryList[index].statusDescription ?? "", onPressed: () {}, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: Colors.transparent, textColor: AppColors.primaryRedColor, fontSize: 10, isBold: true, diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 6fab990c..87ae3a97 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -117,7 +117,7 @@ class PatientInsuranceCard extends StatelessWidget { } }, backgroundColor: isCurrentPatientInsuranceExpired(insuranceCardDetailsModel.cardValidTo!) - ? AppColors.primaryRedColor.withOpacity(0.1) + ? AppColors.secondaryLightRedColor : insuranceViewModel.isInsuranceActive ? AppColors.successColor.withOpacity(0.1) : AppColors.warningColorYellow.withOpacity(0.1), @@ -129,7 +129,7 @@ class PatientInsuranceCard extends StatelessWidget { // iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, // textColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, // iconSize: 12, - // backgroundColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), + // backgroundColor: isInsuranceExpired ? AppColors.primaryRedColorBase.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), // labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), // ), ], diff --git a/lib/presentation/lab/alphabeticScroll.dart b/lib/presentation/lab/alphabeticScroll.dart index ff0efbf4..a1a9add8 100644 --- a/lib/presentation/lab/alphabeticScroll.dart +++ b/lib/presentation/lab/alphabeticScroll.dart @@ -14,8 +14,8 @@ import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'lab_order_by_test.dart'; -class AlphabeticScroll extends StatefulWidget{ - final List alpahbetsAvailable; +class AlphabeticScroll extends StatefulWidget { + final List alpahbetsAvailable; final List details; final AppState appState; final LabViewModel labViewModel; @@ -38,13 +38,13 @@ class _AlphabetScrollPageState extends State { Map density = {}; Map _offsetMap = {}; - Map _offsetKeys = {}; + Map _offsetKeys = {}; int _activeIndex = 0; // <-- Highlighted letter @override void initState() { super.initState(); - scheduleMicrotask((){ + scheduleMicrotask(() { _rebuildData(); }); itemPositionsListener.itemPositions.addListener(_onPositionChanged); @@ -61,9 +61,7 @@ class _AlphabetScrollPageState extends State { void _rebuildData() { data.clear(); for (var char in widget.alpahbetsAvailable) { - data[char] = widget.details - .where((element) => element.description?.toLowerCase().startsWith(char.toLowerCase()) == true) - .toList(); + data[char] = widget.details.where((element) => element.description?.toLowerCase().startsWith(char.toLowerCase()) == true).toList(); } if (_activeIndex >= widget.alpahbetsAvailable.length) { _activeIndex = 0; @@ -79,15 +77,12 @@ class _AlphabetScrollPageState extends State { // Get FIRST visible item (top-most) final firstVisible = positions .where((p) => p.itemTrailingEdge > 0) // visible - .reduce((min, p) => - p.itemLeadingEdge < min.itemLeadingEdge ? p : min); + .reduce((min, p) => p.itemLeadingEdge < min.itemLeadingEdge ? p : min); - if(_activeIndex == firstVisible.index) return ; + if (_activeIndex == firstVisible.index) return; setState(() { _activeIndex = firstVisible.index; }); - - print("Active index = $_activeIndex"); } @override @@ -97,12 +92,8 @@ class _AlphabetScrollPageState extends State { } void _scrollToLetter(String letter) async { - // itemScrollController.jumpTo(index:density[letter]?.toInt()??0, ); - itemScrollController.scrollTo( - index: data.keys.toList().indexOf(letter).toInt()??0, - duration: Duration(seconds: 2), - curve: Curves.easeInOutCubic); + itemScrollController.scrollTo(index: data.keys.toList().indexOf(letter).toInt() ?? 0, duration: Duration(seconds: 2), curve: Curves.easeInOutCubic); // final key = _offsetKeys[letter]; // if (key == null) return; // @@ -134,58 +125,52 @@ class _AlphabetScrollPageState extends State { @override Widget build(BuildContext context) { return SizedBox( - width: MediaQuery.sizeOf(context).width, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, // Add this - children: [ - Expanded( - child: Column( - children: [ - SizedBox( - height: (MediaQuery.sizeOf(context).height), - child: _buildList(widget.alpahbetsAvailable) - ), - ], - ), + width: MediaQuery.sizeOf(context).width, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, // Add this + children: [ + Expanded( + child: Column( + children: [ + SizedBox(height: (MediaQuery.sizeOf(context).height), child: _buildList(widget.alpahbetsAvailable)), + ], ), - SizedBox( - width: 24.w, - height: MediaQuery.sizeOf(context).height-(70.h+ kToolbarHeight+100.h), - child: Column( - mainAxisSize: MainAxisSize.max, // Add this - mainAxisAlignment: MainAxisAlignment.center, // Changed from center to start - crossAxisAlignment: CrossAxisAlignment.center, - children:List.generate(widget.alpahbetsAvailable.length, (i) { - final isActive = (i == _activeIndex); - return GestureDetector( - onTap: () { - setState(() => _activeIndex = i); - _scrollToLetter(widget.alpahbetsAvailable[i]); - }, - child: TweenAnimationBuilder( - tween: Tween(begin: 1.0, end: isActive ? 1.8 : 1.0), - duration: Duration(milliseconds: 120), - curve: Curves.easeOut, - builder: (_, scale, child) { - return Transform.scale( - scale: scale, - child: Opacity( - opacity: isActive ? 1.0 : 0.5, - child: widget.alpahbetsAvailable[i].toUpperCase().toText14( - color: !isActive ? AppColors.greyTextColor : AppColors.primaryRedColor, isEnglishOnly: true - ), - ), - ); - }, - ), - ); - } + ), + SizedBox( + width: 24.w, + height: MediaQuery.sizeOf(context).height - (70.h + kToolbarHeight + 100.h), + child: Column( + mainAxisSize: MainAxisSize.max, // Add this + mainAxisAlignment: MainAxisAlignment.center, // Changed from center to start + crossAxisAlignment: CrossAxisAlignment.center, + children: List.generate(widget.alpahbetsAvailable.length, (i) { + final isActive = (i == _activeIndex); + return GestureDetector( + onTap: () { + setState(() => _activeIndex = i); + _scrollToLetter(widget.alpahbetsAvailable[i]); + }, + child: TweenAnimationBuilder( + tween: Tween(begin: 1.0, end: isActive ? 1.8 : 1.0), + duration: Duration(milliseconds: 120), + curve: Curves.easeOut, + builder: (_, scale, child) { + return Transform.scale( + scale: scale, + child: Opacity( + opacity: isActive ? 1.0 : 0.5, + child: widget.alpahbetsAvailable[i].toUpperCase().toText14(color: !isActive ? AppColors.greyTextColor : AppColors.primaryRedColor, isEnglishOnly: true), + ), + ); + }, ), - ), - ), - ], - ), - ); + ); + }), + ), + ), + ], + ), + ); // ); } @@ -205,35 +190,35 @@ class _AlphabetScrollPageState extends State { key: _offsetKeys[letter], child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: - List.generate(items.length,(itemIndex)=> - AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: LabOrderByTest( - appState: getIt(), - onTap: () { - if (items[itemIndex].model != null) { - widget.rangeViewModel.flush(); - widget.labViewModel.getPatientLabResult(items[itemIndex].model!, items[itemIndex].description!, - (widget.appState.isArabic() ? items[itemIndex].testDescriptionAr! : items[itemIndex].testDescriptionEn!),""); - } - }, - tests: items[itemIndex], - index: itemIndex, - isExpanded: true)), - ), - )) + children: List.generate( + items.length, + (itemIndex) => AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: LabOrderByTest( + appState: getIt(), + onTap: () { + if (items[itemIndex].model != null) { + widget.rangeViewModel.flush(); + widget.labViewModel.getPatientLabResult(items[itemIndex].model!, items[itemIndex].description!, + (widget.appState.isArabic() ? items[itemIndex].testDescriptionAr! : items[itemIndex].testDescriptionEn!), ""); + } + }, + tests: items[itemIndex], + index: itemIndex, + isExpanded: true)), + ), + )) // ...items.indexed((item) => // // // // ) - ), + ), ); }, ); diff --git a/lib/presentation/lab/lab_order_by_test.dart b/lib/presentation/lab/lab_order_by_test.dart index 7740a596..733fb792 100644 --- a/lib/presentation/lab/lab_order_by_test.dart +++ b/lib/presentation/lab/lab_order_by_test.dart @@ -26,64 +26,65 @@ class LabOrderByTest extends StatelessWidget { @override build(BuildContext context) { return AnimatedContainer( - key:key, - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), - child: Container( - key: ValueKey(index), - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - '${tests!.description}'.toText16(isBold: true, isEnglishOnly: true), - SizedBox(height: 4.h), - (appState.isArabic() ? tests!.testDescriptionAr : tests!.testDescriptionEn)!.toText12(isBold: true), - SizedBox(height: 8.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - AppCustomChipWidget( - richText: Row( - children: [ - "${LocaleKeys.lastTested.tr(context: context)}: ".toText12(isBold: true), - Directionality( - textDirection: ui.TextDirection.ltr,child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false).toText12(isBold: true, isEnglishOnly: true)) - ], - ), - backgroundColor: AppColors.greyLightColor, - textColor: AppColors.textColor, + key: key, + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: Container( + key: ValueKey(index), + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + '${tests!.description}'.toText16(isBold: true, isEnglishOnly: true), + SizedBox(height: 4.h), + (appState.isArabic() ? tests!.testDescriptionAr : tests!.testDescriptionEn)!.toText12(isBold: true), + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + richText: Row( + children: [ + "${LocaleKeys.lastTested.tr(context: context)}: ".toText12(isBold: true), + Directionality( + textDirection: ui.TextDirection.ltr, child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false).toText12(isBold: true, isEnglishOnly: true)) + ], ), - ], - ), - SizedBox(height: 16.h), - Row( - children: [ - Expanded(child: Container()), - Expanded( - child: CustomButton( - icon: AppAssets.view_report_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - text: LocaleKeys.viewResults.tr(context: context), - onPressed: () { - onTap(); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), + backgroundColor: AppColors.greyLightColor, + textColor: AppColors.textColor, + ), + ], + ), + SizedBox(height: 16.h), + Row( + children: [ + Expanded(child: Container()), + Expanded( + child: CustomButton( + icon: AppAssets.view_report_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + text: LocaleKeys.viewResults.tr(context: context), + onPressed: () { + onTap(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: Colors.transparent, + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, ), - ], - ), - ], - ), - )); + ), + ], + ), + ], + ), + ), + ); } } diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 0e8890ba..332a89da 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'dart:ui' as ui; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.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'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'alphabeticScroll.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return CollapsingListView( title: LocaleKeys.labResults.tr(context: context), physics: activeIndex == 1 ? NeverScrollableScrollPhysics() : ScrollPhysics(), search: () async { if (labProvider.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: labProvider.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; labProvider.filterLabReports(value); } } }, child: Consumer( builder: (context, labViewModel, child) { return SingleChildScrollView( physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.all(24.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, LocaleKeys.byVisit.tr()), CustomTabBarModel(null, LocaleKeys.byTest.tr()), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), ), ], ), if (activeIndex == 0) Padding( padding: EdgeInsets.symmetric(vertical: 10.h), child: Row( children: [ // CustomButton( // text: LocaleKeys.byClinic.tr(context: context), // onPressed: () { // labViewModel.setIsSortByClinic(true); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, // borderColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), // textColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, // fontSize: 12.f, // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), // SizedBox(width: 8.h), // CustomButton( // text: LocaleKeys.byHospital.tr(context: context), // onPressed: () { // labViewModel.setIsSortByClinic(false); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, // borderColor: labViewModel.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, // textColor: labViewModel.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, // fontSize: 12, // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? Column( children: [ AppCustomChipWidget( labelText: selectedFilterText!, backgroundColor: AppColors.alertColor, textColor: AppColors.whiteColor, deleteIcon: AppAssets.close_bottom_sheet_icon, deleteIconColor: AppColors.whiteColor, deleteIconHasColor: true, isEnglishOnly: true, onDeleteTap: () { selectedFilterText = ""; labProvider.filterLabReports(""); }, ), SizedBox(height: 8.h), ], ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available labViewModel.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (labViewModel.patientLabOrders.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: labViewModel.filteredLabOrders.length, itemBuilder: (context, index) { final group = labViewModel.filteredLabOrders[index]; final isExpanded = expandedIndex == index; return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), decoration: RoundedRectangleBorder() .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), child: InkWell( onTap: () { setState(() { expandedIndex = isExpanded ? null : index; }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( richText: Row( children: [ "${group.testDetails!.length} ".toText10( color: AppColors.successColor, isEnglishOnly: true, weight: FontWeight.w600), LocaleKeys.tests .tr(context: context) .toText10(color: AppColors.successColor, weight: FontWeight.w600), ], ), labelText: "${group.testDetails!.length} ${LocaleKeys.tests.tr(context: context)}", backgroundColor: AppColors.successColor.withOpacity(0.1), textColor: AppColors.successColor, ), AppCustomChipWidget( labelText: "${_appState.isArabic() ? group.isInOutPatientDescriptionN : group.isInOutPatientDescription}", backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), textColor: AppColors.warningColorYellow, ) ], ), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( group.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 24.w, height: 24.h, fit: BoxFit.cover, ).circle(100), SizedBox(width: 8.h), Expanded( child: (group.doctorName ?? group.doctorNameEnglish ?? "") .toString() .toText14(isBold: true)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ Directionality( textDirection: ui.TextDirection.ltr, child: AppCustomChipWidget( labelText: DateUtil.formatDateToDate( DateUtil.convertStringToDate(group.orderDate ?? ""), false), isEnglishOnly: true, )), AppCustomChipWidget( labelText: (group.projectName ?? ""), ), AppCustomChipWidget( labelText: (group.clinicDescription ?? ""), ), ], ), ], ), ), AnimatedSwitcher( duration: Duration(milliseconds: 500), switchInCurve: Curves.easeIn, switchOutCurve: Curves.easeOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: animation, child: SizeTransition( sizeFactor: animation, axisAlignment: 0.0, child: child, ), ); }, child: isExpanded ? Container( key: ValueKey(index), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 0.h), child: Column( children: [ ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ "• ${order.testDetails![index].description!}" .toText14(isBold: true, isEnglishOnly: true), SizedBox(height: 4.h), (_appState.isArabic() ? order.testDetails![index].testDescriptionAr! : order.testDetails![index].testDescriptionEn!).toText12( isBold: true, color: AppColors.textColorLight, isEnglishOnly: true), ], ).paddingOnly(top: 16, bottom: 16); }, separatorBuilder: (cxt, index) => Divider( color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), itemCount: group.testDetails!.length > 3 ? 3 : group.testDetails!.length), SizedBox(height: 16.h), CustomButton( text: "${LocaleKeys.viewResults.tr()} (${group.testDetails!.length})", onPressed: () { labProvider.currentlySelectedPatientOrder = group; labProvider.getPatientLabResultByHospital(group); labProvider.getPatientSpecialResult(group); Navigator.of(context).push( CustomPageRoute( page: LabResultByClinic(labOrder: group), ), ); }, backgroundColor: AppColors.infoColor.withAlpha(20), borderColor: AppColors.infoColor.withAlpha(0), textColor: AppColors.infoColor, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, isBold: true, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: 40.h, iconSize: 14.h, icon: AppAssets.view_report_icon, iconColor: AppColors.infoColor, ), SizedBox(height: 16.h), ], ), ) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (labViewModel.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: labViewModel.filteredIndexedCharacterForUniqueTest, details: labViewModel.filteredUniqueTestsList, labViewModel: labViewModel, rangeViewModel: rangeViewModel, appState: _appState, ) ], )); }, ), ); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'dart:ui' as ui; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.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'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'alphabeticScroll.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return CollapsingListView( title: LocaleKeys.labResults.tr(context: context), physics: activeIndex == 1 ? NeverScrollableScrollPhysics() : ScrollPhysics(), search: () async { if (labProvider.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: labProvider.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; labProvider.filterLabReports(value); } } }, child: Consumer( builder: (context, labViewModel, child) { return SingleChildScrollView( physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.all(24.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: CustomTabBar( activeTextColor: AppColors.primaryRedColor, activeBackgroundColor: AppColors.secondaryLightRedColor, tabs: [ CustomTabBarModel(null, LocaleKeys.byVisit.tr()), CustomTabBarModel(null, LocaleKeys.byTest.tr()), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), ), ], ), if (activeIndex == 0) Padding( padding: EdgeInsets.symmetric(vertical: 10.h), child: Row( children: [ // CustomButton( // text: LocaleKeys.byClinic.tr(context: context), // onPressed: () { // labViewModel.setIsSortByClinic(true); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, // borderColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), // textColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, // fontSize: 12.f, // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), // SizedBox(width: 8.h), // CustomButton( // text: LocaleKeys.byHospital.tr(context: context), // onPressed: () { // labViewModel.setIsSortByClinic(false); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, // borderColor: labViewModel.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, // textColor: labViewModel.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, // fontSize: 12, // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? Column( children: [ AppCustomChipWidget( labelText: selectedFilterText!, backgroundColor: AppColors.alertColor, textColor: AppColors.whiteColor, deleteIcon: AppAssets.close_bottom_sheet_icon, deleteIconColor: AppColors.whiteColor, deleteIconHasColor: true, isEnglishOnly: true, onDeleteTap: () { selectedFilterText = ""; labProvider.filterLabReports(""); }, ), SizedBox(height: 8.h), ], ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available labViewModel.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (labViewModel.patientLabOrders.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: labViewModel.filteredLabOrders.length, itemBuilder: (context, index) { final group = labViewModel.filteredLabOrders[index]; final isExpanded = expandedIndex == index; return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), child: InkWell( onTap: () { setState(() { expandedIndex = isExpanded ? null : index; }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( richText: Row( children: [ "${group.testDetails!.length} ".toText10(color: AppColors.successColor, isEnglishOnly: true, weight: FontWeight.w600), LocaleKeys.tests.tr(context: context).toText10(color: AppColors.successColor, weight: FontWeight.w600), ], ), labelText: "${group.testDetails!.length} ${LocaleKeys.tests.tr(context: context)}", backgroundColor: AppColors.successColor.withOpacity(0.1), textColor: AppColors.successColor, ), AppCustomChipWidget( labelText: "${_appState.isArabic() ? group.isInOutPatientDescriptionN : group.isInOutPatientDescription}", backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), textColor: AppColors.warningColorYellow, ) ], ), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( group.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 24.w, height: 24.h, fit: BoxFit.cover, ).circle(100), SizedBox(width: 8.h), Expanded(child: (group.doctorName ?? group.doctorNameEnglish ?? "").toString().toText14(isBold: true)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ Directionality( textDirection: ui.TextDirection.ltr, child: AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(group.orderDate ?? ""), false), isEnglishOnly: true, )), AppCustomChipWidget( labelText: (group.projectName ?? ""), ), AppCustomChipWidget( labelText: (group.clinicDescription ?? ""), ), ], ), ], ), ), AnimatedSwitcher( duration: Duration(milliseconds: 500), switchInCurve: Curves.easeIn, switchOutCurve: Curves.easeOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: animation, child: SizeTransition( sizeFactor: animation, axisAlignment: 0.0, child: child, ), ); }, child: isExpanded ? Container( key: ValueKey(index), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 0.h), child: Column( children: [ ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ "• ${order.testDetails![index].description!}".toText14(isBold: true, isEnglishOnly: true), SizedBox(height: 4.h), (_appState.isArabic() ? order.testDetails![index].testDescriptionAr! : order.testDetails![index].testDescriptionEn!) .toText12(isBold: true, color: AppColors.textColorLight, isEnglishOnly: true), ], ).paddingOnly(top: 16, bottom: 16); }, separatorBuilder: (cxt, index) => Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), itemCount: group.testDetails!.length > 3 ? 3 : group.testDetails!.length), SizedBox(height: 16.h), CustomButton( text: "${LocaleKeys.viewResults.tr()} (${group.testDetails!.length})", onPressed: () { labProvider.currentlySelectedPatientOrder = group; labProvider.getPatientLabResultByHospital(group); labProvider.getPatientSpecialResult(group); Navigator.of(context).push( CustomPageRoute( page: LabResultByClinic(labOrder: group), ), ); }, backgroundColor: AppColors.infoColor.withAlpha(20), borderColor: AppColors.infoColor.withAlpha(0), textColor: AppColors.infoColor, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, isBold: true, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: 40.h, iconSize: 14.h, icon: AppAssets.view_report_icon, iconColor: AppColors.infoColor, ), SizedBox(height: 16.h), ], ), ) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (labViewModel.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: labViewModel.filteredIndexedCharacterForUniqueTest, details: labViewModel.filteredUniqueTestsList, labViewModel: labViewModel, rangeViewModel: rangeViewModel, appState: _appState, ) ], )); }, ), ); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 27978723..add423d8 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -280,7 +280,7 @@ class _MedicalFilePageState extends State { AppCustomChipWidget( icon: AppAssets.blood_icon, labelText: appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup, - iconColor: AppColors.primaryRedColor, + iconColor: AppColors.primaryRedColorBase, labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), padding: EdgeInsets.zero, ), @@ -340,7 +340,7 @@ class _MedicalFilePageState extends State { } }, backgroundColor: insuranceVM.isInsuranceExpired - ? AppColors.primaryRedColor.withOpacity(0.1) + ? AppColors.secondaryLightRedColor : insuranceVM.isInsuranceActive ? AppColors.successColor.withOpacity(0.1) : AppColors.warningColorYellow.withOpacity(0.1), diff --git a/lib/presentation/medical_file/widgets/medical_report_card.dart b/lib/presentation/medical_file/widgets/medical_report_card.dart index 1a43b83b..905823b4 100644 --- a/lib/presentation/medical_file/widgets/medical_report_card.dart +++ b/lib/presentation/medical_file/widgets/medical_report_card.dart @@ -84,15 +84,15 @@ class MedicalReportCard extends StatelessWidget { ), ); }, - backgroundColor: AppColors.lightRedButtonColor, - borderColor: AppColors.lightRedButtonColor, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, iconColor: AppColors.primaryRedColor, fontSize: 14.f, - // fontWeight: FontWeight.w600, isBold: true, borderRadius: 12.r, height: 40.h, + iconSize: 18.f, ).toShimmer2(isShow: isLoading), ], ), diff --git a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart index f06e6a0e..2bb879b5 100644 --- a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart +++ b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart @@ -122,7 +122,7 @@ class PatientSickLeaveCard extends StatelessWidget { }); }, backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.transparent, textColor: AppColors.primaryRedColor, fontSize: 14.f, isBold: true, diff --git a/lib/presentation/medical_report/medical_report_request_page.dart b/lib/presentation/medical_report/medical_report_request_page.dart index 47a2df32..859a6b2f 100644 --- a/lib/presentation/medical_report/medical_report_request_page.dart +++ b/lib/presentation/medical_report/medical_report_request_page.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/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/book_appointments/book_appointments_view_model.dart'; @@ -26,8 +27,9 @@ class MedicalReportRequestPage extends StatelessWidget { isClose: true, child: Column( children: [ - ListView.separated( - padding: EdgeInsets.only(top: 24.h), + medicalFileViewModel.patientMedicalReportAppointmentHistoryList.isNotEmpty + ? ListView.separated( + padding: EdgeInsets.only(top: 24.h), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemCount: medicalFileViewModel.patientMedicalReportAppointmentHistoryList.length, @@ -57,7 +59,8 @@ class MedicalReportRequestPage extends StatelessWidget { ); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ), + ) + : Utils.getNoDataWidget(context), SizedBox(height: 24.h), ], ), diff --git a/lib/presentation/medical_report/medical_reports_page.dart b/lib/presentation/medical_report/medical_reports_page.dart index e1ac3969..33f9e6e3 100644 --- a/lib/presentation/medical_report/medical_reports_page.dart +++ b/lib/presentation/medical_report/medical_reports_page.dart @@ -50,42 +50,7 @@ class _MedicalReportsPageState extends State { child: Consumer(builder: (context, medicalFileVM, child) { return Column( children: [ - SizedBox(height: 16.h), - // Status filter tabs - // Row( - // children: [ - // CustomButton( - // text: LocaleKeys.byClinic.tr(context: context), - // onPressed: () { - // medicalFileVM.setIsMedicalReportsSortByClinic(true); - // }, - // backgroundColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, - // borderColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), - // textColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, - // fontSize: 12, - // isBold: true, - // borderRadius: 10, - // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - // height: 40.h, - // ), - // SizedBox(width: 8.h), - // CustomButton( - // text: LocaleKeys.byHospital.tr(context: context), - // onPressed: () { - // medicalFileVM.setIsMedicalReportsSortByClinic(false); - // }, - // backgroundColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, - // borderColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, - // textColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, - // fontSize: 12, - // isBold: true, - // borderRadius: 10, - // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - // height: 40.h, - // ), - // ], - // ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 8.h), + SizedBox(height: 24.h), Row( children: [ CustomButton( @@ -404,6 +369,7 @@ class _MedicalReportsPageState extends State { showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.yourMedicalReportRequestSubmittedSuccessfully.tr(context: context)), callBackFunc: () async{ + medicalFileViewModel.setIsMedicalReportsDataNeedsReloading(true); medicalFileViewModel.setIsPatientMedicalReportsLoading(true); medicalFileViewModel.onMedicalReportTabChange(0); medicalFileViewModel.getPatientMedicalReportList(); diff --git a/lib/presentation/my_family/my_family.dart b/lib/presentation/my_family/my_family.dart index 4b6f820a..76f318f7 100644 --- a/lib/presentation/my_family/my_family.dart +++ b/lib/presentation/my_family/my_family.dart @@ -34,14 +34,13 @@ class _FamilyMedicalScreenState extends State { super.initState(); medicalVM = context.read(); WidgetsBinding.instance.addPostFrameCallback((_) async { - if(medicalVM!.isFamilyFilesLoading) { + if (medicalVM!.isFamilyFilesLoading) { LoaderBottomSheet.showLoader(); } else { LoaderBottomSheet.hideLoader(); } medicalVM?.onFamilyFileTabChange(0); medicalVM?.getAllPendingRecordsByResponseId(); - }); } @@ -71,6 +70,8 @@ class _FamilyMedicalScreenState extends State { medicalVM!.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); }); }, + borderColor: AppColors.primaryRedColor, + backgroundColor: AppColors.primaryRedColor, icon: AppAssets.add_icon, fontSize: 16.f, borderRadius: 12.r, @@ -104,9 +105,7 @@ class _FamilyMedicalScreenState extends State { ), shouldRebuild: (previous, next) { // Only rebuild if something actually changed - return previous.selectedIndex != next.selectedIndex || - !identical(previous.patientFiles, next.patientFiles) || - !identical(previous.pendingFiles, next.pendingFiles); + return previous.selectedIndex != next.selectedIndex || !identical(previous.patientFiles, next.patientFiles) || !identical(previous.pendingFiles, next.pendingFiles); }, builder: (context, data, child) => getFamilyTabs( index: data.selectedIndex, @@ -127,7 +126,6 @@ class _FamilyMedicalScreenState extends State { }) { switch (index) { case 0: - return FamilyCards( profiles: patientFiles, onSelect: (FamilyFileResponseModelLists profile) { diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 8feb8434..362aada0 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -165,67 +165,63 @@ class _FamilyCardsState extends State { title: LocaleKeys.whoCanViewMyMedicalFile.tr(context: context).toText18(isBold: true), expandedBackgroundColor: Colors.transparent, initiallyExpanded: true, - children: [ - SizedBox(height: 10.h), - manageFamily() - ], + children: [SizedBox(height: 10.h), manageFamily()], ), ExpandableListItem(title: LocaleKeys.notifications.tr(context: context).toText18(isBold: true), expandedBackgroundColor: Colors.transparent, initiallyExpanded: true, children: [ SizedBox(height: 10.h), widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).isEmpty ? Utils.getNoDataWidget(context) : ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - itemCount: widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).length, - itemBuilder: (context, index) { - final mySideProfiles = widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).toList(); - FamilyFileResponseModelLists profile = mySideProfiles[index]; - return Container( - margin: EdgeInsets.only(bottom: 12.h), - padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), - child: Opacity( - opacity: 1.0, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - AppCustomChipWidget( - labelText: profile.statusDescription ?? " N/A", - backgroundColor: profile.status == FamilyFileEnum.pending.toInt - ? AppColors.alertLightColor.withValues(alpha: 0.20) - : profile.status == FamilyFileEnum.rejected.toInt - ? AppColors.primaryRedColor.withValues(alpha: 0.20) - : profile.status == FamilyFileEnum.active.toInt - ? AppColors.lightGreenColor - : AppColors.lightGrayBGColor, - textColor: profile.status == FamilyFileEnum.pending.toInt - ? AppColors.alertLightColor - : profile.status == FamilyFileEnum.rejected.toInt - ? AppColors.primaryRedColor - : profile.status == FamilyFileEnum.active.toInt - ? AppColors.textGreenColor - : AppColors.alertColor - ), - SizedBox(height: 8.h), - Wrap(alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, runAlignment: WrapAlignment.start, spacing: 0.h, children: [ - (profile.patientName ?? "").toText14(isBold: true, isCenter: false, maxlines: 1), - (getStatusTextByRequest(FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)) - .toText12(isCenter: false, maxLine: 1, isBold: true, color: AppColors.greyTextColor), - ]), - SizedBox(height: 8.h), - AppCustomChipWidget( - labelText: "Medical File: ${profile.responseId ?? "N/A"}", + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).length, + itemBuilder: (context, index) { + final mySideProfiles = widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).toList(); + FamilyFileResponseModelLists profile = mySideProfiles[index]; + return Container( + margin: EdgeInsets.only(bottom: 12.h), + padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), + child: Opacity( + opacity: 1.0, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + AppCustomChipWidget( + labelText: profile.statusDescription ?? " N/A", + backgroundColor: profile.status == FamilyFileEnum.pending.toInt + ? AppColors.alertLightColor.withValues(alpha: 0.20) + : profile.status == FamilyFileEnum.rejected.toInt + ? AppColors.primaryRedColor.withValues(alpha: 0.20) + : profile.status == FamilyFileEnum.active.toInt + ? AppColors.lightGreenColor + : AppColors.lightGrayBGColor, + textColor: profile.status == FamilyFileEnum.pending.toInt + ? AppColors.alertLightColor + : profile.status == FamilyFileEnum.rejected.toInt + ? AppColors.primaryRedColor + : profile.status == FamilyFileEnum.active.toInt + ? AppColors.textGreenColor + : AppColors.alertColor), + SizedBox(height: 8.h), + Wrap(alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, runAlignment: WrapAlignment.start, spacing: 0.h, children: [ + (profile.patientName ?? "").toText14(isBold: true, isCenter: false, maxlines: 1), + (getStatusTextByRequest(FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)) + .toText12(isCenter: false, maxLine: 1, isBold: true, color: AppColors.greyTextColor), + ]), + SizedBox(height: 8.h), + AppCustomChipWidget( + labelText: "Medical File: ${profile.responseId ?? "N/A"}", + ), + ], + ), ), - ], - ), + ); + }, ), - ); - }, - ), ]) ], theme: ExpandableListTheme.custom( @@ -463,7 +459,7 @@ class _FamilyCardsState extends State { isCircular: true, isFamilyMember: true, // Prevent AppState fallback ), - SizedBox(height: 4.h), + SizedBox(height: 3.h), (profile.patientName ?? "Unknown").toText14(isBold: true, isCenter: true, maxlines: 1), // SizedBox(height: 8.h), // CustomChipWidget( @@ -479,7 +475,7 @@ class _FamilyCardsState extends State { ? Wrap(direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( icon: AppAssets.file_icon, - richText: "${LocaleKeys.fileno.tr(context: context)}: ${profile.responseId}".toText10(isBold: true), + richText: "${profile.responseId}".toText10(isBold: true), labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), ), AppCustomChipWidget( @@ -493,7 +489,7 @@ class _FamilyCardsState extends State { : SizedBox( height: 4.h, ), - Spacer(), + SizedBox(height: 8.h), widget.isForWalletRecharge ? CustomButton( height: 40.h, @@ -517,7 +513,7 @@ class _FamilyCardsState extends State { }, text: isActive ? LocaleKeys.active.tr(context: context) : LocaleKeys.switchLogin.tr(context: context), backgroundColor: isActive || !canSwitch ? Colors.grey.shade200 : AppColors.secondaryLightRedColor, - borderColor: isActive || !canSwitch ? Colors.grey.shade200 : AppColors.secondaryLightRedColor, + borderColor: Colors.transparent, textColor: isActive || !canSwitch ? AppColors.greyTextColor : AppColors.primaryRedColor, fontSize: 13.h, icon: isActive ? AppAssets.activeCheck : AppAssets.switch_user, @@ -527,13 +523,15 @@ class _FamilyCardsState extends State { ], ), if (widget.isShowRemoveButton) ...[ - isParentUser ? Positioned( - top: 0, - right: 0, - child: Utils.buildSvgWithAssets(icon: AppAssets.deleteIcon).onPress(() { - if (!isActive) widget.onRemove(profile); - }), - ) : SizedBox(), + isParentUser + ? Positioned( + top: 0, + right: 0, + child: Utils.buildSvgWithAssets(icon: AppAssets.deleteIcon).onPress(() { + if (!isActive) widget.onRemove(profile); + }), + ) + : SizedBox(), ], ], ), diff --git a/lib/presentation/my_invoices/my_invoices_list.dart b/lib/presentation/my_invoices/my_invoices_list.dart index 67ee05a6..95ff3e16 100644 --- a/lib/presentation/my_invoices/my_invoices_list.dart +++ b/lib/presentation/my_invoices/my_invoices_list.dart @@ -122,8 +122,8 @@ class _MyInvoicesListState extends State { children: [ SizedBox(height: 16.h), CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.secondaryLightRedColor, initialIndex: 0, tabs: [ CustomTabBarModel(null, LocaleKeys.visitsOrders.tr(context: context)), @@ -149,13 +149,11 @@ class _MyInvoicesListState extends State { myInvoicesViewModel.filterInvoices(InvoiceFilterType.all); }, backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.all - ? AppColors.primaryRedColor - : AppColors.textColor.withValues(alpha: 0.2), + borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, + fontSize: 12.f, fontWeight: FontWeight.w600, - borderRadius: 10, + borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), @@ -166,15 +164,12 @@ class _MyInvoicesListState extends State { if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return; _showHospitalFilterBottomSheet(context); }, - backgroundColor: - myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital - ? AppColors.primaryRedColor - : AppColors.textColor.withValues(alpha: 0.2), + backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, + fontSize: 12.f, fontWeight: FontWeight.w600, - borderRadius: 10, + borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), @@ -185,15 +180,12 @@ class _MyInvoicesListState extends State { if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return; _showClinicFilterBottomSheet(context); }, - backgroundColor: - myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic - ? AppColors.primaryRedColor - : AppColors.textColor.withValues(alpha: 0.2), + backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, + fontSize: 12.f, fontWeight: FontWeight.w600, - borderRadius: 10, + borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), @@ -204,15 +196,12 @@ class _MyInvoicesListState extends State { if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return; _showDoctorFilterBottomSheet(context); }, - backgroundColor: - myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor - ? AppColors.primaryRedColor - : AppColors.textColor.withValues(alpha: 0.2), + backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, + fontSize: 12.f, fontWeight: FontWeight.w600, - borderRadius: 10, + borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), @@ -245,16 +234,14 @@ class _MyInvoicesListState extends State { getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], onTap: () async { if (Utils.isVidaPlusProject(myInvoicesVM.allInvoicesList[index].projectId ?? 0)) { - LoaderBottomSheet.showLoader( - loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context)); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context)); await myInvoicesViewModel.sendInvoiceEmail( appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, projectID: myInvoicesVM.allInvoicesList[index].projectId!, onSuccess: (val) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, - child: Utils.getSuccessWidget( - loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)), + child: Utils.getSuccessWidget(loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -289,8 +276,7 @@ class _MyInvoicesListState extends State { onSuccess: (value) async { LoaderBottomSheet.hideLoader(); if (myInvoicesViewModel.downloadInvoicePDFBase64!.isNotEmpty) { - String path = await Utils.createFileFromString( - myInvoicesViewModel.downloadInvoicePDFBase64!, "pdf"); + String path = await Utils.createFileFromString(myInvoicesViewModel.downloadInvoicePDFBase64!, "pdf"); try { OpenFilex.open(path); } catch (ex) { @@ -377,8 +363,7 @@ class _MyInvoicesListState extends State { onSuccess: (value) async { LoaderBottomSheet.hideLoader(); if (myInvoicesViewModel.downloadPharmacyInvoicePDFBase64!.isNotEmpty) { - String path = await Utils.createFileFromString( - myInvoicesViewModel.downloadPharmacyInvoicePDFBase64!, "pdf"); + String path = await Utils.createFileFromString(myInvoicesViewModel.downloadPharmacyInvoicePDFBase64!, "pdf"); try { OpenFilex.open(path); } catch (ex) { diff --git a/lib/presentation/notifications/notifications_list_page.dart b/lib/presentation/notifications/notifications_list_page.dart index 77f9a20a..eb1af696 100644 --- a/lib/presentation/notifications/notifications_list_page.dart +++ b/lib/presentation/notifications/notifications_list_page.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/notifications/notification_details_page.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'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; import 'dart:ui' as ui; @@ -51,6 +52,54 @@ class _NotificationsListPageState extends State { child: Consumer(builder: (context, notificationsVM, child) { return Column( children: [ + Row( + children: [ + CustomButton( + text: LocaleKeys.all.tr(context: context), + onPressed: () { + notificationsVM.onNotificationTabChange(0); + }, + backgroundColor: notificationsVM.selectedNotificationsTabIndex == 0 ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: notificationsVM.selectedNotificationsTabIndex == 0 ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), + textColor: notificationsVM.selectedNotificationsTabIndex == 0 ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + isBold: true, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.unread.tr(context: context), + onPressed: () { + notificationsVM.onNotificationTabChange(1); + }, + backgroundColor: notificationsVM.selectedNotificationsTabIndex == 1 ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: notificationsVM.selectedNotificationsTabIndex == 1 ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), + textColor: notificationsVM.selectedNotificationsTabIndex == 1 ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + isBold: true, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.read.tr(context: context), + onPressed: () { + notificationsVM.onNotificationTabChange(2); + }, + backgroundColor: notificationsVM.selectedNotificationsTabIndex == 2 ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: notificationsVM.selectedNotificationsTabIndex == 2 ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), + textColor: notificationsVM.selectedNotificationsTabIndex == 2 ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + isBold: true, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(24.h, 0.h), Container( margin: EdgeInsets.symmetric(vertical: 24.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( diff --git a/lib/presentation/onboarding/onboarding_screen.dart b/lib/presentation/onboarding/onboarding_screen.dart index 0af63b40..1c5b64d8 100644 --- a/lib/presentation/onboarding/onboarding_screen.dart +++ b/lib/presentation/onboarding/onboarding_screen.dart @@ -111,7 +111,7 @@ class _OnboardingScreenState extends State { onPressed: () => goToHomePage(), width: 86.w, height: 56.h, - backgroundColor: Color(0xffFEE9EA), + backgroundColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, borderColor: Colors.transparent, ).paddingOnly(left: 24.w) diff --git a/lib/presentation/onboarding/splash_animation_screen.dart b/lib/presentation/onboarding/splash_animation_screen.dart index 105922bb..0fdd86a8 100644 --- a/lib/presentation/onboarding/splash_animation_screen.dart +++ b/lib/presentation/onboarding/splash_animation_screen.dart @@ -164,7 +164,7 @@ class _AnimatedScreenState extends State with TickerProviderStat width: 400.h, height: 653.h, decoration: BoxDecoration( - color: Color(0xffED1C2B), + color: AppColors.primaryRedColor, borderRadius: BorderRadius.circular(330.h), ), ), @@ -273,7 +273,7 @@ class _MoveObjectDemoState extends State with SingleTickerProvid width: 200, height: 375, decoration: BoxDecoration( - color: const Color(0xffED1C2B), + color: AppColors.primaryRedColor, borderRadius: BorderRadius.circular(330), ), ), @@ -367,7 +367,7 @@ class _MoveOnClickDemoState extends State with TickerProviderSt width: 100, height: 150, decoration: BoxDecoration( - color: const Color(0xffED1C2B), + color: AppColors.primaryRedColor, borderRadius: BorderRadius.circular(330), ), ), diff --git a/lib/presentation/prescriptions/prescription_item_view.dart b/lib/presentation/prescriptions/prescription_item_view.dart index b0e11af0..59b10620 100644 --- a/lib/presentation/prescriptions/prescription_item_view.dart +++ b/lib/presentation/prescriptions/prescription_item_view.dart @@ -177,8 +177,8 @@ class PrescriptionItemView extends StatelessWidget { // child: CustomButton( // text: LocaleKeys.checkAvailability.tr(context: context), // onPressed: () {}, - // backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - // borderColor: AppColors.primaryRedColor.withOpacity(0.0), + // backgroundColor: AppColors.primaryRedColorBase.withOpacity(0.1), + // borderColor: AppColors.primaryRedColorBase.withOpacity(0.0), // textColor: AppColors.primaryRedColor, // fontSize: 14.f, // isBold: true, diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 2e9bb3b7..ce300c9b 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -52,14 +52,13 @@ class ProfileSettings extends StatefulWidget { } class ProfileSettingsState extends State { - final InAppReview inAppReview = InAppReview.instance; @override void initState() { super.initState(); _loadPermissions(); - + // Load data after first frame WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { @@ -434,7 +433,15 @@ class ProfileSettingsState extends State { if (trailingLabel.isNotEmpty) ConstrainedBox( constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.35), - child: isEnglishOnly ? Directionality(textDirection: ui.TextDirection.ltr, child: trailingLabel.toText14(color: AppColors.greyTextColor, isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: isEnglishOnly).toShimmer2(isShow: isShowLoading)) : trailingLabel.toText14(color: AppColors.greyTextColor, isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: isEnglishOnly).toShimmer2(isShow: isShowLoading), + child: isEnglishOnly + ? Directionality( + textDirection: ui.TextDirection.ltr, + child: trailingLabel + .toText14(color: AppColors.greyTextColor, isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: isEnglishOnly) + .toShimmer2(isShow: isShowLoading)) + : trailingLabel + .toText14(color: AppColors.greyTextColor, isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: isEnglishOnly) + .toShimmer2(isShow: isShowLoading), ), switchValue != null ? Switch( @@ -452,8 +459,10 @@ class ProfileSettingsState extends State { ); } - Widget socialMedia(String icon, String label, - ) { + Widget socialMedia( + String icon, + String label, + ) { return SizedBox( height: 56.h, child: Row( @@ -461,14 +470,13 @@ class ProfileSettingsState extends State { children: [ Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.greyTextColor), label.toText14(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, - Row( spacing: 24.w, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.x2).onPress(()=> launchUrl(Uri.parse("https://twitter.com/HMG"))), - Utils.buildSvgWithAssets(icon: AppAssets.fb).onPress(()=> launchUrl(Uri.parse("https://www.facebook.com/DrSulaimanAlHabib?ref=tn_tnmn"))), - Utils.buildSvgWithAssets(icon: AppAssets.linkedin).onPress(()=> launchUrl(Uri.parse("https://www.linkedin.com/company/drsulaiman-alhabib-medical-group"))), - Utils.buildSvgWithAssets(icon: AppAssets.youtube).onPress(()=> launchUrl(Uri.parse("https://www.youtube.com/c/DrsulaimanAlhabibHospitals"))), + Utils.buildSvgWithAssets(icon: AppAssets.x2).onPress(() => launchUrl(Uri.parse("https://twitter.com/HMG"))), + Utils.buildSvgWithAssets(icon: AppAssets.fb).onPress(() => launchUrl(Uri.parse("https://www.facebook.com/DrSulaimanAlHabib?ref=tn_tnmn"))), + Utils.buildSvgWithAssets(icon: AppAssets.linkedin).onPress(() => launchUrl(Uri.parse("https://www.linkedin.com/company/drsulaiman-alhabib-medical-group"))), + Utils.buildSvgWithAssets(icon: AppAssets.youtube).onPress(() => launchUrl(Uri.parse("https://www.youtube.com/c/DrsulaimanAlhabibHospitals"))), ], ) ], @@ -546,9 +554,7 @@ class _FamilyCardWidgetState extends State { // final isParentUser = appState.getAuthenticatedUser()?.isParentUser ?? false; // final canSwitch = isParentUser || (!isParentUser && widget.profile.responseId == appState.getSuperUserID); return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, hasShadow: true, hasDenseShadow: true), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true, hasDenseShadow: true), child: Column( children: [ Column( @@ -607,12 +613,14 @@ class _FamilyCardWidgetState extends State { labelText: "${LocaleKeys.fileno.tr(context: context)}: ${widget.profile.responseId}", iconSize: 12.w, ), - isActive ? AppCustomChipWidget( - icon: AppAssets.checkmark_icon, - labelText: LocaleKeys.verified.tr(context: context), - iconColor: AppColors.successColor, - labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), - ) : SizedBox.shrink(), + isActive + ? AppCustomChipWidget( + icon: AppAssets.checkmark_icon, + labelText: LocaleKeys.verified.tr(context: context), + iconColor: AppColors.successColor, + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), + ) + : SizedBox.shrink(), ], ), ], @@ -639,73 +647,76 @@ class _FamilyCardWidgetState extends State { ), // isActive && appState.getAuthenticatedUser()!.bloodGroup != null // ? - isActive ? AppCustomChipWidget( - icon: AppAssets.blood_icon, + isActive + ? AppCustomChipWidget( + icon: AppAssets.blood_icon, labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 8.w), - labelText: appState.getAuthenticatedUser()!.bloodGroup ?? "N/A", - iconColor: AppColors.primaryRedColor) - : SizedBox(), + labelText: appState.getAuthenticatedUser()!.bloodGroup ?? "N/A", + iconColor: AppColors.primaryRedColor) + : SizedBox(), Consumer(builder: (context, insuranceVM, child) { return isActive ? AppCustomChipWidget( icon: insuranceVM.isInsuranceExpired ? AppAssets.cancel_circle_icon : insuranceVM.isInsuranceActive - ? AppAssets.insurance_active_icon - : AppAssets.alertSquare, - labelText: insuranceVM.isInsuranceExpired - ? LocaleKeys.insuranceExpired.tr(context: context) - : insuranceVM.isInsuranceActive - ? LocaleKeys.insuranceActive.tr(context: context) - : LocaleKeys.insuranceInActive.tr(context: context), - iconColor: insuranceVM.isInsuranceExpired - ? AppColors.primaryRedColor - : insuranceVM.isInsuranceActive - ? AppColors.successColor - : AppColors.warningColorYellow, - textColor: insuranceVM.isInsuranceExpired - ? AppColors.primaryRedColor - : insuranceVM.isInsuranceActive - ? AppColors.successColor - : AppColors.warningColorYellow, - iconSize: 12.w, - deleteIcon: insuranceVM.isInsuranceActive ? null : AppAssets.forward_chevron_icon, - deleteIconColor: insuranceVM.isInsuranceExpired - ? AppColors.primaryRedColor - : insuranceVM.isInsuranceActive - ? AppColors.successColor - : AppColors.warningColorYellow, - deleteIconHasColor: true, - onChipTap: () { - if (!insuranceVM.isInsuranceActive) { - insuranceVM.setIsInsuranceUpdateDetailsLoading(true); - insuranceVM.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); - showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); - // showCommonBottomSheetWithoutHeight( - // title: LocaleKeys.notice.tr(context: context), - // context, - // child: Utils.getWarningWidget( - // loadingText: LocaleKeys.insuranceInActiveContactSupport.tr(context: context), - // confirmText: LocaleKeys.contactUs.tr(context: context), - // isShowActionButtons: true, - // onCancelTap: () { - // Navigator.pop(context); - // }, - // onConfirmTap: () async { - // launchUrl(Uri.parse("tel://" + "+966 92 006 6666")); - // }), - // callBackFunc: () {}, - // isFullScreen: false, - // isCloseButtonVisible: true, - // ); - } - }, - backgroundColor: insuranceVM.isInsuranceExpired - ? AppColors.primaryRedColor.withValues(alpha: 0.1) - : insuranceVM.isInsuranceActive - ? AppColors.successColor.withValues(alpha: 0.1) - : AppColors.warningColorYellow.withValues(alpha: 0.1), - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceVM.isInsuranceActive ? 6.w : 0.w), + ? AppAssets.insurance_active_icon + : AppAssets.alertSquare, + labelText: insuranceVM.isInsuranceExpired + ? LocaleKeys.insuranceExpired.tr(context: context) + : insuranceVM.isInsuranceActive + ? LocaleKeys.insuranceActive.tr(context: context) + : LocaleKeys.insuranceInActive.tr(context: context), + iconColor: insuranceVM.isInsuranceExpired + ? AppColors.primaryRedColor + : insuranceVM.isInsuranceActive + ? AppColors.successColor + : AppColors.warningColorYellow, + textColor: insuranceVM.isInsuranceExpired + ? AppColors.primaryRedColor + : insuranceVM.isInsuranceActive + ? AppColors.successColor + : AppColors.warningColorYellow, + iconSize: 12.w, + deleteIcon: insuranceVM.isInsuranceActive ? null : AppAssets.forward_chevron_icon, + deleteIconColor: insuranceVM.isInsuranceExpired + ? AppColors.primaryRedColor + : insuranceVM.isInsuranceActive + ? AppColors.successColor + : AppColors.warningColorYellow, + deleteIconHasColor: true, + onChipTap: () { + if (!insuranceVM.isInsuranceActive) { + insuranceVM.setIsInsuranceUpdateDetailsLoading(true); + insuranceVM.getPatientInsuranceDetailsForUpdate( + appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + showCommonBottomSheetWithoutHeight(context, + child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + // showCommonBottomSheetWithoutHeight( + // title: LocaleKeys.notice.tr(context: context), + // context, + // child: Utils.getWarningWidget( + // loadingText: LocaleKeys.insuranceInActiveContactSupport.tr(context: context), + // confirmText: LocaleKeys.contactUs.tr(context: context), + // isShowActionButtons: true, + // onCancelTap: () { + // Navigator.pop(context); + // }, + // onConfirmTap: () async { + // launchUrl(Uri.parse("tel://" + "+966 92 006 6666")); + // }), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + } + }, + backgroundColor: insuranceVM.isInsuranceExpired + ? AppColors.primaryRedColor.withValues(alpha: 0.1) + : insuranceVM.isInsuranceActive + ? AppColors.successColor.withValues(alpha: 0.1) + : AppColors.warningColorYellow.withValues(alpha: 0.1), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceVM.isInsuranceActive ? 6.w : 0.w), ).toShimmer2(isShow: insuranceVM.isInsuranceLoading) : SizedBox.shrink(); }), @@ -747,7 +758,7 @@ class _FamilyCardWidgetState extends State { height: isFoldable ? 50.h : 45.h, fontSize: 14.f, ).paddingOnly(top: 12.h, right: 16.w, left: 16.w, bottom: 16.h); - } + } Widget _buildNonParentUserButton(int? superUserId) { final canSwitchBack = superUserId != null && superUserId == widget.profile.responseId; diff --git a/lib/presentation/rate_appointment/rate_appointment_clinic.dart b/lib/presentation/rate_appointment/rate_appointment_clinic.dart index c8aedea8..e285a7c1 100644 --- a/lib/presentation/rate_appointment/rate_appointment_clinic.dart +++ b/lib/presentation/rate_appointment/rate_appointment_clinic.dart @@ -159,9 +159,9 @@ class _RateAppointmentClinicState extends State { Expanded( child: CustomButton( text: LocaleKeys.back.tr(context: context), - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, onPressed: () { appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context)); appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context)); diff --git a/lib/presentation/rate_appointment/rate_appointment_doctor.dart b/lib/presentation/rate_appointment/rate_appointment_doctor.dart index d78dc59e..b54b5741 100644 --- a/lib/presentation/rate_appointment/rate_appointment_doctor.dart +++ b/lib/presentation/rate_appointment/rate_appointment_doctor.dart @@ -181,9 +181,9 @@ class _RateAppointmentDoctorState extends State { Expanded( child: CustomButton( text: LocaleKeys.later.tr(context: context), - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, onPressed: () { Navigator.pop(context); }, diff --git a/lib/presentation/smartwatches/activity_detail.dart b/lib/presentation/smartwatches/activity_detail.dart index 7911baf6..888533af 100644 --- a/lib/presentation/smartwatches/activity_detail.dart +++ b/lib/presentation/smartwatches/activity_detail.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:easy_localization/easy_localization.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -43,7 +44,7 @@ class _ActivityDetailsState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "All Health Data".needTranslation, + title: "allHealthData".tr(), child: Column( spacing: 16.h, children: [ @@ -66,8 +67,8 @@ class _ActivityDetailsState extends State { children: [ Expanded( child: CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.secondaryLightRedColor, tabs: [ CustomTabBarModel(null, "D"), CustomTabBarModel(null, "W"), @@ -113,8 +114,8 @@ class _ActivityDetailsState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - widget.sectionName.capitalizeFirstofEach.toText32(isBold: true, color: AppColors.textColor), - "Average".toText12(isBold: true, color: AppColors.greyTextColor) + widget.sectionName.capitalizeFirstofEach.tr().toText32(isBold: true, color: AppColors.textColor), + "average".tr().needTranslation.toText12(isBold: true, color: AppColors.greyTextColor) ], ), Selector>( @@ -242,6 +243,7 @@ class _ActivityDetailsState extends State { builder: (_, data, __) { // if (context.read().selectedData.values.toList().first.isEmpty == true) return SizedBox(); if (context.read().selectedData.values.toList().isEmpty == true) return SizedBox(); + if (context.read().selectedData.values.toList().first.isEmpty == true) return SizedBox(); return CustomBarChart( dataPoints: context.read().selectedData.values.toList().first, diff --git a/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart b/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart index f30d87a9..84ec64e9 100644 --- a/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart +++ b/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart @@ -56,8 +56,8 @@ class _HealthDashboardState extends State with SingleTickerProv children: [ SizedBox(height: 96.h), CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.secondaryLightRedColor, tabs: [ CustomTabBarModel(null, "Overview".needTranslation), CustomTabBarModel(null, "Details".needTranslation), diff --git a/lib/presentation/smartwatches/smart_watches_health_data_screen.dart b/lib/presentation/smartwatches/smart_watches_health_data_screen.dart index baa65b49..78e888ad 100644 --- a/lib/presentation/smartwatches/smart_watches_health_data_screen.dart +++ b/lib/presentation/smartwatches/smart_watches_health_data_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -20,14 +21,14 @@ class SmartWatchesHealthDataScreen extends StatelessWidget { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "All Health Data".needTranslation, + title: "allHealthData".tr(context: context), child: Column( spacing: 16.h, children: [ resultItem( leadingIcon: AppAssets.watchActivity, - title: "Activity Calories".needTranslation, - description: "Activity rings give you a quick visual reference of how active you are each day. ".needTranslation, + title: "activityCalories".tr(context: context), + description: "activityCaloriesDescription".tr(context: context), trailingIcon: AppAssets.watchActivityTrailing, result: context.read().sumOfNonEmptyData(context.read().vitals?.activity ?? []), unitsOfMeasure: "Kcal") @@ -36,12 +37,12 @@ class SmartWatchesHealthDataScreen extends StatelessWidget { context.read().deleteDataIfSectionIsDifferent("activity"); context.read().saveSelectedSection("activity"); context.read().fetchData(); - context.read().navigateToDetails("activity", sectionName: "Activity Calories", uom: "Kcal"); + context.read().navigateToDetails("activity", sectionName: "activityCalories".tr(), uom: "Kcal"); }), resultItem( leadingIcon: AppAssets.watchSteps, - title: "Steps".needTranslation, - description: "Step count is the number of steps you take throughout the day.".needTranslation, + title: "steps".tr(context: context), + description: "stepsDescription".tr(context: context), trailingIcon: AppAssets.watchStepsTrailing, result: context.read().sumOfNonEmptyData(context.read().vitals?.step ?? []), unitsOfMeasure: "Steps") @@ -60,12 +61,12 @@ class SmartWatchesHealthDataScreen extends StatelessWidget { context.read().deleteDataIfSectionIsDifferent("steps"); context.read().saveSelectedSection("steps"); context.read().fetchData(); - context.read().navigateToDetails("steps", sectionName: "Steps", uom: "Steps"); + context.read().navigateToDetails("steps", sectionName: "steps".tr(), uom: "Steps"); }), resultItem( leadingIcon: AppAssets.watchSteps, - title: "Distance Covered".needTranslation, - description: "Step count is the distance you take throughout the day.".needTranslation, + title: "distanceCovered".tr(context: context), + description: "distanceCoveredDescription".tr(context: context), trailingIcon: AppAssets.watchStepsTrailing, result: context.read().sumOfNonEmptyData(context.read().vitals?.distance ?? []), unitsOfMeasure: "Km") @@ -74,12 +75,12 @@ class SmartWatchesHealthDataScreen extends StatelessWidget { context.read().deleteDataIfSectionIsDifferent("distance"); context.read().saveSelectedSection("distance"); context.read().fetchData(); - context.read().navigateToDetails("distance", sectionName: "Distance Covered", uom: "km"); + context.read().navigateToDetails("distance", sectionName: "distanceCovered".tr(), uom: "km"); }), resultItem( leadingIcon: AppAssets.watchSleep, - title: "Sleep Score".needTranslation, - description: "This will keep track of how much hours you sleep in a day".needTranslation, + title: "sleepScore".tr(context: context), + description: "sleepScoreDescription".tr(context: context), trailingIcon: AppAssets.watchSleepTrailing, result: DateUtil.millisToHourMin( int.parse(context.read().firstNonEmptyValue(context.read().vitals?.sleep ?? []))) @@ -94,12 +95,12 @@ class SmartWatchesHealthDataScreen extends StatelessWidget { context.read().deleteDataIfSectionIsDifferent("sleep"); context.read().saveSelectedSection("sleep"); context.read().fetchData(); - context.read().navigateToDetails("sleep", sectionName: "Sleep Score", uom: ""); + context.read().navigateToDetails("sleep", sectionName: "sleepScore".tr(), uom: ""); }), resultItem( leadingIcon: AppAssets.watchWeight, - title: "Blood Oxygen".needTranslation, - description: "This will calculate your Blood Oxygen to keep track and update history".needTranslation, + title: "bloodOxygen".tr(context: context), + description: "bloodOxygenTrackingDescription".tr(context: context), trailingIcon: AppAssets.watchWeightTrailing, result: context.read().firstNonEmptyValue( context.read().vitals?.bodyOxygen ?? [], @@ -110,12 +111,12 @@ class SmartWatchesHealthDataScreen extends StatelessWidget { context.read().deleteDataIfSectionIsDifferent("bodyOxygen"); context.read().saveSelectedSection("bodyOxygen"); context.read().fetchData(); - context.read().navigateToDetails("bodyOxygen", uom: "%", sectionName: "Blood Oxygen"); + context.read().navigateToDetails("bodyOxygen", uom: "%", sectionName: "bloodOxygen".tr()); }), resultItem( leadingIcon: AppAssets.watchWeight, - title: "Body temperature".needTranslation, - description: "This will calculate your Body temprerature to keep track and update history".needTranslation, + title: "bodyTemperature".tr(context: context), + description: "bodyTemperatureTrackingDescription".tr(context: context), trailingIcon: AppAssets.watchWeightTrailing, result: context.read().firstNonEmptyValue(context.read().vitals?.bodyTemperature ?? []), unitsOfMeasure: "C") @@ -124,7 +125,7 @@ class SmartWatchesHealthDataScreen extends StatelessWidget { context.read().deleteDataIfSectionIsDifferent("bodyTemperature"); context.read().saveSelectedSection("bodyTemperature"); context.read().fetchData(); - context.read().navigateToDetails("bodyTemperature", sectionName: "Body temperature".capitalizeFirstofEach, uom: "C"); + context.read().navigateToDetails("bodyTemperature", sectionName: "bodyTemperature".tr().capitalizeFirstofEach, uom: "C"); }), ], ).paddingSymmetrical(24.w, 24.h), diff --git a/lib/presentation/smartwatches/smartwatch_home_page.dart b/lib/presentation/smartwatches/smartwatch_home_page.dart index 3bd8e9f5..2c3c88ae 100644 --- a/lib/presentation/smartwatches/smartwatch_home_page.dart +++ b/lib/presentation/smartwatches/smartwatch_home_page.dart @@ -25,203 +25,269 @@ class SmartwatchHomePage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: LocaleKeys.smartWatches.tr(), - bottomChild: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, + return Consumer(builder: (context, healthProvider, child) { + + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.smartWatches.tr(), + bottomChild: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: CustomButton( + text: LocaleKeys.supportedSmartWatches.tr(context: context), + onPressed: () { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.supportedSmartWatches.tr(context: context), + context, + child: SupportedWatchesList(), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + backgroundColor: AppColors.infoColor.withAlpha(40), + borderColor: AppColors.infoColor.withAlpha(0), + textColor: AppColors.infoColor, + fontSize: 16.f, + isBold: true, + borderRadius: 12.r, + height: 50.h, + icon: AppAssets.ask_doctor_icon, + iconColor: AppColors.infoColor, + iconSize: 18.h, + ).paddingSymmetrical(24.w, 30.h), ), - child: CustomButton( - text: LocaleKeys.supportedSmartWatches.tr(context: context), - onPressed: () { - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.supportedSmartWatches.tr(context: context), - context, - child: SupportedWatchesList(), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }, - backgroundColor: AppColors.infoColor.withAlpha(40), - borderColor: AppColors.infoColor.withAlpha(0), - textColor: AppColors.infoColor, - fontSize: 16.f, - isBold: true, - borderRadius: 12.r, - height: 50.h, - icon: AppAssets.ask_doctor_icon, - iconColor: AppColors.infoColor, - iconSize: 18.h, - ).paddingSymmetrical(24.w, 30.h), - ), - child: SingleChildScrollView( - child: Column( - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h), - child: GridView( - padding: EdgeInsets.zero, - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 16.h, - mainAxisSpacing: 16.w, - childAspectRatio: isFoldable ? 1.1 : (isTablet ? 1.3 : 0.7), - ), - children: [ - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - ), - child: Column( - children: [ - Utils.buildImgWithAssets(icon: "assets/images/png/smartwatches/apple-watch-5.jpg", width: 136.h, height: 136.h).paddingSymmetrical(24.w, 8.h), - "Apple Watch".needTranslation.toText16(isBold: true), - CustomButton( - text: LocaleKeys.selectSmartWatch.tr(context: context), - onPressed: () { - context - .read() - .setSelectedWatchType(SmartWatchTypes.apple, "assets/images/png/smartwatches/apple-watch-5.jpg"); - getIt.get().pushPage( - page: SmartwatchInstructionsPage( - smartwatchDetails: SmartwatchDetails( - SmartWatchTypes.apple, + child: SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: + EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h), + child: GridView( + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 16.h, + mainAxisSpacing: 16.w, + childAspectRatio: + isFoldable ? 1.1 : (isTablet ? 1.3 : 0.7), + ), + children: [ + Container( + decoration: + RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: Column( + children: [ + Utils.buildImgWithAssets( + icon: "assets/images/png/smartwatches/apple-watch-5.jpg", - AppAssets.bluetooth, - LocaleKeys.applehealthapplicationshouldbeinstalledinyourphone.tr(context: context), - LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), - LocaleKeys.applewatchshouldbeconnected.tr(context: context)), - )); - }, - backgroundColor: AppColors.primaryRedColor.withAlpha(40), - borderColor: AppColors.primaryRedColor.withAlpha(0), - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - isBold: true, - borderRadius: 12.r, - height: 40.h, - ).paddingSymmetrical(16.w, 12.h), - ], + width: 136.h, + height: 136.h) + .paddingSymmetrical(24.w, 8.h), + "applewatch" + .tr() + .needTranslation + .toText16(isBold: true), + CustomButton( + text: LocaleKeys.selectSmartWatch + .tr(context: context), + onPressed: () { + context.read().setSelectedWatchType( + SmartWatchTypes.apple, + "assets/images/png/smartwatches/apple-watch-5.jpg"); + getIt.get().pushPage( + page: SmartwatchInstructionsPage( + smartwatchDetails: SmartwatchDetails( + SmartWatchTypes.apple, + "assets/images/png/smartwatches/apple-watch-5.jpg", + AppAssets.bluetooth, + LocaleKeys + .applehealthapplicationshouldbeinstalledinyourphone + .tr(context: context), + LocaleKeys + .unabletodetectapplicationinstalledpleasecomebackonceinstalled + .tr(context: context), + LocaleKeys.applewatchshouldbeconnected + .tr(context: context)), + )); + }, + backgroundColor: + AppColors.primaryRedColor.withAlpha(40), + borderColor: + AppColors.primaryRedColor.withAlpha(0), + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + isBold: true, + borderRadius: 12.r, + height: 40.h, + ).paddingSymmetrical(16.w, 12.h), + ], + ), ), - ), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), - child: Column( - children: [ - Utils.buildImgWithAssets(icon: "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", fit: BoxFit.contain, width: 136.w, height: 136.h) - .paddingSymmetrical(24.w, 8.h), - "Samsung Watch".needTranslation.toText16(isBold: true), - CustomButton( - text: LocaleKeys.selectSmartWatch.tr(context: context), - onPressed: () { - context - .read() - .setSelectedWatchType(SmartWatchTypes.samsung, "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg"); - getIt.get().pushPage( - page: SmartwatchInstructionsPage( - smartwatchDetails: SmartwatchDetails( - SmartWatchTypes.samsung, + Container( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r), + child: Column( + children: [ + Utils.buildImgWithAssets( + icon: "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", - AppAssets.bluetooth, - LocaleKeys.samsunghealthapplicationshouldbeinstalledinyourphone.tr(context: context), - LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), - LocaleKeys.samsungwatchshouldbeconnected.tr(context: context)), - )); - }, - backgroundColor: AppColors.primaryRedColor.withAlpha(40), - borderColor: AppColors.primaryRedColor.withAlpha(0), - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - isBold: true, - borderRadius: 12.r, - height: 40.h, - ).paddingSymmetrical(16.w, 12.h), - ], + fit: BoxFit.contain, + width: 136.w, + height: 136.h) + .paddingSymmetrical(24.w, 8.h), + "samsungwatch" + .tr() + .needTranslation + .toText16(isBold: true), + CustomButton( + text: LocaleKeys.selectSmartWatch + .tr(context: context), + onPressed: () { + context.read().setSelectedWatchType( + SmartWatchTypes.samsung, + "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg"); + getIt.get().pushPage( + page: SmartwatchInstructionsPage( + smartwatchDetails: SmartwatchDetails( + SmartWatchTypes.samsung, + "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", + AppAssets.bluetooth, + LocaleKeys + .samsunghealthapplicationshouldbeinstalledinyourphone + .tr(context: context), + LocaleKeys + .unabletodetectapplicationinstalledpleasecomebackonceinstalled + .tr(context: context), + LocaleKeys + .samsungwatchshouldbeconnected + .tr(context: context)), + )); + }, + backgroundColor: + AppColors.primaryRedColor.withAlpha(40), + borderColor: + AppColors.primaryRedColor.withAlpha(0), + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + isBold: true, + borderRadius: 12.r, + height: 40.h, + ).paddingSymmetrical(16.w, 12.h), + ], + ), ), - ), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, + Container( + decoration: + RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: Column( + children: [ + Utils.buildImgWithAssets( + icon: + "assets/images/png/smartwatches/Huawei_Watch.png", + width: 136.w, + height: 136.h) + .paddingSymmetrical(24.w, 8.h), + "huaweiwatch" + .tr() + .needTranslation + .toText16(isBold: true), + CustomButton( + text: LocaleKeys.selectSmartWatch + .tr(context: context), + onPressed: () { + // context.read().setSelectedWatchType(SmartWatchTypes.huawei, "assets/images/png/smartwatches/Huawei_Watch.png"); + // getIt.get().pushPage(page: SmartwatchInstructionsPage( + // smartwatchDetails: SmartwatchDetails(SmartWatchTypes.huawei, + // "assets/images/png/smartwatches/Huawei_Watch.png", + // AppAssets.bluetooth, + // LocaleKeys.huaweihealthapplicationshouldbeinstalledinyourphone.tr(context: context), + // LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), + // LocaleKeys.huaweiwatchshouldbeconnected.tr(context: context)), + // )); + showUnavailableDialog(context); + }, + backgroundColor: + AppColors.primaryRedColor.withAlpha(40), + borderColor: + AppColors.primaryRedColor.withAlpha(0), + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + isBold: true, + borderRadius: 12.r, + height: 40.h, + ).paddingSymmetrical(16.w, 12.h), + ], + ), ), - child: Column( - children: [ - Utils.buildImgWithAssets(icon: "assets/images/png/smartwatches/Huawei_Watch.png", width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h), - "Huawei Watch".needTranslation.toText16(isBold: true), - CustomButton( - text: LocaleKeys.selectSmartWatch.tr(context: context), - onPressed: () { - // context.read().setSelectedWatchType(SmartWatchTypes.huawei, "assets/images/png/smartwatches/Huawei_Watch.png"); - // getIt.get().pushPage(page: SmartwatchInstructionsPage( - // smartwatchDetails: SmartwatchDetails(SmartWatchTypes.huawei, - // "assets/images/png/smartwatches/Huawei_Watch.png", - // AppAssets.bluetooth, - // LocaleKeys.huaweihealthapplicationshouldbeinstalledinyourphone.tr(context: context), - // LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), - // LocaleKeys.huaweiwatchshouldbeconnected.tr(context: context)), - // )); - showUnavailableDialog(context); - }, - backgroundColor: AppColors.primaryRedColor.withAlpha(40), - borderColor: AppColors.primaryRedColor.withAlpha(0), - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - isBold: true, - borderRadius: 12.r, - height: 40.h, - ).paddingSymmetrical(16.w, 12.h), - ], + Container( + decoration: + RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: Column( + children: [ + Utils.buildImgWithAssets( + icon: + "assets/images/png/smartwatches/Whoop_Watch.png", + width: 136.w, + height: 136.h) + .paddingSymmetrical(24.w, 8.h), + "whoopwatch" + .tr() + .needTranslation + .toText16(isBold: true), + CustomButton( + text: LocaleKeys.selectSmartWatch + .tr(context: context), + onPressed: () { + showUnavailableDialog(context); + // context.read().setSelectedWatchType(SmartWatchTypes.whoop, "assets/images/png/smartwatches/Whoop_Watch.png"); + // getIt.get().pushPage(page: SmartwatchInstructionsPage( + // smartwatchDetails: SmartwatchDetails(SmartWatchTypes.whoop, + // "assets/images/png/smartwatches/Whoop_Watch.png", + // AppAssets.bluetooth, + // LocaleKeys.whoophealthapplicationshouldbeinstalledinyourphone.tr(context: context), + // LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), + // LocaleKeys.whoopwatchshouldbeconnected.tr(context: context)), + // )); + }, + backgroundColor: + AppColors.primaryRedColor.withAlpha(40), + borderColor: + AppColors.primaryRedColor.withAlpha(0), + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + isBold: true, + borderRadius: 12.r, + height: 40.h, + ).paddingSymmetrical(16.w, 12.h), + ], + ), ), - ), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - ), - child: Column( - children: [ - Utils.buildImgWithAssets(icon: "assets/images/png/smartwatches/Whoop_Watch.png", width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h), - "Whoop Watch".needTranslation.toText16(isBold: true), - CustomButton( - text: LocaleKeys.selectSmartWatch.tr(context: context), - onPressed: () { - showUnavailableDialog(context); - // context.read().setSelectedWatchType(SmartWatchTypes.whoop, "assets/images/png/smartwatches/Whoop_Watch.png"); - // getIt.get().pushPage(page: SmartwatchInstructionsPage( - // smartwatchDetails: SmartwatchDetails(SmartWatchTypes.whoop, - // "assets/images/png/smartwatches/Whoop_Watch.png", - // AppAssets.bluetooth, - // LocaleKeys.whoophealthapplicationshouldbeinstalledinyourphone.tr(context: context), - // LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), - // LocaleKeys.whoopwatchshouldbeconnected.tr(context: context)), - // )); - }, - backgroundColor: AppColors.primaryRedColor.withAlpha(40), - borderColor: AppColors.primaryRedColor.withAlpha(0), - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - isBold: true, - borderRadius: 12.r, - height: 40.h, - ).paddingSymmetrical(16.w, 12.h), - ], - ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), - ), - ); + ); + }); } void showUnavailableDialog(BuildContext context) { @@ -229,7 +295,8 @@ class SmartwatchHomePage extends StatelessWidget { title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( - loadingText: LocaleKeys.featureComingSoonDescription.tr(context: context), + loadingText: + LocaleKeys.featureComingSoonDescription.tr(context: context), isShowActionButtons: false, showOkButton: true, onConfirmTap: () async { diff --git a/lib/presentation/smartwatches/smartwatch_instructions_page.dart b/lib/presentation/smartwatches/smartwatch_instructions_page.dart index 7c0d0b97..fcef1d15 100644 --- a/lib/presentation/smartwatches/smartwatch_instructions_page.dart +++ b/lib/presentation/smartwatches/smartwatch_instructions_page.dart @@ -21,65 +21,76 @@ class SmartwatchInstructionsPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: "How does it work".needTranslation, - bottomChild: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - ), - child: CustomButton( - text: LocaleKeys.getStarted.tr(context: context), - onPressed: () { - context.read().initDevice(); - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - fontSize: 16.f, - isBold: true, - borderRadius: 12.r, - height: 50.h, - ).paddingSymmetrical(24.w, 30.h), - ), - child: Column( - mainAxisSize: MainAxisSize.max, - spacing: 18.h, - children: [ - Utils.buildImgWithAssets( - icon: smartwatchDetails.watchIcon, - fit: BoxFit.contain, - height: 280.h, - width: 280.w, + return Consumer(builder: (context, healthProvider, child) { + final errorMessage = healthProvider.error; + if (errorMessage != null && errorMessage.isNotEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Utils.showToast(errorMessage, longDuration: false); + healthProvider.clearError(); + }); + } + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "smartwatchHowItWorks".tr(context: context), + bottomChild: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: CustomButton( + text: LocaleKeys.getStarted.tr(context: context), + onPressed: () { + context.read().initDevice(); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + isBold: true, + borderRadius: 12.r, + height: 50.h, + ).paddingSymmetrical(24.w, 30.h), ), - DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), - child: Column( - children: [ - watchContentDetails( - title: smartwatchDetails.detailsTitle, - description: smartwatchDetails.details, - icon: smartwatchDetails.smallIcon, - descriptionTextColor: AppColors.primaryRedColor, - ), - Divider( - color: AppColors.dividerColor, - thickness: 1.h, - ).paddingOnly(top: 16.h, bottom: 16.h), - watchContentDetails( - title: smartwatchDetails.secondTitle, - description: LocaleKeys.updatetheinformation.tr(), - icon: AppAssets.bluetooth, - descriptionTextColor: AppColors.greyTextColor, - ), - ], - ).paddingSymmetrical(16.w, 16.h), - ) - ], - ).paddingSymmetrical(24.w, 16.h), - ), + child: Column( + mainAxisSize: MainAxisSize.max, + spacing: 18.h, + children: [ + Utils.buildImgWithAssets( + icon: smartwatchDetails.watchIcon, + fit: BoxFit.contain, + height: 280.h, + width: 280.w, + ), + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Column( + children: [ + watchContentDetails( + title: smartwatchDetails.detailsTitle, + // description: smartwatchDetails.details, + description: "", + icon: smartwatchDetails.smallIcon, + descriptionTextColor: AppColors.primaryRedColor, + ), + Divider( + color: AppColors.dividerColor, + thickness: 1.h, + ).paddingOnly(top: 16.h, bottom: 16.h), + watchContentDetails( + title: smartwatchDetails.secondTitle, + description: LocaleKeys.updatetheinformation.tr(), + icon: AppAssets.bluetooth, + descriptionTextColor: AppColors.greyTextColor, + ), + ], + ).paddingSymmetrical(16.w, 16.h), + ) + ], + ).paddingSymmetrical(24.w, 16.h), + ), + ); + } ); } diff --git a/lib/presentation/symptoms_checker/organ_selector_screen.dart b/lib/presentation/symptoms_checker/organ_selector_screen.dart index 438d9728..5b5d8f2e 100644 --- a/lib/presentation/symptoms_checker/organ_selector_screen.dart +++ b/lib/presentation/symptoms_checker/organ_selector_screen.dart @@ -480,7 +480,7 @@ class _OrganSelectorPageState extends State with SingleTicker backgroundColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, deleteIcon: AppAssets.cancel, - deleteIconColor: AppColors.primaryRedColor, + deleteIconColor: AppColors.criticalActionColor, deleteIconHasColor: false, onDeleteTap: () { viewModel.removeOrgan(organ.id); @@ -500,6 +500,7 @@ class _OrganSelectorPageState extends State with SingleTicker isDisabled: viewModel.selectedOrgans.isEmpty, backgroundColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, + borderColor: Colors.transparent, ), ); } diff --git a/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart b/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart index e2b342b6..a4f5f1ee 100644 --- a/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart +++ b/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart @@ -121,7 +121,7 @@ class _SelectedOrgansSectionState extends State { backgroundColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, deleteIcon: AppAssets.cross_circle, - deleteIconColor: AppColors.primaryRedColor, + deleteIconColor: AppColors.criticalActionColor, deleteIconHasColor: true, onDeleteTap: () => widget.onRemoveOrgan(organ.id), padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h), diff --git a/lib/presentation/symptoms_checker/widgets/triage_level_card.dart b/lib/presentation/symptoms_checker/widgets/triage_level_card.dart index 19ad3935..8633d830 100644 --- a/lib/presentation/symptoms_checker/widgets/triage_level_card.dart +++ b/lib/presentation/symptoms_checker/widgets/triage_level_card.dart @@ -94,7 +94,7 @@ class _TriageLevelCardState extends State with SingleTickerProv switch (triageLevel) { case 'emergency_ambulance': case 'emergency': - return AppColors.chipColorEmergency.withValues(alpha: 0.1); + return AppColors.primaryRedColor.withValues(alpha: 0.1); case 'consultation_24': case 'consultation': return AppColors.chipColorSeekMedicalAdvice.withValues(alpha: 0.1); diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index 365dff2b..e6665037 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -819,6 +819,7 @@ class _AncillaryOrderPaymentPageState extends State { applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", onFailed: (failureResult) async { + payfortViewModel.addApplePayPayfortResponse(failureResult.response); log("failureResult: ${failureResult.message.toString()}"); LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( @@ -830,6 +831,7 @@ class _AncillaryOrderPaymentPageState extends State { ); }, onSucceeded: (successResult) async { + payfortViewModel.addApplePayPayfortResponse(successResult); LoaderBottomSheet.hideLoader(); log("successResult: ${successResult.responseMessage.toString()}"); selectedPaymentMethod = successResult.paymentOption ?? "VISA"; diff --git a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart index cc5bbbb6..da75dd7b 100644 --- a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart +++ b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart @@ -257,9 +257,9 @@ class AncillaryOrderCard extends StatelessWidget { onViewDetails!(); } }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w600, borderRadius: 10.r, diff --git a/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart b/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart index 54c92789..b7f0f28a 100644 --- a/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart +++ b/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart @@ -252,9 +252,9 @@ class AncillaryOrderCard extends StatelessWidget { onViewDetails!(); } }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, fontSize: 14.f, isBold: true, borderRadius: 10.r, diff --git a/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart b/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart index 058f14e8..aef892f9 100644 --- a/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart +++ b/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart @@ -91,7 +91,7 @@ class SwitchCupBottomSheet extends StatelessWidget { width: 60.w, height: 60.w, decoration: BoxDecoration( - color: isSelected ? AppColors.primaryRedColor.withOpacity(0.08) : Colors.transparent, + color: isSelected ? AppColors.secondaryLightRedColor : Colors.transparent, borderRadius: BorderRadius.circular(12.r), border: Border.all( color: isSelected ? AppColors.primaryRedColor : AppColors.bgScaffoldColor, @@ -271,9 +271,9 @@ class _CustomizeCupBottomSheetState extends State { SliderTheme( data: SliderTheme.of(context).copyWith( activeTrackColor: AppColors.primaryRedColor, - inactiveTrackColor: AppColors.primaryRedColor.withOpacity(0.2), + inactiveTrackColor: AppColors.secondaryLightRedColor, thumbColor: AppColors.primaryRedColor, - overlayColor: AppColors.primaryRedColor.withOpacity(0.2), + overlayColor: AppColors.secondaryLightRedColor, trackHeight: 4.h, thumbShape: RoundSliderThumbShape(enabledThumbRadius: 10.w), ), diff --git a/lib/presentation/weather/weather_details_page.dart b/lib/presentation/weather/weather_details_page.dart index 8762a55a..50dbefa3 100644 --- a/lib/presentation/weather/weather_details_page.dart +++ b/lib/presentation/weather/weather_details_page.dart @@ -62,7 +62,7 @@ class _WeatherDetailsPageState extends State { case ColorName.ORANGE: return AppColors.warningLightColor.withValues(alpha: 0.1); case ColorName.RED: - return AppColors.errorLightColor.withValues(alpha: 0.1); + return AppColors.primaryRedColor.withValues(alpha: 0.1); } } @@ -120,7 +120,7 @@ class _WeatherDetailsPageState extends State { } // Red: Very High, At High Risk else if (category.contains('very high') || category.contains('at high risk')) { - return AppColors.errorLightColor.withValues(alpha: 0.1); + return AppColors.primaryRedColor.withValues(alpha: 0.1); } // Orange: Risk, High (but not "very high" or "at high risk") else if (category.contains('risk') || category.contains('high')) { diff --git a/lib/services/localauth_service.dart b/lib/services/localauth_service.dart index 2875a7fe..cdce9005 100644 --- a/lib/services/localauth_service.dart +++ b/lib/services/localauth_service.dart @@ -17,10 +17,12 @@ class LocalAuthService { final isAuthenticated = await localAuth.authenticate( localizedReason: 'Please authenticate to proceed', - options: const AuthenticationOptions( - biometricOnly: true, - stickyAuth: true, - ), + persistAcrossBackgrounding: false, // Replaces stickyAuth + biometricOnly: true, + // options: const AuthenticationOptions( + // biometricOnly: true, + // stickyAuth: true, + // ), ); return isAuthenticated; diff --git a/lib/services/logger_service.dart b/lib/services/logger_service.dart index 617539aa..d113f06d 100644 --- a/lib/services/logger_service.dart +++ b/lib/services/logger_service.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:logger/logger.dart'; abstract class LoggerService { @@ -18,6 +20,6 @@ class LoggerServiceImp implements LoggerService { @override void logInfo(String message) { - logger.d(message); + log(message); } } diff --git a/lib/services/remote_config_service.dart b/lib/services/remote_config_service.dart new file mode 100644 index 00000000..d39a7fbe --- /dev/null +++ b/lib/services/remote_config_service.dart @@ -0,0 +1,88 @@ +import 'dart:convert'; + +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter/foundation.dart' show kDebugMode; +import 'package:flutter/services.dart' show rootBundle; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +/// Firebase Remote Config wrapper. Fully generic — the app never hardcodes +/// occasion-specific values here; it only knows about the `occasion_config` +/// remote key and reads whatever JSON is published on the Firebase console. +abstract class RemoteConfigService { + /// Must be called once at startup, before the first frame if possible. + Future initialize(); + + /// Fetches the latest values from Firebase (respecting the configured + /// minimum fetch interval) and activates them. + Future fetchAndActivate(); + + /// Raw JSON array string currently held for the `occasion_config` key — + /// a list of scheduled occasions (bundled default `[]` until the first + /// successful fetch/activate). + String get occasionConfigRawJson; +} + +class RemoteConfigServiceImp implements RemoteConfigService { + static const String occasionConfigKey = 'occasion_config'; + static const String _defaultConfigAssetPath = 'assets/json/default_occasion_config.json'; + + final LoggerService loggerService; + final FirebaseRemoteConfig _remoteConfig; + + RemoteConfigServiceImp({ + required this.loggerService, + FirebaseRemoteConfig? remoteConfig, + }) : _remoteConfig = remoteConfig ?? FirebaseRemoteConfig.instance; + + @override + Future initialize() async { + try { + await _remoteConfig.setConfigSettings( + RemoteConfigSettings( + fetchTimeout: const Duration(seconds: 10), + // In debug builds, always fetch fresh values so you can test + // Remote Config changes instantly without waiting/throttling. + // Release/profile builds keep a 1-hour minimum interval — + // balances timely occasion-theme rollout against not hammering + // Remote Config across the whole user base. + minimumFetchInterval: kDebugMode ? Duration.zero : const Duration(hours: 1), + ), + ); + + // Bundled fallback so behaviour is deterministic (and identical to the + // current app look) before the very first successful remote fetch. + final defaultJson = await rootBundle.loadString(_defaultConfigAssetPath); + await _remoteConfig.setDefaults({occasionConfigKey: defaultJson}); + } catch (ex) { + loggerService.logError('RemoteConfigService.initialize error: $ex'); + } + } + + @override + Future fetchAndActivate() async { + try { + return await _remoteConfig.fetchAndActivate(); + } catch (ex) { + loggerService.logError('RemoteConfigService.fetchAndActivate error: $ex'); + return false; + } + } + + @override + String get occasionConfigRawJson { + try { + final value = _remoteConfig.getString(occasionConfigKey); + if (value.isEmpty) return _fallbackDisabledJson; + // Validate JSON shape early; if malformed, fall back safely. + json.decode(value); + return value; + } catch (ex) { + loggerService.logError('RemoteConfigService.occasionConfigRawJson parse error: $ex'); + return _fallbackDisabledJson; + } + } + + /// Empty schedule — [OccasionConfigModel.resolveActive] treats this the + /// same as "no occasion currently active". + static const String _fallbackDisabledJson = '[]'; +} diff --git a/lib/services/security_service.dart b/lib/services/security_service.dart index b72dffeb..2af9c45a 100644 --- a/lib/services/security_service.dart +++ b/lib/services/security_service.dart @@ -82,7 +82,7 @@ class SecurityServiceImpl implements SecurityService { onObfuscationIssues: () => _handleThreat('Obfuscation Issues', ThreatSeverity.warning), onDebug: () => _handleThreat('Debug Mode', ThreatSeverity.critical), onDeviceBinding: () => _handleThreat('Device Binding', ThreatSeverity.critical), - onDeviceID: () => _handleThreat('Device ID Mismatch', ThreatSeverity.critical), + onDeviceID: () => _handleThreat('Device ID Mismatch', ThreatSeverity.warning), onHooks: () => _handleThreat('Hooks Detected', ThreatSeverity.critical), onPasscode: () => _handleThreat('Passcode Not Set', ThreatSeverity.warning), onPrivilegedAccess: () => _handleThreat('Privileged Access (Root/Jailbreak)', ThreatSeverity.critical), diff --git a/lib/splashPage.dart b/lib/splashPage.dart index cf407d41..b1d03c80 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_callkit_incoming/entities/call_event.dart'; import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:get_it/get_it.dart'; +import 'package:hmg_patient_app_new/app_closed_page.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/app_assets.dart'; @@ -29,6 +30,7 @@ import 'package:hmg_patient_app_new/services/notification_service.dart'; import 'package:hmg_patient_app_new/services/zoom_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/unsafe_device.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:lottie/lottie.dart'; import 'package:safe_device/safe_device.dart'; @@ -121,24 +123,24 @@ class _SplashScreenState extends State { // Set initial app context tags try { final appState = getIt.get(); - + // Set device and app metadata Clarity.setCustomTag('app_version', ApiConsts.appVersionID.toString()); Clarity.setCustomTag('device_type', appState.deviceTypeID ?? 'unknown'); Clarity.setCustomTag('platform', Platform.isIOS ? 'ios' : 'android'); - + // Set user context if authenticated if (appState.isAuthenticated) { final user = appState.getAuthenticatedUser(); if (user != null) { // Set user ID for session tracking Clarity.setCustomUserId(user.patientId.toString()); - + // Set custom tags for filtering sessions Clarity.setCustomTag('user_type', user.patientType?.toString() ?? 'unknown'); Clarity.setCustomTag('user_segment', user.outSa == 0 ? 'in_sa' : 'out_sa'); Clarity.setCustomTag('auth_status', 'authenticated'); - + // Optional: Set other non-sensitive user properties if (user.mobileNumber != null) { // Mask phone number for privacy (show only last 4 digits) @@ -149,10 +151,9 @@ class _SplashScreenState extends State { } else { Clarity.setCustomTag('auth_status', 'guest'); } - + // Send initialization event Clarity.sendCustomEvent('app_initialized'); - } catch (e) { debugPrint('Failed to set Clarity user context: $e'); } @@ -165,8 +166,7 @@ class _SplashScreenState extends State { // GetIt.instance().remove(key: CacheConst.isAppOpenedFromCall); Utils.removeFromPrefs(CacheConst.isAppOpenedFromCall); - Navigator.of(GetIt.instance().navigatorKey.currentContext!) - .pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation()))); + Navigator.of(GetIt.instance().navigatorKey.currentContext!).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation()))); Navigator.pushReplacementNamed( // context, GetIt.instance().navigatorKey.currentContext!, @@ -386,8 +386,6 @@ class _SplashScreenState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.whiteColor, - body: Lottie.asset(AppAnimations.loadingAnimation, - repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.fill) - .center); + body: Lottie.asset(AppAnimations.loadingAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.fill).center); } } diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 1ecbcb61..3b9cbdae 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -5,41 +5,115 @@ class AppColors { /// When true, every getter below returns the dark palette value. static bool isDarkMode = false; + /// Global occasion-theme flag — toggled by OccasionThemeViewModel based on + /// Firebase Remote Config. When false (the default), the app looks exactly + /// like it does today — no colors are affected. + static bool isOccasionThemeActive = false; + + /// Remotely-configured primary color for the currently active occasion + /// (e.g. National Day). Null unless an occasion theme is active. + static Color? occasionPrimaryColor; + + /// Lighter/translucent (11% opacity) variant of [occasionPrimaryColor], + /// derived automatically by OccasionThemeViewModel. Null unless an + /// occasion theme is active. + static Color? occasionSecondaryColor; + static const transparent = Colors.transparent; // ── Scaffold / Background ───────────────────────────────────────────────── static Color get scaffoldBgColor => isDarkMode ? dark.scaffoldBgColor : const Color(0xFFF8F8F8); + static Color get bottomSheetBgColor => isDarkMode ? dark.bottomSheetBgColor : const Color(0xFFF8F8FA); + static Color get lightGreyEFColor => isDarkMode ? dark.lightGreyEFColor : const Color(0xffeaeaff); + static Color get greyF7Color => isDarkMode ? dark.greyF7Color : const Color(0xffF7F7F7); + static Color get greyInfoTextColor => isDarkMode ? dark.greyInfoTextColor : const Color(0xff777777); + static Color get lightGrayColor => isDarkMode ? dark.lightGrayColor : const Color(0xff808080); + static Color get greyTextColorLight => isDarkMode ? dark.greyTextColorLight : const Color(0xFFA2A2A2); + static Color get mainPurple => isDarkMode ? dark.mainPurple : const Color(0xFF7954F7); // ── Core Colors ─────────────────────────────────────────────────────────── static Color get whiteColor => isDarkMode ? dark.whiteColor : const Color(0xFFffffff); + // static const Color primaryRedColor = Color(0xFFED1C2B); - static Color get primaryRedColor => isDarkMode ? dark.primaryRedColor : Color(0xFFED1C2B); - static Color get secondaryLightRedColor => isDarkMode ? dark.secondaryLightRedColor : const Color(0xFFFEE9EA); + /// Occasion-aware primary brand color. Resolves to the remotely-configured + /// occasion color when active (e.g. National Day green), otherwise falls + /// back to the original dark/light red — identical to current behavior. + /// + /// NOTE: Do NOT use this getter for `.withOpacity()/.withValues()` derived + /// (translucent) colors — use [primaryRedColorBase] for those instead, so + /// occasion theming never affects translucent overlays/backgrounds. + static Color get primaryRedColor => (isOccasionThemeActive && occasionPrimaryColor != null) ? occasionPrimaryColor! : primaryRedColorBase; + + /// The original, non-themed brand red (or its dark-mode variant). Always + /// stable regardless of any active occasion theme — intended for opacity + /// derived usages (e.g. `AppColors.primaryRedColorBase.withOpacity(0.1)`). + static Color get primaryRedColorBase => isDarkMode ? dark.primaryRedColor : const Color(0xFFED1C2B); + + /// Semantic color for critical/negative actions (delete/deactivate account, + /// cancel appointment, clear/reset, emergency, etc.). Same light/dark + /// values as [primaryRedColorBase] — respects dark mode, but is never + /// affected by the active occasion/remote-config theme, so destructive + /// actions always read as "red" regardless of any occasion branding. + static Color get criticalActionColor => primaryRedColorBase; + + /// Occasion-aware light/tint variant of [primaryRedColor] (e.g. chip and + /// badge backgrounds). Resolves to the remotely-configured occasion's + /// secondary color when active (e.g. National Day's light green), + /// otherwise falls back to the original dark/light tint — identical to + /// current behavior. + /// + /// NOTE: Do NOT use this getter for `.withOpacity()/.withValues()` derived + /// colors — use [secondaryLightRedColorBase] for those instead, so + /// occasion theming never affects translucent overlays/backgrounds. + static Color get secondaryLightRedColor => (isOccasionThemeActive && occasionSecondaryColor != null) ? occasionSecondaryColor! : secondaryLightRedColorBase; + + /// The original, non-themed light-red tint (or its dark-mode variant). + /// Always stable regardless of any active occasion theme — intended for + /// opacity derived usages. + static Color get secondaryLightRedColorBase => isDarkMode ? dark.secondaryLightRedColor : const Color(0xFFFEE9EA); static const Color successColor = Color(0xFF18C273); + static Color get successLightBgColor => isDarkMode ? dark.successLightBgColor : const Color(0xffDDF6EA); + static Color get greyColor => isDarkMode ? dark.greyColor : const Color(0xFFEFEFF0); + static Color get chipBgColor => isDarkMode ? dark.chipBgColor : const Color(0xFFEFEFF0); + static Color get textColor => isDarkMode ? dark.textColor : const Color(0xFF2E3039); + static Color get iconColor => isDarkMode ? dark.iconColor : const Color(0xFF2E3039); + static Color get borderGrayColor => isDarkMode ? dark.borderGrayColor : const Color(0x332E3039); + static Color get textColorLight => isDarkMode ? dark.textColorLight : const Color(0xFF5E5E5E); + static Color get dividerColor => isDarkMode ? dark.dividerColor : const Color(0x40D2D2D2); + static Color get warningColorYellow => isDarkMode ? dark.warningColorYellow : const Color(0xFFF4A308); + static Color get inputLabelTextColor => isDarkMode ? dark.inputLabelTextColor : const Color(0xff898A8D); + static Color get greyTextColor => isDarkMode ? dark.greyTextColor : const Color(0xFF8F9AA3); + static Color get lightGrayBGColor => isDarkMode ? dark.lightGrayBGColor : const Color(0x142E3039); + static Color get checkBoxBorderColor => isDarkMode ? dark.checkBoxBorderColor : const Color(0xffD2D2D2); + static Color get pharmacyBGColor => isDarkMode ? dark.pharmacyBGColor : const Color(0xFF359846); + static Color get lightGreenColor => isDarkMode ? dark.lightGreenColor : const Color(0xFF0ccedde); + static Color get ratingColorYellow => isDarkMode ? dark.ratingColorYellow : const Color(0xFFFFAF15); + static Color get spacerLineColor => isDarkMode ? dark.spacerLineColor : const Color(0x2E30391A); + static Color get habibPharmacyColor => const Color(0xFF2d9137); // ── Doctor Rating Colors ────────────────────────────────────────────────── @@ -52,92 +126,147 @@ class AppColors { static const Color ratingBadgeFiveStarColor = Color(0xFF5BBD8E); // Green badge for ≥4.0 static const Color ratingPercentageTextColor = Color(0xFF4A4A4A); // Dark grey for percentage text - // ── Semantic / Status Colors ────────────────────────────────────────────── static Color get errorColor => isDarkMode ? dark.errorColor : const Color(0xFFED1C2B); + static Color get alertColor => isDarkMode ? dark.alertColor : const Color(0xFFD48D05); + static Color get infoColor => isDarkMode ? dark.infoColor : const Color(0xFF0B85F7); + static Color get warningColor => isDarkMode ? dark.warningColor : const Color(0xFFFFCC00); + static Color get switchBackgroundColor => isDarkMode ? dark.switchBackgroundColor : const Color(0x2618C273); + static Color get bottomNAVBorder => isDarkMode ? dark.bottomNAVBorder : const Color(0xFFEEEEEE); + static Color get quickLoginColor => isDarkMode ? dark.quickLoginColor : const Color(0xFF666666); + static Color get tooltipTextColor => isDarkMode ? dark.tooltipTextColor : const Color(0xFF414D55); + static Color get graphGridColor => isDarkMode ? dark.graphGridColor : const Color(0x4D18C273); + static Color get highAndLow => isDarkMode ? dark.highAndLow : const Color(0xFFFFAF15); + static Color get labelTextColor => isDarkMode ? dark.labelTextColor : const Color(0xFF838383); + static Color get calenderTextColor => isDarkMode ? dark.calenderTextColor : const Color(0xFFD0D0D0); + static Color get lightGreenButtonColor => isDarkMode ? dark.lightGreenButtonColor : const Color(0x2618C273); + static Color get lightRedButtonColor => isDarkMode ? dark.lightRedButtonColor : const Color(0x1AED1C2B); // ── Status Colors ───────────────────────────────────────────────────────── static Color get statusPendingColor => isDarkMode ? dark.statusPendingColor : const Color(0xffCC9B14); + static Color get statusProcessingColor => isDarkMode ? dark.statusProcessingColor : const Color(0xff2E303A); + static Color get statusCompletedColor => isDarkMode ? dark.statusCompletedColor : const Color(0xff359846); + static Color get statusRejectedColor => isDarkMode ? dark.statusRejectedColor : const Color(0xffD02127); // ── Info Banner Colors ──────────────────────────────────────────────────── static Color get infoBannerBgColor => isDarkMode ? dark.infoBannerBgColor : const Color(0xFFFFF4E6); + static Color get infoBannerBorderColor => isDarkMode ? dark.infoBannerBorderColor : const Color(0xFFFFE5B4); + static Color get infoBannerIconColor => isDarkMode ? dark.infoBannerIconColor : const Color(0xFFCC9B14); + static Color get infoBannerTextColor => isDarkMode ? dark.infoBannerTextColor : const Color(0xFF856404); // ── Symptoms Checker ─────────────────────────────────────────────��──────── static Color get chipColorSeekMedicalAdvice => isDarkMode ? dark.chipColorSeekMedicalAdvice : const Color(0xFFFFAF15); + static Color get chipTextColorSeekMedicalAdvice => isDarkMode ? dark.chipTextColorSeekMedicalAdvice : const Color(0xFFD48D05); // ── Services Page Colors ────────────────────────────────────────────────── static Color get eReferralCardColor => isDarkMode ? dark.eReferralCardColor : const Color(0xFFFF8012); + static Color get bloodDonationCardColor => isDarkMode ? dark.bloodDonationCardColor : const Color(0xFFFF5662); + static Color get myChildVaccineCardColor => isDarkMode ? dark.myChildVaccineCardColor : const Color(0xFFFF2D78); + static Color get bookAppointment => isDarkMode ? dark.bookAppointment : const Color(0xFF415364); // ── Health Calculators Colors ───────────────────────────────────────────── static Color get bmiCalculatorColor => isDarkMode ? dark.bmiCalculatorColor : const Color(0xFFF43333); + static Color get caloriesCalculatorColor => isDarkMode ? dark.caloriesCalculatorColor : const Color(0xFF0B85F7); + static Color get bmrCalculatorColor => isDarkMode ? dark.bmrCalculatorColor : const Color(0xFF18C273); + static Color get idealBodyWeightColor => isDarkMode ? dark.idealBodyWeightColor : const Color(0xFFFF8012); + static Color get bodyFatCalculatorColor => isDarkMode ? dark.bodyFatCalculatorColor : const Color(0xFFFF5662); + static Color get carbsProteinFatColor => isDarkMode ? dark.carbsProteinFatColor : const Color(0xFF18C273); + static Color get ovulationPeriodColor => isDarkMode ? dark.ovulationPeriodColor : const Color(0xFFFF47A9); + static Color get deliveryDueDateColor => isDarkMode ? dark.deliveryDueDateColor : const Color(0xFFFF2D78); // ── Water Monitor ───────────────────────────────────────────────────────── static Color get blueColor => isDarkMode ? dark.blueColor : const Color(0xFF4EB5FF); + static Color get blueGradientColorOne => isDarkMode ? dark.blueGradientColorOne : const Color(0xFFF1F7FD); + static Color get blueGradientColorTwo => isDarkMode ? dark.blueGradientColorTwo : const Color(0xFFD9EFFF); // ── Shimmer ─────────────────────────────────────────────────────────────── static Color get shimmerBaseColor => isDarkMode ? dark.shimmerBaseColor : const Color(0xFFE0E0E0); + static Color get shimmerHighlightColor => isDarkMode ? dark.shimmerHighlightColor : const Color(0xFFF5F5F5); + static Color get covid29Color => isDarkMode ? dark.covid29Color : const Color(0xff2563EB); + static Color get lightGreyTextColor => isDarkMode ? dark.lightGreyTextColor : const Color(0xFF959595); + static Color get labelColorYellow => isDarkMode ? dark.labelColorYellow : const Color(0xFFFBCB6E); - static const LinearGradient aiLinearGradient = - LinearGradient(colors: [Color(0xFF8A38F5), Color(0xFFE20BBB)], begin: Alignment.topLeft, end: Alignment.bottomRight); + static const LinearGradient aiLinearGradient = LinearGradient(colors: [Color(0xFF8A38F5), Color(0xFFE20BBB)], begin: Alignment.topLeft, end: Alignment.bottomRight); // ── Aliases ─────────────────────────────────────────────────────────────── static Color get bgScaffoldColor => scaffoldBgColor; + static Color get primaryRedBorderColor => primaryRedColor; + static Color get chipPrimaryRedBorderColor => primaryRedColor; + static Color get criticalLowAndHigh => primaryRedColor; + static Color get chipColorEmergency => primaryRedColor; + static Color get errorLightColor => primaryRedColor; + static Color get secondaryLightRedBorderColor => secondaryLightRedColor; + static Color get bgRedLightColor => secondaryLightRedColor; + static Color get chipSecondaryLightRedColor => secondaryLightRedColor; + static Color get blackBgColor => textColor; + static Color get blackColor => textColor; + static Color get borderOnlyColor => textColor; + static Color get chipBorderColorOpacity20 => borderGrayColor; + static Color get bgGreenColor => successColor; + static Color get textGreenColor => successColor; + static Color get successLightColor => successColor; + static Color get thumbColor => successColor; + static Color get chipColorMonitor => successColor; + static Color get greyLightColor => greyColor; + static Color get alertLightColor => alertColor; + static Color get infoLightColor => infoColor; + static Color get warningLightColor => warningColor; // ── Dark Mode Palette ───────────────────────────────────────────────────── @@ -152,11 +281,17 @@ class AppColorsDark { // Scaffold / Background Color get scaffoldBgColor => const Color(0xFF191919); + Color get bottomSheetBgColor => const Color(0xFF1C1C1E); + Color get lightGreyEFColor => const Color(0xFF2A2A3A); + Color get greyF7Color => const Color(0xFF1E1E1E); + Color get greyInfoTextColor => const Color(0xFF9E9E9E); + Color get lightGrayColor => const Color(0xFF9E9E9E); + Color get greyTextColorLight => const Color(0xFF757575); // ⚠️ NEW — was missing from AppColorsDark @@ -164,114 +299,192 @@ class AppColorsDark { // Core Color get baseColor => const Color(0xFF1E1E1E); + // ⚠️ NEW — maps whiteColor → dark surface color Color get whiteColor => const Color(0xFF1E1E1E); // surface replacement for white Color get primaryRedColor => const Color(0xFFDE5C5D); + Color get secondaryLightRedColor => const Color(0xFF3A1015); + Color get successColor => const Color(0xFF18C273); + Color get successLightBgColor => const Color(0xFF0D2E1F); + Color get textColor => const Color(0xFFFFFFFF); + Color get iconColor => const Color(0xFFFFFFFF); + Color get borderGrayColor => const Color(0x55ECECEC); + Color get textColorLight => const Color(0xFFB0B0B0); + Color get dividerColor => const Color(0x33FFFFFF); + Color get warningColorYellow => const Color(0xFFF4A308); + Color get inputLabelTextColor => const Color(0xFF9E9E9E); + Color get greyTextColor => const Color(0xFF8F9AA3); + Color get lightGrayBGColor => const Color(0x22FFFFFF); + Color get checkBoxBorderColor => const Color(0xFF555555); + Color get pharmacyBGColor => const Color(0xFF359846); + // ⚠️ NEW — was missing from AppColorsDark Color get lightGreenColor => const Color(0xFF0ccedde); // same teal accent — works on dark Color get ratingColorYellow => const Color(0xFFFFAF15); + Color get spacerLineColor => const Color(0x22FFFFFF); // Semantic / Status Color get errorColor => const Color(0xFFD63D48); + Color get alertColor => const Color(0xFFD48D05); + Color get infoColor => const Color(0xFF4DA6FF); + Color get warningColor => const Color(0xFFFFCC00); + Color get greyColor => const Color(0xFF2C2C2E); + Color get chipBgColor => const Color(0xFF3E3E3E); + Color get cardDarkBorderColor => const Color(0x0DEAEAEA); + Color get serviceCardButtonColor => const Color(0xFF3E3E3E); + Color get switchBackgroundColor => const Color(0x2618C273); + Color get bottomNAVBorder => const Color(0xFF2C2C2E); + Color get quickLoginColor => const Color(0xFF9E9E9E); + Color get tooltipTextColor => const Color(0xFFCFD8DC); + Color get graphGridColor => const Color(0x4D18C273); + Color get highAndLow => const Color(0xFFFFAF15); + Color get labelTextColor => const Color(0xFF9E9E9E); + Color get calenderTextColor => const Color(0xFF555555); + Color get lightGreenButtonColor => const Color(0x2618C273); + Color get lightRedButtonColor => const Color(0x1AED1C2B); // Status Color get statusPendingColor => const Color(0xFFCC9B14); + Color get statusProcessingColor => const Color(0xFFECECEC); + Color get statusCompletedColor => const Color(0xFF359846); + Color get statusRejectedColor => const Color(0xFFD02127); // Info Banner Color get infoBannerBgColor => const Color(0xFF2A1F0A); + Color get infoBannerBorderColor => const Color(0xFF5A3D0A); + Color get infoBannerIconColor => const Color(0xFFCC9B14); + Color get infoBannerTextColor => const Color(0xFFFFCB6B); // Symptoms Checker Color get chipColorSeekMedicalAdvice => const Color(0xFFFFAF15); + Color get chipTextColorSeekMedicalAdvice => const Color(0xFFD48D05); // Services Page Color get eReferralCardColor => const Color(0xFFFF8012); + Color get bloodDonationCardColor => const Color(0xFFFF5662); + Color get myChildVaccineCardColor => const Color(0xFFFF2D78); + Color get bookAppointment => const Color(0xFF415364); // Health Calculators Color get bmiCalculatorColor => const Color(0xFFF43333); + Color get caloriesCalculatorColor => const Color(0xFF0B85F7); + Color get bmrCalculatorColor => const Color(0xFF18C273); + Color get idealBodyWeightColor => const Color(0xFFFF8012); + Color get bodyFatCalculatorColor => const Color(0xFFFF5662); + Color get carbsProteinFatColor => const Color(0xFF18C273); + Color get ovulationPeriodColor => const Color(0xFFFF47A9); + Color get deliveryDueDateColor => const Color(0xFFFF2D78); // Water Monitor Color get blueColor => const Color(0xFF4EB5FF); + Color get blueGradientColorOne => const Color(0xFF101C2A); + Color get blueGradientColorTwo => const Color(0xFF0D2235); // Shimmer Color get shimmerBaseColor => const Color(0xFF2C2C2C); + Color get shimmerHighlightColor => const Color(0xFF3D3D3D); + Color get covid29Color => const Color(0xFF2563EB); + Color get lightGreyTextColor => const Color(0xFF757575); + Color get labelColorYellow => const Color(0xFFFBCB6E); // Aliases Color get bgScaffoldColor => scaffoldBgColor; + Color get primaryRedBorderColor => primaryRedColor; + Color get chipPrimaryRedBorderColor => primaryRedColor; + Color get criticalLowAndHigh => primaryRedColor; + Color get chipColorEmergency => primaryRedColor; + Color get errorLightColor => primaryRedColor; + Color get secondaryLightRedBorderColor => secondaryLightRedColor; + Color get bgRedLightColor => secondaryLightRedColor; + Color get chipSecondaryLightRedColor => secondaryLightRedColor; + Color get blackBgColor => textColor; + Color get blackColor => textColor; + Color get borderOnlyColor => textColor; + Color get chipBorderColorOpacity20 => borderGrayColor; + Color get bgGreenColor => successColor; + Color get textGreenColor => successColor; + Color get successLightColor => successColor; + Color get thumbColor => successColor; + Color get chipColorMonitor => successColor; + Color get greyLightColor => greyColor; + Color get alertLightColor => alertColor; + Color get infoLightColor => infoColor; + Color get warningLightColor => warningColor; } @@ -283,61 +496,99 @@ extension AppColorsContext on BuildContext { // Scaffold / Background Color get scaffoldBgColor => _isDark ? AppColors.dark.scaffoldBgColor : const Color(0xFFF8F8F8); + Color get bottomSheetBgColor => _isDark ? AppColors.dark.bottomSheetBgColor : const Color(0xFFF8F8FA); + Color get greyF7Color => _isDark ? AppColors.dark.greyF7Color : const Color(0xffF7F7F7); + Color get greyInfoTextColor => _isDark ? AppColors.dark.greyInfoTextColor : const Color(0xff777777); + Color get lightGrayColor => _isDark ? AppColors.dark.lightGrayColor : const Color(0xff808080); // Core Color get cardBaseColor => _isDark ? AppColors.dark.baseColor : const Color(0xFFFFFFFF); + Color get primaryRedColor => _isDark ? AppColors.dark.primaryRedColor : AppColors.primaryRedColor; + Color get secondaryLightRedColor => _isDark ? AppColors.dark.serviceCardButtonColor : const Color(0xFFFEE9EA); + Color get secondaryLightRedForegroundColor => _isDark ? const Color(0xFFFFFFFF) : AppColors.primaryRedColor; + Color get successColor => AppColors.successColor; + Color get successLightBgColor => _isDark ? AppColors.dark.successLightBgColor : const Color(0xffDDF6EA); + Color get textColor => _isDark ? AppColors.dark.textColor : const Color(0xFF2E3039); + Color get iconColor => _isDark ? AppColors.dark.iconColor : const Color(0xFF2E3039); + Color get borderGrayColor => _isDark ? AppColors.dark.borderGrayColor : const Color(0x332E3039); + Color get textColorLight => _isDark ? AppColors.dark.textColorLight : const Color(0xFF5E5E5E); + Color get dividerColor => _isDark ? AppColors.dark.dividerColor : const Color(0x40D2D2D2); + Color get inputLabelTextColor => _isDark ? AppColors.dark.inputLabelTextColor : const Color(0xff898A8D); + Color get greyTextColor => AppColors.greyTextColor; + Color get lightGrayBGColor => _isDark ? AppColors.dark.lightGrayBGColor : const Color(0x142E3039); + Color get checkBoxBorderColor => _isDark ? AppColors.dark.checkBoxBorderColor : const Color(0xffD2D2D2); // Status / Semantic Color get errorColor => _isDark ? AppColors.dark.errorColor : const Color(0xFFED1C2B); + Color get alertColor => AppColors.alertColor; + Color get infoColor => _isDark ? AppColors.dark.infoColor : const Color(0xFF0B85F7); + Color get warningColor => AppColors.warningColor; + Color get greyColor => _isDark ? AppColors.dark.greyColor : const Color(0xFFEFEFF0); + Color get chipBgColor => _isDark ? AppColors.dark.chipBgColor : const Color(0xFFEFEFF0); + Color get cardDarkBorderColor => _isDark ? AppColors.dark.cardDarkBorderColor : Colors.transparent; + Color get serviceCardButtonColor => _isDark ? AppColors.dark.serviceCardButtonColor : const Color(0xFF2E3039); + Color get bottomNAVBorder => _isDark ? AppColors.dark.bottomNAVBorder : const Color(0xFFEEEEEE); + Color get labelTextColor => _isDark ? AppColors.dark.labelTextColor : const Color(0xFF838383); + Color get lightGreyTextColor => _isDark ? AppColors.dark.lightGreyTextColor : const Color(0xFF959595); // Info Banner Color get infoBannerBgColor => _isDark ? AppColors.dark.infoBannerBgColor : const Color(0xFFFFF4E6); + Color get infoBannerBorderColor => _isDark ? AppColors.dark.infoBannerBorderColor : const Color(0xFFFFE5B4); + Color get infoBannerIconColor => AppColors.infoBannerIconColor; + Color get infoBannerTextColor => _isDark ? AppColors.dark.infoBannerTextColor : const Color(0xFF856404); // Water Monitor Color get blueColor => AppColors.blueColor; + Color get blueGradientColorOne => _isDark ? AppColors.dark.blueGradientColorOne : const Color(0xFFF1F7FD); + Color get blueGradientColorTwo => _isDark ? AppColors.dark.blueGradientColorTwo : const Color(0xFFD9EFFF); // Shimmer Color get shimmerBaseColor => _isDark ? AppColors.dark.shimmerBaseColor : const Color(0xFFE0E0E0); + Color get shimmerHighlightColor => _isDark ? AppColors.dark.shimmerHighlightColor : const Color(0xFFF5F5F5); static const Color tooltipColor = Color(0xFF1AACACAC); // Aliases Color get bgScaffoldColor => scaffoldBgColor; + Color get bgGreenColor => successColor; + Color get bgRedLightColor => secondaryLightRedColor; + Color get blackColor => textColor; + Color get blackBgColor => textColor; } diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index c3ebe4ba..0c677002 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -115,8 +115,7 @@ class CollapsingListView extends StatelessWidget { margin: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0), child: Transform.flip( flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: isClose ? AppAssets.close_bottom_nav_trans : AppAssets.arrow_back_new, width: 24.h, height: 24.h), + child: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.close_bottom_nav_trans : AppAssets.arrow_back_new, width: 24.h, height: 24.h), ), ).onPress(() { if (leadingCallback != null) { @@ -289,8 +288,7 @@ class _ScrollAnimatedTitleState extends State { return Container( // height: (widget.preferredSize.height - _fontSize / 2).h, height: 56.h, - alignment: - isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft), + alignment: isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft), padding: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0), child: Row( spacing: 4.h, @@ -306,38 +304,19 @@ class _ScrollAnimatedTitleState extends State { ), ).expanded, ...[ - if (widget.logout != null) - actionButton(context, t, title: LocaleKeys.logout.tr(context: context), icon: AppAssets.logout).onPress(widget.logout!), - if (widget.report != null) - actionButton(context, t, title: LocaleKeys.feedback.tr(context: context), icon: AppAssets.report_icon).onPress(widget.report!), - if (widget.history != null) - actionButton(context, t, title: LocaleKeys.history.tr(context: context), icon: AppAssets.insurance_history_icon) - .onPress(widget.history!), - if (widget.instructions != null) - actionButton(context, t, title: LocaleKeys.instructions.tr(context: context), icon: AppAssets.requests).onPress(widget.instructions!), - if (widget.requests != null) - actionButton(context, t, title: LocaleKeys.requests.tr(context: context), icon: AppAssets.insurance_history_icon) - .onPress(widget.requests!), - if (widget.sendEmail != null) - actionButton(context, t, title: LocaleKeys.sendEmail.tr(context: context), icon: AppAssets.email).onPress(widget.sendEmail!), - if (widget.doctorResponse != null) - actionButton(context, t, title: LocaleKeys.doctorResponses.tr(context: context), icon: AppAssets.doctorResponseIcon) - .onPress(widget.doctorResponse!), + if (widget.logout != null) actionButton(context, t, title: LocaleKeys.logout.tr(context: context), icon: AppAssets.logout).onPress(widget.logout!), + if (widget.report != null) actionButton(context, t, title: LocaleKeys.feedback.tr(context: context), icon: AppAssets.report_icon).onPress(widget.report!), + if (widget.history != null) actionButton(context, t, title: LocaleKeys.history.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.history!), + if (widget.instructions != null) actionButton(context, t, title: LocaleKeys.instructions.tr(context: context), icon: AppAssets.requests).onPress(widget.instructions!), + if (widget.requests != null) actionButton(context, t, title: LocaleKeys.requests.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.requests!), + if (widget.sendEmail != null) actionButton(context, t, title: LocaleKeys.sendEmail.tr(context: context), icon: AppAssets.email).onPress(widget.sendEmail!), + if (widget.doctorResponse != null) actionButton(context, t, title: LocaleKeys.doctorResponses.tr(context: context), icon: AppAssets.doctorResponseIcon).onPress(widget.doctorResponse!), if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!), - if (widget.aiOverview != null) - actionButton(context, t, title: LocaleKeys.aiOverView.tr(context: context), icon: AppAssets.aiOverView, isAiButton: true) - .onPress(widget.aiOverview!), - if (widget.downloadReport != null) - actionButton(context, t, title: LocaleKeys.downloadReport.tr(context: context), icon: AppAssets.download) - .onPress(widget.downloadReport!), - if (widget.viewImage != null) - actionButton(context, t, title: LocaleKeys.viewRadiologyImage.tr(context: context), icon: AppAssets.download) - .onPress(widget.viewImage!), - if (widget.location != null) - actionButton(context, t, title: LocaleKeys.sortByLocation.tr(context: context), icon: AppAssets.location).onPress(widget.location!), - if (widget.downloadInvoice != null) - actionButton(context, t, title: LocaleKeys.downloadInvoice.tr(context: context), icon: AppAssets.download) - .onPress(widget.downloadInvoice!), + if (widget.aiOverview != null) actionButton(context, t, title: LocaleKeys.aiOverView.tr(context: context), icon: AppAssets.aiOverView, isAiButton: true).onPress(widget.aiOverview!), + if (widget.downloadReport != null) actionButton(context, t, title: LocaleKeys.downloadReport.tr(context: context), icon: AppAssets.download).onPress(widget.downloadReport!), + if (widget.viewImage != null) actionButton(context, t, title: LocaleKeys.viewRadiologyImage.tr(context: context), icon: AppAssets.download).onPress(widget.viewImage!), + if (widget.location != null) actionButton(context, t, title: LocaleKeys.sortByLocation.tr(context: context), icon: AppAssets.location).onPress(widget.location!), + if (widget.downloadInvoice != null) actionButton(context, t, title: LocaleKeys.downloadInvoice.tr(context: context), icon: AppAssets.download).onPress(widget.downloadInvoice!), if (widget.trailing != null) widget.trailing!, ] ], @@ -353,8 +332,7 @@ class _ScrollAnimatedTitleState extends State { child: Container( height: isFoldable ? 50.h : 40.h, padding: EdgeInsets.all(8.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, borderRadius: 8.r, side: BorderSide(width: 1, color: AppColors.borderGrayColor)), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 8.r, side: BorderSide(width: 1, color: AppColors.borderGrayColor)), child: ShaderMask( blendMode: BlendMode.srcIn, shaderCallback: (bounds) => AppColors.aiLinearGradient.createShader(bounds), diff --git a/lib/widgets/bottom_navigation/bottom_navigation.dart b/lib/widgets/bottom_navigation/bottom_navigation.dart index e999842f..a5b8b239 100644 --- a/lib/widgets/bottom_navigation/bottom_navigation.dart +++ b/lib/widgets/bottom_navigation/bottom_navigation.dart @@ -28,7 +28,13 @@ class BottomNavigation extends StatelessWidget { icon: AppAssets.bookAppoBottom, fillIcon: AppAssets.bookAppoBottom, label: LocaleKeys.appointments.tr(context: context), - iconSize: 32, isSpecial: true), + iconSize: 32, + isSpecial: true, + // Explicit dynamic color — the SVG asset itself is a fixed red, so + // without this override the Appointments icon would never react to + // dark mode or the active occasion theme (e.g. National Day green). + color: AppColors.primaryRedColor, + ), appState.isAuthenticated // ? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr(context: context)) ? BottomNavItem(icon: AppAssets.symptomCheckerBottomIcon, fillIcon: AppAssets.symptomCheckerBottomFillIcon, label: LocaleKeys.symptoms.tr(context: context)) @@ -75,7 +81,8 @@ class BottomNavigation extends StatelessWidget { icon: isSelected ? item.fillIcon : item.icon, height: item.iconSize.h, width: item.iconSize.w, - applyThemeColor: !item.isSpecial + applyThemeColor: !item.isSpecial, + iconColor: item.color, // iconColor: isSelected ? Colors.black87 : Colors.black87, ), // const SizedBox(height: 10), diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index 82b62507..8b8e599a 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -10,8 +10,8 @@ class CustomButton extends StatefulWidget { final String text; final String? icon; final VoidCallback? onPressed; - final Color backgroundColor; - final Color borderColor; + final Color? backgroundColor; + final Color? borderColor; final Color textColor; final double? borderRadius; final double borderWidth; @@ -33,8 +33,8 @@ class CustomButton extends StatefulWidget { super.key, required this.text, required this.onPressed, - this.backgroundColor = const Color(0xFFED1C2B), - this.borderColor = const Color(0xFFED1C2B), + this.backgroundColor, + this.borderColor, this.textColor = Colors.white, this.borderRadius, this.borderWidth = 2, @@ -93,6 +93,8 @@ class _CustomButtonState extends State with SingleTickerProviderSt final iconS = widget.iconSize ?? (24.h); final fontS = widget.fontSize ?? (16.f); final isButtonDisabled = widget.isDisabled || widget.onPressed == null; + final resolvedBackgroundColor = widget.backgroundColor ?? AppColors.primaryRedColor; + final resolvedBorderColor = widget.borderColor ?? AppColors.primaryRedColor; return Semantics( button: true, @@ -134,10 +136,10 @@ class _CustomButtonState extends State with SingleTickerProviderSt width: widget.width, padding: widget.padding, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: isButtonDisabled ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : widget.backgroundColor, + color: isButtonDisabled ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : resolvedBackgroundColor, borderRadius: radius, customBorder: BorderRadius.circular(radius), - side: widget.borderSide ?? BorderSide(width: widget.borderWidth.h, color: isButtonDisabled ? Colors.transparent : widget.borderColor)), + side: widget.borderSide ?? BorderSide(width: widget.borderWidth.h, color: isButtonDisabled ? Colors.transparent : resolvedBorderColor)), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/widgets/date_range_selector/date_range_calender.dart b/lib/widgets/date_range_selector/date_range_calender.dart index a544f010..28098824 100644 --- a/lib/widgets/date_range_selector/date_range_calender.dart +++ b/lib/widgets/date_range_selector/date_range_calender.dart @@ -163,7 +163,7 @@ class _DateRangeSelectorState extends State { ), startRangeSelectionColor: AppColors.primaryRedColor, endRangeSelectionColor: AppColors.primaryRedColor, - rangeSelectionColor: AppColors.primaryRedColor.withOpacity(0.1), + rangeSelectionColor: AppColors.secondaryLightRedColor, rangeTextStyle: TextStyle( fontFamily: "Poppins", ), @@ -963,7 +963,7 @@ class _DateRangeSelectorState extends State { children: [ AppCustomChipWidget( labelText: LocaleKeys.thisWeek.tr(context: context), - backgroundColor: model.currentlySelectedRange == Range.WEEKLY ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, + backgroundColor: model.currentlySelectedRange == Range.WEEKLY ? AppColors.secondaryLightRedColor : AppColors.whiteColor, shape: RoundedRectangleBorder( side: BorderSide( color: model.currentlySelectedRange == Range.WEEKLY ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20, @@ -977,7 +977,7 @@ class _DateRangeSelectorState extends State { }), AppCustomChipWidget( labelText: LocaleKeys.lastMonth.tr(context: context), - backgroundColor: model.currentlySelectedRange == Range.LAST_MONTH ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, + backgroundColor: model.currentlySelectedRange == Range.LAST_MONTH ? AppColors.secondaryLightRedColor : AppColors.whiteColor, shape: RoundedRectangleBorder( side: BorderSide( color: model.currentlySelectedRange == Range.LAST_MONTH ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20, @@ -991,7 +991,7 @@ class _DateRangeSelectorState extends State { }), AppCustomChipWidget( labelText: LocaleKeys.lastSixMonths.tr(context: context), - backgroundColor: model.currentlySelectedRange == Range.LAST_6MONTH ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, + backgroundColor: model.currentlySelectedRange == Range.LAST_6MONTH ? AppColors.secondaryLightRedColor : AppColors.whiteColor, shape: RoundedRectangleBorder( side: BorderSide( color: model.currentlySelectedRange == Range.LAST_6MONTH ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20, @@ -1012,7 +1012,7 @@ class _DateRangeSelectorState extends State { ], ), labelText: "${LocaleKeys.year.tr(context: context)} ${model.getCurrentYear}", - backgroundColor: model.currentlySelectedRange == Range.THIS_YEAR ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, + backgroundColor: model.currentlySelectedRange == Range.THIS_YEAR ? AppColors.secondaryLightRedColor : AppColors.whiteColor, shape: RoundedRectangleBorder( side: BorderSide( color: model.currentlySelectedRange == Range.THIS_YEAR ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20, diff --git a/lib/widgets/in_app_browser/InAppBrowser.dart b/lib/widgets/in_app_browser/InAppBrowser.dart index fe08fbbe..1d3b6fa4 100644 --- a/lib/widgets/in_app_browser/InAppBrowser.dart +++ b/lib/widgets/in_app_browser/InAppBrowser.dart @@ -307,12 +307,15 @@ class MyInAppBrowser extends InAppBrowser { form = form.replaceFirst('CUSTNAME_VALUE', patientName); form = form.replaceFirst('CUSTID_VALUE', patientID.toString()); + form = form.replaceFirst('APPOINTMENT_DATE_VALUE', appoDate); + form = form.replaceFirst('APPOINTMENT_NO_VALUE', appoNo.toString()); + form = form.replaceFirst('DOCTOR_ID_VALUE', doctorID.toString()); + form = form.replaceFirst('CLINIC_ID_VALUE', clinicID.toString()); + if (isLiveCareAppo) { form = form.replaceFirst('IS_SCHEDULE_VALUE', "true"); - form = form.replaceFirst('APPOINTMENT_DATE_VALUE', appoDate); - form = form.replaceFirst('APPOINTMENT_NO_VALUE', appoNo.toString()); - form = form.replaceFirst('DOCTOR_ID_VALUE', doctorID.toString()); - form = form.replaceFirst('CLINIC_ID_VALUE', clinicID.toString()); + } else { + form = form.replaceFirst('IS_SCHEDULE_VALUE', "false"); } var bytes = utf8.encode(form); @@ -363,6 +366,10 @@ class MyInAppBrowser extends InAppBrowser { '' + '' + '' + + '' + + '' + + '' + + '' + '' + '' + '' + diff --git a/lib/widgets/language_switcher.dart b/lib/widgets/language_switcher.dart index 15237363..7a556c84 100644 --- a/lib/widgets/language_switcher.dart +++ b/lib/widgets/language_switcher.dart @@ -56,21 +56,22 @@ class _LanguageSelectorState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text( // currentLangData['name']?.toUpperCase() ?? 'EN', widget.currentLanguage == 'ar' ? "ENGLISH" : "العربية", style: context.dynamicTextStyle( fontWeight: FontWeight.w600, - fontSize: 14.f, - color: AppColors.primaryRedColor, - letterSpacing: 0.1, - isLanguageSwitcher: true, + fontSize: 14.f, + color: AppColors.primaryRedColor, + letterSpacing: 0.1, + isLanguageSwitcher: true, fontFamily: widget.currentLanguage == 'ar' ? "Poppins" : "CairoArabic"), ), SizedBox(width: 6.h), - Utils.buildSvgWithAssets(icon: AppAssets.language), - + Utils.buildSvgWithAssets( + icon: AppAssets.language, + iconColor: AppColors.primaryRedColor, + ), ], ), ), diff --git a/lib/widgets/map/HMSMap.dart b/lib/widgets/map/HMSMap.dart index 50da5f3a..e2059a52 100644 --- a/lib/widgets/map/HMSMap.dart +++ b/lib/widgets/map/HMSMap.dart @@ -220,7 +220,7 @@ class _MapTypeItem extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.h), decoration: BoxDecoration( color: isSelected - ? AppColors.primaryRedColor.withOpacity(0.1) + ? AppColors.secondaryLightRedColor : Colors.transparent, borderRadius: BorderRadius.circular(8.r), ), diff --git a/lib/widgets/map/gms_map.dart b/lib/widgets/map/gms_map.dart index 33e0befb..07bf2674 100644 --- a/lib/widgets/map/gms_map.dart +++ b/lib/widgets/map/gms_map.dart @@ -372,7 +372,7 @@ class _MapTypeItem extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.h), decoration: BoxDecoration( color: isSelected - ? AppColors.primaryRedColor.withOpacity(0.1) + ? AppColors.secondaryLightRedColor : Colors.transparent, borderRadius: BorderRadius.circular(8.r), ), diff --git a/pubspec.lock b/pubspec.lock index 81dfad3e..f8789dac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 + sha256: f6966125633a34f82d9be2ee895b755cff3554087b1d00a9eb884e5947f4bca9 url: "https://pub.dev" source: hosted - version: "1.3.59" + version: "1.3.77" adaptive_number: dependency: transitive description: @@ -133,10 +133,10 @@ packages: dependency: "direct main" description: name: clarity_flutter - sha256: b3a114fec6e873caf03cd59a32953f8cb4b1693bd3b4f749ef3030fea027ebce + sha256: "4d46afd76c31d3876f23384d44d6f5728a556daff6cec8b2c98d251dac14899e" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.10.0" clock: dependency: transitive description: @@ -165,18 +165,18 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec + sha256: "762c99f890ca8bf87f7337236f99edd42793843bc6c3631da294a76653a54bd0" url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "7.3.1" connectivity_plus_platform_interface: dependency: transitive description: name: connectivity_plus_platform_interface - sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" + sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" convert: dependency: transitive description: @@ -402,90 +402,114 @@ packages: dependency: "direct main" description: name: firebase_analytics - sha256: "4f85b161772e1d54a66893ef131c0a44bd9e552efa78b33d5f4f60d2caa5c8a3" + sha256: b82d2cabf4905a465ac95937037cbab4f2ca80f559cb04abd3da3f2abb9960bb url: "https://pub.dev" source: hosted - version: "11.6.0" + version: "12.6.0" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface - sha256: a44b6d1155ed5cae7641e3de7163111cfd9f6f6c954ca916dc6a3bdfa86bf845 + sha256: "5c3ab4b681c837afd71f0abf77c7485f51c7b645d4ae77b96f9ed54779ff4126" url: "https://pub.dev" source: hosted - version: "4.4.3" + version: "6.0.7" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web - sha256: c7d1ed1f86ae64215757518af5576ff88341c8ce5741988c05cc3b2e07b0b273 + sha256: "09eb4d0ce4c9a16efbc277ac709a90eb197d5512592215a73c6a302ab1ab6c3a" url: "https://pub.dev" source: hosted - version: "0.5.10+16" + version: "0.6.1+13" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" + sha256: "5fbbaa2aeb08a52feee559f92a62f5a6b04c9bdff81c8f608636771cf0d7b680" url: "https://pub.dev" source: hosted - version: "3.15.2" + version: "4.15.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 + sha256: "9bfbc85faca09346471ac56fbcee00757ebcfd85887c6f602764eff0001902d8" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "8.1.1" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" + sha256: "8ed84f5001dbd7a0390e2818d46bc858ceebb970edededdfc8d6552c76fc2f6e" url: "https://pub.dev" source: hosted - version: "2.24.1" + version: "3.12.0" firebase_crashlytics: dependency: "direct main" description: name: firebase_crashlytics - sha256: "662ae6443da91bca1fb0be8aeeac026fa2975e8b7ddfca36e4d90ebafa35dde1" + sha256: f37fe0df162f08bd549114b784037c73a093c64d22625632f97dbbddf18669b9 url: "https://pub.dev" source: hosted - version: "4.3.10" + version: "5.4.0" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface - sha256: "7222a8a40077c79f6b8b3f3439241c9f2b34e9ddfde8381ffc512f7b2e61f7eb" + sha256: "8270dcdb1800db3e5f888ce30a2d5e8f92c379c461f022297c83b2c794aa1792" url: "https://pub.dev" source: hosted - version: "3.8.10" + version: "3.9.0" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc" + sha256: "47a8142a0b897783103b01018ad69f123b819b0120483847bde30d1b2fa89b36" url: "https://pub.dev" source: hosted - version: "15.2.10" + version: "16.7.0" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754" + sha256: f9707e185b8ce1155d58e7e29348245072d99981f899fa8d3214abb2652c4ccb url: "https://pub.dev" source: hosted - version: "4.6.10" + version: "4.10.0" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390" + sha256: "311b16c75fc645c7a70c9ade38908abb12310dfce449fc3d3cee1771f196351e" + url: "https://pub.dev" + source: hosted + version: "4.2.5" + firebase_remote_config: + dependency: "direct main" + description: + name: firebase_remote_config + sha256: fa518445638fa3903e359ac544bd3243e72ada025728c8dbfa4b28ae939e8b38 url: "https://pub.dev" source: hosted - version: "3.10.10" + version: "6.7.0" + firebase_remote_config_platform_interface: + dependency: transitive + description: + name: firebase_remote_config_platform_interface + sha256: dc5a9713031f2ca744cb266d96a58609e929db19f3d91a1dd6bb8f8a0d77acad + url: "https://pub.dev" + source: hosted + version: "3.0.7" + firebase_remote_config_web: + dependency: transitive + description: + name: firebase_remote_config_web + sha256: "500abf695c370c76a486ad7480a6df7caf55d2d35ccc5c66b9fc9c1c47f2bc14" + url: "https://pub.dev" + source: hosted + version: "1.10.14" fixnum: dependency: transitive description: @@ -510,10 +534,9 @@ packages: flutter_amazonpaymentservices: dependency: "direct main" description: - name: flutter_amazonpaymentservices - sha256: "23f45fd3b1e12a0933da3704d66cebd19c4c41e28a50d4fd458d3e3e6311f332" - url: "https://pub.dev" - source: hosted + path: "package/flutter_amazonpaymentservices" + relative: true + source: path version: "0.0.13" flutter_cache_manager: dependency: transitive @@ -809,6 +832,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.2.14" + freerasp: + dependency: "direct main" + description: + name: freerasp + sha256: a3d019fa2c1a8f4bb15504d86997f583d1066fe0832172fd68ea6cd521e6fdcc + url: "https://pub.dev" + source: hosted + version: "8.2.2" fwfh_cached_network_image: dependency: transitive description: @@ -1073,6 +1104,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.12.0+301" + huawei_push: + dependency: "direct main" + description: + name: huawei_push + sha256: acc149482c03d0efe3ae50236b1a0aac9c0808cba07ad4eb41f06475b04c06f6 + url: "https://pub.dev" + source: hosted + version: "6.15.0+300" image: dependency: transitive description: @@ -1261,26 +1300,26 @@ packages: dependency: "direct main" description: name: local_auth - sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" + sha256: ecf24edf2283c509ecd217e3595f6f71034b68888d28ad1dae6bfa0857b816ac url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "3.0.2" local_auth_android: dependency: transitive description: name: local_auth_android - sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467 + sha256: "76c389998fe95cff4352644776ab5775f24ddd58f179dbf3df1b9c268cbbadc4" url: "https://pub.dev" source: hosted - version: "1.0.56" + version: "2.2.0" local_auth_darwin: dependency: transitive description: name: local_auth_darwin - sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" + sha256: b1b2d938cc3511512e552d92e782e055b8dc29c83e21c59e6ecfc6bd79ab0744 url: "https://pub.dev" source: hosted - version: "1.6.1" + version: "2.0.4" local_auth_platform_interface: dependency: transitive description: @@ -1293,10 +1332,10 @@ packages: dependency: transitive description: name: local_auth_windows - sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 + sha256: d72b8a441e5e6a679902f87c51529fcce65f442d77598484cf35454c3d733eda url: "https://pub.dev" source: hosted - version: "1.0.11" + version: "2.0.2" location: dependency: "direct main" description: @@ -1393,6 +1432,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + myfatoorah_flutter: + dependency: "direct main" + description: + name: myfatoorah_flutter + sha256: c9fa65c6329f3c076e48421726b855a444d9790b480d50822452f14f0e56d95f + url: "https://pub.dev" + source: hosted + version: "3.3.2" ndef: dependency: transitive description: @@ -1657,6 +1704,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + safe_device: + dependency: "direct main" + description: + name: safe_device + sha256: ad1b32a564484c710f6d52b5a526838c8a7d999c8ab2e14890ff55c06a018248 + url: "https://pub.dev" + source: hosted + version: "1.4.1" sanitize_html: dependency: transitive description: @@ -1741,10 +1796,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + sha256: "1e85e0dccf5b4a016077fea7dfbb4a7cb39c759ac1596fcf60c06deadb7da91f" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.6" shared_preferences_android: dependency: transitive description: @@ -2247,5 +2302,5 @@ packages: source: hosted version: "1.2.0" sdks: - dart: ">=3.10.0-0 <4.0.0" - flutter: ">=3.35.0" + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/pubspec.yaml b/pubspec.yaml index 82ae76ac..c6bc2c93 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,8 +2,8 @@ name: hmg_patient_app_new description: "New HMG Patient App" publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 0.0.48+49 -#version: 0.0.16+1 +#version: 4.6.046+1 +version: 4.6.1004+40501004 environment: sdk: ">=3.6.0 <4.0.0" @@ -17,17 +17,17 @@ dependencies: cached_network_image: ^3.4.1 http: ^1.5.0 flutter_svg: ^2.2.0 - shared_preferences: ^2.5.3 - connectivity_plus: ^6.1.5 + shared_preferences: ^2.5.5 + connectivity_plus: ^7.3.1 auto_size_text: ^3.0.0 shimmer: ^3.0.0 sizer: ^3.1.3 easy_localization: ^3.0.8 fluttertoast: ^8.2.12 flutter_rating_bar: ^4.0.1 - firebase_messaging: ^15.2.10 + firebase_messaging: ^16.7.0 # firebase_messaging: any - firebase_core: any + firebase_core: ^4.15.0 # firebase_core: ^3.13.1 permission_handler: ^12.0.1 flutter_local_notifications: ^19.4.1 @@ -45,7 +45,7 @@ dependencies: flutter_ios_voip_kit_karmm: ^0.8.0 image_picker: ^1.2.0 file_picker: 10.3.8 - local_auth: ^2.3.0 + local_auth: ^3.0.2 share_plus: ^11.1.0 device_calendar_plus: path: ./package/device_calendar_plus @@ -63,14 +63,14 @@ dependencies: geolocator: ^14.0.2 dropdown_search: ^6.0.2 google_maps_flutter: ^2.13.1 -# flutter_zoom_videosdk: 2.1.10 - flutter_zoom_videosdk: ^2.5.10 + flutter_zoom_videosdk: 2.1.10 dart_jsonwebtoken: ^3.2.0 dartz: ^0.10.1 equatable: ^2.0.7 google_api_availability: ^5.0.1 - firebase_analytics: ^11.5.1 - firebase_crashlytics: ^4.3.8 + firebase_analytics: ^12.6.0 + firebase_crashlytics: ^5.4.0 + firebase_remote_config: ^6.7.0 jiffy: ^6.4.3 # hijri_gregorian_calendar: # path: /Users/aamir/StudioProjects/hijri_gregorian_calendar @@ -101,18 +101,22 @@ dependencies: intl: ^0.20.2 flutter_widget_from_html: ^0.17.1 huawei_map: ^6.12.0+301 + huawei_push: ^6.15.0+300 flutter_image_compress: ^2.3.0 scrollable_positioned_list: ^0.3.8 in_app_review: ^2.0.11 flutter_paytabs_bridge: ^2.7.14 - clarity_flutter: 1.9.0 - flutter_amazonpaymentservices: ^0.0.13 + clarity_flutter: ^1.10.0 +# flutter_amazonpaymentservices: ^0.0.13 + flutter_amazonpaymentservices: + path: ./package/flutter_amazonpaymentservices screen_brightness: ^1.0.1 flutter_screenshot_blocker: ^1.0.4 cloudflare_turnstile: ^3.7.2 freerasp: ^8.2.1 safe_device: ^1.4.1 + myfatoorah_flutter: ^3.3.2 dev_dependencies: flutter_test: