diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 1d0e8453..347ab714 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -7,6 +7,7 @@ plugins { id("com.google.gms.google-services") version "4.4.1" // Add the version here id("dev.flutter.flutter-gradle-plugin") id("com.huawei.agconnect") + id("kotlin-parcelize") // id("com.mapbox.gradle.application") // id("com.mapbox.gradle.plugins.ndk") } @@ -191,6 +192,9 @@ dependencies { implementation(files("libs/PenNavUI.aar")) implementation(files("libs/Penguin.aar")) implementation(files("libs/PenguinRenderer.aar")) + api(files("libs/samsung-health-data-api.aar")) + implementation("com.huawei.hms:health:6.11.0.300") + implementation("com.huawei.hms:hmscoreinstaller:6.6.0.300") implementation("com.github.kittinunf.fuel:fuel:2.3.1") implementation("com.github.kittinunf.fuel:fuel-android:2.3.1") diff --git a/android/app/libs/samsung-health-data-api.aar b/android/app/libs/samsung-health-data-api.aar new file mode 100644 index 00000000..1fd24034 Binary files /dev/null and b/android/app/libs/samsung-health-data-api.aar differ diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0e77b6b6..ffa3a908 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -124,6 +124,7 @@ , + 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() + } + +// override fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) { +// super.onActivityResult(requestCode, resultCode, data) +// +// // Process only the response result of the authorization process. +// if (requestCode == 1002) { +// // Obtain the authorization response result from the intent. +// val result: HealthKitAuthResult? = huaweiWatch?.mSettingController?.parseHealthKitAuthResultFromIntent(data) +// if (result == null) { +// Log.w(huaweiWatch?.TAG, "authorization fail") +// return +// } +// +// if (result.isSuccess) { +// Log.i(huaweiWatch?.TAG, "authorization success") +// if (result.getAuthAccount() != null && result.authAccount.authorizedScopes != null) { +// val authorizedScopes: MutableSet = result.authAccount.authorizedScopes +// if(authorizedScopes.isNotEmpty()) { +// huaweiWatch?.getHealthAppAuthorization() +// } +// } +// } else { +// Log.w("MainActivty", "authorization fail, errorCode:" + result.getErrorCode()) +// } +// } +// } +} diff --git a/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PenguinInPlatformBridge.kt b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PenguinInPlatformBridge.kt new file mode 100644 index 00000000..5fae68ca --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PenguinInPlatformBridge.kt @@ -0,0 +1,60 @@ +package com.cloudsolutions.HMGPatientApp + +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import com.cloudsolutions.HMGPatientApp.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 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/cloudsolutions/HMGPatientApp/PermissionManager/AppPreferences.java b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/AppPreferences.java new file mode 100644 index 00000000..2f6c9722 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/AppPreferences.java @@ -0,0 +1,139 @@ +package com.cloudsolutions.HMGPatientApp.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/cloudsolutions/HMGPatientApp/PermissionManager/HostBgLocationManager.java b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/HostBgLocationManager.java new file mode 100644 index 00000000..da0d8138 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/HostBgLocationManager.java @@ -0,0 +1,136 @@ +package com.cloudsolutions.HMGPatientApp.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/cloudsolutions/HMGPatientApp/PermissionManager/HostGpsStateManager.java b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/HostGpsStateManager.java new file mode 100644 index 00000000..f7f39c97 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/HostGpsStateManager.java @@ -0,0 +1,68 @@ +package com.cloudsolutions.HMGPatientApp.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/cloudsolutions/HMGPatientApp/PermissionManager/HostNotificationPermissionManager.java b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/HostNotificationPermissionManager.java new file mode 100644 index 00000000..2dac16ca --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/HostNotificationPermissionManager.java @@ -0,0 +1,73 @@ +package com.cloudsolutions.HMGPatientApp.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/cloudsolutions/HMGPatientApp/PermissionManager/PermissionHelper.kt b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/PermissionHelper.kt new file mode 100644 index 00000000..9a033f36 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/PermissionHelper.kt @@ -0,0 +1,27 @@ +package com.cloudsolutions.HMGPatientApp.PermissionManager + +import android.Manifest + +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/cloudsolutions/HMGPatientApp/PermissionManager/PermissionManager.kt b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/PermissionManager.kt new file mode 100644 index 00000000..6dadddb1 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/PermissionManager.kt @@ -0,0 +1,50 @@ +package com.cloudsolutions.HMGPatientApp.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/cloudsolutions/HMGPatientApp/PermissionManager/PermissionResultReceiver.kt b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/PermissionResultReceiver.kt new file mode 100644 index 00000000..7c2df4cb --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/PermissionManager/PermissionResultReceiver.kt @@ -0,0 +1,15 @@ +package com.cloudsolutions.HMGPatientApp.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/cloudsolutions/HMGPatientApp/penguin/PenguinMethod.kt b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/penguin/PenguinMethod.kt new file mode 100644 index 00000000..4807bcc7 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/penguin/PenguinMethod.kt @@ -0,0 +1,13 @@ +package com.cloudsolutions.HMGPatientApp.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/cloudsolutions/HMGPatientApp/penguin/PenguinNavigator.kt b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/penguin/PenguinNavigator.kt new file mode 100644 index 00000000..29cc82df --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/penguin/PenguinNavigator.kt @@ -0,0 +1,97 @@ +package com.cloudsolutions.HMGPatientApp.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/cloudsolutions/HMGPatientApp/penguin/PenguinView.kt b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/penguin/PenguinView.kt new file mode 100644 index 00000000..2122e01c --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/penguin/PenguinView.kt @@ -0,0 +1,376 @@ +package com.cloudsolutions.HMGPatientApp.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.cloudsolutions.HMGPatientApp.PermissionManager.PermissionManager +import com.cloudsolutions.HMGPatientApp.PermissionManager.PermissionResultReceiver +import com.cloudsolutions.HMGPatientApp.MainActivity +import com.cloudsolutions.HMGPatientApp.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.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) +// navigateTo("3-1") + } + + /** + * 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/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/watch/samsung_watch/SamsungWatch.kt b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/watch/samsung_watch/SamsungWatch.kt new file mode 100644 index 00000000..336651e4 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/watch/samsung_watch/SamsungWatch.kt @@ -0,0 +1,402 @@ +package com.cloudsolutions.HMGPatientApp.watch.samsung_watch + + + +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import com.cloudsolutions.HMGPatientApp.MainActivity +import com.cloudsolutions.HMGPatientApp.watch.samsung_watch.model.Vitals +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodCall +import com.samsung.android.sdk.health.data.HealthDataService +import com.samsung.android.sdk.health.data.HealthDataStore +import com.samsung.android.sdk.health.data.data.AggregatedData +import com.samsung.android.sdk.health.data.data.HealthDataPoint +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 com.samsung.android.sdk.health.data.response.DataResponse +import io.flutter.plugin.common.MethodChannel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import java.time.LocalDateTime +import java.time.LocalTime + +class SamsungWatch( + 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 = "SamsungWatch" + + + private lateinit var vitals: MutableMap> + companion object { + private const val CHANNEL = "samsung_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" -> { + Log.d(TAG, "onMethodCall: init called") + dataStore = HealthDataService.getStore(mainActivity) + vitals = mutableMapOf() + result.success("initialized") + } + + "getPermission"->{ + if(!this::dataStore.isInitialized) + result.error("DataStoreNotInitialized", "Please call init before requesting permissions", null) + val permSet = setOf( + Permission.of(DataTypes.HEART_RATE, AccessType.READ), + Permission.of(DataTypes.STEPS, AccessType.READ), + Permission.of(DataTypes.BLOOD_OXYGEN, AccessType.READ), + Permission.of(DataTypes.ACTIVITY_SUMMARY, AccessType.READ), + Permission.of(DataTypes.SLEEP, AccessType.READ), + Permission.of(DataTypes.BODY_TEMPERATURE, AccessType.READ), + Permission.of(DataTypes.EXERCISE, AccessType.READ), +// Permission.of(DataTypes.SKIN_TEMPERATURE, AccessType.READ), +// Permission.of(DataTypes.NUTRITION, AccessType.READ), + + ) + scope.launch { + try { + var granted = dataStore.getGrantedPermissions(permSet) + + if (granted.containsAll(permSet)) { + result.success("Permission Granted") + return@launch + } + + granted = dataStore.requestPermissions(permSet, mainActivity) + + if (granted.containsAll(permSet)) { + result.success("Permission Granted") // adapt result as needed + return@launch + } + result.error("PermissionError", "Permission Not Granted", null) // adapt result as needed + } catch (e: Exception) { + Log.e(TAG, "create: getPermission failed", e) + result.error("PermissionError", e.message, null) + } + } + } + + "getHeartRate"->{ + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val readRequest = DataTypes.HEART_RATE.readDataRequestBuilder + .setLocalTimeFilter(localTimeFilter) + .setOrdering(Ordering.DESC) + .build() + + scope.launch { + val heartRateList = dataStore.readData(readRequest).dataList + processHeartVital(heartRateList) + Log.d("TAG"," the data is ${vitals}") + print("the data is ${vitals}") + result.success("Data is obtained") + } + } + + + "getSleepData" -> { + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val readRequest = DataTypes.SLEEP.readDataRequestBuilder + .setLocalTimeFilter(localTimeFilter) + .setOrdering(Ordering.ASC) + .build() + scope.launch { + val sleepData = dataStore.readData(readRequest).dataList + processSleepVital(sleepData) + print("the data is $vitals") + Log.d(TAG, "the data is $vitals") + result.success("Data is obtained") + } + + } + + "steps"->{ + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val localTimeGroup = LocalTimeGroup.of(LocalTimeGroupUnit.HOURLY, 1) + val aggregateRequest = DataType.StepsType.TOTAL.requestBuilder + .setLocalTimeFilterWithGroup(localTimeFilter, localTimeGroup) + .setOrdering(Ordering.ASC) + .build() + + scope.launch { + val steps = dataStore.aggregateData(aggregateRequest) + processStepsCount(steps) + print("the data is $vitals") + Log.d(TAG, "the data is $vitals") + result.success("Data is obtained") + } + } + + "activitySummary"->{ + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val localTimeGroup = LocalTimeGroup.of(LocalTimeGroupUnit.HOURLY, 1) + val readRequest = DataType.ActivitySummaryType.TOTAL_ACTIVE_CALORIES_BURNED + .requestBuilder + .setLocalTimeFilterWithGroup(localTimeFilter, localTimeGroup) + .setOrdering(Ordering.DESC) + .build() + + scope.launch { + val activityResult = dataStore.aggregateData(readRequest).dataList + processActivity(activityResult) + Log.d("TAG"," the data is ${vitals}") + print("the data is ${vitals}") + result.success("Data is obtained") + } + +// val readRequest = DataTypes.EXERCISE.readDataRequestBuilder +// .setLocalTimeFilter(localTimeFilter) +// .build() +// +// scope.launch{ +// try { +// val readResult = dataStore.readData(readRequest) +// val dataPoints = readResult.dataList +// +// processActivity(dataPoints) +// +// +// } catch (e: Exception) { +// e.printStackTrace() +// } +// result.success("Data is obtained") +// } + } + + "bloodOxygen"->{ + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val readRequest = DataTypes.BLOOD_OXYGEN.readDataRequestBuilder + .setLocalTimeFilter(localTimeFilter) + .setOrdering(Ordering.DESC) + .build() + + scope.launch { + val bloodOxygenList = dataStore.readData(readRequest).dataList + processBloodOxygen(bloodOxygenList) + Log.d("TAG"," the data is ${vitals}") + print("the data is ${vitals["bloodOxygen"]}") + result.success("Data is obtained") + } + } + + + "bodyTemperature"->{ + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val readRequest = DataTypes.BODY_TEMPERATURE.readDataRequestBuilder + .setLocalTimeFilter(localTimeFilter) + .setOrdering(Ordering.DESC) + .build() + + scope.launch { + val bodyTemperatureList = dataStore.readData(readRequest).dataList + processBodyTemperature(bodyTemperatureList) + Log.d("TAG"," the data is ${vitals}") + print("the data is ${vitals["bodyTemperature"]}") + result.success("Data is obtained") + } + } + + "distance"->{ + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val localTimeGroup = LocalTimeGroup.of(LocalTimeGroupUnit.HOURLY, 1) + val readRequest = DataType.ActivitySummaryType.TOTAL_DISTANCE.requestBuilder + .setLocalTimeFilterWithGroup(localTimeFilter, localTimeGroup) + .setOrdering(Ordering.DESC) + .build() + + scope.launch { + val activityResult = dataStore.aggregateData(readRequest).dataList + processDistance(activityResult) + Log.d("TAG"," the data is ${vitals}") + print("the data is ${vitals}") + result.success("Data is obtained") + } + } + + "retrieveData"->{ + if(vitals.isEmpty()){ + result.error("NoDataFound", "No Data was obtained", null) + return@setMethodCallHandler + } + result.success(""" + { + "heartRate": ${vitals["heartRate"]}, + "steps": ${vitals["steps"]}, + "sleep": ${vitals["sleep"]}, + "activity": ${vitals["activity"]}, + "bloodOxygen": ${vitals["bloodOxygen"]}, + "bodyTemperature": ${vitals["bodyTemperature"]}, + "distance": ${vitals["distance"]} + } + """.trimIndent()) + } + + + "closeCoroutineScope"->{ + destroy() + result.success("Coroutine Scope Cancelled") + } + + else -> { + result.notImplemented() + } + } + } + } + + private fun CoroutineScope.processDistance(activityResult: List>) { + vitals["distance"] = mutableListOf() + activityResult.forEach { stepData -> + val vitalData = Vitals().apply { + + value = stepData.value.toString() + timeStamp = stepData.startTime.toString() + } + (vitals["distance"] as MutableList).add(vitalData) + } + } + + private fun CoroutineScope.processBodyTemperature( bodyTemperatureList :List) { + vitals["bodyTemperature"] = mutableListOf() + bodyTemperatureList.forEach { stepData -> + val vitalData = Vitals().apply { + value = stepData.getValue(DataType.BodyTemperatureType.BODY_TEMPERATURE).toString() + timeStamp = stepData.endTime.toString() + } + (vitals["bodyTemperature"] as MutableList).add(vitalData) + } + } + + private fun CoroutineScope.processBloodOxygen( bloodOxygenList :List) { + vitals["bloodOxygen"] = mutableListOf() + bloodOxygenList.forEach { stepData -> + val vitalData = Vitals().apply { + value = stepData.getValue(DataType.BloodOxygenType.OXYGEN_SATURATION).toString() + timeStamp = stepData.endTime.toString() + } + (vitals["bloodOxygen"] as MutableList).add(vitalData) + } + } + + +// private fun CoroutineScope.processActivity(activityResult: List>) { +// +// vitals["activity"] = mutableListOf() +// activityResult.forEach { stepData -> +// val vitalData = Vitals().apply { +// +// value = stepData.value.toString() +// timeStamp = stepData.startTime.toString() +// } +// (vitals["activity"] as MutableList).add(vitalData) +// } +// } + private fun CoroutineScope.processActivity(activityResult: List>) { + + vitals["activity"] = mutableListOf() + activityResult.forEach { stepData -> + val vitalData = Vitals().apply { + + value = stepData.value.toString() + timeStamp = stepData.startTime.toString() + } + (vitals["activity"] as MutableList).add(vitalData) + } + +// dataPoints.forEach { dataPoint -> +// val sessions = dataPoint.getValue(DataType.ExerciseType.SESSIONS) +// +// sessions?.forEach { session -> +// +// val exerciseSessionCalories = session.calories +// val vitalData = Vitals().apply { +// value = exerciseSessionCalories.toString() +// timeStamp = session.startTime.toString() +// } +// (vitals["activity"] as MutableList).add(vitalData) +// } +// } + } + + private fun CoroutineScope.processStepsCount(result: DataResponse>) { + val stepCount = ArrayList>() + var totalSteps: Long = 0 + vitals["steps"] = mutableListOf() + result.dataList.forEach { stepData -> + val vitalData = Vitals().apply { + value = (stepData.value as Long).toString() + timeStamp = stepData.startTime.toString() + } + (vitals["steps"] as MutableList).add(vitalData) + } + + } + + private fun CoroutineScope.processSleepVital(sleepData: List) { + vitals["sleep"] = mutableListOf() + sleepData.forEach { + (vitals["sleep"] as MutableList).add( + Vitals().apply { + timeStamp = it.startTime.toString() + value = (it.getValue(DataType.SleepType.DURATION)?.toMillis().toString()) + } + ) + } + } + + private suspend fun CoroutineScope.processHeartVital( + heartRateList: List, + ) { + vitals["heartRate"] = mutableListOf() + heartRateList.forEach { + (vitals["heartRate"] as MutableList).add(processHeartRateData(it)) + } + } + + private fun processHeartRateData(heartRateData: HealthDataPoint) = + Vitals().apply { + heartRateData.getValue(DataType.HeartRateType.MAX_HEART_RATE)?.let { + value = it.toString() + } + timeStamp = heartRateData.startTime.toString() + } + + + fun destroy() { + scope.cancel() + } + +} diff --git a/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/watch/samsung_watch/model/Vitals.kt b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/watch/samsung_watch/model/Vitals.kt new file mode 100644 index 00000000..577ab283 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloudsolutions/HMGPatientApp/watch/samsung_watch/model/Vitals.kt @@ -0,0 +1,13 @@ +package com.cloudsolutions.HMGPatientApp.watch.samsung_watch.model + +data class Vitals( + var value : String = "", + var timeStamp :String = "" +){ + override fun toString(): String { + return """{ + "value": "$value", + "timeStamp": "$timeStamp"} + """.trimIndent() + } +} \ No newline at end of file diff --git a/android/app/src/main2/AndroidManifest.xml b/android/app/src/main2/AndroidManifest.xml new file mode 100644 index 00000000..e07739b9 --- /dev/null +++ b/android/app/src/main2/AndroidManifest.xml @@ -0,0 +1,274 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main2/kotlin/com/ejada/hmg/MainActivity.kt b/android/app/src/main2/kotlin/com/ejada/hmg/MainActivity.kt new file mode 100644 index 00000000..ece584b2 --- /dev/null +++ b/android/app/src/main2/kotlin/com/ejada/hmg/MainActivity.kt @@ -0,0 +1,84 @@ +package com.ejada.hmg + +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.Nullable +import androidx.annotation.RequiresApi +import com.ejada.hmg.penguin.PenguinInPlatformBridge +import com.ejada.hmg.watch.huawei.HuaweiWatch +import com.ejada.hmg.watch.huawei.samsung_watch.SamsungWatch +import com.huawei.hms.hihealth.result.HealthKitAuthResult +import com.huawei.hms.support.api.entity.auth.Scope +import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugins.GeneratedPluginRegistrant + + +class MainActivity: FlutterFragmentActivity() { + + private var huaweiWatch : HuaweiWatch? = null + @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() + SamsungWatch(flutterEngine, this) + huaweiWatch = HuaweiWatch(flutterEngine, this) + } + + 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() + } + +// override fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) { +// super.onActivityResult(requestCode, resultCode, data) +// +// // Process only the response result of the authorization process. +// if (requestCode == 1002) { +// // Obtain the authorization response result from the intent. +// val result: HealthKitAuthResult? = huaweiWatch?.mSettingController?.parseHealthKitAuthResultFromIntent(data) +// if (result == null) { +// Log.w(huaweiWatch?.TAG, "authorization fail") +// return +// } +// +// if (result.isSuccess) { +// Log.i(huaweiWatch?.TAG, "authorization success") +// if (result.getAuthAccount() != null && result.authAccount.authorizedScopes != null) { +// val authorizedScopes: MutableSet = result.authAccount.authorizedScopes +// if(authorizedScopes.isNotEmpty()) { +// huaweiWatch?.getHealthAppAuthorization() +// } +// } +// } else { +// Log.w("MainActivty", "authorization fail, errorCode:" + result.getErrorCode()) +// } +// } +// } +} diff --git a/android/app/src/main2/kotlin/com/ejada/hmg/watch/samsung_watch/SamsungWatch.kt b/android/app/src/main2/kotlin/com/ejada/hmg/watch/samsung_watch/SamsungWatch.kt new file mode 100644 index 00000000..09aafff2 --- /dev/null +++ b/android/app/src/main2/kotlin/com/ejada/hmg/watch/samsung_watch/SamsungWatch.kt @@ -0,0 +1,402 @@ +package com.ejada.hmg.watch.huawei.samsung_watch + + + +import com.ejada.hmg.MainActivity +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodCall +import com.ejada.hmg.watch.huawei.samsung_watch.model.Vitals +import com.samsung.android.sdk.health.data.HealthDataService +import com.samsung.android.sdk.health.data.HealthDataStore +import com.samsung.android.sdk.health.data.data.AggregatedData +import com.samsung.android.sdk.health.data.data.HealthDataPoint +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 com.samsung.android.sdk.health.data.response.DataResponse +import io.flutter.plugin.common.MethodChannel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import java.time.LocalDateTime +import java.time.LocalTime + +class SamsungWatch( + 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 = "SamsungWatch" + + + private lateinit var vitals: MutableMap> + companion object { + private const val CHANNEL = "samsung_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" -> { + Log.d(TAG, "onMethodCall: init called") + dataStore = HealthDataService.getStore(mainActivity) + vitals = mutableMapOf() + result.success("initialized") + } + + "getPermission"->{ + if(!this::dataStore.isInitialized) + result.error("DataStoreNotInitialized", "Please call init before requesting permissions", null) + val permSet = setOf( + Permission.of(DataTypes.HEART_RATE, AccessType.READ), + Permission.of(DataTypes.STEPS, AccessType.READ), + Permission.of(DataTypes.BLOOD_OXYGEN, AccessType.READ), + Permission.of(DataTypes.ACTIVITY_SUMMARY, AccessType.READ), + Permission.of(DataTypes.SLEEP, AccessType.READ), + Permission.of(DataTypes.BODY_TEMPERATURE, AccessType.READ), + Permission.of(DataTypes.EXERCISE, AccessType.READ), +// Permission.of(DataTypes.SKIN_TEMPERATURE, AccessType.READ), +// Permission.of(DataTypes.NUTRITION, AccessType.READ), + + ) + scope.launch { + try { + var granted = dataStore.getGrantedPermissions(permSet) + + if (granted.containsAll(permSet)) { + result.success("Permission Granted") + return@launch + } + + granted = dataStore.requestPermissions(permSet, mainActivity) + + if (granted.containsAll(permSet)) { + result.success("Permission Granted") // adapt result as needed + return@launch + } + result.error("PermissionError", "Permission Not Granted", null) // adapt result as needed + } catch (e: Exception) { + Log.e(TAG, "create: getPermission failed", e) + result.error("PermissionError", e.message, null) + } + } + } + + "getHeartRate"->{ + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val readRequest = DataTypes.HEART_RATE.readDataRequestBuilder + .setLocalTimeFilter(localTimeFilter) + .setOrdering(Ordering.DESC) + .build() + + scope.launch { + val heartRateList = dataStore.readData(readRequest).dataList + processHeartVital(heartRateList) + Log.d("TAG"," the data is ${vitals}") + print("the data is ${vitals}") + result.success("Data is obtained") + } + } + + + "getSleepData" -> { + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val readRequest = DataTypes.SLEEP.readDataRequestBuilder + .setLocalTimeFilter(localTimeFilter) + .setOrdering(Ordering.ASC) + .build() + scope.launch { + val sleepData = dataStore.readData(readRequest).dataList + processSleepVital(sleepData) + print("the data is $vitals") + Log.d(TAG, "the data is $vitals") + result.success("Data is obtained") + } + + } + + "steps"->{ + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val localTimeGroup = LocalTimeGroup.of(LocalTimeGroupUnit.HOURLY, 1) + val aggregateRequest = DataType.StepsType.TOTAL.requestBuilder + .setLocalTimeFilterWithGroup(localTimeFilter, localTimeGroup) + .setOrdering(Ordering.ASC) + .build() + + scope.launch { + val steps = dataStore.aggregateData(aggregateRequest) + processStepsCount(steps) + print("the data is $vitals") + Log.d(TAG, "the data is $vitals") + result.success("Data is obtained") + } + } + + "activitySummary"->{ + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val localTimeGroup = LocalTimeGroup.of(LocalTimeGroupUnit.HOURLY, 1) + val readRequest = DataType.ActivitySummaryType.TOTAL_ACTIVE_CALORIES_BURNED + .requestBuilder + .setLocalTimeFilterWithGroup(localTimeFilter, localTimeGroup) + .setOrdering(Ordering.DESC) + .build() + + scope.launch { + val activityResult = dataStore.aggregateData(readRequest).dataList + processActivity(activityResult) + Log.d("TAG"," the data is ${vitals}") + print("the data is ${vitals}") + result.success("Data is obtained") + } + +// val readRequest = DataTypes.EXERCISE.readDataRequestBuilder +// .setLocalTimeFilter(localTimeFilter) +// .build() +// +// scope.launch{ +// try { +// val readResult = dataStore.readData(readRequest) +// val dataPoints = readResult.dataList +// +// processActivity(dataPoints) +// +// +// } catch (e: Exception) { +// e.printStackTrace() +// } +// result.success("Data is obtained") +// } + } + + "bloodOxygen"->{ + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val readRequest = DataTypes.BLOOD_OXYGEN.readDataRequestBuilder + .setLocalTimeFilter(localTimeFilter) + .setOrdering(Ordering.DESC) + .build() + + scope.launch { + val bloodOxygenList = dataStore.readData(readRequest).dataList + processBloodOxygen(bloodOxygenList) + Log.d("TAG"," the data is ${vitals}") + print("the data is ${vitals["bloodOxygen"]}") + result.success("Data is obtained") + } + } + + + "bodyTemperature"->{ + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val readRequest = DataTypes.BODY_TEMPERATURE.readDataRequestBuilder + .setLocalTimeFilter(localTimeFilter) + .setOrdering(Ordering.DESC) + .build() + + scope.launch { + val bodyTemperatureList = dataStore.readData(readRequest).dataList + processBodyTemperature(bodyTemperatureList) + Log.d("TAG"," the data is ${vitals}") + print("the data is ${vitals["bodyTemperature"]}") + result.success("Data is obtained") + } + } + + "distance"->{ + val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) + val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) + val localTimeGroup = LocalTimeGroup.of(LocalTimeGroupUnit.HOURLY, 1) + val readRequest = DataType.ActivitySummaryType.TOTAL_DISTANCE.requestBuilder + .setLocalTimeFilterWithGroup(localTimeFilter, localTimeGroup) + .setOrdering(Ordering.DESC) + .build() + + scope.launch { + val activityResult = dataStore.aggregateData(readRequest).dataList + processDistance(activityResult) + Log.d("TAG"," the data is ${vitals}") + print("the data is ${vitals}") + result.success("Data is obtained") + } + } + + "retrieveData"->{ + if(vitals.isEmpty()){ + result.error("NoDataFound", "No Data was obtained", null) + return@setMethodCallHandler + } + result.success(""" + { + "heartRate": ${vitals["heartRate"]}, + "steps": ${vitals["steps"]}, + "sleep": ${vitals["sleep"]}, + "activity": ${vitals["activity"]}, + "bloodOxygen": ${vitals["bloodOxygen"]}, + "bodyTemperature": ${vitals["bodyTemperature"]}, + "distance": ${vitals["distance"]} + } + """.trimIndent()) + } + + + "closeCoroutineScope"->{ + destroy() + result.success("Coroutine Scope Cancelled") + } + + else -> { + result.notImplemented() + } + } + } + } + + private fun CoroutineScope.processDistance(activityResult: List>) { + vitals["distance"] = mutableListOf() + activityResult.forEach { stepData -> + val vitalData = Vitals().apply { + + value = stepData.value.toString() + timeStamp = stepData.startTime.toString() + } + (vitals["distance"] as MutableList).add(vitalData) + } + } + + private fun CoroutineScope.processBodyTemperature( bodyTemperatureList :List) { + vitals["bodyTemperature"] = mutableListOf() + bodyTemperatureList.forEach { stepData -> + val vitalData = Vitals().apply { + value = stepData.getValue(DataType.BodyTemperatureType.BODY_TEMPERATURE).toString() + timeStamp = stepData.endTime.toString() + } + (vitals["bodyTemperature"] as MutableList).add(vitalData) + } + } + + private fun CoroutineScope.processBloodOxygen( bloodOxygenList :List) { + vitals["bloodOxygen"] = mutableListOf() + bloodOxygenList.forEach { stepData -> + val vitalData = Vitals().apply { + value = stepData.getValue(DataType.BloodOxygenType.OXYGEN_SATURATION).toString() + timeStamp = stepData.endTime.toString() + } + (vitals["bloodOxygen"] as MutableList).add(vitalData) + } + } + + +// private fun CoroutineScope.processActivity(activityResult: List>) { +// +// vitals["activity"] = mutableListOf() +// activityResult.forEach { stepData -> +// val vitalData = Vitals().apply { +// +// value = stepData.value.toString() +// timeStamp = stepData.startTime.toString() +// } +// (vitals["activity"] as MutableList).add(vitalData) +// } +// } + private fun CoroutineScope.processActivity(activityResult: List>) { + + vitals["activity"] = mutableListOf() + activityResult.forEach { stepData -> + val vitalData = Vitals().apply { + + value = stepData.value.toString() + timeStamp = stepData.startTime.toString() + } + (vitals["activity"] as MutableList).add(vitalData) + } + +// dataPoints.forEach { dataPoint -> +// val sessions = dataPoint.getValue(DataType.ExerciseType.SESSIONS) +// +// sessions?.forEach { session -> +// +// val exerciseSessionCalories = session.calories +// val vitalData = Vitals().apply { +// value = exerciseSessionCalories.toString() +// timeStamp = session.startTime.toString() +// } +// (vitals["activity"] as MutableList).add(vitalData) +// } +// } + } + + private fun CoroutineScope.processStepsCount(result: DataResponse>) { + val stepCount = ArrayList>() + var totalSteps: Long = 0 + vitals["steps"] = mutableListOf() + result.dataList.forEach { stepData -> + val vitalData = Vitals().apply { + value = (stepData.value as Long).toString() + timeStamp = stepData.startTime.toString() + } + (vitals["steps"] as MutableList).add(vitalData) + } + + } + + private fun CoroutineScope.processSleepVital(sleepData: List) { + vitals["sleep"] = mutableListOf() + sleepData.forEach { + (vitals["sleep"] as MutableList).add( + Vitals().apply { + timeStamp = it.startTime.toString() + value = (it.getValue(DataType.SleepType.DURATION)?.toMillis().toString()) + } + ) + } + } + + private suspend fun CoroutineScope.processHeartVital( + heartRateList: List, + ) { + vitals["heartRate"] = mutableListOf() + heartRateList.forEach { + (vitals["heartRate"] as MutableList).add(processHeartRateData(it)) + } + } + + private fun processHeartRateData(heartRateData: HealthDataPoint) = + Vitals().apply { + heartRateData.getValue(DataType.HeartRateType.MAX_HEART_RATE)?.let { + value = it.toString() + } + timeStamp = heartRateData.startTime.toString() + } + + + fun destroy() { + scope.cancel() + } + +} diff --git a/android/app/src/main2/kotlin/com/ejada/hmg/watch/samsung_watch/model/Vitals.kt b/android/app/src/main2/kotlin/com/ejada/hmg/watch/samsung_watch/model/Vitals.kt new file mode 100644 index 00000000..3b5cdfe4 --- /dev/null +++ b/android/app/src/main2/kotlin/com/ejada/hmg/watch/samsung_watch/model/Vitals.kt @@ -0,0 +1,13 @@ +package com.ejada.hmg.watch.huawei.samsung_watch.model + +data class Vitals( + var value : String = "", + var timeStamp :String = "" +){ + override fun toString(): String { + return """{ + "value": "$value", + "timeStamp": "$timeStamp"} + """.trimIndent() + } +} \ No newline at end of file diff --git a/android/app/src/main2/res/drawable-v21/launch_background.xml b/android/app/src/main2/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/android/app/src/main2/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main2/res/drawable/app_icon.png b/android/app/src/main2/res/drawable/app_icon.png new file mode 100755 index 00000000..2d394f83 Binary files /dev/null and b/android/app/src/main2/res/drawable/app_icon.png differ diff --git a/android/app/src/main2/res/drawable/food.png b/android/app/src/main2/res/drawable/food.png new file mode 100644 index 00000000..41b394d3 Binary files /dev/null and b/android/app/src/main2/res/drawable/food.png differ diff --git a/android/app/src/main2/res/drawable/launch_background.xml b/android/app/src/main2/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/android/app/src/main2/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main2/res/drawable/me.png b/android/app/src/main2/res/drawable/me.png new file mode 100644 index 00000000..ba75bc55 Binary files /dev/null and b/android/app/src/main2/res/drawable/me.png differ diff --git a/android/app/src/main2/res/drawable/sample_large_icon.png b/android/app/src/main2/res/drawable/sample_large_icon.png new file mode 100644 index 00000000..f354ca23 Binary files /dev/null and b/android/app/src/main2/res/drawable/sample_large_icon.png differ diff --git a/android/app/src/main2/res/drawable/secondary_icon.png b/android/app/src/main2/res/drawable/secondary_icon.png new file mode 100644 index 00000000..9de9ff41 Binary files /dev/null and b/android/app/src/main2/res/drawable/secondary_icon.png differ diff --git a/android/app/src/main2/res/layout/activity_whats_app_code.xml b/android/app/src/main2/res/layout/activity_whats_app_code.xml new file mode 100644 index 00000000..3cd824c9 --- /dev/null +++ b/android/app/src/main2/res/layout/activity_whats_app_code.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/android/app/src/main2/res/layout/local_video.xml b/android/app/src/main2/res/layout/local_video.xml new file mode 100644 index 00000000..f47c48cd --- /dev/null +++ b/android/app/src/main2/res/layout/local_video.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main2/res/layout/remote_video.xml b/android/app/src/main2/res/layout/remote_video.xml new file mode 100644 index 00000000..cfdbeb0d --- /dev/null +++ b/android/app/src/main2/res/layout/remote_video.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/package/device_calendar_plus/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main2/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from package/device_calendar_plus/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to android/app/src/main2/res/mipmap-hdpi/ic_launcher.png diff --git a/android/app/src/main2/res/mipmap-hdpi/ic_launcher_local.png b/android/app/src/main2/res/mipmap-hdpi/ic_launcher_local.png new file mode 100644 index 00000000..348b5116 Binary files /dev/null and b/android/app/src/main2/res/mipmap-hdpi/ic_launcher_local.png differ diff --git a/package/device_calendar_plus/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main2/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from package/device_calendar_plus/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to android/app/src/main2/res/mipmap-mdpi/ic_launcher.png diff --git a/android/app/src/main2/res/mipmap-mdpi/ic_launcher_local.png b/android/app/src/main2/res/mipmap-mdpi/ic_launcher_local.png new file mode 100644 index 00000000..410b1b1e Binary files /dev/null and b/android/app/src/main2/res/mipmap-mdpi/ic_launcher_local.png differ diff --git a/package/device_calendar_plus/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main2/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from package/device_calendar_plus/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to android/app/src/main2/res/mipmap-xhdpi/ic_launcher.png diff --git a/android/app/src/main2/res/mipmap-xhdpi/ic_launcher_local.png b/android/app/src/main2/res/mipmap-xhdpi/ic_launcher_local.png new file mode 100644 index 00000000..bb9943af Binary files /dev/null and b/android/app/src/main2/res/mipmap-xhdpi/ic_launcher_local.png differ diff --git a/package/device_calendar_plus/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main2/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from package/device_calendar_plus/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to android/app/src/main2/res/mipmap-xxhdpi/ic_launcher.png diff --git a/android/app/src/main2/res/mipmap-xxhdpi/ic_launcher_local.png b/android/app/src/main2/res/mipmap-xxhdpi/ic_launcher_local.png new file mode 100644 index 00000000..0b9d9359 Binary files /dev/null and b/android/app/src/main2/res/mipmap-xxhdpi/ic_launcher_local.png differ diff --git a/package/device_calendar_plus/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main2/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from package/device_calendar_plus/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to android/app/src/main2/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/android/app/src/main2/res/mipmap-xxxhdpi/ic_launcher_local.png b/android/app/src/main2/res/mipmap-xxxhdpi/ic_launcher_local.png new file mode 100644 index 00000000..aaa9808d Binary files /dev/null and b/android/app/src/main2/res/mipmap-xxxhdpi/ic_launcher_local.png differ diff --git a/android/app/src/main2/res/raw/keep.xml b/android/app/src/main2/res/raw/keep.xml new file mode 100644 index 00000000..944a7ace --- /dev/null +++ b/android/app/src/main2/res/raw/keep.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/android/app/src/main2/res/raw/slow_spring_board.mp3 b/android/app/src/main2/res/raw/slow_spring_board.mp3 new file mode 100644 index 00000000..60dbf979 Binary files /dev/null and b/android/app/src/main2/res/raw/slow_spring_board.mp3 differ diff --git a/android/app/src/main2/res/values-night/styles.xml b/android/app/src/main2/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/android/app/src/main2/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main2/res/values/mapbox_access_token.xml b/android/app/src/main2/res/values/mapbox_access_token.xml new file mode 100644 index 00000000..65bc4b37 --- /dev/null +++ b/android/app/src/main2/res/values/mapbox_access_token.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/android/app/src/main2/res/values/strings.xml b/android/app/src/main2/res/values/strings.xml new file mode 100644 index 00000000..2d103337 --- /dev/null +++ b/android/app/src/main2/res/values/strings.xml @@ -0,0 +1,23 @@ + + HMG Patient App + + + Unknown error: the Geofence service is not available now. + + + Geofence service is not available now. Go to Settings>Location>Mode and choose High accuracy. + + + Your app has registered too many geofences. + + + You have provided too many PendingIntents to the addGeofences() call. + + + App do not have permission to access location service. + + + Geofence requests happened too frequently. + + pk.eyJ1IjoicndhaWQiLCJhIjoiY2x5cGo4aHNjMGNsbTJyc2djempobGQxaSJ9.RCaC6WrUt4A4YnZNfxnONQ + diff --git a/android/app/src/main2/res/values/styles.xml b/android/app/src/main2/res/values/styles.xml new file mode 100644 index 00000000..1f83a33f --- /dev/null +++ b/android/app/src/main2/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/gradle.properties b/android/gradle.properties index f018a618..647291d1 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,10 @@ -org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true +#org.gradle.jvmargs=-xmx4608m +android.enableR8=true android.enableJetifier=true +android.useDeprecatedNdk=true +org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.configureondemand=true +android.useAndroidX=true +android.enableImpeller=false diff --git a/assets/images/svg/bluetooth.svg b/assets/images/svg/bluetooth.svg new file mode 100644 index 00000000..5fe2cf0c --- /dev/null +++ b/assets/images/svg/bluetooth.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/watch_activity.svg b/assets/images/svg/watch_activity.svg new file mode 100644 index 00000000..8da915fa --- /dev/null +++ b/assets/images/svg/watch_activity.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/watch_activity_trailing.svg b/assets/images/svg/watch_activity_trailing.svg new file mode 100644 index 00000000..dc09c058 --- /dev/null +++ b/assets/images/svg/watch_activity_trailing.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/watch_bmi.svg b/assets/images/svg/watch_bmi.svg new file mode 100644 index 00000000..4d34d5d0 --- /dev/null +++ b/assets/images/svg/watch_bmi.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/watch_bmi_trailing.svg b/assets/images/svg/watch_bmi_trailing.svg new file mode 100644 index 00000000..772d18a4 --- /dev/null +++ b/assets/images/svg/watch_bmi_trailing.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/watch_height.svg b/assets/images/svg/watch_height.svg new file mode 100644 index 00000000..e70b4b89 --- /dev/null +++ b/assets/images/svg/watch_height.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/watch_sleep.svg b/assets/images/svg/watch_sleep.svg new file mode 100644 index 00000000..dd21f628 --- /dev/null +++ b/assets/images/svg/watch_sleep.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/watch_sleep_trailing.svg b/assets/images/svg/watch_sleep_trailing.svg new file mode 100644 index 00000000..dff5ab36 --- /dev/null +++ b/assets/images/svg/watch_sleep_trailing.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/watch_steps.svg b/assets/images/svg/watch_steps.svg new file mode 100644 index 00000000..730c1892 --- /dev/null +++ b/assets/images/svg/watch_steps.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/watch_steps_trailing.svg b/assets/images/svg/watch_steps_trailing.svg new file mode 100644 index 00000000..2c1b2f20 --- /dev/null +++ b/assets/images/svg/watch_steps_trailing.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/svg/watch_weight.svg b/assets/images/svg/watch_weight.svg new file mode 100644 index 00000000..3acddca5 --- /dev/null +++ b/assets/images/svg/watch_weight.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/watch_weight_trailing.svg b/assets/images/svg/watch_weight_trailing.svg new file mode 100644 index 00000000..3e2b3dd8 --- /dev/null +++ b/assets/images/svg/watch_weight_trailing.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index f929b1ae..437fa7e6 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1580,8 +1580,24 @@ "reschedulingAppo": "إعادة جدولة الموعد، يرجى الانتظار...", "invalidEligibility": "لا يمكنك إجراء الدفع عبر الإنترنت لأنك غير مؤهل لاستخدام الخدمة المقدمة.", "invalidInsurance": "لا يمكنك إجراء الدفع عبر الإنترنت لأنه ليس لديك تأمين صالح.", + "continueCash": "تواصل نقدا", + "applewatch": "ساعة آبل", + "applehealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Apple Health على هاتفك", + "unabletodetectapplicationinstalledpleasecomebackonceinstalled": "لا يمكننا اكتشاف التطبيق المثبت على جهازك. يرجى العودة إلى هنا بمجرد تثبيت هذا التطبيق.", + "applewatchshouldbeconnected": "يجب توصيل ساعة آبل", + "samsungwatch": "ساعة سامسونج", + "samsunghealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Samsung Health على هاتفك", + "samsungwatchshouldbeconnected": "يجب توصيل ساعة سامسونج", + "huaweiwatch": "ساعة هواوي", + "huaweihealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Huawei Health على هاتفك", + "huaweiwatchshouldbeconnected": "يجب توصيل ساعة هواوي", + "whoopwatch": "ساعة Whoop", + "whoophealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Whoop Health على هاتفك", + "whoopwatchshouldbeconnected": "يجب توصيل ساعة Whoop", + "updatetheinformation": "سيتيح ذلك جمع أحدث المعلومات من ساعة آبل الخاصة بك", "continueCash": "متابعة الدفع نقدًا", "timeFor": "الوقت", "hmgPolicies": "سياسات مجموعة الحبيب الطبية", - "darkMode": "المظهر الداكن" + "darkMode": "المظهر الداكن", + "featureComingSoonDescription": "هذه الميزة ستتوفر قريباً. نحن نعمل جاهدين لإضافة ميزات أكثر تميزاً إلى التطبيق. انتظرونا لمتابعة التحديثات." } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index b1cc5b08..76c936f4 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1574,7 +1574,22 @@ "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", + "applewatch": "Apple Watch", + "applehealthapplicationshouldbeinstalledinyourphone": "Apple Health application should be installed in your phone", + "unabletodetectapplicationinstalledpleasecomebackonceinstalled": "We are unable to detect the application installed in your device. Please come back here once you have installed this application.", + "applewatchshouldbeconnected": "Apple Watch should be connected", + "samsungwatch": "Samsung Watch", + "samsunghealthapplicationshouldbeinstalledinyourphone": "Samsung Health application should be installed in your phone", + "samsungwatchshouldbeconnected": "Samsung Watch should be connected", + "huaweiwatch": "Huawei Watch", + "huaweihealthapplicationshouldbeinstalledinyourphone": "Huawei Health application should be installed in your phone", + "huaweiwatchshouldbeconnected": "Huawei Watch should be connected", + "whoopwatch": "Whoop Watch", + "whoophealthapplicationshouldbeinstalledinyourphone": "Whoop Health application should be installed in your phone", + "whoopwatchshouldbeconnected": "Whoop Watch should be connected", + "updatetheinformation": "This will allow to gather the most up to date information from your apple watch", "timeFor": "Time For", "hmgPolicies": "HMG Policies", - "darkMode": "Dark Mode" + "darkMode": "Dark Mode", + "featureComingSoonDescription": "Feature is coming soon. We are actively working to bring more exciting features into the app. Stay tuned for updates." } diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 00000000..02c8b5da --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,543 @@ +PODS: + - amazon_payfort (1.1.4): + - Flutter + - PayFortSDK + - audio_session (0.0.1): + - Flutter + - barcode_scan2 (0.0.1): + - Flutter + - SwiftProtobuf (~> 1.33) + - connectivity_plus (0.0.1): + - Flutter + - CryptoSwift (1.8.4) + - device_calendar (0.0.1): + - Flutter + - device_calendar_plus_ios (0.0.1): + - Flutter + - device_info_plus (0.0.1): + - Flutter + - DKImagePickerController/Core (4.3.9): + - DKImagePickerController/ImageDataManager + - DKImagePickerController/Resource + - DKImagePickerController/ImageDataManager (4.3.9) + - DKImagePickerController/PhotoGallery (4.3.9): + - DKImagePickerController/Core + - DKPhotoGallery + - DKImagePickerController/Resource (4.3.9) + - DKPhotoGallery (0.0.19): + - DKPhotoGallery/Core (= 0.0.19) + - DKPhotoGallery/Model (= 0.0.19) + - DKPhotoGallery/Preview (= 0.0.19) + - DKPhotoGallery/Resource (= 0.0.19) + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Core (0.0.19): + - DKPhotoGallery/Model + - DKPhotoGallery/Preview + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Model (0.0.19): + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Preview (0.0.19): + - DKPhotoGallery/Model + - DKPhotoGallery/Resource + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Resource (0.0.19): + - SDWebImage + - SwiftyGif + - file_picker (0.0.1): + - DKImagePickerController/PhotoGallery + - Flutter + - Firebase/Analytics (11.15.0): + - Firebase/Core + - Firebase/Core (11.15.0): + - Firebase/CoreOnly + - FirebaseAnalytics (~> 11.15.0) + - Firebase/CoreOnly (11.15.0): + - FirebaseCore (~> 11.15.0) + - Firebase/Messaging (11.15.0): + - Firebase/CoreOnly + - FirebaseMessaging (~> 11.15.0) + - firebase_analytics (11.6.0): + - Firebase/Analytics (= 11.15.0) + - firebase_core + - Flutter + - firebase_core (3.15.2): + - Firebase/CoreOnly (= 11.15.0) + - Flutter + - firebase_messaging (15.2.10): + - Firebase/Messaging (= 11.15.0) + - firebase_core + - Flutter + - FirebaseAnalytics (11.15.0): + - FirebaseAnalytics/Default (= 11.15.0) + - FirebaseCore (~> 11.15.0) + - FirebaseInstallations (~> 11.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - FirebaseAnalytics/Default (11.15.0): + - FirebaseCore (~> 11.15.0) + - FirebaseInstallations (~> 11.0) + - GoogleAppMeasurement/Default (= 11.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - FirebaseCore (11.15.0): + - FirebaseCoreInternal (~> 11.15.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Logger (~> 8.1) + - FirebaseCoreInternal (11.15.0): + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - FirebaseInstallations (11.15.0): + - FirebaseCore (~> 11.15.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) + - PromisesObjC (~> 2.4) + - FirebaseMessaging (11.15.0): + - FirebaseCore (~> 11.15.0) + - FirebaseInstallations (~> 11.0) + - GoogleDataTransport (~> 10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Reachability (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) + - nanopb (~> 3.30910.0) + - FLAnimatedImage (1.0.17) + - Flutter (1.0.0) + - flutter_callkit_incoming (0.0.1): + - CryptoSwift + - Flutter + - flutter_inappwebview_ios (0.0.1): + - Flutter + - flutter_inappwebview_ios/Core (= 0.0.1) + - OrderedSet (~> 6.0.3) + - flutter_inappwebview_ios/Core (0.0.1): + - Flutter + - OrderedSet (~> 6.0.3) + - flutter_ios_voip_kit_karmm (0.8.0): + - Flutter + - flutter_local_notifications (0.0.1): + - Flutter + - flutter_nfc_kit (3.6.0): + - Flutter + - flutter_zoom_videosdk (0.0.1): + - Flutter + - ZoomVideoSDK/CptShare (= 2.1.10) + - ZoomVideoSDK/zm_annoter_dynamic (= 2.1.10) + - ZoomVideoSDK/zoomcml (= 2.1.10) + - ZoomVideoSDK/ZoomVideoSDK (= 2.1.10) + - fluttertoast (0.0.2): + - Flutter + - geolocator_apple (1.2.0): + - Flutter + - FlutterMacOS + - Google-Maps-iOS-Utils (5.0.0): + - GoogleMaps (~> 8.0) + - google_maps_flutter_ios (0.0.1): + - Flutter + - Google-Maps-iOS-Utils (< 7.0, >= 5.0) + - GoogleMaps (< 11.0, >= 8.4) + - GoogleAdsOnDeviceConversion (2.1.0): + - GoogleUtilities/Logger (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/Core (11.15.0): + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/Default (11.15.0): + - GoogleAdsOnDeviceConversion (= 2.1.0) + - GoogleAppMeasurement/Core (= 11.15.0) + - GoogleAppMeasurement/IdentitySupport (= 11.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/IdentitySupport (11.15.0): + - GoogleAppMeasurement/Core (= 11.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleDataTransport (10.1.0): + - nanopb (~> 3.30910.0) + - PromisesObjC (~> 2.4) + - GoogleMaps (8.4.0): + - GoogleMaps/Maps (= 8.4.0) + - GoogleMaps/Base (8.4.0) + - GoogleMaps/Maps (8.4.0): + - GoogleMaps/Base + - GoogleUtilities/AppDelegateSwizzler (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (8.1.0): + - GoogleUtilities/Privacy + - GoogleUtilities/Logger (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/MethodSwizzler (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/Network (8.1.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (8.1.0)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (8.1.0) + - GoogleUtilities/Reachability (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - health (13.1.4): + - Flutter + - image_picker_ios (0.0.1): + - Flutter + - just_audio (0.0.1): + - Flutter + - FlutterMacOS + - local_auth_darwin (0.0.1): + - Flutter + - FlutterMacOS + - location (0.0.1): + - Flutter + - manage_calendar_events (0.0.1): + - Flutter + - map_launcher (0.0.1): + - Flutter + - MapboxCommon (23.11.0) + - MapboxCoreMaps (10.19.1): + - MapboxCommon (~> 23.11) + - MapboxCoreNavigation (2.19.0): + - MapboxDirections (~> 2.14) + - MapboxNavigationNative (< 207.0.0, >= 206.0.1) + - MapboxDirections (2.14.3): + - Polyline (~> 5.0) + - Turf (~> 2.8.0) + - MapboxMaps (10.19.0): + - MapboxCommon (= 23.11.0) + - MapboxCoreMaps (= 10.19.1) + - MapboxMobileEvents (= 2.0.0) + - Turf (= 2.8.0) + - MapboxMobileEvents (2.0.0) + - MapboxNavigation (2.19.0): + - MapboxCoreNavigation (= 2.19.0) + - MapboxMaps (~> 10.18) + - MapboxSpeech (~> 2.0) + - Solar-dev (~> 3.0) + - MapboxNavigationNative (206.2.2): + - MapboxCommon (~> 23.10) + - MapboxSpeech (2.1.1) + - nanopb (3.30910.0): + - nanopb/decode (= 3.30910.0) + - nanopb/encode (= 3.30910.0) + - nanopb/decode (3.30910.0) + - nanopb/encode (3.30910.0) + - network_info_plus (0.0.1): + - Flutter + - open_filex (0.0.2): + - Flutter + - OrderedSet (6.0.3) + - package_info_plus (0.4.5): + - Flutter + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - PayFortSDK (3.2.1) + - permission_handler_apple (9.3.0): + - Flutter + - Polyline (5.1.0) + - PromisesObjC (2.4.0) + - SDWebImage (5.21.5): + - SDWebImage/Core (= 5.21.5) + - SDWebImage/Core (5.21.5) + - share_plus (0.0.1): + - Flutter + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - Solar-dev (3.0.1) + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + - SwiftProtobuf (1.33.3) + - SwiftyGif (5.4.5) + - Turf (2.8.0) + - url_launcher_ios (0.0.1): + - Flutter + - video_player_avfoundation (0.0.1): + - Flutter + - FlutterMacOS + - wakelock_plus (0.0.1): + - Flutter + - webview_flutter_wkwebview (0.0.1): + - Flutter + - FlutterMacOS + - ZoomVideoSDK/CptShare (2.1.10) + - ZoomVideoSDK/zm_annoter_dynamic (2.1.10) + - ZoomVideoSDK/zoomcml (2.1.10) + - ZoomVideoSDK/ZoomVideoSDK (2.1.10) + +DEPENDENCIES: + - amazon_payfort (from `.symlinks/plugins/amazon_payfort/ios`) + - audio_session (from `.symlinks/plugins/audio_session/ios`) + - barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - device_calendar (from `.symlinks/plugins/device_calendar/ios`) + - device_calendar_plus_ios (from `.symlinks/plugins/device_calendar_plus_ios/ios`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) + - file_picker (from `.symlinks/plugins/file_picker/ios`) + - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) + - FLAnimatedImage + - Flutter (from `Flutter`) + - flutter_callkit_incoming (from `.symlinks/plugins/flutter_callkit_incoming/ios`) + - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) + - flutter_ios_voip_kit_karmm (from `.symlinks/plugins/flutter_ios_voip_kit_karmm/ios`) + - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) + - flutter_nfc_kit (from `.symlinks/plugins/flutter_nfc_kit/ios`) + - flutter_zoom_videosdk (from `.symlinks/plugins/flutter_zoom_videosdk/ios`) + - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) + - geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`) + - google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`) + - health (from `.symlinks/plugins/health/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - just_audio (from `.symlinks/plugins/just_audio/darwin`) + - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) + - location (from `.symlinks/plugins/location/ios`) + - manage_calendar_events (from `.symlinks/plugins/manage_calendar_events/ios`) + - map_launcher (from `.symlinks/plugins/map_launcher/ios`) + - MapboxMaps (= 10.19.0) + - MapboxNavigation (= 2.19.0) + - network_info_plus (from `.symlinks/plugins/network_info_plus/ios`) + - open_filex (from `.symlinks/plugins/open_filex/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - share_plus (from `.symlinks/plugins/share_plus/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) + - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) + +SPEC REPOS: + trunk: + - CryptoSwift + - DKImagePickerController + - DKPhotoGallery + - Firebase + - FirebaseAnalytics + - FirebaseCore + - FirebaseCoreInternal + - FirebaseInstallations + - FirebaseMessaging + - FLAnimatedImage + - Google-Maps-iOS-Utils + - GoogleAdsOnDeviceConversion + - GoogleAppMeasurement + - GoogleDataTransport + - GoogleMaps + - GoogleUtilities + - MapboxCommon + - MapboxCoreMaps + - MapboxCoreNavigation + - MapboxDirections + - MapboxMaps + - MapboxMobileEvents + - MapboxNavigation + - MapboxNavigationNative + - MapboxSpeech + - nanopb + - OrderedSet + - PayFortSDK + - Polyline + - PromisesObjC + - SDWebImage + - Solar-dev + - SwiftProtobuf + - SwiftyGif + - Turf + - ZoomVideoSDK + +EXTERNAL SOURCES: + amazon_payfort: + :path: ".symlinks/plugins/amazon_payfort/ios" + audio_session: + :path: ".symlinks/plugins/audio_session/ios" + barcode_scan2: + :path: ".symlinks/plugins/barcode_scan2/ios" + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/ios" + device_calendar: + :path: ".symlinks/plugins/device_calendar/ios" + device_calendar_plus_ios: + :path: ".symlinks/plugins/device_calendar_plus_ios/ios" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" + file_picker: + :path: ".symlinks/plugins/file_picker/ios" + firebase_analytics: + :path: ".symlinks/plugins/firebase_analytics/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + firebase_messaging: + :path: ".symlinks/plugins/firebase_messaging/ios" + Flutter: + :path: Flutter + flutter_callkit_incoming: + :path: ".symlinks/plugins/flutter_callkit_incoming/ios" + flutter_inappwebview_ios: + :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" + flutter_ios_voip_kit_karmm: + :path: ".symlinks/plugins/flutter_ios_voip_kit_karmm/ios" + flutter_local_notifications: + :path: ".symlinks/plugins/flutter_local_notifications/ios" + flutter_nfc_kit: + :path: ".symlinks/plugins/flutter_nfc_kit/ios" + flutter_zoom_videosdk: + :path: ".symlinks/plugins/flutter_zoom_videosdk/ios" + fluttertoast: + :path: ".symlinks/plugins/fluttertoast/ios" + geolocator_apple: + :path: ".symlinks/plugins/geolocator_apple/darwin" + google_maps_flutter_ios: + :path: ".symlinks/plugins/google_maps_flutter_ios/ios" + health: + :path: ".symlinks/plugins/health/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" + just_audio: + :path: ".symlinks/plugins/just_audio/darwin" + local_auth_darwin: + :path: ".symlinks/plugins/local_auth_darwin/darwin" + location: + :path: ".symlinks/plugins/location/ios" + manage_calendar_events: + :path: ".symlinks/plugins/manage_calendar_events/ios" + map_launcher: + :path: ".symlinks/plugins/map_launcher/ios" + network_info_plus: + :path: ".symlinks/plugins/network_info_plus/ios" + open_filex: + :path: ".symlinks/plugins/open_filex/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" + share_plus: + :path: ".symlinks/plugins/share_plus/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + video_player_avfoundation: + :path: ".symlinks/plugins/video_player_avfoundation/darwin" + wakelock_plus: + :path: ".symlinks/plugins/wakelock_plus/ios" + webview_flutter_wkwebview: + :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" + +SPEC CHECKSUMS: + amazon_payfort: 4ad7a3413acc1c4c4022117a80d18fee23c572d3 + audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0 + barcode_scan2: 4e4b850b112f4e29017833e4715f36161f987966 + connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd + CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90 + device_calendar: b55b2c5406cfba45c95a59f9059156daee1f74ed + device_calendar_plus_ios: 2c04ad7643c6e697438216e33693b84e8ca45ded + device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c + DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 + file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be + Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e + firebase_analytics: 0e25ca1d4001ccedd40b4e5b74c0ec34e18f6425 + firebase_core: 995454a784ff288be5689b796deb9e9fa3601818 + firebase_messaging: f4a41dd102ac18b840eba3f39d67e77922d3f707 + FirebaseAnalytics: 6433dfd311ba78084fc93bdfc145e8cb75740eae + FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e + FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 + FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843 + FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09 + FLAnimatedImage: bbf914596368867157cc71b38a8ec834b3eeb32b + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + flutter_callkit_incoming: cb8138af67cda6dd981f7101a5d709003af21502 + flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 + flutter_ios_voip_kit_karmm: 371663476722afb631d5a13a39dee74c56c1abd0 + flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb + flutter_nfc_kit: e1b71583eafd2c9650bc86844a7f2d185fb414f6 + flutter_zoom_videosdk: 0f59e71685a03ddb0783ecc43bf3155b8599a7f5 + fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 + geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e + Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321 + google_maps_flutter_ios: 3213e1e5f5588b6134935cb8fc59acb4e6d88377 + GoogleAdsOnDeviceConversion: 2be6297a4f048459e0ae17fad9bfd2844e10cf64 + GoogleAppMeasurement: 700dce7541804bec33db590a5c496b663fbe2539 + GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 + GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d + GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 + health: 32d2fbc7f26f9a2388d1a514ce168adbfa5bda65 + image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 + just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed + local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb + location: 155caecf9da4f280ab5fe4a55f94ceccfab838f8 + manage_calendar_events: fe1541069431af035ced925ebd9def8b4b271254 + map_launcher: 8051ad5783913cafce93f2414c6858f2904fd8df + MapboxCommon: 119f3759f7dc9457f0695848108ab323eb643cb4 + MapboxCoreMaps: ca17f67baced23f8c952166ac6314c35bad3f66c + MapboxCoreNavigation: 3be9990fae3ed732a101001746d0e3b4234ec023 + MapboxDirections: d9ad8452e8927d95ed21e35f733834dbca7e0eb1 + MapboxMaps: b7f29ec7c33f7dc6d2947c1148edce6db81db9a7 + MapboxMobileEvents: d044b9edbe0ec7df60f6c2c9634fe9a7f449266b + MapboxNavigation: da9cf3d773ed5b0fa0fb388fccdaa117ee681f31 + MapboxNavigationNative: 629e359f3d2590acd1ebbacaaf99e1a80ee57e42 + MapboxSpeech: cd25ef99c3a3d2e0da72620ff558276ea5991a77 + nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc + open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1 + OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 + PayFortSDK: 233eabe9a45601fdbeac67fa6e5aae46ed8faf82 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838 + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + Solar-dev: 4612dc9878b9fed2667d23b327f1d4e54e16e8d0 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + SwiftProtobuf: e1b437c8e31a4c5577b643249a0bb62ed4f02153 + SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 + Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e + url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b + video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a + wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 + webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d + ZoomVideoSDK: 94e939820e57a075c5e712559f927017da0de06a + +PODFILE CHECKSUM: 8235407385ddd5904afc2563d65406117a51993e + +COCOAPODS: 1.16.2 diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 64d7428e..308891a7 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -16,11 +16,11 @@ import GoogleMaps return super.application(application, didFinishLaunchingWithOptions: launchOptions) } func initializePlatformChannels(){ - if let mainViewController = window?.rootViewController as? FlutterViewController{ // platform initialization suppose to be in foreground - - HMGPenguinInPlatformBridge.initialize(flutterViewController: mainViewController) - - } +// if let mainViewController = window?.rootViewController as? FlutterViewController{ // platform initialization suppose to be in foreground +// +//// HMGPenguinInPlatformBridge.initialize(flutterViewController: mainViewController) +// +// } } override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken:Data){ // Messaging.messaging().apnsToken = deviceToken diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index fdd32814..742db5c6 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -231,6 +231,21 @@ class AppAssets { static const String forward_top_nav_icon = '$svgBasePath/forward_top_nav_icon.svg'; static const String back_top_nav_icon = '$svgBasePath/back_top_nav_icon.svg'; + static const String bluetooth = '$svgBasePath/bluetooth.svg'; + + //smartwatch + static const String watchActivity = '$svgBasePath/watch_activity.svg'; + static const String watchActivityTrailing = '$svgBasePath/watch_activity_trailing.svg'; + static const String watchSteps= '$svgBasePath/watch_steps.svg'; + static const String watchStepsTrailing= '$svgBasePath/watch_steps_trailing.svg'; + static const String watchSleep= '$svgBasePath/watch_sleep.svg'; + static const String watchSleepTrailing= '$svgBasePath/watch_sleep_trailing.svg'; + static const String watchBmi= '$svgBasePath/watch_bmi.svg'; + static const String watchBmiTrailing= '$svgBasePath/watch_bmi_trailing.svg'; + static const String watchWeight= '$svgBasePath/watch_weight.svg'; + static const String watchWeightTrailing= '$svgBasePath/watch_weight_trailing.svg'; + static const String watchHeight= '$svgBasePath/watch_height.svg'; + //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/common_models/smart_watch.dart b/lib/core/common_models/smart_watch.dart new file mode 100644 index 00000000..b9259f5a --- /dev/null +++ b/lib/core/common_models/smart_watch.dart @@ -0,0 +1,18 @@ +enum SmartWatchTypes{ + apple, + samsung, + huawei, + whoop +} + + +class SmartwatchDetails { + final SmartWatchTypes watchType; + final String watchIcon; + final String smallIcon; + final String detailsTitle; + final String details; + final String secondTitle; + + SmartwatchDetails(this.watchType, this.watchIcon, this.smallIcon, this.detailsTitle, this.details, this.secondTitle); +} \ No newline at end of file diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index 87af0a27..6c4e7bec 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -1,7 +1,10 @@ import 'package:device_calendar/device_calendar.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:intl/intl.dart'; +import '../app_state.dart' show AppState; + class DateUtil { /// convert String To Date function /// [date] String we want to convert @@ -198,6 +201,10 @@ class DateUtil { } } + static getMonthDayAsOfLang(int month){ + return getIt.get().isArabic()?getMonthArabic(month):getMonth(month); + } + /// get month by /// [month] convert month number in to month name in Arabic static getMonthArabic(int month) { @@ -268,6 +275,10 @@ class DateUtil { return date ?? DateTime.now(); } + static getWeekDayAsOfLang(int weekDay){ + return getIt.get().isArabic()?getWeekDayArabic(weekDay):getWeekDayEnglish(weekDay); + } + /// get month by /// [weekDay] convert week day in int to week day name static getWeekDay(int weekDay) { @@ -580,6 +591,14 @@ class DateUtil { return weekDayName; // Return as-is if not recognized } } + + static String millisToHourMin(int milliseconds) { + int totalMinutes = (milliseconds / 60000).floor(); // convert ms → min + int hours = totalMinutes ~/ 60; // integer division + int minutes = totalMinutes % 60; // remaining minutes + + return '${hours} hr ${minutes} min'; + } } extension OnlyDate on DateTime { diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 0d600bd7..ac8c152a 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -405,6 +405,7 @@ class Utils { static Widget getWarningWidget({ String? loadingText, bool isShowActionButtons = false, + bool showOkButton = false, Widget? bodyWidget, Function? onConfirmTap, Function? onCancelTap, @@ -457,7 +458,26 @@ class Utils { ), ], ) - : SizedBox.shrink(), + : showOkButton? + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.ok.tr(), + onPressed: () async { + if (onConfirmTap != null) { + onConfirmTap(); + } + }, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + // icon: AppAssets.confirm, + ), + ), + ], + ) + :SizedBox.shrink(), ], ).center; } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 3f9327fc..0026c801 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -18,6 +18,7 @@ extension CapExtension on String { String get needTranslation => this; String get capitalizeFirstofEach => trim().isNotEmpty ? trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; + } extension EmailValidator on String { diff --git a/lib/features/smartwatch_health_data/HealthDataTransformation.dart b/lib/features/smartwatch_health_data/HealthDataTransformation.dart new file mode 100644 index 00000000..ffda4f4f --- /dev/null +++ b/lib/features/smartwatch_health_data/HealthDataTransformation.dart @@ -0,0 +1,134 @@ +import 'dart:math'; + +import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; +import 'package:intl/intl.dart'; + +import 'model/Vitals.dart'; + +enum Durations { + daily("daily"), + weekly("weekly"), + monthly("monthly"), + halfYearly("halfYearly"), + yearly("yearly"); + + final String value; + const Durations(this.value); +} + +class HealthDataTransformation { + Map> transformVitalsToDataPoints(VitalsWRTType vitals, String filterType, String selectedSection,) { + final Map> dataPointMap = {}; + Map> data = vitals.getVitals(); + // Group data based on the filter type + Map> groupedData = {}; + // List > items = data.values.toList(); + List keys = data.keys.toList(); + var count = 0; + List item = data[selectedSection] ?? []; + // for(var item in items) { + List dataPoints = []; + + for (var vital in item) { + String key = ""; + if (vital.value == "" || vital.timestamp == "") continue; + var parseDate = DateTime.parse(vital.timestamp); + var currentDate = normalizeToStartOfDay(DateTime.now()); + if (filterType == Durations.daily.value) { + if(isBetweenInclusive(parseDate, currentDate, DateTime.now())) { + key = DateFormat('yyyy-MM-dd HH').format(DateTime.parse(vital.timestamp)); + groupedData.putIfAbsent(key, () => []).add(vital); + }// Group by hour + } else if (filterType == Durations.weekly.value) { + if(isBetweenInclusive(parseDate, currentDate.subtract(Duration(days: 7)), DateTime.now())) { + key = DateFormat('yyyy-MM-dd').format(DateTime.parse(vital.timestamp)); + groupedData.putIfAbsent(key, () => []).add(vital); + + } // Group by day + } else if (filterType == Durations.monthly.value) { + if(isBetweenInclusive(parseDate, currentDate.subtract(Duration(days: 30)), DateTime.now())) { + print("the value for the monthly filter is ${vital.value} with the timestamp ${vital.timestamp} and the current date is $currentDate and the parse date is $parseDate"); + key = DateFormat('yyyy-MM-dd').format(DateTime.parse(vital.timestamp)); + groupedData.putIfAbsent(key, () => []).add(vital); + + } // Group by day + } else if (filterType == Durations.halfYearly.value || filterType == Durations.yearly.value) { + if(isBetweenInclusive(parseDate, currentDate.subtract(Duration(days: filterType == Durations.halfYearly.value?180: 365)), DateTime.now())) { + key = DateFormat('yyyy-MM').format(DateTime.parse(vital.timestamp)); + groupedData.putIfAbsent(key, () => []).add(vital); + + } // Group by month + } else { + throw ArgumentError('Invalid filter type'); + } + } + print("the size of groupData is ${groupedData.values.length}"); + + // Process grouped data + groupedData.forEach((key, values) { + double sum = values.fold(0, (acc, v) => acc + num.parse(v.value)); + double mean = sum / values.length; + if(selectedSection == "bodyOxygen" || selectedSection == "bodyTemperature") { + mean = sum / values.length; + }else { + mean = sum; + } + + double finalValue = mean; + print("the final value is $finalValue for the key $key with the original values ${values.map((v) => v.value).toList()} and uom is ${values.first.unitOfMeasure}"); + dataPoints.add(DataPoint( + value: smartScale(finalValue), + label: key, + actualValue: finalValue.toStringAsFixed(2), + displayTime: key, + unitOfMeasurement:values.first.unitOfMeasure , + time: DateTime.parse(values.first.timestamp), + )); + }); + + dataPointMap[filterType] = dataPoints; + // } + return dataPointMap; + } + + double smartScale(double number) { + // if (number <= 0) return 0; + // final _random = Random(); + // double ratio = number / 100; + // + // double scalingFactor = ratio > 1 ? 100 / number : 100; + // + // double result = (number / 100) * scalingFactor; + // print("the ratio is ${ratio.toInt()+1}"); + // double max = (100+_random.nextInt(ratio.toInt()+10)).toDouble(); + // + // return result.clamp(0, max); + + if (number <= 0) return 0; + + final random = Random(); + + // Smooth compression scaling + double baseScaled = number <20 ? number:100 * (number / (number + 100)); + + // Random factor between 0.9 and 1.1 (±10%) + double randomFactor = number <20 ? random.nextDouble() * 1.5: 0.9 + random.nextDouble() * 0.2; + + double result = baseScaled * randomFactor; + + return result.clamp(0, 100); + } + + DateTime normalizeToStartOfDay(DateTime date) { + return DateTime(date.year, date.month, date.day); + } + bool isBetweenInclusive( + DateTime target, + DateTime start, + DateTime end, + ) { + return !normalizeToStartOfDay(target).isBefore(start) && !normalizeToStartOfDay(target).isAfter(end); + } + + +} \ No newline at end of file diff --git a/lib/features/smartwatch_health_data/health_provider.dart b/lib/features/smartwatch_health_data/health_provider.dart index 0acb25bb..963c0352 100644 --- a/lib/features/smartwatch_health_data/health_provider.dart +++ b/lib/features/smartwatch_health_data/health_provider.dart @@ -1,6 +1,18 @@ import 'package:flutter/foundation.dart'; import 'package:health/health.dart'; +import 'package:hmg_patient_app_new/core/common_models/smart_watch.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/features/smartwatch_health_data/health_service.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; + +import '../../core/common_models/data_points.dart'; +import '../../core/dependencies.dart'; +import '../../presentation/smartwatches/activity_detail.dart' show ActivityDetails; +import '../../presentation/smartwatches/smart_watch_activity.dart' show SmartWatchActivity; +import '../../services/navigation_service.dart' show NavigationService; +import 'HealthDataTransformation.dart'; +import 'model/Vitals.dart'; class HealthProvider with ChangeNotifier { final HealthService _healthService = HealthService(); @@ -10,13 +22,27 @@ class HealthProvider with ChangeNotifier { String selectedTimeRange = '7D'; int selectedTabIndex = 0; - String selectedWatchType = 'apple'; + SmartWatchTypes? selectedWatchType ; String selectedWatchURL = 'assets/images/png/smartwatches/apple-watch-5.jpg'; + HealthDataTransformation healthDataTransformation = HealthDataTransformation(); + + String selectedSection = ""; + Map> daily = {}; + Map> weekly = {}; + Map> monthly = {}; + Map> halgyearly = {}; + Map> yearly = {}; + Map> selectedData = {}; + Durations selectedDuration = Durations.daily; + VitalsWRTType? vitals; + double? averageValue; + String? averageValueString; - setSelectedWatchType(String type, String imageURL) { + setSelectedWatchType(SmartWatchTypes type, String imageURL) { selectedWatchType = type; selectedWatchURL = imageURL; notifyListeners(); + _healthService.addWatchHelper(type); } void onTabChanged(int index) { @@ -40,9 +66,7 @@ class HealthProvider with ChangeNotifier { final startTime = _getStartDate(); final endTime = DateTime.now(); - healthData = await _healthService.getAllHealthData(startTime, endTime); - isLoading = false; notifyListeners(); } catch (e) { @@ -91,4 +115,176 @@ class HealthProvider with ChangeNotifier { return DateTime.now().subtract(const Duration(days: 7)); } } + + void initDevice() async { + LoaderBottomSheet.showLoader(); + notifyListeners(); + final result = await _healthService.initDevice(); + isLoading = false; + LoaderBottomSheet.hideLoader(); + if (result.isError) { + error = 'Error initializing device: ${result.asError}'; + } else { + LoaderBottomSheet.showLoader(); + await getVitals(); + // LoaderBottomSheet.hideLoader(); + // await Future.delayed(Duration(seconds: 5)); + getIt.get().pushPage(page: SmartWatchActivity()); + print('Device initialized successfully'); + } + notifyListeners(); + } + + Future getVitals() async { + + final result = await _healthService.getVitals(); + vitals = result; + LoaderBottomSheet.hideLoader(); + + notifyListeners(); + } + + mapValuesForFilters( + Durations filter, + String selectedSection, + ) { + if (vitals == null) return {}; + + switch (filter) { + case Durations.daily: + if (daily.isNotEmpty) { + selectedData = daily; + break; + } + selectedData = daily = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.daily.value, selectedSection); + break; + case Durations.weekly: + if (weekly.isNotEmpty) { + selectedData = weekly; + break; + } + selectedData = weekly = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.weekly.value, selectedSection); + break; + case Durations.monthly: + if (monthly.isNotEmpty) { + selectedData = monthly; + break; + } + selectedData = monthly = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.monthly.value, selectedSection); + break; + case Durations.halfYearly: + if (halgyearly.isNotEmpty) { + selectedData = halgyearly; + break; + } + selectedData = halgyearly = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.halfYearly.value, selectedSection); + break; + case Durations.yearly: + if (yearly.isNotEmpty) { + selectedData = yearly; + break; + } + selectedData = yearly = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.yearly.value, selectedSection); + break; + default: + {} + ; + } + notifyListeners(); + } + + void navigateToDetails(String value, {required String sectionName, required String uom}) { + getIt.get().pushPage(page: ActivityDetails(selectedActivity: value, sectionName:sectionName, uom: uom,)); + } + + void saveSelectedSection(String value) { + // if(selectedSection == value) return; + selectedSection = value; + } + + void deleteDataIfSectionIsDifferent(String value) { + // if(selectedSection == value){ + // return; + // } + daily.clear(); + weekly.clear(); + halgyearly.clear(); + monthly.clear(); + yearly.clear(); + selectedSection = ""; + selectedSection = ""; + averageValue = null; + averageValueString = null; + selectedDuration = Durations.daily; + } + + void fetchData() { + // if(selectedSection == value) return; + mapValuesForFilters(selectedDuration, selectedSection); + getAverageForData(); + transformValueIfSleepIsSelected(); + } + + void setDurations(Durations duration) { + selectedDuration = duration; + } + + void getAverageForData() { + if (selectedData.isEmpty) { + averageValue = 0.0; + notifyListeners(); + return; + } + double total = 0; + int count = 0; + selectedData.forEach((key, dataPoints) { + for (var dataPoint in dataPoints) { + total += num.parse(dataPoint.actualValue); + count++; + } + }); + print("total count is $count and total is $total"); + averageValue = count > 0 ? total / count : null; + notifyListeners(); + } + + void transformValueIfSleepIsSelected() { + if (selectedSection != "sleep") return; + if (averageValue == null) { + averageValueString = null; + return; + } + averageValueString = DateUtil.millisToHourMin(averageValue?.toInt() ?? 0); + averageValue = null; + notifyListeners(); + } + + String firstNonEmptyValue(List dataPoints) { + try { + return dataPoints.firstWhere((dp) => dp.value != null && dp.value!.trim().isNotEmpty).value; + } catch (e) { + return "0"; // no non-empty value found + } + } + + String sumOfNonEmptyData(List list) { + final now = DateTime.now().toLocal(); + final today = DateTime(now.year, now.month, now.day); + + var data = double.parse(list + .where((dp) { + final localDate = DateTime.parse(dp.timestamp); + final normalized = DateTime(localDate.year, localDate.month, localDate.day); + + return normalized.isAtSameMomentAs(today); + }) + .fold("0", (sum, dp) => (double.parse(sum) + double.parse(dp.value)).toString()) + .toString()); + var formattedString = data.toStringAsFixed(2); + + if (formattedString.endsWith('.00')) { + return formattedString.substring(0, formattedString.length - 3); + } + return formattedString; + } } diff --git a/lib/features/smartwatch_health_data/health_service.dart b/lib/features/smartwatch_health_data/health_service.dart index d3815b3b..7d42092d 100644 --- a/lib/features/smartwatch_health_data/health_service.dart +++ b/lib/features/smartwatch_health_data/health_service.dart @@ -1,9 +1,17 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:developer'; import 'dart:io'; import 'package:health/health.dart'; +import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/model/Vitals.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart'; import 'package:permission_handler/permission_handler.dart'; import 'health_utils.dart'; +import 'package:async/async.dart'; class HealthService { static final HealthService _instance = HealthService._internal(); @@ -14,6 +22,8 @@ class HealthService { final Health health = Health(); + WatchHelper? watchHelper; + final List _healthMetrics = [ HealthDataType.HEART_RATE, // HealthDataType.STEPS, @@ -161,4 +171,42 @@ class HealthService { return []; } } + + void addWatchHelper(SmartWatchTypes watchType){ + watchHelper = CreateWatchHelper.getWatchName(watchType) ; + } + + Future> initDevice() async { + if(watchHelper == null){ + return Result.error('No watch helper found'); + } + return await watchHelper!.initDevice(); + } + + Future getVitals() async { + if (watchHelper == null) { + print('No watch helper found'); + return null; + } + try { + await watchHelper!.getHeartRate(); + await watchHelper!.getSleep(); + await watchHelper!.getSteps(); + await watchHelper!.getActivity(); + await watchHelper!.getBodyTemperature(); + await watchHelper!.getDistance(); + await watchHelper!.getBloodOxygen(); + Result data = await watchHelper!.retrieveData(); + if(data.isError) { + print('Unable to get the data'); + } + var response = jsonDecode(data.asValue?.value?.toString()?.trim().replaceAll("\n", "")??""); + VitalsWRTType vitals = VitalsWRTType.fromMap(response); + log("the data is ${vitals}"); + return vitals; + }catch(e){ + print('Error getting heart rate: $e'); + } + return null; + } } diff --git a/lib/features/smartwatch_health_data/model/Vitals.dart b/lib/features/smartwatch_health_data/model/Vitals.dart new file mode 100644 index 00000000..96386f2d --- /dev/null +++ b/lib/features/smartwatch_health_data/model/Vitals.dart @@ -0,0 +1,103 @@ +class Vitals { + String value; + final String timestamp; + final String unitOfMeasure; + + Vitals({ + required this.value, + required this.timestamp, + this.unitOfMeasure = "", + }); + + factory Vitals.fromMap(Map map) { + return Vitals( + value: map['value'] ?? "", + timestamp: map['timeStamp'] ?? "", + unitOfMeasure: map['uom'] ?? "", + ); + } + + + toString(){ + return "{\"value\": \"$value\", \"timeStamp\": \"$timestamp\", \"uom\": \"$unitOfMeasure\"}"; + } +} + +class VitalsWRTType { + final List heartRate; + final List sleep; + final List step; + final List distance; + final List activity; + final List bodyOxygen; + final List bodyTemperature; + double maxHeartRate = double.negativeInfinity; + double maxSleep = double.negativeInfinity; + double maxStep= double.negativeInfinity; + double maxActivity = double.negativeInfinity; + double maxBloodOxygen = double.negativeInfinity; + double maxBodyTemperature = double.negativeInfinity; + + + VitalsWRTType({required this.distance, required this.bodyOxygen, required this.bodyTemperature, required this.heartRate, required this.sleep, required this.step, required this.activity}); + + factory VitalsWRTType.fromMap(Map map) { + List activity = []; + List steps = []; + List sleeps = []; + List heartRate = []; + List bodyOxygen = []; + List distance = []; + List bodyTemperature = []; + map["activity"].forEach((element) { + element["uom"] = "Kcal"; + var data = Vitals.fromMap(element); + activity.add(data); + }); + map["steps"].forEach((element) { + element["uom"] = ""; + + steps.add(Vitals.fromMap(element)); + }); + map["sleep"].forEach((element) { + element["uom"] = "hr"; + sleeps.add(Vitals.fromMap(element)); + }); + map["heartRate"].forEach((element) { + element["uom"] = "bpm"; + + heartRate.add(Vitals.fromMap(element)); + }); + map["bloodOxygen"].forEach((element) { + element["uom"] = ""; + + bodyOxygen.add(Vitals.fromMap(element)); + }); + + map["bodyTemperature"].forEach((element) { + element["uom"] = "C"; + bodyTemperature.add(Vitals.fromMap(element)); + }); + + map["distance"].forEach((element) { + element["uom"] = "km"; + var data = Vitals.fromMap(element); + data.value = (double.parse(data.value)/1000).toStringAsFixed(2); + distance.add(data); + }); + + return VitalsWRTType(bodyTemperature: bodyTemperature, bodyOxygen: bodyOxygen, heartRate: heartRate, sleep: sleeps, step: steps, activity: activity, distance: distance); + } + + Map> getVitals() { + return { + "heartRate": heartRate , + "sleep": sleep, + "steps": step, + "activity": activity, + "bodyOxygen": bodyOxygen, + "bodyTemperature": bodyTemperature, + "distance": distance, + }; + } +} diff --git a/lib/features/smartwatch_health_data/platform_channel/samsung_platform_channel.dart b/lib/features/smartwatch_health_data/platform_channel/samsung_platform_channel.dart new file mode 100644 index 00000000..a36cda39 --- /dev/null +++ b/lib/features/smartwatch_health_data/platform_channel/samsung_platform_channel.dart @@ -0,0 +1,90 @@ + +import 'dart:async'; + +import 'package:async/async.dart'; +import 'package:flutter/services.dart'; +class SamsungPlatformChannel { + final MethodChannel _channel = MethodChannel('samsung_watch'); + Future> initDevice() async { + try{ + await _channel.invokeMethod('init'); + return Result.value(true); + }catch(e){ + return Result.error(e); + } + } + + Future> getRequestedPermission() async { + try{ + await _channel.invokeMethod('getPermission'); + return Result.value(true); + }catch(e){ + return Result.error(e); + } + } + Future> getHeartRate() async { + try{ + await _channel.invokeMethod('getHeartRate'); + return Result.value(true); + }catch(e){ + return Result.error(e); + } + } + Future> getSleep() async { + try{ + await _channel.invokeMethod('getSleepData'); + return Result.value(true); + }catch(e){ + return Result.error(e); + } + } + Future> getSteps() async { + try{ + await _channel.invokeMethod('steps'); + return Result.value(true); + }catch(e){ + return Result.error(e); + } + } + + Future> getActivity() async { + try{ + await _channel.invokeMethod('activitySummary'); + return Result.value(true); + }catch(e){ + return Result.error(e); + } + } + + Future> retrieveData() async { + try{ + return Result.value(await _channel.invokeMethod('retrieveData')); + }catch(e){ + return Result.error(e); + } + } + + Future> getBloodOxygen() async { + try{ + return Result.value(await _channel.invokeMethod('bloodOxygen')); + }catch(e){ + return Result.error(e); + } + } + + Future> getBodyTemperature() async { + try{ + return Result.value(await _channel.invokeMethod('bodyTemperature')); + }catch(e){ + return Result.error(e); + } + } + + Future> getDistance() async { + try{ + return Result.value(await _channel.invokeMethod('distance')); + }catch(e){ + return Result.error(e); + } + } +} \ No newline at end of file diff --git a/lib/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart b/lib/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart new file mode 100644 index 00000000..8abb2136 --- /dev/null +++ b/lib/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart @@ -0,0 +1,22 @@ +import 'dart:io'; + +import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/health_connect_helper.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/huawei_watch_connecter.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/samsung_health.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart'; + +class CreateWatchHelper { + static WatchHelper getWatchName(SmartWatchTypes watchType) { + /// if running device is ios + if(Platform.isIOS) return HealthConnectHelper(); + switch(watchType){ + case SmartWatchTypes.samsung: + return SamsungHealth(); + case SmartWatchTypes.huawei: + return HuaweiHealthDataConnector(); + default: + return SamsungHealth(); + } + } +} \ No newline at end of file diff --git a/lib/features/smartwatch_health_data/watch_connectors/health_connect_helper.dart b/lib/features/smartwatch_health_data/watch_connectors/health_connect_helper.dart new file mode 100644 index 00000000..87e1cf3a --- /dev/null +++ b/lib/features/smartwatch_health_data/watch_connectors/health_connect_helper.dart @@ -0,0 +1,188 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:async/src/result/result.dart'; +import 'package:flutter/material.dart'; +import 'package:health/health.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart' show WatchHelper; +import 'package:permission_handler/permission_handler.dart'; + +import '../model/Vitals.dart'; + +class HealthConnectHelper extends WatchHelper { + final Health health = Health(); + + final List _healthPermissions = [ + HealthDataType.ACTIVE_ENERGY_BURNED, + HealthDataType.HEART_RATE, + HealthDataType.STEPS, + HealthDataType.BLOOD_OXYGEN, + HealthDataType.BODY_TEMPERATURE, + HealthDataType.DISTANCE_WALKING_RUNNING, + HealthDataType.TOTAL_CALORIES_BURNED + ]; + + Map> mappedData = {}; + + @override + FutureOr getHeartRate() async { + try { + final types = HealthDataType.HEART_RATE; + final endDate = DateTime.now(); + // final startDate = endDate.subtract(Duration(days: 365)); + final startDate = endDate.subtract(Duration(days: 365)); + final data = await getHeartData(startDate, endDate, types); + addDataToMap("heartRate",data ); + } catch (e) { + print('Error getting heart rate: $e'); + } + } + + @override + FutureOr getSleep() async { + try { + final types = HealthDataType.SLEEP_IN_BED; + final endDate = DateTime.now(); + final startDate = endDate.subtract(Duration(days: 365)); + final data = await getData(startDate, endDate, types); + addDataToMap("sleep",data ); + } catch (e) { + print('Error getting sleep data: $e'); + } + } + + @override + FutureOr getSteps() async { + try { + final types = HealthDataType.STEPS; + final endDate = DateTime.now(); + final startDate = endDate.subtract(Duration(days: 365)); + final data = await getData(startDate, endDate, types); + addDataToMap("steps",data ); + debugPrint('Steps Data: $data'); + } catch (e) { + debugPrint('Error getting steps: $e'); + } + } + + @override + Future getActivity() async { + try { + final types = HealthDataType.ACTIVE_ENERGY_BURNED; + final endDate = DateTime.now(); + final startDate = endDate.subtract(Duration(days: 365)); + final data = await getData(startDate, endDate, types); + addDataToMap("activity",data ); + debugPrint('Activity Data: $data'); + } catch (e) { + debugPrint('Error getting activity: $e'); + } + } + + @override + Future retrieveData() async { + return Result.value(getMappedData()); + } + + @override + Future getBloodOxygen() async { + try { + final types = HealthDataType.BLOOD_OXYGEN; + final endDate = DateTime.now(); + final startDate = endDate.subtract(Duration(days: 365)); + final data = await getData(startDate, endDate, types); + addDataToMapBloodOxygen("bloodOxygen", data); + } catch (e) { + debugPrint('Error getting blood oxygen: $e'); + } + } + + @override + Future getBodyTemperature() async { + try { + final types = HealthDataType.BODY_TEMPERATURE; + final endDate = DateTime.now(); + final startDate = endDate.subtract(Duration(days: 365)); + final data = await getData(startDate, endDate, types); + addDataToMap("bodyTemperature",data ); + } catch (e) { + debugPrint('Error getting body temp erature: $e'); + } + } + + @override + FutureOr getDistance() async { + try { + final types = HealthDataType.DISTANCE_WALKING_RUNNING; + final endDate = DateTime.now(); + final startDate = endDate.subtract(Duration(days: 365)); + final data = await getData(startDate, endDate, types); + addDataToMap("distance",data ); + } catch (e) { + debugPrint('Error getting distance: $e'); + } + } + + @override + Future> initDevice() async { + try { + final types = _healthPermissions; + final granted = await health.requestAuthorization(types); + await Permission.activityRecognition.request(); + await Permission.location.request(); + await Health().requestHealthDataHistoryAuthorization(); + return Result.value(granted); + } catch (e) { + debugPrint('Authorization error: $e'); + return Result.error(false); + } + } + + getData(startTime, endTime,type) async { + return await health.getHealthIntervalDataFromTypes( + startDate: startTime, + endDate: endTime, + types: [type], + interval: 3600, + ); + } + + void addDataToMap(String s, data) { + mappedData[s] = []; + for (var point in data) { + if (point.value is NumericHealthValue) { + final numericValue = (point.value as NumericHealthValue).numericValue; + Vitals vitals = Vitals( + value: (point.value as NumericHealthValue).numericValue.toStringAsFixed(2), + timestamp: point.dateFrom.toString() + ); + mappedData[s]?.add(vitals); + } + } + } + + void addDataToMapBloodOxygen(String s, data) { + mappedData[s] = []; + for (var point in data) { + if (point.value is NumericHealthValue) { + final numericValue = (point.value as NumericHealthValue).numericValue; + point.value = NumericHealthValue( + numericValue: numericValue * 100, + ); + Vitals vitals = Vitals(value: (point.value as NumericHealthValue).numericValue.toStringAsFixed(2), timestamp: point.dateFrom.toString()); + mappedData[s]?.add(vitals); + } + } + } + + getMappedData() { + return " { \"heartRate\": ${mappedData["heartRate"] ?? []}, \"sleep\": ${mappedData["sleep"] ?? []}, \"steps\": ${mappedData["steps"] ?? []}, \"activity\": ${mappedData["activity"] ?? []}, \"bloodOxygen\": ${mappedData["bloodOxygen"] ?? []}, \"bodyTemperature\": ${mappedData["bodyTemperature"] ?? []}, \"distance\": ${mappedData["distance"] ?? []} }"; + } + + getHeartData(DateTime startDate, DateTime endDate, HealthDataType types) async { + return await health.getHealthDataFromTypes( + startTime: startDate, + endTime: endDate, + types: [types], + ); + } +} diff --git a/lib/features/smartwatch_health_data/watch_connectors/huawei_watch_connecter.dart b/lib/features/smartwatch_health_data/watch_connectors/huawei_watch_connecter.dart new file mode 100644 index 00000000..a8300e2d --- /dev/null +++ b/lib/features/smartwatch_health_data/watch_connectors/huawei_watch_connecter.dart @@ -0,0 +1,86 @@ +import 'dart:async'; + +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/watch_connectors/watch_helper.dart'; +import 'package:huawei_health/huawei_health.dart'; + +class HuaweiHealthDataConnector extends WatchHelper{ + final MethodChannel _channel = MethodChannel('huawei_watch'); + @override + Future> initDevice() async{ + try{ + await _channel.invokeMethod('init'); + + }catch(e){ + + } + // List of scopes to ask for authorization. + // Note: These scopes should also be authorized on the Huawei Developer Console. + List scopes = [ + Scope.HEALTHKIT_STEP_READ, Scope.HEALTHKIT_OXYGEN_SATURATION_READ, // View and store height and weight data in Health Service Kit. + Scope.HEALTHKIT_HEARTRATE_READ, Scope.HEALTHKIT_SLEEP_READ, + Scope.HEALTHKIT_BODYTEMPERATURE_READ, Scope.HEALTHKIT_CALORIES_READ + ]; + try { + bool? result = await SettingController.getHealthAppAuthorization(); + debugPrint( + 'Granted Scopes for result == is $result}', + ); + return Result.value(true); + } catch (e) { + debugPrint('Error on authorization, Error:${e.toString()}'); + return Result.error(false); + } + } + + @override + Future getActivity() async { + DataType dataTypeResult = await SettingController.readDataType( + DataType.DT_CONTINUOUS_STEPS_DELTA.name + ); + + + } + + @override + Future getBloodOxygen() { + throw UnimplementedError(); + + } + + @override + Future getBodyTemperature() { + + throw UnimplementedError(); + } + + @override + FutureOr getHeartRate() { + throw UnimplementedError(); + } + + @override + FutureOr getSleep() { + throw UnimplementedError(); + } + + @override + FutureOr getSteps() { + throw UnimplementedError(); + } + + + @override + Future retrieveData() { + throw UnimplementedError(); + } + + @override + FutureOr getDistance() { + // TODO: implement getDistance + throw UnimplementedError(); + } + +} \ No newline at end of file diff --git a/lib/features/smartwatch_health_data/watch_connectors/samsung_health.dart b/lib/features/smartwatch_health_data/watch_connectors/samsung_health.dart new file mode 100644 index 00000000..d3cbcfc9 --- /dev/null +++ b/lib/features/smartwatch_health_data/watch_connectors/samsung_health.dart @@ -0,0 +1,98 @@ +import 'dart:async'; + +import 'package:async/src/result/result.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/platform_channel/samsung_platform_channel.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart' show WatchHelper; + +class SamsungHealth extends WatchHelper { + + final SamsungPlatformChannel platformChannel = SamsungPlatformChannel(); + + @override + FutureOr getHeartRate() async { + try { + await platformChannel.getHeartRate(); + + }catch(e){ + print('Error getting heart rate: $e'); + } + + + } + + @override + Future> initDevice() async { + var result = await platformChannel.initDevice(); + if(result.isError){ + return result; + } + return await platformChannel.getRequestedPermission(); + } + + @override + FutureOr getSleep() async { + try { + await platformChannel.getSleep(); + + }catch(e){ + print('Error getting heart rate: $e'); + } + } + + @override + FutureOr getSteps() async{ + try { + await platformChannel.getSteps(); + + }catch(e){ + print('Error getting heart rate: $e'); + } + } + @override + Future getActivity() async{ + try { + await platformChannel.getActivity(); + }catch(e){ + print('Error getting heart rate: $e'); + } + } + + @override + Future retrieveData() async{ + try { + return await platformChannel.retrieveData(); + }catch(e){ + print('Error getting heart rate: $e'); + } + } + + @override + Future getBloodOxygen() async{ + try { + return await platformChannel.getBloodOxygen(); + }catch(e){ + print('Error getting heart rate: $e'); + } + } + + @override + Future getBodyTemperature() async { + try { + return await platformChannel.getBodyTemperature(); + }catch(e){ + print('Error getting heart rate: $e'); + } + } + + @override + FutureOr getDistance() async{ + try { + return await platformChannel.getDistance(); + }catch(e){ + print('Error getting heart rate: $e'); + } + } + + + +} \ No newline at end of file diff --git a/lib/features/smartwatch_health_data/watch_connectors/watch_helper.dart b/lib/features/smartwatch_health_data/watch_connectors/watch_helper.dart new file mode 100644 index 00000000..a09064d8 --- /dev/null +++ b/lib/features/smartwatch_health_data/watch_connectors/watch_helper.dart @@ -0,0 +1,14 @@ +import 'dart:async'; +import 'package:async/async.dart'; +abstract class WatchHelper { + Future> initDevice(); + FutureOr getHeartRate(); + FutureOr getSleep(); + FutureOr getSteps(); + FutureOr getDistance(); + Future getActivity(); + Future retrieveData(); + Future getBodyTemperature(); + Future getBloodOxygen(); + +} \ No newline at end of file diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 1a33df93..e06e977e 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1574,8 +1574,23 @@ abstract class LocaleKeys { static const invalidEligibility = 'invalidEligibility'; static const invalidInsurance = 'invalidInsurance'; static const continueCash = 'continueCash'; + static const applewatch = 'applewatch'; + static const applehealthapplicationshouldbeinstalledinyourphone = 'applehealthapplicationshouldbeinstalledinyourphone'; + static const unabletodetectapplicationinstalledpleasecomebackonceinstalled = 'unabletodetectapplicationinstalledpleasecomebackonceinstalled'; + static const applewatchshouldbeconnected = 'applewatchshouldbeconnected'; + static const samsungwatch = 'samsungwatch'; + static const samsunghealthapplicationshouldbeinstalledinyourphone = 'samsunghealthapplicationshouldbeinstalledinyourphone'; + static const samsungwatchshouldbeconnected = 'samsungwatchshouldbeconnected'; + static const huaweiwatch = 'huaweiwatch'; + static const huaweihealthapplicationshouldbeinstalledinyourphone = 'huaweihealthapplicationshouldbeinstalledinyourphone'; + static const huaweiwatchshouldbeconnected = 'huaweiwatchshouldbeconnected'; + static const whoopwatch = 'whoopwatch'; + static const whoophealthapplicationshouldbeinstalledinyourphone = 'whoophealthapplicationshouldbeinstalledinyourphone'; + static const whoopwatchshouldbeconnected = 'whoopwatchshouldbeconnected'; + static const updatetheinformation = 'updatetheinformation'; static const timeFor = 'timeFor'; static const hmgPolicies = 'hmgPolicies'; static const darkMode = 'darkMode'; + static const featureComingSoonDescription = 'featureComingSoonDescription'; } diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 4996850b..de3235aa 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -294,11 +294,11 @@ class ServicesPage extends StatelessWidget { true, route: null, onTap: () async { - if (getIt.get().isAuthenticated) { + // if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.smartWatches); - } else { - await getIt.get().onLoginPressed(); - } + // } else { + // await getIt.get().onLoginPressed(); + // } }, // route: AppRoutes.huaweiHealthExample, ), diff --git a/lib/presentation/smartwatches/activity_detail.dart b/lib/presentation/smartwatches/activity_detail.dart new file mode 100644 index 00000000..78527140 --- /dev/null +++ b/lib/presentation/smartwatches/activity_detail.dart @@ -0,0 +1,359 @@ +import 'dart:math'; + +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/common_models/data_points.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/smartwatch_health_data/health_provider.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/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/graph/CustomBarGraph.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:provider/provider.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/HealthDataTransformation.dart' as durations; +import 'package:dartz/dartz.dart' show Tuple2; + +import '../../core/utils/date_util.dart'; + +class ActivityDetails extends StatefulWidget { + final String selectedActivity; + final String sectionName; + final String uom; + + const ActivityDetails({super.key, required this.selectedActivity, required this.sectionName, required this.uom}); + + @override + State createState() => _ActivityDetailsState(); +} + +class _ActivityDetailsState extends State { + int index = 0; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "All Health Data".needTranslation, + child: Column( + spacing: 16.h, + children: [ + periodSelectorView((index) {}), + activityDetails(), + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: + activityGraph().paddingOnly(left: 16.w, right: 16.w, top: 32.h, bottom: 16.h),) + ], + ).paddingSymmetrical(24.w, 24.h), + ), + ); + } + + Widget periodSelectorView(Function(int) onItemSelected) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Row( + children: [ + Expanded( + child: CustomTabBar( + activeTextColor: Color(0xffED1C2B), + activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, "D"), + CustomTabBarModel(null, "W"), + CustomTabBarModel(null, "M"), + CustomTabBarModel(null, "6M"), + CustomTabBarModel(null, "Y"), + // CustomTabBarModel(null, "Completed".needTranslation), + ], + shouldTabExpanded: true, + onTabChange: (index) { + switch (index) { + case 0: + context.read().setDurations(durations.Durations.daily); + break; + case 1: + context.read().setDurations(durations.Durations.weekly); + break; + case 2: + context.read().setDurations(durations.Durations.monthly); + break; + case 3: + context.read().setDurations(durations.Durations.halfYearly); + break; + case 4: + context.read().setDurations(durations.Durations.yearly); + break; + } + context.read().fetchData(); + }, + ), + ), + ], + ).paddingSymmetrical(4.w, 4.h)); + } + + Widget activityDetails() { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h, hasShadow: true), + child: Column( + spacing: 8.h, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + widget.sectionName.capitalizeFirstofEach.toText32(weight: FontWeight.w600, color: AppColors.textColor), + "Average".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor) + ], + ), + Selector>( + selector: (_, model) => Tuple2(model.averageValue, model.averageValueString), + builder: (_, data, __) { + var averageAsDouble = data.value1; + var averageAsString = data.value2; + return Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + spacing: 4.w, + children: [ + (averageAsDouble?.toStringAsFixed(2) ?? averageAsString ?? "N/A").toText24(color: AppColors.textGreenColor, fontWeight: FontWeight.w600), + Visibility( + visible: averageAsDouble != null || averageAsString != null, + child: widget.uom.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500) + ) + ], + ); + }) + ], + ).paddingSymmetrical(16.w, 16.h)); + } + + + Widget activityGraph() { + // final _random = Random(); + // + // int randomBP() => 100 + _random.nextInt(51); // 100–150 + // final List data6Months = List.generate(6, (index) { + // final date = DateTime.now().subtract(Duration(days: 30 * (5 - index))); + // + // final value = randomBP(); + // + // return DataPoint( + // value: value.toDouble(), + // label: value.toString(), + // actualValue: value.toString(), + // displayTime: DateFormat('MMM').format(date), + // unitOfMeasurement: 'mmHg', + // time: date, + // ); + // }); + // final List data12Months = List.generate(12, (index) { + // final date = DateTime.now().subtract(Duration(days: 30 * (11 - index))); + // + // final value = randomBP(); + // + // return DataPoint( + // value: value.toDouble(), + // label: value.toString(), + // actualValue: value.toString(), + // displayTime: DateFormat('MMM').format(date), + // unitOfMeasurement: 'mmHg', + // time: date, + // ); + // }); + // + // List data =[]; + // if(index == 0){ + // data = data6Months; + // } else if(index == 1){ + // data = data12Months; + // } else + // data = [ + // DataPoint( + // value: 128, + // label: "128", + // actualValue: '128', + // displayTime: 'Sun', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now().subtract(const Duration(days: 6)), + // ), + // DataPoint( + // value: 135, + // label: "135", + // actualValue: '135', + // displayTime: 'Mon', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now().subtract(const Duration(days: 5)), + // ), + // DataPoint( + // value: 122, + // label: "122", + // actualValue: '122', + // displayTime: 'Tue', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now().subtract(const Duration(days: 4)), + // ), + // DataPoint( + // value: 140, + // label: "140", + // actualValue: '140', + // displayTime: 'Wed', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now().subtract(const Duration(days: 3)), + // ), + // DataPoint( + // value: 118, + // label: "118", + // actualValue: '118', + // displayTime: 'Thu', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now().subtract(const Duration(days: 2)), + // ), + // DataPoint( + // value: 125, + // label: "125", + // actualValue: '125', + // displayTime: 'Fri', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now().subtract(const Duration(days: 1)), + // ), + // DataPoint( + // value: 130, + // label: "130", + // actualValue: '130', + // displayTime: 'Sat', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now(), + // ),23 + // ]; + return Selector>?>( + selector: (_, model) => model.selectedData, + builder: (_, data, __) { + if (context.read().selectedData.values.toList().first?.isEmpty == true) return SizedBox(); + + return CustomBarChart( + dataPoints: context.read().selectedData.values.toList().first, + height: 300.h, + maxY: 150, + barColor: AppColors.bgGreenColor, + barWidth: getBarWidth(), + barRadius: BorderRadius.circular(8), + bottomLabelColor: Colors.black, + bottomLabelSize: 12, + leftLabelInterval: .1, + leftLabelReservedSize: 20, + // Left axis label formatter (Y-axis) + leftLabelFormatter: (value) { + var labelValue = double.tryParse(value.toStringAsFixed(0)); + + if (labelValue == null) return SizedBox.shrink(); + // if (labelValue == 0 || labelValue == 150 / 2 || labelValue == 150) { + // return Text( + // labelValue.toStringAsFixed(0), + // style: const TextStyle( + // color: Colors.black26, + // fontSize: 11, + // ), + // ); + // } + + return SizedBox.shrink(); + }, + + /// for the handling of the sleep time + getTooltipItem: (widget.selectedActivity == "sleep") + ? (data) { + return BarTooltipItem( + '${DateUtil. millisToHourMin(num.parse(data.actualValue).toInt())}\n${DateFormat('dd MMM, yyyy').format(data.time)}', + TextStyle( + color: Colors.black, + fontSize: 12.f, + fontWeight: FontWeight.w500, + ), + ); + } + : null, + + // Bottom axis label formatter (X-axis - Days) + bottomLabelFormatter: (value, dataPoints) { + final index = value.toInt(); + print("value is $value"); + print("the index is $index"); + print("the dataPoints.length is ${dataPoints.length}"); + + var bottomText = ""; + var date = dataPoints[index].time; + print("the time is ${date}"); + switch (context.read().selectedDuration) { + case durations.Durations.daily: + bottomText = getHour(date).toString(); + break; + case durations.Durations.weekly: + bottomText = getDayName(date)[0]; + break; + case durations.Durations.monthly: + case durations.Durations.halfYearly: + case durations.Durations.yearly: + bottomText = getMonthName(date)[0]; + } + + return Text( + bottomText, + style: const TextStyle( + color: Colors.grey, + fontSize: 11, + ), + ); + return const Text(''); + }, + verticalInterval: 1 / context.read().selectedData.values.toList().first.length, + getDrawingVerticalLine: (value) { + return FlLine( + color: AppColors.greyColor, + strokeWidth: 1, + ); + }, + showGridLines: true); + }); + } + + //todo remove these from here + String getDayName(DateTime date) { + return DateUtil.getWeekDayAsOfLang(date.weekday); + } + + String getHour(DateTime date) { + return date.hour.toString().padLeft(2, '0').toString(); + } + + static String getMonthName(DateTime date) { + return DateUtil.getMonthDayAsOfLang(date.month); + } + + double getBarWidth() { + var duration = context.read().selectedDuration; + switch(duration){ + case durations.Durations.daily: + return 26.w; + case durations.Durations.weekly: + return 26.w; + case durations.Durations.monthly: + return 6.w; + case durations.Durations.halfYearly: + return 26.w; + case durations.Durations.yearly: + return 18.w; + } + } +} diff --git a/lib/presentation/smartwatches/smart_watch_activity.dart b/lib/presentation/smartwatches/smart_watch_activity.dart new file mode 100644 index 00000000..678fd715 --- /dev/null +++ b/lib/presentation/smartwatches/smart_watch_activity.dart @@ -0,0 +1,252 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/dependencies.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/smartwatch_health_data/health_provider.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/activity_detail.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:provider/provider.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/HealthDataTransformation.dart' as durations; + +import '../../core/utils/date_util.dart' show DateUtil; + +class SmartWatchActivity extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "All Health Data".needTranslation, + child: Column( + spacing: 16.h, + children: [ + resultItem( + leadingIcon: AppAssets.watchActivity, + title: "Activity Calories".needTranslation, + description: "Activity rings give you a quick visual reference of how active you are each day. ".needTranslation, + trailingIcon: AppAssets.watchActivityTrailing, + result: context.read().sumOfNonEmptyData(context.read().vitals?.activity??[]), + unitsOfMeasure: "Kcal" + ).onPress((){ + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + + context.read().deleteDataIfSectionIsDifferent("activity"); + context.read().saveSelectedSection("activity"); + context.read().fetchData(); + context.read().navigateToDetails("activity", sectionName:"Activity Calories", uom: "Kcal"); + + }), + resultItem( + leadingIcon: AppAssets.watchSteps, + title: "Steps".needTranslation, + description: "Step count is the number of steps you take throughout the day.".needTranslation, + trailingIcon: AppAssets.watchStepsTrailing, + result: context.read().sumOfNonEmptyData(context.read().vitals?.step??[]), + unitsOfMeasure: "Steps" + ).onPress((){ + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + + context.read().deleteDataIfSectionIsDifferent("steps"); + context.read().saveSelectedSection("steps"); + context.read().fetchData(); + context.read().navigateToDetails("steps", sectionName: "Steps", uom: "Steps"); + + }), + resultItem( + leadingIcon: AppAssets.watchSteps, + title: "Distance Covered".needTranslation, + description: "Step count is the distance you take throughout the day.".needTranslation, + trailingIcon: AppAssets.watchStepsTrailing, + result: context.read().sumOfNonEmptyData(context.read().vitals?.distance??[]), + unitsOfMeasure: "Km" + ).onPress((){ + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + + context.read().deleteDataIfSectionIsDifferent("distance"); + context.read().saveSelectedSection("distance"); + context.read().fetchData(); + context.read().navigateToDetails("distance", sectionName: "Distance Covered", uom: "km"); + + }), + + resultItem( + leadingIcon: AppAssets.watchSleep, + title: "Sleep Score".needTranslation, + description: "This will keep track of how much hours you sleep in a day".needTranslation, + trailingIcon: AppAssets.watchSleepTrailing, + result: DateUtil.millisToHourMin(int.parse(context.read().firstNonEmptyValue(context.read().vitals?.sleep??[]))).split(" ")[0], + unitsOfMeasure: "hr", + resultSecondValue: DateUtil.millisToHourMin(int.parse(context.read().firstNonEmptyValue(context.read().vitals?.sleep??[]))).split(" ")[2], + unitOfSecondMeasure: "min" + ).onPress((){ + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + + context.read().deleteDataIfSectionIsDifferent("sleep"); + context.read().saveSelectedSection("sleep"); + context.read().fetchData(); + context.read().navigateToDetails("sleep", sectionName:"Sleep Score",uom:""); + + }), + + resultItem( + leadingIcon: AppAssets.watchWeight, + title: "Blood Oxygen".needTranslation, + description: "This will calculate your Blood Oxygen to keep track and update history".needTranslation, + trailingIcon: AppAssets.watchWeightTrailing, + result: context.read().firstNonEmptyValue(context.read().vitals?.bodyOxygen??[], ), + unitsOfMeasure: "%" + ).onPress((){ + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + + context.read().deleteDataIfSectionIsDifferent("bodyOxygen"); + context.read().saveSelectedSection("bodyOxygen"); + context.read().fetchData(); + context.read().navigateToDetails("bodyOxygen", uom: "%", sectionName:"Blood Oxygen" ); + + }), + resultItem( + leadingIcon: AppAssets.watchWeight, + title: "Body temperature".needTranslation, + description: "This will calculate your Body temprerature to keep track and update history".needTranslation, + trailingIcon: AppAssets.watchWeightTrailing, + result: context.read().firstNonEmptyValue(context.read().vitals?.bodyTemperature??[]), + unitsOfMeasure: "C" + ).onPress((){ + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + + context.read().deleteDataIfSectionIsDifferent("bodyTemperature"); + context.read().saveSelectedSection("bodyTemperature"); + context.read().fetchData(); + context.read().navigateToDetails("bodyTemperature" , sectionName: "Body temperature".capitalizeFirstofEach, uom: "C"); + + }), + ], + ).paddingSymmetrical(24.w, 24.h), + )); + } + + Widget resultItem({ + required String leadingIcon, + required String title, + required String description, + required String trailingIcon, + required String result, + required String unitsOfMeasure, + String? resultSecondValue, + String? unitOfSecondMeasure + }) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Row( + spacing: 16.w, + children: [ + Expanded( + child:Column( + spacing: 8.h, + children: [ + Row( + spacing: 8.w, + children: [ + Utils.buildSvgWithAssets(icon: leadingIcon, height: 16.h, width: 14.w), + title.toText16( weight: FontWeight.w600, color: AppColors.textColor), + ], + ), + description.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + spacing: 2.h, + children: [ + result.toText21(weight: FontWeight.w600, color: AppColors.textColor), + unitsOfMeasure.toText10(weight: FontWeight.w500, color:AppColors.greyTextColor ), + if(resultSecondValue != null) + Visibility( + visible: resultSecondValue != null , + child: Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + spacing: 2.h, + children: [ + SizedBox(width: 2.w,), + resultSecondValue.toText21(weight: FontWeight.w600, color: AppColors.textColor), + unitOfSecondMeasure!.toText10(weight: FontWeight.w500, color:AppColors.greyTextColor ) + ], + ), + ) + ], + ), + + ], + ) , + ), + Utils.buildSvgWithAssets(icon: trailingIcon, width: 72.w, height: 72.h), + ], + ).paddingSymmetrical(16.w, 16.h) + ); + } +} diff --git a/lib/presentation/smartwatches/smartwatch_home_page.dart b/lib/presentation/smartwatches/smartwatch_home_page.dart index 8fd79098..800ff030 100644 --- a/lib/presentation/smartwatches/smartwatch_home_page.dart +++ b/lib/presentation/smartwatches/smartwatch_home_page.dart @@ -3,18 +3,25 @@ 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/common_models/smart_watch.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/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/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/smartwatches/smartwatch_instructions_page.dart'; import 'package:hmg_patient_app_new/presentation/smartwatches/widgets/supported_watches_list.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/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:provider/provider.dart'; +import '../../core/utils/utils.dart'; + class SmartwatchHomePage extends StatelessWidget { const SmartwatchHomePage({super.key}); @@ -80,7 +87,15 @@ class SmartwatchHomePage extends StatelessWidget { CustomButton( text: LocaleKeys.select.tr(context: context), onPressed: () { - context.read().setSelectedWatchType("apple", "assets/images/png/smartwatches/apple-watch-5.jpg"); + context.read().setSelectedWatchType(SmartWatchTypes.apple, "assets/images/png/smartwatches/apple-watch-5.jpg"); + getIt.get().pushPage(page: SmartwatchInstructionsPage( + smartwatchDetails: SmartwatchDetails(SmartWatchTypes.apple, + "assets/images/png/smartwatches/apple-watch-5.jpg", + AppAssets.bluetooth, + LocaleKeys.applehealthapplicationshouldbeinstalledinyourphone.tr(context: context), + LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), + LocaleKeys.applewatchshouldbeconnected.tr(context: context)), + )); }, backgroundColor: AppColors.primaryRedColor.withAlpha(40), borderColor: AppColors.primaryRedColor.withAlpha(0), @@ -105,8 +120,15 @@ class SmartwatchHomePage extends StatelessWidget { CustomButton( text: LocaleKeys.select.tr(context: context), onPressed: () { - context.read().setSelectedWatchType("samsung", "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg"); - }, + context.read().setSelectedWatchType(SmartWatchTypes.samsung, "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg"); + getIt.get().pushPage(page: SmartwatchInstructionsPage( + smartwatchDetails: SmartwatchDetails(SmartWatchTypes.samsung, + "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", + AppAssets.bluetooth, + LocaleKeys.samsunghealthapplicationshouldbeinstalledinyourphone.tr(context: context), + LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), + LocaleKeys.samsungwatchshouldbeconnected.tr(context: context)), + )); }, backgroundColor: AppColors.primaryRedColor.withAlpha(40), borderColor: AppColors.primaryRedColor.withAlpha(0), textColor: AppColors.primaryRedColor, @@ -130,7 +152,16 @@ class SmartwatchHomePage extends StatelessWidget { CustomButton( text: LocaleKeys.select.tr(context: context), onPressed: () { - context.read().setSelectedWatchType("huawei", "assets/images/png/smartwatches/Huawei_Watch.png"); + // context.read().setSelectedWatchType(SmartWatchTypes.huawei, "assets/images/png/smartwatches/Huawei_Watch.png"); + // getIt.get().pushPage(page: SmartwatchInstructionsPage( + // smartwatchDetails: SmartwatchDetails(SmartWatchTypes.huawei, + // "assets/images/png/smartwatches/Huawei_Watch.png", + // AppAssets.bluetooth, + // LocaleKeys.huaweihealthapplicationshouldbeinstalledinyourphone.tr(context: context), + // LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), + // LocaleKeys.huaweiwatchshouldbeconnected.tr(context: context)), + // )); + showUnavailableDialog(context); }, backgroundColor: AppColors.primaryRedColor.withAlpha(40), borderColor: AppColors.primaryRedColor.withAlpha(0), @@ -155,7 +186,17 @@ class SmartwatchHomePage extends StatelessWidget { CustomButton( text: LocaleKeys.select.tr(context: context), onPressed: () { - context.read().setSelectedWatchType("whoop", "assets/images/png/smartwatches/Whoop_Watch.png"); + + showUnavailableDialog(context); + // context.read().setSelectedWatchType(SmartWatchTypes.whoop, "assets/images/png/smartwatches/Whoop_Watch.png"); + // getIt.get().pushPage(page: SmartwatchInstructionsPage( + // smartwatchDetails: SmartwatchDetails(SmartWatchTypes.whoop, + // "assets/images/png/smartwatches/Whoop_Watch.png", + // AppAssets.bluetooth, + // LocaleKeys.whoophealthapplicationshouldbeinstalledinyourphone.tr(context: context), + // LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), + // LocaleKeys.whoopwatchshouldbeconnected.tr(context: context)), + // )); }, backgroundColor: AppColors.primaryRedColor.withAlpha(40), borderColor: AppColors.primaryRedColor.withAlpha(0), @@ -177,4 +218,23 @@ class SmartwatchHomePage extends StatelessWidget { ), ); } + + void showUnavailableDialog(BuildContext context) { + + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.featureComingSoonDescription.tr(context: context), + isShowActionButtons: false, + showOkButton: true, + onConfirmTap: () async { + context.pop(); + } + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } } diff --git a/lib/presentation/smartwatches/smartwatch_instructions_page.dart b/lib/presentation/smartwatches/smartwatch_instructions_page.dart index 48683d50..fa5f3135 100644 --- a/lib/presentation/smartwatches/smartwatch_instructions_page.dart +++ b/lib/presentation/smartwatches/smartwatch_instructions_page.dart @@ -1,15 +1,26 @@ 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/common_models/smart_watch.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/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/presentation/smartwatches/smart_watch_activity.dart' show SmartWatchActivity; +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/buttons/custom_button.dart'; +import 'package:provider/provider.dart'; + +import '../../core/utils/utils.dart'; +import '../../features/smartwatch_health_data/health_provider.dart' show HealthProvider; class SmartwatchInstructionsPage extends StatelessWidget { - const SmartwatchInstructionsPage({super.key}); + final SmartwatchDetails smartwatchDetails; + + const SmartwatchInstructionsPage({super.key, required this.smartwatchDetails}); @override Widget build(BuildContext context) { @@ -25,6 +36,7 @@ class SmartwatchInstructionsPage extends StatelessWidget { child: CustomButton( text: LocaleKeys.getStarted.tr(context: context), onPressed: () { + context.read().initDevice(); }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, @@ -35,8 +47,55 @@ class SmartwatchInstructionsPage extends StatelessWidget { height: 50.h, ).paddingSymmetrical(24.w, 30.h), ), - child: SingleChildScrollView(), + child: Column( + mainAxisSize: MainAxisSize.max, + spacing: 18.h, + children: [ + Image.asset(smartwatchDetails.watchIcon, fit: BoxFit.contain, height: 280.h,width: 280.w,), + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Column( + children: [ + watchContentDetails( + title: smartwatchDetails.detailsTitle, + description: smartwatchDetails.details, + icon: smartwatchDetails.smallIcon, + descriptionTextColor: AppColors.primaryRedColor + ), + Divider( + color: AppColors.dividerColor, + thickness: 1.h, + ).paddingOnly(top: 16.h, bottom: 16.h), + watchContentDetails( + title: smartwatchDetails.secondTitle, + description: LocaleKeys.updatetheinformation.tr(), + icon: AppAssets.bluetooth, + descriptionTextColor: AppColors.greyTextColor + ), + ], + ).paddingSymmetrical(16.w, 16.h), + ) + ], + ).paddingSymmetrical(24.w, 16.h), ), ); } + + + Widget watchContentDetails({required String title, required String description, required String icon, required Color descriptionTextColor}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [ + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Utils.buildSvgWithAssets(icon: icon, width: 40.w, height: 40.h), + + ), + + title.toText16(weight: FontWeight.w600, color: AppColors.textColor), + description.toText12(fontWeight: FontWeight.w500, color: descriptionTextColor) + ], + ); + } } diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 01cc50b7..f22c6805 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -41,6 +41,7 @@ import '../features/monthly_reports/monthly_reports_repo.dart'; import '../features/monthly_reports/monthly_reports_view_model.dart'; import '../features/qr_parking/qr_parking_view_model.dart'; import '../presentation/parking/paking_page.dart'; +import '../presentation/smartwatches/smartwatch_instructions_page.dart'; import '../services/error_handler_service.dart'; class AppRoutes { diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 8f1ff4a6..64518f30 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -57,7 +57,7 @@ class _SplashScreenState extends State { await notificationService.initialize(onNotificationClick: (payload) { // Handle notification click here }); - //ZoomService().initializeZoomSDK(); + ZoomService().initializeZoomSDK(); if (isAppOpenedFromCall) { navigateToTeleConsult(); } else { diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 514acfb4..17e53ba7 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -300,6 +300,7 @@ extension AppColorsContext on BuildContext { // Shimmer Color get shimmerBaseColor => _isDark ? AppColors.dark.shimmerBaseColor : const Color(0xFFE0E0E0); Color get shimmerHighlightColor => _isDark ? AppColors.dark.shimmerHighlightColor : const Color(0xFFF5F5F5); + static const Color tooltipColor = Color(0xFF1AACACAC); // Aliases Color get bgScaffoldColor => scaffoldBgColor; diff --git a/lib/widgets/custom_tab_bar.dart b/lib/widgets/custom_tab_bar.dart index 0fd354e0..b93519ca 100644 --- a/lib/widgets/custom_tab_bar.dart +++ b/lib/widgets/custom_tab_bar.dart @@ -21,6 +21,7 @@ class CustomTabBar extends StatefulWidget { final Color? inActiveTextColor; final Color? inActiveBackgroundColor; final Function(int)? onTabChange; + final bool shouldTabExpanded; const CustomTabBar({ super.key, @@ -31,6 +32,7 @@ class CustomTabBar extends StatefulWidget { this.activeBackgroundColor, this.inActiveBackgroundColor, this.onTabChange, + this.shouldTabExpanded = false }); @override @@ -62,6 +64,11 @@ class CustomTabBarState extends State { final resolvedActiveBgColor = widget.activeBackgroundColor ?? AppColors.lightGrayBGColor; final resolvedInActiveBgColor = widget.inActiveBackgroundColor ?? AppColors.whiteColor; late Widget parentWidget; + if(widget.shouldTabExpanded){ + return Row( + children:List.generate(widget.tabs.length, (index)=>myTab(widget.tabs[index], index, resolvedActiveTextColor, resolvedInActiveTextColor, resolvedActiveBgColor, resolvedInActiveBgColor).expanded), + ); + } if (widget.tabs.length > 3) { parentWidget = ListView.separated( diff --git a/lib/widgets/graph/CustomBarGraph.dart b/lib/widgets/graph/CustomBarGraph.dart new file mode 100644 index 00000000..2f50f20d --- /dev/null +++ b/lib/widgets/graph/CustomBarGraph.dart @@ -0,0 +1,250 @@ +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/common_models/data_points.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +/// A customizable bar chart widget using `fl_chart`. +/// +/// Displays a bar chart with configurable axis labels, colors, and data points. +/// Useful for visualizing comparative data, categories, or grouped values. +/// +/// **Parameters:** +/// - [dataPoints]: List of `DataPoint` objects to plot. +/// - [secondaryDataPoints]: Optional list for grouped bars (e.g., comparison data). +/// - [leftLabelFormatter]: Function to build left axis labels. +/// - [bottomLabelFormatter]: Function to build bottom axis labels. +/// - [width]: Optional width of the chart. +/// - [height]: Required height of the chart. +/// - [maxY], [maxX], [minX]: Axis bounds. +/// - [barColor]: Color of the bars. +/// - [secondaryBarColor]: Color of the secondary bars. +/// - [barRadius]: Border radius for bar corners. +/// - [barWidth]: Width of each bar. +/// - [bottomLabelColor]: Color of bottom axis labels. +/// - [bottomLabelSize]: Font size for bottom axis labels. +/// - [bottomLabelFontWeight]: Font weight for bottom axis labels. +/// - [leftLabelInterval]: Interval between left axis labels. +/// - [leftLabelReservedSize]: Reserved space for left axis labels. +/// - [showBottomTitleDates]: Whether to show bottom axis labels. +/// - [isFullScreeGraph]: Whether the graph is fullscreen. +/// - [makeGraphBasedOnActualValue]: Use `actualValue` for plotting. +/// +/// Example usage: +/// ```dart +/// CustomBarChart( +/// dataPoints: sampleData, +/// leftLabelFormatter: (value) => ..., +/// bottomLabelFormatter: (value, dataPoints) => ..., +/// height: 300, +/// maxY: 100, +/// ) +class CustomBarChart extends StatelessWidget { + final List dataPoints; + final List? secondaryDataPoints; // For grouped bar charts + final double? width; + final double height; + final double? maxY; + final double? maxX; + final double? minX; + Color? barColor; + final Color? secondaryBarColor; + Color? barGridColor; + Color? bottomLabelColor; + final double? bottomLabelSize; + final FontWeight? bottomLabelFontWeight; + final double? leftLabelInterval; + final double? leftLabelReservedSize; + final double? bottomLabelReservedSize; + final bool? showGridLines; + final GetDrawingGridLine? getDrawingVerticalLine; + final double? verticalInterval; + final double? minY; + final BorderRadius? barRadius; + final double barWidth; + final BarTooltipItem Function(DataPoint)? getTooltipItem; + + /// Creates the left label and provides it to the chart + final Widget Function(double) leftLabelFormatter; + final Widget Function(double, List) bottomLabelFormatter; + + final bool showBottomTitleDates; + final bool isFullScreeGraph; + final bool makeGraphBasedOnActualValue; + + CustomBarChart( + {super.key, + required this.dataPoints, + this.secondaryDataPoints, + required this.leftLabelFormatter, + this.width, + required this.height, + this.maxY, + this.maxX, + this.showBottomTitleDates = true, + this.isFullScreeGraph = false, + this.secondaryBarColor, + this.bottomLabelFontWeight = FontWeight.w500, + this.bottomLabelSize, + this.leftLabelInterval, + this.leftLabelReservedSize, + this.bottomLabelReservedSize, + this.makeGraphBasedOnActualValue = false, + required this.bottomLabelFormatter, + this.minX, + this.showGridLines = false, + this.getDrawingVerticalLine, + this.verticalInterval, + this.minY, + this.barRadius, + this.barWidth = 16, + this.getTooltipItem, + this.barColor , + this.barGridColor , + this.bottomLabelColor, + }); + + @override + Widget build(BuildContext context) { + barColor ??= AppColors.bgGreenColor; + barGridColor ??= AppColors.graphGridColor; + bottomLabelColor ??= AppColors.textColor; + return Material( + color: Colors.white, + child: SizedBox( + width: width, + height: height, + child: BarChart( + BarChartData( + minY: minY ?? 0, + maxY: maxY, + + barTouchData: BarTouchData( + handleBuiltInTouches: true, + touchCallback: (FlTouchEvent event, BarTouchResponse? touchResponse) { + // Let fl_chart handle the touch + }, + + touchTooltipData: BarTouchTooltipData( + getTooltipColor: (_)=>AppColorsContext.tooltipColor, + getTooltipItem: (group, groupIndex, rod, rodIndex) { + final dataPoint = dataPoints[groupIndex]; + if(getTooltipItem != null) { + return getTooltipItem!(dataPoint); + } + + return BarTooltipItem( + '${dataPoint.actualValue} ${dataPoint.unitOfMeasurement ?? ""}\n${DateFormat('dd MMM, yyyy').format(dataPoint.time)}', + TextStyle( + color: Colors.black, + fontSize: 12.f, + fontWeight: FontWeight.w500, + ), + ); + }, + ), + enabled: true, + ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: leftLabelReservedSize ?? 80, + interval: leftLabelInterval ?? .1, + getTitlesWidget: (value, _) { + return leftLabelFormatter(value); + }, + ), + ), + bottomTitles: AxisTitles( + axisNameSize: 20, + sideTitles: SideTitles( + showTitles: showBottomTitleDates, + reservedSize: bottomLabelReservedSize ?? 30, + getTitlesWidget: (value, _) { + return bottomLabelFormatter(value, dataPoints); + }, + interval: 1, + ), + ), + topTitles: AxisTitles(), + rightTitles: AxisTitles(), + ), + borderData: FlBorderData( + show: true, + border: const Border( + bottom: BorderSide.none, + left: BorderSide(color: Colors.grey, width: .5), + right: BorderSide.none, + top: BorderSide.none, + ), + ), + barGroups: _buildBarGroups(dataPoints), + + gridData: FlGridData( + show: showGridLines ?? true, + drawHorizontalLine: false, + verticalInterval: verticalInterval, + getDrawingVerticalLine: getDrawingVerticalLine ?? + (value) { + return FlLine( + color: barGridColor, + strokeWidth: 1, + dashArray: [5, 5], + ); + }, + )), + ), + ), + ); + } + + /// Builds bar chart groups from data points + List _buildBarGroups(List dataPoints) { + return dataPoints.asMap().entries.map((entry) { + final index = entry.key; + final dataPoint = entry.value; + double value = (makeGraphBasedOnActualValue) + ? double.tryParse(dataPoint.actualValue) ?? 0.0 + : dataPoint.value; + + final barRods = [ + BarChartRodData( + toY: value, + color: barColor, + width: barWidth, + borderRadius: barRadius ?? BorderRadius.circular(6), + // backDrawRodData: BackgroundBarChartRodData( + // show: true, + // toY: maxY, + // color: Colors.grey[100], + // ), + ), + ]; + + // Add secondary bar if provided (for grouped bar charts) + if (secondaryDataPoints != null && index < secondaryDataPoints!.length) { + final secondaryDataPoint = secondaryDataPoints![index]; + double secondaryValue = (makeGraphBasedOnActualValue) + ? double.tryParse(secondaryDataPoint.actualValue) ?? 0.0 + : secondaryDataPoint.value; + + barRods.add( + BarChartRodData( + toY: secondaryValue, + color: secondaryBarColor ?? AppColors.blueColor, + width: barWidth, + borderRadius: barRadius ?? BorderRadius.circular(6), + ), + ); + } + + return BarChartGroupData( + x: index, + barRods: barRods, + barsSpace: 8.w + ); + }).toList(); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 00000000..8395a120 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,2097 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 + url: "https://pub.dev" + source: hosted + version: "1.3.59" + adaptive_number: + dependency: transitive + description: + name: adaptive_number + sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + amazon_payfort: + dependency: "direct main" + description: + name: amazon_payfort + sha256: "7732df0764aecbb814f910db36d0dca2f696e7e5ea380b49aa3ec62965768b33" + url: "https://pub.dev" + source: hosted + version: "1.1.4" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + audio_session: + dependency: transitive + description: + name: audio_session + sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + auto_size_text: + dependency: "direct main" + description: + name: auto_size_text + sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + barcode_scan2: + dependency: "direct main" + description: + name: barcode_scan2 + sha256: "9b539b0ce419005c451de66374c79f39801986f1fd7a213e63d948f21487cd69" + url: "https://pub.dev" + source: hosted + version: "4.7.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + carp_serializable: + dependency: transitive + description: + name: carp_serializable + sha256: f039f8ea22e9437aef13fe7e9743c3761c76d401288dcb702eadd273c3e4dcef + url: "https://pub.dev" + source: hosted + version: "2.0.1" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + chewie: + dependency: transitive + description: + name: chewie + sha256: "44bcfc5f0dfd1de290c87c9d86a61308b3282a70b63435d5557cfd60f54a69ca" + url: "https://pub.dev" + source: hosted + version: "1.13.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec + url: "https://pub.dev" + source: hosted + version: "6.1.5" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" + url: "https://pub.dev" + source: hosted + version: "0.3.5+1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + dart_jsonwebtoken: + dependency: "direct main" + description: + name: dart_jsonwebtoken + sha256: "0de65691c1d736e9459f22f654ddd6fd8368a271d4e41aa07e53e6301eff5075" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + dartz: + dependency: "direct main" + description: + name: dartz + sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + device_calendar: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: "5ea5ed9e2bb499c0633383b53103f2920b634755" + url: "https://github.com/bardram/device_calendar" + source: git + version: "4.3.1" + device_calendar_plus: + dependency: "direct main" + description: + name: device_calendar_plus + sha256: d11a70d98eb123e8eb09fdcfaf220ca4f1aa65a1512e12092f176f4b54983507 + url: "https://pub.dev" + source: hosted + version: "0.3.3" + device_calendar_plus_android: + dependency: transitive + description: + name: device_calendar_plus_android + sha256: a341ef29fa0251251287d63c1d009dfd35c1459dc6a129fd5e03f5ac92d8d7ff + url: "https://pub.dev" + source: hosted + version: "0.3.3" + device_calendar_plus_ios: + dependency: transitive + description: + name: device_calendar_plus_ios + sha256: "3b2f84ce1ed002be8460e214a3229e66748bbaad4077603f2c734d67c42033ff" + url: "https://pub.dev" + source: hosted + version: "0.3.3" + device_calendar_plus_platform_interface: + dependency: transitive + description: + name: device_calendar_plus_platform_interface + sha256: "0ce7511c094ca256831a48e16efe8f1e97e7bd00a5ff3936296ffd650a1d76b5" + url: "https://pub.dev" + source: hosted + version: "0.3.3" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" + url: "https://pub.dev" + source: hosted + version: "11.5.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + url: "https://pub.dev" + source: hosted + version: "7.0.3" + dropdown_search: + dependency: "direct main" + description: + name: dropdown_search + sha256: c29b3e5147a82a06a4a08b3b574c51cb48cc17ad89893d53ee72a6f86643622e + url: "https://pub.dev" + source: hosted + version: "6.0.2" + easy_localization: + dependency: "direct main" + description: + name: easy_localization + sha256: "2ccdf9db8fe4d9c5a75c122e6275674508fd0f0d49c827354967b8afcc56bbed" + url: "https://pub.dev" + source: hosted + version: "3.0.8" + easy_logger: + dependency: transitive + description: + name: easy_logger + sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 + url: "https://pub.dev" + source: hosted + version: "0.0.2" + ed25519_edwards: + dependency: transitive + description: + name: ed25519_edwards + sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.dev" + source: hosted + version: "2.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c + url: "https://pub.dev" + source: hosted + version: "2.1.5" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: d974b6ba2606371ac71dd94254beefb6fa81185bde0b59bdc1df09885da85fde + url: "https://pub.dev" + source: hosted + version: "10.3.8" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" + firebase_analytics: + dependency: "direct main" + description: + name: firebase_analytics + sha256: "4f85b161772e1d54a66893ef131c0a44bd9e552efa78b33d5f4f60d2caa5c8a3" + url: "https://pub.dev" + source: hosted + version: "11.6.0" + firebase_analytics_platform_interface: + dependency: transitive + description: + name: firebase_analytics_platform_interface + sha256: a44b6d1155ed5cae7641e3de7163111cfd9f6f6c954ca916dc6a3bdfa86bf845 + url: "https://pub.dev" + source: hosted + version: "4.4.3" + firebase_analytics_web: + dependency: transitive + description: + name: firebase_analytics_web + sha256: c7d1ed1f86ae64215757518af5576ff88341c8ce5741988c05cc3b2e07b0b273 + url: "https://pub.dev" + source: hosted + version: "0.5.10+16" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" + url: "https://pub.dev" + source: hosted + version: "3.15.2" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" + url: "https://pub.dev" + source: hosted + version: "2.24.1" + firebase_crashlytics: + dependency: "direct main" + description: + name: firebase_crashlytics + sha256: "662ae6443da91bca1fb0be8aeeac026fa2975e8b7ddfca36e4d90ebafa35dde1" + url: "https://pub.dev" + source: hosted + version: "4.3.10" + firebase_crashlytics_platform_interface: + dependency: transitive + description: + name: firebase_crashlytics_platform_interface + sha256: "7222a8a40077c79f6b8b3f3439241c9f2b34e9ddfde8381ffc512f7b2e61f7eb" + url: "https://pub.dev" + source: hosted + version: "3.8.10" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc" + url: "https://pub.dev" + source: hosted + version: "15.2.10" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754" + url: "https://pub.dev" + source: hosted + version: "4.6.10" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390" + url: "https://pub.dev" + source: hosted + version: "3.10.10" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_callkit_incoming: + dependency: "direct main" + description: + name: flutter_callkit_incoming + sha256: "3589deb8b71e43f2d520a9c8a5240243f611062a8b246cdca4b1fda01fbbf9b8" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + flutter_hooks: + dependency: transitive + description: + name: flutter_hooks + sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 + url: "https://pub.dev" + source: hosted + version: "0.20.5" + flutter_inappwebview: + dependency: "direct main" + description: + name: flutter_inappwebview + sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + flutter_inappwebview_android: + dependency: transitive + description: + name: flutter_inappwebview_android + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_inappwebview_ios: + dependency: transitive + description: + name: flutter_inappwebview_ios + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_macos: + dependency: transitive + description: + name: flutter_inappwebview_macos + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_windows: + dependency: transitive + description: + name: flutter_inappwebview_windows + sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + flutter_ios_voip_kit_karmm: + dependency: "direct main" + description: + name: flutter_ios_voip_kit_karmm + sha256: "31a445d78aacacdf128a0354efb9f4e424285dfe4c0af3ea872e64f03e6f6bfc" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875" + url: "https://pub.dev" + source: hosted + version: "19.5.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe" + url: "https://pub.dev" + source: hosted + version: "9.1.0" + flutter_local_notifications_windows: + dependency: transitive + description: + name: flutter_local_notifications_windows + sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_nfc_kit: + dependency: "direct main" + description: + name: flutter_nfc_kit + sha256: "3cf589592373f1d0b0bd9583532368bb85e7cd76ae014a2b67a5ab2d68ae9450" + url: "https://pub.dev" + source: hosted + version: "3.6.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + url: "https://pub.dev" + source: hosted + version: "2.0.33" + flutter_rating_bar: + dependency: "direct main" + description: + name: flutter_rating_bar + sha256: d2af03469eac832c591a1eba47c91ecc871fe5708e69967073c043b2d775ed93 + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flutter_staggered_animations: + dependency: "direct main" + description: + name: flutter_staggered_animations + sha256: "81d3c816c9bb0dca9e8a5d5454610e21ffb068aedb2bde49d2f8d04f75538351" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + flutter_swiper_view: + dependency: "direct main" + description: + name: flutter_swiper_view + sha256: "2a165b259e8a4c49d4da5626b967ed42a73dac2d075bd9e266ad8d23b9f01879" + url: "https://pub.dev" + source: hosted + version: "1.1.8" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_widget_from_html: + dependency: "direct main" + description: + name: flutter_widget_from_html + sha256: "7f1daefcd3009c43c7e7fb37501e6bb752d79aa7bfad0085fb0444da14e89bd0" + url: "https://pub.dev" + source: hosted + version: "0.17.1" + flutter_widget_from_html_core: + dependency: transitive + description: + name: flutter_widget_from_html_core + sha256: "1120ee6ed3509ceff2d55aa6c6cbc7b6b1291434422de2411b5a59364dd6ff03" + url: "https://pub.dev" + source: hosted + version: "0.17.0" + flutter_zoom_videosdk: + dependency: "direct main" + description: + name: flutter_zoom_videosdk + sha256: "46a4dea664b1c969099328a499c198a1755adf9ac333dea28bea5187910b3bf9" + url: "https://pub.dev" + source: hosted + version: "2.1.10" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8" + url: "https://pub.dev" + source: hosted + version: "8.2.14" + fwfh_cached_network_image: + dependency: transitive + description: + name: fwfh_cached_network_image + sha256: "484cb5f8047f02cfac0654fca5832bfa91bb715fd7fc651c04eb7454187c4af8" + url: "https://pub.dev" + source: hosted + version: "0.16.1" + fwfh_chewie: + dependency: transitive + description: + name: fwfh_chewie + sha256: ae74fc26798b0e74f3983f7b851e74c63b9eeb2d3015ecd4b829096b2c3f8818 + url: "https://pub.dev" + source: hosted + version: "0.16.1" + fwfh_just_audio: + dependency: transitive + description: + name: fwfh_just_audio + sha256: dfd622a0dfe049ac647423a2a8afa7f057d9b2b93d92710b624e3d370b1ac69a + url: "https://pub.dev" + source: hosted + version: "0.17.0" + fwfh_svg: + dependency: transitive + description: + name: fwfh_svg + sha256: "2e6bb241179eeeb1a7941e05c8c923b05d332d36a9085233e7bf110ea7deb915" + url: "https://pub.dev" + source: hosted + version: "0.16.1" + fwfh_url_launcher: + dependency: transitive + description: + name: fwfh_url_launcher + sha256: c38aa8fb373fda3a89b951fa260b539f623f6edb45eee7874cb8b492471af881 + url: "https://pub.dev" + source: hosted + version: "0.16.1" + fwfh_webview: + dependency: transitive + description: + name: fwfh_webview + sha256: f71b0aa16e15d82f3c017f33560201ff5ae04e91e970cab5d12d3bcf970b870c + url: "https://pub.dev" + source: hosted + version: "0.15.6" + geoclue: + dependency: transitive + description: + name: geoclue + sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f + url: "https://pub.dev" + source: hosted + version: "0.1.1" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516" + url: "https://pub.dev" + source: hosted + version: "14.0.2" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a" + url: "https://pub.dev" + source: hosted + version: "5.0.2" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 + url: "https://pub.dev" + source: hosted + version: "2.3.13" + geolocator_linux: + dependency: transitive + description: + name: geolocator_linux + sha256: d64112a205931926f4363bb6bd48f14cb38e7326833041d170615586cd143797 + url: "https://pub.dev" + source: hosted + version: "0.2.4" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67" + url: "https://pub.dev" + source: hosted + version: "4.2.6" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172 + url: "https://pub.dev" + source: hosted + version: "4.1.3" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6" + url: "https://pub.dev" + source: hosted + version: "0.2.5" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: ae78de7c3f2304b8d81f2bb6e320833e5e81de942188542328f074978cc0efa9 + url: "https://pub.dev" + source: hosted + version: "8.3.0" + gms_check: + dependency: "direct main" + description: + name: gms_check + sha256: b3fc08fd41da233f9761f9981303346aa9778b4802e90ce9bd8122674fcca6f0 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + google_api_availability: + dependency: "direct main" + description: + name: google_api_availability + sha256: "2ffdc91e1e0cf4e7974fef6c2988a24cefa81f03526ff04b694df6dc0fcbca03" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + google_api_availability_android: + dependency: transitive + description: + name: google_api_availability_android + sha256: "4794147f43a8f3eee6b514d3ae30dbe6f7b9048cae8cd2a74cb4055cd28d74a8" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + google_api_availability_platform_interface: + dependency: transitive + description: + name: google_api_availability_platform_interface + sha256: "65b7da62fe5b582bb3d508628ad827d36d890710ea274766a992a56fa5420da6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + google_maps: + dependency: transitive + description: + name: google_maps + sha256: "5d410c32112d7c6eb7858d359275b2aa04778eed3e36c745aeae905fb2fa6468" + url: "https://pub.dev" + source: hosted + version: "8.2.0" + google_maps_flutter: + dependency: "direct main" + description: + name: google_maps_flutter + sha256: "819985697596a42e1054b5feb2f407ba1ac92262e02844a40168e742b9f36dca" + url: "https://pub.dev" + source: hosted + version: "2.14.0" + google_maps_flutter_android: + dependency: transitive + description: + name: google_maps_flutter_android + sha256: "6dbbfc697eedd29c3634affb2d6b3e5ecfc4e6e50c8345f4b975cc969c74b582" + url: "https://pub.dev" + source: hosted + version: "2.18.9" + google_maps_flutter_ios: + dependency: transitive + description: + name: google_maps_flutter_ios + sha256: b3f9aa62f65f7f266651e156a910ce88b8158de6546c6b145c9ba8080eb861b3 + url: "https://pub.dev" + source: hosted + version: "2.16.1" + google_maps_flutter_platform_interface: + dependency: transitive + description: + name: google_maps_flutter_platform_interface + sha256: e8b1232419fcdd35c1fdafff96843f5a40238480365599d8ca661dde96d283dd + url: "https://pub.dev" + source: hosted + version: "2.14.1" + google_maps_flutter_web: + dependency: transitive + description: + name: google_maps_flutter_web + sha256: d416602944e1859f3cbbaa53e34785c223fa0a11eddb34a913c964c5cbb5d8cf + url: "https://pub.dev" + source: hosted + version: "0.5.14+3" + gsettings: + dependency: transitive + description: + name: gsettings + sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c" + url: "https://pub.dev" + source: hosted + version: "0.2.8" + health: + dependency: "direct main" + description: + name: health + sha256: "320633022fb2423178baa66508001c4ca5aee5806ffa2c913e66488081e9fd47" + url: "https://pub.dev" + source: hosted + version: "13.1.4" + hijri_gregorian_calendar: + dependency: "direct main" + description: + name: hijri_gregorian_calendar + sha256: aecdbe3c9365fac55f17b5e1f24086a81999b1e5c9372cb08888bfbe61e07fa1 + url: "https://pub.dev" + source: hosted + version: "0.1.1" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + huawei_health: + dependency: "direct main" + description: + name: huawei_health + sha256: "52fb9990e1fc857e2fa1b1251dde63b2146086a13b2d9c50bdfc3c4f715c8a12" + url: "https://pub.dev" + source: hosted + version: "6.16.0+300" + huawei_location: + dependency: "direct main" + description: + name: huawei_location + sha256: dd939b0add3e228865cb7da230d7723551e55677d7d59de7dbfd466229847b9f + url: "https://pub.dev" + source: hosted + version: "6.16.0+300" + huawei_map: + dependency: "direct main" + description: + path: flutter-hms-map + ref: HEAD + resolved-ref: "9a16541e4016e3bf58a2571e6aa658a4751af399" + url: "https://github.com/fleoparra/hms-flutter-plugin.git" + source: git + version: "6.11.2+303" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677" + url: "https://pub.dev" + source: hosted + version: "0.8.13+10" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad" + url: "https://pub.dev" + source: hosted + version: "0.8.13+3" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" + in_app_update: + dependency: "direct main" + description: + name: in_app_update + sha256: "9924a3efe592e1c0ec89dda3683b3cfec3d4cd02d908e6de00c24b759038ddb1" + url: "https://pub.dev" + source: hosted + version: "4.2.5" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + jiffy: + dependency: "direct main" + description: + name: jiffy + sha256: e6f3b2aaec032f95ae917268edcbf007a5b834b57a602d39eb0ab17995a9c64a + url: "https://pub.dev" + source: hosted + version: "6.4.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + just_audio: + dependency: "direct main" + description: + name: just_audio + sha256: "9694e4734f515f2a052493d1d7e0d6de219ee0427c7c29492e246ff32a219908" + url: "https://pub.dev" + source: hosted + version: "0.10.5" + just_audio_platform_interface: + dependency: transitive + description: + name: just_audio_platform_interface + sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + just_audio_web: + dependency: transitive + description: + name: just_audio_web + sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" + url: "https://pub.dev" + source: hosted + version: "0.4.16" + keyboard_actions: + dependency: "direct main" + description: + name: keyboard_actions + sha256: "5155a158c0d22c3a2f4a2192040445fe84977620cf0eeb29f6148a1dcb5835fa" + url: "https://pub.dev" + source: hosted + version: "4.2.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + local_auth: + dependency: "direct main" + description: + name: local_auth + sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + local_auth_android: + dependency: transitive + description: + name: local_auth_android + sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467 + url: "https://pub.dev" + source: hosted + version: "1.0.56" + local_auth_darwin: + dependency: transitive + description: + name: local_auth_darwin + sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" + url: "https://pub.dev" + source: hosted + version: "1.6.1" + local_auth_platform_interface: + dependency: transitive + description: + name: local_auth_platform_interface + sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122 + url: "https://pub.dev" + source: hosted + version: "1.1.0" + local_auth_windows: + dependency: transitive + description: + name: local_auth_windows + sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 + url: "https://pub.dev" + source: hosted + version: "1.0.11" + location: + dependency: "direct main" + description: + name: location + sha256: b080053c181c7d152c43dd576eec6436c40e25f326933051c330da563ddd5333 + url: "https://pub.dev" + source: hosted + version: "8.0.1" + location_platform_interface: + dependency: transitive + description: + name: location_platform_interface + sha256: ca8700bb3f6b1e8b2afbd86bd78b2280d116c613ca7bfa1d4d7b64eba357d749 + url: "https://pub.dev" + source: hosted + version: "6.0.1" + location_web: + dependency: transitive + description: + name: location_web + sha256: b8e3add5efe0d65c5e692b7a135d80a4015c580d3ea646fa71973e97668dd868 + url: "https://pub.dev" + source: hosted + version: "6.0.1" + logger: + dependency: "direct main" + description: + name: logger + sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 + url: "https://pub.dev" + source: hosted + version: "2.6.2" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95" + url: "https://pub.dev" + source: hosted + version: "3.3.2" + manage_calendar_events: + dependency: "direct main" + description: + name: manage_calendar_events + sha256: f17600fcb7dc7047120c185993045e493d686930237b4e3c2689c26a64513d66 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + map_launcher: + dependency: "direct main" + description: + name: map_launcher + sha256: "85ae218777b79c830477ed59d97f5ee9d6025b00c47b05d0b901f4dd7d2297cc" + url: "https://pub.dev" + source: hosted + version: "4.4.3" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + ndef: + dependency: transitive + description: + name: ndef + sha256: "198ba3798e80cea381648569d84059dbba64cd140079fb7b0d9c3f1e0f5973f3" + url: "https://pub.dev" + source: hosted + version: "0.4.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + network_info_plus: + dependency: "direct main" + description: + name: network_info_plus + sha256: f926b2ba86aa0086a0dfbb9e5072089bc213d854135c1712f1d29fc89ba3c877 + url: "https://pub.dev" + source: hosted + version: "6.1.4" + network_info_plus_platform_interface: + dependency: transitive + description: + name: network_info_plus_platform_interface + sha256: "7e7496a8a9d8136859b8881affc613c4a21304afeb6c324bcefc4bd0aff6b94b" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + open_filex: + dependency: "direct main" + description: + name: open_filex + sha256: "9976da61b6a72302cf3b1efbce259200cd40232643a467aac7370addf94d6900" + url: "https://pub.dev" + source: hosted + version: "4.7.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d + url: "https://pub.dev" + source: hosted + version: "9.0.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 + url: "https://pub.dev" + source: hosted + version: "12.0.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" + url: "https://pub.dev" + source: hosted + version: "13.0.1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "75ec242d22e950bdcc79ee38dd520ce4ee0bc491d7fadc4ea47694604d22bf06" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + rrule: + dependency: transitive + description: + name: rrule + sha256: f6f6ad5bf7b19d218d4c985d6055d3c9717f1d6efd5d1c0127b1146f1eb3640c + url: "https://pub.dev" + source: hosted + version: "0.2.18" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + sanitize_html: + dependency: transitive + description: + name: sanitize_html + sha256: "12669c4a913688a26555323fb9cec373d8f9fbe091f2d01c40c723b33caa8989" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + scrollable_positioned_list: + dependency: "direct main" + description: + name: scrollable_positioned_list + sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287" + url: "https://pub.dev" + source: hosted + version: "0.3.8" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 + url: "https://pub.dev" + source: hosted + version: "11.1.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" + url: "https://pub.dev" + source: hosted + version: "2.4.18" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sizer: + dependency: "direct main" + description: + name: sizer + sha256: "9963c89e4d30d7c2108de3eafc0a7e6a4a8009799376ea6be5ef0a9ad87cfbad" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + smooth_corner: + dependency: "direct main" + description: + name: smooth_corner + sha256: "112d7331f82ead81ec870c5d1eb0624f2e7e367eccd166c2fffe4c11d4f87c4f" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + sms_otp_auto_verify: + dependency: "direct main" + description: + name: sms_otp_auto_verify + sha256: ee02af0d6b81d386ef70d7d0317a1929bc0b4a3a30a451284450bbcf6901ba1a + url: "https://pub.dev" + source: hosted + version: "2.2.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + url: "https://pub.dev" + source: hosted + version: "2.4.2+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + syncfusion_flutter_calendar: + dependency: "direct main" + description: + name: syncfusion_flutter_calendar + sha256: "8e8a4eef01d6a82ae2c17e76d497ff289ded274de014c9f471ffabc12d1e2e71" + url: "https://pub.dev" + source: hosted + version: "30.2.7" + syncfusion_flutter_core: + dependency: transitive + description: + name: syncfusion_flutter_core + sha256: bfd026c0f9822b49ff26fed11cd3334519acb6a6ad4b0c81d9cd18df6af1c4c0 + url: "https://pub.dev" + source: hosted + version: "30.2.7" + syncfusion_flutter_datepicker: + dependency: transitive + description: + name: syncfusion_flutter_datepicker + sha256: b5f35cc808e91b229d41613efe71dadab1549a35bfd493f922fc06ccc2fe908c + url: "https://pub.dev" + source: hosted + version: "30.2.7" + syncfusion_localizations: + dependency: transitive + description: + name: syncfusion_localizations + sha256: bb32b07879b4c1dee5d4c8ad1c57343a4fdae55d65a87f492727c11b68f23164 + url: "https://pub.dev" + source: hosted + version: "30.2.7" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.dev" + source: hosted + version: "0.7.6" + time: + dependency: transitive + description: + name: time + sha256: "46187cf30bffdab28c56be9a63861b36e4ab7347bf403297595d6a97e10c789f" + url: "https://pub.dev" + source: hosted + version: "2.1.6" + timezone: + dependency: "direct main" + description: + name: timezone + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + url: "https://pub.dev" + source: hosted + version: "6.3.28" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + url: "https://pub.dev" + source: hosted + version: "6.3.6" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + url: "https://pub.dev" + source: hosted + version: "4.5.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + video_player: + dependency: transitive + description: + name: video_player + sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: ee4fd520b0cafa02e4a867a0f882092e727cdaa1a2d24762171e787f8a502b0a + url: "https://pub.dev" + source: hosted + version: "2.9.1" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: d1eb970495a76abb35e5fa93ee3c58bd76fb6839e2ddf2fbb636674f2b971dd4 + url: "https://pub.dev" + source: hosted + version: "2.8.9" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "57c5d73173f76d801129d0531c2774052c5a7c11ccb962f1830630decd9f24ec" + url: "https://pub.dev" + source: hosted + version: "6.6.0" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + web: + dependency: "direct main" + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: a3da219916aba44947d3a5478b1927876a09781174b5a2b67fa5be0555154bf9 + url: "https://pub.dev" + source: hosted + version: "4.13.1" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: eeeb3fcd5f0ff9f8446c9f4bbc18a99b809e40297528a3395597d03aafb9f510 + url: "https://pub.dev" + source: hosted + version: "4.10.11" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0" + url: "https://pub.dev" + source: hosted + version: "2.14.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: e49f378ed066efb13fc36186bbe0bd2425630d4ea0dbc71a18fdd0e4d8ed8ebc + url: "https://pub.dev" + source: hosted + version: "3.23.5" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" +sdks: + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index 43969000..d561347c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -91,7 +91,7 @@ dependencies: location: ^8.0.1 gms_check: ^1.0.4 huawei_location: ^6.14.2+301 -# huawei_health: ^6.16.0+300 + huawei_health: ^6.15.0+300 intl: ^0.20.2 flutter_widget_from_html: ^0.17.1 huawei_map: ^6.12.0+301