diff --git a/AuthKey_JG87SJ23A4.p8 b/AuthKey_JG87SJ23A4.p8 new file mode 100644 index 0000000..6a3c2a5 --- /dev/null +++ b/AuthKey_JG87SJ23A4.p8 @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgaPetsOq3tqUlswBN +BFUWIIdEg4dcTCGRUKbAEx8ViFygCgYIKoZIzj0DAQehRANCAAS6IKkZ4tCeBlN6 +DtXiBekCiIbgHxWk8AWppP0H3mJ+I2s8cIEaquL4/yeLDcHzAHf8tHcYhF/TvggT +nc30yDLv +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/agconnect-services_NewApp.json b/agconnect-services_NewApp.json new file mode 100644 index 0000000..66f04d1 --- /dev/null +++ b/agconnect-services_NewApp.json @@ -0,0 +1 @@ +{"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 diff --git a/android/app/agconnect-services.json b/android/app/agconnect-services.json index 91aad2c..66f04d1 100644 --- a/android/app/agconnect-services.json +++ b/android/app/agconnect-services.json @@ -1,74 +1 @@ -{ - "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" - } - } - ] -} +{"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 diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 5d74c4d..d9d2f0b 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -18,12 +18,14 @@ if (keystorePropertiesFile.exists()) { } android { - namespace = "com.ejada.hmg" +// namespace = "com.ejada.hmg" + namespace = "com.cloudsolutions.HMGPatientApp" compileSdk = 36 ndkVersion = "28.2.13676358" defaultConfig { - applicationId = "com.ejada.hmg" +// applicationId = "com.ejada.hmg" + applicationId = "com.cloudsolutions.HMGPatientApp" // minSdk = 24 minSdk = 26 targetSdk = 36 diff --git a/android/app/google-services.json b/android/app/google-services.json index 3707617..49bf1ac 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -3,9 +3,45 @@ "project_number": "815750722565", "firebase_url": "https://api-project-815750722565.firebaseio.com", "project_id": "api-project-815750722565", - "storage_bucket": "api-project-815750722565.appspot.com" + "storage_bucket": "api-project-815750722565.firebasestorage.app" }, "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/libs/PenNavUI.aar b/android/app/libs/PenNavUI.aar index 7832df8..a565860 100644 Binary files a/android/app/libs/PenNavUI.aar and b/android/app/libs/PenNavUI.aar differ diff --git a/android/app/libs/PenguinRenderer.aar b/android/app/libs/PenguinRenderer.aar index 2926e9a..b498726 100644 Binary files a/android/app/libs/PenguinRenderer.aar and b/android/app/libs/PenguinRenderer.aar differ diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8c1388b..54e1a5e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ + package="com.cloudsolutions.HMGPatientApp"> + + + + + + + + + --> - + + + + diff --git a/android/app/src/main/kotlin/com/ejada/hmg/Application.kt b/android/app/src/main/kotlin/com/ejada/hmg/Application.kt deleted file mode 100644 index 32eb97c..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/Application.kt +++ /dev/null @@ -1,27 +0,0 @@ -//package com.cloud.diplomaticquarterapp -package com.ejada.hmg - - -import io.flutter.app.FlutterApplication - -class Application : FlutterApplication() { - override fun onCreate() { - super.onCreate() - } -} - -//import io.flutter.app.FlutterApplication -//import io.flutter.plugin.common.PluginRegistry -//import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback -//import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService -// -//class Application : FlutterApplication(), PluginRegistrantCallback { -// override fun onCreate() { -// super.onCreate() -// FlutterFirebaseMessagingService.setPluginRegistrant(this) -// } -// -// override fun registerWith(registry: PluginRegistry?) { -// FirebaseCloudMessagingPluginRegistrant.registerWith(registry) -// } -//} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/ejada/hmg/MainActivity.kt b/android/app/src/main/kotlin/com/ejada/hmg/MainActivity.kt deleted file mode 100644 index b81a6fb..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/MainActivity.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.ejada.hmg - -import android.app.PendingIntent -import android.content.Intent -import android.content.pm.PackageManager -import android.os.Build -import android.util.Log -import android.view.WindowManager -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi -import com.ejada.hmg.penguin.PenguinInPlatformBridge -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugins.GeneratedPluginRegistrant - -import io.flutter.embedding.android.FlutterFragmentActivity - - -class MainActivity: FlutterFragmentActivity() { - @RequiresApi(Build.VERSION_CODES.O) - override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { - GeneratedPluginRegistrant.registerWith(flutterEngine); - // Create Flutter Platform Bridge - this.window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) - - PenguinInPlatformBridge(flutterEngine, this).create() - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - - val granted = grantResults.all { it == PackageManager.PERMISSION_GRANTED } - val intent = Intent("PERMISSION_RESULT_ACTION").apply { - putExtra("PERMISSION_GRANTED", granted) - } - sendBroadcast(intent) - - // Log the request code and permission results - Log.d("PermissionsResult", "Request Code: $requestCode") - Log.d("PermissionsResult", "Permissions: ${permissions.joinToString()}") - Log.d("PermissionsResult", "Grant Results: ${grantResults.joinToString()}") - - } - - override fun onResume() { - super.onResume() - } -} diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PenguinInPlatformBridge.kt b/android/app/src/main/kotlin/com/ejada/hmg/PenguinInPlatformBridge.kt deleted file mode 100644 index 4df25bc..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/PenguinInPlatformBridge.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.ejada.hmg.penguin - -import com.ejada.hmg.MainActivity -import android.os.Build -import android.util.Log -import androidx.annotation.RequiresApi -import com.ejada.hmg.penguin.PenguinView -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugin.common.MethodCall -import com.ejada.hmg.PermissionManager.HostNotificationPermissionManager -import com.ejada.hmg.PermissionManager.HostBgLocationManager -import com.ejada.hmg.PermissionManager.HostGpsStateManager -import io.flutter.plugin.common.MethodChannel - -class PenguinInPlatformBridge( - private var flutterEngine: FlutterEngine, - private var mainActivity: MainActivity -) { - - private lateinit var channel: MethodChannel - - companion object { - private const val CHANNEL = "launch_penguin_ui" - } - - @RequiresApi(Build.VERSION_CODES.O) - fun create() { -// openTok = OpenTok(mainActivity, flutterEngine) - channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) - channel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result -> - when (call.method) { - "launchPenguin" -> { - print("the platform channel is being called") - - if (HostNotificationPermissionManager.isNotificationPermissionGranted(mainActivity)) - else HostNotificationPermissionManager.requestNotificationPermission(mainActivity) - HostBgLocationManager.requestLocationBackgroundPermission(mainActivity) - HostGpsStateManager.requestLocationPermission(mainActivity) - val args = call.arguments as Map? - Log.d("TAG", "configureFlutterEngine: $args") - println("args") - args?.let { - PenguinView( - mainActivity, - 100, - args, - flutterEngine.dartExecutor.binaryMessenger, - activity = mainActivity, - channel - ) - } - } - - else -> { - result.notImplemented() - } - } - } - } - -} diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/AppPreferences.java b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/AppPreferences.java deleted file mode 100644 index d012799..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/AppPreferences.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.ejada.hmg.PermissionManager; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Handler; -import android.os.HandlerThread; - -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; - - -/** - * This preferences for app level - */ - -public class AppPreferences { - - public static final String PREF_NAME = "PenguinINUI_AppPreferences"; - public static final int MODE = Context.MODE_PRIVATE; - - public static final String campusIdKey = "campusId"; - - public static final String LANG = "Lang"; - - public static final String settingINFO = "SETTING-INFO"; - - public static final String userName = "userName"; - public static final String passWord = "passWord"; - - private static HandlerThread handlerThread; - private static Handler handler; - - static { - handlerThread = new HandlerThread("PreferencesHandlerThread"); - handlerThread.start(); - handler = new Handler(handlerThread.getLooper()); - } - - - - public static SharedPreferences getPreferences(final Context context) { - return context.getSharedPreferences(AppPreferences.PREF_NAME, AppPreferences.MODE); - } - - public static SharedPreferences.Editor getEditor(final Context context) { - return getPreferences(context).edit(); - } - - - public static void writeInt(final Context context, final String key, final int value) { - handler.post(() -> { - SharedPreferences.Editor editor = getEditor(context); - editor.putInt(key, value); - editor.apply(); - }); - } - - - public static int readInt(final Context context, final String key, final int defValue) { - Callable callable = () -> { - SharedPreferences preferences = getPreferences(context); - return preferences.getInt(key, -1); - }; - - Future future = new FutureTask<>(callable); - handler.post((Runnable) future); - - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); // Handle the exception appropriately - } - - return -1; // Return the default value in case of an error - } - - public static int getCampusId(final Context context) { - return readInt(context,campusIdKey,-1); - } - - - - public static void writeString(final Context context, final String key, final String value) { - handler.post(() -> { - SharedPreferences.Editor editor = getEditor(context); - editor.putString(key, value); - editor.apply(); - }); - } - - - public static String readString(final Context context, final String key, final String defValue) { - Callable callable = () -> { - SharedPreferences preferences = getPreferences(context); - return preferences.getString(key, defValue); - }; - - Future future = new FutureTask<>(callable); - handler.post((Runnable) future); - - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); // Handle the exception appropriately - } - - return defValue; // Return the default value in case of an error - } - - - public static void writeBoolean(final Context context, final String key, final boolean value) { - handler.post(() -> { - SharedPreferences.Editor editor = getEditor(context); - editor.putBoolean(key, value); - editor.apply(); - }); - } - - public static boolean readBoolean(final Context context, final String key, final boolean defValue) { - Callable callable = () -> { - SharedPreferences preferences = getPreferences(context); - return preferences.getBoolean(key, defValue); - }; - - Future future = new FutureTask<>(callable); - handler.post((Runnable) future); - - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); // Handle the exception appropriately - } - - return defValue; // Return the default value in case of an error - } - -} diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostBgLocationManager.java b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostBgLocationManager.java deleted file mode 100644 index 5bc332d..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostBgLocationManager.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.ejada.hmg.PermissionManager; - -import android.Manifest; -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.provider.Settings; - -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -import com.peng.pennavmap.PlugAndPlaySDK; -import com.peng.pennavmap.R; -import com.peng.pennavmap.enums.InitializationErrorType; - -/** - * Manages background location permission requests and handling for the application. - */ -public class HostBgLocationManager { - /** - * Request code for background location permission - */ - public static final int REQUEST_ACCESS_BACKGROUND_LOCATION_CODE = 301; - - /** - * Request code for navigating to app settings - */ - private static final int REQUEST_CODE_SETTINGS = 11234; - - /** - * Alert dialog for denied permissions - */ - private static AlertDialog deniedAlertDialog; - - /** - * Checks if the background location permission has been granted. - * - * @param context the context of the application or activity - * @return true if the permission is granted, false otherwise - */ - - public static boolean isLocationBackgroundGranted(Context context) { - return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) - == PackageManager.PERMISSION_GRANTED; - } - - /** - * Requests the background location permission from the user. - * - * @param activity the activity from which the request is made - */ - public static void requestLocationBackgroundPermission(Activity activity) { - // Check if the ACCESS_BACKGROUND_LOCATION permission is already granted - if (!isLocationBackgroundGranted(activity)) { - // Permission is not granted, so request it - ActivityCompat.requestPermissions(activity, - new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, - REQUEST_ACCESS_BACKGROUND_LOCATION_CODE); - } - } - - /** - * Displays a dialog prompting the user to grant the background location permission. - * - * @param activity the activity where the dialog is displayed - */ - public static void showLocationBackgroundPermission(Activity activity) { - AlertDialog alertDialog = new AlertDialog.Builder(activity) - .setCancelable(false) - .setMessage(activity.getString(R.string.com_penguin_nav_ui_geofence_alert_msg)) - .setPositiveButton(activity.getString(R.string.com_penguin_nav_ui_go_to_settings), (dialog, which) -> { - if (activity.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) { - HostBgLocationManager.requestLocationBackgroundPermission(activity); - } else { - openAppSettings(activity); - } - if (dialog != null) { - dialog.dismiss(); - } - }) - .setNegativeButton(activity.getString(R.string.com_penguin_nav_ui_later), (dialog, which) -> { - dialog.cancel(); - }) - .create(); - - alertDialog.show(); - } - - /** - * Handles the scenario where permissions are denied by the user. - * Displays a dialog to guide the user to app settings or exit the activity. - * - * @param activity the activity where the dialog is displayed - */ - public static synchronized void handlePermissionsDenied(Activity activity) { - if (deniedAlertDialog != null && deniedAlertDialog.isShowing()) { - deniedAlertDialog.dismiss(); - } - - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setCancelable(false) - .setMessage(activity.getString(R.string.com_penguin_nav_ui_permission_denied_dialog_msg)) - .setNegativeButton(activity.getString(R.string.com_penguin_nav_ui_cancel), (dialogInterface, i) -> { - if (PlugAndPlaySDK.externalPenNavUIDelegate != null) { - PlugAndPlaySDK.externalPenNavUIDelegate.onPenNavInitializationError( - InitializationErrorType.permissions.getTypeKey(), - InitializationErrorType.permissions); - } - activity.finish(); - }) - .setPositiveButton(activity.getString(R.string.com_penguin_nav_ui_go_settings), (dialogInterface, i) -> { - dialogInterface.dismiss(); - openAppSettings(activity); - }); - deniedAlertDialog = builder.create(); - deniedAlertDialog.show(); - } - - /** - * Opens the application's settings screen to allow the user to modify permissions. - * - * @param activity the activity from which the settings screen is launched - */ - private static void openAppSettings(Activity activity) { - Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - Uri uri = Uri.fromParts("package", activity.getPackageName(), null); - intent.setData(uri); - - if (intent.resolveActivity(activity.getPackageManager()) != null) { - activity.startActivityForResult(intent, REQUEST_CODE_SETTINGS); - } - } -} diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostGpsStateManager.java b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostGpsStateManager.java deleted file mode 100644 index adde120..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostGpsStateManager.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.ejada.hmg.PermissionManager; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageManager; -import android.location.LocationManager; - -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -import com.peng.pennavmap.managers.permissions.managers.BgLocationManager; - -public class HostGpsStateManager { - private static final int LOCATION_PERMISSION_REQUEST_CODE = 1; - - - public boolean checkGPSEnabled(Activity activity) { - LocationManager gpsStateManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE); - return gpsStateManager.isProviderEnabled(LocationManager.GPS_PROVIDER); - } - - public static boolean isGpsGranted(Activity activity) { - return BgLocationManager.isLocationBackgroundGranted(activity) - || ContextCompat.checkSelfPermission( - activity, - Manifest.permission.ACCESS_FINE_LOCATION - ) == PackageManager.PERMISSION_GRANTED - && ContextCompat.checkSelfPermission( - activity, - Manifest.permission.ACCESS_COARSE_LOCATION - ) == PackageManager.PERMISSION_GRANTED; - } - - - /** - * Checks if the location permission is granted. - * - * @param activity the Activity context - * @return true if permission is granted, false otherwise - */ - public static boolean isLocationPermissionGranted(Activity activity) { - return ContextCompat.checkSelfPermission( - activity, - Manifest.permission.ACCESS_FINE_LOCATION - ) == PackageManager.PERMISSION_GRANTED && - ContextCompat.checkSelfPermission( - activity, - Manifest.permission.ACCESS_COARSE_LOCATION - ) == PackageManager.PERMISSION_GRANTED; - } - - /** - * Requests the location permission. - * - * @param activity the Activity context - */ - public static void requestLocationPermission(Activity activity) { - ActivityCompat.requestPermissions( - activity, - new String[]{ - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION, - }, - LOCATION_PERMISSION_REQUEST_CODE - ); - } -} diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostNotificationPermissionManager.java b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostNotificationPermissionManager.java deleted file mode 100644 index 5b9f19e..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostNotificationPermissionManager.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.ejada.hmg.PermissionManager; - -import android.app.Activity; -import android.content.pm.PackageManager; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.core.app.ActivityCompat; -import androidx.core.app.NotificationManagerCompat; - -public class HostNotificationPermissionManager { - private static final int REQUEST_NOTIFICATION_PERMISSION = 100; - - - /** - * Checks if the notification permission is granted. - * - * @return true if the notification permission is granted, false otherwise. - */ - public static boolean isNotificationPermissionGranted(Activity activity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - try { - return ActivityCompat.checkSelfPermission(activity, android.Manifest.permission.POST_NOTIFICATIONS) - == PackageManager.PERMISSION_GRANTED; - } catch (Exception e) { - // Handle cases where the API is unavailable - e.printStackTrace(); - return NotificationManagerCompat.from(activity).areNotificationsEnabled(); - } - } else { - // Permissions were not required below Android 13 for notifications - return NotificationManagerCompat.from(activity).areNotificationsEnabled(); - } - } - - /** - * Requests the notification permission. - */ - public static void requestNotificationPermission(Activity activity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (!isNotificationPermissionGranted(activity)) { - ActivityCompat.requestPermissions(activity, - new String[]{android.Manifest.permission.POST_NOTIFICATIONS}, - REQUEST_NOTIFICATION_PERMISSION); - } - } - } - - /** - * Handles the result of the permission request. - * - * @param requestCode The request code passed in requestPermissions(). - * @param permissions The requested permissions. - * @param grantResults The grant results for the corresponding permissions. - */ - public static boolean handlePermissionResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - if (permissions.length > 0 && - permissions[0].equals(android.Manifest.permission.POST_NOTIFICATIONS) && - grantResults.length > 0 && - grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // Permission granted - System.out.println("Notification permission granted."); - return true; - } else { - // Permission denied - System.out.println("Notification permission denied."); - return false; - } - - } - - -} diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionHelper.kt b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionHelper.kt deleted file mode 100644 index 9856a49..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionHelper.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.ejada.hmg.PermissionManager - -import android.Manifest -import android.os.Build - -object PermissionHelper { - - fun getRequiredPermissions(): Array { - val permissions = mutableListOf( - Manifest.permission.INTERNET, - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_NETWORK_STATE, - Manifest.permission.BLUETOOTH, - Manifest.permission.BLUETOOTH_ADMIN, -// Manifest.permission.ACTIVITY_RECOGNITION - ) - - // For Android 12 (API level 31) and above, add specific permissions -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12 (API 31) and above - permissions.add(Manifest.permission.BLUETOOTH_SCAN) - permissions.add(Manifest.permission.BLUETOOTH_CONNECT) - permissions.add(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS) -// } - - return permissions.toTypedArray() - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionManager.kt b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionManager.kt deleted file mode 100644 index d8aea7b..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionManager.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.ejada.hmg.PermissionManager - -import android.app.Activity -import android.content.Context -import android.content.pm.PackageManager -import android.os.Build -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat - -class PermissionManager( - private val context: Context, - val listener: PermissionListener, - private val requestCode: Int, - vararg permissions: String -) { - - private val permissionsArray = permissions - - interface PermissionListener { - fun onPermissionGranted() - fun onPermissionDenied() - } - - fun arePermissionsGranted(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - permissionsArray.all { - ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED - } - } else { - true - } - } - - fun requestPermissions(activity: Activity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - ActivityCompat.requestPermissions(activity, permissionsArray, requestCode) - } - } - - fun handlePermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - if (this.requestCode == requestCode) { - val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED } - if (allGranted) { - listener.onPermissionGranted() - } else { - listener.onPermissionDenied() - } - } - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionResultReceiver.kt b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionResultReceiver.kt deleted file mode 100644 index c07d1de..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionResultReceiver.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.ejada.hmg.PermissionManager - -// PermissionResultReceiver.kt -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent - -class PermissionResultReceiver( - private val callback: (Boolean) -> Unit -) : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - val granted = intent?.getBooleanExtra("PERMISSION_GRANTED", false) ?: false - callback(granted) - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinMethod.kt b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinMethod.kt deleted file mode 100644 index 18463d2..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinMethod.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.ejada.hmg.penguin - -enum class PenguinMethod { - // initializePenguin("initializePenguin"), - // configurePenguin("configurePenguin"), - // showPenguinUI("showPenguinUI"), - // onPenNavUIDismiss("onPenNavUIDismiss"), - // onReportIssue("onReportIssue"), - // onPenNavSuccess("onPenNavSuccess"), - onPenNavInitializationError // onLocationOffCampus("onLocationOffCampus"), - // navigateToPOI("navigateToPOI"), - // openSharedLocation("openSharedLocation"); -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt deleted file mode 100644 index 70889d3..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.ejada.hmg.penguin - -import android.content.Context -import com.google.gson.Gson -import com.peng.pennavmap.PlugAndPlaySDK -import com.peng.pennavmap.connections.ApiController -import com.peng.pennavmap.interfaces.RefIdDelegate -import com.peng.pennavmap.models.TokenModel -import com.peng.pennavmap.models.postmodels.PostToken -import com.peng.pennavmap.utils.AppSharedData -import okhttp3.ResponseBody -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response -import android.util.Log - - -class PenguinNavigator() { - - fun navigateTo(mContext: Context, refID: String, delegate: RefIdDelegate,clientID : String,clientKey : String ) { - val postToken = PostToken(clientID, clientKey) - getToken(mContext, postToken, object : RefIdDelegate { - override fun onRefByIDSuccess(PoiId: String?) { - Log.e("navigateTo", "PoiId is+++++++ $refID") - - PlugAndPlaySDK.navigateTo(mContext, refID, object : RefIdDelegate { - override fun onRefByIDSuccess(PoiId: String?) { - Log.e("navigateTo", "PoiId 2is+++++++ $PoiId") - - delegate.onRefByIDSuccess(refID) - - } - - override fun onGetByRefIDError(error: String?) { - delegate.onRefByIDSuccess(error) - } - - }) - - - } - - override fun onGetByRefIDError(error: String?) { - delegate.onRefByIDSuccess(error) - } - - }) - - } - - fun getToken(mContext: Context, postToken: PostToken?, apiTokenCallBack: RefIdDelegate) { - try { - // Create the API call - val purposesCall: Call = ApiController.getInstance(mContext) - .apiMethods - .getToken(postToken) - - // Enqueue the call for asynchronous execution - purposesCall.enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - if (response.isSuccessful() && response.body() != null) { - try { - response.body()?.use { responseBody -> - val responseBodyString: String = responseBody.string() // Use `string()` to get the actual response content - if (responseBodyString.isNotEmpty()) { - val tokenModel = Gson().fromJson(responseBodyString, TokenModel::class.java) - if (tokenModel != null && tokenModel.token != null) { - AppSharedData.apiToken = tokenModel.token - apiTokenCallBack.onRefByIDSuccess(tokenModel.token) - } else { - apiTokenCallBack.onGetByRefIDError("Failed to parse token model") - } - } else { - apiTokenCallBack.onGetByRefIDError("Response body is empty") - } - } - } catch (e: Exception) { - apiTokenCallBack.onGetByRefIDError("An error occurred: ${e.message}") - } - } else { - apiTokenCallBack.onGetByRefIDError("Unsuccessful response: " + response.code()) - } - } - - override fun onFailure(call: Call, t: Throwable) { - apiTokenCallBack.onGetByRefIDError(t.message) - } - }) - } catch (error: Exception) { - apiTokenCallBack.onGetByRefIDError("Exception during API call: $error") - } - } - -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt deleted file mode 100644 index 6c7306d..0000000 --- a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt +++ /dev/null @@ -1,376 +0,0 @@ -package com.ejada.hmg.penguin - -import android.app.Activity -import android.content.Context -import android.content.Context.RECEIVER_EXPORTED -import android.content.IntentFilter -import android.graphics.Color -import android.os.Build -import android.util.Log -import android.view.View -import android.view.ViewGroup -import android.widget.RelativeLayout -import android.widget.Toast -import androidx.annotation.RequiresApi -import com.ejada.hmg.PermissionManager.PermissionManager -import com.ejada.hmg.PermissionManager.PermissionResultReceiver -import com.ejada.hmg.MainActivity -import com.ejada.hmg.PermissionManager.PermissionHelper -import com.peng.pennavmap.PlugAndPlayConfiguration -import com.peng.pennavmap.PlugAndPlaySDK -import com.peng.pennavmap.enums.InitializationErrorType -import com.peng.pennavmap.interfaces.PenNavUIDelegate -import com.peng.pennavmap.utils.Languages -import io.flutter.plugin.common.BinaryMessenger -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.platform.PlatformView -import com.ejada.hmg.penguin.PenguinNavigator -import com.peng.pennavmap.interfaces.PIEventsDelegate -import com.peng.pennavmap.interfaces.PILocationDelegate -import com.peng.pennavmap.interfaces.RefIdDelegate -import com.peng.pennavmap.models.LocationMessage -import com.peng.pennavmap.models.PIReportIssue -import java.util.ArrayList -import penguin.com.pennav.renderer.PIRendererSettings - -/** - * Custom PlatformView for displaying Penguin UI components within a Flutter app. - * Implements `PlatformView` for rendering the view, `MethodChannel.MethodCallHandler` for handling method calls, - * and `PenNavUIDelegate` for handling SDK events. - */ -@RequiresApi(Build.VERSION_CODES.O) -internal class PenguinView( - context: Context, - id: Int, - val creationParams: Map, - messenger: BinaryMessenger, - activity: MainActivity, - val channel: MethodChannel -) : PlatformView, MethodChannel.MethodCallHandler, PenNavUIDelegate, PIEventsDelegate, - PILocationDelegate { - // The layout for displaying the Penguin UI - private val mapLayout: RelativeLayout = RelativeLayout(context) - private val _context: Context = context - - private val permissionResultReceiver: PermissionResultReceiver - private val permissionIntentFilter = IntentFilter("PERMISSION_RESULT_ACTION") - - private companion object { - const val PERMISSIONS_REQUEST_CODE = 1 - } - - private lateinit var permissionManager: PermissionManager - - // Reference to the main activity - private var _activity: Activity = activity - - private lateinit var mContext: Context - - lateinit var navigator: PenguinNavigator - - init { - // Set layout parameters for the mapLayout - mapLayout.layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT - ) - - mContext = context - - - permissionResultReceiver = PermissionResultReceiver { granted -> - if (granted) { - onPermissionsGranted() - } else { - onPermissionsDenied() - } - } - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - mContext.registerReceiver( - permissionResultReceiver, - permissionIntentFilter, - RECEIVER_EXPORTED - ) - } else { - mContext.registerReceiver( - permissionResultReceiver, - permissionIntentFilter, - ) - } - - // Set the background color of the layout - mapLayout.setBackgroundColor(Color.RED) - - permissionManager = PermissionManager( - context = mContext, - listener = object : PermissionManager.PermissionListener { - override fun onPermissionGranted() { - // Handle permissions granted - onPermissionsGranted() - } - - override fun onPermissionDenied() { - // Handle permissions denied - onPermissionsDenied() - } - }, - requestCode = PERMISSIONS_REQUEST_CODE, - PermissionHelper.getRequiredPermissions().get(0) - ) - - if (!permissionManager.arePermissionsGranted()) { - permissionManager.requestPermissions(_activity) - } else { - // Permissions already granted - permissionManager.listener.onPermissionGranted() - } - - - } - - private fun onPermissionsGranted() { - // Handle the actions when permissions are granted - Log.d("PermissionsResult", "onPermissionsGranted") - // Register the platform view factory for creating custom views - - // Initialize the Penguin SDK - initPenguin() - - - } - - private fun onPermissionsDenied() { - // Handle the actions when permissions are denied - Log.d("PermissionsResult", "onPermissionsDenied") - - } - - /** - * Returns the view associated with this PlatformView. - * - * @return The main view for this PlatformView. - */ - override fun getView(): View { - return mapLayout - } - - /** - * Cleans up resources associated with this PlatformView. - */ - override fun dispose() { - // Cleanup code if needed - } - - /** - * Handles method calls from Dart code. - * - * @param call The method call from Dart. - * @param result The result callback to send responses back to Dart. - */ - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - // Handle method calls from Dart code here - } - - /** - * Initializes the Penguin SDK with custom configuration and delegates. - */ - private fun initPenguin() { - navigator = PenguinNavigator() - // Configure the PlugAndPlaySDK - val language = when (creationParams["languageCode"] as String) { - "ar" -> Languages.ar - "en" -> Languages.en - else -> { - Languages.en - } - } - - -// PlugAndPlaySDK.configuration = Builder() -// .setClientData(MConstantsDemo.CLIENT_ID, MConstantsDemo.CLIENT_KEY) -// .setLanguageID(selectedLanguage) -// .setBaseUrl(MConstantsDemo.DATA_URL, MConstantsDemo.POSITION_URL) -// .setServiceName(MConstantsDemo.DATA_SERVICE_NAME, MConstantsDemo.POSITION_SERVICE_NAME) -// .setUserName(name) -// .setSimulationModeEnabled(isSimulation) -// .setCustomizeColor(if (MConstantsDemo.APP_COLOR != null) MConstantsDemo.APP_COLOR else "#2CA0AF") -// .setEnableBackButton(MConstantsDemo.SHOW_BACK_BUTTON) -// .setCampusId(MConstantsDemo.selectedCampusId) -// -// .setShowUILoader(true) -// .build() - - PIRendererSettings.styleUri = "mapbox://styles/rwaid/cm3h30b36007v01qz7ik8a0sk" - - PlugAndPlaySDK.configuration = PlugAndPlayConfiguration.Builder() - .setBaseUrl( - creationParams["dataURL"] as String, - creationParams["positionURL"] as String - ) - .setServiceName( - creationParams["dataServiceName"] as String, - creationParams["positionServiceName"] as String - ) - .setClientData( - creationParams["clientID"] as String, - creationParams["clientKey"] as String - ) - .setUserName(creationParams["username"] as String) -// .setLanguageID(Languages.en) - .setLanguageID(language) - .setSimulationModeEnabled(creationParams["isSimulationModeEnabled"] as Boolean) - .setEnableBackButton(true) -// .setDeepLinkData("deeplink") - .setCustomizeColor("#2CA0AF") - .setDeepLinkSchema("", "") - .setIsEnableReportIssue(true) - .setDeepLinkData("") - .setEnableSharedLocationCallBack(false) - .setShowUILoader(true) - .setCampusId(creationParams["projectID"] as Int) - .build() - - - Log.d( - "TAG", - "initPenguin: ${creationParams["projectID"]}" - ) - - Log.d( - "TAG", - "initPenguin: creation param are ${creationParams}" - ) - - // Set location delegate to handle location updates -// PlugAndPlaySDK.setPiLocationDelegate { - // Example code to handle location updates - // Uncomment and modify as needed - // if (location.size() > 0) - // Toast.makeText(_context, "Location Info Latitude: ${location[0]}, Longitude: ${location[1]}", Toast.LENGTH_SHORT).show() -// } - - // Set events delegate for reporting issues -// PlugAndPlaySDK.setPiEventsDelegate(new PIEventsDelegate() { -// @Override -// public void onReportIssue(PIReportIssue issue) { -// Log.e("Issue Reported: ", issue.getReportType()); -// } -// // Implement issue reporting logic here } -// @Override -// public void onSharedLocation(String link) { -// // Implement Shared location logic here -// } -// }) - - // Start the Penguin SDK - PlugAndPlaySDK.setPiEventsDelegate(this) - PlugAndPlaySDK.setPiLocationDelegate(this) - PlugAndPlaySDK.start(mContext, this) - } - - - /** - * Navigates to the specified reference ID. - * - * @param refID The reference ID to navigate to. - */ - fun navigateTo(refID: String) { - try { - if (refID.isBlank()) { - Log.e("navigateTo", "Invalid refID: The reference ID is blank.") - } -// referenceId = refID - navigator.navigateTo(mContext, refID,object : RefIdDelegate { - override fun onRefByIDSuccess(PoiId: String?) { - Log.e("navigateTo", "PoiId is penguin view+++++++ $PoiId") - -// channelFlutter.invokeMethod( -// PenguinMethod.navigateToPOI.name, -// "navigateTo Success" -// ) - } - - override fun onGetByRefIDError(error: String?) { - Log.e("navigateTo", "error is penguin view+++++++ $error") - -// channelFlutter.invokeMethod( -// PenguinMethod.navigateToPOI.name, -// "navigateTo Failed: Invalid refID" -// ) - } - } , creationParams["clientID"] as String, creationParams["clientKey"] as String ) - - } catch (e: Exception) { - Log.e("navigateTo", "Exception occurred during navigation: ${e.message}", e) -// channelFlutter.invokeMethod( -// PenguinMethod.navigateToPOI.name, -// "Failed: Exception - ${e.message}" -// ) - } - } - - /** - * Called when Penguin UI setup is successful. - * - * @param warningCode Optional warning code received from the SDK. - */ - override fun onPenNavSuccess(warningCode: String?) { - val clinicId = creationParams["clinicID"] as String - - if(clinicId.isEmpty()) return - - navigateTo(clinicId) - } - - /** - * Called when there is an initialization error with Penguin UI. - * - * @param description Description of the error. - * @param errorType Type of initialization error. - */ - override fun onPenNavInitializationError( - description: String?, - errorType: InitializationErrorType? - ) { - val arguments: Map = mapOf( - "description" to description, - "type" to errorType?.name - ) - Log.d( - "description", - "description : ${description}" - ) - - channel.invokeMethod(PenguinMethod.onPenNavInitializationError.name, arguments) - Toast.makeText(mContext, "Navigation Error: $description", Toast.LENGTH_SHORT).show() - } - - /** - * Called when Penguin UI is dismissed. - */ - override fun onPenNavUIDismiss() { - // Handle UI dismissal if needed - try { - mContext.unregisterReceiver(permissionResultReceiver) - dispose(); - } catch (e: IllegalArgumentException) { - Log.e("PenguinView", "Receiver not registered: $e") - } - } - - override fun onReportIssue(issue: PIReportIssue?) { - TODO("Not yet implemented") - } - - override fun onSharedLocation(link: String?) { - TODO("Not yet implemented") - } - - override fun onLocationOffCampus(location: ArrayList?) { - TODO("Not yet implemented") - } - - override fun onLocationMessage(locationMessage: LocationMessage?) { - TODO("Not yet implemented") - } -} diff --git a/android/google-services.json b/android/google-services.json index 3707617..49bf1ac 100644 --- a/android/google-services.json +++ b/android/google-services.json @@ -3,9 +3,45 @@ "project_number": "815750722565", "firebase_url": "https://api-project-815750722565.firebaseio.com", "project_id": "api-project-815750722565", - "storage_bucket": "api-project-815750722565.appspot.com" + "storage_bucket": "api-project-815750722565.firebasestorage.app" }, "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/settings.gradle.kts b/android/settings.gradle.kts index 6d0842d..1c91fb7 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,6 +19,9 @@ pluginManagement { 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 } diff --git a/assets/animations/lottie/checkmark.json b/assets/animations/lottie/checkmark.json old mode 100755 new mode 100644 index 998a915..57b67e2 --- a/assets/animations/lottie/checkmark.json +++ b/assets/animations/lottie/checkmark.json @@ -1,767 +1 @@ -{ - "nm": "HDFC Success", - "h": 512, - "w": 512, - "meta": { - "g": "LottieFiles AE 1.0.0" - }, - "layers": [ - { - "ty": 4, - "nm": "check", - "sr": 1, - "st": 0, - "op": 240, - "ip": 0, - "hasMask": false, - "ao": 0, - "ks": { - "a": { - "a": 0, - "k": [ - 0, - 0, - 0 - ] - }, - "s": { - "a": 0, - "k": [ - 100, - 100, - 100 - ] - }, - "sk": { - "a": 0, - "k": 0 - }, - "p": { - "a": 0, - "k": [ - 256, - 256, - 0 - ] - }, - "r": { - "a": 0, - "k": 0 - }, - "sa": { - "a": 0, - "k": 0 - }, - "o": { - "a": 0, - "k": 100 - } - }, - "ef": [], - "shapes": [ - { - "ty": "gr", - "nm": "Shape 1", - "it": [ - { - "ty": "sh", - "nm": "Path 1", - "d": 1, - "ks": { - "a": 0, - "k": { - "c": false, - "i": [ - [ - 0, - 0 - ], - [ - 0, - 0 - ], - [ - 0, - 0 - ] - ], - "o": [ - [ - 0, - 0 - ], - [ - 0, - 0 - ], - [ - 0, - 0 - ] - ], - "v": [ - [ - -82.5, - 4.5 - ], - [ - -31, - 55 - ], - [ - 73, - -52.5 - ] - ] - } - } - }, - { - "ty": "tm", - "nm": "Trim Paths 1", - "e": { - "a": 1, - "k": [ - { - "o": { - "x": 1, - "y": 0.076 - }, - "i": { - "x": 0.667, - "y": 1 - }, - "s": [ - 0 - ], - "t": 60 - }, - { - "s": [ - 100 - ], - "t": 85 - } - ] - }, - "o": { - "a": 0, - "k": 0 - }, - "s": { - "a": 0, - "k": 0 - }, - "m": 1 - }, - { - "ty": "st", - "nm": "Stroke 1", - "lc": 2, - "lj": 2, - "ml": 1, - "o": { - "a": 0, - "k": 100 - }, - "w": { - "a": 0, - "k": 30 - }, - "c": { - "a": 0, - "k": [ - 1, - 1, - 1 - ] - } - }, - { - "ty": "tr", - "a": { - "a": 0, - "k": [ - 0, - 0 - ] - }, - "s": { - "a": 0, - "k": [ - 100, - 100 - ] - }, - "sk": { - "a": 0, - "k": 0 - }, - "p": { - "a": 0, - "k": [ - 0, - 0 - ] - }, - "r": { - "a": 0, - "k": 0 - }, - "sa": { - "a": 0, - "k": 0 - }, - "o": { - "a": 0, - "k": 100 - } - } - ] - } - ], - "ind": 1 - }, - { - "ty": 4, - "nm": "Shape Layer 2", - "sr": 1, - "st": 0, - "op": 240, - "ip": 0, - "hasMask": false, - "ao": 0, - "ks": { - "a": { - "a": 0, - "k": [ - 0, - 0, - 0 - ] - }, - "s": { - "a": 1, - "k": [ - { - "o": { - "x": 0.656, - "y": 0.872 - }, - "i": { - "x": 0, - "y": 0.98 - }, - "s": [ - 0, - 0, - 100 - ], - "t": 20 - }, - { - "s": [ - 150, - 150, - 100 - ], - "t": 60 - } - ] - }, - "sk": { - "a": 0, - "k": 0 - }, - "p": { - "a": 0, - "k": [ - 256, - 257.86, - 0 - ] - }, - "r": { - "a": 0, - "k": 0 - }, - "sa": { - "a": 0, - "k": 0 - }, - "o": { - "a": 0, - "k": 100 - } - }, - "ef": [], - "shapes": [ - { - "ty": "gr", - "nm": "Ellipse 1", - "it": [ - { - "ty": "el", - "nm": "Ellipse Path 1", - "d": 1, - "p": { - "a": 0, - "k": [ - 0, - 0 - ] - }, - "s": { - "a": 0, - "k": [ - 236, - 236 - ] - } - }, - { - "ty": "fl", - "nm": "Fill 1", - "c": { - "a": 0, - "k": [ - 0.094, - 0.761, - 0.451 - ] - }, - "r": 1, - "o": { - "a": 0, - "k": 100 - } - }, - { - "ty": "tr", - "a": { - "a": 0, - "k": [ - 0, - 0 - ] - }, - "s": { - "a": 0, - "k": [ - 100, - 100 - ] - }, - "sk": { - "a": 0, - "k": 0 - }, - "p": { - "a": 0, - "k": [ - 0, - -3 - ] - }, - "r": { - "a": 0, - "k": 0 - }, - "sa": { - "a": 0, - "k": 0 - }, - "o": { - "a": 0, - "k": 100 - } - } - ] - } - ], - "ind": 2 - }, - { - "ty": 4, - "nm": "Shape Layer 1", - "sr": 1, - "st": 0, - "op": 240, - "ip": 0, - "hasMask": false, - "ao": 0, - "ks": { - "a": { - "a": 0, - "k": [ - 0, - 0, - 0 - ] - }, - "s": { - "a": 1, - "k": [ - { - "o": { - "x": 0.477, - "y": 0.587 - }, - "i": { - "x": 0, - "y": 0.999 - }, - "s": [ - 0, - 0, - 100 - ], - "t": 10 - }, - { - "o": { - "x": 0.167, - "y": 0.167 - }, - "i": { - "x": 0.833, - "y": 0.833 - }, - "s": [ - 150, - 150, - 100 - ], - "t": 50 - }, - { - "o": { - "x": 0.167, - "y": 0 - }, - "i": { - "x": 0.833, - "y": 1 - }, - "s": [ - 150, - 150, - 100 - ], - "t": 76 - }, - { - "s": [ - 210, - 210, - 100 - ], - "t": 123 - } - ] - }, - "sk": { - "a": 0, - "k": 0 - }, - "p": { - "a": 0, - "k": [ - 256, - 257.86, - 0 - ] - }, - "r": { - "a": 0, - "k": 0 - }, - "sa": { - "a": 0, - "k": 0 - }, - "o": { - "a": 1, - "k": [ - { - "o": { - "x": 0.912, - "y": 0.073 - }, - "i": { - "x": 0.626, - "y": 0.729 - }, - "s": [ - 100 - ], - "t": 76 - }, - { - "s": [ - 0 - ], - "t": 119 - } - ] - } - }, - "ef": [], - "shapes": [ - { - "ty": "gr", - "nm": "Ellipse 1", - "it": [ - { - "ty": "el", - "nm": "Ellipse Path 1", - "d": 1, - "p": { - "a": 0, - "k": [ - 0, - 0 - ] - }, - "s": { - "a": 0, - "k": [ - 236, - 236 - ] - } - }, - { - "ty": "st", - "nm": "Stroke 1", - "lc": 1, - "lj": 1, - "ml": 4, - "o": { - "a": 0, - "k": 100 - }, - "w": { - "a": 0, - "k": 2 - }, - "c": { - "a": 0, - "k": [ - 1, - 1, - 1 - ] - } - }, - { - "ty": "fl", - "nm": "Fill 1", - "c": { - "a": 0, - "k": [ - 0.784, - 0.945, - 0.878 - ] - }, - "r": 1, - "o": { - "a": 0, - "k": 100 - } - }, - { - "ty": "tr", - "a": { - "a": 0, - "k": [ - 0, - 0 - ] - }, - "s": { - "a": 0, - "k": [ - 100, - 100 - ] - }, - "sk": { - "a": 0, - "k": 0 - }, - "p": { - "a": 0, - "k": [ - 0, - -3 - ] - }, - "r": { - "a": 0, - "k": 0 - }, - "sa": { - "a": 0, - "k": 0 - }, - "o": { - "a": 0, - "k": 100 - } - } - ] - } - ], - "ind": 3 - }, - { - "ty": 4, - "nm": "BG", - "sr": 1, - "st": 0, - "op": 240, - "ip": 0, - "hasMask": false, - "ao": 0, - "ks": { - "a": { - "a": 0, - "k": [ - 0, - 0, - 0 - ] - }, - "s": { - "a": 0, - "k": [ - 100, - 100, - 100 - ] - }, - "sk": { - "a": 0, - "k": 0 - }, - "p": { - "a": 0, - "k": [ - 256, - 256, - 0 - ] - }, - "r": { - "a": 0, - "k": 0 - }, - "sa": { - "a": 0, - "k": 0 - }, - "o": { - "a": 0, - "k": 100 - } - }, - "ef": [], - "shapes": [ - { - "ty": "gr", - "nm": "Rectangle 1", - "it": [ - { - "ty": "rc", - "nm": "Rectangle Path 1", - "d": 1, - "p": { - "a": 0, - "k": [ - 0, - 0 - ] - }, - "r": { - "a": 0, - "k": 0 - }, - "s": { - "a": 0, - "k": [ - 554, - 556 - ] - } - }, - { - "ty": "fl", - "nm": "Fill 1", - "c": { - "a": 0, - "k": [ - 0.972, - 0.972, - 0.972 - ] - }, - "r": 1, - "o": { - "a": 0, - "k": 100 - } - }, - { - "ty": "tr", - "a": { - "a": 0, - "k": [ - 0, - 0 - ] - }, - "s": { - "a": 0, - "k": [ - 100, - 100 - ] - }, - "sk": { - "a": 0, - "k": 0 - }, - "p": { - "a": 0, - "k": [ - -1, - 6 - ] - }, - "r": { - "a": 0, - "k": 0 - }, - "sa": { - "a": 0, - "k": 0 - }, - "o": { - "a": 0, - "k": 100 - } - } - ] - } - ], - "ind": 4 - } - ], - "v": "4.8.0", - "fr": 60, - "op": 130, - "ip": 0, - "assets": [] -} \ No newline at end of file +{"nm":"Main Scene","ddd":0,"h":512,"w":512,"meta":{"g":"@lottiefiles/creator 1.75.0"},"layers":[{"ty":4,"nm":"check","sr":1,"st":0,"op":240,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":1,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-82.5,4.5],[-31,55],[73,-52.5]]},"ix":2}},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":1,"k":[{"o":{"x":1,"y":0.076},"i":{"x":0.667,"y":1},"s":[0],"t":60},{"s":[100],"t":85}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":0,"k":0,"ix":1},"m":1},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":30,"ix":5},"c":{"a":0,"k":[1,1,1],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1},{"ty":4,"nm":"Shape Layer 2","sr":1,"st":0,"op":240,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.656,"y":0.872},"i":{"x":0,"y":0.98},"s":[0,0,100],"t":20},{"s":[150,150,100],"t":60}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,257.86],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[236,236],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0941,0.7608,0.451],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,-3],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"Shape Layer 1","sr":1,"st":0,"op":240,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.477,"y":0.587},"i":{"x":0,"y":0.999},"s":[0,0,100],"t":10},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[150,150,100],"t":50},{"o":{"x":0.167,"y":0},"i":{"x":0.833,"y":1},"s":[150,150,100],"t":76},{"s":[210,210,100],"t":123}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,257.86],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.912,"y":0.073},"i":{"x":0.626,"y":0.729},"s":[100],"t":76},{"s":[0],"t":119}],"ix":11}},"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[236,236],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"c":{"a":0,"k":[1,1,1],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.7843,0.9451,0.8784],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,-3],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3}],"v":"5.7.0","fr":60,"op":130,"ip":0,"assets":[]} \ No newline at end of file diff --git a/assets/animations/lottie/checkmark_old.json b/assets/animations/lottie/checkmark_old.json new file mode 100755 index 0000000..998a915 --- /dev/null +++ b/assets/animations/lottie/checkmark_old.json @@ -0,0 +1,767 @@ +{ + "nm": "HDFC Success", + "h": 512, + "w": 512, + "meta": { + "g": "LottieFiles AE 1.0.0" + }, + "layers": [ + { + "ty": 4, + "nm": "check", + "sr": 1, + "st": 0, + "op": 240, + "ip": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ] + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 256, + 256, + 0 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100 + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "nm": "Shape 1", + "it": [ + { + "ty": "sh", + "nm": "Path 1", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": false, + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -82.5, + 4.5 + ], + [ + -31, + 55 + ], + [ + 73, + -52.5 + ] + ] + } + } + }, + { + "ty": "tm", + "nm": "Trim Paths 1", + "e": { + "a": 1, + "k": [ + { + "o": { + "x": 1, + "y": 0.076 + }, + "i": { + "x": 0.667, + "y": 1 + }, + "s": [ + 0 + ], + "t": 60 + }, + { + "s": [ + 100 + ], + "t": 85 + } + ] + }, + "o": { + "a": 0, + "k": 0 + }, + "s": { + "a": 0, + "k": 0 + }, + "m": 1 + }, + { + "ty": "st", + "nm": "Stroke 1", + "lc": 2, + "lj": 2, + "ml": 1, + "o": { + "a": 0, + "k": 100 + }, + "w": { + "a": 0, + "k": 30 + }, + "c": { + "a": 0, + "k": [ + 1, + 1, + 1 + ] + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ] + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100 + } + } + ] + } + ], + "ind": 1 + }, + { + "ty": 4, + "nm": "Shape Layer 2", + "sr": 1, + "st": 0, + "op": 240, + "ip": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 1, + "k": [ + { + "o": { + "x": 0.656, + "y": 0.872 + }, + "i": { + "x": 0, + "y": 0.98 + }, + "s": [ + 0, + 0, + 100 + ], + "t": 20 + }, + { + "s": [ + 150, + 150, + 100 + ], + "t": 60 + } + ] + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 256, + 257.86, + 0 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100 + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "nm": "Ellipse 1", + "it": [ + { + "ty": "el", + "nm": "Ellipse Path 1", + "d": 1, + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 236, + 236 + ] + } + }, + { + "ty": "fl", + "nm": "Fill 1", + "c": { + "a": 0, + "k": [ + 0.094, + 0.761, + 0.451 + ] + }, + "r": 1, + "o": { + "a": 0, + "k": 100 + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ] + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 0, + -3 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100 + } + } + ] + } + ], + "ind": 2 + }, + { + "ty": 4, + "nm": "Shape Layer 1", + "sr": 1, + "st": 0, + "op": 240, + "ip": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 1, + "k": [ + { + "o": { + "x": 0.477, + "y": 0.587 + }, + "i": { + "x": 0, + "y": 0.999 + }, + "s": [ + 0, + 0, + 100 + ], + "t": 10 + }, + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 150, + 150, + 100 + ], + "t": 50 + }, + { + "o": { + "x": 0.167, + "y": 0 + }, + "i": { + "x": 0.833, + "y": 1 + }, + "s": [ + 150, + 150, + 100 + ], + "t": 76 + }, + { + "s": [ + 210, + 210, + 100 + ], + "t": 123 + } + ] + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 256, + 257.86, + 0 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 1, + "k": [ + { + "o": { + "x": 0.912, + "y": 0.073 + }, + "i": { + "x": 0.626, + "y": 0.729 + }, + "s": [ + 100 + ], + "t": 76 + }, + { + "s": [ + 0 + ], + "t": 119 + } + ] + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "nm": "Ellipse 1", + "it": [ + { + "ty": "el", + "nm": "Ellipse Path 1", + "d": 1, + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 236, + 236 + ] + } + }, + { + "ty": "st", + "nm": "Stroke 1", + "lc": 1, + "lj": 1, + "ml": 4, + "o": { + "a": 0, + "k": 100 + }, + "w": { + "a": 0, + "k": 2 + }, + "c": { + "a": 0, + "k": [ + 1, + 1, + 1 + ] + } + }, + { + "ty": "fl", + "nm": "Fill 1", + "c": { + "a": 0, + "k": [ + 0.784, + 0.945, + 0.878 + ] + }, + "r": 1, + "o": { + "a": 0, + "k": 100 + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ] + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 0, + -3 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100 + } + } + ] + } + ], + "ind": 3 + }, + { + "ty": 4, + "nm": "BG", + "sr": 1, + "st": 0, + "op": 240, + "ip": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ] + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 256, + 256, + 0 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100 + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "nm": "Rectangle 1", + "it": [ + { + "ty": "rc", + "nm": "Rectangle Path 1", + "d": 1, + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "s": { + "a": 0, + "k": [ + 554, + 556 + ] + } + }, + { + "ty": "fl", + "nm": "Fill 1", + "c": { + "a": 0, + "k": [ + 0.972, + 0.972, + 0.972 + ] + }, + "r": 1, + "o": { + "a": 0, + "k": 100 + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ] + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + -1, + 6 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100 + } + } + ] + } + ], + "ind": 4 + } + ], + "v": "4.8.0", + "fr": 60, + "op": 130, + "ip": 0, + "assets": [] +} \ No newline at end of file diff --git a/assets/images/png/full_body_back_female.png b/assets/images/png/full_body_back_female.png new file mode 100644 index 0000000..9b3aa74 Binary files /dev/null and b/assets/images/png/full_body_back_female.png differ diff --git a/assets/images/png/full_body_back.png b/assets/images/png/full_body_back_male.png similarity index 100% rename from assets/images/png/full_body_back.png rename to assets/images/png/full_body_back_male.png diff --git a/assets/images/png/full_body_front_female.png b/assets/images/png/full_body_front_female.png new file mode 100644 index 0000000..c89608b Binary files /dev/null and b/assets/images/png/full_body_front_female.png differ diff --git a/assets/images/png/full_body_front.png b/assets/images/png/full_body_front_male.png similarity index 100% rename from assets/images/png/full_body_front.png rename to assets/images/png/full_body_front_male.png diff --git a/assets/images/svg/ai_overview.svg b/assets/images/svg/ai_overview.svg new file mode 100644 index 0000000..95c29aa --- /dev/null +++ b/assets/images/svg/ai_overview.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/svg/calculator_icon.svg b/assets/images/svg/calculator_icon.svg new file mode 100644 index 0000000..989a4db --- /dev/null +++ b/assets/images/svg/calculator_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/guard_icon.svg b/assets/images/svg/guard_icon.svg new file mode 100644 index 0000000..e3a68d9 --- /dev/null +++ b/assets/images/svg/guard_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/high_caution.svg b/assets/images/svg/high_caution.svg new file mode 100644 index 0000000..d268468 --- /dev/null +++ b/assets/images/svg/high_caution.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/information-circle.svg b/assets/images/svg/information-circle.svg new file mode 100644 index 0000000..f1a850e --- /dev/null +++ b/assets/images/svg/information-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/livecare_audio.svg b/assets/images/svg/livecare_audio.svg new file mode 100644 index 0000000..339d534 --- /dev/null +++ b/assets/images/svg/livecare_audio.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/livecare_phone.svg b/assets/images/svg/livecare_phone.svg new file mode 100644 index 0000000..1722f0f --- /dev/null +++ b/assets/images/svg/livecare_phone.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/livecare_video.svg b/assets/images/svg/livecare_video.svg new file mode 100644 index 0000000..bab506f --- /dev/null +++ b/assets/images/svg/livecare_video.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 96296cc..3ec0a8a 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -945,6 +945,7 @@ "offline": "غير متصل", "videoCall": "مكالمة فيديو", "liveVideoCallWithHMGDoctors": "مكالمة فيديو مباشرة مع أطباء مجموعة الحبيب الطبية", + "liveAudioCallWithHMGDoctors": "مكالمة صوتية مباشرة مع أطباء مجموعة الحبيب الطبية", "audioCall": "مكالمة صوتية", "phoneCall": "مكالمة هاتفية", "livePhoneCallWithHMGDoctors": "مكالمة هاتفية مباشرة مع أطباء مجموعة الحبيب الطبية", @@ -1522,7 +1523,64 @@ "trackerAndOthers": "المتتبع وأخرى", "bmi": "مؤشر كتلة الجسم", "yesPleaseINeedHelp": "نعم، من فضلك أريد المساعدة", - "noThanksIKnowTheClinic": "لا شكراً، أعرف العيادة", + "aiOverView": "AI ملخص", + "generateAiAnalysis": "قم بإجراء تحليل لهذا الطلب AI", + "aiAnalysis": "AI تحليل", + "prediction": "تنبؤ", + "prevention": "وقاية", + "disclaimer": "تنصل", + "thisAboveInfo": "المعلومات المذكورة أعلاه مُولّدة بواسطة AI بناءً على نتائج تحاليلك المخبرية، وهي لأغراض إعلامية فقط. لا ينبغي اعتبارها تشخيصًا طبيًا أو خطة علاجية. استشر طبيبك دائمًا للحصول على نصيحة طبية مُخصصة قبل اتخاذ أي قرارات تتعلق بصحتك.", + "aiDisclaimer": "سيتم مشاركة بيانات نتائج تحاليلك المخبرية بشكل آمن مع خدمة الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في تقديم رؤى صحية مُخصصة. هل ترغب في المتابعة؟", + "consent": "موافقة", + "bmiNormalDescription": "مؤشر كتلة جسمك ضمن المعدل الصحي. استمر في اتباع نظام غذائي متوازن، وممارسة النشاط البدني بانتظام، وإجراء الفحوصات الدورية للحفاظ على صحة جيدة. راقب أي تغييرات ملحوظة مع مرور الوقت.", + "bmiUnderweightDescription": "يشير مؤشر كتلة الجسم إلى أنك تعاني من نقص الوزن. لذا، يُنصح بزيادة السعرات الحرارية المتناولة من خلال تناول الأطعمة الغنية بالعناصر الغذائية، وممارسة تمارين المقاومة لبناء العضلات، واستشارة أخصائي رعاية صحية للتحقق من وجود أسباب كامنة.", + "bmiOverweightDescription": "يشير مؤشر كتلة الجسم إلى زيادة الوزن. لذا، يُنصح بالسعي إلى إنقاص الوزن تدريجياً وبشكل مستدام من خلال تقليل السعرات الحرارية بشكل معتدل، وممارسة النشاط البدني بانتظام، واختيار الأطعمة الصحية. استشر طبيبك قبل إجراء أي تغييرات جذرية في نمط حياتك.", + "caloriesMaintenanceDescription": "السعرات الحرارية اليومية المُقدَّرة للحفاظ على وزنك الحالي بناءً على مستوى نشاطك ومعدل الأيض لديك. استخدم هذه الكمية كخط أساس: زدها قليلاً لزيادة الكتلة العضلية، أو قللها قليلاً لإنقاص الوزن تدريجياً. تابع تقدمك وعدّل الخطة كل أسبوعين إلى أربعة أسابيع.", + "caloriesModerateDeficitDescription": "نظام غذائي معتدل لتقليل السعرات الحرارية مصمم لإنقاص الوزن بشكل ثابت ومستدام (عادةً من 0.25 إلى 0.5 كيلوغرام أسبوعيًا). يُنصح بدمجه مع تمارين المقاومة وتناول كمية كافية من البروتين للحفاظ على الكتلة العضلية. تجنب التخفيضات الحادة في السعرات الحرارية واستشر طبيبًا مختصًا إذا كنت تعاني من أي حالة صحية.", + "caloriesAggressiveDeficitDescription": "يؤدي خفض السعرات الحرارية بشكل كبير إلى فقدان الوزن بشكل أسرع، ولكنه قد يصعب الاستمرار عليه ويزيد من خطر نقص العناصر الغذائية والإرهاق وفقدان الكتلة العضلية. لا يُنصح باتباعه على المدى الطويل؛ استشر طبيبًا أو أخصائي تغذية قبل الاستمرار.", + "bodyFatEssentialDescription": "نسبة الدهون في جسمك ضمن النطاق الأساسي/الرياضي. هذا المستوى شائع لدى الرياضيين ويشير إلى انخفاض نسبة الدهون في الجسم. مع أن هذا قد يكون مثاليًا للأداء، تأكد من الحفاظ على تغذية سليمة ووظائف هرمونية متوازنة. استشر طبيبًا إذا شعرت بالتعب، أو واجهت مشاكل هرمونية، أو أي مخاوف صحية أخرى.", + "bodyFatFitnessDescription": "نسبة الدهون في جسمك ضمن النطاق الصحي. وهذا يدل على مستوى صحي من الدهون في الجسم يرتبط بلياقة بدنية جيدة. حافظ على نمط حياتك الحالي، بما في ذلك ممارسة الرياضة بانتظام واتباع نظام غذائي متوازن، للحفاظ على هذا المستوى الصحي.", + "bodyFatAverageDescription": "نسبة الدهون في جسمك ضمن المعدل الطبيعي/المقبول، وهي نسبة تُعتبر صحية لعموم الناس. لتحسين تكوين جسمك، يُنصح بممارسة تمارين القوة، وتمارين القلب والأوعية الدموية، واتباع نظام غذائي متوازن. ركّز على التغييرات التدريجية والمستدامة للحفاظ على صحتك على المدى الطويل.", + "bodyFatObeseDescription": "نسبة الدهون في جسمك أعلى من المعدل الصحي الموصى به. قد يزيد هذا من المخاطر الصحية، بما في ذلك أمراض القلب والأوعية الدموية، والسكري، ومشاكل المفاصل. يُنصح باستشارة طبيب أو أخصائي تغذية معتمد لوضع خطة علاجية شخصية تشمل التغذية المتوازنة، والنشاط البدني المنتظم، وتعديلات نمط الحياة لتحقيق إدارة مستدامة للوزن.", + "bmrNormalDescription": "معدل الأيض الأساسي لديك ضمن النطاق المتوقع لحالتك. معدل الأيض الأساسي هو الطاقة التي يحتاجها جسمك في حالة الراحة للحفاظ على وظائفه الحيوية (التنفس، الدورة الدموية، درجة الحرارة). استخدم هذه القيمة كأساس لتقدير إجمالي احتياجاتك اليومية من الطاقة من خلال تطبيق مُعامل النشاط. حافظ على نظام غذائي متوازن وممارسة النشاط البدني بانتظام لدعم صحة التمثيل الغذائي.", + "bmrLowDescription": "معدل الأيض الأساسي لديك أقل من المعدل الطبيعي لحالتك. تشمل الأسباب المحتملة انخفاض كتلة العضلات، أو تراجع التمثيل الغذائي المرتبط بالتقدم في السن، أو التكيف الأيضي الناتج عن تقييد السعرات الحرارية لفترة طويلة. يُنصح بالتركيز على تمارين المقاومة لبناء العضلات والحفاظ عليها، والتأكد من تناول كميات كافية من البروتين والمغذيات الدقيقة، واستشارة أخصائي رعاية صحية إذا كان هذا الأمر غير متوقع.", + "bmrHighDescription": "معدل الأيض الأساسي لديك أعلى من المتوسط \u200B\u200Bبالنسبة لحالتك. قد يعكس هذا زيادة في كتلة العضلات، أو صغر السن، أو ارتفاع معدل الأيض لديك بشكل طبيعي، مما يعني أنك تحرق طاقة أكبر أثناء الراحة. استخدم هذه المعلومات لتحديد احتياجاتك من السعرات الحرارية: احتياجات أعلى للحفاظ على الوزن أو بناء العضلات، ونقص معتدل في السعرات الحرارية لإنقاص الوزن. استشر طبيبك إذا لاحظت أي تغيرات سريعة غير مبررة.", + "ibwAtTargetDescription": "Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.", + "ibwBelowTargetDescription": "You are below the ideal weight by {kg} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.", + "ibwAboveTargetDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.", + "ibwCloseToRangeDescription": "Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.", + "ibwUnderweightDescription": "You are below the ideal weight by {kg} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.", + "ibwUnderweightGenericDescription": "You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.", + "ibwOverweightDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.", + "ibwOverweightGenericDescription": "You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.", + "dietVeryLowCarbsDescription": "Very low‑carb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for long‑term use.", + "dietLowCarbsDescription": "Low‑carb, higher‑protein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.", + "dietModerateCarbsDescription": "Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.", + "dietUSDAGuidelinesDescription": "USDA‑based balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidence‑based framework for general health, nutrient adequacy, and chronic disease prevention.", + "dietZoneDietDescription": "Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.", + "contextDependent": "يعتمد السياق", + "calculatedValue": "القيمة المحسوبة", + "calculationFormula": "صيغة الحساب", + "confirmYourLocation": "قم بتأكيد موقعك", + "confirmYourLocationDesc": "يرجى تأكيد موقعك الحالي لعرض الاتجاهات المناسبة.", + "insideHospital": "أنا داخل المستشفى", + "outsideHospital": "أنا خارج المستشفى", + "servicePriceList": "قائمة أسعار الخدمات", + "servicePriceListDesc": "توضح قائمة أسعار الخدمات أدناه رسوم الخدمات الصحية المقدمة للمرضى بنظام الدفع النقدي. أما فيما يتعلق بالخدمات المشمولة بالتأمين، فسيتم تطبيق التغطية التأمينية والتحقق من الأهلية واحتساب نسب التحمل وفقًا لشروط وثيقة التأمين وجدول المنافع المعتمد لكل شركة تأمين.", + "servicePriceListRights": "يحق للمريض الحصول على متابعة مجانية في غضون 14 يومًا من الزيارة الأولى", + "serviceName": "اسم الخدمة", + "noFreeSlot": "لا توجد مواعيد متاحة في التاريخ المحدد. يرجى اختيار تاريخ آخر أو إعادة المحاولة لاحقاً.", + "noThanksIKnowTheClinic": "لا، شكراً. أنا أعرف العيادة.", "unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة", + "loadingAIAnalysis": "جارٍ تحليل نتائج المختبر، يرجى التحلي بالصبر والسماح للذكاء الاصطناعي بالقيام بالمهمة، قد يستغرق هذا بعض الوقت.", + "symptoms": "أعراض", + "insuranceInActive": "التأمين غير نشط", + "insuranceInActiveContactSupport": "لديك تأمين ساري المفعول، ولكنه غير مُفعّل حاليًا في مجموعة حبيب الطبية. يُرجى التواصل مع خدمة عملاء مجموعة حبيب الطبية.", + "updateAppNow": "تحديث التطبيق الآن", + "Dr": "الدكتور.", + "reschedulingAppo": "إعادة جدولة الموعد، يرجى الانتظار...", + "invalidEligibility": "لا يمكنك إجراء الدفع عبر الإنترنت لأنك غير مؤهل لاستخدام الخدمة المقدمة.", + "invalidInsurance": "لا يمكنك إجراء الدفع عبر الإنترنت لأنه ليس لديك تأمين صالح.", + "continueCash": "تواصل نقدا", "timeFor": "الوقت" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index d937f6b..bf73d6d 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -937,6 +937,7 @@ "offline": "Offline", "videoCall": "Video Call", "liveVideoCallWithHMGDoctors": "Live Video Call with HMG Doctors", + "liveAudioCallWithHMGDoctors": "Live Audio Call with HMG Doctors", "audioCall": "Audio Call", "phoneCall": "Phone Call", "livePhoneCallWithHMGDoctors": "Live Phone Call with HMG Doctors", @@ -1439,7 +1440,6 @@ "healthy": "Healthy", "warning": "Warning", "vitalSignDetails": "Vital Sign Details", - "resultOf": "Result of {date}", "resultOfNoDate": "Result of --", "referenceRangeBetween": "Reference range: {low} – {high} {unit}", "referenceRangeMin": "Reference range: ≥ {low} {unit}", @@ -1475,7 +1475,6 @@ "remindersSet": "Reminders Set!", "dailyWaterRemindersScheduledAt": "Daily water reminders scheduled at:", "waterConsumption": "Water Consumption", - "selectActivityLevel": "Select Activity Level", "selectNumberOfReminders": "Select Number of Reminders", "h2oSettings": "H20 Settings", "settingsSavedSuccessfully": "Settings saved successfully", @@ -1515,6 +1514,63 @@ "trackerAndOthers": "Tracker & Others", "bmi": "BMI", "yesPleaseINeedHelp": "Yes, Please I need help", + "confirmYourLocation": "Confirm Your Location", + "confirmYourLocationDesc": "Please confirm your present location to view appropriate directions.", + "insideHospital": "I am inside the hospital", + "outsideHospital": "I am outside the hospital", + "servicePriceList": "Services Price List", + "servicePriceListDesc": "Below is the services price list outline the healthcare services fees for cash payments, where the insurance coverage, eligibility, and co-payment deductions will be processed in accordance with the insurance policy terms and the table of benefits of each insurance providers:", + "servicePriceListRights": "The patient has the right to a free follow-up within 14 days of initial visit", + "serviceName": "Service Name", + "noFreeSlot": "No free slot available for the selected date. Please select another date or check back later.", "noThanksIKnowTheClinic": "No, Thanks. I Know the clinic", - "unableToSendOTP": "Unable to send Verification OTP" + "unableToSendOTP": "Unable to send Verification OTP", + "aiOverView": "AI Overview", + "generateAiAnalysis": "Generate AI analysis for this order", + "aiAnalysis": "AI Analysis", + "prediction": "Prediction", + "prevention": "Prevention", + "disclaimer": "Disclaimer", + "thisAboveInfo": "The above information is generated by AI based on your lab results and is intended for informational purposes only. It should not be considered a medical diagnosis or treatment plan. Always consult with your healthcare provider for personalized medical advice and before making any health-related decisions.", + "aiDisclaimer": "Your lab result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?", + "consent": "Consent", + "bmiNormalDescription": "Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.", + "bmiUnderweightDescription": "BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.", + "bmiOverweightDescription": "BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.", + "caloriesMaintenanceDescription": "Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 2–4 weeks.", + "caloriesModerateDeficitDescription": "A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.25–0.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.", + "caloriesAggressiveDeficitDescription": "A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.", + "bodyFatEssentialDescription": "Your body fat percentage is in the essential/athletic range. This level is typical for athletes and indicates very low body fat. While this may be optimal for performance, ensure you maintain adequate nutrition and hormone function. Consult a healthcare professional if you experience fatigue, hormonal issues, or other health concerns.", + "bodyFatFitnessDescription": "Your body fat percentage is in the fitness range. This indicates a healthy level of body fat associated with good physical conditioning. Maintain your current lifestyle habits including regular exercise and balanced nutrition to sustain this healthy range.", + "bodyFatAverageDescription": "Your body fat percentage is in the average/acceptable range. This is considered a healthy range for general population. To improve body composition, consider incorporating strength training, cardiovascular exercise, and a balanced diet. Focus on gradual, sustainable changes for long-term health.", + "bodyFatObeseDescription": "Your body fat percentage is above the recommended healthy range. This may increase health risks including cardiovascular disease, diabetes, and joint problems. Consider consulting a healthcare provider or registered dietitian to develop a personalized plan that includes balanced nutrition, regular physical activity, and lifestyle modifications for sustainable weight management.", + "bmrNormalDescription": "Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.", + "bmrLowDescription": "Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.", + "bmrHighDescription": "Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.", + "ibwAtTargetDescription": "Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.", + "ibwBelowTargetDescription": "You are below the ideal weight by {kg} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.", + "ibwAboveTargetDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.", + "ibwCloseToRangeDescription": "Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.", + "ibwUnderweightDescription": "You are below the ideal weight by {kg} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.", + "ibwUnderweightGenericDescription": "You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.", + "ibwOverweightDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.", + "ibwOverweightGenericDescription": "You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.", + "dietVeryLowCarbsDescription": "Very low‑carb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for long‑term use.", + "dietLowCarbsDescription": "Low‑carb, higher‑protein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.", + "dietModerateCarbsDescription": "Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.", + "dietUSDAGuidelinesDescription": "USDA‑based balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidence‑based framework for general health, nutrient adequacy, and chronic disease prevention.", + "dietZoneDietDescription": "Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.", + "contextDependent": "Context Dependent", + "calculatedValue": "Calculated Value", + "calculationFormula": "Calculation Formula", + "loadingAIAnalysis": "Analysing your lab results, Please be patient and let the AI do the magic, This might take some time.", + "symptoms": "Symptoms", + "insuranceInActive": "Insurance Inactive", + "insuranceInActiveContactSupport": "You have a valid insurance but it is currently inactive in HMG. Please contact HMG customer support to activate it.", + "updateAppNow": "Update App Now", + "Dr": "Dr.", + "reschedulingAppo": "Rescheduling Appointment, Please wait...", + "invalidEligibility": "You cannot make online payment because you are not eligible to use the provided service.", + "invalidInsurance": "You cannot make online payment because you do not have a valid insurance.", + "continueCash": "Continue as cash" } diff --git a/assets/sounds/ring_30Sec.caf b/assets/sounds/ring_30Sec.caf new file mode 100644 index 0000000..b5bb4b8 Binary files /dev/null and b/assets/sounds/ring_30Sec.caf differ diff --git a/assets/sounds/ring_30Sec.mp3 b/assets/sounds/ring_30Sec.mp3 new file mode 100644 index 0000000..f647cca Binary files /dev/null and b/assets/sounds/ring_30Sec.mp3 differ diff --git a/assets/sounds/ring_60Sec.mp3 b/assets/sounds/ring_60Sec.mp3 new file mode 100644 index 0000000..ac32394 Binary files /dev/null and b/assets/sounds/ring_60Sec.mp3 differ diff --git a/convert_json.dart b/convert_json.dart deleted file mode 100644 index af5c594..0000000 --- a/convert_json.dart +++ /dev/null @@ -1,73 +0,0 @@ -// import 'dart:convert'; -// import 'dart:io'; -// -// void main() async { -// final file = File('assets/json/body_symptoms_data.json'); -// final content = await file.readAsString(); -// -// print('File size: ${content.length} characters'); -// -// // Split into two parts -// final parts = content.split('export const SymptomsData = ['); -// if (parts.length != 2) { -// print('ERROR: Could not split file properly'); -// exit(1); -// } -// -// print('Split into ${parts.length} parts'); -// -// // Process BodySymptomsData -// var bodyPart = parts[0] -// .replaceAll('export const BodySymptomsData = ', '') -// .trim() -// .replaceAll(RegExp(r';$'), ''); -// -// print('Parsing body symptoms...'); -// Map bodySymptomsData; -// try { -// bodySymptomsData = json.decode(bodyPart); -// print('✅ Body symptoms parsed: ${bodySymptomsData.length} body parts'); -// } catch (e) { -// print('❌ Error parsing body symptoms: $e'); -// exit(1); -// } -// -// // Process SymptomsData -// print('Processing symptoms data...'); -// var symptomsPart = parts[1] -// .trim() -// .replaceAll(RegExp(r'\];$'), '') -// .replaceAllMapped(RegExp(r'\n(\s*)id:'), (m) => '\n${m.group(1)}"id":') -// .replaceAllMapped(RegExp(r'\n(\s*)type:'), (m) => '\n${m.group(1)}"type":') -// .replaceAllMapped(RegExp(r'\n(\s*)name:'), (m) => '\n${m.group(1)}"name":') -// .replaceAllMapped(RegExp(r'\n(\s*)common_name:'), (m) => '\n${m.group(1)}"common_name":'); -// -// print('Parsing symptoms...'); -// List symptomsData; -// try { -// symptomsData = json.decode('[$symptomsPart]'); -// print('✅ Symptoms parsed: ${symptomsData.length} symptoms'); -// } catch (e) { -// print('❌ Error parsing symptoms: $e'); -// print('First 1000 chars:'); -// print('[$symptomsPart]'.substring(0, 1000)); -// exit(1); -// } -// -// // Create final JSON -// print('Creating final JSON structure...'); -// final jsonStructure = { -// 'bodySymptoms': bodySymptomsData, -// 'symptoms': symptomsData, -// }; -// -// // Write to file -// print('Writing to file...'); -// final encoder = JsonEncoder.withIndent(' '); -// await file.writeAsString(encoder.convert(jsonStructure)); -// -// print('\n✅ SUCCESS! File converted to proper JSON'); -// print('✅ Body parts: ${bodySymptomsData.length}'); -// print('✅ Symptoms: ${symptomsData.length}'); -// } -// diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..7d1b1fd --- /dev/null +++ b/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"android":{"default":{"projectId":"api-project-815750722565","appId":"1:815750722565:android:62281cd3e5df4063","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"api-project-815750722565","appId":"1:815750722565:ios:34ec03c09e02e21f3c186c","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"api-project-815750722565","configurations":{"android":"1:815750722565:android:62281cd3e5df4063","ios":"1:815750722565:ios:34ec03c09e02e21f3c186c"}}}}}} \ No newline at end of file diff --git a/google-services_NewApp.json b/google-services_NewApp.json new file mode 100644 index 0000000..49bf1ac --- /dev/null +++ b/google-services_NewApp.json @@ -0,0 +1,83 @@ +{ + "project_info": { + "project_number": "815750722565", + "firebase_url": "https://api-project-815750722565.firebaseio.com", + "project_id": "api-project-815750722565", + "storage_bucket": "api-project-815750722565.firebasestorage.app" + }, + "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", + "android_client_info": { + "package_name": "com.ejada.hmg" + } + }, + "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" + } + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/icon.jpg b/icon.jpg new file mode 100644 index 0000000..65ca5c0 Binary files /dev/null and b/icon.jpg differ diff --git a/ios/GoogleService-Info.plist b/ios/GoogleService-Info.plist index 633037c..a9b1f53 100644 --- a/ios/GoogleService-Info.plist +++ b/ios/GoogleService-Info.plist @@ -2,10 +2,6 @@ - CLIENT_ID - 815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt ANDROID_CLIENT_ID 815750722565-m14h8mkosm7cnq6uh6rhqr54dn02d705.apps.googleusercontent.com API_KEY @@ -15,11 +11,11 @@ PLIST_VERSION 1 BUNDLE_ID - com.HMG.HMG-Smartphone + com.cloudsolutions.HMGPatientApp PROJECT_ID api-project-815750722565 STORAGE_BUCKET - api-project-815750722565.appspot.com + api-project-815750722565.firebasestorage.app IS_ADS_ENABLED IS_ANALYTICS_ENABLED @@ -31,7 +27,7 @@ IS_SIGNIN_ENABLED GOOGLE_APP_ID - 1:815750722565:ios:328ec247a81a2ca23c186c + 1:815750722565:ios:34ec03c09e02e21f3c186c DATABASE_URL https://api-project-815750722565.firebaseio.com diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 7a41ae2..e031a97 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -18,12 +18,14 @@ 61243B562EC5FA3700D46FA0 /* PenguinNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */; }; 61243B572EC5FA3700D46FA0 /* PenguinViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 766D8CB62EC60BE600D05E07 /* Penguin.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */; }; - 766D8CB72EC60BE600D05E07 /* Penguin.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 766D8CB82EC60BE600D05E07 /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */; }; - 766D8CB92EC60BE600D05E07 /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 766D8CBA2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */; }; - 766D8CBB2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 765A5A8C2F35CD8B0003FF7D /* Penguin.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A802F35CD730003FF7D /* Penguin.xcframework */; }; + 765A5A8D2F35CD8B0003FF7D /* Penguin.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A802F35CD730003FF7D /* Penguin.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 765A5A8E2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */; }; + 765A5A8F2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 765A5A902F35CD8B0003FF7D /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */; }; + 765A5A912F35CD8B0003FF7D /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 76AA18AE2F3B2A4D00DC8DFC /* ring_30Sec.caf in Resources */ = {isa = PBXBuildFile; fileRef = 76AA18AC2F3B2A4D00DC8DFC /* ring_30Sec.caf */; }; + 76AA18AF2F3B2A4D00DC8DFC /* ring_30Sec.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 76AA18AD2F3B2A4D00DC8DFC /* ring_30Sec.mp3 */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -41,15 +43,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + 765A5A922F35CD8B0003FF7D /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - 766D8CB72EC60BE600D05E07 /* Penguin.xcframework in Embed Frameworks */, - 766D8CBB2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Embed Frameworks */, - 766D8CB92EC60BE600D05E07 /* PenNavUI.xcframework in Embed Frameworks */, + 765A5A8F2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Embed Frameworks */, + 765A5A8D2F35CD8B0003FF7D /* Penguin.xcframework in Embed Frameworks */, + 765A5A912F35CD8B0003FF7D /* PenNavUI.xcframework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -73,10 +75,12 @@ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Penguin.xcframework; path = Frameworks/Penguin.xcframework; sourceTree = ""; }; - 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenNavUI.xcframework; path = Frameworks/PenNavUI.xcframework; sourceTree = ""; }; - 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenguinINRenderer.xcframework; path = Frameworks/PenguinINRenderer.xcframework; sourceTree = ""; }; + 765A5A802F35CD730003FF7D /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Penguin.xcframework; sourceTree = ""; }; + 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenguinINRenderer.xcframework; sourceTree = ""; }; + 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenNavUI.xcframework; sourceTree = ""; }; 769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; + 76AA18AC2F3B2A4D00DC8DFC /* ring_30Sec.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ring_30Sec.caf; sourceTree = ""; }; + 76AA18AD2F3B2A4D00DC8DFC /* ring_30Sec.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ring_30Sec.mp3; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 8E12CEEB8E334EE22D5259D7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; @@ -95,9 +99,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 766D8CB62EC60BE600D05E07 /* Penguin.xcframework in Frameworks */, - 766D8CBA2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Frameworks */, - 766D8CB82EC60BE600D05E07 /* PenNavUI.xcframework in Frameworks */, + 765A5A8C2F35CD8B0003FF7D /* Penguin.xcframework in Frameworks */, + 765A5A902F35CD8B0003FF7D /* PenNavUI.xcframework in Frameworks */, + 765A5A8E2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Frameworks */, DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -136,9 +140,9 @@ 766D8CB22EC60BE600D05E07 /* Frameworks */ = { isa = PBXGroup; children = ( - 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */, - 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */, - 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */, + 765A5A802F35CD730003FF7D /* Penguin.xcframework */, + 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */, + 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */, D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */, ); name = Frameworks; @@ -189,6 +193,8 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 76AA18AC2F3B2A4D00DC8DFC /* ring_30Sec.caf */, + 76AA18AD2F3B2A4D00DC8DFC /* ring_30Sec.mp3 */, 61243B412EC5FA3700D46FA0 /* Helper */, 61243B472EC5FA3700D46FA0 /* Penguin */, 769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */, @@ -235,10 +241,10 @@ 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 8372B02399CDF54531650AD4 /* [CP] Embed Pods Frameworks */, 81DE7C26F41956799E954FCE /* [CP] Copy Pods Resources */, + 765A5A922F35CD8B0003FF7D /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); @@ -300,6 +306,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 76AA18AE2F3B2A4D00DC8DFC /* ring_30Sec.caf in Resources */, + 76AA18AF2F3B2A4D00DC8DFC /* ring_30Sec.mp3 in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 478CFA942E638C8E0064F3D7 /* GoogleService-Info.plist in Resources */, @@ -515,7 +523,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -524,8 +532,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -701,7 +709,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -710,8 +718,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -730,7 +738,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -739,8 +747,8 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index 08f3422..1eb27a2 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -109,7 +109,7 @@ "size" : "83.5x83.5" }, { - "filename" : "icon 1.jpg", + "filename" : "icon.jpg", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist index c179ec6..a9b1f53 100644 --- a/ios/Runner/GoogleService-Info.plist +++ b/ios/Runner/GoogleService-Info.plist @@ -2,10 +2,6 @@ - CLIENT_ID - 815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt ANDROID_CLIENT_ID 815750722565-m14h8mkosm7cnq6uh6rhqr54dn02d705.apps.googleusercontent.com API_KEY @@ -15,24 +11,24 @@ PLIST_VERSION 1 BUNDLE_ID - com.HMG.HMG-Smartphone + com.cloudsolutions.HMGPatientApp PROJECT_ID api-project-815750722565 STORAGE_BUCKET - api-project-815750722565.appspot.com + api-project-815750722565.firebasestorage.app IS_ADS_ENABLED - + IS_ANALYTICS_ENABLED - + IS_APPINVITE_ENABLED - + IS_GCM_ENABLED - + IS_SIGNIN_ENABLED - + GOOGLE_APP_ID - 1:815750722565:ios:328ec247a81a2ca23c186c + 1:815750722565:ios:34ec03c09e02e21f3c186c DATABASE_URL https://api-project-815750722565.firebaseio.com - + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index ab9828e..9c805ac 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -13,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - Dr. Alhabib + Dr. Alhabib Beta CFBundlePackageType APPL CFBundleShortVersionString diff --git a/ios/Runner/Penguin/PenguinView.swift b/ios/Runner/Penguin/PenguinView.swift index 508fb74..e683a35 100644 --- a/ios/Runner/Penguin/PenguinView.swift +++ b/ios/Runner/Penguin/PenguinView.swift @@ -309,22 +309,13 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia PenNavUIManager.shared.eventsDelegate = self - - - print("====== after eventsDelegate onPenNavSuccess =========") - - PenNavUIManager.shared.navigate(to: "108") - - +// print("====== after onPenNavSuccess navigate =========") +// PenNavUIManager.shared.navigate(to: "3-1") // Present the Penguin UI on top of the Flutter view controller - + print("====== after onPenNavSuccess present =========") PenNavUIManager.shared.present(root: controller, view: _view) - - - - print("====== after present onPenNavSuccess =========") print(model?.clinicID) @@ -347,11 +338,21 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia let clientID = self.model?.clientID, !clientID.isEmpty else { - print("Error: Config Client ID is nil or empty") + print("Error: Config Clinic ID is nil or empty") return } + + print("====== after onPenNavSuccess navigate =========") +// PenNavUIManager.shared.navigate(to: "3-1") { success, error in + PenNavUIManager.shared.navigate(to: clinicID) { success, error in + if success { + print("Nav Success") + } else { + print("Nav Error") + } + } @@ -373,20 +374,20 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia - guard let token = token else { - - print("Token is nil") - - return - - } - - print("Token Generated") - - print(token); +// guard let token = token else { +// +// print("Token is nil") +// +// return +// +// } +// +// print("Token Generated") +// +// print(token); - +// // self?.handleNavigation(clinicID: clinicID, token: token) { success, errorMessage in // // if success { @@ -425,7 +426,8 @@ class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitia PenNavUIManager.shared.setToken(token: token) - PenNavUIManager.shared.navigate(to: clinicID) +// PenNavUIManager.shared.navigate(to: clinicID) + PenNavUIManager.shared.navigate(to: "3-1") completion(true,nil) diff --git a/ios/Runner/ring_30Sec 2.caf b/ios/Runner/ring_30Sec 2.caf new file mode 100644 index 0000000..b5bb4b8 Binary files /dev/null and b/ios/Runner/ring_30Sec 2.caf differ diff --git a/ios/Runner/ring_30Sec.caf b/ios/Runner/ring_30Sec.caf new file mode 100644 index 0000000..b5bb4b8 Binary files /dev/null and b/ios/Runner/ring_30Sec.caf differ diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 4286618..970b1e4 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -7,9 +7,11 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/presentation/home/app_update_page.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:http/http.dart' as http; import '../exceptions/api_failure.dart'; @@ -36,6 +38,7 @@ abstract class ApiClient { required Function(dynamic response, int statusCode) onSuccess, required Function(String error, int statusCode) onFailure, Map? queryParams, + Map apiHeaders, bool isAllowAny, bool isExternal, bool isRCService, @@ -181,14 +184,30 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } + if (url.contains("HMGAI_Lab_Analyze_Orders_API")) { + url = "https://uat.hmgwebservices.com/Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; + body['TokenID'] = "@dm!n"; + } + + if (url.contains("HMGAI_Lab_Analyzer_API")) { + url = "https://uat.hmgwebservices.com/Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; + body['TokenID'] = "@dm!n"; + } + // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 4768663; + // body['PatientID'] = 4773715; // body['PatientTypeID'] = 1; // body['PatientOutSA'] = 0; // body['SessionID'] = "45786230487560q"; + + // body['IdentificationNo'] = "2235558844"; + // body['MobileNo'] = "966533147722"; + // body['PatientID'] = 809289; } - // body['IsNewFlutterApp'] = true; + if(!url.contains("/paymentApi")) { + body['IsNewFlutterApp'] = true; + } body.removeWhere((key, value) => value == null); } @@ -208,7 +227,8 @@ class ApiClientImp implements ApiClient { // Handle body encoding based on isBodyPlainText flag final dynamic requestBody = isBodyPlainText ? body : json.encode(body); debugPrint("uri: ${Uri.parse(url.trim())}"); - debugPrint("body: ${json.encode(body)}", wrapWidth: 2048); + var requestBodyJSON = json.encode(body); + debugPrint("body: $requestBodyJSON", wrapWidth: 2048); final response = await http.post(Uri.parse(url.trim()), body: requestBody, headers: headers); final int statusCode = response.statusCode; // log("response.body: ${response.body}"); @@ -233,8 +253,16 @@ class ApiClientImp implements ApiClient { } else { if (parsed['ErrorType'] == 4) { //TODO : handle app update - onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, - failureType: AppUpdateFailure("parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']")); + // onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, + // failureType: AppUpdateFailure("parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']")); + + ApiClient._navigationService.pushAndRemoveUntil( + CustomPageRoute( + page: AppUpdatePage( + appUpdateMessage: parsed['ErrorEndUserMessage'], + )), + ModalRoute.withName("/appUpdate") + ); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } if (parsed['ErrorType'] == 2) { @@ -354,6 +382,7 @@ class ApiClientImp implements ApiClient { {required Function(dynamic response, int statusCode) onSuccess, required Function(String error, int statusCode) onFailure, Map? queryParams, + Map? apiHeaders, bool isAllowAny = false, bool isExternal = false, bool isRCService = false}) async { @@ -378,7 +407,7 @@ class ApiClientImp implements ApiClient { if (await Utils.checkConnection(bypassConnectionCheck: true)) { final response = await http.get( Uri.parse(url.trim()), - headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}, + headers: apiHeaders ?? {'Content-Type': 'application/json', 'Accept': 'application/json'}, ); final int statusCode = response.statusCode; // print("statusCode :$statusCode"); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 87ca2f8..a57169d 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -1,6 +1,242 @@ import 'package:amazon_payfort/amazon_payfort.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +class ApiConsts { + static const maxSmallScreen = 660; + + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + + // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT + + static String baseUrl = 'https://hmgwebservices.com/'; // HIS API URL PROD + + static String rcBaseUrl = 'https://rc.hmg.com/'; // dRC API URL PROD + static String hmgPharmacyApiBaseUrl = 'https://hmgpharmacyapi.hmg.com/'; // dRC API URL PROD + static String symptomsCheckerApi = '${hmgPharmacyApiBaseUrl}symptomsapi/api/SymptomChecker'; // dRC API URL PROD + + static var payFortEnvironment = FortEnvironment.production; + static var applePayMerchantId = "merchant.com.hmgwebservices"; + + static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx'; //Payfort Payment Gateway URL LIVE + // static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; // Payfort Payment Gateway URL UAT + + static String TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout"; + static String GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; + static String GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; + + static String QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + + static String CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId="; + + // static String GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; + +// var payFortEnvironment = FortEnvironment.test; +// var applePayMerchantId = "merchant.com.hmgwebservices.uat"; + + static setBackendURLs() { + switch (appEnvironmentType) { + case AppEnvironmentTypeEnum.prod: + baseUrl = "https://hmgwebservices.com/"; + payFortEnvironment = FortEnvironment.production; + applePayMerchantId = "merchant.com.hmgwebservices"; + SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx"; + TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; + GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; + rcBaseUrl = 'https://rc.hmg.com/'; + QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId="; + break; + case AppEnvironmentTypeEnum.dev: + baseUrl = "https://uat.hmgwebservices.com/"; + payFortEnvironment = FortEnvironment.test; + applePayMerchantId = "merchant.com.hmgwebservices.uat"; + SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx'; + TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; + GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; + rcBaseUrl = 'https://rc.hmg.com/uat/'; + QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; + break; + case AppEnvironmentTypeEnum.uat: + baseUrl = "https://uat.hmgwebservices.com/"; + payFortEnvironment = FortEnvironment.test; + applePayMerchantId = "merchant.com.hmgwebservices.uat"; + SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx'; + TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; + GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; + rcBaseUrl = 'https://rc.hmg.com/uat/'; + QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; + break; + case AppEnvironmentTypeEnum.preProd: + baseUrl = "https://webservices.hmg.com/"; + payFortEnvironment = FortEnvironment.production; + applePayMerchantId = "merchant.com.hmgwebservices"; + SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx"; + TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; + GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; + rcBaseUrl = 'https://rc.hmg.com/'; + QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; + break; + case AppEnvironmentTypeEnum.qa: + baseUrl = "https://uat.hmgwebservices.com/"; + payFortEnvironment = FortEnvironment.test; + applePayMerchantId = "merchant.com.hmgwebservices.uat"; + SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx'; + TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; + GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; + rcBaseUrl = 'https://rc.hmg.com/uat/'; + QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; + break; + case AppEnvironmentTypeEnum.staging: + baseUrl = "https://uat.hmgwebservices.com/"; + payFortEnvironment = FortEnvironment.test; + applePayMerchantId = "merchant.com.hmgwebservices.uat"; + SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx'; + TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; + GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; + GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; + rcBaseUrl = 'https://rc.hmg.com/uat/'; + QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; + break; + } + } + + static final String selectDeviceImei = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI'; + static final String checkPatientAuth = 'Services/Authentication.svc/REST/CheckPatientAuthentication'; + + static final String sendActivationCode = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationType'; + static final String sendActivationCodeRegister = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationTypeForRegistration'; + static final String checkActivationCode = 'Services/Authentication.svc/REST/CheckActivationCode'; + static final String checkActivationCodeRegister = 'Services/Authentication.svc/REST/CheckActivationCodeForRegistration'; + static final String checkUsageAgreement = 'Services/Patients.svc/REST/CheckForUsageAgreement'; + static final String getUserAgreementContent = 'Services/Patients.svc/REST/GetUsageAgreementText'; + + static final String checkPatientForRegistration = 'Services/Authentication.svc/REST/CheckPatientForRegisteration'; + static final String checkUserStatus = 'Services/NHIC.svc/REST/GetPatientInfo'; + + static final String insertPatientDeviceIMEIData = 'Services/Patients.svc/REST/Patient_INSERTDeviceIMEI'; + static final String insertPatientMobileData = 'Services/MobileNotifications.svc/REST/Insert_PatientMobileDeviceInfo'; + static final String getPatientMobileData = '/Services/Authentication.svc/REST/GetMobileLoginInfo'; + static final String getPrivileges = 'Services/Patients.svc/REST/Service_Privilege'; + static final String registerUser = 'Services/Authentication.svc/REST/PatientRegistration'; + + static final String addFamilyFile = 'Services/Patients.svc/REST/ShareFamilyFileService'; + static final String sendFamilyFileActivation = 'Services/Authentication.svc/REST/SendActivationCodeForFamilyFile'; + static final String checkActivationCodeForFamily = 'Services/Authentication.svc/REST/CheckActivationCodeForFamilyFile'; + static final String getAllPendingRecordsByResponseId = 'Services/Authentication.svc/REST/GetAllPendingRecordsByResponseId'; + static final String getAllSharedRecordsByStatus = 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatus'; + static final String removeFileFromFamilyMembers = 'Services/Authentication.svc/REST/ActiveDeactive_PatientFile'; + static final String acceptAndRejectFamilyFile = 'Services/Authentication.svc/REST/Update_FileStatus'; + static final String getActivePrescriptionsDetails = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID'; + static final String getTermsConditions = 'Services/Patients.svc/Rest/GetUserTermsAndConditions'; + static final String getMonthlyReports = 'Services/Patients.svc/Rest/UpdatePateintHealthSummaryReport'; + static final String updatePatientEmail = 'Services/Patients.svc/Rest/UpdatePateintEmail'; + static final String getQrParkingDetails = 'Services/SWP.svc/REST/GetQRParkingByID'; + + // Ancillary Order Apis + static final String getOnlineAncillaryOrderList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList'; + static final String getOnlineAncillaryOrderProcList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderProcList'; + static final String generateAncillaryOrderInvoice = 'Services/Doctors.svc/REST/AutoGenerateAncillaryOrderInvoice'; + static final String autoGenerateAncillaryOrdersInvoice = 'Services/Doctors.svc/REST/AutoGenerateAncillaryOrderInvoice'; + static final String getRequestStatusByRequestID = 'Services/PayFort_Serv.svc/REST/GetRequestStatusByRequestID'; + + //Payment APIs + static final String applePayInsertRequest = "Services/PayFort_Serv.svc/REST/PayFort_ApplePayRequestData_Insert"; + static final String createAdvancePayments = 'Services/Patients.svc/REST/HIS_CreateAdvancePayment'; + static final String addAdvanceNumberRequest = 'Services/PayFort_Serv.svc/REST/AddAdvancedNumberRequest'; + + // RC COMPREHENSIVE MEDICAL CHECKUP ServIces + static final String allCMCOrdersRc = 'api/cmc/list'; + static final String allCMCServicesRc = 'api/cmc/getallcmc'; + static final String updateCMCOrder = 'api/cmc/update'; + static final String addCMCOrder = 'api/cmc/add'; + static final String getHospitalsList = 'Services/Lists.svc/REST/GetProject'; + + // RC HOME HEALTHCARE ServIces + static final String allHHCOrdersRc = 'api/hhc/list'; + static final String allHHCServicesRc = 'api/HHC/getallhhc'; + static final String updateHHCOrder = 'api/hhc/update'; + static final String addHHCOrder = 'api/HHC/add'; + + // SYMPTOMS CHECKER API + static final String symptomsUserLogin = '$symptomsCheckerApi/user_login'; + static final String getBodySymptomsByName = '$symptomsCheckerApi/GetBodySymptomsByName'; + static final String getRiskFactors = '$symptomsCheckerApi/GetRiskFactors'; + static final String getSuggestions = '$symptomsCheckerApi/GetSuggestion'; + static final String diagnosis = '$symptomsCheckerApi/GetDiagnosis'; + static final String explain = '$symptomsCheckerApi/ExplainDiagnosisResult'; + static final String getClinicFromCondition = '$symptomsCheckerApi/GetClinicsByCondition?condition='; + + //E-REFERRAL SERVICES + static final getAllRelationshipTypes = "Services/Patients.svc/REST/GetAllRelationshipTypes"; + static final sendActivationCodeForEReferral = 'Services/Authentication.svc/REST/SendActivationCodeForEReferral'; + static final checkActivationCodeForEReferral = 'Services/Authentication.svc/REST/CheckActivationCodeForEReferral'; + static final getAllCities = 'services/Lists.svc/rest/GetAllCities'; + static final createEReferral = "Services/Patients.svc/REST/CreateEReferral"; + static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals"; + + //WATER CONSUMPTION + static String h2oGetUserProgress = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress"; + static String h2oInsertUserActivity = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity"; + static String h2oInsertUserDetailsNew = "Services/H2ORemainder.svc/REST/H2O_InsertUserDetails_New"; + static String h2oGetUserDetail = "Services/H2ORemainder.svc/REST/H2O_GetUserDetails_New"; + static String h2oUpdateUserDetail = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetails_New"; + static String h2oUndoUserActivity = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity"; + + // HEALTH TRACKERS + // Blood Sugar (Diabetic) + static String getDiabeticResultAverage = 'Services/Patients.svc/REST/Patient_GetDiabeticResultAverage'; + static String getDiabeticResult = 'Services/Patients.svc/REST/Patient_GetDiabtecResults'; + static String addDiabeticResult = 'Services/Patients.svc/REST/Patient_AddDiabtecResult'; + static String updateDiabeticResult = 'Services/Patients.svc/REST/Patient_UpdateDiabtecResult'; + static String deactivateDiabeticStatus = 'Services/Patients.svc/REST/Patient_DeactivateDiabeticStatus'; + static String sendAverageBloodSugarReport = 'Services/Notifications.svc/REST/SendAverageBloodSugarReport'; + + // Blood Pressure + static String getBloodPressureResultAverage = 'Services/Patients.svc/REST/Patient_GetBloodPressureResultAverage'; + static String getBloodPressureResult = 'Services/Patients.svc/REST/Patient_GetBloodPressureResult'; + static String addBloodPressureResult = 'Services/Patients.svc/REST/Patient_AddBloodPressureResult'; + static String updateBloodPressureResult = 'Services/Patients.svc/REST/Patient_UpdateBloodPressureResult'; + static String deactivateBloodPressureStatus = 'Services/Patients.svc/REST/Patient_DeactivateBloodPressureStatus'; + static String sendAverageBloodPressureReport = 'Services/Notifications.svc/REST/SendAverageBloodPressureReport'; + + // Weight Measurement + static String getWeightMeasurementResultAverage = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResultAverage'; + static String getWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResult'; + static String addWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_AddWeightMeasurementResult'; + static String updateWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_UpdateWeightMeasurementResult'; + static String deactivateWeightMeasurementStatus = 'Services/Patients.svc/REST/Patient_DeactivateWeightMeasurementStatus'; + static String sendAverageBodyWeightReport = 'Services/Notifications.svc/REST/SendAverageBodyWeightReport'; + + //Blood Donation + static String bloodGroupUpdate = "Services/PatientVarification.svc/REST/BloodDonation_RegisterBloodType"; + static String userAgreementForBloodGroupUpdate = "Services/PatientVarification.svc/REST/AddUserAgreementForBloodDonation"; + static String getProjectsHaveBDClinics = "Services/OUTPs.svc/REST/BD_getProjectsHaveBDClinics"; + static String getClinicsBDFreeSlots = "Services/OUTPs.svc/REST/BD_GetFreeSlots"; + + static String getPatientBloodGroup = "services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails"; + static String getPatientBloodAgreement = "Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation"; + static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; + static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; + + // ************ static values for Api **************** + static final double appVersionID = 19.7; + // 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'; +} + // PACKAGES and OFFERS var EXA_CART_API_BASE_URL = 'https://mdlaboratories.com/offersdiscounts'; // var EXA_CART_API_BASE_URL = 'http://10.200.101.75:9000'; @@ -380,6 +616,8 @@ var GET_PATIENT_SICK_LEAVE = 'Services/Patients.svc/REST/GetPatientSickLeave'; var GET_PATIENT_SICK_LEAVE_STATUS = 'Services/Patients.svc/REST/GetPatientSickLeave_Status'; +var GET_SERVICES_PRICE_LIST = 'Services/OUTPs.svc/REST/GetServicesPriceList'; + var SendSickLeaveEmail = 'Services/Notifications.svc/REST/SendSickLeaveEmail'; var GET_PATIENT_AdVANCE_BALANCE_AMOUNT = 'Services/Patients.svc/REST/GetPatientAdvanceBalanceAmount'; @@ -678,239 +916,6 @@ var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatb const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard'; -class ApiConsts { - static const maxSmallScreen = 660; - - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; - - // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT - - static String baseUrl = 'https://hmgwebservices.com/'; // HIS API URL PROD - - static String rcBaseUrl = 'https://rc.hmg.com/'; // dRC API URL PROD - static String hmgPharmacyApiBaseUrl = 'https://hmgpharmacyapi.hmg.com/'; // dRC API URL PROD - static String symptomsCheckerApi = '${hmgPharmacyApiBaseUrl}symptomsapi/api/SymptomChecker'; // dRC API URL PROD - - static var payFortEnvironment = FortEnvironment.production; - static var applePayMerchantId = "merchant.com.hmgwebservices"; - - static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx'; //Payfort Payment Gateway URL LIVE - // static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; // Payfort Payment Gateway URL UAT - - static String TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout"; - static String GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; - static String GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; - - static String QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; - - static String CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId="; - - // static String GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; - -// var payFortEnvironment = FortEnvironment.test; -// var applePayMerchantId = "merchant.com.hmgwebservices.uat"; - - static setBackendURLs() { - switch (appEnvironmentType) { - case AppEnvironmentTypeEnum.prod: - baseUrl = "https://hmgwebservices.com/"; - payFortEnvironment = FortEnvironment.production; - applePayMerchantId = "merchant.com.hmgwebservices"; - SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx"; - TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout"; - GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; - GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; - rcBaseUrl = 'https://rc.hmg.com/'; - QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; - CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId="; - break; - case AppEnvironmentTypeEnum.dev: - baseUrl = "https://uat.hmgwebservices.com/"; - payFortEnvironment = FortEnvironment.test; - applePayMerchantId = "merchant.com.hmgwebservices.uat"; - SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx'; - TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; - GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; - GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - rcBaseUrl = 'https://rc.hmg.com/uat/'; - QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; - CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; - break; - case AppEnvironmentTypeEnum.uat: - baseUrl = "https://uat.hmgwebservices.com/"; - payFortEnvironment = FortEnvironment.test; - applePayMerchantId = "merchant.com.hmgwebservices.uat"; - SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx'; - TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; - GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; - GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - rcBaseUrl = 'https://rc.hmg.com/uat/'; - QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; - CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; - break; - case AppEnvironmentTypeEnum.preProd: - baseUrl = "https://webservices.hmg.com/"; - payFortEnvironment = FortEnvironment.production; - applePayMerchantId = "merchant.com.hmgwebservices"; - SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx"; - TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; - GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; - GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - rcBaseUrl = 'https://rc.hmg.com/'; - QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; - CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; - break; - case AppEnvironmentTypeEnum.qa: - baseUrl = "https://uat.hmgwebservices.com/"; - payFortEnvironment = FortEnvironment.test; - applePayMerchantId = "merchant.com.hmgwebservices.uat"; - SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx'; - TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; - GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; - GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - rcBaseUrl = 'https://rc.hmg.com/uat/'; - QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; - CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; - break; - case AppEnvironmentTypeEnum.staging: - baseUrl = "https://uat.hmgwebservices.com/"; - payFortEnvironment = FortEnvironment.test; - applePayMerchantId = "merchant.com.hmgwebservices.uat"; - SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx'; - TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; - GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; - GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - rcBaseUrl = 'https://rc.hmg.com/uat/'; - QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; - CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId"; - break; - } - } - - static final String selectDeviceImei = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI'; - static final String checkPatientAuth = 'Services/Authentication.svc/REST/CheckPatientAuthentication'; - - static final String sendActivationCode = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationType'; - static final String sendActivationCodeRegister = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationTypeForRegistration'; - static final String checkActivationCode = 'Services/Authentication.svc/REST/CheckActivationCode'; - static final String checkActivationCodeRegister = 'Services/Authentication.svc/REST/CheckActivationCodeForRegistration'; - static final String checkUsageAgreement = 'Services/Patients.svc/REST/CheckForUsageAgreement'; - static final String getUserAgreementContent = 'Services/Patients.svc/REST/GetUsageAgreementText'; - - static final String checkPatientForRegistration = 'Services/Authentication.svc/REST/CheckPatientForRegisteration'; - static final String checkUserStatus = 'Services/NHIC.svc/REST/GetPatientInfo'; - - static final String insertPatientDeviceIMEIData = 'Services/Patients.svc/REST/Patient_INSERTDeviceIMEI'; - static final String insertPatientMobileData = 'Services/MobileNotifications.svc/REST/Insert_PatientMobileDeviceInfo'; - static final String getPatientMobileData = '/Services/Authentication.svc/REST/GetMobileLoginInfo'; - static final String getPrivileges = 'Services/Patients.svc/REST/Service_Privilege'; - static final String registerUser = 'Services/Authentication.svc/REST/PatientRegistration'; - - static final String addFamilyFile = 'Services/Patients.svc/REST/ShareFamilyFileService'; - static final String sendFamilyFileActivation = 'Services/Authentication.svc/REST/SendActivationCodeForFamilyFile'; - static final String checkActivationCodeForFamily = 'Services/Authentication.svc/REST/CheckActivationCodeForFamilyFile'; - static final String getAllPendingRecordsByResponseId = 'Services/Authentication.svc/REST/GetAllPendingRecordsByResponseId'; - static final String getAllSharedRecordsByStatus = 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatus'; - static final String removeFileFromFamilyMembers = 'Services/Authentication.svc/REST/ActiveDeactive_PatientFile'; - static final String acceptAndRejectFamilyFile = 'Services/Authentication.svc/REST/Update_FileStatus'; - static final String getActivePrescriptionsDetails = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID'; - static final String getTermsConditions = 'Services/Patients.svc/Rest/GetUserTermsAndConditions'; - static final String getMonthlyReports = 'Services/Patients.svc/Rest/UpdatePateintHealthSummaryReport'; - static final String updatePatientEmail = 'Services/Patients.svc/Rest/UpdatePateintEmail'; - static final String getQrParkingDetails = 'Services/SWP.svc/REST/GetQRParkingByID'; - - // Ancillary Order Apis - static final String getOnlineAncillaryOrderList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList'; - static final String getOnlineAncillaryOrderProcList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderProcList'; - static final String generateAncillaryOrderInvoice = 'Services/Doctors.svc/REST/AutoGenerateAncillaryOrderInvoice'; - static final String autoGenerateAncillaryOrdersInvoice = 'Services/Doctors.svc/REST/AutoGenerateAncillaryOrderInvoice'; - static final String getRequestStatusByRequestID = 'Services/PayFort_Serv.svc/REST/GetRequestStatusByRequestID'; - - //Payment APIs - static final String applePayInsertRequest = "Services/PayFort_Serv.svc/REST/PayFort_ApplePayRequestData_Insert"; - static final String createAdvancePayments = 'Services/Patients.svc/REST/HIS_CreateAdvancePayment'; - static final String addAdvanceNumberRequest = 'Services/PayFort_Serv.svc/REST/AddAdvancedNumberRequest'; - - // RC COMPREHENSIVE MEDICAL CHECKUP ServIces - static final String allCMCOrdersRc = 'api/cmc/list'; - static final String allCMCServicesRc = 'api/cmc/getallcmc'; - static final String updateCMCOrder = 'api/cmc/update'; - static final String addCMCOrder = 'api/cmc/add'; - static final String getHospitalsList = 'Services/Lists.svc/REST/GetProject'; - - // RC HOME HEALTHCARE ServIces - static final String allHHCOrdersRc = 'api/hhc/list'; - static final String allHHCServicesRc = 'api/HHC/getallhhc'; - static final String updateHHCOrder = 'api/hhc/update'; - static final String addHHCOrder = 'api/HHC/add'; - - // SYMPTOMS CHECKER API - static final String symptomsUserLogin = '$symptomsCheckerApi/user_login'; - static final String getBodySymptomsByName = '$symptomsCheckerApi/GetBodySymptomsByName'; - static final String getRiskFactors = '$symptomsCheckerApi/GetRiskFactors'; - static final String getSuggestions = '$symptomsCheckerApi/GetSuggestion'; - static final String diagnosis = '$symptomsCheckerApi/GetDiagnosis'; - static final String explain = '$symptomsCheckerApi/ExplainDiagnosisResult'; - - //E-REFERRAL SERVICES - static final getAllRelationshipTypes = "Services/Patients.svc/REST/GetAllRelationshipTypes"; - static final sendActivationCodeForEReferral = 'Services/Authentication.svc/REST/SendActivationCodeForEReferral'; - static final checkActivationCodeForEReferral = 'Services/Authentication.svc/REST/CheckActivationCodeForEReferral'; - static final getAllCities = 'services/Lists.svc/rest/GetAllCities'; - static final createEReferral = "Services/Patients.svc/REST/CreateEReferral"; - static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals"; - - //WATER CONSUMPTION - static String h2oGetUserProgress = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress"; - static String h2oInsertUserActivity = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity"; - static String h2oInsertUserDetailsNew = "Services/H2ORemainder.svc/REST/H2O_InsertUserDetails_New"; - static String h2oGetUserDetail = "Services/H2ORemainder.svc/REST/H2O_GetUserDetails_New"; - static String h2oUpdateUserDetail = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetails_New"; - static String h2oUndoUserActivity = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity"; - - // HEALTH TRACKERS - // Blood Sugar (Diabetic) - static String getDiabeticResultAverage = 'Services/Patients.svc/REST/Patient_GetDiabeticResultAverage'; - static String getDiabeticResult = 'Services/Patients.svc/REST/Patient_GetDiabtecResults'; - static String addDiabeticResult = 'Services/Patients.svc/REST/Patient_AddDiabtecResult'; - static String updateDiabeticResult = 'Services/Patients.svc/REST/Patient_UpdateDiabtecResult'; - static String deactivateDiabeticStatus = 'Services/Patients.svc/REST/Patient_DeactivateDiabeticStatus'; - static String sendAverageBloodSugarReport = 'Services/Notifications.svc/REST/SendAverageBloodSugarReport'; - - // Blood Pressure - static String getBloodPressureResultAverage = 'Services/Patients.svc/REST/Patient_GetBloodPressureResultAverage'; - static String getBloodPressureResult = 'Services/Patients.svc/REST/Patient_GetBloodPressureResult'; - static String addBloodPressureResult = 'Services/Patients.svc/REST/Patient_AddBloodPressureResult'; - static String updateBloodPressureResult = 'Services/Patients.svc/REST/Patient_UpdateBloodPressureResult'; - static String deactivateBloodPressureStatus = 'Services/Patients.svc/REST/Patient_DeactivateBloodPressureStatus'; - static String sendAverageBloodPressureReport = 'Services/Notifications.svc/REST/SendAverageBloodPressureReport'; - - // Weight Measurement - static String getWeightMeasurementResultAverage = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResultAverage'; - static String getWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_GetWeightMeasurementResult'; - static String addWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_AddWeightMeasurementResult'; - static String updateWeightMeasurementResult = 'Services/Patients.svc/REST/Patient_UpdateWeightMeasurementResult'; - static String deactivateWeightMeasurementStatus = 'Services/Patients.svc/REST/Patient_DeactivateWeightMeasurementStatus'; - static String sendAverageBodyWeightReport = 'Services/Notifications.svc/REST/SendAverageBodyWeightReport'; - - //Blood Donation - static String bloodGroupUpdate = "Services/PatientVarification.svc/REST/BloodDonation_RegisterBloodType"; - static String userAgreementForBloodGroupUpdate = "Services/PatientVarification.svc/REST/AddUserAgreementForBloodDonation"; - static String getProjectsHaveBDClinics = "Services/OUTPs.svc/REST/BD_getProjectsHaveBDClinics"; - static String getClinicsBDFreeSlots = "Services/OUTPs.svc/REST/BD_GetFreeSlots"; - - - static String getPatientBloodGroup = "services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails"; - static String getPatientBloodAgreement = "Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation"; - - // ************ static values for Api **************** - static final double appVersionID = 50.3; - 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'; -} - class ApiKeyConstants { static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng'; } diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 5b8d2ed..942eb46 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -251,6 +251,10 @@ class AppAssets { static const String alertSquare = '$svgBasePath/alert-square.svg'; static const String arrowRight = '$svgBasePath/arrow-right.svg'; static const String tickIcon = '$svgBasePath/tick.svg'; + static const String guradIcon = '$svgBasePath/guard_icon.svg'; + static const String calculatorIcon = '$svgBasePath/calculator_icon.svg'; + static const String informationIcon = '$svgBasePath/information-circle.svg'; + static const String highCautionIcon = '$svgBasePath/high_caution.svg'; // Symptoms Checker static const String calendarGrey = '$svgBasePath/calendar-grey.svg'; @@ -291,6 +295,10 @@ class AppAssets { static const String bulb = '$svgBasePath/bulb.svg'; static const String switchBtn = '$svgBasePath/switch.svg'; + static const String livecare_video = '$svgBasePath/livecare_video.svg'; + static const String livecare_audio = '$svgBasePath/livecare_audio.svg'; + static const String livecare_phone = '$svgBasePath/livecare_phone.svg'; + //Health Trackers static const String bloodPressureIcon = '$svgBasePath/blood_pressure_icon.svg'; static const String bloodSugarOnlyIcon = '$svgBasePath/blood_sugar_only_icon.svg'; @@ -322,6 +330,7 @@ class AppAssets { static const String doctorResponseIcon = '$svgBasePath/doctor_response_icon.svg'; static const String changeLanguageHomePageIcon = '$svgBasePath/change_language_home_page.svg'; + static const String aiOverView = '$svgBasePath/ai_overview.svg'; // PNGS // static const String hmgLogo = '$pngBasePath/hmg_logo.png'; @@ -346,8 +355,10 @@ class AppAssets { static const String maleIcon = '$pngBasePath/male_icon.png'; static const String femaleIcon = '$pngBasePath/female_icon.png'; - static const String fullBodyFront = '$pngBasePath/full_body_front.png'; - static const String fullBodyBack = '$pngBasePath/full_body_back.png'; + static const String fullBodyFrontMale = '$pngBasePath/full_body_front_male.png'; + static const String fullBodyBackMale = '$pngBasePath/full_body_back_male.png'; + static const String fullBodyFrontFemale = '$pngBasePath/full_body_front_female.png'; + static const String fullBodyBackFemale = '$pngBasePath/full_body_back_female.png'; static const String bmiFullBody = '$pngBasePath/bmi_image_1.png'; } diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 0560ff9..e7ac051 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -54,6 +54,8 @@ import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_v import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_repo.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; @@ -169,6 +171,7 @@ class AppDependencies { getIt.registerLazySingleton(() => QrParkingRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => NotificationsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => AskDoctorRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => ServicesPriceListRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -311,5 +314,6 @@ class AppDependencies { getIt.registerLazySingleton(() => AskDoctorViewModel(askDoctorRepo: getIt(), errorHandlerService: getIt())); + getIt.registerLazySingleton(() => ServicesPriceListViewModel(servicesPriceListRepo: getIt(), errorHandlerService: getIt())); } } diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index 746d2a7..2b13473 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -110,7 +110,7 @@ class DateUtil { } static String formatDateToTime(DateTime date) { - return DateFormat('hh:mm a').format(date); + return DateFormat('hh:mm a', "en-US").format(date); } static String yearMonthDay(DateTime dateTime) { @@ -487,7 +487,9 @@ class DateUtil { } static String getFormattedDate(DateTime dateTime, String formattedString) { - return DateFormat(formattedString).format(dateTime); + String formattedDate = DateFormat(formattedString, "en-US").format(dateTime); + print(formattedDate); + return formattedDate; } static convertISODateToJsonDate(String isoDate) { diff --git a/lib/core/utils/loading_utils.dart b/lib/core/utils/loading_utils.dart index 1a99278..bd24d30 100644 --- a/lib/core/utils/loading_utils.dart +++ b/lib/core/utils/loading_utils.dart @@ -38,7 +38,7 @@ class LoadingUtils { child: Material( child: Center( child: isSuccessDialog ? Utils.getSuccessWidget(loadingText: loadingText) : Utils.getLoadingWidget(loadingText: loadingText), - ), + ).paddingSymmetrical(24.w, 0), ), ); }).then((value) { diff --git a/lib/core/utils/penguin_method_channel.dart b/lib/core/utils/penguin_method_channel.dart index 69ef4be..c3da724 100644 --- a/lib/core/utils/penguin_method_channel.dart +++ b/lib/core/utils/penguin_method_channel.dart @@ -12,13 +12,17 @@ class PenguinMethodChannel { try { await _channel.invokeMethod('launchPenguin', { "storyboardName": storyboardName, - "baseURL": "https://penguinuat.hmg.com", - // "dataURL": "https://hmg.nav.penguinin.com", - // "positionURL": "https://hmg.nav.penguinin.com", - // "dataURL": "https://hmg-v33.local.penguinin.com", - // "positionURL": "https://hmg-v33.local.penguinin.com", - "dataURL": "https://penguinuat.hmg.com", - "positionURL": "https://penguinuat.hmg.com", + + // UAT URLs + // "baseURL": "https://penguinuat.hmg.com", + // "dataURL": "https://penguinuat.hmg.com", + // "positionURL": "https://penguinuat.hmg.com", + + // Prod URLs + "baseURL": "https://eagleiq.hmg.com", + "dataURL": "https://eagleiq.hmg.com", + "positionURL": "https://eagleiq.hmg.com", + "dataServiceName": "api", "positionServiceName": "pe", "clientID": "HMG", diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart index 88e8cc8..8215d57 100644 --- a/lib/core/utils/push_notification_handler.dart +++ b/lib/core/utils/push_notification_handler.dart @@ -95,7 +95,7 @@ _incomingCall(Map data) async { missedCallNotificationChannelName: "Missed Call", isShowCallID: false), ios: IOSParams( - iconName: 'Meena Health', + iconName: 'CallKitLogo', handleType: 'generic', supportsVideo: true, maximumCallGroups: 2, @@ -372,6 +372,14 @@ class PushNotificationHandler { } }); + if (Platform.isIOS) { + FirebaseMessaging.instance.getAPNSToken().then((String? token) { + print("Push Notification getAPNSToken: ${token!}"); + }).catchError((err) { + print("Push Notification getAPNSToken ERR: ${err.toString()}"); + }); + } + FirebaseMessaging.instance.getToken().then((String? token) { print("Push Notification getToken: ${token!}"); onToken(token!); diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index e57039c..dd81a13 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -210,8 +210,8 @@ class RequestUtils { List names = fullName != null ? fullName.split(" ") : []; var dob = appState.getUserRegistrationPayload.dob; - final DateFormat dateFormat1 = DateFormat('MM/dd/yyyy'); - final DateFormat dateFormat2 = DateFormat('dd/MM/yyyy'); + final DateFormat dateFormat1 = DateFormat('MM/dd/yyyy', "en-US"); + final DateFormat dateFormat2 = DateFormat('dd/MM/yyyy', "en-US"); DateTime gregorianDate = dateFormat2.parse(dob!); HijriGregDate hijriDate = HijriGregConverter.gregorianToHijri(gregorianDate); String? date = "${hijriDate.day}/${hijriDate.month}/${hijriDate.year}"; diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 673ef73..671312d 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -128,7 +128,7 @@ class Utils { static String convertStringToDateTime(String dateTimeString) { String timeString = dateTimeString; // Parse the time string using DateFormat - DateFormat format = DateFormat.Hms(); // 'Hms' = 'HH:mm:ss' + DateFormat format = DateFormat.Hms(["en-US"]); // 'Hms' = 'HH:mm:ss' DateTime time = format.parse(timeString); DateTime now = DateTime.now(); @@ -408,6 +408,8 @@ class Utils { Widget? bodyWidget, Function? onConfirmTap, Function? onCancelTap, + String? cancelText, + String? confirmText, }) { return Column( mainAxisAlignment: MainAxisAlignment.center, @@ -425,7 +427,7 @@ class Utils { children: [ Expanded( child: CustomButton( - text: LocaleKeys.cancel.tr(), + text: cancelText ?? LocaleKeys.cancel.tr(), onPressed: () { if (onCancelTap != null) { onCancelTap(); @@ -434,14 +436,14 @@ class Utils { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - icon: AppAssets.cancel, - iconColor: AppColors.whiteColor, + // icon: AppAssets.cancel, + // iconColor: AppColors.whiteColor, ), ), SizedBox(width: 8.h), Expanded( child: CustomButton( - text: LocaleKeys.confirm.tr(), + text: confirmText ?? LocaleKeys.confirm.tr(), onPressed: () async { if (onConfirmTap != null) { onConfirmTap(); @@ -450,7 +452,7 @@ class Utils { backgroundColor: AppColors.bgGreenColor, borderColor: AppColors.bgGreenColor, textColor: Colors.white, - icon: AppAssets.confirm, + // icon: AppAssets.confirm, ), ), ], @@ -987,4 +989,23 @@ class Utils { return isAllowed; } + + static bool containsArabic(String text) { + // Using Unicode range for Arabic characters + final RegExp arabicRegex = RegExp(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]'); + return arabicRegex.hasMatch(text); + } + + static String toEnglishNumbers(String input) { + const english = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + const arabic = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩']; + const persian = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; + + String result = input; + for (int i = 0; i < 10; i++) { + result = result.replaceAll(arabic[i], english[i]); + result = result.replaceAll(persian[i], english[i]); + } + return result; + } } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 30b0f7e..792bff5 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -23,8 +23,7 @@ extension CapExtension on String { extension EmailValidator on String { Widget get toWidget => Text(this); - Widget toText8({Color? color, FontWeight? fontWeight, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => - Text( + Widget toText8({Color? color, FontWeight? fontWeight, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( this, maxLines: maxlines, overflow: textOverflow, @@ -38,7 +37,8 @@ extension EmailValidator on String { ); Widget toText10( - {Color? color, + {bool isEnglishOnly = false, + Color? color, FontWeight? weight, bool isBold = false, bool isUnderLine = false, @@ -59,19 +59,20 @@ extension EmailValidator on String { color: color ?? AppColors.blackColor, letterSpacing: letterSpacing, decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', decorationColor: color ?? AppColors.blackColor), ); Widget toText9( - {Color? color, - FontWeight? weight, - bool isBold = false, - bool isUnderLine = false, - bool isCenter = false, - int? maxlines, - FontStyle? fontStyle, - TextOverflow? textOverflow, - double letterSpacing = 0}) => + {Color? color, + FontWeight? weight, + bool isBold = false, + bool isUnderLine = false, + bool isCenter = false, + int? maxlines, + FontStyle? fontStyle, + TextOverflow? textOverflow, + double letterSpacing = 0}) => Text( this, textAlign: isCenter ? TextAlign.center : null, @@ -87,15 +88,7 @@ extension EmailValidator on String { decorationColor: color ?? AppColors.blackColor), ); - Widget toText11( - {Color? color, - FontWeight? weight, - bool isUnderLine = false, - bool isCenter = false, - bool isBold = false, - int maxLine = 0, - double letterSpacing = 0}) => - Text( + Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isCenter = false, bool isBold = false, int maxLine = 0, double letterSpacing = 0}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -110,7 +103,7 @@ extension EmailValidator on String { ); Widget toText12( - {Color? color, + {bool isEnglishOnly = false, Color? color, bool isUnderLine = false, TextAlign textAlignment = TextAlign.start, bool isBold = false, @@ -131,6 +124,7 @@ extension EmailValidator on String { height: height, decorationColor: isUnderLine ? AppColors.blackColor : null, decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', ), ); @@ -176,15 +170,7 @@ extension EmailValidator on String { ), ); - Widget toText13( - {Color? color, - bool isUnderLine = false, - bool isBold = false, - bool isCenter = false, - int maxLine = 0, - FontWeight? weight, - double? letterSpacing = 0}) => - Text( + Widget toText13({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0, FontWeight? weight, double? letterSpacing = 0}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -197,6 +183,7 @@ extension EmailValidator on String { ); Widget toText14({ + bool isEnglishOnly = false, Color? color, bool isUnderLine = false, bool isBold = false, @@ -205,6 +192,7 @@ extension EmailValidator on String { int? maxlines, double? letterSpacing = 0, double? height, + String? fontFamily, TextOverflow? textOverflow, }) => Text( @@ -219,18 +207,11 @@ extension EmailValidator on String { height: height, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: fontFamily ?? (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), decorationColor: color ?? AppColors.blackColor), ); - Widget toText15( - {Color? color, - bool isUnderLine = false, - bool isBold = false, - bool isCenter = false, - FontWeight? weight, - int? maxlines, - double? letterSpacing = -1}) => - Text( + Widget toText15({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines, double? letterSpacing = -1}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, @@ -242,8 +223,7 @@ extension EmailValidator on String { decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText16( - {Color? color, + Widget toText16({bool isEnglishOnly = false, Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, @@ -252,12 +232,14 @@ extension EmailValidator on String { TextAlign? textAlign, FontWeight? weight, TextOverflow? textOverflow, + String? fontFamily, double? letterSpacing = -0.4, Color decorationColor = AppColors.errorColor}) => Text( this, maxLines: maxlines, - textAlign: isCenter ? TextAlign.center : null, + textAlign: isCenter ? textAlign ?? TextAlign.center : null, + // locale: Locale('en', 'US'), style: TextStyle( color: color ?? AppColors.blackColor, fontSize: 16.f, @@ -266,14 +248,14 @@ extension EmailValidator on String { overflow: textOverflow, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, + fontFamily: fontFamily ?? (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), decorationColor: decorationColor), ); - Widget toText17({Color? color, bool isBold = false, bool isCenter = false}) => Text( + Widget toText17({bool isEnglishOnly = false, Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), ); Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCenter = false, int? maxlines, TextOverflow? textOverflow}) => Text( @@ -281,17 +263,12 @@ extension EmailValidator on String { textAlign: isCenter ? TextAlign.center : null, this, overflow: textOverflow, - style: TextStyle( - fontSize: 18.f, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), - color: color ?? AppColors.blackColor, - letterSpacing: -0.4), + style: TextStyle(fontSize: 18.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText19({Color? color, bool isBold = false}) => Text( this, - style: TextStyle( - fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), + style: TextStyle(fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText20({ @@ -301,86 +278,51 @@ extension EmailValidator on String { }) => Text( this, - style: TextStyle( - fontSize: 20.f, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), - color: color ?? AppColors.blackColor, - letterSpacing: -0.4), + style: TextStyle(fontSize: 20.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( this, maxLines: maxlines, - style: TextStyle( - color: color ?? AppColors.blackColor, - fontSize: 21.f, - letterSpacing: -1, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), + style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 21.f, letterSpacing: -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), ); Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - height: 1, - color: color ?? AppColors.blackColor, - fontSize: 22.f, - letterSpacing: -1, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 1, color: color ?? AppColors.blackColor, fontSize: 22.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText24({Color? color, bool isBold = false, bool isCenter = false, FontWeight? fontWeight, double? letterSpacing}) => Text( this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - height: 23 / 24, - color: color ?? AppColors.blackColor, - fontSize: 24.f, - letterSpacing: letterSpacing ?? -1, - fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.normal), + height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.normal), ); Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false, FontWeight? weight, double? letterSpacing}) => Text( this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - height: height ?? 23 / 26, - color: color ?? AppColors.blackColor, - fontSize: 26.f, - letterSpacing: letterSpacing ?? -1, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), + height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.f, letterSpacing: letterSpacing ?? -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), ); - Widget toText28({Color? color, bool isBold = false, double? height, bool isCenter = false, double? letterSpacing}) => Text( + Widget toText28({bool isEnglishOnly = false, Color? color, bool isBold = false, double? height, bool isCenter = false, double? letterSpacing}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - height: height ?? 23 / 28, - color: color ?? AppColors.blackColor, - fontSize: 28.f, - letterSpacing: letterSpacing ?? -1, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: height ?? 23 / 28, color: color ?? AppColors.blackColor, fontSize: 28.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), ); - Widget toText32({FontWeight? weight, Color? color, bool isBold = false, bool isCenter = false}) => Text( + Widget toText32({bool isEnglishOnly = false, FontWeight? weight, Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - height: 32 / 32, - color: color ?? AppColors.blackColor, - fontSize: 32.f, - letterSpacing: -1, - fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal), + height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal), ); Widget toText44({Color? color, bool isBold = false}) => Text( this, - style: TextStyle( - height: 32 / 32, - color: color ?? AppColors.blackColor, - fontSize: 44.f, - letterSpacing: -1, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) { @@ -416,9 +358,7 @@ extension EmailValidator on String { } bool isValidEmail() { - return RegExp( - r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$') - .hasMatch(this); + return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this); } String toFormattedDate() { @@ -485,11 +425,12 @@ extension DynamicTextStyleExtension on BuildContext { Locale? locale, TextBaseline? textBaseline, FontStyle? fontStyle, + String? fontFamily, bool isLanguageSwitcher = false}) { AppState appState = getIt.get(); final family = appState.getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'; return TextStyle( - fontFamily: family, + fontFamily: fontFamily ?? family, fontSize: fontSize, fontWeight: fontWeight, color: color, diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index b48912d..9a3fffc 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:get_it/get_it.dart'; import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; @@ -74,6 +75,7 @@ class AuthenticationViewModel extends ChangeNotifier { dobController = TextEditingController(), nameController = TextEditingController(), emailController = TextEditingController(); + CountryEnum selectedCountrySignup = CountryEnum.saudiArabia; MaritalStatusTypeEnum? maritalStatus; GenderTypeEnum? genderType; @@ -279,9 +281,10 @@ class AuthenticationViewModel extends ChangeNotifier { loginTypeEnum = otpTypeEnum == OTPTypeEnum.sms ? LoginTypeEnum.sms : LoginTypeEnum.whatsapp; - if (phoneNumberController.text.isEmpty) { - phoneNumberController.text = "504278212"; - } + // if (phoneNumberController.text.isEmpty) { + // phoneNumberController.text = "504278212"; + // } + bool isValidated = ValidationUtils.isValidatePhoneAndId(phoneNumber: phoneNumberController.text, nationalId: nationalIdController.text); if (!isValidated) { @@ -404,7 +407,6 @@ class AuthenticationViewModel extends ChangeNotifier { return; } patientShareRequestID = apiResponse.data['PatientShareRequestID']; - print("PatientShareRequestID ==============: $patientShareRequestID"); _appState.setFamilyFileTokenID = apiResponse.data['LogInTokenID']; } if (apiResponse.messageStatus == 2) { @@ -580,9 +582,13 @@ class AuthenticationViewModel extends ChangeNotifier { MedicalFileViewModel medicalVm = getIt(); if (!_appState.getIsChildLoggedIn) { await medicalVm.getFamilyFiles(status: 0); + if (isFormFamilyFile) { + await medicalVm.getAllPendingRecordsByResponseId(); + _navigationService.pushAndReplace(AppRoutes.medicalFilePage); + } else { + _navigationService.replaceAllRoutesAndNavigateToLanding(); + } // await medicalVm.getAllPendingRecordsByResponseId(); - // _navigationService.replaceAllRoutesAndNavigateToLanding(); - _navigationService.popUntilNamed(AppRoutes.landingScreen); } } else { if (activation.list != null && activation.list!.isNotEmpty) { @@ -742,7 +748,12 @@ class AuthenticationViewModel extends ChangeNotifier { Future onWrongActivationCode({String? message}) async { otpScreenNotifier.value = true; - await _dialogService.showErrorBottomSheet(message: message ?? "Something went wrong. ", onOkPressed: () {}); + await _dialogService.showErrorBottomSheet( + message: message ?? "Something went wrong. ", + onOkPressed: () { + LoaderBottomSheet.hideLoader(); + _navigationService.pushAndReplace(AppRoutes.loginScreen); + }); } loginWithFingerPrintFace(Function success) async { @@ -836,7 +847,7 @@ class AuthenticationViewModel extends ChangeNotifier { } else { LoadingUtils.hideFullScreenLoader(); if (apiResponse.data["MessageStatus"] == 1) { - LoadingUtils.showFullScreenLoader(isSuccessDialog: true); + LoadingUtils.showFullScreenLoader(isSuccessDialog: true, 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. await clearDefaultInputValues(); // This will Clear All Default Values Of User. Future.delayed(Duration(seconds: 1), () { @@ -1045,6 +1056,7 @@ class AuthenticationViewModel extends ChangeNotifier { } Future getServicePrivilege() async { + ApiConsts.setBackendURLs(); final resultEither = await _authenticationRepo.getServicePrivilege(); List privilegeModelList = []; List vidaPlusProjectListModel = []; @@ -1052,7 +1064,10 @@ class AuthenticationViewModel extends ChangeNotifier { List projectDetailListModel = []; resultEither.fold( - (failure) async => await _errorHandlerService.handleError(failure: failure), + // (failure) async => await _errorHandlerService.handleError(failure: failure), + (failure) async { + print(failure.message); + }, (apiResponse) async { if (apiResponse.messageStatus == 2) { await _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage ?? "ErrorEmpty"); diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index cab79ad..fc541d2 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -185,7 +185,7 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { body: mapDevice, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; - onError!(error); + // onError!(error); }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 34858b4..c6c5faf 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -34,6 +34,7 @@ import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'models/resp_models/get_livecare_clinics_response_model.dart'; @@ -103,6 +104,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { String selectedAppointmentDate = ""; String selectedAppointmentTime = ""; + String selectedAppointmentDisplayDate = ""; dynamic freeSlotsResponse; @@ -385,9 +387,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } - setSelectedAppointmentDateTime(String date, String time) { + setSelectedAppointmentDateTime(String date, String time, String displayDate) { selectedAppointmentDate = date; selectedAppointmentTime = time; + selectedAppointmentDisplayDate = displayDate; notifyListeners(); } @@ -581,7 +584,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { - onError?.call(LocaleKeys.noDoctorFound.tr()); + onError?.call(selectedClinic.clinicID == 23 ? failure.message : LocaleKeys.noDoctorFound.tr()); }, (apiResponse) async { if (apiResponse.messageStatus == 2) { @@ -721,7 +724,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { Future getLiveCareDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { docFreeSlots.clear(); DateTime date; - final DateFormat formatter = DateFormat('HH:mm'); + final DateFormat formatter = DateFormat('HH:mm', "en-US"); final DateFormat dateFormatter = DateFormat('yyyy-MM-dd'); Map _eventsParsed; @@ -806,6 +809,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { result.fold( (failure) async { print(failure); + onError!(failure.message); }, (apiResponse) { if (apiResponse.messageStatus == 2) { @@ -835,16 +839,11 @@ class BookAppointmentsViewModel extends ChangeNotifier { appointmentDate: apiResponse.data["SameClinicApptList"][0]['AppointmentDate'], ); - showCommonBottomSheet(navigationService.navigatorKey.currentContext!, - child: Utils.getLoadingWidget(loadingText: LocaleKeys.cancellingAppointmentPleaseWait.tr()), - callBackFunc: (str) {}, - title: "", - height: ResponsiveExtension.screenHeight * 0.3, - isCloseButtonVisible: false, - isDismissible: false, - isFullScreen: false); + LoaderBottomSheet.showLoader( + loadingText: LocaleKeys.reschedulingAppo.tr(context: navigationService.navigatorKey.currentContext!), + ); await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { - navigationService.pop(); + LoaderBottomSheet.hideLoader(); Future.delayed(Duration(milliseconds: 50)).then((value) async {}); LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: LocaleKeys.bookingYourAppointment.tr()); await insertSpecificAppointment( diff --git a/lib/features/contact_us/contact_us_repo.dart b/lib/features/contact_us/contact_us_repo.dart index 5b9057b..c758155 100644 --- a/lib/features/contact_us/contact_us_repo.dart +++ b/lib/features/contact_us/contact_us_repo.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/req_models/request_insert_coc_item.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_hmg_locations.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_patient_ic_projects.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_status_coc_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -17,6 +18,8 @@ abstract class ContactUsRepo { Future>> getChatRequestID({required String name, required String mobileNo, required String workGroup}); Future>> insertCOCItem({required RequestInsertCOCItem requestInsertCOCItem, PatientAppointmentHistoryResponseModel? patientSelectedAppointment}); + + Future>>> getStatusForCOC(String identificationNo, String mobileNo, {Function(dynamic)? onSuccess, Function(String)? onError}); } class ContactUsRepoImp implements ContactUsRepo { @@ -181,4 +184,44 @@ class ContactUsRepoImp implements ContactUsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getStatusForCOC(String identificationNo, String mobileNo, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + Map body = {}; + body['IdentificationNo'] = identificationNo; + body['MobileNo'] = mobileNo; + body['Searching_type'] = 1; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_STATUS_FOR_COCO, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListCOCItems']; + final cocItemsList = list.map((item) => COCItem.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: cocItemsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart index 1029802..72bcff7 100644 --- a/lib/features/contact_us/contact_us_view_model.dart +++ b/lib/features/contact_us/contact_us_view_model.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dar import 'package:hmg_patient_app_new/features/contact_us/models/req_models/request_insert_coc_item.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_hmg_locations.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_patient_ic_projects.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_status_coc_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -33,6 +34,9 @@ class ContactUsViewModel extends ChangeNotifier { List feedbackAttachmentList = []; + List cocItemsList = []; + bool isCOCItemsListLoading = false; + PatientAppointmentHistoryResponseModel? patientFeedbackSelectedAppointment; List feedbackTypeList = [ @@ -52,10 +56,12 @@ class ContactUsViewModel extends ChangeNotifier { isHMGLocationsListLoading = true; isHMGHospitalsListSelected = true; isLiveChatProjectsListLoading = true; + isCOCItemsListLoading = true; hmgHospitalsLocationsList.clear(); hmgPharmacyLocationsList.clear(); liveChatProjectsList.clear(); feedbackAttachmentList.clear(); + cocItemsList.clear(); selectedFeedbackType = FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'); setPatientFeedbackSelectedAppointment(null); getHMGLocations(); @@ -74,6 +80,9 @@ class ContactUsViewModel extends ChangeNotifier { setIsSendFeedbackTabSelected(bool isSelected) { isSendFeedbackTabSelected = isSelected; + if (!isSelected) { + getStatusForCOC(identificationNo: appState.getAuthenticatedUser()!.patientIdentificationNo!, mobileNo: "966${Utils.getPhoneNumberWithoutZero(appState.getAuthenticatedUser()!.mobileNumber!)}"); + } notifyListeners(); } @@ -223,4 +232,38 @@ class ContactUsViewModel extends ChangeNotifier { }, ); } + + Future getStatusForCOC({required String identificationNo, required String mobileNo, Function(dynamic)? onSuccess, Function(String)? onError}) async { + isCOCItemsListLoading = true; + cocItemsList.clear(); + notifyListeners(); + + final result = await contactUsRepo.getStatusForCOC(identificationNo, mobileNo); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + isCOCItemsListLoading = false; + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + if (onError != null) { + isCOCItemsListLoading = false; + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } else if (apiResponse.messageStatus == 1) { + cocItemsList = apiResponse.data!; + cocItemsList.sort((a, b) => a.date!.compareTo(b.date!)); + isCOCItemsListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/contact_us/models/resp_models/get_status_coc_response_model.dart b/lib/features/contact_us/models/resp_models/get_status_coc_response_model.dart new file mode 100644 index 0000000..ed62d8b --- /dev/null +++ b/lib/features/contact_us/models/resp_models/get_status_coc_response_model.dart @@ -0,0 +1,124 @@ +class COCItem { + dynamic appointment; + String? appointmentClinicName; + String? appointmentDate; + String? appointmentProjectName; + String? cOCID; + String? cOCTitle; + String? channel; + dynamic clinic; + String? clinicID; + String? date; + dynamic detail; + dynamic doctor; + String? doctorID; + String? formType; + int? formTypeID; + dynamic identificationNo; + int? itemID; + dynamic mobileNo; + dynamic naturename; + dynamic patientID; + dynamic patientName; + dynamic project; + dynamic projectID; + String? solution; + String? status; + String? statusAr; + dynamic statusEn; + int? statusId; + + COCItem({ + this.appointment, + this.appointmentClinicName, + this.appointmentDate, + this.appointmentProjectName, + this.cOCID, + this.cOCTitle, + this.channel, + this.clinic, + this.clinicID, + this.date, + this.detail, + this.doctor, + this.doctorID, + this.formType, + this.formTypeID, + this.identificationNo, + this.itemID, + this.mobileNo, + this.naturename, + this.patientID, + this.patientName, + this.project, + this.projectID, + this.solution, + this.status, + this.statusAr, + this.statusEn, + this.statusId, + }); + + COCItem.fromJson(Map json) { + appointment = json['Appointment']; + appointmentClinicName = json['AppointmentClinicName']; + appointmentDate = json['AppointmentDate']; + appointmentProjectName = json['AppointmentProjectName']; + cOCID = json['COCID']; + cOCTitle = json['COCTitle']; + channel = json['Channel']; + clinic = json['Clinic']; + clinicID = json['ClinicID']; + date = json['Date']; + detail = json['Detail']; + doctor = json['Doctor']; + doctorID = json['DoctorID']; + formType = json['FormType']; + formTypeID = json['FormTypeID']; + identificationNo = json['IdentificationNo']; + itemID = json['ItemID']; + mobileNo = json['MobileNo']; + naturename = json['Naturename']; + patientID = json['PatientID']; + patientName = json['PatientName']; + project = json['Project']; + projectID = json['ProjectID']; + solution = json['Solution']; + status = json['Status']; + statusAr = json['StatusAr']; + statusEn = json['StatusEn']; + statusId = json['StatusId']; + } + + Map toJson() { + final Map data = new Map(); + data['Appointment'] = this.appointment; + data['AppointmentClinicName'] = this.appointmentClinicName; + data['AppointmentDate'] = this.appointmentDate; + data['AppointmentProjectName'] = this.appointmentProjectName; + data['COCID'] = this.cOCID; + data['COCTitle'] = this.cOCTitle; + data['Channel'] = this.channel; + data['Clinic'] = this.clinic; + data['ClinicID'] = this.clinicID; + data['Date'] = this.date; + data['Detail'] = this.detail; + data['Doctor'] = this.doctor; + data['DoctorID'] = this.doctorID; + data['FormType'] = this.formType; + data['FormTypeID'] = this.formTypeID; + data['IdentificationNo'] = this.identificationNo; + data['ItemID'] = this.itemID; + data['MobileNo'] = this.mobileNo; + data['Naturename'] = this.naturename; + data['PatientID'] = this.patientID; + data['PatientName'] = this.patientName; + data['Project'] = this.project; + data['ProjectID'] = this.projectID; + data['Solution'] = this.solution; + data['Status'] = this.status; + data['StatusAr'] = this.statusAr; + data['StatusEn'] = this.statusEn; + return data; + } +} diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index daddbff..5bf450b 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart'; @@ -17,6 +18,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/search_e_referral_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -31,8 +33,7 @@ class HmgServicesViewModel extends ChangeNotifier { final ErrorHandlerService errorHandlerService; final NavigationService navigationService; - HmgServicesViewModel( - {required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService, required this.navigationService}); + HmgServicesViewModel({required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService, required this.navigationService}); bool isCmcOrdersLoading = false; bool isCmcServicesLoading = false; @@ -55,9 +56,11 @@ class HmgServicesViewModel extends ChangeNotifier { // Vital Sign PageView Controller PageController _vitalSignPageController = PageController(); + PageController get vitalSignPageController => _vitalSignPageController; int _vitalSignCurrentPage = 0; + int get vitalSignCurrentPage => _vitalSignCurrentPage; void setVitalSignCurrentPage(int page) { @@ -65,7 +68,6 @@ class HmgServicesViewModel extends ChangeNotifier { notifyListeners(); } - // HHC specific lists List hhcOrdersList = []; List hhcServicesList = []; @@ -82,9 +84,8 @@ class HmgServicesViewModel extends ChangeNotifier { List searchReferralList = []; List covidTestProcedureList = []; Covid19GetPaymentInfo? covidPaymentInfo; - Future getOrdersList() async {} - + Future getOrdersList() async {} // HHC multiple services selection List selectedHhcServices = []; @@ -792,22 +793,20 @@ class HmgServicesViewModel extends ChangeNotifier { }, ); } + List getQuestionsFromJson() { - final String questionsJson = '''[ { "id": 1, "questionEN": "Is the test intended for travel?", "questionAR": "هل تجري التحليل بغرض السفر؟", "ans": 2 }, { "id": 2, "questionEN": "Coming from outside KSA within last 2 weeks?", "questionAR": "هل قدمت من خارج المملكة خلال الأسبوعين الماضيين؟", "ans": 2 }, { "id": 3, "questionEN": "Do you currently have fever?", "questionAR": "هل تعاني حاليا من حرارة؟", "ans": 2 }, { "id": 4, "questionEN": "Did you have fever in last 2 weeks?", "questionAR": "هل عانيت من حرارة في الأسبوعين الماضيين؟", "ans": 2 }, { "id": 5, "questionEN": "Do you have a sore throat?", "questionAR": "هل لديك التهاب في الحلق؟", "ans": 2 }, { "id": 6, "questionEN": "Do you have a runny nose?", "questionAR": "هل لديك سيلان بالأنف؟" }, { "id": 7, "questionEN": "Do you have a cough?", "questionAR": "هل لديك سعال؟", "ans": 2 }, { "id": 8, "questionEN": "Do you have shortness of breath?", "questionAR": "هل تعاني من ضيق في التنفس؟", "ans": 2 }, { "id": 9, "questionEN": "Do you have nausea?", "questionAR": "هل تعاني من غثيان؟", "ans": 2 }, { "id": 10, "questionEN": "Do you have vomiting?", "questionAR": "هل تعاني من القيء؟", "ans": 2 }, { "id": 11, "questionEN": "Do you have a headache?", "questionAR": "هل تعاني من صداع في الرأس؟", "ans": 2 }, { "id": 12, "questionEN": "Do you have muscle pain?", "questionAR": "هل تعانين من آلام عضلية؟", "ans": 2 }, { "id": 13, "questionEN": "Do you have joint pain?", "questionAR": "هل تعاني من آلام المفاصل؟", "ans": 2 }, { "id": 14, "questionEN": "Do you have diarrhea?", "questionAR": "هل لديك اسهال؟", "ans": 2 } ]'''; + final String questionsJson = + '''[ { "id": 1, "questionEN": "Is the test intended for travel?", "questionAR": "هل تجري التحليل بغرض السفر؟", "ans": 2 }, { "id": 2, "questionEN": "Coming from outside KSA within last 2 weeks?", "questionAR": "هل قدمت من خارج المملكة خلال الأسبوعين الماضيين؟", "ans": 2 }, { "id": 3, "questionEN": "Do you currently have fever?", "questionAR": "هل تعاني حاليا من حرارة؟", "ans": 2 }, { "id": 4, "questionEN": "Did you have fever in last 2 weeks?", "questionAR": "هل عانيت من حرارة في الأسبوعين الماضيين؟", "ans": 2 }, { "id": 5, "questionEN": "Do you have a sore throat?", "questionAR": "هل لديك التهاب في الحلق؟", "ans": 2 }, { "id": 6, "questionEN": "Do you have a runny nose?", "questionAR": "هل لديك سيلان بالأنف؟" }, { "id": 7, "questionEN": "Do you have a cough?", "questionAR": "هل لديك سعال؟", "ans": 2 }, { "id": 8, "questionEN": "Do you have shortness of breath?", "questionAR": "هل تعاني من ضيق في التنفس؟", "ans": 2 }, { "id": 9, "questionEN": "Do you have nausea?", "questionAR": "هل تعاني من غثيان؟", "ans": 2 }, { "id": 10, "questionEN": "Do you have vomiting?", "questionAR": "هل تعاني من القيء؟", "ans": 2 }, { "id": 11, "questionEN": "Do you have a headache?", "questionAR": "هل تعاني من صداع في الرأس؟", "ans": 2 }, { "id": 12, "questionEN": "Do you have muscle pain?", "questionAR": "هل تعانين من آلام عضلية؟", "ans": 2 }, { "id": 13, "questionEN": "Do you have joint pain?", "questionAR": "هل تعاني من آلام المفاصل؟", "ans": 2 }, { "id": 14, "questionEN": "Do you have diarrhea?", "questionAR": "هل لديك اسهال؟", "ans": 2 } ]'''; try { final parsed = json.decode(questionsJson) as List; - return parsed - .map((e) => CovidQuestionnaireModel.fromJson(Map.from(e))) - .toList(); + return parsed.map((e) => CovidQuestionnaireModel.fromJson(Map.from(e))).toList(); } catch (_) { return []; } - } - + } Future getCovidProcedureList({ - Function(dynamic)? onSuccess, Function(String)? onError, }) async { @@ -816,14 +815,14 @@ class HmgServicesViewModel extends ChangeNotifier { final result = await hmgServicesRepo.getCovidTestProcedures(); result.fold( - (failure) async { + (failure) async { notifyListeners(); await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 1) { covidTestProcedureList = apiResponse.data ?? []; notifyListeners(); @@ -840,7 +839,6 @@ class HmgServicesViewModel extends ChangeNotifier { ); } - Future getPaymentInfo({ String? procedureID, int? projectID, @@ -852,14 +850,14 @@ class HmgServicesViewModel extends ChangeNotifier { final result = await hmgServicesRepo.getCovidPaymentInfo(procedureID!, projectID!); result.fold( - (failure) async { + (failure) async { notifyListeners(); await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 1) { covidPaymentInfo = apiResponse.data; notifyListeners(); @@ -900,6 +898,8 @@ class HmgServicesViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 1) { vitalSignList = apiResponse.data ?? []; hasVitalSignDataLoaded = true; + getIt.get().setSelectedHeight(vitalSignList.first.heightCm); + getIt.get().setSelectedWeight(vitalSignList.first.weightKg); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/hospital/hospital_selection_view_model.dart b/lib/features/hospital/hospital_selection_view_model.dart index 57bd360..24c7b9b 100644 --- a/lib/features/hospital/hospital_selection_view_model.dart +++ b/lib/features/hospital/hospital_selection_view_model.dart @@ -97,7 +97,7 @@ class HospitalSelectionBottomSheetViewModel extends ChangeNotifier { initPenguinSDK(hospital.iD, clinicID: clinicID); } - initPenguinSDK(int projectID, {String clinicID = "1"}) async { + initPenguinSDK(int projectID, {String clinicID = ""}) async { NavigationClinicDetails data = NavigationClinicDetails(); data.projectId = projectID.toString(); data.clinicId = clinicID; diff --git a/lib/features/immediate_livecare/immediate_livecare_view_model.dart b/lib/features/immediate_livecare/immediate_livecare_view_model.dart index ffa0b00..90bf9a0 100644 --- a/lib/features/immediate_livecare/immediate_livecare_view_model.dart +++ b/lib/features/immediate_livecare/immediate_livecare_view_model.dart @@ -1,16 +1,26 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; +import 'package:get_it/get_it.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/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_fees_response_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import '../../services/navigation_service.dart'; @@ -42,6 +52,9 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { late ValueNotifier durationNotifier; late Timer timer; + bool isShowNotEligibleDialog = false; + String notEligibleErrorMsg = ""; + initImmediateLiveCare() { _appState = getIt(); immediateLiveCareClinicsList = []; @@ -102,8 +115,80 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { liveCareImmediateAppointmentFeesList = apiResponse.data!; + + if ((liveCareImmediateAppointmentFeesList.isCash!)) { + isShowNotEligibleDialog = false; + } else { + if (liveCareImmediateAppointmentFeesList.isInsured! && liveCareImmediateAppointmentFeesList.isEligible!) { + isShowNotEligibleDialog = false; + } else { + if (liveCareImmediateAppointmentFeesList.isInsured! && liveCareImmediateAppointmentFeesList.isEligible!) { + isShowNotEligibleDialog = true; + notEligibleErrorMsg = LocaleKeys.invalidEligibility.tr(context: GetIt.instance().navigatorKey.currentContext!); + } else { + isShowNotEligibleDialog = true; + notEligibleErrorMsg = LocaleKeys.invalidInsurance.tr(context: GetIt.instance().navigatorKey.currentContext!); + } + } + } + + if (isShowNotEligibleDialog) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(GetIt.instance().navigatorKey.currentContext!, + child: Padding( + padding: EdgeInsets.only( + left: 24.w, + top: 24.h, + right: 24.w, + bottom: 12.h, + ), + child: Utils.getWarningWidget( + loadingText: notEligibleErrorMsg, + confirmText: LocaleKeys.updateInsuranceInfo.tr(context: GetIt.instance().navigatorKey.currentContext!), + cancelText: LocaleKeys.continueCash.tr(context: GetIt.instance().navigatorKey.currentContext!), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + LoaderBottomSheet.showLoader(); + myAppointmentsViewModel.convertPatientToCash( + projectID: ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingFeesPleaseWait.tr()); + getLiveCareImmediateAppointmentFees(); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: GetIt.instance().navigatorKey.currentContext!), + GetIt.instance().navigatorKey.currentContext!, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () { + Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + onConfirmTap: () async { + Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + Navigator.push( + GetIt.instance().navigatorKey.currentContext!, + CustomPageRoute( + page: InsuranceHomePage(), + ), + ); + }), + ), callBackFunc: () { + // Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + }, isFullScreen: false, isCloseButtonVisible: false, isDismissible: false); + } + notifyListeners(); - if (onSuccess != null) { + if (onSuccess != null && !isShowNotEligibleDialog) { onSuccess(apiResponse); } } diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart index 261bda2..fa1ec5d 100644 --- a/lib/features/insurance/insurance_view_model.dart +++ b/lib/features/insurance/insurance_view_model.dart @@ -18,6 +18,7 @@ class InsuranceViewModel extends ChangeNotifier { bool isInsuranceApprovalsLoading = false; bool isInsuranceExpired = false; + bool isInsuranceActive = false; InsuranceRepo insuranceRepo; ErrorHandlerService errorHandlerService; @@ -79,6 +80,8 @@ class InsuranceViewModel extends ChangeNotifier { (failure) async { isInsuranceLoading = false; isInsuranceDataToBeLoaded = false; + isInsuranceExpired = false; + isInsuranceActive = false; notifyListeners(); }, (apiResponse) { @@ -94,6 +97,9 @@ class InsuranceViewModel extends ChangeNotifier { DateUtil.convertStringToDate(patientInsuranceList.first.cardValidTo), ); + isInsuranceActive = patientInsuranceList.first.isActive ?? false; + // isInsuranceActive = true; + notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart index e661157..a4cdcf1 100644 --- a/lib/features/lab/lab_repo.dart +++ b/lib/features/lab/lab_repo.dart @@ -5,6 +5,8 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_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/features/lab/models/resp_models/patient_lab_special_result.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -13,6 +15,7 @@ import 'models/resp_models/lab_result.dart' show LabResult; abstract class LabRepo { Future>>> getPatientLabOrders(); + Future>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName); Future>>> getPatientLabResultsByHospitals(PatientLabOrdersResponseModel laborder, bool isVidaPlus); @@ -22,6 +25,10 @@ abstract class LabRepo { Future>> getLabResultReportPDF({required PatientLabOrdersResponseModel labOrder}); Future>> getLabResultsByAppointmentNo({required num appointmentNo, required num projectID, required num clinicID}); + + Future>> getPatientAiOverViewLabOrders(var payload); + + Future>> getPatientAiOverViewLabOrder(var payload); } class LabRepoImp implements LabRepo { @@ -71,10 +78,7 @@ class LabRepoImp implements LabRepo { } @override - Future>>> getPatientLabResults( - PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName - ) async { - + Future>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName) async { Map request = Map(); request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0"; request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo; @@ -100,10 +104,7 @@ class LabRepoImp implements LabRepo { throw Exception("lab list is empty"); } - final labOrders = list - .map((item) => LabResult.fromJson(item as Map)) - .toList() - .cast(); + final labOrders = list.map((item) => LabResult.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, @@ -125,9 +126,7 @@ class LabRepoImp implements LabRepo { } @override - Future>>> - getPatientLabResultsByHospitals( - PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { + Future>>> getPatientLabResultsByHospitals(PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { Map request = Map(); request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0"; request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo; @@ -175,9 +174,7 @@ class LabRepoImp implements LabRepo { } @override - Future>>> - getSpecialLabResult( - PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { + Future>>> getSpecialLabResult(PatientLabOrdersResponseModel laborder, bool isVidaPlus) async { Map request = Map(); request['InvoiceNo_VP'] = isVidaPlus ? laborder!.invoiceNo : "0"; request['InvoiceNo'] = isVidaPlus ? "0" : laborder!.invoiceNo; @@ -203,11 +200,7 @@ class LabRepoImp implements LabRepo { throw Exception("lab list is empty"); } - final labOrders = list - .map((item) => PatientLabSpecialResult.fromJson( - item as Map)) - .toList() - .cast(); + final labOrders = list.map((item) => PatientLabSpecialResult.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, @@ -318,4 +311,76 @@ class LabRepoImp implements LabRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getPatientAiOverViewLabOrder(var labOrder) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getAiOverViewLabOrder, + body: labOrder, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + var res; + if (response["analysis_result"] != null) { + res = response["analysis_result"]; + } + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: LabOrderResponseByAi.fromJson(res), + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> getPatientAiOverViewLabOrders(var labOrder) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getAiOverViewLabOrders, + body: {"lab_results": labOrder}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + var res; + if (response["lab_analysis_result"] != null) { + res = response["lab_analysis_result"]; + } + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: LabOrdersResponseByAi.fromJson(res), + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 1047dbc..efb1c06 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -1,16 +1,24 @@ import 'dart:collection'; +import 'dart:convert'; import 'dart:core'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/common_models/data_points.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/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; +import 'package:hmg_patient_app_new/features/lab/models/ai_overview_labresult_model_payload.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_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/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_details.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/lab_ai_analysis_detailed_page.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -53,20 +61,7 @@ class LabViewModel extends ChangeNotifier { List mainLabResults = []; List mainGraphPoints = []; List filteredGraphValues = []; - List months = [ - 'Jan', - 'Feb', - 'Mar', - 'April', - 'May', - 'Jun', - 'July', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec' - ]; + List months = ['Jan', 'Feb', 'Mar', 'April', 'May', 'Jun', 'July', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; late List _labSuggestionsList = []; @@ -86,22 +81,22 @@ class LabViewModel extends ChangeNotifier { List> patientLabOrdersByHospital = []; List> patientLabOrdersViewList = []; - LabViewModel( - {required this.labRepo, - required this.errorHandlerService, - required this.navigationService}); + LabOrderResponseByAi? labOrderResponseByAi; + LabOrdersResponseByAi? labOrdersResponseByAi; + + LabViewModel({required this.labRepo, required this.errorHandlerService, required this.navigationService}); initLabProvider() { // if (isLabNeedToLoad) { - patientLabOrders.clear(); - filteredLabOrders.clear(); - labOrderTests.clear(); - isLabOrdersLoading = true; - isLabResultsLoading = true; - patientLabOrdersByClinic.clear(); - patientLabOrdersByHospital.clear(); - patientLabOrdersViewList.clear(); - getPatientLabOrders(); + patientLabOrders.clear(); + filteredLabOrders.clear(); + labOrderTests.clear(); + isLabOrdersLoading = true; + isLabResultsLoading = true; + patientLabOrdersByClinic.clear(); + patientLabOrdersByHospital.clear(); + patientLabOrdersViewList.clear(); + getPatientLabOrders(); // } notifyListeners(); } @@ -133,13 +128,13 @@ class LabViewModel extends ChangeNotifier { final result = await labRepo.getPatientLabOrders(); result.fold( - (failure) async { - isLabOrdersLoading = false; - isLabResultsLoading = false; - notifyListeners(); - }, - // => await errorHandler_service.handleError(failure: failure), - (apiResponse) { + (failure) async { + isLabOrdersLoading = false; + isLabResultsLoading = false; + notifyListeners(); + }, + // => await errorHandler_service.handleError(failure: failure), + (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { @@ -203,11 +198,7 @@ class LabViewModel extends ChangeNotifier { } filterSuggestions() { - final List labels = patientLabOrders - .expand((order) => order.testDetails ?? []) - .map((detail) => detail.description) - .whereType() - .toList(); + final List labels = patientLabOrders.expand((order) => order.testDetails ?? []).map((detail) => detail.description).whereType().toList(); _labSuggestionsList = labels.toSet().toList(); notifyListeners(); @@ -322,14 +313,12 @@ class LabViewModel extends ChangeNotifier { ); } - Future getPatientLabResultByHospital( - PatientLabOrdersResponseModel laborder) async { + Future getPatientLabResultByHospital(PatientLabOrdersResponseModel laborder) async { isLabResultByHospitalLoading = true; notifyListeners(); mainLabResultsByHospitals.clear(); - final result = await labRepo.getPatientLabResultsByHospitals(laborder, - Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0"))); + final result = await labRepo.getPatientLabResultsByHospitals(laborder, Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0"))); result.fold( (failure) async { @@ -353,12 +342,9 @@ class LabViewModel extends ChangeNotifier { mainLabResults.clear(); filteredGraphValues.clear(); maxY = double.negativeInfinity; - minY = double.infinity; - maxX = double.infinity; - final result = await labRepo.getPatientLabResults( - laborder, - Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")), - procedureName); + minY = double.infinity; + maxX = double.infinity; + final result = await labRepo.getPatientLabResults(laborder, Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")), procedureName); result.fold( (failure) async { @@ -372,7 +358,6 @@ class LabViewModel extends ChangeNotifier { var recentThree = sort(apiResponse.data!); mainLabResults = recentThree; - double highRefrenceValue = double.negativeInfinity; String? flagForHighReferenceRange; double lowRefenceValue = double.infinity; @@ -380,11 +365,10 @@ class LabViewModel extends ChangeNotifier { recentThree.reversed.forEach((element) { try { - var dateTime = - DateUtil.convertStringToDate(element.verifiedOnDateTime!); + var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!); var resultValue = double.parse(element.resultValue!); - var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""); - if (resultValue>maxY) { + var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag ?? ""); + if (resultValue > maxY) { maxY = resultValue; maxX = maxY; } @@ -401,14 +385,13 @@ class LabViewModel extends ChangeNotifier { } filteredGraphValues.add(DataPoint( - value: transformedValue, - actualValue:element.resultValue!, - label: formatDateAsMMYY(dateTime), - unitOfMeasurement: element.uOM, - displayTime: resultDate(dateTime), - time: DateUtil.convertStringToDate(element.verifiedOnDateTime), - referenceValue: element.calculatedResultFlag ?? "", - + value: transformedValue, + actualValue: element.resultValue!, + label: formatDateAsMMYY(dateTime), + unitOfMeasurement: element.uOM, + displayTime: resultDate(dateTime), + time: DateUtil.convertStringToDate(element.verifiedOnDateTime), + referenceValue: element.calculatedResultFlag ?? "", )); } catch (e) {} }); @@ -417,6 +400,10 @@ class LabViewModel extends ChangeNotifier { lowRefenceValue = minY; } + if (minY > lowRefenceValue) { + minY = lowRefenceValue - 25; + } + this.flagForHighReferenceRange = flagForHighReferenceRange; this.flagForLowReferenceRange = flagForLowReferenceRange; highTransformedReferenceValue = double.parse(transformValueInRange(highRefrenceValue, flagForHighReferenceRange ?? "").toStringAsFixed(1)); @@ -424,7 +411,7 @@ class LabViewModel extends ChangeNotifier { this.highRefrenceValue = double.parse(highRefrenceValue.toStringAsFixed(1)); this.lowRefenceValue = double.parse(lowRefenceValue.toStringAsFixed(1)); - if(maxY< highRefrenceValue) { + if (maxY < highRefrenceValue) { maxY = highRefrenceValue; } maxY += 25; @@ -434,7 +421,9 @@ class LabViewModel extends ChangeNotifier { checkIfGraphShouldBeDisplayed(recentResult); recentResult.verifiedOn = resultDate(DateUtil.convertStringToDate(recentResult.verifiedOnDateTime!)); // filteredGraphValues = [filteredGraphValues.first]; - navigationService.push(MaterialPageRoute( + labOrderResponseByAi = null; + navigationService.push( + MaterialPageRoute( builder: (_) => LabResultDetails(recentLabResult: recentResult, testDescription: testDescription), ), ); @@ -444,14 +433,13 @@ class LabViewModel extends ChangeNotifier { ); } - void checkIfGraphShouldBeDisplayed(LabResult recentResult){ + void checkIfGraphShouldBeDisplayed(LabResult recentResult) { shouldShowGraph = recentResult.checkIfGraphShouldBeDisplayed(); isGraphVisible = shouldShowGraph; notifyListeners(); } - Future getPatientSpecialResult( - PatientLabOrdersResponseModel laborder) async { + Future getPatientSpecialResult(PatientLabOrdersResponseModel laborder) async { isSpecialResultsLoading = true; labSpecialResult = ""; notifyListeners(); @@ -473,8 +461,7 @@ class LabViewModel extends ChangeNotifier { StringBuffer htmlbuffer = StringBuffer(""); apiResponse.data?.forEach((element) { - if(element.resultDataHTML != null && element.resultDataHTML?.isNotEmpty == true) - htmlbuffer.write("${element.resultDataHTML}

"); + if (element.resultDataHTML != null && element.resultDataHTML?.isNotEmpty == true) htmlbuffer.write("${element.resultDataHTML}

"); }); labSpecialResult = htmlbuffer.toString(); @@ -486,10 +473,8 @@ class LabViewModel extends ChangeNotifier { ); } - String resultDate(DateTime date){ - - - return '${date.day} ${months[date.month-1]},${date.year}'; + String resultDate(DateTime date) { + return '${date.day} ${months[date.month - 1]},${date.year}'; } double transformValueInRange(double inputValue, String flag) { @@ -497,7 +482,7 @@ class LabViewModel extends ChangeNotifier { double rangeStart, rangeEnd; switch (flag) { - case'LCL': + case 'LCL': case 'CL': rangeStart = 0.0; rangeEnd = 19.0; @@ -534,16 +519,17 @@ class LabViewModel extends ChangeNotifier { debugPrint("the transformed value is $transformedValue"); return transformedValue; } + void getSelectedDateRange(DateTime? start, DateTime? end) { maxY = double.negativeInfinity; - if(start == null && end == null) { + if (start == null && end == null) { mainLabResults.forEach((element) { final time = DateUtil.convertStringToDate(element.verifiedOnDateTime!); - try{ + try { var resultValue = double.parse(element.resultValue!); - var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""); + var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag ?? ""); if (resultValue > maxY) { maxY = resultValue; } @@ -552,54 +538,43 @@ class LabViewModel extends ChangeNotifier { actualValue: element.resultValue!, label: formatDateAsMMYY(time), unitOfMeasurement: element.uOM, - displayTime: resultDate(time), time: DateUtil.convertStringToDate(element.verifiedOnDateTime), referenceValue: element.calculatedResultFlag ?? "", )); - }catch(e){ - - } + } catch (e) {} }); - - }else { + } else { filteredGraphValues.clear(); mainLabResults.forEach((element) { try { - var dateTime = - DateUtil.convertStringToDate(element.verifiedOnDateTime!); + var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!); var resultValue = double.parse(element.resultValue!); - var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""); + var transformedValue = transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag ?? ""); if (resultValue > maxY) { maxY = resultValue; } if (start != null && end == null) { if (dateTime.isAtSameMomentAs(start)) { - filteredGraphValues.add(DataPoint( value: transformedValue, actualValue: element.resultValue!, unitOfMeasurement: element.uOM, - label: formatDateAsMMYY(dateTime), displayTime: resultDate(dateTime), - time: - DateUtil.convertStringToDate(element.verifiedOnDateTime), + time: DateUtil.convertStringToDate(element.verifiedOnDateTime), referenceValue: element.calculatedResultFlag ?? "")); } } else if (start != null && end != null) { if ((dateTime.isAfter(start)) && (dateTime.isBefore(end))) { - filteredGraphValues.add(DataPoint( value: transformedValue, actualValue: element.resultValue!, label: formatDateAsMMYY(dateTime), unitOfMeasurement: element.uOM, - displayTime: resultDate(dateTime), - time: - DateUtil.convertStringToDate(element.verifiedOnDateTime), + time: DateUtil.convertStringToDate(element.verifiedOnDateTime), referenceValue: element.calculatedResultFlag ?? "")); } } @@ -608,16 +583,13 @@ class LabViewModel extends ChangeNotifier { } filteredGraphValues = sortFilteredList(filteredGraphValues).reversed.toList(); - notifyListeners(); } String formatDateAsMMYY(DateTime date) { - - return '${months[date.month-1]}, ${date.year}'; + return '${months[date.month - 1]}, ${date.year}'; } - List sortByFlagAndValue(List original) { const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH']; @@ -651,7 +623,6 @@ class LabViewModel extends ChangeNotifier { } List sort(List original) { - final copy = List.from(original); copy.sort((a, b) { final aDate = DateUtil.convertStringToDate(a.verifiedOnDateTime); @@ -666,8 +637,6 @@ class LabViewModel extends ChangeNotifier { } List sortFilteredList(List original) { - - final copy = List.from(original); copy.sort((a, b) { final aDate = a.time; @@ -702,7 +671,7 @@ class LabViewModel extends ChangeNotifier { } } - String getFormattedDate(DateTime date){ + String getFormattedDate(DateTime date) { return DateFormat('EEEE, dd MMMM. yyyy').format(date); } @@ -716,7 +685,7 @@ class LabViewModel extends ChangeNotifier { case 'N': return AppAssets.ic_normal_result; case 'H': - return AppAssets.ic_low_result; + return AppAssets.ic_low_result; case 'CH': case 'HCH': return AppAssets.ic_critical_low_result; @@ -758,8 +727,81 @@ class LabViewModel extends ChangeNotifier { } } - alterGraphVisibility(){ + alterGraphVisibility() { isGraphVisible = !isGraphVisible; notifyListeners(); } + + Future getAiOverviewLabOrders({required PatientLabOrdersResponseModel labOrder, required String loadingText}) async { + // LoadingUtils.showFullScreenLoader(loadingText: "Loading and analysing your data,\nPlease be patient and let the AI do the magic. \nPlease be patient, This might take some time."); + LoadingUtils.showFullScreenLoader(loadingText: loadingText); + 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!) { + LabResult? matchingResult = + mainLabResultsByHospitals.firstWhere((labResult) => labResult.testCode == testDetail.testCode || labResult.description == testDetail.description, orElse: () => LabResult()); + testResults.add({ + "test_name": testDetail.description ?? "", + "result_value": matchingResult.resultValue ?? "", + "reference_range": matchingResult.referanceRange ?? "", + "unit": matchingResult.uOM ?? "", + "flag": matchingResult.calculatedResultFlag ?? "N" + }); + } + } + orderData["results"] = testResults; + results.add(orderData); + final result = await labRepo.getPatientAiOverViewLabOrders(results); + result.fold( + (failure) async { + LoadingUtils.hideFullScreenLoader(); + await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + LoadingUtils.hideFullScreenLoader(); + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + labOrdersResponseByAi = apiResponse.data; + navigationService.push( + MaterialPageRoute(builder: (_) => LabAiAnalysisDetailedPage()), + ); + } + }, + ); + + LoadingUtils.hideFullScreenLoader(); + } + + Future getAiOverviewSingleLabResult({required String langId, required LabResult recentLabResult, required String loadingText}) async { + LoaderBottomSheet.showLoader(loadingText: loadingText); + List> results = []; + results.add({ + "Description": recentLabResult.description ?? '', + "ResultValue": recentLabResult.resultValue ?? '', + "CalculatedResultFlag": recentLabResult.calculatedResultFlag ?? '', + "UOM": recentLabResult.uOM ?? '', + "ReferanceRange": recentLabResult.referanceRange ?? '', + }); + + var payload = {"patient_id": currentlySelectedPatientOrder!.patientID, "language_id": langId, "lab_results": results}; + final result = await labRepo.getPatientAiOverViewLabOrder(payload); + result.fold( + (failure) async { + LoaderBottomSheet.hideLoader(); + await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + LoaderBottomSheet.hideLoader(); + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + labOrderResponseByAi = apiResponse.data; + notifyListeners(); + } + }, + ); + + LoaderBottomSheet.hideLoader(); + } } diff --git a/lib/features/lab/models/ai_overview_labresult_model_payload.dart b/lib/features/lab/models/ai_overview_labresult_model_payload.dart new file mode 100644 index 0000000..c876592 --- /dev/null +++ b/lib/features/lab/models/ai_overview_labresult_model_payload.dart @@ -0,0 +1,69 @@ +import 'dart:convert'; + +class AiLabOrdersOverView { + String? orderDate; + String? clinic; + String? doctor; + List? results; + + AiLabOrdersOverView({ + this.orderDate, + this.clinic, + this.doctor, + this.results, + }); + + factory AiLabOrdersOverView.fromRawJson(String str) => AiLabOrdersOverView.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiLabOrdersOverView.fromJson(Map json) => AiLabOrdersOverView( + orderDate: json["order_date"] == null ? null : json["order_date"], + clinic: json["clinic"], + doctor: json["doctor"], + results: json["results"] == null ? [] : List.from(json["results"]!.map((x) => Result.fromJson(x))), + ); + + Map toJson() => { + "order_date": orderDate, + "clinic": clinic, + "doctor": doctor, + "results": results == null ? [] : List.from(results!.map((x) => x.toJson())), + }; +} + +class Result { + String? testName; + String? resultValue; + String? referenceRange; + String? unit; + String? flag; + + Result({ + this.testName, + this.resultValue, + this.referenceRange, + this.unit, + this.flag, + }); + + factory Result.fromRawJson(String str) => Result.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Result.fromJson(Map json) => Result( + testName: json["test_name"], + resultValue: json["result_value"], + referenceRange: json["reference_range"], + unit: json["unit"], + flag: json["flag"], + ); + + Map toJson() => { + "test_name": testName, + "result_value": resultValue, + "reference_range": referenceRange, + "unit": unit, + "flag": flag, + }; +} diff --git a/lib/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart b/lib/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart new file mode 100644 index 0000000..2353cf8 --- /dev/null +++ b/lib/features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart @@ -0,0 +1,177 @@ +import 'dart:convert'; + +class LabOrdersResponseByAi { + Analysis? analysis; + String? patientId; + int? totalOrders; + int? totalResults; + + LabOrdersResponseByAi({ + this.analysis, + this.patientId, + this.totalOrders, + this.totalResults, + }); + + factory LabOrdersResponseByAi.fromRawJson(String str) => LabOrdersResponseByAi.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory LabOrdersResponseByAi.fromJson(Map json) => LabOrdersResponseByAi( + analysis: json["analysis"] == null ? null : Analysis.fromJson(json["analysis"]), + patientId: json["patient_id"], + totalOrders: json["total_orders"], + totalResults: json["total_results"], + ); + + Map toJson() => { + "analysis": analysis?.toJson(), + "patient_id": patientId, + "total_orders": totalOrders, + "total_results": totalResults, + }; +} + +class Analysis { + List? aiGeneratedAttributes; + List? aiPredictions; + List? aiRecommendations; + List? prevention; + String? rawMessage; + List? riskFactors; + List? suggestedDoctors; + String? summary; + + Analysis({ + this.aiGeneratedAttributes, + this.aiPredictions, + this.aiRecommendations, + this.prevention, + this.rawMessage, + this.riskFactors, + this.suggestedDoctors, + this.summary, + }); + + factory Analysis.fromRawJson(String str) => Analysis.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Analysis.fromJson(Map json) => Analysis( + aiGeneratedAttributes: json["ai_generated_attributes"] == null ? [] : List.from(json["ai_generated_attributes"]!.map((x) => AiGeneratedAttribute.fromJson(x))), + aiPredictions: json["ai_predictions"] == null ? [] : List.from(json["ai_predictions"]!.map((x) => x)), + aiRecommendations: json["ai_recommendations"] == null ? [] : List.from(json["ai_recommendations"]!.map((x) => AiRecommendation.fromJson(x))), + prevention: json["prevention"] == null ? [] : List.from(json["prevention"]!.map((x) => x)), + rawMessage: json["raw_message"], + riskFactors: json["riskFactors"] == null ? [] : List.from(json["riskFactors"]!.map((x) => RiskFactor.fromJson(x))), + suggestedDoctors: json["suggested_doctors"] == null ? [] : List.from(json["suggested_doctors"]!.map((x) => x)), + summary: json["summary"], + ); + + Map toJson() => { + "ai_generated_attributes": aiGeneratedAttributes == null ? [] : List.from(aiGeneratedAttributes!.map((x) => x.toJson())), + "ai_predictions": aiPredictions == null ? [] : List.from(aiPredictions!.map((x) => x)), + "ai_recommendations": aiRecommendations == null ? [] : List.from(aiRecommendations!.map((x) => x.toJson())), + "prevention": prevention == null ? [] : List.from(prevention!.map((x) => x)), + "raw_message": rawMessage, + "riskFactors": riskFactors == null ? [] : List.from(riskFactors!.map((x) => x.toJson())), + "suggested_doctors": suggestedDoctors == null ? [] : List.from(suggestedDoctors!.map((x) => x)), + "summary": summary, + }; +} + +class AiGeneratedAttribute { + String? calculatedResultFlag; + String? description; + String? referanceRange; + String? resultValue; + String? testShortDescription; + String? uom; + String? calculationFormula; + + AiGeneratedAttribute({ + this.calculatedResultFlag, + this.description, + this.referanceRange, + this.resultValue, + this.testShortDescription, + this.uom, + this.calculationFormula, + }); + + factory AiGeneratedAttribute.fromRawJson(String str) => AiGeneratedAttribute.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiGeneratedAttribute.fromJson(Map json) => AiGeneratedAttribute( + calculatedResultFlag: json["CalculatedResultFlag"], + description: json["Description"], + referanceRange: json["ReferanceRange"], + resultValue: json["ResultValue"], + testShortDescription: json["TestShortDescription"], + uom: json["UOM"], + calculationFormula: json["calculationFormula"], + ); + + Map toJson() => { + "CalculatedResultFlag": calculatedResultFlag, + "Description": description, + "ReferanceRange": referanceRange, + "ResultValue": resultValue, + "TestShortDescription": testShortDescription, + "UOM": uom, + "calculationFormula": calculationFormula, + }; +} + +class AiRecommendation { + String? priority; + String? recommendation; + + AiRecommendation({ + this.priority, + this.recommendation, + }); + + factory AiRecommendation.fromRawJson(String str) => AiRecommendation.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiRecommendation.fromJson(Map json) => AiRecommendation( + priority: json["priority"], + recommendation: json["recommendation"], + ); + + Map toJson() => { + "priority": priority, + "recommendation": recommendation, + }; +} + +class RiskFactor { + String? description; + String? factor; + String? risk; + + RiskFactor({ + this.description, + this.factor, + this.risk, + }); + + factory RiskFactor.fromRawJson(String str) => RiskFactor.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory RiskFactor.fromJson(Map json) => RiskFactor( + description: json["description"], + factor: json["factor"], + risk: json["risk"], + ); + + Map toJson() => { + "description": description, + "factor": factor, + "risk": risk, + }; +} diff --git a/lib/features/lab/models/resp_models/lab_result.dart b/lib/features/lab/models/resp_models/lab_result.dart index a075be3..2314684 100644 --- a/lib/features/lab/models/resp_models/lab_result.dart +++ b/lib/features/lab/models/resp_models/lab_result.dart @@ -26,6 +26,7 @@ class LabResult { String? referenceLow; num? resultTypeID; String? packageShortDescription; + int? testId; LabResult( {this.description, @@ -52,6 +53,7 @@ class LabResult { this.referenceHigh, this.criticalLow, this.referenceLow, + this.testId, }); LabResult.fromJson(Map json) { @@ -82,6 +84,7 @@ class LabResult { referenceLow = json['ReferenceLow']; packageShortDescription = json['PackageShortDescription']; resultTypeID = json['ResultTypeID']; + testId = json['TestID']; } Map toJson() { @@ -110,6 +113,7 @@ class LabResult { data['ReferenceHigh'] = this.referenceHigh; data['CriticalLow'] = this.criticalLow; data['ReferenceLow'] = this.referenceLow; + data['TestID'] = this.testId; return data; } diff --git a/lib/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart b/lib/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart new file mode 100644 index 0000000..7ad3d54 --- /dev/null +++ b/lib/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart @@ -0,0 +1,145 @@ +import 'dart:convert'; + +class LabOrderResponseByAi { + String? summary; + List? aiGeneratedAttributes; + List? aiPredictions; + List? aiRecommendations; + List? riskFactors; + List? prevention; + List? suggestedDoctors; + String? rawMessage; + + LabOrderResponseByAi({ + this.summary, + this.aiGeneratedAttributes, + this.aiPredictions, + this.aiRecommendations, + this.riskFactors, + this.prevention, + this.suggestedDoctors, + this.rawMessage, + }); + + factory LabOrderResponseByAi.fromRawJson(String str) => LabOrderResponseByAi.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory LabOrderResponseByAi.fromJson(Map json) => LabOrderResponseByAi( + summary: json["summary"], + aiGeneratedAttributes: json["ai_generated_attributes"] == null ? [] : List.from(json["ai_generated_attributes"]!.map((x) => AiGeneratedAttribute.fromJson(x))), + aiPredictions: json["ai_predictions"] == null ? [] : List.from(json["ai_predictions"]!.map((x) => x)), + aiRecommendations: json["ai_recommendations"] == null ? [] : List.from(json["ai_recommendations"]!.map((x) => AiRecommendation.fromJson(x))), + riskFactors: json["riskFactors"] == null ? [] : List.from(json["riskFactors"]!.map((x) => RiskFactor.fromJson(x))), + prevention: json["prevention"] == null ? [] : List.from(json["prevention"]!.map((x) => x)), + suggestedDoctors: json["suggested_doctors"] == null ? [] : List.from(json["suggested_doctors"]!.map((x) => x)), + rawMessage: json["raw_message"], + ); + + Map toJson() => { + "summary": summary, + "ai_generated_attributes": aiGeneratedAttributes == null ? [] : List.from(aiGeneratedAttributes!.map((x) => x.toJson())), + "ai_predictions": aiPredictions == null ? [] : List.from(aiPredictions!.map((x) => x)), + "ai_recommendations": aiRecommendations == null ? [] : List.from(aiRecommendations!.map((x) => x.toJson())), + "riskFactors": riskFactors == null ? [] : List.from(riskFactors!.map((x) => x.toJson())), + "prevention": prevention == null ? [] : List.from(prevention!.map((x) => x)), + "suggested_doctors": suggestedDoctors == null ? [] : List.from(suggestedDoctors!.map((x) => x)), + "raw_message": rawMessage, + }; +} + +class AiGeneratedAttribute { + String? description; + String? resultValue; + String? referanceRange; + String? calculatedResultFlag; + String? uom; + String? calculationFormula; + String? testShortDescription; + + AiGeneratedAttribute({ + this.description, + this.resultValue, + this.referanceRange, + this.calculatedResultFlag, + this.uom, + this.calculationFormula, + this.testShortDescription, + }); + + factory AiGeneratedAttribute.fromRawJson(String str) => AiGeneratedAttribute.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiGeneratedAttribute.fromJson(Map json) => AiGeneratedAttribute( + description: json["Description"], + resultValue: json["ResultValue"], + referanceRange: json["ReferanceRange"], + calculatedResultFlag: json["CalculatedResultFlag"], + uom: json["UOM"], + calculationFormula: json["calculationFormula"], + testShortDescription: json["TestShortDescription"], + ); + + Map toJson() => { + "Description": description, + "ResultValue": resultValue, + "ReferanceRange": referanceRange, + "CalculatedResultFlag": calculatedResultFlag, + "UOM": uom, + "calculationFormula": calculationFormula, + "TestShortDescription": testShortDescription, + }; +} + +class AiRecommendation { + String? recommendation; + String? priority; + + AiRecommendation({ + this.recommendation, + this.priority, + }); + + factory AiRecommendation.fromRawJson(String str) => AiRecommendation.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiRecommendation.fromJson(Map json) => AiRecommendation( + recommendation: json["recommendation"], + priority: json["priority"], + ); + + Map toJson() => { + "recommendation": recommendation, + "priority": priority, + }; +} + +class RiskFactor { + String? factor; + String? risk; + String? description; + + RiskFactor({ + this.factor, + this.risk, + this.description, + }); + + factory RiskFactor.fromRawJson(String str) => RiskFactor.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory RiskFactor.fromJson(Map json) => RiskFactor( + factor: json["factor"], + risk: json["risk"], + description: json["description"], + ); + + Map toJson() => { + "factor": factor, + "risk": risk, + "description": description, + }; +} diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index b26a87a..9e50075 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -439,11 +439,10 @@ class MedicalFileViewModel extends ChangeNotifier { ); // Clear and start fresh with current user - patientFamilyFiles.clear(); - patientFamilyFiles.add(currentUserFamilyFile); + final List newPatientFamilyFiles = [currentUserFamilyFile]; final List activeFamilyFiles = []; - final List pendingFamilyFiles = []; + final List newPendingFamilyFiles = []; for (var element in apiResponse.data!) { if (element.status == null) { @@ -484,7 +483,7 @@ class MedicalFileViewModel extends ChangeNotifier { if (isPending) { familyFile.isRequestFromMySide = true; - pendingFamilyFiles.add(familyFile); + newPendingFamilyFiles.add(familyFile); } if (isActive) { activeFamilyFiles.add(familyFile); @@ -492,13 +491,14 @@ class MedicalFileViewModel extends ChangeNotifier { } for (var activeFile in activeFamilyFiles) { - if (!patientFamilyFiles.any((e) => e.responseId == activeFile.responseId)) { - patientFamilyFiles.add(activeFile); + if (!newPatientFamilyFiles.any((e) => e.responseId == activeFile.responseId)) { + newPatientFamilyFiles.add(activeFile); } } - this.pendingFamilyFiles.clear(); - this.pendingFamilyFiles.addAll(pendingFamilyFiles); + // Assign new list instances + patientFamilyFiles = newPatientFamilyFiles; + pendingFamilyFiles = newPendingFamilyFiles; } notifyListeners(); @@ -553,7 +553,9 @@ class MedicalFileViewModel extends ChangeNotifier { } } // pendingFamilyFiles.addAll(tempPendingFamilyFiles.where((element) => !pendingFamilyFiles.any((e) => e.responseId == element.responseId))); - pendingFamilyFiles.addAll(tempPendingFamilyFiles.where((element) => !pendingFamilyFiles.any((e) => e.patientId == element.patientId))); + final existingIds = pendingFamilyFiles.map((e) => e.patientId).toSet(); + final newItems = tempPendingFamilyFiles.where((element) => !existingIds.contains(element.patientId)).toList(); + pendingFamilyFiles = [...pendingFamilyFiles, ...newItems]; } notifyListeners(); } @@ -589,7 +591,7 @@ class MedicalFileViewModel extends ChangeNotifier { onUnHandledFailure: (failure) { LoaderBottomSheet.hideLoader(); _dialogService.showErrorBottomSheet( - message: failure.message!, + message: failure.message, onOkPressed: () { navigationService.pop(); }); @@ -663,7 +665,7 @@ class MedicalFileViewModel extends ChangeNotifier { navigationService.pop(); }); } else if (apiResponse.messageStatus == 1) { - patientFamilyFiles.removeWhere((element) => element.id == id); + patientFamilyFiles = patientFamilyFiles.where((element) => element.id != id).toList(); getFamilyFiles(); getAllPendingRecordsByResponseId(); LoaderBottomSheet.hideLoader(); @@ -741,11 +743,12 @@ class MedicalFileViewModel extends ChangeNotifier { // moveProfile.status = 3; // moveProfile.statusDescription = "Approved"; // patientFamilyFiles.add(moveProfile); - pendingFamilyFiles.removeWhere((element) => element.id == id); + pendingFamilyFiles = pendingFamilyFiles.where((element) => element.id != id).toList(); //TODO: Call Api Here To Load Family Members getFamilyFiles(status: 0); getAllPendingRecordsByResponseId(); LoaderBottomSheet.hideLoader(); + notifyListeners(); // onFamilyFileTabChange(0); } }, diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index ce9e877..0c76c9f 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -73,6 +73,8 @@ abstract class MyAppointmentsRepo { Future>> sendDoctorRate(int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note, String appoDate, String docName, String projectName, String clinicName); Future>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID}); + + Future>> convertPatientToCash({required int projectID}); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -957,4 +959,36 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { } } + @override + Future> convertPatientToCash({required int projectID}) async { + Map request; + + request = {"ProjectID": projectID}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post(CONVERT_PATIENT_TO_CASH, onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = (response['MessageStatus'] == 1); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: list, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, body: request); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } \ No newline at end of file diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 25e41ee..35bf29b 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -1,6 +1,9 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_state.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/features/my_appointments/models/appointemnet_filters.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; @@ -10,7 +13,13 @@ 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_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; class MyAppointmentsViewModel extends ChangeNotifier { int selectedTabIndex = 0; @@ -73,6 +82,9 @@ class MyAppointmentsViewModel extends ChangeNotifier { GetTamaraInstallmentsDetailsResponseModel? getTamaraInstallmentsDetailsResponseModel; bool isTamaraDetailsLoading = false; + bool isShowNotEligibleDialog = false; + String notEligibleErrorMsg = ""; + MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService, required this.appState}); void onTabChange(int index) { @@ -141,6 +153,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { isTamaraDetailsLoading = true; isAppointmentPatientShareLoading = true; isEyeMeasurementsAppointmentsLoading = true; + isShowNotEligibleDialog = false; notifyListeners(); } @@ -349,7 +362,99 @@ class MyAppointmentsViewModel extends ChangeNotifier { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { patientAppointmentShareResponseModel = apiResponse.data!; + + if ((patientAppointmentShareResponseModel!.isCash!)) { + isShowNotEligibleDialog = false; + } else { + if (patientAppointmentShareResponseModel!.isInsured! && patientAppointmentShareResponseModel!.isEligible!) { + isShowNotEligibleDialog = false; + } else { + if (patientAppointmentShareResponseModel!.isInsured! && patientAppointmentShareResponseModel!.isEligible!) { + isShowNotEligibleDialog = true; + notEligibleErrorMsg = LocaleKeys.invalidEligibility.tr(context: GetIt.instance().navigatorKey.currentContext!); + } else { + isShowNotEligibleDialog = true; + notEligibleErrorMsg = LocaleKeys.invalidInsurance.tr(context: GetIt.instance().navigatorKey.currentContext!); + } + } + } isAppointmentPatientShareLoading = false; + + if (isShowNotEligibleDialog) { + showCommonBottomSheetWithoutHeight(GetIt.instance().navigatorKey.currentContext!, + child: Padding( + padding: EdgeInsets.only( + left: 24.w, + top: 24.h, + right: 24.w, + bottom: 12.h, + ), + child: Utils.getWarningWidget( + loadingText: notEligibleErrorMsg, + confirmText: LocaleKeys.updateInsuranceInfo.tr(context: GetIt.instance().navigatorKey.currentContext!), + cancelText: LocaleKeys.continueCash.tr(context: GetIt.instance().navigatorKey.currentContext!), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + LoaderBottomSheet.showLoader(); + convertPatientToCash( + projectID: projectID, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + setIsPatientAppointmentShareLoading(true); + getPatientShareAppointment(projectID, clinicID, appointmentNo, isLiveCareAppointment); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: GetIt.instance().navigatorKey.currentContext!), + GetIt.instance().navigatorKey.currentContext!, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () { + Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + onConfirmTap: () async { + Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + Navigator.push( + GetIt.instance().navigatorKey.currentContext!, + CustomPageRoute( + page: InsuranceHomePage(), + ), + ); + }), + ), callBackFunc: () { + // Navigator.pop(GetIt.instance().navigatorKey.currentContext!); + }, isFullScreen: false, isCloseButtonVisible: false, isDismissible: false); + } + + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future convertPatientToCash({required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.convertPatientToCash(projectID: projectID); + + result.fold( + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -413,7 +518,12 @@ class MyAppointmentsViewModel extends ChangeNotifier { final result = await myAppointmentsRepo.cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + if(onError != null) { + onError(failure.message); + } + }, (apiResponse) { if (apiResponse.messageStatus == 2) { onError!(apiResponse.errorMessage!); @@ -510,7 +620,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { } Future getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async { - if (!isAppointmentDataToBeLoaded) return; + // if (!isAppointmentDataToBeLoaded) return; isPatientMyDoctorsLoading = true; notifyListeners(); diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart index 2eb5d43..3bfcc7f 100644 --- a/lib/features/prescriptions/prescriptions_repo.dart +++ b/lib/features/prescriptions/prescriptions_repo.dart @@ -188,13 +188,13 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { Map mapDevice = { "AppointmentNo": prescriptionsResponseModel.appointmentNo, "SetupID": prescriptionsResponseModel.setupID, - "EpisodeID": prescriptionsResponseModel.episodeID, + "EpisodeNo": prescriptionsResponseModel.episodeID, "ClinicID": prescriptionsResponseModel.clinicID, "ProjectID": prescriptionsResponseModel.projectID, "DischargeNo": prescriptionsResponseModel.dischargeNo, "IsDownload": true, - // "To": Utils.appState.getAuthenticatedUser()!.emailAddress, - "To": "amjad.haroon@cloudsolutions.com.sa", + "To": Utils.appState.getAuthenticatedUser()!.emailAddress, + // "To": "amjad.haroon@cloudsolutions.com.sa", "ClinicName": prescriptionsResponseModel.clinicDescription, "DoctorName": prescriptionsResponseModel.doctorName, }; diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index a7cd42f..c0f9c7b 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -54,7 +54,7 @@ class PrescriptionsViewModel extends ChangeNotifier { PrescriptionsViewModel({required this.prescriptionsRepo, required this.errorHandlerService, required this.navServices}); initPrescriptionsViewModel() { - if (isPrescriptionsDataNeedsReloading) { + // if (isPrescriptionsDataNeedsReloading) { patientPrescriptionOrders.clear(); patientPrescriptionOrdersByClinic.clear(); patientPrescriptionOrdersByHospital.clear(); @@ -62,7 +62,7 @@ class PrescriptionsViewModel extends ChangeNotifier { prescriptionsOrderList.clear(); isPrescriptionsOrdersLoading = true; getPatientPrescriptionOrders(); - } + // } isSortByClinic = true; isPrescriptionsDeliveryOrdersLoading = true; notifyListeners(); @@ -102,9 +102,9 @@ class PrescriptionsViewModel extends ChangeNotifier { } Future getPatientPrescriptionOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { - if (!isPrescriptionsDataNeedsReloading) { - return; - } + // if (!isPrescriptionsDataNeedsReloading) { + // return; + // } final result = await prescriptionsRepo.getPatientPrescriptionOrders(patientId: "1231755"); @@ -153,7 +153,10 @@ class PrescriptionsViewModel extends ChangeNotifier { final result = await prescriptionsRepo.getPatientPrescriptionDetails(prescriptionsResponseModel: prescriptionsResponseModel); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + onError!(failure.message); + }, (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); diff --git a/lib/features/radiology/radiology_repo.dart b/lib/features/radiology/radiology_repo.dart index 2af3e0f..5847718 100644 --- a/lib/features/radiology/radiology_repo.dart +++ b/lib/features/radiology/radiology_repo.dart @@ -121,7 +121,7 @@ class RadiologyRepoImp implements RadiologyRepo { Map mapDevice = { "InvoiceNo": Utils.isVidaPlusProject(patientRadiologyResponseModel.projectID!) ? 0 : patientRadiologyResponseModel.invoiceNo, "InvoiceNo_VP": Utils.isVidaPlusProject(patientRadiologyResponseModel.projectID!) ? patientRadiologyResponseModel.invoiceNo : 0, - "LineItemNo": patientRadiologyResponseModel.invoiceLineItemNo, + // "LineItemNo": patientRadiologyResponseModel.invoiceLineItemNo, "InvoiceLineItemNo": patientRadiologyResponseModel.invoiceLineItemNo, "ProjectID": patientRadiologyResponseModel.projectID!, "InvoiceType": patientRadiologyResponseModel.invoiceType!, diff --git a/lib/features/services_price_list/models/resp_models/services_price_list_response_model.dart b/lib/features/services_price_list/models/resp_models/services_price_list_response_model.dart new file mode 100644 index 0000000..bb60094 --- /dev/null +++ b/lib/features/services_price_list/models/resp_models/services_price_list_response_model.dart @@ -0,0 +1,54 @@ +class ServicesPriceListResponseModel { + int? createdBy; + String? createdOn; + int? editedBy; + String? editedOn; + int? id; + bool? isEnabled; + String? nameAR; + String? nameEN; + num? price; + int? rowID; + + ServicesPriceListResponseModel({ + this.createdBy, + this.createdOn, + this.editedBy, + this.editedOn, + this.id, + this.isEnabled, + this.nameAR, + this.nameEN, + this.price, + this.rowID, + }); + + ServicesPriceListResponseModel.fromJson(Map json) { + createdBy = json['CreatedBy']; + createdOn = json['CreatedOn']; + editedBy = json['EditedBy']; + editedOn = json['EditedOn']; + id = json['ID']; + isEnabled = json['IsEnabled']; + nameAR = json['NameAR']; + nameEN = json['NameEN']; + price = json['Price']; + rowID = json['RowID']; + } + + Map toJson() { + final Map data = {}; + data['CreatedBy'] = createdBy; + data['CreatedOn'] = createdOn; + data['EditedBy'] = editedBy; + data['EditedOn'] = editedOn; + data['ID'] = id; + data['IsEnabled'] = isEnabled; + data['NameAR'] = nameAR; + data['NameEN'] = nameEN; + data['Price'] = price; + data['RowID'] = rowID; + return data; + } +} + diff --git a/lib/features/services_price_list/services_price_list_repo.dart b/lib/features/services_price_list/services_price_list_repo.dart new file mode 100644 index 0000000..4267d77 --- /dev/null +++ b/lib/features/services_price_list/services_price_list_repo.dart @@ -0,0 +1,80 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/models/resp_models/services_price_list_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class ServicesPriceListRepo { + Future>>> getServicesPriceList({ + String searchKey = "", + int pageIndex = 0, + int pageSize = 0, + }); +} + +class ServicesPriceListRepoImp implements ServicesPriceListRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + ServicesPriceListRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getServicesPriceList({ + String searchKey = "", + int pageIndex = 0, + int pageSize = 0, + }) async { + Map mapDevice = { + "ID": 1, + "SearchKey": searchKey, + "PageIndex": pageIndex, + "PageSize": pageSize, + "RowCount": 0, + "TokenID": "@dm!n" + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + GET_SERVICES_PRICE_LIST, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['getServicesPriceList']; + if (list == null || list.isEmpty) { + throw Exception("Services price list is empty"); + } + + final servicesList = list + .map((item) => ServicesPriceListResponseModel.fromJson(item as Map)) + .toList() + .cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: servicesList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} + diff --git a/lib/features/services_price_list/services_price_list_view_model.dart b/lib/features/services_price_list/services_price_list_view_model.dart new file mode 100644 index 0000000..85b9680 --- /dev/null +++ b/lib/features/services_price_list/services_price_list_view_model.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/models/resp_models/services_price_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class ServicesPriceListViewModel extends ChangeNotifier { + bool isServicesPriceListLoading = false; + + ServicesPriceListRepo servicesPriceListRepo; + ErrorHandlerService errorHandlerService; + + List servicesPriceList = []; + List filteredServicesPriceList = []; + + String searchKey = ""; + + ServicesPriceListViewModel({ + required this.servicesPriceListRepo, + required this.errorHandlerService, + }); + + initServicesPriceListProvider() { + servicesPriceList.clear(); + filteredServicesPriceList.clear(); + isServicesPriceListLoading = true; + getServicesPriceList(); + notifyListeners(); + } + + setIsServicesPriceListLoading(bool val) { + isServicesPriceListLoading = val; + notifyListeners(); + } + + setSearchKey(String val) { + searchKey = val; + filterServicesList(val); + } + + void filterServicesList(String query) { + if (query.isEmpty) { + filteredServicesPriceList = List.from(servicesPriceList); + } else { + filteredServicesPriceList = servicesPriceList.where((service) { + final nameEN = service.nameEN?.toLowerCase() ?? ''; + final nameAR = service.nameAR?.toLowerCase() ?? ''; + final searchLower = query.toLowerCase(); + return nameEN.contains(searchLower) || nameAR.contains(searchLower); + }).toList(); + } + notifyListeners(); + } + + Future getServicesPriceList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await servicesPriceListRepo.getServicesPriceList( + searchKey: searchKey, + ); + + result.fold( + (failure) async { + isServicesPriceListLoading = false; + notifyListeners(); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + isServicesPriceListLoading = false; + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? "Error loading services price list"); + } + } else if (apiResponse.messageStatus == 1) { + servicesPriceList = apiResponse.data!; + servicesPriceList.removeWhere((element) => element.isEnabled == false); + filteredServicesPriceList = List.from(servicesPriceList); + isServicesPriceListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/symptoms_checker/data/organ_mapping_data.dart b/lib/features/symptoms_checker/data/organ_mapping_data.dart index c338a94..b0c2ffe 100644 --- a/lib/features/symptoms_checker/data/organ_mapping_data.dart +++ b/lib/features/symptoms_checker/data/organ_mapping_data.dart @@ -1,9 +1,11 @@ +import 'dart:developer'; + import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart'; class OrganData { // ==================== FRONT VIEW ==================== - static const List frontViewOrgans = [ + static const List frontViewOrgansForMale = [ // ========== HEAD & FACE ========== OrganModel( id: 'head', @@ -307,9 +309,313 @@ class OrganData { position: OrganPosition(x: 0.57, y: 0.91), ), ]; + static const List frontViewOrgansForFemale = [ + // ========== HEAD & FACE ========== + OrganModel( + id: 'head', + name: 'whole_head', + nameAr: 'الرأس_كامل', + description: 'Head', + descriptionAr: 'الرأس', + bodyView: BodyView.front, + position: OrganPosition(x: 0.54, y: 0.05), + ), + OrganModel( + id: 'left_eye', + name: 'eyes', + nameAr: 'العيون', + description: 'Left Eye', + descriptionAr: 'العين اليسرى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.49, y: 0.095), + ), + OrganModel( + id: 'right_eye', + name: 'eyes', + nameAr: 'العيون', + description: 'Right Eye', + descriptionAr: 'العين اليمنى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.6, y: 0.095), + ), + OrganModel( + id: 'nose_mouth', + name: 'oral_cavity', + nameAr: 'تجويف_الفم', + description: 'Nose/Mouth', + descriptionAr: 'الأنف/الفم', + bodyView: BodyView.front, + position: OrganPosition(x: 0.54, y: 0.12), + ), + + // ========== NECK ========== + OrganModel( + id: 'throat', + name: 'neck_or_throat', + nameAr: 'الرقبة_أو_الحلق', + description: 'Throat', + descriptionAr: 'الحلق', + bodyView: BodyView.front, + position: OrganPosition(x: 0.54, y: 0.17), + ), + + // ========== SHOULDERS ========== + OrganModel( + id: 'left_shoulder', + name: 'upper_arm', + nameAr: 'الذراع_العلوي', + description: 'Left Shoulder', + descriptionAr: 'الكتف الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.29, y: 0.22), + ), + OrganModel( + id: 'right_shoulder', + name: 'upper_arm', + nameAr: 'الذراع_العلوي', + description: 'Right Shoulder', + descriptionAr: 'الكتف الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.75, y: 0.22), + ), + + // ========== CHEST ========== + OrganModel( + id: 'left_chest', + name: 'chest', + nameAr: 'الصدر', + description: 'Left Chest', + descriptionAr: 'الصدر الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.4, y: 0.22), + ), + OrganModel( + id: 'center_chest', + name: 'chest', + nameAr: 'الصدر', + description: 'Center Chest', + descriptionAr: 'وسط الصدر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.52, y: 0.25), + ), + OrganModel( + id: 'right_chest', + name: 'chest', + nameAr: 'الصدر', + description: 'Right Chest', + descriptionAr: 'الصدر الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.65, y: 0.22), + ), + + // ========== RIBS ========== + OrganModel( + id: 'left_ribs', + name: 'chest', + nameAr: 'الصدر', + description: 'Left Ribs', + descriptionAr: 'الأضلاع اليسرى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.38, y: 0.35), + ), + OrganModel( + id: 'right_ribs', + name: 'chest', + nameAr: 'الصدر', + description: 'Right Ribs', + descriptionAr: 'الأضلاع اليمنى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.64, y: 0.35), + ), + + // ========== ABDOMEN ========== + OrganModel( + id: 'upper_abdomen', + name: 'upper_abdomen', + nameAr: 'البطن_العلوي', + description: 'Upper Abdomen', + descriptionAr: 'أعلى البطن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.51, y: 0.31), + ), + OrganModel( + id: 'navel', + name: 'mid_abdomen', + nameAr: 'البطن_الأوسط', + description: 'Navel', + descriptionAr: 'السرة', + bodyView: BodyView.front, + position: OrganPosition(x: 0.51, y: 0.38), + ), + OrganModel( + id: 'lower_abdomen', + name: 'lower_abdomen', + nameAr: 'البطن_السفلي', + description: 'Lower Abdomen', + descriptionAr: 'أسفل البطن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.51, y: 0.44), + ), + + // ========== PELVIS ========== + OrganModel( + id: 'left_groin', + name: 'sexual_organs', + nameAr: 'الأعضاء_التناسلية', + description: 'Left Groin', + descriptionAr: 'الفخذ الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.42, y: 0.49), + ), + OrganModel( + id: 'right_groin', + name: 'sexual_organs', + nameAr: 'الأعضاء_التناسلية', + description: 'Right Groin', + descriptionAr: 'الفخذ الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.59, y: 0.49), + ), + + // ========== LEFT ARM ========== + + OrganModel( + id: 'left_elbow', + name: 'elbow', + nameAr: 'الكوع', + description: 'Left Elbow', + descriptionAr: 'الكوع الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.24, y: 0.35), + ), + OrganModel( + id: 'left_forearm', + name: 'forearm', + nameAr: 'الساعد', + description: 'Left Forearm', + descriptionAr: 'الساعد الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.24, y: 0.43), + ), + OrganModel( + id: 'left_wrist', + name: 'hand', + nameAr: 'اليد', + description: 'Left Wrist', + descriptionAr: 'المعصم الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.17, y: 0.49), + ), + + // ========== RIGHT ARM ========== + + OrganModel( + id: 'right_elbow', + name: 'elbow', + nameAr: 'الكوع', + description: 'Right Elbow', + descriptionAr: 'الكوع الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.76, y: 0.35), + ), + OrganModel( + id: 'right_forearm', + name: 'forearm', + nameAr: 'الساعد', + description: 'Right Forearm', + descriptionAr: 'الساعد الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.76, y: 0.43), + ), + OrganModel( + id: 'right_wrist', + name: 'hand', + nameAr: 'اليد', + description: 'Right Wrist', + descriptionAr: 'المعصم الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.83, y: 0.5), + ), + + // ========== LEFT LEG ========== + OrganModel( + id: 'left_thigh', + name: 'thigh', + nameAr: 'الفخذ', + description: 'Left Thigh', + descriptionAr: 'الفخذ الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.4, y: 0.60), + ), + OrganModel( + id: 'left_knee', + name: 'knee', + nameAr: 'الركبة', + description: 'Left Knee', + descriptionAr: 'الركبة اليسرى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.4, y: 0.72), + ), + OrganModel( + id: 'left_shin', + name: 'lower_leg', + nameAr: 'الساق_السفلي', + description: 'Left Shin', + descriptionAr: 'الساق اليسرى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.41, y: 0.82), + ), + OrganModel( + id: 'left_ankle', + name: 'foot', + nameAr: 'القدم', + description: 'Left Ankle', + descriptionAr: 'الكاحل الأيسر', + bodyView: BodyView.front, + position: OrganPosition(x: 0.43, y: 0.91), + ), + + // ========== RIGHT LEG ========== + OrganModel( + id: 'right_thigh', + name: 'thigh', + nameAr: 'الفخذ', + description: 'Right Thigh', + descriptionAr: 'الفخذ الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.65, y: 0.60), + ), + OrganModel( + id: 'right_knee', + name: 'knee', + nameAr: 'الركبة', + description: 'Right Knee', + descriptionAr: 'الركبة اليمنى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.6, y: 0.72), + ), + OrganModel( + id: 'right_shin', + name: 'lower_leg', + nameAr: 'الساق_السفلي', + description: 'Right Shin', + descriptionAr: 'الساق اليمنى', + bodyView: BodyView.front, + position: OrganPosition(x: 0.59, y: 0.82), + ), + OrganModel( + id: 'right_ankle', + name: 'foot', + nameAr: 'القدم', + description: 'Right Ankle', + descriptionAr: 'الكاحل الأيمن', + bodyView: BodyView.front, + position: OrganPosition(x: 0.57, y: 0.91), + ), + ]; // ==================== BACK VIEW ==================== - static const List backViewOrgans = [ + static const List backViewOrgansForMale = [ // ========== HEAD & NECK ========== OrganModel( id: 'back_of_head', @@ -504,19 +810,232 @@ class OrganData { position: OrganPosition(x: 0.57, y: 0.79), ), ]; + static const List backViewOrgansForFemale = [ + // ========== HEAD & NECK ========== + OrganModel( + id: 'back_of_head', + name: 'whole_head', + nameAr: 'الرأس_كامل', + description: 'Back of Head', + descriptionAr: 'مؤخرة الرأس', + bodyView: BodyView.back, + position: OrganPosition(x: 0.48, y: 0.055), + ), + OrganModel( + id: 'neck_back', + name: 'nape_of_neck', + nameAr: 'مؤخرة_الرقبة', + description: 'Neck', + descriptionAr: 'الرقبة', + bodyView: BodyView.back, + position: OrganPosition(x: 0.50, y: 0.14), + ), + + // ========== SHOULDERS ========== + OrganModel( + id: 'left_shoulder_back', + name: 'upper_arm', + nameAr: 'الذراع_العلوي', + description: 'Left Shoulder', + descriptionAr: 'الكتف الأيسر', + bodyView: BodyView.back, + position: OrganPosition(x: 0.3, y: 0.2), + ), + OrganModel( + id: 'right_shoulder_back', + name: 'upper_arm', + nameAr: 'الذراع_العلوي', + description: 'Right Shoulder', + descriptionAr: 'الكتف الأيمن', + bodyView: BodyView.back, + position: OrganPosition(x: 0.69, y: 0.2), + ), + + // ========== UPPER BACK ========== + OrganModel( + id: 'upper_spine', + name: 'back', + nameAr: 'الظهر', + description: 'Upper Spine', + descriptionAr: 'العمود الفقري العلوي', + bodyView: BodyView.back, + position: OrganPosition(x: 0.50, y: 0.23), + ), + OrganModel( + id: 'left_upper_back', + name: 'back', + nameAr: 'الظهر', + description: 'Left Upper Back', + descriptionAr: 'أعلى الظهر الأيسر', + bodyView: BodyView.back, + position: OrganPosition(x: 0.38, y: 0.28), + ), + OrganModel( + id: 'right_upper_back', + name: 'back', + nameAr: 'الظهر', + description: 'Right Upper Back', + descriptionAr: 'أعلى الظهر الأيمن', + bodyView: BodyView.back, + position: OrganPosition(x: 0.62, y: 0.28), + ), + + // ========== MID BACK ========== + OrganModel( + id: 'mid_spine', + name: 'back', + nameAr: 'الظهر', + description: 'Mid Spine', + descriptionAr: 'العمود الفقري الأوسط', + bodyView: BodyView.back, + position: OrganPosition(x: 0.50, y: 0.35), + ), + OrganModel( + id: 'left_mid_back', + name: 'back', + nameAr: 'الظهر', + description: 'Left Mid Back', + descriptionAr: 'وسط الظهر الأيسر', + bodyView: BodyView.back, + position: OrganPosition(x: 0.38, y: 0.35), + ), + OrganModel( + id: 'right_mid_back', + name: 'back', + nameAr: 'الظهر', + description: 'Right Mid Back', + descriptionAr: 'وسط الظهر الأيمن', + bodyView: BodyView.back, + position: OrganPosition(x: 0.62, y: 0.35), + ), + + // ========== LOWER BACK ========== + OrganModel( + id: 'lower_spine', + name: 'lower_back', + nameAr: 'أسفل_الظهر', + description: 'Lower Back', + descriptionAr: 'أسفل الظهر', + bodyView: BodyView.back, + position: OrganPosition(x: 0.50, y: 0.43), + ), + OrganModel( + id: 'left_lower_back', + name: 'lower_back', + nameAr: 'أسفل_الظهر', + description: 'Left Lower Back', + descriptionAr: 'أسفل الظهر الأيسر', + bodyView: BodyView.back, + position: OrganPosition(x: 0.36, y: 0.43), + ), + OrganModel( + id: 'right_lower_back', + name: 'lower_back', + nameAr: 'أسفل_الظهر', + description: 'Right Lower Back', + descriptionAr: 'أسفل الظهر الأيمن', + bodyView: BodyView.back, + position: OrganPosition(x: 0.65, y: 0.43), + ), + + // ========== GLUTES ========== + OrganModel( + id: 'sacrum', + name: 'lower_back', + nameAr: 'أسفل_الظهر', + description: 'Sacrum', + descriptionAr: 'العجز', + bodyView: BodyView.back, + position: OrganPosition(x: 0.50, y: 0.5), + ), + OrganModel( + id: 'left_glute', + name: 'buttocks', + nameAr: 'الأرداف', + description: 'Left Glute', + descriptionAr: 'الأرداف اليسرى', + bodyView: BodyView.back, + position: OrganPosition(x: 0.36, y: 0.52), + ), + OrganModel( + id: 'right_glute', + name: 'buttocks', + nameAr: 'الأرداف', + description: 'Right Glute', + descriptionAr: 'الأرداف اليمنى', + bodyView: BodyView.back, + position: OrganPosition(x: 0.61, y: 0.52), + ), + + // ========== LEGS ========== + OrganModel( + id: 'left_hamstring', + name: 'thigh', + nameAr: 'الفخذ', + description: 'Left Hamstring', + descriptionAr: 'عضلة الفخذ الخلفية اليسرى', + bodyView: BodyView.back, + position: OrganPosition(x: 0.41, y: 0.62), + ), + OrganModel( + id: 'right_hamstring', + name: 'thigh', + nameAr: 'الفخذ', + description: 'Right Hamstring', + descriptionAr: 'عضلة الفخذ الخلفية اليمنى', + bodyView: BodyView.back, + position: OrganPosition(x: 0.62, y: 0.62), + ), + OrganModel( + id: 'left_calf', + name: 'lower_leg', + nameAr: 'الساق_السفلي', + description: 'Left Calf', + descriptionAr: 'ربلة الساق اليسرى', + bodyView: BodyView.back, + position: OrganPosition(x: 0.43, y: 0.79), + ), + OrganModel( + id: 'right_calf', + name: 'lower_leg', + nameAr: 'الساق_السفلي', + description: 'Right Calf', + descriptionAr: 'ربلة الساق اليمنى', + bodyView: BodyView.back, + position: OrganPosition(x: 0.57, y: 0.79), + ), + ]; // Helper methods - static List getOrgansForView(BodyView view) { - return view == BodyView.front ? frontViewOrgans : backViewOrgans; + // Accept genderKey (e.g., 'male' / 'female' or Arabic 'ذكر' / 'أنثى') to return gender-specific organ sets + static List getOrgansForView(BodyView view, {String? genderKey}) { + final bool isFemale = genderKey != null && (genderKey.toLowerCase() == 'female' || genderKey == 'أنثى' || genderKey == '2'); + + + if (view == BodyView.front) { + return isFemale ? frontViewOrgansForFemale : frontViewOrgansForMale; + } else { + return isFemale ? backViewOrgansForFemale : backViewOrgansForMale; + } } - static List getAllOrgans() { - return [...frontViewOrgans, ...backViewOrgans]; + /// Return all organs (front + back) for the selected gender. + /// If genderKey is null, default to male to preserve previous behavior. + static List getAllOrgans({String? genderKey}) { + final bool isFemale = genderKey != null && (genderKey.toLowerCase() == 'female' || genderKey == 'أنثى' || genderKey == '2'); + + if (isFemale) { + return [...frontViewOrgansForFemale, ...backViewOrgansForFemale]; + } + + // default: male + return [...frontViewOrgansForMale, ...backViewOrgansForMale]; } - static OrganModel? getOrganById(String id) { + /// Find organ by id within the organs of the selected gender (or male if not specified) + static OrganModel? getOrganById(String id, {String? genderKey}) { try { - return getAllOrgans().firstWhere((organ) => organ.id == id); + return getAllOrgans(genderKey: genderKey).firstWhere((organ) => organ.id == id); } catch (e) { return null; } diff --git a/lib/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart b/lib/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart new file mode 100644 index 0000000..2d878c1 --- /dev/null +++ b/lib/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart @@ -0,0 +1,25 @@ +class GetClinicDetailsResponseModel { + String? category; + int? clinicID; + bool? isActive; + String? categoryAR; + + GetClinicDetailsResponseModel( + {this.category, this.clinicID, this.isActive, this.categoryAR}); + + GetClinicDetailsResponseModel.fromJson(Map json) { + category = json['Category']; + clinicID = json['ClinicID']; + isActive = json['IsActive']; + categoryAR = json['CategoryAR']; + } + + Map toJson() { + final Map data = new Map(); + data['Category'] = this.category; + data['ClinicID'] = this.clinicID; + data['IsActive'] = this.isActive; + data['CategoryAR'] = this.categoryAR; + return data; + } +} diff --git a/lib/features/symptoms_checker/symptoms_checker_repo.dart b/lib/features/symptoms_checker/symptoms_checker_repo.dart index c0214e1..33bcbd8 100644 --- a/lib/features/symptoms_checker/symptoms_checker_repo.dart +++ b/lib/features/symptoms_checker/symptoms_checker_repo.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; @@ -50,6 +51,11 @@ abstract class SymptomsCheckerRepo { required String userSessionToken, required String sessionId, }); + + Future>>> getClinicDetails({ + required String categoryName, + required String userSessionToken, + }); } class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { @@ -390,4 +396,57 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getClinicDetails({ + required String categoryName, + required String userSessionToken, + }) async { + Map headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userSessionToken', + }; + Map body = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.get( + "${ApiConsts.getClinicFromCondition}$categoryName", + apiHeaders: headers, + isExternal: true, + isAllowAny: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType ?? ServerFailure(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + final clinicDetails; + try { + final list = response['dataDetails']; + clinicDetails = list.map((item) => GetClinicDetailsResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus ?? 1, + statusCode: statusCode, + errorMessage: errorMessage, + data: clinicDetails, + ); + } catch (e, stackTrace) { + loggerService.logError("Error parsing GetClinicsByCondition response: $e"); + loggerService.logError("StackTrace: $stackTrace"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e, stackTrace) { + loggerService.logError("Exception in GetClinicsByCondition: $e"); + loggerService.logError("StackTrace: $stackTrace"); + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index ed82451..215b692 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/data/organ_mapping_data.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; @@ -84,9 +85,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier { String? _selectedGender; DateTime? _dateOfBirth; int? _selectedAge; - double _selectedHeight = 170; + num _selectedHeight = 170; bool _isHeightCm = true; - double _selectedWeight = 60; + num _selectedWeight = 60; bool _isWeightKg = true; // Getters @@ -104,11 +105,25 @@ class SymptomsCheckerViewModel extends ChangeNotifier { int? get selectedAge => _selectedAge; - double? get selectedHeight => _selectedHeight; + setSelectedAge(int age) { + _selectedAge = age; + } + + setSelectedHeight(num height) { + _selectedHeight = height; + notifyListeners(); + } + + setSelectedWeight(num weight) { + _selectedWeight = weight; + notifyListeners(); + } + + num? get selectedHeight => _selectedHeight; bool get isHeightCm => _isHeightCm; - double? get selectedWeight => _selectedWeight; + num? get selectedWeight => _selectedWeight; bool get isWeightKg => _isWeightKg; @@ -140,6 +155,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Get the number of triage questions answered int get triageQuestionCount => _triageQuestionCount; + List clinicDetailsList = []; + /// Get choice index for a specific item int? getTriageChoiceForItem(String itemId) { return _selectedTriageChoicesByItemId[itemId]; @@ -161,14 +178,11 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } /// Get organs for current view - List get currentOrgans => OrganData.getOrgansForView(_currentView); + List get currentOrgans => OrganData.getOrgansForView(_currentView, genderKey: _selectedGender); - /// Get all selected organs from both views + /// Get all selected organs from both views (for the selected gender) List get selectedOrgans { - final allOrgans = [ - ...OrganData.frontViewOrgans, - ...OrganData.backViewOrgans, - ]; + final allOrgans = OrganData.getAllOrgans(genderKey: _selectedGender); return allOrgans.where((organ) => _selectedOrganIds.contains(organ.id)).toList(); } @@ -1055,6 +1069,43 @@ class SymptomsCheckerViewModel extends ChangeNotifier { ); } + Future getClinicConditionsFromCategory({ + required String categoryName, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + clinicDetailsList.clear(); + notifyListeners(); + + final result = await symptomsCheckerRepo.getClinicDetails( + categoryName: categoryName, + userSessionToken: currentSessionAuthToken, + ); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 1 && apiResponse.data != null) { + clinicDetailsList = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data!); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Failed to fetch clinic details'); + } + } + }, + ); + } + @override void dispose() { _tooltipTimer?.cancel(); diff --git a/lib/features/todo_section/todo_section_repo.dart b/lib/features/todo_section/todo_section_repo.dart index 76a4438..6d58d80 100644 --- a/lib/features/todo_section/todo_section_repo.dart +++ b/lib/features/todo_section/todo_section_repo.dart @@ -96,6 +96,8 @@ class TodoSectionRepoImp implements TodoSectionRepo { } } + ancillaryOrders.sort((a, b) => b.orderDate!.compareTo(a.orderDate!)); + apiResponse = GenericApiModel>( messageStatus: messageStatus, statusCode: statusCode, diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index d1c53a0..9658edb 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -45,21 +45,19 @@ class DefaultFirebaseOptions { appId: '1:815750722565:android:62281cd3e5df4063', messagingSenderId: '815750722565', projectId: 'api-project-815750722565', - // databaseURL: - // 'https://flutterfire-e2e-tests-default-rtdb.europe-west1.firebasedatabase.app', - storageBucket: 'api-project-815750722565.appspot.com', + databaseURL: 'https://api-project-815750722565.firebaseio.com', + storageBucket: 'api-project-815750722565.firebasestorage.app', ); static const FirebaseOptions ios = FirebaseOptions( apiKey: 'AIzaSyDiXnCO00li4V7Ioa2YZ_M4ECxRsu_P9tA', - appId: '1:815750722565:ios:328ec247a81a2ca23c186c', + appId: '1:815750722565:ios:34ec03c09e02e21f3c186c', messagingSenderId: '815750722565', projectId: 'api-project-815750722565', - storageBucket: 'api-project-815750722565.appspot.com', - androidClientId: - '815750722565-m14h8mkosm7cnq6uh6rhqr54dn02d705.apps.googleusercontent.com', - iosClientId: - '815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt.apps.googleusercontent.com', - iosBundleId: 'com.HMG.HMG-Smartphone', + databaseURL: 'https://api-project-815750722565.firebaseio.com', + storageBucket: 'api-project-815750722565.firebasestorage.app', + androidClientId: '815750722565-m14h8mkosm7cnq6uh6rhqr54dn02d705.apps.googleusercontent.com', + iosBundleId: 'com.cloudsolutions.HMGPatientApp', ); -} + +} \ No newline at end of file diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 9c465f3..e542a29 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -941,6 +941,7 @@ abstract class LocaleKeys { static const offline = 'offline'; static const videoCall = 'videoCall'; static const liveVideoCallWithHMGDoctors = 'liveVideoCallWithHMGDoctors'; + static const liveAudioCallWithHMGDoctors = 'liveAudioCallWithHMGDoctors'; static const audioCall = 'audioCall'; static const phoneCall = 'phoneCall'; static const livePhoneCallWithHMGDoctors = 'livePhoneCallWithHMGDoctors'; @@ -1513,8 +1514,65 @@ abstract class LocaleKeys { static const trackerAndOthers = 'trackerAndOthers'; static const bmi = 'bmi'; static const yesPleaseINeedHelp = 'yesPleaseINeedHelp'; + static const aiOverView = 'aiOverView'; + static const generateAiAnalysis = 'generateAiAnalysis'; + static const aiAnalysis = 'aiAnalysis'; + static const prediction = 'prediction'; + static const prevention = 'prevention'; + static const disclaimer = 'disclaimer'; + static const thisAboveInfo = 'thisAboveInfo'; + static const aiDisclaimer = 'aiDisclaimer'; + static const consent = 'consent'; + static const bmiNormalDescription = 'bmiNormalDescription'; + static const bmiUnderweightDescription = 'bmiUnderweightDescription'; + static const bmiOverweightDescription = 'bmiOverweightDescription'; + static const caloriesMaintenanceDescription = 'caloriesMaintenanceDescription'; + static const caloriesModerateDeficitDescription = 'caloriesModerateDeficitDescription'; + static const caloriesAggressiveDeficitDescription = 'caloriesAggressiveDeficitDescription'; + static const bodyFatEssentialDescription = 'bodyFatEssentialDescription'; + static const bodyFatFitnessDescription = 'bodyFatFitnessDescription'; + static const bodyFatAverageDescription = 'bodyFatAverageDescription'; + static const bodyFatObeseDescription = 'bodyFatObeseDescription'; + static const bmrNormalDescription = 'bmrNormalDescription'; + static const bmrLowDescription = 'bmrLowDescription'; + static const bmrHighDescription = 'bmrHighDescription'; + static const ibwAtTargetDescription = 'ibwAtTargetDescription'; + static const ibwBelowTargetDescription = 'ibwBelowTargetDescription'; + static const ibwAboveTargetDescription = 'ibwAboveTargetDescription'; + static const ibwCloseToRangeDescription = 'ibwCloseToRangeDescription'; + static const ibwUnderweightDescription = 'ibwUnderweightDescription'; + static const ibwUnderweightGenericDescription = 'ibwUnderweightGenericDescription'; + static const ibwOverweightDescription = 'ibwOverweightDescription'; + static const ibwOverweightGenericDescription = 'ibwOverweightGenericDescription'; + static const dietVeryLowCarbsDescription = 'dietVeryLowCarbsDescription'; + static const dietLowCarbsDescription = 'dietLowCarbsDescription'; + static const dietModerateCarbsDescription = 'dietModerateCarbsDescription'; + static const dietUSDAGuidelinesDescription = 'dietUSDAGuidelinesDescription'; + static const dietZoneDietDescription = 'dietZoneDietDescription'; + static const contextDependent = 'contextDependent'; + static const calculatedValue = 'calculatedValue'; + static const calculationFormula = 'calculationFormula'; + static const confirmYourLocation = 'confirmYourLocation'; + static const confirmYourLocationDesc = 'confirmYourLocationDesc'; + static const insideHospital = 'insideHospital'; + static const outsideHospital = 'outsideHospital'; + static const servicePriceList = 'servicePriceList'; + static const servicePriceListDesc = 'servicePriceListDesc'; + static const servicePriceListRights = 'servicePriceListRights'; + static const serviceName = 'serviceName'; + static const noFreeSlot = 'noFreeSlot'; static const noThanksIKnowTheClinic = 'noThanksIKnowTheClinic'; static const unableToSendOTP = 'unableToSendOTP'; - static const timeFor = 'Time for'; + static const loadingAIAnalysis = 'loadingAIAnalysis'; + static const symptoms = 'symptoms'; + static const insuranceInActive = 'insuranceInActive'; + static const insuranceInActiveContactSupport = 'insuranceInActiveContactSupport'; + static const updateAppNow = 'updateAppNow'; + static const Dr = 'Dr'; + static const reschedulingAppo = 'reschedulingAppo'; + static const invalidEligibility = 'invalidEligibility'; + static const invalidInsurance = 'invalidInsurance'; + static const continueCash = 'continueCash'; + static const timeFor = 'Time for'; } diff --git a/lib/main.dart b/lib/main.dart index 1137261..cff0d04 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -36,6 +37,7 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; @@ -75,12 +77,33 @@ Future callAppStateInitializations() async { : "3"); AppState appState = getIt.get(); appState.setDeviceTypeID = deviceTypeId; + + // Pass all uncaught "fatal" errors from the framework to Crashlytics + FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; + + // Pass all uncaught asynchronous errors that aren't handled by the Flutter framework to Crashlytics + PlatformDispatcher.instance.onError = (error, stack) { + if (!kDebugMode) FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); + return true; + }; } Future callInitializations() async { WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); - await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + + // await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + + try { + // Attempt to get the default app. If it exists, this avoids the error. + await Firebase.app(); + } catch (e) { + // If it doesn't exist, initialize it. + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + } + await AppDependencies.addDependencies(); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); HttpOverrides.global = MyHttpOverrides(); @@ -202,6 +225,9 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), // Wrap your app @@ -222,9 +248,16 @@ class MyApp extends StatelessWidget { builder: (context, orientation, deviceType) { // SizeConfig().init(constraints, orientation); return MaterialApp( - title: 'Dr. AlHabib', + // title: 'Dr. AlHabib', + title: 'Dr. AlHabib Beta', builder: (context, mchild) { - return MediaQuery(data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling), child: mchild!); + return MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.noScaling, + alwaysUse24HourFormat: true, + ), + child: mchild!, + ); }, showSemanticsDebugger: false, debugShowCheckedModeBanner: false, diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 023bdee..e5961e3 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -8,6 +9,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/calender_utils_new.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/penguin_method_channel.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -16,6 +18,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart'; +import 'package:hmg_patient_app_new/features/hospital/AppPermission.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; @@ -38,7 +41,8 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import '../medical_file/widgets/medical_file_card.dart'; @@ -95,6 +99,7 @@ class _AppointmentDetailsPageState extends State { ? () { contactUsViewModel.setSelectedFeedbackType(FeedbackType(id: 1, nameEN: "Complaint for appointment", nameAR: 'شكوى على موعد')); contactUsViewModel.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); + contactUsViewModel.setIsSendFeedbackTabSelected(true); Navigator.of(context).push( CustomPageRoute( page: FeedbackPage(), @@ -107,9 +112,9 @@ class _AppointmentDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ AppointmentDoctorCard( - renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || - (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || - !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)), + // renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || + renderWidgetForERDisplay: + ((widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)), patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onAskDoctorTap: () async { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); @@ -140,8 +145,6 @@ class _AppointmentDetailsPageState extends State { }); }, onCancelTap: () async { - myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.cancellingAppointmentPleaseWait.tr(context: context)); await myAppointmentsViewModel.cancelAppointment( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, @@ -160,6 +163,18 @@ class _AppointmentDetailsPageState extends State { isDismissible: false, isFullScreen: false, ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + title: "", + isCloseButtonVisible: true, + isDismissible: false, + isFullScreen: false, + ); }); var isEventAddedOrRemoved = await CalenderUtilsNew.instance.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", ); setState(() { @@ -187,15 +202,57 @@ class _AppointmentDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.appointmentStatus.tr(context: context).toText16(isBold: true), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.appointmentStatus.tr(context: context).toText16(isBold: true), + SizedBox(height: 4.h), + (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) + ? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) + : LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), + SizedBox(height: 16.h), + ], + ), + ((!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) && widget.patientAppointmentHistoryResponseModel.nextAction != 10) + ? CustomButton( + text: LocaleKeys.confirm.tr(), + onPressed: () async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingAppointmentPleaseWait.tr(context: context)); + await myAppointmentsViewModel.confirmAppointment( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + LoaderBottomSheet.hideLoader(); + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + showCommonBottomSheetWithoutHeight( + title: "", + context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), + callBackFunc: () { + Navigator.of(context).pop(); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + backgroundColor: AppColors.successColor, + borderColor: AppColors.successColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + height: 40.h, + icon: AppAssets.confirm_appointment_icon, + iconColor: AppColors.whiteColor, + iconSize: 16.h, + ) + : SizedBox.shrink()) ], ), - SizedBox(height: 4.h), - (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) - ? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) - : LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), - SizedBox(height: 16.h), //TODO Add countdown timer in case of LiveCare Appointment widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false ? Row( @@ -228,9 +285,24 @@ class _AppointmentDetailsPageState extends State { child: SizedBox( width: MediaQuery.of(context).size.width * 0.785, child: CustomButton( - onPressed: () { - MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), - double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); + onPressed: () async { + if (widget.patientAppointmentHistoryResponseModel.projectID == 130 || widget.patientAppointmentHistoryResponseModel.projectID == 120) { + showDirectionsBottomSheet(); + } else { + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", + ); + }); + } }, text: LocaleKeys.getDirections.tr(context: context), backgroundColor: AppColors.textColor.withValues(alpha: 0.8), @@ -471,7 +543,7 @@ class _AppointmentDetailsPageState extends State { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: err), + child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, @@ -807,6 +879,89 @@ class _AppointmentDetailsPageState extends State { ); } + void showDirectionsBottomSheet() { + showCommonBottomSheetWithoutHeight( + context, + title: "", + isDismissible: true, + isCloseButtonVisible: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.confirmYourLocation.tr(context: context).toText28(color: AppColors.textColor, isBold: true, height: 1.5), + SizedBox(height: 4.h), + LocaleKeys.confirmYourLocationDesc.tr(context: context).toText12( + color: AppColors.greyTextColor, + fontWeight: FontWeight.w500, + ), + SizedBox(height: 24.h), + CustomButton( + text: LocaleKeys.insideHospital.tr(context: context), + onPressed: () { + Navigator.pop(context); + initPenguinSDK(widget.patientAppointmentHistoryResponseModel.projectID == 130 ? 1 : (widget.patientAppointmentHistoryResponseModel.projectID == 120 ? 3 : -1), + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID.toString()); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 56.h, + ), + SizedBox(height: 8.h), + CustomButton( + text: LocaleKeys.outsideHospital.tr(context: context), + onPressed: () async { + Navigator.pop(context); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), + title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", + ); + }); + }, + backgroundColor: AppColors.chipSecondaryLightRedColor, + borderColor: AppColors.chipSecondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 56.h, + ), + ], + ).paddingSymmetrical(24.w, 20.h), + callBackFunc: () {}, + ); + } + + initPenguinSDK(int projectID, {String clinicID = ""}) async { + NavigationClinicDetails data = NavigationClinicDetails(); + data.projectId = projectID.toString(); + data.clinicId = clinicID; + // data.clinicId = "1"; + final bool permited = await AppPermission.askPenguinPermissions(); + if (!permited) { + Map statuses = await [ + Permission.location, + Permission.bluetooth, + Permission.bluetoothConnect, + Permission.bluetoothScan, + Permission.activityRecognition, + ].request().whenComplete(() { + PenguinMethodChannel().launch("penguin", getIt.get().isArabic() ? "ar" : "en", getIt.get().getAuthenticatedUser()?.patientId?.toString() ?? "", true, details: data); + }); + } + } + openDoctorScheduleCalendar() async { DoctorsListResponseModel doctor = DoctorsListResponseModel( clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 097efaf..e70ddf4 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -491,6 +491,7 @@ class _AppointmentPaymentPageState extends State { patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); @@ -511,6 +512,7 @@ class _AppointmentPaymentPageState extends State { isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, onSuccess: (apiResponse) { Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart index 3795ce1..fc7798f 100644 --- a/lib/presentation/appointments/my_doctors_page.dart +++ b/lib/presentation/appointments/my_doctors_page.dart @@ -290,6 +290,12 @@ class _MyDoctorsPageState extends State { clinicID: doctor?.clinicID ?? 0, projectID: doctor?.projectID ?? 0, doctorID: doctor?.doctorID ?? 0, + clinicName: doctor?.clinicName, + projectName: doctor?.projectName, + // nationalityFlagURL: doctor?.nationalityFlagURL, + name: doctor?.doctorName, + doctorImageURL: doctor?.doctorImageURL ?? doctor?.doctorImage, + speciality: doctor?.doctorSpeciality )); LoaderBottomSheet.showLoader(); await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) { diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 493d15b..971e9ca 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -26,6 +26,7 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'dart:ui' as ui; class AppointmentCard extends StatelessWidget { final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; @@ -39,20 +40,20 @@ class AppointmentCard extends StatelessWidget { final ContactUsViewModel? contactUsViewModel; final BookAppointmentsViewModel bookAppointmentsViewModel; final bool isForRate; - const AppointmentCard({ - super.key, - required this.patientAppointmentHistoryResponseModel, - required this.myAppointmentsViewModel, - required this.bookAppointmentsViewModel, - this.isLoading = false, - this.isFromHomePage = false, - this.isFromMedicalReport = false, - this.isForEyeMeasurements = false, - this.isForFeedback = false, - this.medicalFileViewModel, - this.contactUsViewModel, - this.isForRate =false - }); + + const AppointmentCard( + {super.key, + required this.patientAppointmentHistoryResponseModel, + required this.myAppointmentsViewModel, + required this.bookAppointmentsViewModel, + this.isLoading = false, + this.isFromHomePage = false, + this.isFromMedicalReport = false, + this.isForEyeMeasurements = false, + this.isForFeedback = false, + this.medicalFileViewModel, + this.contactUsViewModel, + this.isForRate = false}); @override Widget build(BuildContext context) { @@ -64,11 +65,11 @@ class AppointmentCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - isForRate ? SizedBox(): _buildHeader(context, appState), + isForRate ? SizedBox() : _buildHeader(context, appState), SizedBox(height: 16.h), _buildDoctorRow(context), SizedBox(height: 16.h), - isForRate ? SizedBox(): _buildActionArea(context, appState), + isForRate ? SizedBox() : _buildActionArea(context, appState), ], ), ), @@ -101,18 +102,13 @@ class AppointmentCard extends StatelessWidget { textColor: isLoading ? AppColors.textColor : (isLiveCare ? AppColors.whiteColor : AppColors.textColor), ).toShimmer2(isShow: isLoading), AppCustomChipWidget( - labelText: isLoading - ? 'OutPatient' - : (appState.isArabic() - ? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! - : patientAppointmentHistoryResponseModel.isInOutPatientDescription!), + labelText: + isLoading ? 'OutPatient' : (appState.isArabic() ? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : patientAppointmentHistoryResponseModel.isInOutPatientDescription!), backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), textColor: AppColors.primaryRedColor, ).toShimmer2(isShow: isLoading), AppCustomChipWidget( - labelText: isLoading - ? 'Booked' - : AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!), + labelText: isLoading ? 'Booked' : AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!), backgroundColor: AppColors.successColor.withValues(alpha: 0.1), textColor: AppColors.successColor, ).toShimmer2(isShow: isLoading), @@ -188,13 +184,17 @@ class AppointmentCard extends StatelessWidget { ? '${(patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").substring(0, 15)}...' : patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital") .toShimmer2(isShow: isLoading), - AppCustomChipWidget( - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), - icon: AppAssets.appointment_calendar_icon, - labelText: isLoading - ? 'Cardiology' - : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}", - ).toShimmer2(isShow: isLoading), + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + icon: AppAssets.appointment_calendar_icon, + richText: isLoading + ? 'Cardiology'.toText10().toShimmer2(isShow: isLoading) + : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}" + .toText10(isEnglishOnly: true), + ).toShimmer2(isShow: isLoading), + ), // AppCustomChipWidget( // labelPadding: EdgeInsetsDirectional.only(start: -2.w, end: 6.w), diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index d1abf2f..95fc09b 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -58,17 +58,17 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { projectDetailListModel = Utils.getProjectDetailObj(appState, patientAppointmentHistoryResponseModel.projectID); double dist = Utils.distance(value.latitude, value.longitude, double.parse(projectDetailListModel.latitude!), double.parse(projectDetailListModel.longitude!)).ceilToDouble() * 1000; print(dist); - // if (dist <= projectDetailListModel.geofenceRadius!) { + if (dist <= projectDetailListModel.geofenceRadius!) { sendCheckInRequest(projectDetailListModel.checkInQrCode!, 3, context); - // } else { - // showCommonBottomSheetWithoutHeight(context, - // title: LocaleKeys.error.tr(context: context), - // child: Utils.getErrorWidget( - // loadingText: LocaleKeys.ensureWithinHospitalLocation.tr(context: context), - // ), callBackFunc: () { - // Navigator.of(context).pop(); - // }, isFullScreen: false); - // } + } else { + showCommonBottomSheetWithoutHeight(context, + title: LocaleKeys.error.tr(context: context), + child: Utils.getErrorWidget( + loadingText: LocaleKeys.ensureWithinHospitalLocation.tr(context: context), + ), callBackFunc: () { + Navigator.of(context).pop(); + }, isFullScreen: false); + } }); }), SizedBox(height: 16.h), @@ -155,7 +155,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { myAppointmentsViewModel.getPatientAppointments(true, false); LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () async { - await myAppointmentsViewModel.getPatientAppointmentQueueDetails(); + // await myAppointmentsViewModel.getPatientAppointmentQueueDetails(); Navigator.of(context).pop(); Navigator.pushAndRemoveUntil( context, @@ -163,11 +163,11 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { page: LandingNavigation(), ), (r) => false); - Navigator.of(context).push( - CustomPageRoute( - page: AppointmentQueuePage(), - ), - ); + // Navigator.of(context).push( + // CustomPageRoute( + // page: AppointmentQueuePage(), + // ), + // ); }, isFullScreen: false); }, onError: (error) { diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 0e3ade7..9e56053 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -12,6 +12,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'dart:ui' as ui; class AppointmentDoctorCard extends StatelessWidget { const AppointmentDoctorCard( @@ -98,13 +99,16 @@ class AppointmentDoctorCard extends StatelessWidget { labelText: patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), ), - AppCustomChipWidget( - labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), - icon: AppAssets.doctor_calendar_icon, - labelText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang( - DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), - false, - )}", + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), + icon: AppAssets.doctor_calendar_icon, + richText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang( + DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), + false, + )}".toText10(isEnglishOnly: true), + ), ), AppCustomChipWidget( labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), @@ -179,7 +183,7 @@ class AppointmentDoctorCard extends StatelessWidget { ) : SizedBox.shrink(); } else { - return patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false + return patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? CustomButton( text: LocaleKeys.cancel.tr(), onPressed: () { diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index 6dcf15f..9f64bde 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -73,6 +73,7 @@ class LoginScreenState extends State { SizedBox(height: 130.h), // Adjusted to sizer unit LocaleKeys.welcomeToDrSulaiman.tr(context: context).toText32(isBold: true, color: AppColors.textColor), SizedBox(height: 32.h), + Localizations.override(context: context, locale: Locale('en', 'US'), child: Container()), // Force English locale for this widget TextInputWidget( labelText: "${LocaleKeys.nationalId.tr(context: context)} / ${LocaleKeys.fileNo.tr(context: context)}", hintText: "xxxxxxxxx", @@ -89,6 +90,7 @@ class LoginScreenState extends State { leadingIcon: AppAssets.student_card, errorMessage: LocaleKeys.enterValidIDorIqama.tr(context: context), hasError: false, + fontFamily: "Poppins", ), SizedBox(height: 16.h), CustomButton( diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index 71bddb3..2a26a72 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -100,6 +100,7 @@ class _RegisterNew extends State { hintText: "xxxxxxxxx", controller: authVm.nationalIdController, focusNode: _nationalIdFocusNode, + keyboardType: TextInputType.number, isEnable: true, prefix: null, isAllowRadius: true, @@ -108,6 +109,7 @@ class _RegisterNew extends State { autoFocus: true, padding: EdgeInsets.symmetric(vertical: 8.h), leadingIcon: AppAssets.student_card, + fontFamily: "Poppins", ).withVerticalPadding(8), Divider(height: 1), TextInputWidget( @@ -125,6 +127,7 @@ class _RegisterNew extends State { selectionType: SelectionTypeEnum.calendar, onCalendarTypeChanged: authVm.onCalenderTypeChange, onChange: authVm.onDobChange, + fontFamily: "Poppins", ).withVerticalPadding(8), ], ), diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index f0fd8cb..231d7d1 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dar import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; +import 'dart:ui' as ui; class SavedLogin extends StatefulWidget { const SavedLogin({super.key}); @@ -93,14 +94,15 @@ class _SavedLogin extends State { ("${LocaleKeys.lastLoginBy.tr()} ${loginType.displayName}") .toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1), - appState.getSelectDeviceByImeiRespModelElement != null - ? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null - ? DateUtil.getFormattedDate( - DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), - "d MMMM, y 'at' HH:mm") - : '--') - .toText16(isBold: true, color: AppColors.textColor) - : SizedBox(), + Directionality( + textDirection: ui.TextDirection.ltr, + child: appState.getSelectDeviceByImeiRespModelElement != null + ? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null + ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") + : '--') + .toText16(isBold: true, color: AppColors.textColor, isEnglishOnly: true) + : SizedBox(), + ), appState.getSelectDeviceByImeiRespModelElement != null ? Container( @@ -115,6 +117,7 @@ class _SavedLogin extends State { CustomButton( text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", onPressed: () { + if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { authVm.loginWithFingerPrintFace(() {}); } else { diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index fa23979..03de833 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -400,30 +400,30 @@ class _BookAppointmentPageState extends State { ), ); }), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.search_by_region_icon, width: 40.h, height: 40.h), - SizedBox(width: 12.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.pharmaLiveCare.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), - "".toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), - ], - ), - ], - ), - Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), - ], - ).onPress(() { - openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION); - }), + // SizedBox(height: 16.h), + // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + // SizedBox(height: 16.h), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Row( + // children: [ + // Utils.buildSvgWithAssets(icon: AppAssets.search_by_region_icon, width: 40.h, height: 40.h), + // SizedBox(width: 12.h), + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // LocaleKeys.pharmaLiveCare.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), + // "".toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + // ], + // ), + // ], + // ), + // Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + // ], + // ).onPress(() { + // openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION); + // }), ], ), ), diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart index 6b2620b..fb6ebe7 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -1,4 +1,3 @@ - import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; @@ -111,7 +110,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { children: [ Row( children: [ - Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.h, height: 32.h, fit: BoxFit.contain), + Utils.buildSvgWithAssets(icon: getLiveCareTypeIcon(immediateLiveCareVM.liveCareSelectedCallType), width: 32.h, height: 32.h, fit: BoxFit.contain), SizedBox(width: 8.h), getLiveCareType(context, immediateLiveCareVM.liveCareSelectedCallType).toText16(isBold: true), ], @@ -185,7 +184,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { children: [ LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.amount!.toText16(isBold: true), AppColors.blackColor, 13, - isSaudiCurrency: immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), + isSaudiCurrency: immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" || + immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال"), ], ).paddingSymmetrical(24.h, 0.h), Row( @@ -194,7 +194,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( immediateLiveCareVM.liveCareImmediateAppointmentFeesList.tax!.toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, - isSaudiCurrency: immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), + isSaudiCurrency: (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" || + immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال")), ], ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), @@ -203,7 +204,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { children: [ SizedBox(width: 150.h, child: Utils.getPaymentMethods()), Utils.getPaymentAmountWithSymbol(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total!.toText24(isBold: true), AppColors.blackColor, 17, - isSaudiCurrency: immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar"), + isSaudiCurrency: (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "sar" || + immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency!.toLowerCase() == "ريال")), ], ).paddingSymmetrical(24.h, 0.h), (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0") @@ -214,18 +216,16 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { if (val) { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context)); - await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime - .now() - .millisecondsSinceEpoch}"); - await immediateLiveCareVM.getPatientLiveCareHistory(); - LoaderBottomSheet.hideLoader(); - if (immediateLiveCareVM.patientHasPendingLiveCareRequest) { - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); + await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}"); + await immediateLiveCareVM.getPatientLiveCareHistory(); + LoaderBottomSheet.hideLoader(); + if (immediateLiveCareVM.patientHasPendingLiveCareRequest) { + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); Navigator.of(context).push( CustomPageRoute( page: ImmediateLiveCarePendingRequestPage(), @@ -429,6 +429,19 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { // // } // } + String getLiveCareTypeIcon(int callType) { + switch (callType) { + case 1: + return AppAssets.livecare_video; + case 2: + return AppAssets.livecare_audio; + case 3: + return AppAssets.livecare_phone; + default: + return AppAssets.livecare_video; + } + } + String getLiveCareType(BuildContext context, int callType) { switch (callType) { case 1: diff --git a/lib/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart b/lib/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart index a1c3f43..e0f6c5c 100644 --- a/lib/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart +++ b/lib/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart @@ -22,7 +22,7 @@ class SelectLiveCareCallType extends StatelessWidget { return Column( children: [ checkInOptionCard( - AppAssets.checkin_location_icon, + AppAssets.livecare_video, LocaleKeys.videoCall.tr(context: context), LocaleKeys.liveVideoCallWithHMGDoctors.tr(context: context), ).onPress(() { @@ -31,16 +31,16 @@ class SelectLiveCareCallType extends StatelessWidget { }), SizedBox(height: 16.h), checkInOptionCard( - AppAssets.checkin_location_icon, + AppAssets.livecare_audio, LocaleKeys.audioCall.tr(context: context), - LocaleKeys.liveVideoCallWithHMGDoctors.tr(context: context), + LocaleKeys.liveAudioCallWithHMGDoctors.tr(context: context), ).onPress(() { Navigator.of(context).pop(); immediateLiveCareViewModel.setLiveCareSelectedCallType(2); }), SizedBox(height: 16.h), checkInOptionCard( - AppAssets.checkin_location_icon, + AppAssets.livecare_phone, LocaleKeys.phoneCall.tr(context: context), LocaleKeys.livePhoneCallWithHMGDoctors.tr(context: context), ).onPress(() { diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index b874e16..035eb22 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart'; @@ -73,7 +74,7 @@ class _ReviewAppointmentPageState extends State { Row( children: [ Image.network( - bookAppointmentsViewModel.selectedDoctor.doctorImageURL!, + bookAppointmentsViewModel.selectedDoctor.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png", width: 50.h, height: 50.h, fit: BoxFit.cover, @@ -83,15 +84,14 @@ class _ReviewAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SizedBox( - width: MediaQuery.of(context).size.width * 0.49, - child: "${bookAppointmentsViewModel.selectedDoctor.doctorTitle} ${bookAppointmentsViewModel.selectedDoctor.name}" - .toString() - .toText16(isBold: true, maxlines: 1), - ), + "${bookAppointmentsViewModel.selectedDoctor.doctorTitle ?? LocaleKeys.Dr.tr(context: context)} ${bookAppointmentsViewModel.selectedDoctor.name}" + .toString() + .toText16(isBold: true, maxlines: 1), + SizedBox(width: 12.w), Image.network( - bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!, + bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", width: 20.h, height: 15.h, fit: BoxFit.cover, @@ -120,7 +120,7 @@ class _ReviewAppointmentPageState extends State { ), AppCustomChipWidget( labelText: - "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDate}", + "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDisplayDate}", ), AppCustomChipWidget( labelText: @@ -242,11 +242,16 @@ class _ReviewAppointmentPageState extends State { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointmentShare.tr(context: context)); await bookAppointmentsViewModel.getWalkInPatientShareAppointment(onSuccess: (val) { LoaderBottomSheet.hideLoader(); - Navigator.of(context).push( - CustomPageRoute( - page: WaitingAppointmentPaymentPage(), - ), - ); + if (bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax != 0 || + bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax != 0.0) { + Navigator.of(context).push( + CustomPageRoute( + page: WaitingAppointmentPaymentPage(), + ), + ); + } else { + initiateBookAppointmentForWalkInNoPatientShare(); + } }, onError: (err) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( @@ -259,6 +264,49 @@ class _ReviewAppointmentPageState extends State { }); } + void initiateBookAppointmentForWalkInNoPatientShare() async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingWaitingAppointment.tr(context: context)); + await bookAppointmentsViewModel.insertSpecificAppointmentForWalkIn(onSuccess: (val) async { + String appointmentNo = val.data['AppointmentNo'].toString(); + LoaderBottomSheet.hideLoader(); + sendCheckInRequest(bookAppointmentsViewModel.waitingAppointmentNFCCode, appointmentNo, context); + }); + } + + void sendCheckInRequest(String scannedCode, String appointmentNo, BuildContext context) async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.processingCheckIn.tr(context: context)); + PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel = PatientAppointmentHistoryResponseModel( + appointmentNo: appointmentNo, + clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID, + projectID: bookAppointmentsViewModel.selectedDoctor.projectID, + ); + await myAppointmentsViewModel.sendCheckInNfcRequest( + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + scannedCode: scannedCode, + checkInType: 2, + onSuccess: (apiResponse) { + LoaderBottomSheet.hideLoader(); + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.getPatientAppointments(true, false); + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: apiResponse.data["SuccessMsg"]), callBackFunc: () { + Navigator.of(context).pop(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }, isFullScreen: false); + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.error.tr(context: context), child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { + Navigator.of(context).pop(); + }, isFullScreen: false); + }, + ); + } + void initiateBookAppointment() async { LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: LocaleKeys.bookingYourAppointment.tr(context: context)); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); @@ -286,8 +334,20 @@ class _ReviewAppointmentPageState extends State { } else { //TODO: Add patient Derma package check API Here await bookAppointmentsViewModel.insertSpecificAppointment(onError: (err) { - print(err.data["ErrorEndUserMessage"]); + // print(err.data["ErrorEndUserMessage"]); LoadingUtils.hideFullScreenLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () { + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + // Navigator.pop(context); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); }, onSuccess: (apiResp) async { LoadingUtils.hideFullScreenLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index ac40d69..cad8e2e 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -261,8 +261,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmiCategory"], calories: calculatedResult["bmiResult"], - description: - 'Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.', + description: LocaleKeys.bmiNormalDescription.tr(), color: Colors.green, icon: Icons.scale, hide: true, @@ -272,8 +271,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmiCategory"], calories: calculatedResult["bmiResult"], - description: - 'BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.', + description: LocaleKeys.bmiUnderweightDescription.tr(), color: Colors.orange, hide: true, icon: Icons.trending_down, @@ -283,8 +281,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmiCategory"], calories: calculatedResult["bmiResult"], - description: - 'BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.', + description: LocaleKeys.bmiOverweightDescription.tr(), color: Colors.red, hide: true, icon: Icons.warning_rounded, @@ -295,8 +292,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["calorieRange"], calories: calculatedResult["calories"], - description: - 'Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 2–4 weeks.', + description: LocaleKeys.caloriesMaintenanceDescription.tr(), color: Colors.green, icon: Icons.scale, ), @@ -305,8 +301,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["calorieRange"], calories: calculatedResult["calories"], - description: - 'A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.25–0.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.', + description: LocaleKeys.caloriesModerateDeficitDescription.tr(), color: Colors.orange, icon: Icons.trending_down, ), @@ -315,19 +310,59 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["calorieRange"], calories: calculatedResult["calories"], - description: - 'A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.', + description: LocaleKeys.caloriesAggressiveDeficitDescription.tr(), color: Colors.red, icon: Icons.warning_rounded, ), ] + ] else if (type == HealthCalculatorsTypeEnum.bodyFat) ...[ + if (calculatedResult["fatCategory"] == "Essential Fat" || calculatedResult["fatCategory"] == "Athletes") ...[ + calorieWidget( + title: calculatedResult["fatCategory"], + calories: calculatedResult["fatPercentage"], + description: LocaleKeys.bodyFatEssentialDescription.tr(), + color: Colors.green, + hide: true, + icon: Icons.fitness_center, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["fatCategory"] == "Fitness") ...[ + calorieWidget( + title: calculatedResult["fatCategory"], + calories: calculatedResult["fatPercentage"], + description: LocaleKeys.bodyFatFitnessDescription.tr(), + color: Colors.green, + hide: true, + icon: Icons.fitness_center, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["fatCategory"] == "Average" || calculatedResult["fatCategory"] == "Acceptable") ...[ + calorieWidget( + title: calculatedResult["fatCategory"], + calories: calculatedResult["fatPercentage"], + description: LocaleKeys.bodyFatAverageDescription.tr(), + color: Colors.orange, + hide: true, + icon: Icons.accessibility_new, + ), + SizedBox(height: 16.h), + ] else if (calculatedResult["fatCategory"] == "Obese" || calculatedResult["fatCategory"] == "Overweight") ...[ + calorieWidget( + title: calculatedResult["fatCategory"], + calories: calculatedResult["fatPercentage"], + description: LocaleKeys.bodyFatObeseDescription.tr(), + color: Colors.red, + hide: true, + icon: Icons.warning_rounded, + ), + SizedBox(height: 16.h), + ] ] else if (type == HealthCalculatorsTypeEnum.bmr) ...[ if (calculatedResult["bmrRange"] == "Normal") ...[ calorieWidget( title: calculatedResult["bmrRange"], calories: calculatedResult["bmr"], - description: - 'Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.', + description: LocaleKeys.bmrNormalDescription.tr(), color: Colors.green, hide: true, icon: Icons.scale, @@ -337,8 +372,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmrRange"], calories: calculatedResult["bmr"], - description: - 'Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.', + description: LocaleKeys.low.tr(), color: Colors.orange, hide: true, icon: Icons.trending_down, @@ -348,8 +382,7 @@ class _SelectClinicPageState extends State { calorieWidget( title: calculatedResult["bmrRange"], calories: calculatedResult["bmr"], - description: - 'Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.', + description: LocaleKeys.bmrHighDescription.tr(), color: Colors.red, hide: true, icon: Icons.warning_rounded, @@ -915,9 +948,16 @@ class _SelectClinicPageState extends State { Widget _buildStatusIcon(String title) { final key = title.trim(); - if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') { + if (key == 'Maintenance Calories' || + key == 'Normal' || + key == 'Ideal Weight' || + key == 'Moderate Crabs' || + key == 'USDA Guidelines' || + key == 'Essential Fat' || + key == 'Athletes' || + key == 'Fitness') { return Utils.buildSvgWithAssets(icon: AppAssets.checkmark_icon, width: 24.w, height: 24.h); - } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') { + } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet' || key == 'Average' || key == 'Acceptable') { return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_yellow, width: 24.w, height: 24.h); } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") { return Utils.buildSvgWithAssets(icon: AppAssets.trade_down_red, width: 24.w, height: 24.h); @@ -928,9 +968,16 @@ class _SelectClinicPageState extends State { Color getTextColor(String title) { final key = title.trim(); - if (key == 'Maintenance Calories' || key == 'Normal' || key == 'Ideal Weight' || key == 'Moderate Crabs' || key == 'USDA Guidelines') { + if (key == 'Maintenance Calories' || + key == 'Normal' || + key == 'Ideal Weight' || + key == 'Moderate Crabs' || + key == 'USDA Guidelines' || + key == 'Essential Fat' || + key == 'Athletes' || + key == 'Fitness') { return AppColors.successColor; - } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet') { + } else if (key == 'Moderate Deficit' || key == 'Low' || key == 'Underweight' || key == 'Very Low Crabs' || key == 'Low Crabs' || key == 'Zone Diet' || key == 'Average' || key == 'Acceptable') { return AppColors.warningColor; } else if (key == 'Aggressive Deficit' || key == 'High' || key == 'Overweight' || key == "Obese") { return AppColors.errorColor; @@ -1156,7 +1203,8 @@ class _SelectClinicPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ bookAppointmentsViewModel.patientDentalPlanEstimationList[index].procedureName!.toText12(isBold: true), - AppCustomChipWidget(icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} ${LocaleKeys.mins.tr(context: context)}"), + AppCustomChipWidget( + icon: AppAssets.appointment_time_icon, labelText: "${bookAppointmentsViewModel.totalTimeNeededForDentalProcedure} ${LocaleKeys.mins.tr(context: context)}"), ], ); }, diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index f8a0ad9..1fc9d9a 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -75,7 +75,7 @@ class _SelectDoctorPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: LocaleKeys.chooseDoctor.tr(), + title: LocaleKeys.chooseDoctor.tr(context: context), // bottomChild: Container( // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))), // padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h), diff --git a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart index ca60e9e..bb347e5 100644 --- a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart +++ b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart @@ -640,6 +640,8 @@ class _WaitingAppointmentPaymentPageState extends State { child: selectedDateDisplay.toText16(weight: FontWeight.w500), ), //TODO: Add Next Day Span here - SizedBox( - height: 100.h, - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Wrap( - direction: Axis.horizontal, - alignment: WrapAlignment.start, + dayEvents.isNotEmpty + ? SizedBox( + height: 100.h, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.start, spacing: 6.h, runSpacing: 6.h, children: List.generate( @@ -168,10 +170,13 @@ class _AppointmentCalendarState extends State { ), ), ), - ), + ) + : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noFreeSlot.tr(context: context)), + SizedBox(height: 16.h), CustomButton( text: LocaleKeys.select.tr(context: context), + isDisabled: dayEvents.isEmpty, onPressed: () async { if (appState.isAuthenticated) { if(selectedTime == LocaleKeys.waitingAppointment.tr(context: context)){ @@ -186,7 +191,7 @@ class _AppointmentCalendarState extends State { ); } else { bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!); - bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime); + bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay); Navigator.of(context).pop(); Navigator.of(context).push( CustomPageRoute( @@ -304,11 +309,14 @@ class _AppointmentCalendarState extends State { Map timeSlot = {"isoTime": dayEvents[i].isoTime, "start": dayEvents[i].start.toString(), "end": dayEvents[i].end.toString(), "vidaDate": dayEvents[i].vidaDate}; timeList.add(timeSlot); } - selectedTime = dayEvents[selectedButtonIndex].isoTime!; + if (dayEvents.isNotEmpty) { + selectedTime = dayEvents[selectedButtonIndex].isoTime!; + } + setState(() {}); } void _onDaySelected(DateTime day) { - final DateFormat formatter = DateFormat('yyyy-MM-dd'); + final DateFormat formatter = DateFormat('yyyy-MM-dd', "en-US"); setState(() { selectedDateDisplay = DateUtil.getMonthDayYearDateFormatted(day); selectedNextDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day.add(Duration(days: 1)), "en"); diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index e2009bb..ddb43e9 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -38,7 +38,7 @@ class DoctorCard extends StatelessWidget { hasShadow: false, ), child: Padding( - padding: EdgeInsets.only(top: 14.h,bottom: 20.h), + padding: EdgeInsets.only(top: 14.h, bottom: 20.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -91,9 +91,7 @@ class DoctorCard extends StatelessWidget { children: [ SizedBox( width: MediaQuery.of(context).size.width * 0.55, - child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}") - .toString() - .toText16(isBold: true, maxlines: 1), + child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}").toString().toText16(isBold: true, maxlines: 1), ).toShimmer2(isShow: isLoading), ], ), @@ -135,12 +133,14 @@ class DoctorCard extends StatelessWidget { ).toShimmer2(isShow: isLoading), bookAppointmentsViewModel.isNearestAppointmentSelected ? doctorsListResponseModel.nearestFreeSlot != null - ? AppCustomChipWidget( - labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)), - backgroundColor: AppColors.successColor, - textColor: AppColors.whiteColor, - ).toShimmer2(isShow: isLoading) - : SizedBox.shrink() + ? AppCustomChipWidget( + // labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)), + richText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)) + .toText10(isEnglishOnly: true, color: AppColors.whiteColor), + backgroundColor: AppColors.successColor, + textColor: AppColors.whiteColor, + ).toShimmer2(isShow: isLoading) + : SizedBox.shrink() : SizedBox.shrink(), ], ), @@ -149,8 +149,7 @@ class DoctorCard extends StatelessWidget { ), Expanded( flex: 1, - child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown) - .toShimmer2(isShow: isLoading), + child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading), ), ], ), diff --git a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart index 9b78eca..840d38c 100644 --- a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart +++ b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart @@ -1,4 +1,5 @@ import 'dart:developer'; +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -22,7 +23,7 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/map/location_map_widget.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; import 'package:provider/provider.dart'; class CmcSelectionReviewPage extends StatefulWidget { @@ -319,16 +320,27 @@ class _CmcSelectionReviewPageState extends State { CmcUiSelectionHelper.showHospitalSelectionBottomSheet(context: context, onHospitalSelected: (hospital) => context.pop()); } - void _launchDirections(HospitalsModel selectedHospital) { + void _launchDirections(HospitalsModel selectedHospital) async { final double lat = double.parse(selectedHospital.latitude ?? "0.0"); final double lng = double.parse(selectedHospital.longitude ?? "0.0"); if (lat != 0.0 && lng != 0.0) { - MapsLauncher.launchCoordinates( - lat, - lng, - selectedHospital.name ?? "Hospital", - ); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(lat, lng), + title: selectedHospital.name ?? "Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(lat, lng), + title: selectedHospital.name ?? "Hospital", + ); + }); + // MapsLauncher.launchCoordinates( + // lat, + // lng, + // selectedHospital.name ?? "Hospital", + // ); } } diff --git a/lib/presentation/contact_us/contact_us.dart b/lib/presentation/contact_us/contact_us.dart index 40d42d5..fd0a439 100644 --- a/lib/presentation/contact_us/contact_us.dart +++ b/lib/presentation/contact_us/contact_us.dart @@ -34,7 +34,7 @@ class ContactUs extends StatelessWidget { return Column( children: [ checkInOptionCard( - AppAssets.checkin_location_icon, + AppAssets.location, LocaleKeys.findUs.tr(), LocaleKeys.viewNearestHMGLocations.tr(), ).onPress(() { @@ -50,13 +50,14 @@ class ContactUs extends StatelessWidget { }), SizedBox(height: 16.h), checkInOptionCard( - AppAssets.checkin_location_icon, + AppAssets.feedbackFill, LocaleKeys.feedback.tr(), LocaleKeys.provideFeedbackOnServices.tr(), ).onPress(() { contactUsViewModel.setSelectedFeedbackType( FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'), ); + contactUsViewModel.setIsSendFeedbackTabSelected(true); Navigator.pop(context); Navigator.of(context).push( CustomPageRoute( @@ -66,7 +67,7 @@ class ContactUs extends StatelessWidget { }), SizedBox(height: 16.h), checkInOptionCard( - AppAssets.checkin_location_icon, + AppAssets.ask_doctor_icon, LocaleKeys.liveChat.tr(), LocaleKeys.liveChatWithHMG.tr(), ).onPress(() { @@ -94,7 +95,7 @@ class ContactUs extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.buildSvgWithAssets(icon: icon, width: 40.h, height: 40.h, fit: BoxFit.fill), + Utils.buildSvgWithAssets(icon: icon, width: 24.h, height: 24.h, fit: BoxFit.fill, iconColor: AppColors.textColor), SizedBox(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/presentation/contact_us/feedback_page.dart b/lib/presentation/contact_us/feedback_page.dart index 8f10bc5..93954af 100644 --- a/lib/presentation/contact_us/feedback_page.dart +++ b/lib/presentation/contact_us/feedback_page.dart @@ -1,5 +1,6 @@ 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'; @@ -13,6 +14,7 @@ import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dar import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/widgets/feedback_appointment_selection.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -24,6 +26,7 @@ import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; +import 'dart:ui' as ui; class FeedbackPage extends StatelessWidget { FeedbackPage({super.key}); @@ -34,10 +37,13 @@ class FeedbackPage extends StatelessWidget { final TextEditingController subjectTextController = TextEditingController(); final TextEditingController messageTextController = TextEditingController(); + late AppState appState; + @override Widget build(BuildContext context) { contactUsViewModel = Provider.of(context, listen: false); medicalFileViewModel = Provider.of(context, listen: false); + appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, contactUsVM, child) { @@ -67,11 +73,12 @@ class FeedbackPage extends StatelessWidget { ), ), ), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - customBorder: BorderRadius.only( - topLeft: Radius.circular(24.h), + contactUsViewModel.isSendFeedbackTabSelected + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), topRight: Radius.circular(24.h), ), hasShadow: true, @@ -126,7 +133,8 @@ class FeedbackPage extends StatelessWidget { iconColor: AppColors.whiteColor, iconSize: 20.h, ).paddingSymmetrical(24.h, 24.h), - ), + ) + : SizedBox.shrink(), ], ); }), @@ -389,7 +397,99 @@ class FeedbackPage extends StatelessWidget { ], ); } else { - return Container(); + return getIt.get().isAuthenticated + ? Column( + children: [ + SizedBox(height: 24.h), + ListView.builder( + itemCount: contactUsViewModel.isCOCItemsListLoading + ? 4 + : contactUsViewModel.cocItemsList.isEmpty + ? 1 + : contactUsViewModel.cocItemsList.length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: EdgeInsetsGeometry.zero, + itemBuilder: (context, index) { + return contactUsViewModel.isCOCItemsListLoading + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ) + : contactUsViewModel.cocItemsList.isNotEmpty + ? 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, + child: Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppCustomChipWidget( + labelText: appState.isArabic() ? contactUsViewModel.cocItemsList[index].statusAr : contactUsViewModel.cocItemsList[index].status, + // backgroundColor: AppColors.warningColorYellow, + backgroundColor: (contactUsViewModel.cocItemsList[index].statusId == 7 || contactUsViewModel.cocItemsList[index].statusId == 8) + ? AppColors.primaryRedColor + : (contactUsViewModel.cocItemsList[index].statusId == 10 || + contactUsViewModel.cocItemsList[index].statusId == 6 || + contactUsViewModel.cocItemsList[index].statusId == 9) + ? AppColors.successColor + : AppColors.warningColorYellow, + textColor: AppColors.whiteColor, + ), + SizedBox(height: 8.h), + "${LocaleKeys.requestID.tr(context: context)} ${contactUsViewModel.cocItemsList[index].itemID}".toText16(weight: FontWeight.bold), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 4.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: contactUsViewModel.cocItemsList[index].date!.split(" ")[0], + icon: AppAssets.calendarGrey, + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + ), + AppCustomChipWidget( + labelText: contactUsViewModel.cocItemsList[index].formType, + icon: AppAssets.ask_doctor_icon, + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + ), + ], + ), + SizedBox(height: 8.h), + "${contactUsViewModel.cocItemsList[index].cOCTitle}" + .toText16(weight: FontWeight.bold, fontFamily: Utils.isArabicText(contactUsViewModel.cocItemsList[index].cOCTitle ?? "") ? "GESSTwo" : "Poppins"), + SizedBox(height: 8.h), + "${contactUsViewModel.cocItemsList[index].detail}".toText14( + weight: FontWeight.w500, + color: AppColors.textColorLight, + fontFamily: Utils.isArabicText(contactUsViewModel.cocItemsList[index].detail ?? "") ? "GESSTwo" : "Poppins"), + ], + ), + ).paddingOnly(bottom: 16.h), + ), + ), + ), + ) + : Utils.getNoDataWidget(context); + }), + ], + ) + : Utils.getNoDataWidget(context); } } } diff --git a/lib/presentation/contact_us/widgets/find_us_item_card.dart b/lib/presentation/contact_us/widgets/find_us_item_card.dart index 6e295e0..1774404 100644 --- a/lib/presentation/contact_us/widgets/find_us_item_card.dart +++ b/lib/presentation/contact_us/widgets/find_us_item_card.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -10,7 +12,7 @@ import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_h import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; import 'package:url_launcher/url_launcher.dart'; class FindUsItemCard extends StatelessWidget { @@ -82,8 +84,18 @@ class FindUsItemCard extends StatelessWidget { iconColor: AppColors.whiteColor, backgroundColor: AppColors.textColor.withValues(alpha: 0.8), textColor: AppColors.whiteColor, - onChipTap: () { - MapsLauncher.launchCoordinates(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0"), getHMGLocationsModel.locationName!); + onChipTap: () async { + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0")), + title: getHMGLocationsModel.locationName ?? "Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0")), + title: getHMGLocationsModel.locationName ?? "Hospital", + ); + }); }, ), SizedBox(width: 4.w), diff --git a/lib/presentation/e_referral/widget/e_referral_patient_info.dart b/lib/presentation/e_referral/widget/e_referral_patient_info.dart index 0cc5c7e..61b92c7 100644 --- a/lib/presentation/e_referral/widget/e_referral_patient_info.dart +++ b/lib/presentation/e_referral/widget/e_referral_patient_info.dart @@ -140,6 +140,7 @@ class PatientInformationStepState extends State { controller: _phoneController, padding: const EdgeInsets.all(8), keyboardType: TextInputType.number, + fontFamily: "Poppins", onChange: (value) { formManager.updatePatientPhone(value ?? ''); }, diff --git a/lib/presentation/e_referral/widget/search_e_referral_form.dart b/lib/presentation/e_referral/widget/search_e_referral_form.dart index fa3ae19..33697d5 100644 --- a/lib/presentation/e_referral/widget/search_e_referral_form.dart +++ b/lib/presentation/e_referral/widget/search_e_referral_form.dart @@ -73,6 +73,7 @@ class SearchEReferralFormFormState extends State { hintText: formManager.searchCriteria == 0 ? "Enter Identification Number" : "Enter Referral Number", labelText: formManager.searchCriteria == 0 ? "Identification Number" : "Referral Number", keyboardType: TextInputType.number, + fontFamily: "Poppins", errorMessage: formManager.errors.searchValue, hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.searchValue), onChange: (value) { @@ -94,6 +95,7 @@ class SearchEReferralFormFormState extends State { controller: _phoneController, padding: const EdgeInsets.all(8), keyboardType: TextInputType.number, + fontFamily: "Poppins", onChange: (value) { formManager.updateSearchPhone(value ?? ''); // _validateForm(formManager); diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart index 999a43e..bbaa5f5 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart @@ -105,7 +105,9 @@ class ErOnlineCheckinHome extends StatelessWidget { ); }); }, - onError: (err) {}); + onError: (err) { + LoaderBottomSheet.hideLoader(); + }); }); }, onCancel: () {}); }); diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart index 3510197..2645679 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/features/emergency_services/emergency_servic import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -445,7 +446,14 @@ class _ErOnlineCheckinPaymentPageState extends State showCommonBottomSheetWithoutHeight( context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)), - callBackFunc: () {}, + callBackFunc: () { + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }, isFullScreen: false, isCloseButtonVisible: true, ); @@ -458,7 +466,14 @@ class _ErOnlineCheckinPaymentPageState extends State showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), - callBackFunc: () {}, + callBackFunc: () { + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }, isFullScreen: false, isCloseButtonVisible: true, ); diff --git a/lib/presentation/habib_wallet/recharge_wallet_page.dart b/lib/presentation/habib_wallet/recharge_wallet_page.dart index 22e854a..78608e8 100644 --- a/lib/presentation/habib_wallet/recharge_wallet_page.dart +++ b/lib/presentation/habib_wallet/recharge_wallet_page.dart @@ -98,11 +98,12 @@ class _RechargeWalletPageState extends State { isBorderAllowed: false, isAllowLeadingIcon: true, autoFocus: true, - fontSize: 40, + fontSize: 40.f, padding: EdgeInsets.symmetric(horizontal: 8.h, vertical: 0.h), focusNode: textFocusNode, isWalletAmountInput: true, keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), + fontFamily: "Poppins", // leadingIcon: AppAssets.student_card, ), ), @@ -217,12 +218,11 @@ class _RechargeWalletPageState extends State { keyboardType: TextInputType.text, isEnable: true, prefix: null, - autoFocus: true, + autoFocus: false, isAllowRadius: true, isBorderAllowed: false, isAllowLeadingIcon: true, leadingIcon: AppAssets.notes_icon, - errorMessage: LocaleKeys.enterValidIDorIqama.tr(context: context), hasError: false, ), SizedBox(height: 8.h), diff --git a/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart index 4a83f51..8cccf03 100644 --- a/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart +++ b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart @@ -52,8 +52,8 @@ class _HealthCalculatorDetailedPageState extends State(); Navigator.of(context).push(CustomPageRoute( page: SelectClinicPage(calculatorType: widget.calculatorType, calculatedResult: calculatedResult), )); @@ -174,69 +172,58 @@ class _HealthCalculatorDetailedPageState extends State { children: [ Radio( value: unit, - groupValue: selectedHeightUnit, + groupValue: selectedHipUnit, activeColor: AppColors.errorColor, onChanged: (String? value) { if (value == null) return; diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index ce2c537..f0bf08f 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -29,6 +29,7 @@ import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dar import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart'; +import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -52,14 +53,7 @@ class ServicesPage extends StatelessWidget { late MedicalFileViewModel medicalFileViewModel; late final List hmgServices = [ - HmgServicesComponentModel( - 11, - LocaleKeys.emergencyServices.tr(), - "", - AppAssets.emergency_services_icon, - bgColor: AppColors.primaryRedColor, - true, - route: null, onTap: () async { + HmgServicesComponentModel(11, LocaleKeys.emergencyServices.tr(), "", AppAssets.emergency_services_icon, bgColor: AppColors.primaryRedColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().flushData(); getIt.get().getTransportationOrders( @@ -87,8 +81,7 @@ class ServicesPage extends StatelessWidget { true, route: AppRoutes.bookAppointmentPage, ), - HmgServicesComponentModel( - 5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { + HmgServicesComponentModel(5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.comprehensiveCheckupPage); } else { @@ -140,8 +133,7 @@ class ServicesPage extends StatelessWidget { ); }, ), - HmgServicesComponentModel( - 11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { + HmgServicesComponentModel(11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.eReferralPage); } else { @@ -150,13 +142,27 @@ class ServicesPage extends StatelessWidget { }), HmgServicesComponentModel( 3, - "Blood Donation".needTranslation, + LocaleKeys.bloodDonation.tr(context: getIt.get().navigatorKey.currentContext!), "".needTranslation, AppAssets.blood_donation_icon, bgColor: AppColors.bloodDonationCardColor, true, route: AppRoutes.bloodDonationPage, ), + HmgServicesComponentModel( + 113, + LocaleKeys.servicePriceList.tr(context: getIt.get().navigatorKey.currentContext!), + "".needTranslation, + AppAssets.saudi_riyal_icon, + bgColor: AppColors.textColor, + false, + route: null, + onTap: () { + Navigator.of(getIt.get().navigatorKey.currentContext!).push( + CustomPageRoute(page: ServicesPriceListPage()), + ); + }, + ) // HmgServicesComponentModel( // 3, // "My Child Vaccine".needTranslation, @@ -302,6 +308,9 @@ class ServicesPage extends StatelessWidget { Widget build(BuildContext context) { bloodDonationViewModel = Provider.of(context); medicalFileViewModel = Provider.of(context); + + hmgServices.removeWhere((element) => Utils.havePrivilege(element.action) == false); + return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( @@ -382,7 +391,7 @@ class ServicesPage extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h), LocaleKeys.habibWallet.tr().toText14(weight: FontWeight.w600, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), ], ), Spacer(), @@ -397,19 +406,19 @@ class ServicesPage extends StatelessWidget { ? CustomButton( height: 40.h, icon: AppAssets.recharge_icon, - iconSize: 24.w, - iconColor: AppColors.infoColor, - textColor: AppColors.infoColor, - text: LocaleKeys.recharge.tr(), - borderWidth: 0.w, - fontWeight: FontWeight.w500, - borderColor: Colors.transparent, - backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), - padding: EdgeInsets.all(8.w), - fontSize: 14.f, - onPressed: () { - Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); - }, + iconSize: 24.w, + iconColor: AppColors.infoColor, + textColor: AppColors.infoColor, + text: LocaleKeys.recharge.tr(), + borderWidth: 0.w, + fontWeight: FontWeight.w500, + borderColor: Colors.transparent, + backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), + padding: EdgeInsets.all(8.w), + fontSize: 14.f, + onPressed: () { + Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); + }, ) : SizedBox.shrink(), ], @@ -442,67 +451,67 @@ class ServicesPage extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h), LocaleKeys.medicalFile.tr().toText16(weight: FontWeight.w600, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: AppAssets.arrow_forward), + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), ], ), Spacer(), getIt.get().isAuthenticated ? Wrap( spacing: -12.h, - // runSpacing: 0.h, - children: [ - Utils.buildImgWithAssets( - icon: AppAssets.babyGirlImg, - height: 32.h, - width: 32.w, - border: 1, - fit: BoxFit.contain, - borderRadius: 50.r, - ), - Utils.buildImgWithAssets( - icon: AppAssets.femaleImg, - height: 32.h, - width: 32.w, - border: 1, - borderRadius: 50.r, - fit: BoxFit.contain, - ), - Utils.buildImgWithAssets( - icon: AppAssets.maleImg, - height: 32.h, - width: 32.w, - border: 1, - borderRadius: 50.r, - fit: BoxFit.contain, - ), - ], + // runSpacing: 0.h, + children: [ + Utils.buildImgWithAssets( + icon: AppAssets.babyGirlImg, + height: 32.h, + width: 32.w, + border: 1, + fit: BoxFit.contain, + borderRadius: 50.r, + ), + Utils.buildImgWithAssets( + icon: AppAssets.femaleImg, + height: 32.h, + width: 32.w, + border: 1, + borderRadius: 50.r, + fit: BoxFit.contain, + ), + Utils.buildImgWithAssets( + icon: AppAssets.maleImg, + height: 32.h, + width: 32.w, + border: 1, + borderRadius: 50.r, + fit: BoxFit.contain, + ), + ], ) : LocaleKeys.loginToViewMedicalFile.tr().toText12(fontWeight: FontWeight.w500, maxLine: 2), Spacer(), getIt.get().isAuthenticated ? CustomButton( height: 40.h, - icon: AppAssets.add_icon, - iconSize: 24.h, - iconColor: AppColors.primaryRedColor, - textColor: AppColors.primaryRedColor, - text: LocaleKeys.addMember.tr(), - borderWidth: 0.w, - fontWeight: FontWeight.w500, - borderColor: Colors.transparent, - backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), - padding: EdgeInsets.all(8.w), - fontSize: 14.f, - onPressed: () { - DialogService dialogService = getIt.get(); - medicalFileViewModel.clearAuthValues(); - dialogService.showAddFamilyFileSheet( - label: LocaleKeys.addFamilyMember.tr(), - message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(), - onVerificationPress: () { - medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); - }); - }, + icon: AppAssets.add_icon, + iconSize: 24.h, + iconColor: AppColors.primaryRedColor, + textColor: AppColors.primaryRedColor, + text: LocaleKeys.addMember.tr(), + borderWidth: 0.w, + fontWeight: FontWeight.w500, + borderColor: Colors.transparent, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), + padding: EdgeInsets.all(8.w), + fontSize: 14.f, + onPressed: () { + DialogService dialogService = getIt.get(); + medicalFileViewModel.clearAuthValues(); + dialogService.showAddFamilyFileSheet( + label: LocaleKeys.addFamilyMember.tr(), + message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(), + onVerificationPress: () { + medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); + }); + }, ) : SizedBox.shrink(), ], @@ -667,7 +676,7 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.w), - LocaleKeys.hmgContact.tr().toText14(weight: FontWeight.w500) + Expanded(child: LocaleKeys.hmgContact.tr().toText14(weight: FontWeight.w500)) ], ), ), @@ -682,6 +691,58 @@ class ServicesPage extends StatelessWidget { }), ) ], + ), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.privacy_terms, width: 32.w, height: 32.h, fit: BoxFit.contain, iconColor: AppColors.blackColor), + SizedBox(width: 8.w), + Expanded(child: LocaleKeys.termsConditoins.tr().toText14(weight: FontWeight.w500)) + ], + ), + ), + ).onPress(() { + Utils.openWebView( + url: 'https://hmg.com/en/Pages/Terms.aspx', + ); + }), + ), + SizedBox(width: 16.w), + Expanded( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.privacy_terms, width: 32.w, height: 32.h, fit: BoxFit.contain, iconColor: AppColors.blackColor), + SizedBox(width: 8.w), + Expanded(child: LocaleKeys.privacyPolicy.tr().toText14(weight: FontWeight.w500)) + ], + ), + ), + ).onPress(() { + Utils.openWebView( + url: 'https://hmg.com/en/Pages/Privacy.aspx', + ); + }), + ) + ], ) ], ).paddingSymmetrical(24.w, 0), diff --git a/lib/presentation/home/app_update_page.dart b/lib/presentation/home/app_update_page.dart new file mode 100644 index 0000000..5e6481b --- /dev/null +++ b/lib/presentation/home/app_update_page.dart @@ -0,0 +1,96 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:in_app_update/in_app_update.dart'; +import 'package:lottie/lottie.dart'; + +class AppUpdatePage extends StatelessWidget { + AppUpdatePage({super.key, required this.appUpdateMessage}); + + String appUpdateMessage; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.whiteColor, + body: Column( + children: [ + Expanded( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + children: [ + SizedBox(height: 120.h), + Align( + alignment: Alignment.topCenter, + child: Transform.flip( + flipX: getIt.get().isArabic() ? true : false, + child: Lottie.asset(AppAnimations.onboarding_1, + repeat: true, reverse: false, frameRate: FrameRate(60), width: MediaQuery.sizeOf(context).width - 50.w, height: MediaQuery.sizeOf(context).width - 50.w), + ), + ), + SizedBox(height: 50.h), + appUpdateMessage.toText16(weight: FontWeight.w500), + ], + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: CustomButton( + text: LocaleKeys.updateAppNow.tr(context: context), + onPressed: () { + openAppUpdateLink(); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + borderRadius: 12.r, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 56.h, + ), + ).paddingAll(24.h), + ], + ), + ); + } + + openAppUpdateLink() { + if (Platform.isAndroid) { + // _launchURL("https://play.google.com/store/apps/details?id=com.ejada.hmg"); + InAppUpdate.checkForUpdate().then((info) { + print("checkForUpdate!!!"); + print(info.toString()); + if (info.immediateUpdateAllowed) { + print("Immediate Allowed!!!"); + InAppUpdate.performImmediateUpdate().then((value) {}).catchError((e) => print(e.toString())); + } + }).catchError((e) { + print(e.toString()); + 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", + ); + } + } +} diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index a8a64fa..e8482bf 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -460,7 +460,7 @@ class _LandingPageState extends State { return emergencyServicesVM.patientHasAdvanceERBalance ? Column( children: [ - SizedBox(height: 4.h), + SizedBox(height: 16.h), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -490,12 +490,15 @@ class _LandingPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.youHaveEROnlineCheckInRequest.tr(context: context).toText12(isBold: true), - Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, - iconColor: AppColors.blackColor, - width: 20.h, - height: 15.h, - fit: BoxFit.contain, + 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, + ), ), ], ), @@ -509,10 +512,10 @@ class _LandingPageState extends State { SizedBox(height: 12.h), ], ) - : SizedBox(height: 12.h); + : SizedBox(height: 0.h); }, ), - SizedBox(height: 24.h), + SizedBox(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index e09b162..ed48058 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -52,42 +52,45 @@ class HabibWalletCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ LocaleKeys.habibWallet.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), - Container( - height: 40.h, - width: 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.textColor, - borderRadius: 8.h, - ), - child: Padding( - padding: EdgeInsets.all(8.h), - child: Utils.buildSvgWithAssets( - icon: AppAssets.show_icon, - width: 12.h, - height: 12.h, - fit: BoxFit.contain, - ), - ), - ), - ], - ), + // Container( + // height: 40.h, + // width: 40.h, + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.textColor, + // borderRadius: 8.h, + // ), + // child: Padding( + // padding: EdgeInsets.all(8.h), + // child: Utils.buildSvgWithAssets( + // icon: AppAssets.show_icon, + // width: 12.h, + // height: 12.h, + // fit: BoxFit.contain, + // ), + // ), + // ), + // ], + // ), SizedBox(height: 4.h), Consumer(builder: (context, habibWalletVM, child) { return Row( children: [ Utils.buildSvgWithAssets( icon: AppAssets.saudi_riyal_icon, - iconColor: AppColors.dividerColor, + iconColor: AppColors.inputLabelTextColor, width: 24.h, height: 24.h, fit: BoxFit.contain, ), SizedBox(width: 8.h), - habibWalletVM.habibWalletAmount.toString().toText32(isBold: true).toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), + habibWalletVM.habibWalletAmount + .toString() + .toText32(isBold: true, isEnglishOnly: true) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), ], ); }), diff --git a/lib/presentation/home_health_care/hhc_selection_review_page.dart b/lib/presentation/home_health_care/hhc_selection_review_page.dart index ae2430a..9a9342d 100644 --- a/lib/presentation/home_health_care/hhc_selection_review_page.dart +++ b/lib/presentation/home_health_care/hhc_selection_review_page.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -19,7 +21,7 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/map/location_map_widget.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; import 'package:provider/provider.dart'; class HhcSelectionReviewPage extends StatefulWidget { @@ -142,9 +144,19 @@ class _HhcSelectionReviewPageState extends State { ); } - void _launchDirectionsToLocation(double lat, double lng, String locationName) { + void _launchDirectionsToLocation(double lat, double lng, String locationName) async { if (lat != 0.0 && lng != 0.0) { - MapsLauncher.launchCoordinates(lat, lng, locationName); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(lat, lng), + title: locationName, + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(lat, lng), + title: locationName, + ); + }); } } diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 84fdee4..531341b 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/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/insurance/insurance_view_model.dart'; @@ -16,6 +17,7 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; class PatientInsuranceCard extends StatelessWidget { PatientInsuranceCard({super.key, required this.insuranceCardDetailsModel, required this.isInsuranceExpired}); @@ -49,19 +51,73 @@ class PatientInsuranceCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( - width: MediaQuery.of(context).size.width * 0.45, child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true)), + width: MediaQuery.of(context).size.width * 0.4, + child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true, textOverflow: TextOverflow.clip)), LocaleKeys.policyNumber.tr(namedArgs: {'number': insuranceCardDetailsModel.insurancePolicyNo ?? ''}, context: context).toText12(isBold: true, color: AppColors.lightGrayColor), ], ), AppCustomChipWidget( - icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, - labelText: isInsuranceExpired ? LocaleKeys.insuranceExpired.tr(context: context) : LocaleKeys.insuranceActive.tr(context: context), - 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), - labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), - ), + icon: insuranceViewModel.isInsuranceExpired + ? AppAssets.cancel_circle_icon + : insuranceViewModel.isInsuranceActive + ? AppAssets.insurance_active_icon + : AppAssets.alertSquare, + labelText: insuranceViewModel.isInsuranceExpired + ? LocaleKeys.insuranceExpired.tr(context: context) + : insuranceViewModel.isInsuranceActive + ? LocaleKeys.insuranceActive.tr(context: context) + : LocaleKeys.insuranceInActive.tr(context: context), + iconColor: insuranceViewModel.isInsuranceExpired + ? AppColors.primaryRedColor + : insuranceViewModel.isInsuranceActive + ? AppColors.successColor + : AppColors.warningColorYellow, + textColor: insuranceViewModel.isInsuranceExpired + ? AppColors.primaryRedColor + : insuranceViewModel.isInsuranceActive + ? AppColors.successColor + : AppColors.warningColorYellow, + iconSize: 12.w, + deleteIcon: insuranceViewModel.isInsuranceActive ? null : AppAssets.forward_chevron_icon, + deleteIconColor: AppColors.warningColorYellow, + deleteIconHasColor: true, + onChipTap: () { + if (!insuranceViewModel.isInsuranceActive) { + 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: insuranceViewModel.isInsuranceExpired + ? AppColors.primaryRedColor.withOpacity(0.1) + : insuranceViewModel.isInsuranceActive + ? AppColors.successColor.withOpacity(0.1) + : AppColors.warningColorYellow.withOpacity(0.1), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceViewModel.isInsuranceActive ? 6.w : 0.w), + ).toShimmer2(isShow: insuranceViewModel.isInsuranceLoading), + // AppCustomChipWidget( + // icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, + // labelText: isInsuranceExpired ? LocaleKeys.insuranceExpired.tr(context: context) : LocaleKeys.insuranceActive.tr(context: context), + // 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), + // labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), + // ), ], ), SizedBox(height: 12.h), @@ -75,10 +131,11 @@ class PatientInsuranceCard extends StatelessWidget { children: [ AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, - labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", + // labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", + richText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}".toText10(isEnglishOnly: true), labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), ), - AppCustomChipWidget(labelText: LocaleKeys.patientCardID.tr(namedArgs: {'id': insuranceCardDetailsModel.patientCardID ?? ''}, context: context)), + AppCustomChipWidget(richText: LocaleKeys.patientCardID.tr(namedArgs: {'id': insuranceCardDetailsModel.patientCardID ?? ''}, context: context).toText10(isEnglishOnly: true)), ], ), SizedBox(height: 10.h), diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 006280f..4016aff 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.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/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.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/appbar/collapsing_toolbar.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/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), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: Consumer( builder: (context, model, 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()), // CustomTabBarModel(null, "Completed".needTranslation), ], 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: () { model.setIsSortByClinic(true); }, backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), SizedBox(width: 8.h), CustomButton( text: LocaleKeys.byHospital.tr(context: context), onPressed: () { model.setIsSortByClinic(false); }, backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available model.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (model.patientLabOrdersViewList.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.patientLabOrdersViewList.length, itemBuilder: (context, index) { final group = model.patientLabOrdersViewList[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: [ AppCustomChipWidget(labelText: "${group.length} ${LocaleKeys.results.tr(context: context)}"), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Text( model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown'), style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ], ), ), 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: ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group[index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( order.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: (order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( labelText: ("${LocaleKeys.orderNo.tr(context: context)}: ${order.orderNo!}"), ), AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), ), AppCustomChipWidget( labelText: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), ), ], ), // Row( // children: [ // CustomButton( // text: ("Order No: ".needTranslation + order.orderNo!), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // SizedBox(width: 8.h), // CustomButton( // text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // ], // ), // SizedBox(height: 8.h), // Row( // children: [ // CustomButton( // text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // ], // ), SizedBox(height: 12.h), Row( children: [ Expanded(flex: 2, child: SizedBox()), // Expanded( // flex: 1, // child: Container( // height: 40.h, // width: 40.w, // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( // color: AppColors.textColor, // borderRadius: 12, // ), // child: Padding( // padding: EdgeInsets.all(12.h), // child: Transform.flip( // flipX: _appState.isArabic(), // child: Utils.buildSvgWithAssets( // icon: AppAssets.forward_arrow_icon_small, // iconColor: AppColors.whiteColor, // fit: BoxFit.contain, // ), // ), // ), // ).onPress(() { // model.currentlySelectedPatientOrder = order; // labProvider.getPatientLabResultByHospital(order); // labProvider.getPatientSpecialResult(order); // Navigator.of(context).push( // CustomPageRoute(page: LabResultByClinic(labOrder: order)), // ); // }), // ) Expanded( flex: 2, child: CustomButton( icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, text: LocaleKeys.viewResults.tr(context: context), onPressed: () { model.currentlySelectedPatientOrder = order; labProvider.getPatientLabResultByHospital(order); labProvider.getPatientSpecialResult(order); Navigator.of(context).push( CustomPageRoute(page: LabResultByClinic(labOrder: order)), ); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14, fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ) ], ), // SizedBox(height: 12.h), // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), // SizedBox(height: 12.h), ], ).paddingOnly(top: 16, bottom: 16); }, separatorBuilder: (cxt, index) => Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), itemCount: group.length)) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (model.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: model.indexedCharacterForUniqueTest, details: model.uniqueTestsList, labViewModel: model, 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:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.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/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.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/appbar/collapsing_toolbar.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/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), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: Consumer( builder: (context, model, 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()), // CustomTabBarModel(null, "Completed".needTranslation), ], 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: () { model.setIsSortByClinic(true); }, backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), SizedBox(width: 8.h), CustomButton( text: LocaleKeys.byHospital.tr(context: context), onPressed: () { model.setIsSortByClinic(false); }, backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available model.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (model.patientLabOrdersViewList.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.patientLabOrdersViewList.length, itemBuilder: (context, index) { final group = model.patientLabOrdersViewList[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: [ AppCustomChipWidget(labelText: "${group.length} ${LocaleKeys.results.tr(context: context)}"), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Text( model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown'), style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ], ), ), 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: ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group[index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( order.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: (order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( labelText: ("${LocaleKeys.orderNo.tr(context: context)}: ${order.orderNo!}"), ), AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), ), AppCustomChipWidget( labelText: model.isSortByClinic ? (order.projectName ?? "") : (order.clinicDescription ?? ""), ), ], ), // Row( // children: [ // CustomButton( // text: ("Order No: ".needTranslation + order.orderNo!), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // SizedBox(width: 8.h), // CustomButton( // text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // ], // ), // SizedBox(height: 8.h), // Row( // children: [ // CustomButton( // text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // ], // ), SizedBox(height: 12.h), Row( children: [ Expanded(flex: 2, child: SizedBox()), // Expanded( // flex: 1, // child: Container( // height: 40.h, // width: 40.w, // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( // color: AppColors.textColor, // borderRadius: 12, // ), // child: Padding( // padding: EdgeInsets.all(12.h), // child: Transform.flip( // flipX: _appState.isArabic(), // child: Utils.buildSvgWithAssets( // icon: AppAssets.forward_arrow_icon_small, // iconColor: AppColors.whiteColor, // fit: BoxFit.contain, // ), // ), // ), // ).onPress(() { // model.currentlySelectedPatientOrder = order; // labProvider.getPatientLabResultByHospital(order); // labProvider.getPatientSpecialResult(order); // Navigator.of(context).push( // CustomPageRoute(page: LabResultByClinic(labOrder: order)), // ); // }), // ) Expanded( flex: 2, child: CustomButton( icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, text: LocaleKeys.viewResults.tr(context: context), onPressed: () { model.currentlySelectedPatientOrder = order; labProvider.getPatientLabResultByHospital(order); labProvider.getPatientSpecialResult(order); Navigator.of(context).push( CustomPageRoute(page: LabResultByClinic(labOrder: order)), ); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14, fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ) ], ), // SizedBox(height: 12.h), // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), // SizedBox(height: 12.h), ], ).paddingOnly(top: 16, bottom: 16); }, separatorBuilder: (cxt, index) => Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), itemCount: group.length)) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (model.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: model.indexedCharacterForUniqueTest, details: model.uniqueTestsList, labViewModel: model, 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/lab/lab_result_via_clinic/LabResultByClinic.dart b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart index 50fd1f1..8fb3090 100644 --- a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart +++ b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart @@ -1,17 +1,22 @@ -import 'package:easy_localization/easy_localization.dart' - show tr, StringTranslateExtension; +import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_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/LabResultList.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/lab_order_specialResult.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -25,6 +30,7 @@ class LabResultByClinic extends StatelessWidget { late LabViewModel labViewModel; PatientLabOrdersResponseModel labOrder; + @override Widget build(BuildContext context) { labViewModel = Provider.of(context, listen: false); @@ -35,6 +41,21 @@ class LabResultByClinic extends StatelessWidget { Expanded( child: CollapsingListView( title: LocaleKeys.labResults.tr(), + aiOverview: () async { + final _dialogService = getIt.get(); + await _dialogService.showCommonBottomSheetWithoutH( + message: LocaleKeys.aiDisclaimer.tr(), + label: LocaleKeys.consent.tr(), + okLabel: LocaleKeys.acceptLbl.tr(), + cancelLabel: LocaleKeys.rejectView.tr(), + onOkPressed: () { + context.pop(); + labViewModel.getAiOverviewLabOrders(labOrder: labOrder, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); + }, + onCancelPressed: () { + context.pop(); + }); + }, child: SingleChildScrollView( child: Column( spacing: 8.h, @@ -77,7 +98,7 @@ class LabResultByClinic extends StatelessWidget { } }, ), - LabOrderSpecialResult() + LabOrderSpecialResult(), ], ).paddingAll(24.h), )), diff --git a/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart b/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart new file mode 100644 index 0000000..f0c9b26 --- /dev/null +++ b/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart @@ -0,0 +1,101 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class AiAnalysisWidget extends StatelessWidget { + final LabOrderResponseByAi data; + + const AiAnalysisWidget({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(24.r), gradient: AppColors.aiLinearGradient), + padding: EdgeInsets.all(1.5.r), // Border thickness + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(22.r), + ), + padding: EdgeInsets.all(20.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.aiAnalysis.tr().toText18(weight: FontWeight.w700, color: AppColors.blackColor), + Container( + padding: EdgeInsets.all(8.r), + decoration: BoxDecoration(gradient: AppColors.aiLinearGradient, borderRadius: BorderRadius.circular(8.r)), + child: SvgPicture.asset(AppAssets.aiOverView, colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), height: 18.r, width: 18.r), + ), + ], + ), + SizedBox(height: 16.h), + if (data.summary != null && data.summary!.isNotEmpty) ...[ + LocaleKeys.suggestion.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 8.h), + data.summary!.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 16.h), + ], + if (data.aiPredictions != null && data.aiPredictions!.isNotEmpty) ...[ + LocaleKeys.prediction.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 12.h), + ...data.aiPredictions!.map((item) => _buildBulletItem(item)), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 16.h), + ], + if (data.prevention != null && data.prevention!.isNotEmpty) ...[ + LocaleKeys.prevention.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 12.h), + ...data.prevention!.map((item) => _buildBulletItem(item)), + SizedBox(height: 20.h), + ], + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.info_outline, color: AppColors.greyTextColor, size: 20.r), + SizedBox(width: 8.w), + Expanded( + child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.thisAboveInfo.tr()}").toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildBulletItem(String text) { + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 8.h, right: 12.w, left: 4.w), + child: Container( + width: 5.r, + height: 5.r, + decoration: const BoxDecoration(color: AppColors.blackColor, shape: BoxShape.circle), + ), + ), + Expanded( + child: text.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/lab/lab_result_via_clinic/lab_ai_analysis_detailed_page.dart b/lib/presentation/lab/lab_result_via_clinic/lab_ai_analysis_detailed_page.dart new file mode 100644 index 0000000..14ff620 --- /dev/null +++ b/lib/presentation/lab/lab_result_via_clinic/lab_ai_analysis_detailed_page.dart @@ -0,0 +1,548 @@ +import 'dart:convert'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/expandable_list_widget.dart'; +import 'package:provider/provider.dart'; + +import '../../../features/lab/models/resp_models/ai_overview_labresults_by_ai_model.dart'; + +class LabAiAnalysisDetailedPage extends StatefulWidget { + const LabAiAnalysisDetailedPage({super.key}); + + @override + State createState() => _LabAiAnalysisDetailedPageState(); +} + +class _LabAiAnalysisDetailedPageState extends State { + late LabViewModel labProvider; + + @override + Widget build(BuildContext context) { + labProvider = Provider.of(context, listen: false); + return CollapsingListView( + title: LocaleKeys.aiAnalysis.tr(context: context), + child: Consumer( + builder: (context, model, child) { + if (model.labOrdersResponseByAi == null || model.labOrdersResponseByAi?.analysis == null) { + return Utils.getNoDataWidget(context); + } + + final analysis = model.labOrdersResponseByAi!.analysis!; + List expandableItems = []; + + // Summary Section + if (analysis.summary != null && analysis.summary!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.summary.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + initiallyExpanded: true, + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFF0B85F7))), + padding: EdgeInsets.all(16.r), + child: (analysis.summary ?? "").toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // AI Generated Attributes Section (Lab Test Results) + if (analysis.aiGeneratedAttributes != null && analysis.aiGeneratedAttributes!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.labResults.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + ...analysis.aiGeneratedAttributes!.map((attr) { + return Column( + children: [ + _buildCombinedLabCard(attr), + SizedBox(height: 16.h), + ], + ); + }).toList(), + ], + ), + ); + } + + // AI Predictions Section + if (analysis.aiPredictions != null && analysis.aiPredictions!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.prediction.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFFFFAF15))), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: analysis.aiPredictions!.map((prediction) => _buildBulletItem(prediction)).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // AI Recommendations Section + if (analysis.aiRecommendations != null && analysis.aiRecommendations!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.suggestions.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: analysis.aiRecommendations!.asMap().entries.map((entry) { + final rec = entry.value; + final isLast = entry.key == analysis.aiRecommendations!.length - 1; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (rec.priority != null) ...[ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: _getPriorityColor(rec.priority), borderRadius: BorderRadius.circular(4.r)), + child: rec.priority!.toText12(fontWeight: FontWeight.w600, color: _getPriorityTextColor(rec.priority)), + ), + SizedBox(height: 8.h), + ], + (rec.recommendation ?? "").toText14( + color: AppColors.textColorLight, + height: 1.5, + weight: FontWeight.w400, + ), + if (!isLast) ...[ + SizedBox(height: 12.h), + const Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 12.h), + ], + ], + ); + }).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // Risk Factors Section + if (analysis.riskFactors != null && analysis.riskFactors!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.riskFactors.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + ...analysis.riskFactors!.asMap().entries.map((entry) { + final risk = entry.value; + return Column( + children: [ + Container( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration(color: Color(0xFFFBCB6E).withOpacity(0.10), borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFFFBCB6E).withOpacity(0.10))), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration(color: Color(0xFFFBCB6E).withAlpha(38), borderRadius: BorderRadius.circular(8)), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: (risk.risk ?? "").toText10(weight: FontWeight.w500, color: AppColors.blackColor), + ), + ), + ), + ], + ), + if (risk.risk != null && risk.risk!.isNotEmpty) ...[ + SizedBox(height: 4.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.guradIcon), + Padding( + padding: const EdgeInsets.all(8.0), + child: (risk.factor ?? "").toText16(weight: FontWeight.w700, color: AppColors.blackColor), + ), + ], + ), + SizedBox(height: 4.h), + (risk.description ?? "").toText14( + color: AppColors.textColorLight, + height: 1.5, + weight: FontWeight.w400, + ), + ], + if (risk.description != null && risk.description!.isNotEmpty) ...[ + SizedBox(height: 8.h), + (risk.description ?? "").toText14( + color: AppColors.textColorLight, + height: 1.5, + weight: FontWeight.w400, + ), + ], + ], + ), + ), + SizedBox(height: 16.h), + ], + ); + }).toList(), + ], + ), + ); + } + + // Prevention Section + if (analysis.prevention != null && analysis.prevention!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + title: LocaleKeys.prevention.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, side: BorderSide(width: 1, color: Color(0xFF18C273))), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: analysis.prevention!.map((prevention) => _buildBulletItem(prevention)).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // // Suggested Doctors Section + // if (analysis.suggestedDoctors != null && analysis.suggestedDoctors!.isNotEmpty) { + // expandableItems.add( + // ExpandableListItem( + // title: "Suggested Doctors".toText18(weight: FontWeight.w700, color: AppColors.blackColor), + // children: [ + // SizedBox(height: 10.h), + // Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.whiteColor, + // borderRadius: 12.r, + // side: BorderSide(width: 1, color: Color(0xFF3498DB)), + // ), + // padding: EdgeInsets.all(16.r), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: analysis.suggestedDoctors!.asMap().entries.map((entry) { + // final doctor = entry.value; + // final isLast = entry.key == analysis.suggestedDoctors!.length - 1; + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Row( + // children: [ + // Container( + // width: 40.r, + // height: 40.r, + // decoration: BoxDecoration( + // gradient: AppColors.aiLinearGradient, + // shape: BoxShape.circle, + // ), + // child: Icon( + // Icons.person, + // color: Colors.white, + // size: 24.r, + // ), + // ), + // SizedBox(width: 12.w), + // Expanded( + // child: doctor.toString().toText14( + // color: AppColors.textColorLight, + // height: 1.5, + // weight: FontWeight.w600, + // ), + // ), + // ], + // ), + // if (!isLast) ...[ + // SizedBox(height: 12.h), + // const Divider(color: AppColors.dividerColor, thickness: 1), + // SizedBox(height: 12.h), + // ], + // ], + // ); + // }).toList(), + // ), + // ), + // SizedBox(height: 8.h), + // ], + // ), + // ); + // } + + return Column( + children: [ + if (expandableItems.isEmpty) + Utils.getNoDataWidget(context) + else + CustomExpandableList( + expansionMode: ExpansionMode.exactlyOne, + dividerColor: Color(0xff2b353e1a), + itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h), + items: expandableItems, + theme: ExpandableListTheme.custom( + defaultTrailingIcon: Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w, iconColor: AppColors.textColor), + ), + ).paddingSymmetrical(16.w, 0.0), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.info_outline, color: AppColors.greyTextColor, size: 20.r), + SizedBox(width: 8.w), + Expanded( + child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.thisAboveInfo.tr()}").toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400), + ), + ], + ).paddingSymmetrical(16.w, 16.h) + ], + ); + }, + ), + ); + } + + Color _getPriorityColor(String? priority) { + if (priority == null) return AppColors.primaryRedColor; + switch (priority.toLowerCase()) { + case 'high': + return Color(0xFFED1C2B).withOpacity(.10); + case 'medium' || 'moderate': + return AppColors.lightGreenButtonColor; + case 'low': + return AppColors.warningColorYellow.withOpacity(.3); + default: + return AppColors.primaryRedColor; + } + } + + Color _getPriorityTextColor(String? priority) { + if (priority == null) return AppColors.whiteColor; + switch (priority.toLowerCase()) { + case 'high': + return AppColors.primaryRedColor; + case 'medium' || 'moderate': + return AppColors.textGreenColor; + case 'low': + return AppColors.alertColor; + default: + return AppColors.primaryRedColor; + } + } + + + + Color _getResultFlagColor(String? flag) { + if (flag == null || flag.isEmpty) return Colors.grey; + switch (flag.toUpperCase()) { + case 'H': // High + case 'HH': // Very High + case 'CRITICAL HIGH': + return AppColors.lightRedButtonColor; + case 'L': // Low + case 'LL': // Very Low + case 'CRITICAL LOW': + return AppColors.chipColorSeekMedicalAdvice.withAlpha(75); + case 'N': // Normal + return AppColors.lightGreenButtonColor; + case 'A': // Abnormal + return Colors.deepOrange; + default: + return AppColors.greyLightColor; + } + } + + Color _getResultTextColor(String? priority) { + if (priority == null) return AppColors.whiteColor; + switch (priority.toUpperCase()) { + case 'H': // High + case 'HH': // Very High + case 'CRITICAL HIGH': + return AppColors.primaryRedColor; + case 'N': + return AppColors.textGreenColor; + case 'L': // Low + case 'LL': // Very Low + case 'CRITICAL LOW': + return AppColors.chipTextColorSeekMedicalAdvice; + default: + return AppColors.textColor; + } + } + + Widget _buildBulletItem(String text) { + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: text.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + ], + ), + ); + } + + Widget _buildCombinedLabCard(AiGeneratedAttribute attr) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 2.h, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (attr.calculatedResultFlag != null && attr.calculatedResultFlag!.isNotEmpty && attr.calculatedResultFlag == "-") ...[ + Row( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: AppColors.greyLightColor, borderRadius: BorderRadius.circular(4.r)), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.informationIcon, width: 14.w, height: 14.h), + SizedBox(width: 4.w), + LocaleKeys.contextDependent.tr().toText12(fontWeight: FontWeight.w600, color: AppColors.textColor), + ], + ), + ), + ], + ), + ] else ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: _getResultFlagColor(attr.calculatedResultFlag), borderRadius: BorderRadius.circular(4.r)), + child: Row( + children: [ + (attr.calculatedResultFlag ?? "").toText12(fontWeight: FontWeight.w600, color: _getResultTextColor(attr.calculatedResultFlag)), + ], + ), + ), + if (attr.calculatedResultFlag != null && attr.calculatedResultFlag!.isNotEmpty && attr.calculatedResultFlag == "H") + Utils.buildSvgWithAssets(icon: AppAssets.highCautionIcon, iconColor: AppColors.primaryRedColor, width: 24.w, height: 24.h), + ], + ), + ], + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: (attr.description ?? "").toText16(isBold: true, color: _getResultTextColor(attr.calculatedResultFlag))), + + // if (attr.calculatedResultFlag != null && attr.calculatedResultFlag!.isNotEmpty) + // Column( + // spacing: 2.h, + // children: [ + // Consumer( + // builder: (context, labViewModel, child) { + // return labViewModel.getSeverityText(attr.calculatedResultFlag ?? "").tr(context: context).toText10(weight: FontWeight.w500, color: AppColors.greyTextColor); + // }, + // ), + // Consumer( + // builder: (context, labViewModel, child) { + // return Utils.buildSvgWithAssets( + // icon: AppAssets.lab_result_indicator, + // width: 21, + // height: 23, + // iconColor: labViewModel.getColor(attr.calculatedResultFlag ?? ""), + // ); + // }, + // ), + // ], + // ), + ], + ), + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: (attr.testShortDescription ?? "").toText12(color: AppColors.textColor)), + ], + ), + SizedBox(height: 4.h) + ], + ), + Row( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: _getResultFlagColor(attr.calculatedResultFlag), borderRadius: BorderRadius.circular(4.r)), + child: ("${LocaleKeys.calculatedValue.tr()}: ${attr.resultValue!}").toText12(fontWeight: FontWeight.w600, color: _getResultTextColor(attr.calculatedResultFlag)), + ), + SizedBox(width: 8.h), + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: AppColors.greyLightColor, borderRadius: BorderRadius.circular(4.r)), + child: ("${LocaleKeys.unit.tr()}: ${attr.uom!}").toText12(fontWeight: FontWeight.w600, color: AppColors.textColor), + ), + ], + ), + SizedBox( + height: 4.h, + ), + if (attr.referanceRange != null && attr.referanceRange!.isNotEmpty) + Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration(color: AppColors.greyLightColor, borderRadius: BorderRadius.circular(4.r)), + child: ("${LocaleKeys.referenceRange.tr(context: context)}: ${attr.referanceRange!.trim()}").toText12(fontWeight: FontWeight.w600, color: AppColors.textColor), + ), + if (attr.calculationFormula != null && attr.calculationFormula!.isNotEmpty) ...[ + SizedBox(height: 8.h), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.calendar, iconColor: Colors.grey), + SizedBox(width: 8.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LocaleKeys.calculationFormula.tr().toText16(weight: FontWeight.w600), + (attr.calculationFormula ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + ], + ), + ), + ], + ), + ], + ], + ), + ); + } +} diff --git a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart index e332066..ba97731 100644 --- a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart +++ b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:provider/provider.dart'; +import 'dart:ui' as ui; class LabOrderResultItem extends StatelessWidget { final VoidCallback onTap; @@ -43,49 +44,52 @@ class LabOrderResultItem extends StatelessWidget { ), (tests!.packageShortDescription ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), SizedBox(height: 12.h), - Row( - mainAxisSize: MainAxisSize.max, - children: [ - Flexible( - child: Text( - "${tests?.resultValue} ${tests?.uOM}", - style: TextStyle( - fontSize: 24.f, - fontWeight: FontWeight.w600, - fontFamily: 'Poppins', - color: tests!.checkIfGraphShouldBeDisplayed() - ? context.read().getColor( - tests?.calculatedResultFlag ?? "", - ) - : Colors.grey.shade700, - letterSpacing: -2, - ), - ), - ), - SizedBox(width: 4.h,), - Visibility( - // visible: tests?.checkIfGraphShouldBeDisplayed() == true, - visible: true, - child: Expanded( - flex: 2, - child: Visibility( - visible: tests?.referanceRange != null, + Directionality( + textDirection: ui.TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( child: Text( - "(${LocaleKeys.referenceRange.tr(context: context)}: ${tests?.referanceRange})", + "${tests?.resultValue} ${tests?.uOM}", style: TextStyle( - fontSize: 12.f, - fontWeight: FontWeight.w500, + fontSize: 24.f, + fontWeight: FontWeight.w600, fontFamily: 'Poppins', - color: AppColors.greyTextColor, + color: tests!.checkIfGraphShouldBeDisplayed() + ? context.read().getColor( + tests?.calculatedResultFlag ?? "", + ) + : Colors.grey.shade700, + letterSpacing: -2, ), - // overflow: TextOverflow.ellipsis, - // maxLines: 2, - softWrap: true, ), ), - ), - ) - ], + SizedBox(width: 4.h,), + Visibility( + // visible: tests?.checkIfGraphShouldBeDisplayed() == true, + visible: true, + child: Expanded( + flex: 2, + child: Visibility( + visible: tests?.referanceRange != null, + child: Text( + "(${LocaleKeys.referenceRange.tr(context: context)}: ${tests?.referanceRange})", + style: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w500, + fontFamily: 'Poppins', + color: AppColors.greyTextColor, + ), + // overflow: TextOverflow.ellipsis, + // maxLines: 2, + softWrap: true, + ), + ), + ), + ) + ], + ), ), SizedBox(height: 12.h), diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 29e15ee..cab20ae 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -1,45 +1,72 @@ - 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_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_list_item.dart'; import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart'; -import 'package:provider/provider.dart' show Consumer, Provider, ReadContext; +import 'package:provider/provider.dart' show Consumer, Provider, ReadContext, Selector; -import '../../../widgets/common_bottom_sheet.dart' - show showCommonBottomSheetWithoutHeight; +import '../../../widgets/common_bottom_sheet.dart' show showCommonBottomSheetWithoutHeight; class LabResultDetails extends StatelessWidget { final LabResult recentLabResult; final String? testDescription; - const LabResultDetails( - {super.key, - required this.recentLabResult, - required this.testDescription}); + const LabResultDetails({super.key, required this.recentLabResult, required this.testDescription}); @override Widget build(BuildContext context) { + LabViewModel labViewModel = Provider.of(context, listen: false); + final appState = getIt.get(); return CollapsingListView( title: LocaleKeys.labResultDetails.tr(context: context), + aiOverview: () async { + final _dialogService = getIt.get(); + await _dialogService.showCommonBottomSheetWithoutH( + message: LocaleKeys.aiDisclaimer.tr(), + label: LocaleKeys.consent.tr(), + okLabel: LocaleKeys.acceptLbl.tr(), + cancelLabel: LocaleKeys.rejectView.tr(), + onOkPressed: () { + context.pop(); + labViewModel.getAiOverviewSingleLabResult(langId: appState.getLanguageID().toString(), recentLabResult: recentLabResult, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); + }, + onCancelPressed: () { + context.pop(); + }); + }, child: SingleChildScrollView( child: Column( spacing: 16.h, children: [ LabNameAndStatus(context), getLabDescription(context), - LabGraph(context) + LabGraph(context), + Selector( + selector: (_, model) => model.labOrderResponseByAi, + builder: (_, aiData, __) { + if (aiData != null) { + return AiAnalysisWidget(data: aiData).paddingOnly(bottom: 16.h); + } + return const SizedBox.shrink(); + }, + ), ], ).paddingAll(24.h), ), @@ -47,11 +74,7 @@ class LabResultDetails extends StatelessWidget { } Widget LabNameAndStatus(BuildContext context) => Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: true, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -107,7 +130,9 @@ class LabResultDetails extends StatelessWidget { ), letterSpacing: -2), ), - SizedBox(width: 4.h,), + SizedBox( + width: 4.h, + ), ], ), ), @@ -150,9 +175,7 @@ class LabResultDetails extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - labmodel.isGraphVisible - ? LocaleKeys.historyFlowchart.tr(context: context) - : LocaleKeys.history.tr(context: context), + labmodel.isGraphVisible ? LocaleKeys.historyFlowchart.tr(context: context) : LocaleKeys.history.tr(context: context), style: TextStyle( fontSize: 16, fontFamily: 'Poppins', @@ -164,22 +187,12 @@ class LabResultDetails extends StatelessWidget { spacing: 16.h, children: [ //todo handle when the graph icon is being displayed - Utils.buildSvgWithAssets( - icon: labmodel.isGraphVisible - ? AppAssets.ic_list - : AppAssets.ic_graph, - width: 24.h, - height: 24.h) - .onPress(() { + Utils.buildSvgWithAssets(icon: labmodel.isGraphVisible ? AppAssets.ic_list : AppAssets.ic_graph, width: 24.h, height: 24.h).onPress(() { if (labmodel.shouldShowGraph) { labmodel.alterGraphVisibility(); } }), - Utils.buildSvgWithAssets( - icon: AppAssets.ic_date_filter, - width: 24, - height: 24) - .onPress(() { + Utils.buildSvgWithAssets(icon: AppAssets.ic_date_filter, width: 24, height: 24).onPress(() { showCommonBottomSheetWithoutHeight( title: LocaleKeys.setTheDateRange.tr(context: context), context, @@ -218,21 +231,17 @@ class LabResultDetails extends StatelessWidget { Widget buildBottomLabel(String label) { return Padding( - padding: const EdgeInsets.only(top:8.0), + padding: const EdgeInsets.only(top: 8.0), child: Text( label, - style: TextStyle( - fontSize: 8.f, - fontFamily: 'Poppins', - fontWeight: FontWeight.w600, - color: AppColors.labelTextColor), + style: TextStyle(fontSize: 8.f, fontFamily: 'Poppins', fontWeight: FontWeight.w600, color: AppColors.labelTextColor), ), ); } Widget historyBody(LabViewModel labmodel) { if (labmodel.isGraphVisible && labmodel.shouldShowGraph) { - var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag??"N"); + var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag ?? "N"); return CustomGraph( dataPoints: labmodel.filteredGraphValues, makeGraphBasedOnActualValue: true, @@ -242,11 +251,11 @@ class LabResultDetails extends StatelessWidget { // maxY: (labmodel.maxY)+(getInterval(labmodel)??0)/2, maxY: (labmodel.maxY), minY: labmodel.minY, - maxX: labmodel.filteredGraphValues.length.toDouble()-.75, + maxX: labmodel.filteredGraphValues.length.toDouble() - .75, horizontalInterval: .1, - getDrawingHorizontalLine: (value){ + getDrawingHorizontalLine: (value) { value = double.parse(value.toStringAsFixed(1)); - if(value == labmodel.highRefrenceValue ||value== labmodel.lowRefenceValue) { + if (value == labmodel.highRefrenceValue || value == labmodel.lowRefenceValue) { return FlLine( color: AppColors.bgGreenColor.withOpacity(0.6), strokeWidth: 1, @@ -256,7 +265,8 @@ class LabResultDetails extends StatelessWidget { return FlLine( color: Colors.transparent, strokeWidth: 1, - );; + ); + ; }, leftLabelFormatter: (value) { value = double.parse(value.toStringAsFixed(1)); @@ -272,11 +282,11 @@ class LabResultDetails extends StatelessWidget { return SizedBox.shrink(); // } }, - graphColor:AppColors.blackColor, + graphColor: AppColors.blackColor, graphShadowColor: Colors.transparent, graphGridColor: graphColor.withOpacity(.4), bottomLabelFormatter: (value, data) { - if(data.isEmpty) return SizedBox.shrink(); + if (data.isEmpty) return SizedBox.shrink(); if (value == 0) { return buildBottomLabel(data[value.toInt()].label); } @@ -288,34 +298,34 @@ class LabResultDetails extends StatelessWidget { } return SizedBox.shrink(); }, - rangeAnnotations: RangeAnnotations( - horizontalRangeAnnotations: _buildRangeShades(labmodel) - ), - minX:(labmodel.filteredGraphValues.length == 1)?null : -.2, + rangeAnnotations: RangeAnnotations(horizontalRangeAnnotations: _buildRangeShades(labmodel)), + minX: (labmodel.filteredGraphValues.length == 1) ? null : -.2, scrollDirection: Axis.horizontal, height: 180.h); - }else { + } else { return labHistoryList(labmodel); } } - List _buildRangeShades( LabViewModel model,) { + List _buildRangeShades( + LabViewModel model, + ) { List ranges = []; - ranges.add(HorizontalRangeAnnotation( - y1:model.minY, - y2: model.lowRefenceValue, - color: AppColors.highAndLow.withOpacity(0.05), - )); + ranges.add(HorizontalRangeAnnotation( + y1: model.minY, + y2: model.lowRefenceValue, + color: AppColors.highAndLow.withOpacity(0.05), + )); ranges.add(HorizontalRangeAnnotation( - y1:model.lowRefenceValue, + y1: model.lowRefenceValue, y2: model.highRefrenceValue, color: AppColors.bgGreenColor.withOpacity(0.05), )); ranges.add(HorizontalRangeAnnotation( - y1:model.highRefrenceValue, + y1: model.highRefrenceValue, y2: model.maxY, color: AppColors.criticalLowAndHigh.withOpacity(0.05), )); @@ -324,11 +334,12 @@ class LabResultDetails extends StatelessWidget { Widget labHistoryList(LabViewModel labmodel) { return SizedBox( - height: labmodel.filteredGraphValues.length<3?labmodel.filteredGraphValues.length*64:180.h, + height: labmodel.filteredGraphValues.length < 3 ? labmodel.filteredGraphValues.length * 64 : 180.h, child: ListView.separated( padding: EdgeInsets.zero, - itemCount: labmodel.filteredGraphValues.length,itemBuilder: (context, index){ - var data = labmodel.filteredGraphValues.reversed.toList()[index]; + itemCount: labmodel.filteredGraphValues.length, + itemBuilder: (context, index) { + var data = labmodel.filteredGraphValues.reversed.toList()[index]; return LabHistoryItem( dayNameAndDate: labmodel.getFormattedDate(data.time), result: data.actualValue, @@ -368,11 +379,8 @@ class LabResultDetails extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ - LocaleKeys.whatIsThisResult.tr(context: context) - .toText16(weight: FontWeight.w600, color: AppColors.textColor), - testDescription?.toText12( - fontWeight: FontWeight.w500, color: AppColors.textColorLight) ?? - SizedBox.shrink() + LocaleKeys.whatIsThisResult.tr(context: context).toText16(weight: FontWeight.w600, color: AppColors.textColor), + testDescription?.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight) ?? SizedBox.shrink() ], )); } diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index b11602e..1a68f22 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -33,6 +33,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ 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/prescriptions/prescriptions_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/active_medication/active_medication_page.dart'; import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart'; @@ -78,6 +79,7 @@ import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; import '../../features/active_prescriptions/active_prescriptions_view_model.dart'; import '../prescriptions/prescription_detail_page.dart'; @@ -250,7 +252,7 @@ class _MedicalFilePageState extends State { children: [ AppCustomChipWidget( icon: AppAssets.file_icon, - labelText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}", + richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isEnglishOnly: true), labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), ), AppCustomChipWidget( @@ -286,14 +288,58 @@ class _MedicalFilePageState extends State { ), Consumer(builder: (context, insuranceVM, child) { return AppCustomChipWidget( - icon: insuranceVM.isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, - labelText: insuranceVM.isInsuranceExpired ? LocaleKeys.insuranceExpired.tr(context: context) : LocaleKeys.insuranceActive.tr(context: context), - iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, - textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, + 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, - backgroundColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), - ); + deleteIcon: insuranceVM.isInsuranceActive ? null : AppAssets.forward_chevron_icon, + deleteIconColor: AppColors.warningColorYellow, + deleteIconHasColor: true, + onChipTap: () { + if (!insuranceVM.isInsuranceActive) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), + navigationService.navigatorKey.currentContext!, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.insuranceInActiveContactSupport.tr(context: context), + confirmText: LocaleKeys.contactUs.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + navigationService.pop(); + }, + onConfirmTap: () async { + launchUrl(Uri.parse("tel://" + "+966 92 006 6666")); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, + backgroundColor: insuranceVM.isInsuranceExpired + ? AppColors.primaryRedColor.withOpacity(0.1) + : insuranceVM.isInsuranceActive + ? AppColors.successColor.withOpacity(0.1) + : AppColors.warningColorYellow.withOpacity(0.1), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceVM.isInsuranceActive ? 6.w : 0.w), + ).toShimmer2(isShow: insuranceVM.isInsuranceLoading); }), ], ), @@ -1180,6 +1226,7 @@ class _MedicalFilePageState extends State { isLargeText: true, iconSize: 36.w, ).onPress(() { + getIt.get().setIsAncillaryOrdersNeedReloading(true); Navigator.of(context).push( CustomPageRoute( page: ToDoPage(), @@ -1583,6 +1630,7 @@ class _MedicalFilePageState extends State { weight: FontWeight.w600, ), ), + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, width: 18.w, height: 18.h), ], ), SizedBox(height: 14.h), @@ -1605,6 +1653,7 @@ class _MedicalFilePageState extends State { child: value.toText17( isBold: true, color: AppColors.textColor, + isEnglishOnly: true ), ), if (unit.isNotEmpty) ...[ diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart index 3d3624d..73dd165 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -18,6 +18,8 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'dart:ui' as ui; + class MedicalFileAppointmentCard extends StatelessWidget { final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; final MyAppointmentsViewModel myAppointmentsViewModel; @@ -39,11 +41,12 @@ class MedicalFileAppointmentCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ AppCustomChipWidget( - richText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false) - .toText12( - color: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, - fontWeight: FontWeight.w500) - .paddingOnly(left: 8.w), + richText: Directionality( + textDirection: ui.TextDirection.ltr, + child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false) + .toText12(color: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, fontWeight: FontWeight.w500, isEnglishOnly: true) + .paddingSymmetrical(8.w, 0), + ), icon: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppAssets.appointment_calendar_icon : AppAssets.alarm_clock_icon, iconColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, iconSize: 16.w, @@ -71,9 +74,7 @@ class MedicalFileAppointmentCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (patientAppointmentHistoryResponseModel.doctorNameObj ?? "") - .toText14(isBold: true, maxlines: 1) - .toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), (patientAppointmentHistoryResponseModel.clinicName ?? "") .toText12(maxLine: 1, fontWeight: FontWeight.w500, color: AppColors.greyTextColor) .toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), @@ -103,10 +104,8 @@ class MedicalFileAppointmentCard extends StatelessWidget { // widget.myAppointmentsViewModel.getPatientAppointments(true, false); }); }, - backgroundColor: - AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), - borderColor: - AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), + borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), fontSize: 14.f, fontWeight: FontWeight.w500, diff --git a/lib/presentation/my_family/my_family.dart b/lib/presentation/my_family/my_family.dart index bac6f77..dca7574 100644 --- a/lib/presentation/my_family/my_family.dart +++ b/lib/presentation/my_family/my_family.dart @@ -1,4 +1,3 @@ - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -20,14 +19,7 @@ import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:provider/provider.dart'; class FamilyMedicalScreen extends StatefulWidget { - final List profiles; - final Function(FamilyFileResponseModelLists) onSelect; - - const FamilyMedicalScreen({ - super.key, - required this.profiles, - required this.onSelect, - }); + const FamilyMedicalScreen({super.key}); @override State createState() => _FamilyMedicalScreenState(); @@ -43,7 +35,6 @@ class _FamilyMedicalScreenState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { medicalVM?.onFamilyFileTabChange(0); medicalVM?.getAllPendingRecordsByResponseId(); - }); } @@ -55,27 +46,28 @@ class _FamilyMedicalScreenState extends State { title: LocaleKeys.familyTitle.tr(context: context), bottomChild: appState.getAuthenticatedUser()!.isParentUser! ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), - ), - padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 20.h), - child: CustomButton( - text: LocaleKeys.addANewFamilyMember.tr(context: context), - onPressed: () { - DialogService dialogService = getIt.get(); - medicalVM!.clearAuthValues(); - dialogService.showAddFamilyFileSheet( - label: LocaleKeys.addFamilyMember.tr(context: context), - message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(context: context), - onVerificationPress: () { - medicalVM!.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); - }); - }, - icon: AppAssets.add_icon, - fontSize: 16.f, - borderRadius: 12.r, - fontWeight: FontWeight.w500)).paddingOnly(bottom: 20.h) + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + ), + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 20.h), + child: CustomButton( + text: LocaleKeys.addANewFamilyMember.tr(context: context), + onPressed: () { + DialogService dialogService = getIt.get(); + medicalVM!.clearAuthValues(); + dialogService.showAddFamilyFileSheet( + label: LocaleKeys.addFamilyMember.tr(context: context), + message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(context: context), + onVerificationPress: () { + medicalVM!.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); + }); + }, + icon: AppAssets.add_icon, + fontSize: 16.f, + borderRadius: 12.r, + fontWeight: FontWeight.w500)) + .paddingOnly(bottom: 20.h) : SizedBox(), child: Column( mainAxisSize: MainAxisSize.min, @@ -96,18 +88,28 @@ class _FamilyMedicalScreenState extends State { ), ), appState.isChildLoggedIn ? SizedBox() : SizedBox(height: 25.h), - Selector(selector: (_, model) => model.getSelectedFamilyFileTabIndex, builder: (context, selectedIndex, child) => getFamilyTabs(index: selectedIndex)), + Selector patientFiles, List pendingFiles})>( + selector: (_, model) => (selectedIndex: model.getSelectedFamilyFileTabIndex, patientFiles: model.patientFamilyFiles, pendingFiles: model.pendingFamilyFiles), + shouldRebuild: (previous, next) { + return previous.selectedIndex != next.selectedIndex || previous.patientFiles.length != next.patientFiles.length || previous.pendingFiles.length != next.pendingFiles.length; + }, + builder: (context, data, child) => getFamilyTabs(index: data.selectedIndex, patientFiles: data.patientFiles, pendingFiles: data.pendingFiles), + ), SizedBox(height: 20.h), ], ).paddingSymmetrical(20, 0), ); } - Widget getFamilyTabs({required int index}) { + Widget getFamilyTabs({ + required int index, + required List patientFiles, + required List pendingFiles, + }) { switch (index) { case 0: return FamilyCards( - profiles: medicalVM!.patientFamilyFiles, + profiles: patientFiles, onSelect: (FamilyFileResponseModelLists profile) { medicalVM!.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); }, @@ -120,7 +122,7 @@ class _FamilyMedicalScreenState extends State { ); case 1: return FamilyCards( - profiles: medicalVM!.pendingFamilyFiles, + profiles: pendingFiles, isRequestDesign: medicalVM!.getSelectedFamilyFileTabIndex == 1, onSelect: (FamilyFileResponseModelLists profile) { medicalVM!.acceptRejectFileFromFamilyMembers(id: profile.id, status: 3); diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index ea305b0..826b174 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -308,6 +308,7 @@ class _FamilyCardsState extends State { alignment: WrapAlignment.start, children: [ (profile.patientName ?? "").toText14(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), + SizedBox(width: 2.w,), (getStatusTextByRequest( FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)) .toText14( diff --git a/lib/presentation/parking/parking_slot.dart b/lib/presentation/parking/parking_slot.dart index 52ab181..e8c4f09 100644 --- a/lib/presentation/parking/parking_slot.dart +++ b/lib/presentation/parking/parking_slot.dart @@ -1,4 +1,4 @@ - +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -7,12 +7,12 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/qr_parking/models/qr_parking_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:map_launcher/map_launcher.dart'; import '../../features/qr_parking/qr_parking_view_model.dart'; import '../../theme/colors.dart'; import '../../widgets/appbar/app_bar_widget.dart'; import '../../widgets/buttons/custom_button.dart'; import '../../widgets/chip/app_custom_chip_widget.dart'; -import 'package:maps_launcher/maps_launcher.dart'; import 'package:provider/provider.dart'; import '../../widgets/routes/custom_page_route.dart'; @@ -29,7 +29,7 @@ class ParkingSlot extends StatefulWidget { } class _ParkingSlotState extends State { - void _openDirection() { + void _openDirection() async { final lat = widget.model.latitude; final lng = widget.model.longitude; @@ -48,7 +48,17 @@ class _ParkingSlotState extends State { return; } - MapsLauncher.launchCoordinates(lat, lng); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(lat, lng), + title: "Parking Location", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(lat, lng), + title: "Parking Location", + ); + }); } diff --git a/lib/presentation/prescriptions/prescription_item_view.dart b/lib/presentation/prescriptions/prescription_item_view.dart index a41b53f..03a343f 100644 --- a/lib/presentation/prescriptions/prescription_item_view.dart +++ b/lib/presentation/prescriptions/prescription_item_view.dart @@ -155,26 +155,26 @@ class PrescriptionItemView extends StatelessWidget { ], ).paddingSymmetrical(16.h, 0.h), SizedBox(height: 14.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), - Row( - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.checkAvailability.tr(context: context), - onPressed: () {}, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ).toShimmer2(isShow: isLoading), - ), - // SizedBox(width: 16.h), - ], - ).paddingSymmetrical(16.h, 16.h), + // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + // Row( + // children: [ + // Expanded( + // child: CustomButton( + // text: LocaleKeys.checkAvailability.tr(context: context), + // onPressed: () {}, + // backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + // borderColor: AppColors.primaryRedColor.withOpacity(0.0), + // textColor: AppColors.primaryRedColor, + // fontSize: 14.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // ).toShimmer2(isShow: isLoading), + // ), + // // SizedBox(width: 16.h), + // ], + // ).paddingSymmetrical(16.h, 16.h), ], ), ); diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index a3c2667..82150ca 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -30,6 +30,7 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -197,9 +198,11 @@ class ProfileSettingsState extends State { showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.language.tr(context: context), child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false); }, trailingLabel: Utils.appState.isArabic() ? "العربية" : "English"), 1.divider, - actionItem(AppAssets.bell, LocaleKeys.notificationsSettings.tr(context: context), () {}), - 1.divider, - actionItem(AppAssets.touch_face_id, LocaleKeys.touchIDFaceIDServices.tr(context: context), () {}, switchValue: true), + actionItem(AppAssets.bell, LocaleKeys.notificationsSettings.tr(context: context), () { + openAppSettings(); + }), + // 1.divider, + // actionItem(AppAssets.touch_face_id, LocaleKeys.touchIDFaceIDServices.tr(context: context), () {}, switchValue: true), ], ), ), @@ -228,11 +231,11 @@ class ProfileSettingsState extends State { child: Column( children: [ actionItem(AppAssets.call_fill, LocaleKeys.contactUs.tr(context: context), () { - launchUrl(Uri.parse("tel://" + "+966 11 525 9999")); - }, trailingLabel: "011 525 9999"), - 1.divider, - actionItem(AppAssets.permission, LocaleKeys.permissions.tr(context: context), () {}, trailingLabel: "Location, Camera"), + launchUrl(Uri.parse("tel://" + "+966 92 006 6666")); + }, trailingLabel: "92 006 6666"), 1.divider, + // actionItem(AppAssets.permission, LocaleKeys.permissions.tr(context: context), () {}, trailingLabel: "Location, Camera"), + // 1.divider, actionItem(AppAssets.rate, LocaleKeys.rateApp.tr(context: context), () { if (Platform.isAndroid) { Utils.openWebView( @@ -366,61 +369,66 @@ class FamilyCardWidget extends StatelessWidget { labelText: "Blood: ${appState.getAuthenticatedUser()!.bloodGroup ?? ""}", iconColor: AppColors.primaryRedColor) : SizedBox(), - Selector( - selector: (context, insuranceVM) => ( - isEmpty: insuranceVM.patientInsuranceList.isEmpty, - patientID: insuranceVM.patientInsuranceList.isNotEmpty ? insuranceVM.patientInsuranceList.first.patientID : null, - isLoading: insuranceVM.isInsuranceLoading, - cardValidTo: insuranceVM.patientInsuranceList.isNotEmpty ? insuranceVM.patientInsuranceList.first.cardValidTo : null - ), - builder: (context, data, child) { - if (data.isEmpty) { - return const SizedBox(); - } else if (profile.responseId != data.patientID) { - return SizedBox(); - } - - final isLoading = data.isLoading; - final isExpired = !isLoading && DateTime.now().isAfter(DateUtil.convertStringToDate(data.cardValidTo)); - - final String icon; - final String labelText; - final Color iconColor; - final Color backgroundColor; - - if (isLoading) { - icon = AppAssets.cancel_circle_icon; - labelText = LocaleKeys.insurance.tr(context: context); - iconColor = AppColors.primaryRedColor; - backgroundColor = AppColors.primaryRedColor; - } else if (isExpired) { - icon = AppAssets.cancel_circle_icon; - labelText = LocaleKeys.insuranceExpired.tr(context: context); - iconColor = AppColors.primaryRedColor; - backgroundColor = AppColors.primaryRedColor.withValues(alpha: 0.15); - } else { - icon = AppAssets.insurance_active_icon; - labelText = LocaleKeys.insuranceActive.tr(context: context); - iconColor = AppColors.successColor; - backgroundColor = AppColors.successColor.withValues(alpha: 0.15); - } - - return AppCustomChipWidget( - icon: icon, - labelText: labelText, - iconColor: iconColor, - iconSize: 12.w, - backgroundColor: backgroundColor, - labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 8.w), - // padding: EdgeInsets.zero, - ).toShimmer2(isShow: isLoading); - }, - ) + Consumer(builder: (context, insuranceVM, child) { + return 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: AppColors.warningColorYellow, + deleteIconHasColor: true, + onChipTap: () { + if (!insuranceVM.isInsuranceActive) { + 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.withOpacity(0.1) + : insuranceVM.isInsuranceActive + ? AppColors.successColor.withOpacity(0.1) + : AppColors.warningColorYellow.withOpacity(0.1), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceVM.isInsuranceActive ? 6.w : 0.w), + ).toShimmer2(isShow: insuranceVM.isInsuranceLoading); + }), ], ), ), ], - ).paddingOnly(top: 16.h, right: 16.w, left: 16.w, bottom: 0.h), + ).paddingOnly(top: 16.h, right: 16.w, left: 16.w, bottom: 8.h), 1.divider.paddingSymmetrical(16.w, 0.h), _buildActionButton(appState), ], diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index f9b94e2..2f42436 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -310,8 +310,8 @@ class _RadiologyOrdersPageState extends State { ), ), AppCustomChipWidget( - labelText: model.isSortByClinic ? (order.clinicDescription ?? '') : (order.projectName ?? ''), - ), + labelText: model.isSortByClinic ? (order.projectName ?? '') : (order.clinicDescription ?? ''), + ), ], ), SizedBox(height: 12.h), diff --git a/lib/presentation/servicesPriceList/services_price_list_page.dart b/lib/presentation/servicesPriceList/services_price_list_page.dart new file mode 100644 index 0000000..0ee81ae --- /dev/null +++ b/lib/presentation/servicesPriceList/services_price_list_page.dart @@ -0,0 +1,199 @@ +import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/services_price_list/services_price_list_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class ServicesPriceListPage extends StatefulWidget { + const ServicesPriceListPage({super.key}); + + @override + State createState() => _ServicesPriceListPageState(); +} + +class _ServicesPriceListPageState extends State { + late ServicesPriceListViewModel servicesPriceListViewModel; + late AppState appState; + + @override + void initState() { + scheduleMicrotask(() { + servicesPriceListViewModel.initServicesPriceListProvider(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + servicesPriceListViewModel = Provider.of(context, listen: false); + appState = getIt.get(); + + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.servicePriceList.tr(context: context), + child: SingleChildScrollView( + padding: EdgeInsets.all(24.h), + child: Consumer(builder: (context, model, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: LocaleKeys.servicePriceListDesc.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + ), + ), + SizedBox(height: 16.h), + model.isServicesPriceListLoading + ? ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: NeverScrollableScrollPhysics(), + itemCount: 5, + separatorBuilder: (context, index) => SizedBox(height: 12.h), + itemBuilder: (context, index) => _buildLoadingCard(), + ) + : model.filteredServicesPriceList.isEmpty + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(40.h), + child: Center( + child: LocaleKeys.noDataAvailable.tr(context: context).toText16( + color: AppColors.textColorLight, + ), + ), + ) + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.serviceName.tr(context: context).toText18(weight: FontWeight.bold, color: AppColors.textColor), + SizedBox(height: 16.h), + ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: NeverScrollableScrollPhysics(), + itemCount: model.filteredServicesPriceList.length, + separatorBuilder: (context, index) => Divider(height: 1.h, color: AppColors.dividerColor).withVerticalPadding(), + itemBuilder: (context, index) { + final service = model.filteredServicesPriceList[index]; + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: (appState.isArabic() ? service.nameAR ?? service.nameEN ?? '' : service.nameEN ?? service.nameAR ?? '').toText16( + weight: FontWeight.w500, + color: AppColors.textColor, + ), + ), + SizedBox(width: 12.w), + Utils.getPaymentAmountWithSymbol('${service.price ?? 0}'.toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), + ], + ), + ], + ), + ), + ), + ); + }, + ), + ], + ), + ), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: LocaleKeys.servicePriceListRights.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + ), + ), + ], + ); + }), + ), + ), + ); + } + + Widget _buildLoadingCard() { + return Shimmer.fromColors( + baseColor: AppColors.greyColor.withValues(alpha: 0.3), + highlightColor: AppColors.greyColor.withValues(alpha: 0.1), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + height: 20.h, + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(4.r), + ), + ), + ), + SizedBox(width: 12.w), + Container( + width: 80.w, + height: 32.h, + decoration: BoxDecoration( + color: AppColors.greyColor, + borderRadius: BorderRadius.circular(12.r), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart b/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart index 7c537ba..f30d87a 100644 --- a/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart +++ b/lib/presentation/smartwatches/health_dashboard/health_dashboard.dart @@ -24,8 +24,8 @@ class HealthDashboard extends StatefulWidget { class _HealthDashboardState extends State with SingleTickerProviderStateMixin { late TabController _tabController; - final dateFormat = DateFormat('MMM dd, yyyy'); - final timeFormat = DateFormat('hh:mm a'); + final dateFormat = DateFormat('MMM dd, yyyy', "en-US"); + final timeFormat = DateFormat('hh:mm a', "en-US"); @override void initState() { diff --git a/lib/presentation/symptoms_checker/organ_selector_screen.dart b/lib/presentation/symptoms_checker/organ_selector_screen.dart index 9a4a1d9..253fccd 100644 --- a/lib/presentation/symptoms_checker/organ_selector_screen.dart +++ b/lib/presentation/symptoms_checker/organ_selector_screen.dart @@ -142,15 +142,27 @@ class _OrganSelectorPageState extends State { transitionBuilder: (child, animation) => _build3DFlipTransition(child, animation), switchInCurve: Curves.easeInOut, switchOutCurve: Curves.easeInOut, - child: InteractiveBodyWidget( + child: Builder( key: ValueKey(viewModel.currentView), - bodyImageAsset: viewModel.currentView == BodyView.front ? AppAssets.fullBodyFront : AppAssets.fullBodyBack, - organs: viewModel.currentOrgans, - selectedOrganIds: viewModel.selectedOrganIds, - onOrganTap: viewModel.toggleOrganSelection, - isBodyHidden: viewModel.isBodyHidden, - tooltipOrganId: viewModel.tooltipOrganId, - isArabic: _appState.isArabic(), + builder: (context) { + // Detect female gender from viewModel; allow Arabic value as fallback + final bool isFemale = + (viewModel.selectedGender != null && (viewModel.selectedGender!.toLowerCase() == 'female' || viewModel.selectedGender == 'أنثى')); + + final String bodyAsset = viewModel.currentView == BodyView.front + ? (isFemale ? AppAssets.fullBodyFrontFemale : AppAssets.fullBodyFrontMale) + : (isFemale ? AppAssets.fullBodyBackFemale : AppAssets.fullBodyBackMale); + + return InteractiveBodyWidget( + bodyImageAsset: bodyAsset, + organs: viewModel.currentOrgans, + selectedOrganIds: viewModel.selectedOrganIds, + onOrganTap: viewModel.toggleOrganSelection, + isBodyHidden: viewModel.isBodyHidden, + tooltipOrganId: viewModel.tooltipOrganId, + isArabic: _appState.isArabic(), + ); + }, ), ), ), diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index 8fb0e0f..80c7071 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -8,20 +8,32 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:provider/provider.dart'; import 'package:shimmer/shimmer.dart'; +import '../appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; + class PossibleConditionsPage extends StatelessWidget { - const PossibleConditionsPage({super.key}); + PossibleConditionsPage({super.key}); + + late SymptomsCheckerViewModel symptomsCheckerViewModel; + late BookAppointmentsViewModel bookAppointmentsViewModel; + late AppointmentViaRegionViewmodel regionalViewModel; Widget _buildLoadingShimmer() { return ListView.separated( @@ -80,8 +92,20 @@ class PossibleConditionsPage extends StatelessWidget { condition: condition, symptoms: symptoms, onActionPressed: () { - dialogService.showErrorBottomSheet( - message: 'icd10 Code is ${condition.conditionDetails?.icd10Code}. We need to get the clinics mapped against this code.', + LoaderBottomSheet.showLoader(); + symptomsCheckerViewModel.getClinicConditionsFromCategory( + categoryName: (condition.conditionDetails!.category!.name) ?? "Other", + onSuccess: (value) { + LoaderBottomSheet.hideLoader(); + print(symptomsCheckerViewModel.clinicDetailsList.first.clinicID); + initiateBookAppointmentFlow(context); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + dialogService.showErrorBottomSheet( + message: err.toString(), + ); + }, ); }, ); @@ -194,6 +218,9 @@ class PossibleConditionsPage extends StatelessWidget { @override Widget build(BuildContext context) { + symptomsCheckerViewModel = context.read(); + bookAppointmentsViewModel = context.read(); + regionalViewModel = context.read(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( @@ -202,10 +229,10 @@ class PossibleConditionsPage extends StatelessWidget { context: context, onConfirm: () { context.pop(); - final SymptomsCheckerViewModel symptomsCheckerViewModel = context.read(); symptomsCheckerViewModel.reset(); // Clear all symptoms checker data final navigationService = getIt.get(); - navigationService.replaceAllRoutesAndNavigateToLanding(); + // navigationService.replaceAllRoutesAndNavigateToLanding(); + navigationService.replaceAllRoutesAndNavigateToLandingAuth(); }, ), trailing: _buildTrailingSection(context), @@ -229,4 +256,102 @@ class PossibleConditionsPage extends StatelessWidget { ), ); } + + initiateBookAppointmentFlow(BuildContext context) { + bookAppointmentsViewModel.getLocation(); + bookAppointmentsViewModel.setSelectedClinic(GetClinicsListResponseModel( + clinicID: symptomsCheckerViewModel.clinicDetailsList.first.clinicID, + clinicDescription: symptomsCheckerViewModel.clinicDetailsList.first.category, + clinicDescriptionN: symptomsCheckerViewModel.clinicDetailsList.first.categoryAR, + )); + bookAppointmentsViewModel.setIsDoctorsListLoading(true); + + var bottomSheetType = RegionBottomSheetType.FOR_CLINIIC; + openRegionListBottomSheet(context, bottomSheetType); + } + + getTitle(AppointmentViaRegionViewmodel data, BuildContext context) { + if (data.selectedRegionId == null) { + return LocaleKeys.selectRegion.tr(context: context).toText20(weight: FontWeight.w600); + } else { + return Transform.flip( + flipX: data.isArabic ? true : false, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_back, + iconColor: Color(0xff2B353E), + fit: BoxFit.contain, + ), + ).onPress(() { + data.handleBackPress(); + }); + } + } + + void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) { + bookAppointmentsViewModel.setProjectID(null); + + regionalViewModel.flush(); + regionalViewModel.setBottomSheetType(type); + // AppointmentViaRegionViewmodel? viewmodel = null; + showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data, context)), isDismissible: false, + child: Consumer(builder: (context, data, __) { + return getRegionalSelectionWidget(data, context); + }), callBackFunc: () {}); + } + + Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data, BuildContext context) { + if (data.bottomSheetState == AppointmentViaRegionState.REGION_SELECTION) { + return RegionBottomSheetBody(); + } + if (data.bottomSheetState == AppointmentViaRegionState.TYPE_SELECTION) { + bookAppointmentsViewModel.resetFilterList(); + return FacilityTypeSelectionWidget( + selectedRegion: data.selectedRegionId ?? "", + ); + } + if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) { + return HospitalBottomSheetBody( + searchText: data.searchController, + displayList: data.displayList, + onFacilityClicked: (value) { + data.setSelectedFacility(value); + data.getDisplayList(); + }, + onHospitalClicked: (hospital) { + regionalViewModel.setHospitalModel(hospital); + if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) { + regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION); + regionalViewModel.handleLastStepForRegion(); + } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) { + regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); + regionalViewModel.handleLastStepForClinic(); + } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { + var appointmentsViewModel = Provider.of(context, listen: false); + regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); + regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1); + } + }, + onHospitalSearch: (value) { + data.searchHospitals(value ?? ""); + }, + selectedFacility: data.selectedFacility, + hmcCount: data.hmcCount, + hmgCount: data.hmgCount, + ); + } + if (data.bottomSheetState == AppointmentViaRegionState.DOCTOR_SELECTION) { + //if the region screen is opened for the dental clinic then the project id will be in the hospital list as the list is formed form the get project api + var id = ""; + if (data.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { + id = regionalViewModel.selectedHospital?.hospitalList.first.iD?.toString() ?? ""; + } else { + id = regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.first.projectID?.toString() ?? ""; + } + bookAppointmentsViewModel.setProjectID(id); + return SizedBox.shrink(); + } else { + return SizedBox.shrink(); + } + return SizedBox.shrink(); + } } diff --git a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart index 2bfcb62..cbbfc68 100644 --- a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart +++ b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart @@ -89,7 +89,8 @@ class _SymptomsSelectorPageState extends State { context.pop(); viewModel.reset(); // Clear all symptoms checker data final navigationService = getIt.get(); - navigationService.replaceAllRoutesAndNavigateToLanding(); + // navigationService.replaceAllRoutesAndNavigateToLanding(); + navigationService.replaceAllRoutesAndNavigateToLandingAuth(); }), child: viewModel.isBodySymptomsLoading ? _buildLoadingShimmer() diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index 0811833..0231ef1 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -316,7 +316,8 @@ class _TriagePageState extends State { context.pop(); viewModel.reset(); // Clear all symptoms checker data final navigationService = getIt.get(); - navigationService.replaceAllRoutesAndNavigateToLanding(); + // navigationService.replaceAllRoutesAndNavigateToLanding(); + navigationService.replaceAllRoutesAndNavigateToLandingAuth(); }, ), callBackFunc: () {}, diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart index c8d8cde..f1e8413 100644 --- a/lib/presentation/symptoms_checker/user_info_selection.dart +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -39,13 +40,18 @@ class _UserInfoSelectionPageState extends State { if (appState.isAuthenticated) { final user = appState.getAuthenticatedUser(); + getIt.get().getPatientVitalSign(); if (user == null) return; // Populate gender (gender is int: 1=Male, 2=Female) // Use internal keys (male/female) for storage if (user.gender != null) { - String genderKey = user.gender == 1 ? "male" : user.gender == 2 ? "female" : "other"; + String genderKey = user.gender == 1 + ? "male" + : user.gender == 2 + ? "female" + : "other"; viewModel.setGender(genderKey); } @@ -64,13 +70,14 @@ class _UserInfoSelectionPageState extends State { // If not authenticated or fields are empty, user will fill them manually } - _buildEditInfoTile({ + Widget _buildEditInfoTile({ required String leadingIcon, required String title, required String subTitle, required VoidCallback onTap, required String trailingIcon, required BuildContext context, + required HmgServicesViewModel hmgServicesVM, Color? iconColor, }) { return InkWell( @@ -92,7 +99,9 @@ class _UserInfoSelectionPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ title.toText14(weight: FontWeight.w500), - subTitle.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + subTitle + .toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) + .toShimmer2(isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading), ], ), ], @@ -144,6 +153,7 @@ class _UserInfoSelectionPageState extends State { age--; } userAgeFromDOB = age; + getIt.get().setSelectedAge(userAgeFromDOB); } catch (e) { // If date parsing fails, ignore } @@ -154,25 +164,20 @@ class _UserInfoSelectionPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: Consumer( - builder: (context, viewModel, child) { + body: Consumer2( + builder: (context, viewModel, hmgServicesVM, child) { // Check if any field is empty - bool hasEmptyFields = viewModel.selectedGender == null || - viewModel.selectedAge == null || - viewModel.selectedHeight == null || - viewModel.selectedWeight == null; + bool hasEmptyFields = viewModel.selectedGender == null || viewModel.selectedAge == null || viewModel.selectedHeight == null || viewModel.selectedWeight == null; // Get display values String genderText = _getLocalizedGender(viewModel.selectedGender, context); // Show age calculated from DOB (prefer viewModel's age, fallback to calculated from user's DOB) int? displayAge = viewModel.selectedAge ?? userAgeFromDOB; String ageText = displayAge != null ? "$displayAge ${LocaleKeys.years.tr(context: context)}" : LocaleKeys.notSet.tr(context: context); - String heightText = viewModel.selectedHeight != null - ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" - : LocaleKeys.notSet.tr(context: context); - String weightText = viewModel.selectedWeight != null - ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" - : LocaleKeys.notSet.tr(context: context); + + String heightText = viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : LocaleKeys.notSet.tr(context: context); + + String weightText = viewModel.selectedWeight != null ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" : LocaleKeys.notSet.tr(context: context); return Column( children: [ @@ -195,53 +200,53 @@ class _UserInfoSelectionPageState extends State { ), SizedBox(height: 32.h), _buildEditInfoTile( - context: context, - leadingIcon: AppAssets.genderIcon, - title: LocaleKeys.gender.tr(context: context), - subTitle: genderText, - onTap: () { - viewModel.setUserInfoPage(0, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); - }, - trailingIcon: AppAssets.edit_icon, - ), + context: context, + leadingIcon: AppAssets.genderIcon, + title: LocaleKeys.gender.tr(context: context), + subTitle: genderText, + onTap: () { + viewModel.setUserInfoPage(0, isSinglePageEdit: true); + context.navigateWithName(AppRoutes.userInfoFlowManager); + }, + trailingIcon: AppAssets.edit_icon, + hmgServicesVM: hmgServicesVM), _getDivider(), _buildEditInfoTile( - context: context, - leadingIcon: AppAssets.calendarGrey, - title: LocaleKeys.age.tr(context: context), - subTitle: ageText, - iconColor: AppColors.greyTextColor, - onTap: () { - viewModel.setUserInfoPage(1, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); - }, - trailingIcon: AppAssets.edit_icon, - ), + context: context, + leadingIcon: AppAssets.calendarGrey, + title: LocaleKeys.age.tr(context: context), + subTitle: ageText, + iconColor: AppColors.greyTextColor, + onTap: () { + viewModel.setUserInfoPage(1, isSinglePageEdit: true); + context.navigateWithName(AppRoutes.userInfoFlowManager); + }, + trailingIcon: AppAssets.edit_icon, + hmgServicesVM: hmgServicesVM), _getDivider(), _buildEditInfoTile( - context: context, - leadingIcon: AppAssets.rulerIcon, - title: LocaleKeys.height.tr(context: context), - subTitle: heightText, - onTap: () { - viewModel.setUserInfoPage(2, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); - }, - trailingIcon: AppAssets.edit_icon, - ), + context: context, + leadingIcon: AppAssets.rulerIcon, + title: LocaleKeys.height.tr(context: context), + subTitle: heightText, + onTap: () { + viewModel.setUserInfoPage(2, isSinglePageEdit: true); + context.navigateWithName(AppRoutes.userInfoFlowManager); + }, + trailingIcon: AppAssets.edit_icon, + hmgServicesVM: hmgServicesVM), _getDivider(), _buildEditInfoTile( - context: context, - leadingIcon: AppAssets.weightScale, - title: LocaleKeys.weight.tr(context: context), - subTitle: weightText, - onTap: () { - viewModel.setUserInfoPage(3, isSinglePageEdit: true); - context.navigateWithName(AppRoutes.userInfoFlowManager); - }, - trailingIcon: AppAssets.edit_icon, - ), + context: context, + leadingIcon: AppAssets.weightScale, + title: LocaleKeys.weight.tr(context: context), + subTitle: weightText, + onTap: () { + viewModel.setUserInfoPage(3, isSinglePageEdit: true); + context.navigateWithName(AppRoutes.userInfoFlowManager); + }, + trailingIcon: AppAssets.edit_icon, + hmgServicesVM: hmgServicesVM), ], ), ), @@ -291,6 +296,7 @@ class _UserInfoSelectionPageState extends State { text: LocaleKeys.yesItIs.tr(context: context), icon: AppAssets.tickIcon, iconColor: hasEmptyFields ? AppColors.greyTextColor : AppColors.whiteColor, + isDisabled: getIt.get().isVitalSignLoading || hasEmptyFields, onPressed: hasEmptyFields ? () {} // Empty function for disabled state : () => context.navigateWithName(AppRoutes.organSelectorPage), diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart index 65cf5a5..ce8087d 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/height_selection_page.dart @@ -1,9 +1,6 @@ -import 'dart:developer'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.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/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -154,14 +151,14 @@ class HeightSelectionPage extends StatelessWidget { ), Expanded( child: HeightScale( + key: ValueKey(viewModel.isHeightCm), enableHaptic: true, enableSound: true, minValue: minValue, maxValue: maxValue, - initialHeight: viewModel.selectedHeight ?? 100, + initialHeight: (viewModel.selectedHeight ?? 100).toDouble(), isCm: viewModel.isHeightCm, onHeightChanged: (newHeight) { - log("height: $newHeight"); viewModel.setHeight(newHeight, viewModel.isHeightCm); }, ), diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart index 8c83796..6e68eef 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/weight_selection_page.dart @@ -150,7 +150,7 @@ class WeightSelectionPage extends StatelessWidget { enableSound: true, minValue: minValue, maxValue: maxValue, - initialWeight: viewModel.selectedWeight!, + initialWeight: (viewModel.selectedWeight ?? 70).toDouble(), isKg: isKg, onWeightChanged: (newWeight) { log("weight: $newWeight"); diff --git a/lib/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart b/lib/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart index 29baf64..e09380e 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/widgets/height_scale.dart @@ -43,10 +43,17 @@ class _HeightScaleState extends State { // Get increment based on unit (CM = 1.0, FT = 0.1) double get _increment => widget.isCm ? 1.0 : 0.1; + late final int _childCount; + @override void initState() { super.initState(); + // prepare child count and initial index using normal order (min at index 0) + _childCount = ((widget.maxValue - widget.minValue) / _increment).round() + 1; + // initialIndex should point to position of initialHeight in normal list int initialIndex = ((widget.initialHeight - widget.minValue) / _increment).round(); + if (initialIndex < 0) initialIndex = 0; + if (initialIndex >= _childCount) initialIndex = _childCount - 1; _scrollController = FixedExtentScrollController(initialItem: initialIndex); } @@ -95,19 +102,21 @@ class _HeightScaleState extends State { perspective: 0.001, physics: const BouncingScrollPhysics(), onSelectedItemChanged: (index) { + // Map index (0..n-1) to value normal: value = min + index*increment final selectedValue = widget.minValue + (index * _increment); widget.onHeightChanged?.call(selectedValue); _scheduleFeedback(); }, childDelegate: ListWheelChildBuilderDelegate( - childCount: ((widget.maxValue - widget.minValue) / _increment).round() + 1, + childCount: _childCount, builder: (context, index) { + // Displayed height value for this index (normal order) final height = widget.minValue + (index * _increment); // For CM: main mark every 10, mid mark every 5 // For FT: main mark every 1.0 (10 ticks), mid mark every 0.5 (5 ticks) - final isMainMark = widget.isCm ? height % 10 == 0 : (height * 10).round() % 10 == 0; - final isMidMark = widget.isCm ? height % 5 == 0 : (height * 10).round() % 5 == 0; + final isMainMark = widget.isCm ? height.round() % 10 == 0 : ((height * 10).round() % 10 == 0); + final isMidMark = widget.isCm ? height.round() % 5 == 0 : ((height * 10).round() % 5 == 0); return SizedBox( width: 100.w, diff --git a/lib/presentation/tele_consultation/zoom/call_screen.dart b/lib/presentation/tele_consultation/zoom/call_screen.dart index 59b5ce4..59be70e 100644 --- a/lib/presentation/tele_consultation/zoom/call_screen.dart +++ b/lib/presentation/tele_consultation/zoom/call_screen.dart @@ -352,43 +352,44 @@ class _CallScreenState extends State { ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); if (data['status'] == RecordingStatus.Start) { if (mySelf != null && !mySelf.isHost!) { - showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( - content: const Text('The session is being recorded.'), - actions: [ - TextButton( - onPressed: () async { - await zoom.acceptRecordingConsent(); - if (context.mounted) { - Navigator.pop(context); - } - ; - }, - child: const Text('accept'), - ), - TextButton( - onPressed: () async { - String currentConsentType = await zoom.getRecordingConsentType(); - if (currentConsentType == ConsentType.ConsentType_Individual) { - await zoom.declineRecordingConsent(); - Navigator.pop(context); - } else { - await zoom.declineRecordingConsent(); - zoom.leaveSession(false); - if (!context.mounted) return; - Navigator.popAndPushNamed( - context, - "Join", - arguments: JoinArguments(args.isJoin, sessionName.value, sessionPassword.value, args.displayName, args.sessionIdleTimeoutMins, args.role), - ); - } - }, - child: const Text('decline'), - ), - ], - ), - ); + await zoom.acceptRecordingConsent(); + // showDialog( + // context: context, + // builder: (BuildContext context) => AlertDialog( + // content: const Text('The session is being recorded.'), + // actions: [ + // TextButton( + // onPressed: () async { + // await zoom.acceptRecordingConsent(); + // if (context.mounted) { + // Navigator.pop(context); + // } + // ; + // }, + // child: const Text('accept'), + // ), + // TextButton( + // onPressed: () async { + // String currentConsentType = await zoom.getRecordingConsentType(); + // if (currentConsentType == ConsentType.ConsentType_Individual) { + // await zoom.declineRecordingConsent(); + // Navigator.pop(context); + // } else { + // await zoom.declineRecordingConsent(); + // zoom.leaveSession(false); + // if (!context.mounted) return; + // Navigator.popAndPushNamed( + // context, + // "Join", + // arguments: JoinArguments(args.isJoin, sessionName.value, sessionPassword.value, args.displayName, args.sessionIdleTimeoutMins, args.role), + // ); + // } + // }, + // child: const Text('decline'), + // ), + // ], + // ), + // ); } isRecordingStarted.value = true; } else { @@ -1745,11 +1746,11 @@ class _CallScreenState extends State { iconSize: circleButtonSize, tooltip: isMuted.value == true ? "Unmute" : "Mute", ), - IconButton( - onPressed: onPressShare, - icon: isSharing.value ? Image.asset("assets/images/png/zoom/share-off@2x.png") : Image.asset("assets/images/png/zoom/share-on@2x.png"), - iconSize: circleButtonSize, - ), + // IconButton( + // onPressed: onPressShare, + // icon: isSharing.value ? Image.asset("assets/images/png/zoom/share-off@2x.png") : Image.asset("assets/images/png/zoom/share-on@2x.png"), + // iconSize: circleButtonSize, + // ), IconButton( onPressed: onPressVideo, iconSize: circleButtonSize, @@ -1771,35 +1772,35 @@ class _CallScreenState extends State { ], ), )), - Container( - margin: const EdgeInsets.only(left: 16, right: 16, bottom: 40, top: 10), - alignment: Alignment.bottomCenter, - child: SizedBox( - height: MediaQuery.of(context).viewInsets.bottom == 0 ? 65 : MediaQuery.of(context).viewInsets.bottom + 18, - child: TextField( - maxLines: 1, - textAlign: TextAlign.left, - style: TextStyle(color: chatTextColor), - cursorColor: chatTextColor, - textAlignVertical: TextAlignVertical.center, - controller: chatMessageController, - decoration: InputDecoration( - contentPadding: const EdgeInsets.only(left: 16, top: 10, bottom: 10, right: 16), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(width: 1, color: chatTextColor), //<-- SEE HERE - ), - hintText: 'Type your message here...', - hintStyle: TextStyle( - fontSize: 14.0, - color: chatTextColor, - ), - ), - onSubmitted: (String str) { - sendChatMessage(str); - }, - ), - ), - ), + // Container( + // margin: const EdgeInsets.only(left: 16, right: 16, bottom: 40, top: 10), + // alignment: Alignment.bottomCenter, + // child: SizedBox( + // height: MediaQuery.of(context).viewInsets.bottom == 0 ? 65 : MediaQuery.of(context).viewInsets.bottom + 18, + // child: TextField( + // maxLines: 1, + // textAlign: TextAlign.left, + // style: TextStyle(color: chatTextColor), + // cursorColor: chatTextColor, + // textAlignVertical: TextAlignVertical.center, + // controller: chatMessageController, + // decoration: InputDecoration( + // contentPadding: const EdgeInsets.only(left: 16, top: 10, bottom: 10, right: 16), + // enabledBorder: OutlineInputBorder( + // borderSide: BorderSide(width: 1, color: chatTextColor), //<-- SEE HERE + // ), + // hintText: 'Type your message here...', + // hintStyle: TextStyle( + // fontSize: 14.0, + // color: chatTextColor, + // ), + // ), + // onSubmitted: (String str) { + // sendChatMessage(str); + // }, + // ), + // ), + // ), Container( alignment: Alignment.bottomLeft, margin: const EdgeInsets.only(bottom: 120), diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index fe07186..b34bf21 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -103,7 +103,7 @@ class _AncillaryOrderPaymentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.visaOrMastercard.tr(context: context).toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), + Image.asset(AppAssets.mada, width: 50.h, height: 50.h), SizedBox(height: 16.h), LocaleKeys.mada.tr(context: context).toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), ], @@ -468,86 +468,43 @@ class _AncillaryOrderPaymentPageState extends State { languageID: appState.isArabic() ? 1 : 2, onSuccess: (response) { LoaderBottomSheet.hideLoader(); - final invoiceNo = response['AncillaryOrderInvoiceList']?[0]?['InvoiceNo']; - _showSuccessDialog(invoiceNo); }, onError: (error) { LoaderBottomSheet.hideLoader(); todoSectionViewModel.setProcessingPayment(false); - Utils.showToast(error); + showCommonBottomSheetWithoutHeight(context, + child: Utils.getErrorWidget(loadingText: error.toString()), isCloseButtonVisible: true, isDismissible: true, isFullScreen: false, callBackFunc: () {}); }, ); } void _showSuccessDialog(dynamic invoiceNo) { todoSectionViewModel.setProcessingPayment(false); - - log("Ancillary order payment successful! Invoice #: $invoiceNo"); - // Show success message and navigate - Utils.showToast("Payment successful! Invoice #: $invoiceNo"); + // Utils.showToast("Payment successful! Invoice #: $invoiceNo"); // Navigate back to home after a short delay - Future.delayed(Duration(seconds: 1), () { - showCommonBottomSheetWithoutHeight( - context, - child: Column( - children: [ - Row( - children: [ - LocaleKeys.hereIsYourInvoiceNumber.tr(context: context).toText14( - color: AppColors.textColorLight, - weight: FontWeight.w500, - ), - SizedBox(width: 4.w), - ("12345").toText16(isBold: true), - ], - ), - SizedBox(height: 24.h), - Row( - children: [ - Expanded( - child: CustomButton( - height: 56.h, - text: LocaleKeys.ok.tr(context: context), - onPressed: () { - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - ), - ), - ], - ), - ], - ), - // title: LocaleKeys.paymentCompletedSuccessfully.tr(context: context), - titleWidget: Utils.getSuccessWidget(loadingText: LocaleKeys.paymentCompletedSuccessfully.tr(context: context)), - isCloseButtonVisible: false, - isDismissible: false, - isFullScreen: false, - ); + // Future.delayed(Duration(seconds: 1), () { + showCommonBottomSheetWithoutHeight(context, + child: Utils.getSuccessWidget(loadingText: "${LocaleKeys.paymentCompletedSuccessfully.tr(context: context)}, ${LocaleKeys.hereIsYourInvoiceNumber.tr(context: context)}$invoiceNo"), + isCloseButtonVisible: true, + isDismissible: true, + isFullScreen: false, callBackFunc: () { + // Pop until we reach the LandingPage + todoSectionViewModel.setIsAncillaryOrdersNeedReloading(true); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); }); + // }); } _startApplePay() async { - showCommonBottomSheet( - context, - child: Utils.getLoadingWidget(), - callBackFunc: (str) {}, - title: "", - height: ResponsiveExtension.screenHeight * 0.3, - isCloseButtonVisible: false, - isDismissible: false, - isFullScreen: false, - ); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingApplePayDetails.tr(context: context)); final user = appState.getAuthenticatedUser(); transID = Utils.getAdvancePaymentTransID(widget.projectID, user!.patientId!); @@ -604,7 +561,7 @@ class _AncillaryOrderPaymentPageState extends State { await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest); } catch (error) { log("Apple Pay Insert Request Failed: $error"); - Navigator.of(context).pop(); + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: LocaleKeys.failedToInitializeApplePay.tr(context: context)), @@ -627,7 +584,7 @@ class _AncillaryOrderPaymentPageState extends State { currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", onFailed: (failureResult) async { log("failureResult: ${failureResult.message.toString()}"); - Navigator.of(context).pop(); + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), @@ -637,7 +594,7 @@ class _AncillaryOrderPaymentPageState extends State { ); }, onSucceeded: (successResult) async { - Navigator.of(context).pop(); + LoaderBottomSheet.hideLoader(); log("successResult: ${successResult.responseMessage.toString()}"); selectedPaymentMethod = successResult.paymentOption ?? "VISA"; _checkPaymentStatus(); diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart index 980357d..75b6ec9 100644 --- a/lib/presentation/vital_sign/vital_sign_details_page.dart +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -148,6 +148,7 @@ class _VitalSignDetailsPageState extends State { isBold: true, color: scheme.iconFg, letterSpacing: -2, + isEnglishOnly: true ), ), SizedBox(width: 4.h), diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart index e457023..af8f5ab 100644 --- a/lib/presentation/vital_sign/vital_sign_page.dart +++ b/lib/presentation/vital_sign/vital_sign_page.dart @@ -3,6 +3,8 @@ import 'dart:ui'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -330,6 +332,9 @@ class _VitalSignPageState extends State { weight: FontWeight.w600, ), ), + Utils.buildSvgWithAssets( + icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, + width: 18.w, height: 18.h), ], ), SizedBox(height: 14.h), @@ -353,6 +358,7 @@ class _VitalSignPageState extends State { value.toText17( isBold: true, color: AppColors.textColor, + isEnglishOnly: true ), if (unit.isNotEmpty) ...[ SizedBox(width: 3.w), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 9cf0ed9..fd16a2d 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -14,9 +14,11 @@ import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts import 'package:hmg_patient_app_new/presentation/health_trackers/add_health_tracker_entry_page.dart'; import 'package:hmg_patient_app_new/presentation/health_trackers/health_tracker_detail_page.dart'; import 'package:hmg_patient_app_new/presentation/health_trackers/health_trackers_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/app_update_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; +import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart'; import 'package:hmg_patient_app_new/presentation/smartwatches/huawei_health_example.dart'; import 'package:hmg_patient_app_new/presentation/smartwatches/smartwatch_home_page.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart'; @@ -60,6 +62,7 @@ class AppRoutes { static const String huaweiHealthExample = '/huaweiHealthExample'; static const String covid19Test = '/covid19Test'; static const String vitalSign = '/vitalSign'; + static const String appUpdate = '/appUpdate'; //appointments static const String bookAppointmentPage = '/bookAppointmentPage'; @@ -88,6 +91,9 @@ class AppRoutes { // Emergency Services static const String emergencyServicesPage = '/emergencyServicesPage'; + // Services Price List + static const String servicesPriceListPage = '/servicesPriceListPage'; + static Map get routes => { initialRoute: (context) => SplashPage(), loginScreen: (context) => LoginScreen(), @@ -119,7 +125,11 @@ class AppRoutes { healthConvertersPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.converter), healthTrackersPage: (context) => HealthTrackersPage(), vitalSign: (context) => VitalSignPage(), + appUpdate: (context) => AppUpdatePage( + appUpdateMessage: '', + ), emergencyServicesPage: (context) => EmergencyServicesPage(), + servicesPriceListPage: (context) => ServicesPriceListPage(), addHealthTrackerEntryPage: (context) { final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?; return AddHealthTrackerEntryPage( diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 0b3a521..6347a38 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -23,7 +23,7 @@ abstract class DialogService { Future showExceptionBottomSheet({required String message, required Function() onOkPressed, Function()? onCancelPressed}); - Future showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}); + Future showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}); Future showSuccessBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}); @@ -101,27 +101,38 @@ class DialogServiceImp implements DialogService { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), - builder: (_) => ExceptionBottomSheet( - message: message, - showCancel: onCancelPressed != null ? true : false, - onOkPressed: onOkPressed, - onCancelPressed: () { - if (onCancelPressed != null) { - Navigator.of(context).pop(); - } + builder: (_) => StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return ExceptionBottomSheet( + message: message, + showCancel: onCancelPressed != null ? true : false, + onOkPressed: onOkPressed, + onCancelPressed: () { + if (onCancelPressed != null) { + Navigator.of(context).pop(); + } + }, + ); }, ), ); } @override - Future showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}) async { + Future showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}) async { final context = navigationService.navigatorKey.currentContext; if (context == null) return; showCommonBottomSheetWithoutHeight( context, title: label ?? "", - child: exceptionBottomSheetWidget(context: context, message: message, onOkPressed: onOkPressed, onCancelPressed: onCancelPressed), + child: exceptionBottomSheetWidget( + context: context, + message: message, + okLabel: okLabel, + cancelLabel: cancelLabel, + onOkPressed: onOkPressed, + onCancelPressed: onCancelPressed, + ), callBackFunc: () {}, ); } @@ -174,12 +185,8 @@ class DialogServiceImp implements DialogService { textColor: AppColors.primaryRedBorderColor, height: 48.h, onPressed: () { - navigationService.pushPage( - page: FamilyMedicalScreen( - profiles: profiles, - onSelect: (FamilyFileResponseModelLists p1) {}, - ), - ); + navigationService.pop(); + navigationService.pushPage(page: FamilyMedicalScreen()); }) ], ), @@ -231,7 +238,7 @@ class DialogServiceImp implements DialogService { } } -Widget exceptionBottomSheetWidget({required BuildContext context, required String message, required Function() onOkPressed, Function()? onCancelPressed}) { +Widget exceptionBottomSheetWidget({required BuildContext context, required String message, String? okLabel, String? cancelLabel, required Function() onOkPressed, Function()? onCancelPressed}) { return Column( children: [ (message).toText16(isBold: false, color: AppColors.textColor), @@ -242,7 +249,7 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin children: [ Expanded( child: CustomButton( - text: LocaleKeys.cancel.tr(context: context), + text: cancelLabel ?? LocaleKeys.cancel.tr(context: context), onPressed: () { Navigator.of(context).pop(); }, @@ -256,7 +263,7 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin SizedBox(width: 10.h), Expanded( child: CustomButton( - text: LocaleKeys.confirm.tr(context: context), + text: okLabel ?? LocaleKeys.confirm.tr(context: context), onPressed: onOkPressed, backgroundColor: AppColors.bgGreenColor, borderColor: AppColors.bgGreenColor, @@ -270,7 +277,7 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin Padding( padding: EdgeInsets.only(bottom: 10.h), child: CustomButton( - text: LocaleKeys.cancel.tr(context: context), + text: cancelLabel ?? LocaleKeys.cancel.tr(context: context), onPressed: (onCancelPressed == null) ? () { context.pop(); diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index fe95140..da4a75b 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -34,6 +34,13 @@ class NavigationService { ); } + void replaceAllRoutesAndNavigateToLandingAuth() { + navigatorKey.currentState?.pushNamedAndRemoveUntil( + AppRoutes.landingScreen, + (Route route) => false, + ); + } + void pushAndReplace(String routeName) { navigatorKey.currentState?.pushReplacementNamed(routeName); } diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 49680a2..dc7a7c9 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -47,6 +47,7 @@ class _SplashScreenState extends State { PushNotificationHandler().init(context); // Asyncronously }, ); + // TODO: This next line should be tested. This stops the app at this step, sometimes await authVm.getServicePrivilege(); Timer(Duration(seconds: 2, milliseconds: 500), () async { bool isAppOpenedFromCall = getIt.get().getBool(key: CacheConst.isAppOpenedFromCall) ?? false; diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index d56178b..f80085a 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -113,4 +113,6 @@ class AppColors { static const Color covid29Color = Color(0xff2563EB); // #2563EB static const Color lightGreyTextColor = Color(0xFF959595); static const Color labelColorYellow = Color(0xFFFBCB6E); + + static const LinearGradient aiLinearGradient = LinearGradient(colors: [Color(0xFF8A38F5), Color(0xFFE20BBB)], begin: Alignment.topLeft, end: Alignment.bottomRight); } diff --git a/lib/widgets/app_language_change.dart b/lib/widgets/app_language_change.dart index c94195a..1200c53 100644 --- a/lib/widgets/app_language_change.dart +++ b/lib/widgets/app_language_change.dart @@ -4,9 +4,14 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_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/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:provider/provider.dart'; class AppLanguageChange extends StatefulWidget { AppLanguageChange({Key? key}) : super(key: key); @@ -20,6 +25,11 @@ class AppLanguageChange extends StatefulWidget { class _AppLanguageChangeState extends State { String? selectedValue; + late MyAppointmentsViewModel myAppointmentsViewModel; + late ImmediateLiveCareViewModel immediateLiveCareViewModel; + late EmergencyServicesViewModel emergencyServicesViewModel; + late TodoSectionViewModel todoSectionViewModel; + @override void initState() { super.initState(); @@ -33,6 +43,12 @@ class _AppLanguageChangeState extends State { @override Widget build(BuildContext context) { selectedValue ??= context.locale.languageCode; + + myAppointmentsViewModel = Provider.of(context, listen: false); + immediateLiveCareViewModel = Provider.of(context, listen: false); + emergencyServicesViewModel = Provider.of(context, listen: false); + todoSectionViewModel = Provider.of(context, listen: false); + return Column( spacing: 24.h, children: [ @@ -51,6 +67,18 @@ class _AppLanguageChangeState extends State { text: LocaleKeys.save.tr(context: context), onPressed: () { context.setLocale(selectedValue == 'en' ? Locale('en', 'US') : Locale('ar', 'SA')).then((val) { + //Reload My Appointments Data + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + + //Reload Immediate LiveCare Data + immediateLiveCareViewModel.initImmediateLiveCare(); + immediateLiveCareViewModel.getPatientLiveCareHistory(); + + //Reload Emergency Services Data + emergencyServicesViewModel.checkPatientERAdvanceBalance(); + Navigator.pop(context); }); }), @@ -75,7 +103,7 @@ class _AppLanguageChangeState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - title.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + title.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "ar" ? 'GESSTwo' : 'Poppins').expanded, ], ).paddingOnly(left: 16, right: 16).onPress(() { setState(() { diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 575de03..2522196 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -19,6 +19,7 @@ class CollapsingListView extends StatelessWidget { VoidCallback? search; VoidCallback? report; VoidCallback? logout; + VoidCallback? aiOverview; VoidCallback? history; VoidCallback? instructions; VoidCallback? requests; @@ -40,6 +41,7 @@ class CollapsingListView extends StatelessWidget { this.bottomChild, this.report, this.logout, + this.aiOverview, this.history, this.instructions, this.requests, @@ -85,9 +87,11 @@ class CollapsingListView extends StatelessWidget { history: history, instructions: instructions, requests: requests, - sendEmail: sendEmail, doctorResponse: doctorResponse, + sendEmail: sendEmail, + doctorResponse: doctorResponse, bottomChild: bottomChild, trailing: trailing, + aiOverview: aiOverview, ), // : null, leading: Visibility( @@ -190,6 +194,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget VoidCallback? search; VoidCallback? report; VoidCallback? logout; + VoidCallback? aiOverview; VoidCallback? history; VoidCallback? instructions; VoidCallback? requests; @@ -206,6 +211,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget this.search, this.report, this.logout, + this.aiOverview, this.history, this.instructions, this.requests, @@ -288,6 +294,7 @@ class _ScrollAnimatedTitleState extends State { 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.trailing != null) widget.trailing!, ] ], @@ -295,40 +302,77 @@ class _ScrollAnimatedTitleState extends State { ); } - Widget actionButton(BuildContext context, double t, {required String title, required String icon}) { - return AnimatedSize( - duration: Duration(milliseconds: 150), - child: Container( - height: 40.h, - padding: EdgeInsets.all(8.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.secondaryLightRedColor, - borderRadius: 10.r, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - spacing: 8.h, - children: [ - Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor, height: 24.h, width: 24.w), - if (t >= .5) - Text( - title, - style: context.dynamicTextStyle( - color: AppColors.primaryRedColor, - letterSpacing: -0.4, - fontSize: (12 - (2 * (1 - t))).f, - fontWeight: FontWeight.lerp( - FontWeight.w400, - FontWeight.w500, - t, - )!, + Widget actionButton(BuildContext context, double t, {required String title, required String icon, bool isAiButton = false}) { + return isAiButton + ? AnimatedSize( + duration: Duration(milliseconds: 150), + child: Center( + child: Container( + height: 40.h, + padding: EdgeInsets.all(8.w), + 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), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + spacing: 8.h, + children: [ + Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor, height: 24.h, width: 24.w), + Text( + title, + style: context.dynamicTextStyle( + color: AppColors.primaryRedColor, + letterSpacing: -0.4, + fontSize: (14 - (2 * (1 - t))).f, + fontWeight: FontWeight.lerp( + FontWeight.w400, + FontWeight.w500, + t, + )!, + ), + ), + ], + ), ), ), - ], - ), - ), - ); + ), + ) + : AnimatedSize( + duration: Duration(milliseconds: 150), + child: Container( + height: 40.h, + padding: EdgeInsets.all(8.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.secondaryLightRedColor, + borderRadius: 10.r, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + spacing: 8.h, + children: [ + Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor, height: 24.h, width: 24.w), + if (t >= .5) + Text( + title, + style: context.dynamicTextStyle( + color: AppColors.primaryRedColor, + letterSpacing: -0.4, + fontSize: (12 - (2 * (1 - t))).f, + fontWeight: FontWeight.lerp( + FontWeight.w400, + FontWeight.w500, + t, + )!, + ), + ), + ], + ), + ), + ); } } diff --git a/lib/widgets/bottom_navigation/bottom_navigation.dart b/lib/widgets/bottom_navigation/bottom_navigation.dart index bbe8d81..46067df 100644 --- a/lib/widgets/bottom_navigation/bottom_navigation.dart +++ b/lib/widgets/bottom_navigation/bottom_navigation.dart @@ -33,7 +33,7 @@ class BottomNavigation extends StatelessWidget { ), appState.isAuthenticated // ? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr(context: context)) - ? BottomNavItem(icon: AppAssets.symptomCheckerBottomIcon, fillIcon: AppAssets.symptomCheckerBottomFillIcon, label: "Symptoms") + ? BottomNavItem(icon: AppAssets.symptomCheckerBottomIcon, fillIcon: AppAssets.symptomCheckerBottomFillIcon, label: LocaleKeys.symptoms.tr(context: context)) : BottomNavItem(icon: AppAssets.news, fillIcon: AppAssets.newsFill, label: LocaleKeys.news.tr()), BottomNavItem(icon: AppAssets.servicesBottom, fillIcon: AppAssets.servicesBottomFill, label: LocaleKeys.services2.tr(context: context)), ]; diff --git a/lib/widgets/bottomsheet/exception_bottom_sheet.dart b/lib/widgets/bottomsheet/exception_bottom_sheet.dart index fe23264..16531bd 100644 --- a/lib/widgets/bottomsheet/exception_bottom_sheet.dart +++ b/lib/widgets/bottomsheet/exception_bottom_sheet.dart @@ -10,7 +10,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; -class ExceptionBottomSheet extends StatelessWidget { +class ExceptionBottomSheet extends StatefulWidget { final String? title; final String message; final bool showOKButton; @@ -28,6 +28,11 @@ class ExceptionBottomSheet extends StatelessWidget { this.onCancelPressed, }); + @override + State createState() => _ExceptionBottomSheetState(); +} + +class _ExceptionBottomSheetState extends State { @override Widget build(BuildContext context) { return SafeArea( @@ -53,7 +58,7 @@ class ExceptionBottomSheet extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - (title ?? LocaleKeys.notice.tr()).toText28(), + (widget.title ?? LocaleKeys.notice.tr()).toText28(), InkWell( onTap: () { Navigator.of(context).pop(); @@ -66,18 +71,18 @@ class ExceptionBottomSheet extends StatelessWidget { ], ), SizedBox(height: 10.h), - (message).toText16(isBold: false, color: AppColors.textColor), + (widget.message).toText16(isBold: false, color: AppColors.textColor), SizedBox(height: 10.h), SizedBox(height: 24.h), - if (showOKButton && showCancel) + if (widget.showOKButton && widget.showCancel) Row( children: [ Expanded( child: CustomButton( height: 56.h, text: LocaleKeys.cancel.tr(), - onPressed: onCancelPressed != null - ? onCancelPressed! + onPressed: widget.onCancelPressed != null + ? widget.onCancelPressed! : () { Navigator.of(context).pop(); }, @@ -92,8 +97,8 @@ class ExceptionBottomSheet extends StatelessWidget { Expanded( child: CustomButton( height: 56.h, - text: showCancel ? LocaleKeys.confirm.tr() : LocaleKeys.ok.tr(), - onPressed: onOkPressed, + text: widget.showCancel ? LocaleKeys.confirm.tr() : LocaleKeys.ok.tr(), + onPressed: widget.onOkPressed, backgroundColor: AppColors.bgGreenColor, borderColor: AppColors.bgGreenColor, textColor: Colors.white, @@ -102,13 +107,13 @@ class ExceptionBottomSheet extends StatelessWidget { ), ], ), - if (showOKButton && !showCancel) + if (widget.showOKButton && !widget.showCancel) Padding( padding: EdgeInsets.only(bottom: 10.h), child: CustomButton( height: 56.h, text: LocaleKeys.ok.tr(), - onPressed: onOkPressed, + onPressed: widget.onOkPressed, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedBorderColor, textColor: Colors.white, diff --git a/lib/widgets/bottomsheet/generic_bottom_sheet.dart b/lib/widgets/bottomsheet/generic_bottom_sheet.dart index b6be36c..fd79b7c 100644 --- a/lib/widgets/bottomsheet/generic_bottom_sheet.dart +++ b/lib/widgets/bottomsheet/generic_bottom_sheet.dart @@ -158,9 +158,10 @@ class GenericBottomSheetState extends State { prefix: widget.isForEmail ? null : widget.countryCode, isBorderAllowed: false, isAllowLeadingIcon: true, - fontSize: 13, + fontSize: 18.f, isCountryDropDown: widget.isEnableCountryDropdown, leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone, + fontFamily: "Poppins", ) : SizedBox(), ], diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index fb51f28..515d1f7 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -183,6 +183,7 @@ void showCommonBottomSheetWithoutHeight( isScrollControlled: true, showDragHandle: false, isDismissible: isDismissible, + enableDrag: isDismissible, backgroundColor: backgroundColor, useSafeArea: useSafeArea, builder: (BuildContext context) { diff --git a/lib/widgets/dropdown/country_dropdown_widget.dart b/lib/widgets/dropdown/country_dropdown_widget.dart index b6191ed..1cd387f 100644 --- a/lib/widgets/dropdown/country_dropdown_widget.dart +++ b/lib/widgets/dropdown/country_dropdown_widget.dart @@ -100,7 +100,7 @@ class CustomCountryDropdownState extends State { children: [ Text( selectedCountry!.countryCode, - style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5), + style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"), ), SizedBox(width: 4.h), if (widget.isEnableTextField) @@ -111,7 +111,7 @@ class CustomCountryDropdownState extends State { alignment: Alignment.centerLeft, child: TextField( focusNode: textFocusNode, - style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5), + style: TextStyle(fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.4, height: 1.5, fontFamily: "Poppins"), decoration: InputDecoration(hintText: "", isDense: true, border: InputBorder.none, contentPadding: EdgeInsets.zero), keyboardType: TextInputType.phone, onChanged: widget.onPhoneNumberChanged, diff --git a/lib/widgets/family_files/family_file_add_widget.dart b/lib/widgets/family_files/family_file_add_widget.dart index abc529b..15751e9 100644 --- a/lib/widgets/family_files/family_file_add_widget.dart +++ b/lib/widgets/family_files/family_file_add_widget.dart @@ -14,24 +14,29 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; -class FamilyFileAddWidget extends StatelessWidget { +class FamilyFileAddWidget extends StatefulWidget { final Function()? onVerificationPress; final String message; const FamilyFileAddWidget(this.onVerificationPress, this.message, {super.key}); + @override + State createState() => _FamilyFileAddWidgetState(); +} + +class _FamilyFileAddWidgetState extends State { @override Widget build(BuildContext context) { AuthenticationViewModel authVm = getIt.get(); - MedicalFileViewModel? medicalVM = getIt.get(); // TODO: implement build return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - message.toText16(color: AppColors.textColor, weight: FontWeight.w500), + widget.message.toText16(color: AppColors.textColor, weight: FontWeight.w500), SizedBox(height: 20.h), Container( decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)), @@ -54,24 +59,31 @@ class FamilyFileAddWidget extends StatelessWidget { isAllowLeadingIcon: true, autoFocus: true, keyboardType: TextInputType.number, + fontFamily: "Poppins", padding: EdgeInsets.symmetric(vertical: 8.h), leadingIcon: AppAssets.student_card, ).paddingOnly(top: 8.h, bottom: 8.h), Divider(height: 1.h, color: AppColors.spacerLineColor), - TextInputWidget( - labelText: LocaleKeys.phoneNumber.tr(), - hintText: "", - controller: authVm.phoneNumberController, - isEnable: true, - prefix: authVm.selectedCountrySignup.countryCode, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - autoFocus: true, - keyboardType: TextInputType.number, - padding: EdgeInsets.symmetric(vertical: 8.h), - leadingIcon: AppAssets.smart_phone, - ).paddingOnly(top: 8.h, bottom: 4.h), + Selector( + selector: (_, model) => model.selectedCountrySignup.countryCode, + builder: (context, countryCode, child) { + return TextInputWidget( + labelText: LocaleKeys.phoneNumber.tr(), + hintText: "", + controller: authVm.phoneNumberController, + isEnable: true, + prefix: countryCode, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + keyboardType: TextInputType.number, + fontFamily: "Poppins", + padding: EdgeInsets.symmetric(vertical: 8.h), + leadingIcon: AppAssets.smart_phone, + ).paddingOnly(top: 8.h, bottom: 4.h); + }, + ), ], ), ), @@ -89,8 +101,8 @@ class FamilyFileAddWidget extends StatelessWidget { }, )) { // authVm.addFamilyMember(otpTypeEnum: OTPTypeEnum.sms, isExcludedUser: true); - if (onVerificationPress != null) { - onVerificationPress!(); + if (widget.onVerificationPress != null) { + widget.onVerificationPress!(); } } }, diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index cf1b132..aae196c 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -11,6 +12,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; import 'package:hmg_patient_app_new/widgets/time_picker_widget.dart'; +import 'dart:ui' as ui; class TextInputWidget extends StatelessWidget { final String labelText; @@ -48,6 +50,8 @@ class TextInputWidget extends StatelessWidget { final bool? isHideSwitcher; final bool? isArrowTrailing; + final String? fontFamily; + // final List countryList; // final Function(Country)? onCountryChange; @@ -86,6 +90,7 @@ class TextInputWidget extends StatelessWidget { this.maxLines = 6, this.isHideSwitcher, this.isArrowTrailing, + this.fontFamily, // this.countryList = const [], // this.onCountryChange, }); @@ -226,11 +231,8 @@ class TextInputWidget extends StatelessWidget { initialDate: DateTime.now(), showCalendarToggle: isHideSwitcher == true ? false : true, fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "Poppins", - okWidget: - Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), - cancelWidget: Padding( - padding: EdgeInsets.only(right: 8.h), - child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), + okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), + cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), onCalendarTypeChanged: (bool value) { isGregorian = value; }); @@ -300,50 +302,103 @@ class TextInputWidget extends StatelessWidget { Widget _buildTextField(BuildContext context) { double fontS = fontSize ?? 14.f; - return TextField( - enabled: isEnable, - scrollPadding: EdgeInsets.zero, - keyboardType: isMultiline ? TextInputType.multiline : keyboardType, - controller: controller, - readOnly: isReadOnly, - textAlignVertical: TextAlignVertical.top, - textAlign: TextAlign.left, - textDirection: TextDirection.ltr, - onChanged: onChange, - focusNode: focusNode ?? _focusNode, - autofocus: autoFocus, - textInputAction: TextInputAction.done, - cursorHeight: isWalletAmountInput! ? 40.h : 20.h, - onTapOutside: (event) { - FocusManager.instance.primaryFocus?.unfocus(); + return Builder( + builder: (context) { + return Localizations.override( + context: context, + locale: const Locale('en', 'US'), // Force English locale for TextField + child: TextField( + hintLocales: const [Locale('en', 'US')], + enabled: isEnable, + scrollPadding: EdgeInsets.zero, + keyboardType: isMultiline ? TextInputType.multiline : keyboardType, + controller: controller, + readOnly: isReadOnly, + textAlignVertical: TextAlignVertical.top, + textAlign: TextAlign.left, + textDirection: TextDirection.ltr, + onChanged: onChange, + focusNode: focusNode ?? _focusNode, + autofocus: autoFocus, + textInputAction: TextInputAction.done, + cursorHeight: isWalletAmountInput! ? 40.h : 20.h, + onTapOutside: (event) { + FocusManager.instance.primaryFocus?.unfocus(); + }, + onSubmitted: onSubmitted, + minLines: isMultiline ? minLines : 1, + maxLines: isMultiline ? maxLines : 1, + style: TextStyle( + fontSize: fontS, + height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), + fontWeight: FontWeight.w500, + color: AppColors.textColor, + letterSpacing: -1, + // fontFamily: keyboardType == TextInputType.number ? getIt.get().isArabic() ? 'GESSTwo' : 'Poppins' : 'Poppins', + fontFamily: fontFamily, + locale: const Locale('en', 'US'), // Force English locale for text style + ), + decoration: InputDecoration( + isDense: true, + hintText: hintText, + hintStyle: TextStyle(fontSize: 14.f, height: 21 / 16, fontWeight: FontWeight.w500, color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), letterSpacing: -0.75), + prefixIconConstraints: BoxConstraints(minWidth: 30.h), + prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500), + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + ), + ); }, - onSubmitted: onSubmitted, - minLines: isMultiline ? minLines : 1, - maxLines: isMultiline ? maxLines : 1, - style: TextStyle( - fontSize: fontS, - height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), - fontWeight: FontWeight.w500, - color: AppColors.textColor, - letterSpacing: -1, - ), - decoration: InputDecoration( - isDense: true, - hintText: hintText, - hintStyle: TextStyle( - fontSize: 14.f, - height: 21 / 16, - fontWeight: FontWeight.w500, - color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), - letterSpacing: -0.75), - prefixIconConstraints: BoxConstraints(minWidth: 30.h), - prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500), - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - ), ); + + // TextField( + // hintLocales: const [Locale('en', 'US')], + // enabled: isEnable, + // scrollPadding: EdgeInsets.zero, + // keyboardType: isMultiline ? TextInputType.multiline : keyboardType, + // controller: controller, + // readOnly: isReadOnly, + // textAlignVertical: TextAlignVertical.top, + // textAlign: TextAlign.left, + // textDirection: TextDirection.ltr, + // onChanged: onChange, + // focusNode: focusNode ?? _focusNode, + // autofocus: autoFocus, + // textInputAction: TextInputAction.done, + // cursorHeight: isWalletAmountInput! ? 40.h : 20.h, + // onTapOutside: (event) { + // FocusManager.instance.primaryFocus?.unfocus(); + // }, + // onSubmitted: onSubmitted, + // minLines: isMultiline ? minLines : 1, + // maxLines: isMultiline ? maxLines : 1, + // style: TextStyle( + // fontSize: fontS, + // height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), + // fontWeight: FontWeight.w500, + // color: AppColors.textColor, + // letterSpacing: -1, + // ), + // decoration: InputDecoration( + // isDense: true, + // hintText: hintText, + // hintStyle: TextStyle( + // fontSize: 14.f, + // height: 21 / 16, + // fontWeight: FontWeight.w500, + // color: hintColor != null ? AppColors.textColor : Color(0xff898A8D), + // letterSpacing: -0.75), + // prefixIconConstraints: BoxConstraints(minWidth: 30.h), + // prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, weight: FontWeight.w500), + // contentPadding: EdgeInsets.zero, + // border: InputBorder.none, + // focusedBorder: InputBorder.none, + // enabledBorder: InputBorder.none, + // ), + // ); } _buildTrailingIconForSearch(BuildContext context) { diff --git a/lib/widgets/language_switcher.dart b/lib/widgets/language_switcher.dart index 91e45b2..033eaaf 100644 --- a/lib/widgets/language_switcher.dart +++ b/lib/widgets/language_switcher.dart @@ -59,14 +59,15 @@ class _LanguageSelectorState extends State { Utils.buildSvgWithAssets(icon: AppAssets.language), SizedBox(width: 6.h), Text( - currentLangData['name']?.toUpperCase() ?? 'EN', + // currentLangData['name']?.toUpperCase() ?? 'EN', + widget.currentLanguage == 'ar' ? "ENGLISH" : "العربية", style: context.dynamicTextStyle( fontWeight: FontWeight.w500, fontSize: 14.f, color: AppColors.primaryRedColor, letterSpacing: 0.1, isLanguageSwitcher: true, - ), + fontFamily: widget.currentLanguage == 'ar' ? "Poppins" : "GESSTwo"), ), ], ), diff --git a/lib/widgets/loader/bottomsheet_loader.dart b/lib/widgets/loader/bottomsheet_loader.dart index 813ad5b..71b8341 100644 --- a/lib/widgets/loader/bottomsheet_loader.dart +++ b/lib/widgets/loader/bottomsheet_loader.dart @@ -3,7 +3,9 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/enums.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/widget_extensions.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; class LoaderBottomSheet { @@ -31,7 +33,7 @@ class LoaderBottomSheet { ), child: Center( child: Utils.getLoadingWidget(loadingText: loadingText), - ), + ).paddingSymmetrical(24.w, 0), ); }, ).whenComplete(() { diff --git a/lib/widgets/map/location_map_widget.dart b/lib/widgets/map/location_map_widget.dart index 2a1ea65..e3139a9 100644 --- a/lib/widgets/map/location_map_widget.dart +++ b/lib/widgets/map/location_map_widget.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; @@ -8,7 +10,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; -import 'package:maps_launcher/maps_launcher.dart'; +import 'package:map_launcher/map_launcher.dart'; /// A reusable location map widget that displays a static map with address and directions button /// Can be used in any review screen (HHC, CMC, etc.) @@ -56,9 +58,19 @@ class LocationMapWidget extends StatelessWidget { this.padding, }); - void _defaultLaunchDirections() { + void _defaultLaunchDirections() async { if (latitude != 0.0 && longitude != 0.0) { - MapsLauncher.launchCoordinates(latitude, longitude, address); + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(latitude, longitude), + title: address, + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, + coords: Coords(latitude, longitude), + title: address, + ); + }); } } diff --git a/pubspec.yaml b/pubspec.yaml index 02acec8..01c155b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ 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: 1.0.0+1 +version: 0.0.1+3 environment: sdk: ">=3.6.0 <4.0.0" @@ -67,13 +67,14 @@ dependencies: equatable: ^2.0.7 google_api_availability: ^5.0.1 firebase_analytics: ^11.5.1 + firebase_crashlytics: ^4.3.8 jiffy: ^6.4.3 hijri_gregorian_calendar: ^0.1.1 sms_otp_auto_verify: ^2.2.0 web: any flutter_staggered_animations: ^1.1.1 smooth_corner: ^1.1.1 - maps_launcher: ^3.0.0+1 + map_launcher: ^4.4.3 amazon_payfort: ^1.1.4 network_info_plus: ^6.1.4 flutter_nfc_kit: ^3.6.0 @@ -83,6 +84,7 @@ dependencies: open_filex: ^4.7.0 flutter_swiper_view: ^1.1.8 flutter_callkit_incoming: ^3.0.0 + in_app_update: ^4.2.5 # device_preview: ^1.3.1 location: ^8.0.1