Merge branch 'master' of http://34.17.182.140/Haroon6138/HMG_Patient_App_New into dev_sultan
* 'master' of http://34.17.182.140/Haroon6138/HMG_Patient_App_New: (57 commits) updates Update to stores, VersionID 21.6 multi occasion support Apple Pay payfort response API Added Reschedule logic updated family file notification key changed family file card fix updates CR 22303 Medical Report implemented Refactoring In App Browser fixes updates updates updates Notification page changes Update to stores, VersionID 21.4 Updates Auth update Update to stores, VersionID 22.3 remove the notification id send on checkactivation code ...dev_sultan
@ -1 +1,74 @@
|
||||
{"agcgw":{"url":"connect-drcn.dbankcloud.cn","backurl":"connect-drcn.hispace.hicloud.com","websocketurl":"connect-ws-drcn.hispace.dbankcloud.cn","websocketbackurl":"connect-ws-drcn.hispace.dbankcloud.com"},"agcgw_all":{"SG":"connect-dra.dbankcloud.cn","SG_back":"connect-dra.hispace.hicloud.com","CN":"connect-drcn.dbankcloud.cn","CN_back":"connect-drcn.hispace.hicloud.com","RU":"connect-drru.hispace.dbankcloud.ru","RU_back":"connect-drru.hispace.dbankcloud.cn","DE":"connect-dre.dbankcloud.cn","DE_back":"connect-dre.hispace.hicloud.com"},"websocketgw_all":{"SG":"connect-ws-dra.hispace.dbankcloud.cn","SG_back":"connect-ws-dra.hispace.dbankcloud.com","CN":"connect-ws-drcn.hispace.dbankcloud.cn","CN_back":"connect-ws-drcn.hispace.dbankcloud.com","RU":"connect-ws-drru.hispace.dbankcloud.ru","RU_back":"connect-ws-drru.hispace.dbankcloud.cn","DE":"connect-ws-dre.hispace.dbankcloud.cn","DE_back":"connect-ws-dre.hispace.dbankcloud.com"},"client":{"cp_id":"2640966000002322881","product_id":"736430079244816567","client_id":"563735388191982656","client_secret":"650C7C799812AFFD53A10C7756CF05FB9F215A7E49032ABA8EBF3E14B77535CF","project_id":"736430079244816567","app_id":"116912301","api_key":"CgB6e3x9DJzMgRCmnT6dyUEkp6UsIfddb6l3w0ZEXzeiRMHEFi3400Z5fJ5qaHneU0OrAI/JRpk+DMGVs3QpUxlI","package_name":"com.cloudsolutions.HMGPatientApp"},"oauth_client":{"client_id":"116912301","client_type":1},"app_info":{"app_id":"116912301","package_name":"com.cloudsolutions.HMGPatientApp"},"service":{"analytics":{"collector_url":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn","collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn","resource_id":"p1","channel_id":""},"ml":{"mlservice_url":"ml-api-drcn.ai.dbankcloud.com,ml-api-drcn.ai.dbankcloud.cn"},"cloudstorage":{"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg":"https://ops-dra.agcstorage.link","storage_url_de":"https://ops-dre.agcstorage.link","storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia","storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu","storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn"},"search":{"url":"https://search-drcn.cloud.huawei.com"},"edukit":{"edu_url":"edukit.cloud.huawei.com.cn","dh_url":"edukit.cloud.huawei.com.cn"}},"region":"CN","configuration_version":"3.0","appInfos":[{"package_name":"com.cloudsolutions.HMGPatientApp","client":{"app_id":"116912301"},"app_info":{"package_name":"com.cloudsolutions.HMGPatientApp","app_id":"116912301"},"oauth_client":{"client_type":1,"client_id":"116912301"}},{"package_name":"com.ejada.hmg","client":{"app_id":"102857389"},"app_info":{"package_name":"com.ejada.hmg","app_id":"102857389"},"oauth_client":{"client_type":1,"client_id":"102857389"}}]}
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//package com.cloud.diplomaticquarterapp
|
||||
package com.cloudsolutions.HMGPatientApp
|
||||
package com.ejada.hmg
|
||||
|
||||
|
||||
import io.flutter.app.FlutterApplication
|
||||
@ -1,14 +1,14 @@
|
||||
package com.cloudsolutions.HMGPatientApp
|
||||
package com.ejada.hmg
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.cloudsolutions.HMGPatientApp.penguin.PenguinView
|
||||
import com.ejada.hmg.penguin.PenguinView
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import com.cloudsolutions.HMGPatientApp.PermissionManager.HostNotificationPermissionManager
|
||||
import com.cloudsolutions.HMGPatientApp.PermissionManager.HostBgLocationManager
|
||||
import com.cloudsolutions.HMGPatientApp.PermissionManager.HostGpsStateManager
|
||||
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(
|
||||
@ -1,4 +1,4 @@
|
||||
package com.cloudsolutions.HMGPatientApp.PermissionManager;
|
||||
package com.ejada.hmg.PermissionManager;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@ -1,4 +1,4 @@
|
||||
package com.cloudsolutions.HMGPatientApp.PermissionManager;
|
||||
package com.ejada.hmg.PermissionManager;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
@ -1,4 +1,4 @@
|
||||
package com.cloudsolutions.HMGPatientApp.PermissionManager;
|
||||
package com.ejada.hmg.PermissionManager;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
@ -1,4 +1,4 @@
|
||||
package com.cloudsolutions.HMGPatientApp.PermissionManager;
|
||||
package com.ejada.hmg.PermissionManager;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
@ -1,4 +1,4 @@
|
||||
package com.cloudsolutions.HMGPatientApp.PermissionManager
|
||||
package com.ejada.hmg.PermissionManager
|
||||
|
||||
import android.Manifest
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.cloudsolutions.HMGPatientApp.PermissionManager
|
||||
package com.ejada.hmg.PermissionManager
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
@ -1,4 +1,4 @@
|
||||
package com.cloudsolutions.HMGPatientApp.PermissionManager
|
||||
package com.ejada.hmg.PermissionManager
|
||||
|
||||
// PermissionResultReceiver.kt
|
||||
import android.content.BroadcastReceiver
|
||||
@ -1,4 +1,4 @@
|
||||
package com.cloudsolutions.HMGPatientApp.penguin
|
||||
package com.ejada.hmg.penguin
|
||||
|
||||
enum class PenguinMethod {
|
||||
// initializePenguin("initializePenguin"),
|
||||
@ -1,4 +1,4 @@
|
||||
package com.cloudsolutions.HMGPatientApp.penguin
|
||||
package com.ejada.hmg.penguin
|
||||
|
||||
import android.content.Context
|
||||
import com.google.gson.Gson
|
||||
@ -0,0 +1,87 @@
|
||||
package com.ejada.hmg.watch.huawei
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.ejada.hmg.watch.samsung_watch.model.Vitals
|
||||
import com.huawei.hms.adapter.AvailableAdapter
|
||||
import com.huawei.hms.adapter.internal.AvailableCode
|
||||
|
||||
import com.samsung.android.sdk.health.data.HealthDataStore
|
||||
import com.samsung.android.sdk.health.data.permission.AccessType
|
||||
import com.samsung.android.sdk.health.data.permission.Permission
|
||||
import com.samsung.android.sdk.health.data.request.DataType
|
||||
import com.samsung.android.sdk.health.data.request.DataTypes
|
||||
import com.samsung.android.sdk.health.data.request.LocalTimeFilter
|
||||
import com.samsung.android.sdk.health.data.request.LocalTimeGroup
|
||||
import com.samsung.android.sdk.health.data.request.LocalTimeGroupUnit
|
||||
import com.samsung.android.sdk.health.data.request.Ordering
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import com.huawei.hms.hihealth.HuaweiHiHealth
|
||||
import com.ejada.hmg.MainActivity
|
||||
|
||||
|
||||
class HuaweiWatch(
|
||||
private var flutterEngine: FlutterEngine,
|
||||
private var mainActivity: MainActivity
|
||||
) {
|
||||
|
||||
private lateinit var channel: MethodChannel
|
||||
private lateinit var dataStore: HealthDataStore
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
private val TAG = "HuaweiWatch"
|
||||
|
||||
|
||||
private lateinit var vitals: MutableMap<String, List<Vitals>>
|
||||
companion object {
|
||||
private const val CHANNEL = "huawei_watch"
|
||||
|
||||
}
|
||||
init{
|
||||
create()
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun create() {
|
||||
Log.d(TAG, "create: is called")
|
||||
// openTok = OpenTok(mainActivity, flutterEngine)
|
||||
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
|
||||
channel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result ->
|
||||
when (call.method) {
|
||||
"init" -> {
|
||||
init(result)
|
||||
}
|
||||
|
||||
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun init(result: MethodChannel.Result) {
|
||||
|
||||
Log.d(TAG, "onMethodCall: init called")
|
||||
val mSettingController = HuaweiHiHealth.getSettingController(mainActivity);
|
||||
val availableAdapter = AvailableAdapter(60400312)
|
||||
val results= availableAdapter.isHuaweiMobileServicesAvailable(mainActivity) // context indicates a context object.
|
||||
if (results != AvailableCode.SUCCESS) {
|
||||
availableAdapter.startResolution(mainActivity, object : AvailableAdapter.AvailableCallBack {
|
||||
override fun onComplete(result: Int) {
|
||||
Log.d(TAG, "onComplete result: " + result)
|
||||
}
|
||||
})
|
||||
}
|
||||
result.success("init success")
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.cloudsolutions.HMGPatientApp.watch.samsung_watch.model
|
||||
package com.ejada.hmg.watch.samsung_watch.model
|
||||
|
||||
data class Vitals(
|
||||
var value : String = "",
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 5.6 KiB |
@ -1,10 +1,18 @@
|
||||
#org.gradle.jvmargs=-xmx4608m
|
||||
android.enableR8=true
|
||||
android.enableR8.fullMode=true
|
||||
android.enableJetifier=true
|
||||
android.useDeprecatedNdk=true
|
||||
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.daemon=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
android.useAndroidX=true
|
||||
android.enableImpeller=false
|
||||
# This builtInKotlin flag was added automatically by Flutter migrator
|
||||
android.builtInKotlin=false
|
||||
# This newDsl flag was added automatically by Flutter migrator
|
||||
android.newDsl=false
|
||||
|
||||
# Enabled parallel sync for Gradle 9.4+
|
||||
org.gradle.tooling.parallel=true
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="120" height="120" rx="60" fill="#ED1C2B" fill-opacity="0.0784314"/>
|
||||
<rect x="27" y="45" width="18" height="30" rx="3" fill="white"/>
|
||||
<rect x="27" y="45" width="18" height="30" rx="3" stroke="#ED1C2B" stroke-width="2"/>
|
||||
<rect x="30" y="52" width="12" height="16" rx="2" fill="#ED1C2B" fill-opacity="0.101961"/>
|
||||
<path d="M55.333 59.9992H64.6674M60.0002 64.6664L64.6674 59.9992L60.0002 55.332" stroke="#ED1C2B" stroke-width="2" stroke-linecap="round"/>
|
||||
<rect x="75" y="45" width="18" height="30" rx="3" fill="white"/>
|
||||
<rect x="75" y="45" width="18" height="30" rx="3" stroke="#ED1C2B" stroke-width="2"/>
|
||||
<rect x="78" y="52" width="12" height="16" rx="2" fill="#ED1C2B" fill-opacity="0.101961"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 812 B |
@ -0,0 +1 @@
|
||||
[]
|
||||
@ -0,0 +1,194 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "abseil-cpp-binary",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/abseil-cpp-binary.git",
|
||||
"state" : {
|
||||
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
|
||||
"version" : "1.2024072200.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "app-check",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/app-check.git",
|
||||
"state" : {
|
||||
"revision" : "3e33dd27dd4c69bd81c7c81fe61d8ccf58846902",
|
||||
"version" : "11.3.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkcamera",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKCamera",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkimagepickercontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
||||
"state" : {
|
||||
"branch" : "4.3.9",
|
||||
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkphotogallery",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "firebase-ios-sdk",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/firebase-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "fdc352fabaf5916e7faa1f96ad02b1957e93e5a5",
|
||||
"version" : "11.15.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "flutterfire",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/flutterfire",
|
||||
"state" : {
|
||||
"revision" : "dadb0fd27bc9afe4dee4f23326a4a9ba238258ac",
|
||||
"version" : "3.15.2-firebase-core-swift"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "google-ads-on-device-conversion-ios-sdk",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "a2d0f1f1666de591eb1a811f40b1706f5c63a2ed",
|
||||
"version" : "2.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googleappmeasurement",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||
"state" : {
|
||||
"revision" : "45ce435e9406d3c674dd249a042b932bee006f60",
|
||||
"version" : "11.15.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googledatatransport",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleDataTransport.git",
|
||||
"state" : {
|
||||
"revision" : "ba3358d3c3dbae8ef230b58a46b97ad65e84e974",
|
||||
"version" : "10.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googleutilities",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleUtilities.git",
|
||||
"state" : {
|
||||
"revision" : "9f183ae842be978784f2963a343682e0c46d8fb3",
|
||||
"version" : "8.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "grpc-binary",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/grpc-binary.git",
|
||||
"state" : {
|
||||
"revision" : "75b31c842f664a0f46a2e590a570e370249fd8f6",
|
||||
"version" : "1.69.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "gtm-session-fetcher",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/gtm-session-fetcher.git",
|
||||
"state" : {
|
||||
"revision" : "c756a29784521063b6a1202907e2cc47f41b667c",
|
||||
"version" : "4.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "interop-ios-for-google-sdks",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
|
||||
"state" : {
|
||||
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
|
||||
"version" : "101.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "leveldb",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/leveldb.git",
|
||||
"state" : {
|
||||
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
|
||||
"version" : "1.22.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "nanopb",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/nanopb.git",
|
||||
"state" : {
|
||||
"revision" : "3851d94a41890dea16dc3db34caf60e585cb4163",
|
||||
"version" : "2.30910.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "promises",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/promises.git",
|
||||
"state" : {
|
||||
"revision" : "f4a19a3c313dc2616c70bb49d29a799fb16be837",
|
||||
"version" : "2.4.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sdwebimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||
"state" : {
|
||||
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
|
||||
"version" : "5.21.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-protobuf",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||
"state" : {
|
||||
"revision" : "55d7a1cc5666b85c13464aea1c4b4a90feccb4c8",
|
||||
"version" : "1.38.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftygif",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
||||
"state" : {
|
||||
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
||||
"version" : "5.4.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "tocropviewcontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/TimOliver/TOCropViewController",
|
||||
"state" : {
|
||||
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
|
||||
"version" : "2.8.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@ -0,0 +1,194 @@
|
||||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "abseil-cpp-binary",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/abseil-cpp-binary.git",
|
||||
"state" : {
|
||||
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
|
||||
"version" : "1.2024072200.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "app-check",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/app-check.git",
|
||||
"state" : {
|
||||
"revision" : "3e33dd27dd4c69bd81c7c81fe61d8ccf58846902",
|
||||
"version" : "11.3.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkcamera",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKCamera",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkimagepickercontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKImagePickerController",
|
||||
"state" : {
|
||||
"branch" : "4.3.9",
|
||||
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "dkphotogallery",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "firebase-ios-sdk",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/firebase-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "fdc352fabaf5916e7faa1f96ad02b1957e93e5a5",
|
||||
"version" : "11.15.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "flutterfire",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/flutterfire",
|
||||
"state" : {
|
||||
"revision" : "dadb0fd27bc9afe4dee4f23326a4a9ba238258ac",
|
||||
"version" : "3.15.2-firebase-core-swift"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "google-ads-on-device-conversion-ios-sdk",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "a2d0f1f1666de591eb1a811f40b1706f5c63a2ed",
|
||||
"version" : "2.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googleappmeasurement",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||
"state" : {
|
||||
"revision" : "45ce435e9406d3c674dd249a042b932bee006f60",
|
||||
"version" : "11.15.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googledatatransport",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleDataTransport.git",
|
||||
"state" : {
|
||||
"revision" : "ba3358d3c3dbae8ef230b58a46b97ad65e84e974",
|
||||
"version" : "10.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "googleutilities",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleUtilities.git",
|
||||
"state" : {
|
||||
"revision" : "9f183ae842be978784f2963a343682e0c46d8fb3",
|
||||
"version" : "8.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "grpc-binary",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/grpc-binary.git",
|
||||
"state" : {
|
||||
"revision" : "75b31c842f664a0f46a2e590a570e370249fd8f6",
|
||||
"version" : "1.69.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "gtm-session-fetcher",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/gtm-session-fetcher.git",
|
||||
"state" : {
|
||||
"revision" : "c756a29784521063b6a1202907e2cc47f41b667c",
|
||||
"version" : "4.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "interop-ios-for-google-sdks",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
|
||||
"state" : {
|
||||
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
|
||||
"version" : "101.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "leveldb",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/leveldb.git",
|
||||
"state" : {
|
||||
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
|
||||
"version" : "1.22.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "nanopb",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/nanopb.git",
|
||||
"state" : {
|
||||
"revision" : "3851d94a41890dea16dc3db34caf60e585cb4163",
|
||||
"version" : "2.30910.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "promises",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/promises.git",
|
||||
"state" : {
|
||||
"revision" : "f4a19a3c313dc2616c70bb49d29a799fb16be837",
|
||||
"version" : "2.4.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sdwebimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||
"state" : {
|
||||
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
|
||||
"version" : "5.21.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-protobuf",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||
"state" : {
|
||||
"revision" : "55d7a1cc5666b85c13464aea1c4b4a90feccb4c8",
|
||||
"version" : "1.38.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftygif",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kirualex/SwiftyGif.git",
|
||||
"state" : {
|
||||
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
|
||||
"version" : "5.4.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "tocropviewcontroller",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/TimOliver/TOCropViewController",
|
||||
"state" : {
|
||||
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
|
||||
"version" : "2.8.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
||||
@ -1 +1,128 @@
|
||||
{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename" : "icon.jpg",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 815 B |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@ -0,0 +1,104 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:clarity_flutter/clarity_flutter.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freerasp/freerasp.dart';
|
||||
import 'package:hmg_patient_app_new/core/app_assets.dart';
|
||||
import 'package:hmg_patient_app_new/core/dependencies.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
||||
import 'package:hmg_patient_app_new/services/security_service.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||
|
||||
import 'generated/locale_keys.g.dart';
|
||||
import 'widgets/buttons/custom_button.dart';
|
||||
|
||||
class AppClosedPage extends StatefulWidget {
|
||||
const AppClosedPage({super.key});
|
||||
|
||||
@override
|
||||
State<AppClosedPage> createState() => _AppClosedPageState();
|
||||
}
|
||||
|
||||
class _AppClosedPageState extends State<AppClosedPage> {
|
||||
@override
|
||||
void initState() {
|
||||
// Clarity.setCurrentScreenName('Unsafe Device Page');
|
||||
Talsec.instance.detachListener();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.whiteColor,
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Logo
|
||||
Utils.buildImgWithAssets(icon: AppAssets.hmgLogo, width: MediaQuery.of(context).size.width * 0.7, height: 90.h, fit: BoxFit.contain),
|
||||
SizedBox(height: 32.h),
|
||||
Text(
|
||||
LocaleKeys.importantNotice.tr(context: context),
|
||||
style: TextStyle(
|
||||
fontSize: 28.f,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.textColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
// Description
|
||||
Text(
|
||||
LocaleKeys.appDecommissionedText.tr(context: context),
|
||||
style: TextStyle(
|
||||
fontSize: 14.f,
|
||||
color: Colors.black87,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 24.h),
|
||||
Text(
|
||||
LocaleKeys.appDecommissionedSubText.tr(context: context),
|
||||
style: TextStyle(fontSize: 14.f, color: AppColors.textColorLight, fontStyle: FontStyle.italic),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 48.h),
|
||||
CustomButton(
|
||||
text: LocaleKeys.downloadApp.tr(context: context),
|
||||
onPressed: () {
|
||||
if (Platform.isAndroid) {
|
||||
Utils.openWebView(
|
||||
url: "https://play.google.com/store/apps/details?id=com.ejada.hmg",
|
||||
);
|
||||
}
|
||||
if (Platform.isIOS) {
|
||||
Utils.openWebView(
|
||||
url: "https://itunes.apple.com/app/id733503978",
|
||||
);
|
||||
}
|
||||
},
|
||||
backgroundColor: AppColors.primaryRedColor,
|
||||
borderColor: AppColors.primaryRedColor,
|
||||
textColor: AppColors.whiteColor,
|
||||
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
|
||||
fontWeight: FontWeight.w600,
|
||||
borderRadius: 12.r,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
||||
// height: isTablet || isFoldable ? 46.h : 40.h,
|
||||
height: 56.h,
|
||||
iconColor: AppColors.primaryRedColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
class MyFatoorahConfig {
|
||||
|
||||
static const String configAPIKey = "SK_KWT_0auB76h3Frhj7aUBSZa6H5ei792eZoGAPh0bBxKsrgtyjqKgf38MPnZqzGqagErb";
|
||||
|
||||
}
|
||||
@ -0,0 +1,197 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hmg_patient_app_new/core/api_consts.dart';
|
||||
import 'package:hmg_patient_app_new/core/app_state.dart';
|
||||
import 'package:hmg_patient_app_new/core/cache_consts.dart';
|
||||
import 'package:hmg_patient_app_new/core/dependencies.dart';
|
||||
import 'package:hmg_patient_app_new/core/enums.dart';
|
||||
import 'package:hmg_patient_app_new/core/myFatoorah_config.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
||||
import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/payfort/models/payfort_response_insert_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart';
|
||||
import 'package:hmg_patient_app_new/generated/codegen_loader.g.dart';
|
||||
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
|
||||
import 'package:myfatoorah_flutter/myfatoorah_flutter.dart';
|
||||
|
||||
class MyFatoorahViewModel extends ChangeNotifier {
|
||||
final AppState appState;
|
||||
final ErrorHandlerService errorHandlerService;
|
||||
final PayfortRepo payfortRepo;
|
||||
|
||||
MyFatoorahViewModel({
|
||||
required this.appState,
|
||||
required this.errorHandlerService,
|
||||
required this.payfortRepo,
|
||||
});
|
||||
|
||||
// Payment Method ID
|
||||
// 2: Credit/Debit Card
|
||||
// 6: Mada
|
||||
|
||||
initiateCardPayment({
|
||||
num amount = 10.0,
|
||||
paymentDescription = "",
|
||||
String displayCurrencyIso = MFCurrencyISO.KUWAIT_KWD,
|
||||
required String paymentOption,
|
||||
required String liveServiceID,
|
||||
required String isScheduleLiveCare,
|
||||
required String appointmentDate,
|
||||
required String appointmentNo,
|
||||
required String doctorID,
|
||||
required String fileNumber,
|
||||
required String serviceID,
|
||||
required String projectID,
|
||||
Function(MFGetPaymentStatusResponse)? onSuccess,
|
||||
Function(String)? onError,
|
||||
}) async {
|
||||
LoaderBottomSheet.showLoader();
|
||||
await MFSDK.init(MyFatoorahConfig.configAPIKey, ApiConsts.myFatoorahCountry, ApiConsts.myFatoorahEnvironmentType);
|
||||
|
||||
MFSDK.setUpActionBar(toolBarTitle: paymentDescription, toolBarTitleColor: "#ffffff", toolBarBackgroundColor: '#ED1C2B', isShowToolBar: true);
|
||||
|
||||
var request = MFExecutePaymentRequest(paymentMethodId: paymentOption.toLowerCase() == "mada" ? 6 : 2, invoiceValue: amount);
|
||||
request.displayCurrencyIso = displayCurrencyIso;
|
||||
request.customerName = ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? "Test test" : "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}";
|
||||
request.customerEmail = "CustID_${appState.getAuthenticatedUser()!.patientId}@HMG.com";
|
||||
|
||||
await MFSDK.executePayment(request, appState.isArabic() ? MFLanguage.ARABIC : MFLanguage.ENGLISH, (invoiceId) async {
|
||||
PayfortRequestInsertModel payfortRequestInsertModel = PayfortRequestInsertModel();
|
||||
|
||||
payfortRequestInsertModel.commandType = "SALE";
|
||||
payfortRequestInsertModel.amount = amount;
|
||||
payfortRequestInsertModel.customerEmail = getIt.get<AppState>().getAuthenticatedUser()!.emailAddress;
|
||||
payfortRequestInsertModel.orderDescription = paymentDescription;
|
||||
payfortRequestInsertModel.serviceID = serviceID;
|
||||
payfortRequestInsertModel.paymentOption = paymentOption;
|
||||
payfortRequestInsertModel.clientRequestID = invoiceId;
|
||||
payfortRequestInsertModel.projectID = projectID;
|
||||
payfortRequestInsertModel.fileNumber = getIt.get<AppState>().getAuthenticatedUser()!.patientId.toString();
|
||||
payfortRequestInsertModel.customerName = "${getIt.get<AppState>().getAuthenticatedUser()!.firstName} ${getIt.get<AppState>().getAuthenticatedUser()!.lastName}";
|
||||
payfortRequestInsertModel.currency = getIt.get<AppState>().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED";
|
||||
payfortRequestInsertModel.liveServiceID = liveServiceID;
|
||||
payfortRequestInsertModel.latitude = getIt.get<AppState>().userLat.toString();
|
||||
payfortRequestInsertModel.longitude = getIt.get<AppState>().userLong.toString();
|
||||
payfortRequestInsertModel.merchantReference = invoiceId;
|
||||
payfortRequestInsertModel.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken);
|
||||
payfortRequestInsertModel.language = getIt.get<AppState>().isArabic() ? "ar" : "en";
|
||||
payfortRequestInsertModel.integrationId = "6"; // PayTabs integration ID
|
||||
payfortRequestInsertModel.isSchedule = isScheduleLiveCare;
|
||||
payfortRequestInsertModel.appointmentDate = appointmentDate;
|
||||
payfortRequestInsertModel.appointmentNo = appointmentNo;
|
||||
payfortRequestInsertModel.doctorID = doctorID;
|
||||
|
||||
// Call request insert API before payment
|
||||
await paytabsRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel);
|
||||
|
||||
LoaderBottomSheet.hideLoader();
|
||||
}).then((value) async {
|
||||
var valJson = value.toString();
|
||||
print(valJson);
|
||||
|
||||
// // Create PayfortResponseInsertModel (same as Payfort implementation)
|
||||
PayfortResponseInsertModel payfortResponseInsertModel = PayfortResponseInsertModel();
|
||||
|
||||
payfortResponseInsertModel.fortId = value.invoiceTransactions!.first.transactionId ?? "";
|
||||
payfortResponseInsertModel.commandType = "SALE";
|
||||
// payfortResponseInsertModel.amount = paytabsTransactionResponseModel.tranTotal ?? 0;
|
||||
payfortResponseInsertModel.amount = amount;
|
||||
payfortResponseInsertModel.sadadOLP = "";
|
||||
payfortResponseInsertModel.paymentOption = value.invoiceTransactions!.first.paymentGateway ?? "";
|
||||
payfortResponseInsertModel.customerIP = "";
|
||||
payfortResponseInsertModel.eCI = "";
|
||||
payfortResponseInsertModel.responseMessage = value.invoiceTransactions!.first.transactionStatus ?? "";
|
||||
payfortResponseInsertModel.cardNumber = value.invoiceTransactions!.first.cardNumber ?? "";
|
||||
payfortResponseInsertModel.cardBin = "";
|
||||
payfortResponseInsertModel.installmentsNumber = 0;
|
||||
payfortResponseInsertModel.status = (value.invoiceStatus ?? "").toLowerCase() == "paid" ? "14" : "00";
|
||||
payfortResponseInsertModel.merchantRef = value.invoiceId.toString();
|
||||
payfortResponseInsertModel.isRefund = false;
|
||||
payfortResponseInsertModel.remmeberMe = true;
|
||||
payfortResponseInsertModel.reconciliationReference = "";
|
||||
|
||||
// Call response insert API after payment
|
||||
await paytabsResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel);
|
||||
|
||||
if ((value.invoiceStatus ?? "").toLowerCase() == "paid") {
|
||||
if (onSuccess != null) {
|
||||
onSuccess(value);
|
||||
}
|
||||
} else {
|
||||
if (onError != null) {
|
||||
onError(LocaleKeys.paymentFailedPleaseTryAgain.tr());
|
||||
}
|
||||
}
|
||||
}).catchError((error) {
|
||||
if (onError != null) {
|
||||
onError(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
executeApplePayPayment() {
|
||||
MFApplepay.executeApplePayPayment(onInvoiceCreated: (invoiceID) {
|
||||
log(
|
||||
invoiceID.toString(),
|
||||
);
|
||||
}).then((value) {
|
||||
log(
|
||||
value.toString(),
|
||||
);
|
||||
}).catchError((error) {
|
||||
log(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
initApplePayPayment(num invoiceValue) async {
|
||||
await MFSDK.init(MyFatoorahConfig.configAPIKey, ApiConsts.myFatoorahCountry, ApiConsts.myFatoorahEnvironmentType);
|
||||
|
||||
await MFSDK.initSession(MFInitiateSessionRequest(), MFLanguage.ENGLISH).then((value) async {
|
||||
var executePaymentRequest = MFExecutePaymentRequest(invoiceValue: invoiceValue, sessionId: value.sessionId, displayCurrencyIso: MFCurrencyISO.SAUDIARABIA_SAR);
|
||||
|
||||
MFApplepay.setupApplePay(value, executePaymentRequest, MFLanguage.ENGLISH, merchantName: "Dr Sulaiman Al Habib");
|
||||
}).catchError((error) {
|
||||
log(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> paytabsRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
|
||||
final result = await payfortRepo.payfortRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel);
|
||||
|
||||
result.fold(
|
||||
(failure) async => await errorHandlerService.handleError(failure: failure),
|
||||
(apiResponse) {
|
||||
if (apiResponse.messageStatus == 2) {
|
||||
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
|
||||
} else if (apiResponse.messageStatus == 1) {
|
||||
notifyListeners();
|
||||
if (onSuccess != null) {
|
||||
onSuccess(apiResponse);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> paytabsResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
|
||||
final result = await payfortRepo.payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel);
|
||||
|
||||
result.fold(
|
||||
(failure) async => await errorHandlerService.handleError(failure: failure),
|
||||
(apiResponse) {
|
||||
if (apiResponse.messageStatus == 2) {
|
||||
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
|
||||
} else if (apiResponse.messageStatus == 1) {
|
||||
notifyListeners();
|
||||
if (onSuccess != null) {
|
||||
onSuccess(apiResponse);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,147 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Model representing a remotely-configurable "occasion" theme
|
||||
/// (e.g. Saudi National Day, Founding Day, Eid, Ramadan, ...).
|
||||
///
|
||||
/// Everything here is fully data-driven — nothing about a specific
|
||||
/// occasion (name, color, dates, banner asset) is hardcoded in the app.
|
||||
/// All values are supplied via Firebase Remote Config (or the bundled
|
||||
/// default JSON fallback used before the first successful fetch).
|
||||
@immutable
|
||||
class OccasionConfigModel {
|
||||
/// Unique identifier of the occasion (e.g. "national_day"). Used only
|
||||
/// for logging/analytics — no app logic branches on this value.
|
||||
final String key;
|
||||
|
||||
/// Master kill-switch. When false, the app must look exactly like the
|
||||
/// default/current theme, regardless of any other field below.
|
||||
final bool enabled;
|
||||
|
||||
/// Activation window. Both must be present and `DateTime.now()` must fall
|
||||
/// within [startDate, endDate] for this occasion to be eligible — a
|
||||
/// missing date on either side makes the occasion inactive (dates are
|
||||
/// mandatory, not an optional override of [enabled]). This lets several
|
||||
/// occasions be scheduled in advance in the same list (see
|
||||
/// [OccasionConfigModel.resolveActive]) without colliding.
|
||||
final DateTime? startDate;
|
||||
final DateTime? endDate;
|
||||
|
||||
/// Hex string (e.g. "#00894A") for the occasion's primary brand color.
|
||||
/// When null, the app's default primary color is preserved.
|
||||
final String? primaryColorHex;
|
||||
|
||||
/// Remote (or bundled asset path) image rendered as a decorative
|
||||
/// background/watermark, drawn using [backgroundImageOpacity].
|
||||
final String? backgroundOpacityImage;
|
||||
|
||||
/// Opacity (0.0 - 1.0) applied to [backgroundOpacityImage].
|
||||
final double backgroundImageOpacity;
|
||||
|
||||
/// Remote (or bundled asset path) image rendered as the occasion's main
|
||||
/// header/banner (e.g. the National Day greeting banner), drawn at full
|
||||
/// opacity on top of [backgroundOpacityImage].
|
||||
final String? headerImage;
|
||||
|
||||
const OccasionConfigModel({
|
||||
required this.key,
|
||||
required this.enabled,
|
||||
this.startDate,
|
||||
this.endDate,
|
||||
this.primaryColorHex,
|
||||
this.backgroundOpacityImage,
|
||||
this.backgroundImageOpacity = 1.0,
|
||||
this.headerImage,
|
||||
});
|
||||
|
||||
/// Fully inert config — identical to the app's current/default look.
|
||||
factory OccasionConfigModel.disabled() => const OccasionConfigModel(
|
||||
key: 'none',
|
||||
enabled: false,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
primaryColorHex: null,
|
||||
backgroundOpacityImage: null,
|
||||
backgroundImageOpacity: 1.0,
|
||||
headerImage: null,
|
||||
);
|
||||
|
||||
factory OccasionConfigModel.fromJson(Map<String, dynamic> json) {
|
||||
return OccasionConfigModel(
|
||||
key: (json['key'] as String?) ?? 'none',
|
||||
enabled: (json['enabled'] as bool?) ?? false,
|
||||
// enabled: true,
|
||||
startDate: _parseDate(json['start_date']),
|
||||
endDate: _parseDate(json['end_date']),
|
||||
primaryColorHex: json['primary_color'] as String?,
|
||||
backgroundOpacityImage: json['background_opacity_image'] as String?,
|
||||
backgroundImageOpacity: (json['background_image_opacity'] as num?)?.toDouble() ?? 1.0,
|
||||
headerImage: json['header_image'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parses the remote-config value as a JSON array of occasions — lets
|
||||
/// several occasions be scheduled in advance (e.g. National Day, Founding
|
||||
/// Day, Eid), each with its own non-overlapping [startDate]/[endDate]
|
||||
/// window, all under the same `occasion_config` remote-config key.
|
||||
static List<OccasionConfigModel> listFromJson(List<dynamic> jsonList) {
|
||||
return jsonList.map((e) => OccasionConfigModel.fromJson(e as Map<String, dynamic>)).toList();
|
||||
}
|
||||
|
||||
/// Picks whichever scheduled occasion is enabled and whose
|
||||
/// [startDate]/[endDate] window contains `now` — first match in list
|
||||
/// order wins, so entries are expected not to overlap. Returns
|
||||
/// [OccasionConfigModel.disabled] when nothing matches.
|
||||
static OccasionConfigModel resolveActive(List<OccasionConfigModel> configs, [DateTime? now]) {
|
||||
for (final config in configs) {
|
||||
if (config.enabled && config.isWithinActiveWindow(now)) return config;
|
||||
}
|
||||
return OccasionConfigModel.disabled();
|
||||
}
|
||||
|
||||
static DateTime? _parseDate(dynamic value) {
|
||||
if (value == null) return null;
|
||||
if (value is String && value.isNotEmpty) {
|
||||
return DateTime.tryParse(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Whether "now" falls inside the configured activation window.
|
||||
/// Both [startDate] and [endDate] are required — if either is missing,
|
||||
/// this occasion is never considered active (see field doc above).
|
||||
bool isWithinActiveWindow([DateTime? now]) {
|
||||
if (startDate == null || endDate == null) return false;
|
||||
final current = now ?? DateTime.now();
|
||||
if (current.isBefore(startDate!)) return false;
|
||||
if (current.isAfter(endDate!)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Parsed [Color] from [primaryColorHex], or null if not configured.
|
||||
Color? get primaryColor => _hexToColor(primaryColorHex);
|
||||
|
||||
/// Lighter/translucent variant of [primaryColor] — 11% opacity of the same
|
||||
/// hue (e.g. for chip/badge backgrounds). Derived automatically from
|
||||
/// [primaryColor]; not an independent remote-config field. Null unless
|
||||
/// [primaryColor] is configured.
|
||||
Color? get secondaryColor => primaryColor?.withValues(alpha: 0.11);
|
||||
|
||||
static Color? _hexToColor(String? hex) {
|
||||
if (hex == null || hex.isEmpty) return null;
|
||||
var value = hex.trim().replaceFirst('#', '');
|
||||
if (value.length == 6) value = 'FF$value';
|
||||
final parsed = int.tryParse(value, radix: 16);
|
||||
return parsed != null ? Color(parsed) : null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'key': key,
|
||||
'enabled': enabled,
|
||||
'start_date': startDate?.toIso8601String(),
|
||||
'end_date': endDate?.toIso8601String(),
|
||||
'primary_color': primaryColorHex,
|
||||
'background_opacity_image': backgroundOpacityImage,
|
||||
'background_image_opacity': backgroundImageOpacity,
|
||||
'header_image': headerImage,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart' show Color;
|
||||
import 'package:hmg_patient_app_new/features/occasion_theme/occasion_config_model.dart';
|
||||
import 'package:hmg_patient_app_new/services/cache_service.dart';
|
||||
import 'package:hmg_patient_app_new/services/logger_service.dart';
|
||||
import 'package:hmg_patient_app_new/services/remote_config_service.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||
|
||||
/// Drives the fully remote-config-based "occasion" theming (National Day,
|
||||
/// Founding Day, Eid, Ramadan, ...). Nothing about a specific occasion is
|
||||
/// hardcoded here — every color/asset/date comes from [OccasionConfigModel],
|
||||
/// which is parsed from Firebase Remote Config.
|
||||
///
|
||||
/// Mirrors the existing dark-mode pattern in ProfileSettingsViewModel:
|
||||
/// a static flag on [AppColors] is kept in sync so every widget using
|
||||
/// AppColors.primaryRedColor (even ones with no BuildContext/Provider
|
||||
/// access) reflects the active occasion instantly.
|
||||
class OccasionThemeViewModel extends ChangeNotifier {
|
||||
static const String _cachedConfigKey = 'cached_occasion_config_json';
|
||||
|
||||
final RemoteConfigService _remoteConfigService;
|
||||
final CacheService _cacheService;
|
||||
final LoggerService _loggerService;
|
||||
|
||||
OccasionThemeViewModel({
|
||||
required RemoteConfigService remoteConfigService,
|
||||
required CacheService cacheService,
|
||||
required LoggerService loggerService,
|
||||
}) : _remoteConfigService = remoteConfigService,
|
||||
_cacheService = cacheService,
|
||||
_loggerService = loggerService;
|
||||
|
||||
OccasionConfigModel _config = OccasionConfigModel.disabled();
|
||||
OccasionConfigModel get config => _config;
|
||||
|
||||
/// Testing-only override for "now" when resolving which scheduled
|
||||
/// occasion is active — set this instead of changing the device clock to
|
||||
/// preview each occasion in the list. Only ever takes effect in debug
|
||||
/// builds (see [_now]); always ignored in profile/release, so it can
|
||||
/// never leak into production even if accidentally left set.
|
||||
static DateTime? debugNowOverride;
|
||||
|
||||
DateTime? get _now => kDebugMode ? debugNowOverride : null;
|
||||
|
||||
/// True only when enabled AND (if a window is configured) within it.
|
||||
bool get isOccasionActive => _config.enabled && _config.isWithinActiveWindow(_now);
|
||||
|
||||
String? get backgroundOpacityImage => isOccasionActive ? _config.backgroundOpacityImage : null;
|
||||
|
||||
double get backgroundImageOpacity => _config.backgroundImageOpacity;
|
||||
|
||||
/// The occasion's main header/banner image (e.g. National Day greeting
|
||||
/// banner), rendered at full opacity. Null unless an occasion is active.
|
||||
String? get headerImage => isOccasionActive ? _config.headerImage : null;
|
||||
|
||||
/// Lighter/translucent variant of the active occasion's primary color
|
||||
/// (11% opacity) — e.g. for chip/badge backgrounds. Null unless an
|
||||
/// occasion is active.
|
||||
Color? get secondaryColor => isOccasionActive ? _config.secondaryColor : null;
|
||||
|
||||
/// Call once at app startup (before the first frame) to restore the last
|
||||
/// known-good config instantly (offline-safe), then kick off a remote
|
||||
/// fetch in the background.
|
||||
Future<void> initialize() async {
|
||||
_restoreCachedConfig();
|
||||
_applyToAppColors();
|
||||
|
||||
try {
|
||||
await _remoteConfigService.initialize();
|
||||
await refresh();
|
||||
} catch (ex) {
|
||||
_loggerService.logError('OccasionThemeViewModel.initialize error: $ex');
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches the latest occasion schedule from Firebase Remote Config and
|
||||
/// applies whichever entry is currently active. Safe to call again later
|
||||
/// (e.g. on app resume) to pick up changes without requiring an app update.
|
||||
Future<void> refresh() async {
|
||||
try {
|
||||
await _remoteConfigService.fetchAndActivate();
|
||||
final rawJson = _remoteConfigService.occasionConfigRawJson;
|
||||
final configs = OccasionConfigModel.listFromJson(json.decode(rawJson) as List<dynamic>);
|
||||
_config = OccasionConfigModel.resolveActive(configs, _now);
|
||||
_loggerService.logInfo('OccasionThemeViewModel: chosen theme = "${_config.key}" (enabled=${_config.enabled}, active=$isOccasionActive)');
|
||||
_loggerService.logInfo('OccasionThemeViewModel: chosen theme object = ${_config.toJson()}');
|
||||
// Cache the full schedule (not just the resolved entry) so offline
|
||||
// restore at next startup re-resolves against that day's date instead
|
||||
// of replaying whichever occasion happened to be active last time.
|
||||
await _cacheService.saveString(key: _cachedConfigKey, value: rawJson);
|
||||
_applyToAppColors();
|
||||
notifyListeners();
|
||||
} catch (ex) {
|
||||
_loggerService.logError('OccasionThemeViewModel.refresh error: $ex');
|
||||
// Keep whatever config was already applied (cached/disabled) — never
|
||||
// let a remote-config failure change the app's appearance unexpectedly.
|
||||
}
|
||||
}
|
||||
|
||||
void _restoreCachedConfig() {
|
||||
try {
|
||||
final cachedJson = _cacheService.getString(key: _cachedConfigKey);
|
||||
if (cachedJson == null || cachedJson.isEmpty) {
|
||||
_config = OccasionConfigModel.disabled();
|
||||
return;
|
||||
}
|
||||
final configs = OccasionConfigModel.listFromJson(json.decode(cachedJson) as List<dynamic>);
|
||||
_config = OccasionConfigModel.resolveActive(configs, _now);
|
||||
} catch (ex) {
|
||||
_loggerService.logError('OccasionThemeViewModel._restoreCachedConfig error: $ex');
|
||||
_config = OccasionConfigModel.disabled();
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps AppColors in sync so non-Provider call sites (AppColors.primaryRedColor)
|
||||
/// pick up the occasion color exactly like the existing dark-mode flag does.
|
||||
void _applyToAppColors() {
|
||||
AppColors.isOccasionThemeActive = isOccasionActive;
|
||||
AppColors.occasionPrimaryColor = _config.primaryColor;
|
||||
AppColors.occasionSecondaryColor = _config.secondaryColor;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1 @@
|
||||
class IllegalWatchTypeException implements Exception {}
|
||||
@ -1,86 +1,403 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:async/src/result/result.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hmg_patient_app_new/features/smartwatch_health_data/model/vitals_data_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:huawei_health/huawei_health.dart';
|
||||
|
||||
|
||||
class HuaweiHealthDataConnector extends WatchHelper{
|
||||
final MethodChannel _channel = MethodChannel('huawei_watch');
|
||||
@override
|
||||
Future<Result<bool>> initDevice() async{
|
||||
try{
|
||||
await _channel.invokeMethod('init');
|
||||
|
||||
}catch(e){
|
||||
DataController? _dataController;
|
||||
final Map<String, List<Vitals>> mappedData = <String, List<Vitals>>{};
|
||||
|
||||
DateTime get _defaultEndDate => DateTime.now();
|
||||
DateTime get _defaultStartDate => _defaultEndDate.subtract(const Duration(days: 365));
|
||||
|
||||
@override
|
||||
Future<Result<bool>> initDevice() async {
|
||||
try {
|
||||
await _channel.invokeMethod('init');
|
||||
} catch (e) {
|
||||
debugPrint('Huawei channel init failed: $e');
|
||||
}
|
||||
// List of scopes to ask for authorization.
|
||||
// Note: These scopes should also be authorized on the Huawei Developer Console.
|
||||
List<Scope> scopes = [
|
||||
Scope.HEALTHKIT_STEP_READ, Scope.HEALTHKIT_OXYGEN_SATURATION_READ, // View and store height and weight data in Health Service Kit.
|
||||
Scope.HEALTHKIT_HEARTRATE_READ, Scope.HEALTHKIT_SLEEP_READ,
|
||||
Scope.HEALTHKIT_BODYTEMPERATURE_READ, Scope.HEALTHKIT_CALORIES_READ
|
||||
|
||||
final List<Scope> scopes = <Scope>[
|
||||
Scope.HEALTHKIT_ACTIVITY_READ,
|
||||
Scope.HEALTHKIT_ACTIVITY_WRITE,
|
||||
Scope.HEALTHKIT_BLOODGLUCOSE_READ,
|
||||
Scope.HEALTHKIT_BLOODGLUCOSE_WRITE,
|
||||
Scope.HEALTHKIT_CALORIES_READ,
|
||||
Scope.HEALTHKIT_CALORIES_WRITE,
|
||||
Scope.HEALTHKIT_DISTANCE_READ,
|
||||
Scope.HEALTHKIT_DISTANCE_WRITE,
|
||||
Scope.HEALTHKIT_HEARTRATE_READ,
|
||||
Scope.HEALTHKIT_HEARTRATE_WRITE,
|
||||
Scope.HEALTHKIT_HEIGHTWEIGHT_READ,
|
||||
Scope.HEALTHKIT_HEIGHTWEIGHT_WRITE,
|
||||
Scope.HEALTHKIT_LOCATION_READ,
|
||||
Scope.HEALTHKIT_LOCATION_WRITE,
|
||||
Scope.HEALTHKIT_PULMONARY_READ,
|
||||
Scope.HEALTHKIT_PULMONARY_WRITE,
|
||||
Scope.HEALTHKIT_SLEEP_READ,
|
||||
Scope.HEALTHKIT_SLEEP_WRITE,
|
||||
Scope.HEALTHKIT_SPEED_READ,
|
||||
Scope.HEALTHKIT_SPEED_WRITE,
|
||||
Scope.HEALTHKIT_STEP_READ,
|
||||
Scope.HEALTHKIT_STEP_WRITE,
|
||||
Scope.HEALTHKIT_STRENGTH_READ,
|
||||
Scope.HEALTHKIT_STRENGTH_WRITE,
|
||||
Scope.HEALTHKIT_BODYFAT_READ,
|
||||
Scope.HEALTHKIT_BODYFAT_WRITE,
|
||||
Scope.HEALTHKIT_NUTRITION_READ,
|
||||
Scope.HEALTHKIT_NUTRITION_WRITE,
|
||||
Scope.HEALTHKIT_BLOODPRESSURE_READ,
|
||||
Scope.HEALTHKIT_BLOODPRESSURE_WRITE,
|
||||
Scope.HEALTHKIT_BODYTEMPERATURE_READ,
|
||||
Scope.HEALTHKIT_BODYTEMPERATURE_WRITE,
|
||||
Scope.HEALTHKIT_OXYGENSTATURATION_READ,
|
||||
Scope.HEALTHKIT_OXYGENSTATURATION_WRITE,
|
||||
Scope.HEALTHKIT_REPRODUCTIVE_READ,
|
||||
Scope.HEALTHKIT_REPRODUCTIVE_WRITE,
|
||||
Scope.HEALTHKIT_ACTIVITY_RECORD_READ,
|
||||
Scope.HEALTHKIT_ACTIVITY_RECORD_WRITE,
|
||||
Scope.HEALTHKIT_HEARTRATE_REALTIME,
|
||||
Scope.HEALTHKIT_STEP_REALTIME,
|
||||
Scope.HEALTHKIT_HEARTHEALTH_WRITE,
|
||||
Scope.HEALTHKIT_HEARTHEALTH_READ,
|
||||
Scope.HEALTHKIT_STRESS_WRITE,
|
||||
Scope.HEALTHKIT_STRESS_READ,
|
||||
Scope.HEALTHKIT_OXYGEN_SATURATION_WRITE,
|
||||
Scope.HEALTHKIT_OXYGEN_SATURATION_READ,
|
||||
Scope.HEALTHKIT_HISTORYDATA_OPEN_WEEK,
|
||||
Scope.HEALTHKIT_HISTORYDATA_OPEN_MONTH,
|
||||
Scope.HEALTHKIT_HISTORYDATA_OPEN_YEAR,
|
||||
];
|
||||
|
||||
try {
|
||||
bool? result = await SettingController.getHealthAppAuthorization();
|
||||
debugPrint(
|
||||
'Granted Scopes for result == is $result}',
|
||||
);
|
||||
await HealthAuth.signIn(scopes);
|
||||
_dataController = await DataController.init();
|
||||
if (_dataController == null) {
|
||||
return Result.error(false);
|
||||
}
|
||||
return Result.value(true);
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('Error on Huawei authorization: $e');
|
||||
return Result.error(false);
|
||||
} catch (e) {
|
||||
debugPrint('Error on authorization, Error:${e.toString()}');
|
||||
debugPrint('Unexpected Huawei init error: $e');
|
||||
return Result.error(false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> getActivity() async {
|
||||
DataType dataTypeResult = await SettingController.readDataType(
|
||||
DataType.DT_CONTINUOUS_STEPS_DELTA.name
|
||||
);
|
||||
|
||||
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
DataType.DT_CONTINUOUS_CALORIES_BURNT,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addDataToMap('activity', data, preferredKeys: const <String>['calories', 'calories_total']);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting activity data: $e');
|
||||
mappedData['activity'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future getBloodOxygen() {
|
||||
throw UnimplementedError();
|
||||
Future<void> getBloodOxygen() async {
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
HealthDataTypes.DT_INSTANTANEOUS_SPO2,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addDataToMap(
|
||||
'bloodOxygen',
|
||||
data,
|
||||
preferredKeys: const <String>['saturation', 'saturation_last', 'saturation_avg'],
|
||||
transform: _normalizeSpo2,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting blood oxygen data: $e');
|
||||
mappedData['bloodOxygen'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> getBodyTemperature() async {
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
HealthDataTypes.DT_INSTANTANEOUS_BODY_TEMPERATURE,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addDataToMap('bodyTemperature', data, preferredKeys: const <String>['temperature']);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting body temperature data: $e');
|
||||
mappedData['bodyTemperature'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future getBodyTemperature() {
|
||||
FutureOr<void> getHeartRate() async {
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
DataType.DT_INSTANTANEOUS_HEART_RATE,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addDataToMap('heartRate', data, preferredKeys: const <String>['bpm']);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting heart rate data: $e');
|
||||
mappedData['heartRate'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
FutureOr<void> getSleep() async {
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
DataType.DT_CONTINUOUS_SLEEP,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addSleepDataToMap(data);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting sleep data: $e');
|
||||
mappedData['sleep'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getHeartRate() {
|
||||
throw UnimplementedError();
|
||||
FutureOr<void> getSteps() async {
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
DataType.DT_CONTINUOUS_STEPS_DELTA,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addDataToMap('steps', data, preferredKeys: const <String>['steps_delta', 'steps', 'step_rate']);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting steps data: $e');
|
||||
mappedData['steps'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getSleep() {
|
||||
throw UnimplementedError();
|
||||
Future<Result<String>> retrieveData() async {
|
||||
return Result.value(getMappedData());
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getSteps() {
|
||||
throw UnimplementedError();
|
||||
FutureOr<void> getDistance() async {
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
DataType.DT_CONTINUOUS_DISTANCE_DELTA,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addDataToMap('distance', data, preferredKeys: const <String>['distance_delta', 'distance']);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting distance data: $e');
|
||||
mappedData['distance'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
Future<ReadReply?> readDataType(
|
||||
DataType dataType,
|
||||
DateTime startTime,
|
||||
DateTime endTime,
|
||||
) async {
|
||||
final DataCollector dataCollector = DataCollector(
|
||||
dataType: dataType,
|
||||
dataGenerateType: DataGenerateType.DATA_TYPE_RAW,
|
||||
);
|
||||
|
||||
@override
|
||||
Future retrieveData() {
|
||||
throw UnimplementedError();
|
||||
final ReadOptions readOptions = ReadOptions(
|
||||
dataCollectors: <DataCollector>[dataCollector],
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
allowRemoteInquiry: true,
|
||||
)..groupByTime(1, timeUnit: TimeUnit.HOURS);
|
||||
|
||||
try {
|
||||
return await _dataController?.read(readOptions);
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('Huawei read error for ${dataType.name}: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getDistance() {
|
||||
// TODO: implement getDistance
|
||||
throw UnimplementedError();
|
||||
void addDataToMap(
|
||||
String key,
|
||||
ReadReply? data, {
|
||||
List<String> preferredKeys = const <String>[],
|
||||
double Function(double value)? transform,
|
||||
}) {
|
||||
mappedData[key] = <Vitals>[];
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.groups.isNotEmpty) {
|
||||
for (final Group group in data.groups) {
|
||||
for (final SampleSet sampleSet in group.sampleSets ?? <SampleSet>[]) {
|
||||
for (final SamplePoint samplePoint in sampleSet.samplePoints) {
|
||||
_addNumericSampleToMappedData(
|
||||
key: key,
|
||||
samplePoint: samplePoint,
|
||||
preferredKeys: preferredKeys,
|
||||
groupStartTime: group.startTime,
|
||||
transform: transform,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (final SampleSet sampleSet in data.sampleSets) {
|
||||
for (final SamplePoint samplePoint in sampleSet.samplePoints) {
|
||||
_addNumericSampleToMappedData(
|
||||
key: key,
|
||||
samplePoint: samplePoint,
|
||||
preferredKeys: preferredKeys,
|
||||
transform: transform,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addSleepDataToMap(ReadReply? data) {
|
||||
mappedData['sleep'] = <Vitals>[];
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
void appendSleepPoint(SamplePoint samplePoint, {DateTime? groupStart, DateTime? groupEnd}) {
|
||||
final DateTime? start = samplePoint.startTime ?? groupStart;
|
||||
final DateTime? end = samplePoint.endTime ?? groupEnd;
|
||||
|
||||
double? value;
|
||||
if (start != null && end != null && end.isAfter(start)) {
|
||||
value = end.difference(start).inMinutes / 60;
|
||||
}
|
||||
|
||||
value ??= _extractNumericValue(
|
||||
samplePoint,
|
||||
const <String>['sleep_state', 'all_sleep_time', 'deep_sleep_time', 'light_sleep_time'],
|
||||
);
|
||||
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String timestamp =
|
||||
(start ?? samplePoint.samplingTime ?? DateTime.now()).toIso8601String();
|
||||
|
||||
mappedData['sleep']?.add(
|
||||
Vitals(
|
||||
value: value.toStringAsFixed(2),
|
||||
timestamp: timestamp,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (data.groups.isNotEmpty) {
|
||||
for (final Group group in data.groups) {
|
||||
for (final SampleSet sampleSet in group.sampleSets ?? <SampleSet>[]) {
|
||||
for (final SamplePoint samplePoint in sampleSet.samplePoints) {
|
||||
appendSleepPoint(
|
||||
samplePoint,
|
||||
groupStart: group.startTime,
|
||||
groupEnd: group.endTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (final SampleSet sampleSet in data.sampleSets) {
|
||||
for (final SamplePoint samplePoint in sampleSet.samplePoints) {
|
||||
appendSleepPoint(samplePoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _addNumericSampleToMappedData({
|
||||
required String key,
|
||||
required SamplePoint samplePoint,
|
||||
required List<String> preferredKeys,
|
||||
DateTime? groupStartTime,
|
||||
double Function(double value)? transform,
|
||||
}) {
|
||||
final double? numericValue = _extractNumericValue(samplePoint, preferredKeys);
|
||||
if (numericValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final double transformedValue = transform?.call(numericValue) ?? numericValue;
|
||||
final String timestamp =
|
||||
(samplePoint.samplingTime ?? samplePoint.startTime ?? groupStartTime ?? DateTime.now())
|
||||
.toIso8601String();
|
||||
|
||||
mappedData[key]?.add(
|
||||
Vitals(
|
||||
value: transformedValue.toStringAsFixed(2),
|
||||
timestamp: timestamp,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double? _extractNumericValue(SamplePoint samplePoint, List<String> preferredKeys) {
|
||||
final Map<String, dynamic> values = <String, dynamic>{
|
||||
...samplePoint.pairs,
|
||||
...?samplePoint.fieldValues,
|
||||
};
|
||||
|
||||
for (final String key in preferredKeys) {
|
||||
final dynamic value = values[key];
|
||||
if (value is num) {
|
||||
return value.toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
for (final dynamic value in values.values) {
|
||||
if (value is num) {
|
||||
return value.toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
double _normalizeSpo2(double value) {
|
||||
if (value <= 1) {
|
||||
return value * 100;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
String getMappedData() {
|
||||
return jsonEncode(<String, dynamic>{
|
||||
'heartRate': (mappedData['heartRate'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
'sleep': (mappedData['sleep'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
'steps': (mappedData['steps'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
'activity': (mappedData['activity'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
'bloodOxygen':
|
||||
(mappedData['bloodOxygen'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
'bodyTemperature':
|
||||
(mappedData['bodyTemperature'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
'distance': (mappedData['distance'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||