watch integration ui
#183
watch_integration into master
@ -0,0 +1,27 @@
|
||||
//package com.cloud.diplomaticquarterapp
|
||||
package com.cloudsolutions.HMGPatientApp
|
||||
|
||||
|
||||
import io.flutter.app.FlutterApplication
|
||||
|
||||
class Application : FlutterApplication() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
}
|
||||
}
|
||||
|
||||
//import io.flutter.app.FlutterApplication
|
||||
//import io.flutter.plugin.common.PluginRegistry
|
||||
//import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
|
||||
//import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService
|
||||
//
|
||||
//class Application : FlutterApplication(), PluginRegistrantCallback {
|
||||
// override fun onCreate() {
|
||||
// super.onCreate()
|
||||
// FlutterFirebaseMessagingService.setPluginRegistrant(this)
|
||||
// }
|
||||
//
|
||||
// override fun registerWith(registry: PluginRegistry?) {
|
||||
// FirebaseCloudMessagingPluginRegistrant.registerWith(registry)
|
||||
// }
|
||||
//}
|
||||
@ -0,0 +1,78 @@
|
||||
package com.cloudsolutions.HMGPatientApp
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.cloudsolutions.HMGPatientApp.watch.samsung_watch.SamsungWatch
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugins.GeneratedPluginRegistrant
|
||||
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
|
||||
|
||||
class MainActivity: FlutterFragmentActivity() {
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
||||
// Create Flutter Platform Bridge
|
||||
this.window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON)
|
||||
|
||||
PenguinInPlatformBridge(flutterEngine, this).create()
|
||||
SamsungWatch(flutterEngine, this)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
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<Scope?> = result.authAccount.authorizedScopes
|
||||
// if(authorizedScopes.isNotEmpty()) {
|
||||
// huaweiWatch?.getHealthAppAuthorization()
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// Log.w("MainActivty", "authorization fail, errorCode:" + result.getErrorCode())
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@ -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<String, Any>?
|
||||
Log.d("TAG", "configureFlutterEngine: $args")
|
||||
println("args")
|
||||
args?.let {
|
||||
PenguinView(
|
||||
mainActivity,
|
||||
100,
|
||||
args,
|
||||
flutterEngine.dartExecutor.binaryMessenger,
|
||||
activity = mainActivity,
|
||||
channel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<Integer> callable = () -> {
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
return preferences.getInt(key, -1);
|
||||
};
|
||||
|
||||
Future<Integer> 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<String> callable = () -> {
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
return preferences.getString(key, defValue);
|
||||
};
|
||||
|
||||
Future<String> 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<Boolean> callable = () -> {
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
return preferences.getBoolean(key, defValue);
|
||||
};
|
||||
|
||||
Future<Boolean> 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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.cloudsolutions.HMGPatientApp.PermissionManager
|
||||
|
||||
import android.Manifest
|
||||
|
||||
object PermissionHelper {
|
||||
|
||||
fun getRequiredPermissions(): Array<String> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@ -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<out String>, grantResults: IntArray) {
|
||||
if (this.requestCode == requestCode) {
|
||||
val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
|
||||
if (allGranted) {
|
||||
listener.onPermissionGranted()
|
||||
} else {
|
||||
listener.onPermissionDenied()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
@ -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<ResponseBody> = ApiController.getInstance(mContext)
|
||||
.apiMethods
|
||||
.getToken(postToken)
|
||||
|
||||
// Enqueue the call for asynchronous execution
|
||||
purposesCall.enqueue(object : Callback<ResponseBody?> {
|
||||
override fun onResponse(
|
||||
call: Call<ResponseBody?>,
|
||||
response: Response<ResponseBody?>
|
||||
) {
|
||||
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<ResponseBody?>, t: Throwable) {
|
||||
apiTokenCallBack.onGetByRefIDError(t.message)
|
||||
}
|
||||
})
|
||||
} catch (error: Exception) {
|
||||
apiTokenCallBack.onGetByRefIDError("Exception during API call: $error")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<String, Any>,
|
||||
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<String, Any?> = 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<Double>?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onLocationMessage(locationMessage: LocationMessage?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
@ -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<String, List<Vitals>>
|
||||
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<AggregatedData<Float>>) {
|
||||
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<HealthDataPoint>) {
|
||||
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<HealthDataPoint>) {
|
||||
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<AggregatedData<Float>>) {
|
||||
//
|
||||
// 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<AggregatedData<Float>>) {
|
||||
|
||||
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<AggregatedData<Long>>) {
|
||||
val stepCount = ArrayList<AggregatedData<Long>>()
|
||||
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<HealthDataPoint>) {
|
||||
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<HealthDataPoint>,
|
||||
) {
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,274 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.cloudsolutions.HMGPatientApp">
|
||||
<!--
|
||||
io.flutter.app.FlutterApplication is an android.app.Application that
|
||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||
In most cases you can leave this as-is, but you if you want to provide
|
||||
additional functionality it is fine to subclass or reimplement
|
||||
FlutterApplication and put your custom class here.
|
||||
-->
|
||||
<uses-permission
|
||||
android:name="android.permission.ACTIVITY_RECOGNITION"
|
||||
tools:node="remove" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_PHONE_STATE"
|
||||
tools:node="remove" /> <!-- <uses-permission android:name="android.permission.BLUETOOTH" tools:node="remove"/> -->
|
||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" tools:node="remove"/> -->
|
||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" tools:node="remove"/> -->
|
||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH_SCAN" tools:node="remove"/> -->
|
||||
<uses-permission
|
||||
android:name="android.permission.BROADCAST_STICKY"
|
||||
tools:node="remove" />
|
||||
<uses-permission
|
||||
android:name="com.google.android.gms.permission.AD_ID"
|
||||
tools:node="remove" /> <!-- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> -->
|
||||
<uses-permission
|
||||
android:name="android.permission.FOREGROUND_SERVICE"
|
||||
tools:node="remove" />
|
||||
<uses-permission
|
||||
android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"
|
||||
tools:node="remove" />
|
||||
<uses-permission
|
||||
android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"
|
||||
tools:node="remove" />
|
||||
<uses-permission
|
||||
android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"
|
||||
tools:node="remove" />
|
||||
<uses-permission
|
||||
android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"
|
||||
tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" tools:node="remove" />
|
||||
|
||||
<!-- Added by open_filex -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" tools:node="remove" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
|
||||
tools:node="remove" /> <!-- <uses-permission android:name="android.permission.INTERNET" /> -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
|
||||
|
||||
|
||||
<uses-feature android:name="android.hardware.camera.any" />
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.sensor.stepcounter"
|
||||
android:required="false"
|
||||
tools:node="replace" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.sensor.stepdetector"
|
||||
android:required="false"
|
||||
tools:node="replace" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="true" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
|
||||
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
|
||||
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.network"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="com.huawei.appmarket.service.commondata.permission.GET_COMMON_DATA" /> <!-- <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" /> -->
|
||||
<!-- Wifi Permissions -->
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <!-- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> -->
|
||||
<!-- Detect Reboot Permission -->
|
||||
<!-- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.speech.RecognitionService" />
|
||||
</intent>
|
||||
|
||||
<package android:name="com.whatsapp" />
|
||||
<package android:name="com.whatsapp.w4b" />
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:foregroundServiceType="mediaPlayback|connectedDevice|dataSync"
|
||||
android:name=".Application"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher_local"
|
||||
android:label="Dr. Alhabib Beta"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:showOnLockScreen="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:replace="android:label">
|
||||
<meta-data
|
||||
android:name="com.huawei.hms.client.appid"
|
||||
android:value="102857389"/>
|
||||
<!-- <activity-->
|
||||
<!-- android:name="com.cloud.hmg_patient_app.whatsapp.WhatsAppCodeActivity"-->
|
||||
<!-- android:exported="true"-->
|
||||
<!-- android:enabled="true"-->
|
||||
<!-- android:launchMode="standard"-->
|
||||
<!-- >-->
|
||||
<!-- <intent-filter>-->
|
||||
<!-- <action android:name="com.whatsapp.otp.OTP_RETRIEVED" />-->
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </activity>-->
|
||||
|
||||
<meta-data
|
||||
android:name="push_kit_auto_init_enabled"
|
||||
android:value="true" />
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="singleTop"
|
||||
android:showOnLockScreen="true"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
tools:node="merge">
|
||||
|
||||
<!--
|
||||
Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI.
|
||||
-->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme" />
|
||||
<!--
|
||||
Displays an Android View that continues showing the launch screen
|
||||
Drawable until Flutter paints its first frame, then this splash
|
||||
screen fades out. A splash screen is useful to avoid any visual
|
||||
gap between the end of Android's launch screen and the painting of
|
||||
Flutter's first frame.
|
||||
-->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
android:resource="@drawable/launch_background" />
|
||||
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity> <!-- <receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver" android:exported="true"> -->
|
||||
<!-- <intent-filter> -->
|
||||
<!-- <action android:name="android.intent.action.BOOT_COMPLETED"/> -->
|
||||
<!-- <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/> -->
|
||||
<!-- </intent-filter> -->
|
||||
<!-- </receiver> -->
|
||||
<!-- Geofencing -->
|
||||
<!-- <service-->
|
||||
<!-- android:name=".geofence.intent_receivers.GeofenceTransitionsJobIntentService"-->
|
||||
<!-- android:exported="true"-->
|
||||
<!-- android:permission="android.permission.BIND_JOB_SERVICE" />-->
|
||||
|
||||
<!-- <receiver-->
|
||||
<!-- android:name=".geofence.intent_receivers.GeofenceBroadcastReceiver"-->
|
||||
<!-- android:enabled="true"-->
|
||||
<!-- android:exported="false" />-->
|
||||
<!-- <receiver-->
|
||||
<!-- android:name=".geofence.intent_receivers.GeofencingRebootBroadcastReceiver"-->
|
||||
<!-- android:enabled="true"-->
|
||||
<!-- android:exported="false">-->
|
||||
<!-- <intent-filter>-->
|
||||
<!-- <action android:name="android.intent.action.BOOT_COMPLETED" />-->
|
||||
<!-- <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />-->
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </receiver>-->
|
||||
<!-- <receiver-->
|
||||
<!-- android:name=".geofence.intent_receivers.LocationProviderChangeReceiver"-->
|
||||
<!-- android:exported="false">-->
|
||||
<!-- <intent-filter>-->
|
||||
<!-- <action android:name="android.location.PROVIDERS_CHANGED" />-->
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </receiver>-->
|
||||
|
||||
<!-- <service-->
|
||||
<!-- android:name=".geofence.intent_receivers.ReregisterGeofenceJobService"-->
|
||||
<!-- android:exported="true"-->
|
||||
<!-- android:permission="android.permission.BIND_JOB_SERVICE" /> <!– Geofencing –>-->
|
||||
<!--
|
||||
Huawei Push Notifications
|
||||
Set push kit auto enable to true (for obtaining the token on initialize)
|
||||
-->
|
||||
<!-- <meta-data -->
|
||||
<!-- android:name="push_kit_auto_init_enabled" -->
|
||||
<!-- android:value="true" /> -->
|
||||
<!-- These receivers are for sending scheduled local notifications -->
|
||||
<!-- <receiver-->
|
||||
<!-- android:name="com.huawei.hms.flutter.push.receiver.local.HmsLocalNotificationBootEventReceiver"-->
|
||||
<!-- android:exported="false">-->
|
||||
<!-- <intent-filter>-->
|
||||
<!-- <action android:name="android.intent.action.BOOT_COMPLETED" />-->
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </receiver>-->
|
||||
<!-- <receiver-->
|
||||
<!-- android:name="com.huawei.hms.flutter.push.receiver.local.HmsLocalNotificationScheduledPublisher"-->
|
||||
<!-- android:enabled="true"-->
|
||||
<!-- android:exported="false" />-->
|
||||
<!-- <receiver-->
|
||||
<!-- android:name="com.huawei.hms.flutter.push.receiver.BackgroundMessageBroadcastReceiver"-->
|
||||
<!-- android:enabled="true"-->
|
||||
<!-- android:exported="true">-->
|
||||
<!-- <intent-filter>-->
|
||||
<!-- <action android:name="com.huawei.hms.flutter.push.receiver.BACKGROUND_REMOTE_MESSAGE" />-->
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </receiver> <!– Huawei Push Notifications –>-->
|
||||
<meta-data
|
||||
android:name="com.google.android.geo.API_KEY"
|
||||
android:value="AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng" />
|
||||
<!--
|
||||
Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java
|
||||
-->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@ -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<out String>,
|
||||
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<Scope?> = result.authAccount.authorizedScopes
|
||||
// if(authorizedScopes.isNotEmpty()) {
|
||||
// huaweiWatch?.getHealthAppAuthorization()
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// Log.w("MainActivty", "authorization fail, errorCode:" + result.getErrorCode())
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@ -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<String, List<Vitals>>
|
||||
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<AggregatedData<Float>>) {
|
||||
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<HealthDataPoint>) {
|
||||
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<HealthDataPoint>) {
|
||||
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<AggregatedData<Float>>) {
|
||||
//
|
||||
// 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<AggregatedData<Float>>) {
|
||||
|
||||
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<AggregatedData<Long>>) {
|
||||
val stepCount = ArrayList<AggregatedData<Long>>()
|
||||
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<HealthDataPoint>) {
|
||||
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<HealthDataPoint>,
|
||||
) {
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 1021 B |
|
After Width: | Height: | Size: 180 B |
|
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.cloud.diplomaticquarterapp.whatsapp.WhatsAppCodeActivity">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_gravity="center_horizontal">
|
||||
<FrameLayout
|
||||
android:id="@+id/publisher_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#FF9800" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_gravity="center_horizontal">
|
||||
<FrameLayout
|
||||
android:id="@+id/subscriber_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#3F51B5" />
|
||||
|
||||
<TextView
|
||||
android:text="Remote"
|
||||
android:textColor="#FFFFFF"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:keep="@drawable/*,@raw/slow_spring_board" />
|
||||
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -0,0 +1,3 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- <string name="mapbox_access_token" translatable="false" tools:ignore="UnusedResources">sk.eyJ1IjoicndhaWQiLCJhIjoiY2x6NWo0bTMzMWZodzJrcGZpemYzc3Z4dSJ9.uSSZuwNSGCcCdPAiORECmg</string>-->
|
||||
</resources>
|
||||
@ -0,0 +1,23 @@
|
||||
<resources>
|
||||
<string name="app_name">HMG Patient App</string>
|
||||
|
||||
<string name="geofence_unknown_error">
|
||||
Unknown error: the Geofence service is not available now.
|
||||
</string>
|
||||
<string name="geofence_not_available">
|
||||
Geofence service is not available now. Go to Settings>Location>Mode and choose High accuracy.
|
||||
</string>
|
||||
<string name="geofence_too_many_geofences">
|
||||
Your app has registered too many geofences.
|
||||
</string>
|
||||
<string name="geofence_too_many_pending_intents">
|
||||
You have provided too many PendingIntents to the addGeofences() call.
|
||||
</string>
|
||||
<string name="GEOFENCE_INSUFFICIENT_LOCATION_PERMISSION">
|
||||
App do not have permission to access location service.
|
||||
</string>
|
||||
<string name="GEOFENCE_REQUEST_TOO_FREQUENT">
|
||||
Geofence requests happened too frequently.
|
||||
</string>
|
||||
<string name="mapbox_access_token" translatable="false">pk.eyJ1IjoicndhaWQiLCJhIjoiY2x5cGo4aHNjMGNsbTJyc2djempobGQxaSJ9.RCaC6WrUt4A4YnZNfxnONQ</string>
|
||||
</resources>
|
||||
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -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
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" rx="10" fill="#0B85F7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.2743 13.4324L23.9575 13.9486C24.351 14.2459 24.7161 14.5217 24.975 14.784C25.2585 15.0713 25.5387 15.4636 25.5387 16.0028C25.5387 16.542 25.2585 16.9343 24.975 17.2216C24.7161 17.4839 24.351 17.7597 23.9575 18.057L21.3817 20.0034L23.9575 21.9498C24.351 22.2471 24.7161 22.5229 24.975 22.7852C25.2585 23.0726 25.5387 23.4648 25.5387 24.004C25.5387 24.5432 25.2585 24.9355 24.975 25.2228C24.7161 25.4851 24.351 25.7609 23.9575 26.0582L23.2743 26.5745C22.7022 27.0068 22.1995 27.3868 21.7763 27.6186C21.3462 27.8541 20.7396 28.082 20.1123 27.766C19.4877 27.4513 19.3065 26.8305 19.2358 26.345C19.166 25.8658 19.1661 25.2334 19.1662 24.5121L19.1662 21.6777L15.2078 24.6689C14.8406 24.9464 14.318 24.8736 14.0405 24.5064C13.763 24.1393 13.8358 23.6166 14.203 23.3392L18.6173 20.0034L14.203 16.6677C13.8358 16.3902 13.763 15.8676 14.0405 15.5004C14.318 15.1332 14.8406 15.0605 15.2078 15.338L19.1662 18.3292L19.1662 15.4947C19.1661 14.7735 19.166 14.141 19.2358 13.6619C19.3065 13.1764 19.4877 12.5555 20.1123 12.2409C20.7396 11.9248 21.3462 12.1528 21.7763 12.3883C22.1995 12.62 22.7022 13 23.2743 13.4324ZM20.8328 22.0125C20.8328 21.7542 20.8939 21.7238 21.1 21.8795L22.9088 23.2464C22.9773 23.2981 23.0475 23.3494 23.1183 23.401C23.3302 23.5556 23.5466 23.7136 23.7318 23.8966C23.8298 23.9934 23.8298 24.0146 23.7318 24.1114C23.5466 24.2945 23.3302 24.4524 23.1183 24.607C23.0475 24.6587 22.9773 24.7099 22.9088 24.7616L22.3206 25.2061C22.2186 25.2832 22.1131 25.3659 22.005 25.4505C21.7062 25.6846 21.3876 25.9343 21.0677 26.125C20.885 26.2339 20.8327 26.1958 20.8327 25.9859L20.8328 22.0125ZM21.1 18.1273C20.8939 18.2831 20.8328 18.2527 20.8328 17.9943L20.8327 14.0683C20.8327 13.8237 20.8908 13.7927 21.0925 13.9315C21.4296 14.1634 22.0109 14.5667 22.3206 14.8007L22.9088 15.2452C22.9772 15.2969 23.0475 15.3482 23.1182 15.3998C23.3301 15.5544 23.5466 15.7123 23.7318 15.8954C23.8298 15.9922 23.8298 16.0134 23.7318 16.1102C23.5466 16.2933 23.3302 16.4512 23.1183 16.6058C23.0475 16.6574 22.9773 16.7087 22.9088 16.7604L21.1 18.1273Z" fill="white"/>
|
||||
<path d="M24.9999 20.0026C24.9999 19.5424 25.3713 19.1693 25.8295 19.1693H25.837C26.2952 19.1693 26.6666 19.5424 26.6666 20.0026C26.6666 20.4628 26.2952 20.8359 25.837 20.8359H25.8295C25.3713 20.8359 24.9999 20.4628 24.9999 20.0026Z" fill="white"/>
|
||||
<path d="M14.1629 19.1693C13.7047 19.1693 13.3333 19.5424 13.3333 20.0026C13.3333 20.4628 13.7047 20.8359 14.1629 20.8359H14.1703C14.6285 20.8359 14.9999 20.4628 14.9999 20.0026C14.9999 19.5424 14.6285 19.1693 14.1703 19.1693H14.1629Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.59383 1.11189C8.74419 0.954165 8.97212 0.897474 9.17885 0.966384C11.5824 1.76758 13.7863 3.79724 14.9435 6.12945C16.1907 8.48889 16.237 11.1216 15.2416 13.2249C14.2387 15.344 12.193 16.8897 9.36327 17.0615C9.35192 17.0622 9.34056 17.0625 9.32919 17.0625C2.03061 17.0625 -0.541949 8.37522 5.21547 4.37907C5.38744 4.25971 5.61147 4.24572 5.79696 4.34276C5.98245 4.43981 6.0987 4.63183 6.0987 4.84117C6.0987 5.56151 6.1749 6.07698 6.2867 6.42883C6.39975 6.78462 6.53247 6.92354 6.60852 6.9729C6.66642 7.01048 6.74359 7.03168 6.88312 6.98726C7.03735 6.93815 7.24136 6.81277 7.46854 6.5839C7.92091 6.12814 8.35983 5.3671 8.58869 4.47155C8.8162 3.58128 8.82489 2.60277 8.47644 1.70319C8.39773 1.49999 8.44347 1.26961 8.59383 1.11189Z" fill="#18C273"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 861 B |
@ -0,0 +1,4 @@
|
||||
<svg width="78" height="78" viewBox="0 0 78 78" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M76.4788 48.5219C81.7373 27.8239 69.2209 6.7813 48.5227 1.52193C27.8244 -3.73744 6.78231 8.77805 1.5238 29.4761C-3.7347 50.1741 8.78169 71.2167 29.4799 76.4761C50.1782 81.7354 71.2203 69.2199 76.4788 48.5219ZM22.1364 34.7137C24.5028 25.3996 33.9717 19.7676 43.2859 22.1343C52.6001 24.501 58.2325 33.9702 55.8662 43.2843C53.4999 52.5984 44.0309 58.2304 34.7167 55.8637C25.4025 53.497 19.7701 44.0278 22.1364 34.7137Z" fill="#8F9AA3" fill-opacity="0.15"/>
|
||||
<path d="M39.4762 75.6628C39.4905 76.7673 40.3984 77.6564 41.5006 77.585C50.1568 77.0243 58.3957 73.566 64.8713 67.7366C71.8436 61.4602 76.2964 52.8631 77.4 43.5472C78.5036 34.2312 76.1828 24.8318 70.87 17.1C65.9356 9.91907 58.7328 4.63155 50.4473 2.0639C49.3922 1.73695 48.3017 2.38934 48.0297 3.4599L43.7775 20.1953C43.5055 21.2659 44.1586 22.3434 45.1909 22.7364C48.481 23.9889 51.3301 26.2169 53.3415 29.1441C55.7323 32.6234 56.7766 36.8531 56.28 41.0453C55.7834 45.2375 53.7796 49.1062 50.6421 51.9306C48.0024 54.3068 44.7117 55.8075 41.22 56.2566C40.1244 56.3975 39.2376 57.2926 39.2519 58.3971L39.4762 75.6628Z" fill="#18C273"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,6 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.75 2.8125C6.75 1.56986 7.75736 0.5625 9 0.5625C10.2426 0.5625 11.25 1.56986 11.25 2.8125C11.25 3.61712 10.8277 4.32309 10.1926 4.72081C12.3938 5.03292 13.7317 7.48108 12.8512 9.64368C12.6596 10.1143 12.2102 10.4375 11.6939 10.4375H11.3249L10.704 13.0516C10.5142 13.8505 9.82252 14.4375 9 14.4375C8.17749 14.4375 7.48577 13.8505 7.29604 13.0516L6.67515 10.4375H6.30607C5.78977 10.4375 5.34042 10.1143 5.14881 9.64368C4.26828 7.48109 5.60621 5.03292 7.80744 4.72081C7.17235 4.32308 6.75 3.61711 6.75 2.8125Z" fill="#FFA800"/>
|
||||
<path d="M5.25 11.4375H4.5C4.08579 11.4375 3.75 11.7733 3.75 12.1875C3.75 12.6017 4.08579 12.9375 4.5 12.9375H5.25C5.66421 12.9375 6 12.6017 6 12.1875C6 11.7733 5.66421 11.4375 5.25 11.4375Z" fill="#FFA800"/>
|
||||
<path d="M12.75 11.4375C12.3358 11.4375 12 11.7733 12 12.1875C12 12.6017 12.3358 12.9375 12.75 12.9375H13.5C13.9142 12.9375 14.25 12.6017 14.25 12.1875C14.25 11.7733 13.9142 11.4375 13.5 11.4375H12.75Z" fill="#FFA800"/>
|
||||
<path d="M9.75 15.9375C9.75 15.5233 9.41421 15.1875 9 15.1875C8.58579 15.1875 8.25 15.5233 8.25 15.9375V16.6875C8.25 17.1017 8.58579 17.4375 9 17.4375C9.41421 17.4375 9.75 17.1017 9.75 16.6875V15.9375Z" fill="#FFA800"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="76" height="76" viewBox="0 0 76 76" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M72.5 41.5C70.567 41.5 69 39.933 69 38C69 36.067 70.567 34.5 72.5 34.5C74.433 34.5 76 36.067 76 38C76 39.933 74.433 41.5 72.5 41.5Z" fill="#FFA800"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 302 B |
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6534 14.6421C11.8109 14.48 11.8561 14.2393 11.7682 14.0311C11.6802 13.8228 11.4761 13.6875 11.25 13.6875L9.75003 13.6875L9.75003 4.3125L11.25 4.3125C11.4761 4.3125 11.6802 4.17717 11.7682 3.96895C11.8561 3.76072 11.8109 3.52004 11.6534 3.35794C11.5684 3.27054 11.4331 3.10126 11.2428 2.85898C11.0716 2.64081 10.8299 2.333 10.6246 2.09069C10.4042 1.83063 10.1609 1.56453 9.91996 1.35933C9.79927 1.25653 9.666 1.15757 9.52489 1.08206C9.38825 1.00894 9.20686 0.9375 9.00002 0.9375C8.79317 0.9375 8.61178 1.00894 8.47514 1.08206C8.33403 1.15757 8.20076 1.25653 8.08007 1.35933C7.83914 1.56453 7.59578 1.83063 7.37543 2.09069C7.1701 2.333 6.92848 2.64081 6.75722 2.85898C6.56692 3.10126 6.43162 3.27054 6.34666 3.35794C6.1891 3.52004 6.14389 3.76072 6.23188 3.96895C6.31986 4.17718 6.52396 4.3125 6.75002 4.3125H8.25003L8.25003 13.6875H6.75001C6.52396 13.6875 6.31986 13.8228 6.23187 14.0311C6.14389 14.2393 6.1891 14.48 6.34666 14.6421C6.43162 14.7295 6.56692 14.8987 6.75722 15.141C6.92847 15.3592 7.1701 15.667 7.37543 15.9093C7.59578 16.1694 7.83914 16.4355 8.08007 16.6407C8.20076 16.7435 8.33403 16.8424 8.47514 16.9179C8.61178 16.9911 8.79317 17.0625 9.00001 17.0625C9.20686 17.0625 9.38825 16.9911 9.52489 16.9179C9.666 16.8424 9.79927 16.7435 9.91996 16.6407C10.1609 16.4355 10.4042 16.1694 10.6246 15.9093C10.8299 15.667 11.0716 15.3592 11.2428 15.141C11.4331 14.8987 11.5684 14.7295 11.6534 14.6421Z" fill="#FFA800"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.937503 12.9126L0.937501 12.9141V15.5625C0.937501 15.9767 1.27329 16.3125 1.6875 16.3125C2.10172 16.3125 2.4375 15.9767 2.4375 15.5625V13.6875H15.5625V15.5625C15.5625 15.9767 15.8983 16.3125 16.3125 16.3125C16.7267 16.3125 17.0625 15.9767 17.0625 15.5625V13.1263V13.125V11.961C17.0625 11.2871 17.0625 10.7252 17.0026 10.2791C16.9393 9.8083 16.8 9.3832 16.4584 9.04159C16.2015 8.78471 15.8974 8.64225 15.5622 8.56047V5.52518L15.5623 5.43571C15.5635 5.03078 15.5647 4.61131 15.3546 4.21689C15.1452 3.82363 14.8276 3.60814 14.5257 3.40327L14.4676 3.36382C12.9184 2.30718 11.0321 1.6875 9 1.6875C6.96785 1.6875 5.08161 2.30718 3.53236 3.36382L3.47433 3.40327C3.17241 3.60814 2.85484 3.82363 2.64539 4.21689C2.43531 4.61131 2.43651 5.03079 2.43767 5.43571L2.43784 5.52518V8.56048C2.10258 8.64226 1.79848 8.78471 1.5416 9.04159C1.19999 9.3832 1.06074 9.8083 0.997437 10.2791C0.937463 10.7252 0.93748 11.2871 0.937501 11.961L0.937503 12.9126ZM13.539 8.4375C13.737 8.43749 13.9253 8.43749 14.1039 8.439V5.52518C14.1039 5.26409 14.1031 5.1195 14.0919 5.01006C14.0827 4.91955 14.0697 4.89418 14.0624 4.88041C14.0486 4.85453 14.0344 4.83345 13.9859 4.79172C13.9194 4.73446 13.8252 4.66899 13.6357 4.53977C12.3235 3.64477 10.726 3.11932 9 3.11932C7.27396 3.11932 5.67655 3.64477 4.36429 4.53977C4.17483 4.66899 4.08064 4.73446 4.01409 4.79172C3.9656 4.83345 3.95139 4.85453 3.93761 4.88041C3.93028 4.89418 3.91726 4.91955 3.90806 5.01006C3.89692 5.1195 3.8961 5.26409 3.8961 5.52518V8.439C4.07463 8.43749 4.26291 8.43749 4.46086 8.4375H5.4375V7.96336C5.4375 7.76396 5.44903 7.4943 5.60078 7.25058C5.75766 6.99861 6.00084 6.87279 6.19956 6.788C6.98337 6.45359 7.94807 6.1875 9 6.1875C10.0519 6.1875 11.0166 6.45359 11.8004 6.788C11.9992 6.87279 12.2423 6.99861 12.3992 7.25058C12.551 7.4943 12.5625 7.76396 12.5625 7.96336V8.4375H13.539Z" fill="#2563EB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1,5 @@
|
||||
<svg width="76" height="76" viewBox="0 0 76 76" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M38 76C58.9868 76 76 58.9868 76 38C76 17.0132 58.9868 0 38 0C17.0132 0 0 17.0132 0 38C0 58.9868 17.0132 76 38 76Z" fill="#EEF0F1"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.1733 57.7622C62.0865 57.7622 70.9333 48.9153 70.9333 38.0022C70.9333 27.089 62.0865 18.2422 51.1733 18.2422C40.2602 18.2422 31.4133 27.089 31.4133 38.0022C31.4133 48.9153 40.2602 57.7622 51.1733 57.7622Z" fill="#0B85F7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58.5195 45.3418C62.577 45.3418 65.8662 42.0526 65.8662 37.9951C65.8662 33.9377 62.577 30.6484 58.5195 30.6484C54.4621 30.6484 51.1729 33.9377 51.1729 37.9951C51.1729 42.0526 54.4621 45.3418 58.5195 45.3418Z" fill="#04498A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 834 B |
@ -0,0 +1,5 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.03065 3.99764C8.26317 3.88054 8.54566 3.9383 8.71355 4.13726C9.23007 4.74936 9.89464 5.29352 10.6354 5.81785L9.72603 6.72725C9.50636 6.94692 9.50636 7.30308 9.72603 7.52275C9.9457 7.74242 10.3019 7.74242 10.5215 7.52275L11.585 6.45927C11.7918 6.59413 12.0017 6.72898 12.2136 6.8647L11.226 7.85225C11.0064 8.07192 11.0064 8.42808 11.226 8.64775C11.4457 8.86742 11.8019 8.86742 12.0215 8.64775L13.1465 7.52275C13.1582 7.51112 13.1692 7.49912 13.1796 7.48677C14.718 8.49195 16.279 9.63033 16.9168 11.2027C16.9411 11.2625 16.9622 11.3222 16.9803 11.3817L16.9806 11.3828C17.1127 11.8191 17.0754 12.2159 16.9345 12.5584C16.8058 12.8746 16.5209 13.1948 16.2772 13.4015C15.7345 13.8288 14.9919 14.0626 14.3247 14.0626L5.92164 14.0626C4.86348 14.0626 3.99982 14.0627 3.32006 13.9504C2.60001 13.8314 2.01027 13.5755 1.56067 13.0137C1.22687 12.5966 1.05311 12.0717 0.979841 11.5144C0.961047 11.3715 0.948942 11.227 0.942378 11.0826C0.934315 10.905 0.934423 10.7232 0.94166 10.5385C0.963373 9.98453 1.04925 9.40482 1.17116 8.8386C1.4956 7.33173 2.09664 5.82445 2.56332 4.97836C2.67903 4.76857 2.91492 4.6551 3.15104 4.69564C3.38717 4.73618 3.5717 4.92184 3.61081 5.1582C3.69759 5.68261 4.19703 6.0438 5.09532 6.21638C5.66248 6.32535 6.27655 6.33177 6.76992 6.29194C6.63744 5.73186 6.71839 5.252 6.96967 4.85529C7.27174 4.37839 7.79 4.11837 8.03065 3.99764ZM3.50395 12.8401C2.95615 12.7496 2.6605 12.5866 2.43949 12.3104C2.2163 12.1039 2.11095 11.5428 2.08618 11.2881C4.83108 12.1696 6.76679 12.4037 9.18511 11.6392C9.56462 11.5192 9.82978 11.4356 10.0297 11.3827C10.0996 11.3392 10.3337 11.2943 10.7118 11.4627C10.967 11.5706 11.3125 11.7316 11.7922 11.9553C12.8348 12.4415 14.2288 12.776 15.7098 12.4027C15.3837 12.6763 15.0127 12.9373 14.3252 12.9373H5.97152C4.85183 12.9373 4.08149 12.9355 3.50395 12.8401Z" fill="#EC1C2B"/>
|
||||
<path d="M10.8513 5.60225L10.6354 5.81785C10.9407 6.03391 11.2589 6.24658 11.585 6.45927L11.6468 6.39775C11.8665 6.17808 11.8665 5.82192 11.6468 5.60225C11.4271 5.38258 11.071 5.38258 10.8513 5.60225Z" fill="#EC1C2B"/>
|
||||
<path d="M12.3513 6.72725L12.2136 6.8647L12.3515 6.95237C12.5804 7.09893 12.8113 7.24671 13.0424 7.39677C13.0882 7.42655 13.1336 7.45675 13.1796 7.48677C13.3653 7.26576 13.3547 6.9352 13.1468 6.72725C12.9271 6.50758 12.571 6.50758 12.3513 6.72725Z" fill="#EC1C2B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@ -0,0 +1,9 @@
|
||||
<svg width="78" height="66" viewBox="0 0 78 66" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="52" width="7" height="14" rx="1" fill="#EEF0F1"/>
|
||||
<rect x="11" y="8" width="7" height="58" rx="1" fill="#EEF0F1"/>
|
||||
<rect x="22" y="42" width="7" height="24" rx="1" fill="#EEF0F1"/>
|
||||
<rect x="33" y="25" width="7" height="41" rx="1" fill="#EEF0F1"/>
|
||||
<rect x="44" y="57" width="7" height="9" rx="1" fill="#EEF0F1"/>
|
||||
<rect x="55" y="36" width="7" height="30" rx="1" fill="#EEF0F1"/>
|
||||
<rect x="66" y="50" width="7" height="16" rx="1" fill="#ED1C2B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 556 B |
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.98294 0.937501L10.0171 0.937501C10.6943 0.937478 11.2658 0.937458 11.7187 0.990317C12.1912 1.04547 12.653 1.16885 13.0279 1.50686C13.1968 1.65917 13.3405 1.83674 13.4532 2.03335C13.6745 2.4193 13.7109 2.83 13.676 3.23706C14.4077 3.32152 15.0147 3.51453 15.4891 3.98974C15.9403 4.44178 16.1344 5.01085 16.2251 5.68681C16.3125 6.33802 16.3125 7.16608 16.3125 8.19364L16.3125 12.0323C16.3125 13.0598 16.3125 13.8879 16.2251 14.5391C16.1344 15.2151 15.9403 15.7841 15.4891 16.2362C15.0377 16.6883 14.4692 16.8829 13.7939 16.9739C13.1437 17.0614 12.3169 17.0614 11.2912 17.0614L6.70878 17.0614C5.68314 17.0614 4.85635 17.0614 4.20606 16.9739C3.5308 16.8829 2.96232 16.6883 2.51092 16.2362C2.05966 15.7841 1.8656 15.2151 1.77488 14.5391C1.68747 13.8879 1.68749 13.0599 1.6875 12.0323L1.6875 8.19363C1.68749 7.16609 1.68747 6.33801 1.77488 5.68681C1.8656 5.01085 2.05966 4.44178 2.51092 3.98974C2.98532 3.51453 3.59231 3.32152 4.32401 3.23706C4.28909 2.83 4.32552 2.4193 4.54679 2.03335C4.65951 1.83674 4.80316 1.65917 4.97208 1.50686C5.34697 1.16885 5.80878 1.04547 6.2813 0.990317C6.73419 0.937458 7.30573 0.937478 7.98294 0.937501ZM6.4505 2.40548C6.10231 2.44612 5.99439 2.51431 5.94431 2.55945C5.88697 2.61115 5.839 2.67071 5.80176 2.73567C5.77228 2.7871 5.7302 2.8956 5.7731 3.22603C5.81755 3.56848 5.93319 4.02061 6.11117 4.70847C6.25294 5.25636 6.34684 5.61587 6.44666 5.88513C6.54128 6.14037 6.61985 6.25188 6.69652 6.32481C6.75734 6.38266 6.82512 6.43385 6.89865 6.47724C6.99346 6.53318 7.1292 6.58098 7.41292 6.60821C7.60338 6.62648 7.83044 6.63336 8.12257 6.63595L8.66354 5.01303C8.79452 4.62008 9.21926 4.40771 9.61222 4.53869C10.0052 4.66968 10.2175 5.09442 10.0866 5.48738L9.70335 6.63701C10.0818 6.63557 10.3604 6.62996 10.5871 6.60821C10.8708 6.58098 11.0065 6.53318 11.1014 6.47724C11.1749 6.43385 11.2427 6.38266 11.3035 6.32481C11.3801 6.25188 11.4587 6.14037 11.5533 5.88513C11.6532 5.61587 11.7471 5.25636 11.8888 4.70847C12.0668 4.02061 12.1825 3.56848 12.2269 3.22603C12.2698 2.8956 12.2277 2.7871 12.1982 2.73567C12.161 2.67071 12.113 2.61115 12.0557 2.55945C12.0056 2.51431 11.8977 2.44612 11.5495 2.40548C11.1914 2.36368 10.706 2.3625 9.9734 2.3625H8.0266C7.29399 2.3625 6.80862 2.36368 6.4505 2.40548ZM7.5 12.75C7.08579 12.75 6.75 13.0858 6.75 13.5C6.75 13.9142 7.08579 14.25 7.5 14.25H10.5C10.9142 14.25 11.25 13.9142 11.25 13.5C11.25 13.0858 10.9142 12.75 10.5 12.75H7.5Z" fill="#FFA800"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@ -0,0 +1,9 @@
|
||||
<svg width="78" height="66" viewBox="0 0 78 66" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="52" width="7" height="14" rx="1" fill="#EEF0F1"/>
|
||||
<rect x="11" y="8" width="7" height="58" rx="1" fill="#EEF0F1"/>
|
||||
<rect x="22" y="42" width="7" height="24" rx="1" fill="#EEF0F1"/>
|
||||
<rect x="33" y="25" width="7" height="41" rx="1" fill="#EEF0F1"/>
|
||||
<rect x="44" y="57" width="7" height="9" rx="1" fill="#EEF0F1"/>
|
||||
<rect x="55" y="36" width="7" height="30" rx="1" fill="#EEF0F1"/>
|
||||
<rect x="66" y="50" width="7" height="16" rx="1" fill="#FFA800"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 556 B |
@ -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
|
||||
@ -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);
|
||||
}
|
||||
@ -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<String, List<DataPoint>> transformVitalsToDataPoints(VitalsWRTType vitals, String filterType, String selectedSection,) {
|
||||
final Map<String, List<DataPoint>> dataPointMap = {};
|
||||
Map<String, List<Vitals>> data = vitals.getVitals();
|
||||
// Group data based on the filter type
|
||||
Map<String, List<Vitals>> groupedData = {};
|
||||
// List<List<Vitals> > items = data.values.toList();
|
||||
List<String> keys = data.keys.toList();
|
||||
var count = 0;
|
||||
List<Vitals> item = data[selectedSection] ?? [];
|
||||
// for(var item in items) {
|
||||
List<DataPoint> 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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<dynamic, dynamic> map) {
|
||||
return Vitals(
|
||||
value: map['value'] ?? "",
|
||||
timestamp: map['timeStamp'] ?? "",
|
||||
unitOfMeasure: map['uom'] ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
toString(){
|
||||
return "{\"value\": \"$value\", \"timeStamp\": \"$timestamp\", \"uom\": \"$unitOfMeasure\"}";
|
||||
}
|
||||
}
|
||||
|
||||
class VitalsWRTType {
|
||||
final List<Vitals> heartRate;
|
||||
final List<Vitals> sleep;
|
||||
final List<Vitals> step;
|
||||
final List<Vitals> distance;
|
||||
final List<Vitals> activity;
|
||||
final List<Vitals> bodyOxygen;
|
||||
final List<Vitals> 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<dynamic, dynamic> map) {
|
||||
List<Vitals> activity = [];
|
||||
List<Vitals> steps = [];
|
||||
List<Vitals> sleeps = [];
|
||||
List<Vitals> heartRate = [];
|
||||
List<Vitals> bodyOxygen = [];
|
||||
List<Vitals> distance = [];
|
||||
List<Vitals> 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<String, List<Vitals>> getVitals() {
|
||||
return {
|
||||
"heartRate": heartRate ,
|
||||
"sleep": sleep,
|
||||
"steps": step,
|
||||
"activity": activity,
|
||||
"bodyOxygen": bodyOxygen,
|
||||
"bodyTemperature": bodyTemperature,
|
||||
"distance": distance,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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<Result<bool>> initDevice() async {
|
||||
try{
|
||||
await _channel.invokeMethod('init');
|
||||
return Result.value(true);
|
||||
}catch(e){
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<bool>> getRequestedPermission() async {
|
||||
try{
|
||||
await _channel.invokeMethod('getPermission');
|
||||
return Result.value(true);
|
||||
}catch(e){
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
Future<Result<bool>> getHeartRate() async {
|
||||
try{
|
||||
await _channel.invokeMethod('getHeartRate');
|
||||
return Result.value(true);
|
||||
}catch(e){
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
Future<Result<bool>> getSleep() async {
|
||||
try{
|
||||
await _channel.invokeMethod('getSleepData');
|
||||
return Result.value(true);
|
||||
}catch(e){
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
Future<Result<bool>> getSteps() async {
|
||||
try{
|
||||
await _channel.invokeMethod('steps');
|
||||
return Result.value(true);
|
||||
}catch(e){
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<bool>> getActivity() async {
|
||||
try{
|
||||
await _channel.invokeMethod('activitySummary');
|
||||
return Result.value(true);
|
||||
}catch(e){
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<dynamic>> retrieveData() async {
|
||||
try{
|
||||
return Result.value(await _channel.invokeMethod('retrieveData'));
|
||||
}catch(e){
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<dynamic>> getBloodOxygen() async {
|
||||
try{
|
||||
return Result.value(await _channel.invokeMethod('bloodOxygen'));
|
||||
}catch(e){
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<dynamic>> getBodyTemperature() async {
|
||||
try{
|
||||
return Result.value(await _channel.invokeMethod('bodyTemperature'));
|
||||
}catch(e){
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<FutureOr<void>> getDistance() async {
|
||||
try{
|
||||
return Result.value(await _channel.invokeMethod('distance'));
|
||||
}catch(e){
|
||||
return Result.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<HealthDataType> _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<String , List<Vitals>> mappedData = {};
|
||||
|
||||
@override
|
||||
FutureOr<void> 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<void> 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<void> 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<void> 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<dynamic> retrieveData() async {
|
||||
return Result.value(getMappedData());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<dynamic> 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<dynamic> 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<void> 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<Result<bool>> 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],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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<Result<bool>> 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<Scope> scopes = [
|
||||
Scope.HEALTHKIT_STEP_READ, Scope.HEALTHKIT_OXYGEN_SATURATION_READ, // View and store height and weight data in Health Service Kit.
|
||||
Scope.HEALTHKIT_HEARTRATE_READ, Scope.HEALTHKIT_SLEEP_READ,
|
||||
Scope.HEALTHKIT_BODYTEMPERATURE_READ, Scope.HEALTHKIT_CALORIES_READ
|
||||
];
|
||||
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<void> 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<void> getHeartRate() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getSleep() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getSteps() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future retrieveData() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getDistance() {
|
||||
// TODO: implement getDistance
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<void> getHeartRate() async {
|
||||
try {
|
||||
await platformChannel.getHeartRate();
|
||||
|
||||
}catch(e){
|
||||
print('Error getting heart rate: $e');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<bool>> initDevice() async {
|
||||
var result = await platformChannel.initDevice();
|
||||
if(result.isError){
|
||||
return result;
|
||||
}
|
||||
return await platformChannel.getRequestedPermission();
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getSleep() async {
|
||||
try {
|
||||
await platformChannel.getSleep();
|
||||
|
||||
}catch(e){
|
||||
print('Error getting heart rate: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getSteps() async{
|
||||
try {
|
||||
await platformChannel.getSteps();
|
||||
|
||||
}catch(e){
|
||||
print('Error getting heart rate: $e');
|
||||
}
|
||||
}
|
||||
@override
|
||||
Future<void> getActivity() async{
|
||||
try {
|
||||
await platformChannel.getActivity();
|
||||
}catch(e){
|
||||
print('Error getting heart rate: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<dynamic> retrieveData() async{
|
||||
try {
|
||||
return await platformChannel.retrieveData();
|
||||
}catch(e){
|
||||
print('Error getting heart rate: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<dynamic> getBloodOxygen() async{
|
||||
try {
|
||||
return await platformChannel.getBloodOxygen();
|
||||
}catch(e){
|
||||
print('Error getting heart rate: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<dynamic> getBodyTemperature() async {
|
||||
try {
|
||||
return await platformChannel.getBodyTemperature();
|
||||
}catch(e){
|
||||
print('Error getting heart rate: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getDistance() async{
|
||||
try {
|
||||
return await platformChannel.getDistance();
|
||||
}catch(e){
|
||||
print('Error getting heart rate: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'package:async/async.dart';
|
||||
abstract class WatchHelper {
|
||||
Future<Result<bool>> initDevice();
|
||||
FutureOr<void> getHeartRate();
|
||||
FutureOr<void> getSleep();
|
||||
FutureOr<void> getSteps();
|
||||
FutureOr<void> getDistance();
|
||||
Future<void> getActivity();
|
||||
Future<dynamic> retrieveData();
|
||||
Future<dynamic> getBodyTemperature();
|
||||
Future<dynamic> getBloodOxygen();
|
||||
|
||||
}
|
||||
@ -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<HealthProvider>().sumOfNonEmptyData(context.read<HealthProvider>().vitals?.activity??[]),
|
||||
unitsOfMeasure: "Kcal"
|
||||
).onPress((){
|
||||
// Map<String, List<Vitals>> getVitals() {
|
||||
// return {
|
||||
// "heartRate": heartRate ,
|
||||
// "sleep": sleep,
|
||||
// "steps": step,
|
||||
// "activity": activity,
|
||||
// "bodyOxygen": bodyOxygen,
|
||||
// "bodyTemperature": bodyTemperature,
|
||||
// };
|
||||
// }
|
||||
context.read<HealthProvider>().setDurations(durations.Durations.daily);
|
||||
|
||||
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("activity");
|
||||
context.read<HealthProvider>().saveSelectedSection("activity");
|
||||
context.read<HealthProvider>().fetchData();
|
||||
context.read<HealthProvider>().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<HealthProvider>().sumOfNonEmptyData(context.read<HealthProvider>().vitals?.step??[]),
|
||||
unitsOfMeasure: "Steps"
|
||||
).onPress((){
|
||||
// Map<String, List<Vitals>> getVitals() {
|
||||
// return {
|
||||
// "heartRate": heartRate ,
|
||||
// "sleep": sleep,
|
||||
// "steps": step,
|
||||
// "activity": activity,
|
||||
// "bodyOxygen": bodyOxygen,
|
||||
// "bodyTemperature": bodyTemperature,
|
||||
// };
|
||||
// }
|
||||
context.read<HealthProvider>().setDurations(durations.Durations.daily);
|
||||
|
||||
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("steps");
|
||||
context.read<HealthProvider>().saveSelectedSection("steps");
|
||||
context.read<HealthProvider>().fetchData();
|
||||
context.read<HealthProvider>().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<HealthProvider>().sumOfNonEmptyData(context.read<HealthProvider>().vitals?.distance??[]),
|
||||
unitsOfMeasure: "Km"
|
||||
).onPress((){
|
||||
// Map<String, List<Vitals>> getVitals() {
|
||||
// return {
|
||||
// "heartRate": heartRate ,
|
||||
// "sleep": sleep,
|
||||
// "steps": step,
|
||||
// "activity": activity,
|
||||
// "bodyOxygen": bodyOxygen,
|
||||
// "bodyTemperature": bodyTemperature,
|
||||
// };
|
||||
// }
|
||||
context.read<HealthProvider>().setDurations(durations.Durations.daily);
|
||||
|
||||
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("distance");
|
||||
context.read<HealthProvider>().saveSelectedSection("distance");
|
||||
context.read<HealthProvider>().fetchData();
|
||||
context.read<HealthProvider>().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<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.sleep??[]))).split(" ")[0],
|
||||
unitsOfMeasure: "hr",
|
||||
resultSecondValue: DateUtil.millisToHourMin(int.parse(context.read<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.sleep??[]))).split(" ")[2],
|
||||
unitOfSecondMeasure: "min"
|
||||
).onPress((){
|
||||
// Map<String, List<Vitals>> getVitals() {
|
||||
// return {
|
||||
// "heartRate": heartRate ,
|
||||
// "sleep": sleep,
|
||||
// "steps": step,
|
||||
// "activity": activity,
|
||||
// "bodyOxygen": bodyOxygen,
|
||||
// "bodyTemperature": bodyTemperature,
|
||||
// };
|
||||
// }
|
||||
context.read<HealthProvider>().setDurations(durations.Durations.daily);
|
||||
|
||||
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("sleep");
|
||||
context.read<HealthProvider>().saveSelectedSection("sleep");
|
||||
context.read<HealthProvider>().fetchData();
|
||||
context.read<HealthProvider>().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<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.bodyOxygen??[], ),
|
||||
unitsOfMeasure: "%"
|
||||
).onPress((){
|
||||
// Map<String, List<Vitals>> getVitals() {
|
||||
// return {
|
||||
// "heartRate": heartRate ,
|
||||
// "sleep": sleep,
|
||||
// "steps": step,
|
||||
// "activity": activity,
|
||||
// "bodyOxygen": bodyOxygen,
|
||||
// "bodyTemperature": bodyTemperature,
|
||||
// };
|
||||
// }
|
||||
context.read<HealthProvider>().setDurations(durations.Durations.daily);
|
||||
|
||||
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("bodyOxygen");
|
||||
context.read<HealthProvider>().saveSelectedSection("bodyOxygen");
|
||||
context.read<HealthProvider>().fetchData();
|
||||
context.read<HealthProvider>().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<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.bodyTemperature??[]),
|
||||
unitsOfMeasure: "C"
|
||||
).onPress((){
|
||||
// Map<String, List<Vitals>> getVitals() {
|
||||
// return {
|
||||
// "heartRate": heartRate ,
|
||||
// "sleep": sleep,
|
||||
// "steps": step,
|
||||
// "activity": activity,
|
||||
// "bodyOxygen": bodyOxygen,
|
||||
// "bodyTemperature": bodyTemperature,
|
||||
// };
|
||||
// }
|
||||
context.read<HealthProvider>().setDurations(durations.Durations.daily);
|
||||
|
||||
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("bodyTemperature");
|
||||
context.read<HealthProvider>().saveSelectedSection("bodyTemperature");
|
||||
context.read<HealthProvider>().fetchData();
|
||||
context.read<HealthProvider>().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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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<DataPoint> dataPoints;
|
||||
final List<DataPoint>? 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<DataPoint>) 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<BarChartGroupData> _buildBarGroups(List<DataPoint> 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>[
|
||||
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();
|
||||
}
|
||||
}
|
||||