data integration along with the data representation

watch_integration
tahaalam 1 month ago
parent 5ea70397e2
commit bfeb3dce4e

@ -169,6 +169,8 @@ dependencies {
implementation(files("libs/Penguin.aar")) implementation(files("libs/Penguin.aar"))
implementation(files("libs/PenguinRenderer.aar")) implementation(files("libs/PenguinRenderer.aar"))
api(files("libs/samsung-health-data-api.aar")) api(files("libs/samsung-health-data-api.aar"))
implementation("com.huawei.hms:health:6.11.0.300")
implementation("com.huawei.hms:hmscoreinstaller:6.6.0.300")
implementation("com.github.kittinunf.fuel:fuel:2.3.1") implementation("com.github.kittinunf.fuel:fuel:2.3.1")
implementation("com.github.kittinunf.fuel:fuel-android:2.3.1") implementation("com.github.kittinunf.fuel:fuel-android:2.3.1")

@ -134,6 +134,9 @@
android:showOnLockScreen="true" android:showOnLockScreen="true"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:replace="android:label"> tools:replace="android:label">
<meta-data
android:name="com.huawei.hms.client.appid"
android:value="102857389"/>
<activity <activity
android:name="com.cloud.hmg_patient_app.whatsapp.WhatsAppCodeActivity" android:name="com.cloud.hmg_patient_app.whatsapp.WhatsAppCodeActivity"
android:exported="true" android:exported="true"

@ -1,22 +1,26 @@
package com.ejada.hmg package com.ejada.hmg
import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import android.view.WindowManager import android.view.WindowManager
import androidx.annotation.NonNull; import androidx.annotation.NonNull
import androidx.annotation.Nullable
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.ejada.hmg.penguin.PenguinInPlatformBridge import com.ejada.hmg.penguin.PenguinInPlatformBridge
import com.ejada.hmg.samsung_watch.SamsungWatch 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.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity: FlutterFragmentActivity() { class MainActivity: FlutterFragmentActivity() {
private var huaweiWatch : HuaweiWatch? = null
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine); GeneratedPluginRegistrant.registerWith(flutterEngine);
@ -25,6 +29,7 @@ class MainActivity: FlutterFragmentActivity() {
PenguinInPlatformBridge(flutterEngine, this).create() PenguinInPlatformBridge(flutterEngine, this).create()
SamsungWatch(flutterEngine, this) SamsungWatch(flutterEngine, this)
huaweiWatch = HuaweiWatch(flutterEngine, this)
} }
override fun onRequestPermissionsResult( override fun onRequestPermissionsResult(
@ -50,4 +55,30 @@ class MainActivity: FlutterFragmentActivity() {
override fun onResume() { override fun onResume() {
super.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())
// }
// }
// }
} }

@ -1,4 +1,4 @@
package com.ejada.hmg.samsung_watch package com.ejada.hmg.watch.huawei.samsung_watch
@ -8,7 +8,7 @@ import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import com.ejada.hmg.samsung_watch.model.Vitals 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.HealthDataService
import com.samsung.android.sdk.health.data.HealthDataStore 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.AggregatedData
@ -74,8 +74,8 @@ class SamsungWatch(
Permission.of(DataTypes.BLOOD_OXYGEN, AccessType.READ), Permission.of(DataTypes.BLOOD_OXYGEN, AccessType.READ),
Permission.of(DataTypes.ACTIVITY_SUMMARY, AccessType.READ), Permission.of(DataTypes.ACTIVITY_SUMMARY, AccessType.READ),
Permission.of(DataTypes.SLEEP, AccessType.READ), Permission.of(DataTypes.SLEEP, AccessType.READ),
Permission.of(DataTypes.BLOOD_OXYGEN, AccessType.READ),
Permission.of(DataTypes.BODY_TEMPERATURE, 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.SKIN_TEMPERATURE, AccessType.READ),
// Permission.of(DataTypes.NUTRITION, AccessType.READ), // Permission.of(DataTypes.NUTRITION, AccessType.READ),
@ -160,7 +160,8 @@ class SamsungWatch(
val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365) val dateTime = LocalDateTime.now().with(LocalTime.MIDNIGHT).minusDays(365)
val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now()) val localTimeFilter = LocalTimeFilter.of(dateTime, LocalDateTime.now())
val localTimeGroup = LocalTimeGroup.of(LocalTimeGroupUnit.HOURLY, 1) val localTimeGroup = LocalTimeGroup.of(LocalTimeGroupUnit.HOURLY, 1)
val readRequest = DataType.ActivitySummaryType.TOTAL_CALORIES_BURNED.requestBuilder val readRequest = DataType.ActivitySummaryType.TOTAL_ACTIVE_CALORIES_BURNED
.requestBuilder
.setLocalTimeFilterWithGroup(localTimeFilter, localTimeGroup) .setLocalTimeFilterWithGroup(localTimeFilter, localTimeGroup)
.setOrdering(Ordering.DESC) .setOrdering(Ordering.DESC)
.build() .build()
@ -172,6 +173,24 @@ class SamsungWatch(
print("the data is ${vitals}") print("the data is ${vitals}")
result.success("Data is obtained") 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"->{ "bloodOxygen"->{
@ -209,6 +228,24 @@ class SamsungWatch(
} }
} }
"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"->{ "retrieveData"->{
if(vitals.isEmpty()){ if(vitals.isEmpty()){
result.error("NoDataFound", "No Data was obtained", null) result.error("NoDataFound", "No Data was obtained", null)
@ -221,7 +258,8 @@ class SamsungWatch(
"sleep": ${vitals["sleep"]}, "sleep": ${vitals["sleep"]},
"activity": ${vitals["activity"]}, "activity": ${vitals["activity"]},
"bloodOxygen": ${vitals["bloodOxygen"]}, "bloodOxygen": ${vitals["bloodOxygen"]},
"bodyTemperature": ${vitals["bodyTemperature"]} "bodyTemperature": ${vitals["bodyTemperature"]},
"distance": ${vitals["distance"]}
} }
""".trimIndent()) """.trimIndent())
} }
@ -239,6 +277,18 @@ class SamsungWatch(
} }
} }
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>) { private fun CoroutineScope.processBodyTemperature( bodyTemperatureList :List<HealthDataPoint>) {
vitals["bodyTemperature"] = mutableListOf() vitals["bodyTemperature"] = mutableListOf()
bodyTemperatureList.forEach { stepData -> bodyTemperatureList.forEach { stepData ->
@ -262,16 +312,43 @@ class SamsungWatch(
} }
// 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>>) { private fun CoroutineScope.processActivity(activityResult: List<AggregatedData<Float>>) {
vitals["activity"] = mutableListOf() vitals["activity"] = mutableListOf()
activityResult.forEach { stepData -> activityResult.forEach { stepData ->
val vitalData = Vitals().apply { val vitalData = Vitals().apply {
value = stepData.value.toString() value = stepData.value.toString()
timeStamp = stepData.endTime.toString() timeStamp = stepData.startTime.toString()
} }
(vitals["activity"] as MutableList).add(vitalData) (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>>) { private fun CoroutineScope.processStepsCount(result: DataResponse<AggregatedData<Long>>) {
@ -294,7 +371,7 @@ class SamsungWatch(
(vitals["sleep"] as MutableList).add( (vitals["sleep"] as MutableList).add(
Vitals().apply { Vitals().apply {
timeStamp = it.startTime.toString() timeStamp = it.startTime.toString()
value = (it.getValue(DataType.SleepType.DURATION)?.toString().toString()) value = (it.getValue(DataType.SleepType.DURATION)?.toMillis().toString())
} }
) )
} }

@ -1,4 +1,4 @@
package com.ejada.hmg.samsung_watch.model package com.ejada.hmg.watch.huawei.samsung_watch.model
data class Vitals( data class Vitals(
var value : String = "", var value : String = "",

@ -1,7 +1,10 @@
import 'package:device_calendar/device_calendar.dart'; import 'package:device_calendar/device_calendar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../app_state.dart' show AppState;
class DateUtil { class DateUtil {
/// convert String To Date function /// convert String To Date function
/// [date] String we want to convert /// [date] String we want to convert
@ -198,6 +201,10 @@ class DateUtil {
} }
} }
static getMonthDayAsOfLang(int month){
return getIt.get<AppState>().isArabic()?getMonthArabic(month):getMonth(month);
}
/// get month by /// get month by
/// [month] convert month number in to month name in Arabic /// [month] convert month number in to month name in Arabic
static getMonthArabic(int month) { static getMonthArabic(int month) {
@ -268,6 +275,10 @@ class DateUtil {
return date ?? DateTime.now(); return date ?? DateTime.now();
} }
static getWeekDayAsOfLang(int weekDay){
return getIt.get<AppState>().isArabic()?getWeekDayArabic(weekDay):getWeekDayEnglish(weekDay);
}
/// get month by /// get month by
/// [weekDay] convert week day in int to week day name /// [weekDay] convert week day in int to week day name
static getWeekDay(int weekDay) { static getWeekDay(int weekDay) {
@ -580,6 +591,14 @@ class DateUtil {
return weekDayName; // Return as-is if not recognized return weekDayName; // Return as-is if not recognized
} }
} }
static String millisToHourMin(int milliseconds) {
int totalMinutes = (milliseconds / 60000).floor(); // convert ms min
int hours = totalMinutes ~/ 60; // integer division
int minutes = totalMinutes % 60; // remaining minutes
return '${hours} hr ${minutes} min';
}
} }
extension OnlyDate on DateTime { extension OnlyDate on DateTime {

@ -18,6 +18,7 @@ extension CapExtension on String {
String get needTranslation => this; String get needTranslation => this;
String get capitalizeFirstofEach => trim().isNotEmpty ? trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; String get capitalizeFirstofEach => trim().isNotEmpty ? trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : "";
} }
extension EmailValidator on String { extension EmailValidator on String {

@ -0,0 +1,128 @@
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("weekly"),
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())) {
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;
double finalValue = filterType == 'weekly' ? mean : sum;
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);
}
}

@ -1,13 +1,18 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:health/health.dart'; import 'package:health/health.dart';
import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart'; import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/loading_utils.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_service.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import '../../core/common_models/data_points.dart';
import '../../core/dependencies.dart'; import '../../core/dependencies.dart';
import '../../presentation/smartwatches/activity_detail.dart' show ActivityDetails;
import '../../presentation/smartwatches/smart_watch_activity.dart' show SmartWatchActivity; import '../../presentation/smartwatches/smart_watch_activity.dart' show SmartWatchActivity;
import '../../services/navigation_service.dart' show NavigationService; import '../../services/navigation_service.dart' show NavigationService;
import 'HealthDataTransformation.dart';
import 'model/Vitals.dart';
class HealthProvider with ChangeNotifier { class HealthProvider with ChangeNotifier {
final HealthService _healthService = HealthService(); final HealthService _healthService = HealthService();
@ -19,6 +24,19 @@ class HealthProvider with ChangeNotifier {
SmartWatchTypes? selectedWatchType ; SmartWatchTypes? selectedWatchType ;
String selectedWatchURL = 'assets/images/png/smartwatches/apple-watch-5.jpg'; String selectedWatchURL = 'assets/images/png/smartwatches/apple-watch-5.jpg';
HealthDataTransformation healthDataTransformation = HealthDataTransformation();
String selectedSection = "";
Map<String, List<DataPoint>> daily = {};
Map<String, List<DataPoint>> weekly = {};
Map<String, List<DataPoint>> monthly = {};
Map<String, List<DataPoint>> halgyearly = {};
Map<String, List<DataPoint>> yearly = {};
Map<String, List<DataPoint>> selectedData = {};
Durations selectedDuration = Durations.daily;
VitalsWRTType? vitals;
double? averageValue;
String? averageValueString;
setSelectedWatchType(SmartWatchTypes type, String imageURL) { setSelectedWatchType(SmartWatchTypes type, String imageURL) {
selectedWatchType = type; selectedWatchType = type;
@ -109,18 +127,167 @@ class HealthProvider with ChangeNotifier {
if (result.isError) { if (result.isError) {
error = 'Error initializing device: ${result.asError}'; error = 'Error initializing device: ${result.asError}';
} else { } else {
getVitals(); LoaderBottomSheet.hideLoader();
LoaderBottomSheet.showLoader();
await getVitals();
LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(seconds: 5));
getIt.get<NavigationService>().pushPage(page: SmartWatchActivity()); getIt.get<NavigationService>().pushPage(page: SmartWatchActivity());
print('Device initialized successfully'); print('Device initialized successfully');
} }
notifyListeners(); notifyListeners();
} }
void getVitals() async{ Future<void> getVitals() async {
isLoading = true;
notifyListeners();
final result = await _healthService.getVitals(); final result = await _healthService.getVitals();
isLoading = false; vitals = result;
LoaderBottomSheet.hideLoader();
notifyListeners();
}
mapValuesForFilters(
Durations filter,
String selectedSection,
) {
if (vitals == null) return {};
switch (filter) {
case Durations.daily:
if (daily.isNotEmpty) {
selectedData = daily;
break;
}
selectedData = daily = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.daily.value, selectedSection);
break;
case Durations.weekly:
if (weekly.isNotEmpty) {
selectedData = daily;
break;
}
selectedData = weekly = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.weekly.value, selectedSection);
break;
case Durations.monthly:
if (monthly.isNotEmpty) {
selectedData = monthly;
break;
}
selectedData = monthly = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.monthly.value, selectedSection);
break;
case Durations.halfYearly:
if (halgyearly.isNotEmpty) {
selectedData = halgyearly;
break;
}
selectedData = halgyearly = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.halfYearly.value, selectedSection);
break;
case Durations.yearly:
if (yearly.isNotEmpty) {
selectedData = yearly;
break;
}
selectedData = yearly = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.yearly.value, selectedSection);
break;
default:
{}
;
}
notifyListeners(); notifyListeners();
} }
void navigateToDetails(String value, {required String sectionName, required String uom}) {
getIt.get<NavigationService>().pushPage(page: ActivityDetails(selectedActivity: value, sectionName:sectionName, uom: uom,));
}
void saveSelectedSection(String value) {
// if(selectedSection == value) return;
selectedSection = value;
}
void deleteDataIfSectionIsDifferent(String value) {
// if(selectedSection == value){
// return;
// }
daily.clear();
weekly.clear();
halgyearly.clear();
monthly.clear();
yearly.clear();
selectedSection = "";
selectedSection = "";
averageValue = null;
averageValueString = null;
selectedDuration = Durations.daily;
}
void fetchData() {
// if(selectedSection == value) return;
mapValuesForFilters(selectedDuration, selectedSection);
getAverageForData();
transformValueIfSleepIsSelected();
}
void setDurations(Durations duration) {
selectedDuration = duration;
}
void getAverageForData() {
if (selectedData.isEmpty) {
averageValue = 0.0;
notifyListeners();
return;
}
double total = 0;
int count = 0;
selectedData.forEach((key, dataPoints) {
for (var dataPoint in dataPoints) {
total += num.parse(dataPoint.actualValue).toInt();
count++;
}
});
averageValue = count > 0 ? total / count : null;
notifyListeners();
}
void transformValueIfSleepIsSelected() {
if (selectedSection != "sleep") return;
if (averageValue == null) {
averageValueString = null;
return;
}
averageValueString = DateUtil.millisToHourMin(averageValue?.toInt() ?? 0);
averageValue = null;
notifyListeners();
}
String firstNonEmptyValue(List<Vitals> dataPoints) {
try {
return dataPoints.firstWhere((dp) => dp.value != null && dp.value!.trim().isNotEmpty).value;
} catch (e) {
return "0"; // no non-empty value found
}
}
String sumOfNonEmptyData(List<Vitals> list) {
final now = DateTime.now().toLocal();
final today = DateTime(now.year, now.month, now.day);
var data = double.parse(list
.where((dp) {
final localDate = DateTime.parse(dp.timestamp);
final normalized = DateTime(localDate.year, localDate.month, localDate.day);
return normalized.isAtSameMomentAs(today);
})
.fold("0", (sum, dp) => (double.parse(sum) + double.parse(dp.value)).toString())
.toString());
var formattedString = data.toStringAsFixed(2);
if (formattedString.endsWith('.00')) {
return formattedString.substring(0, formattedString.length - 3);
}
return formattedString;
}
} }

@ -183,31 +183,31 @@ class HealthService {
return await watchHelper!.initDevice(); return await watchHelper!.initDevice();
} }
FutureOr<void> getVitals() async { Future<VitalsWRTType?> getVitals() async {
if (watchHelper == null) { if (watchHelper == null) {
print('No watch helper found'); print('No watch helper found');
return; return null;
} }
try { try {
await watchHelper!.getActivity();
await watchHelper!.getHeartRate(); await watchHelper!.getHeartRate();
await watchHelper!.getSleep(); await watchHelper!.getSleep();
await watchHelper!.getSteps(); await watchHelper!.getSteps();
await watchHelper!.getActivity(); await watchHelper!.getActivity();
await watchHelper!.getBodyTemperature(); await watchHelper!.getBodyTemperature();
await watchHelper!.getDistance();
await watchHelper!.getBloodOxygen(); await watchHelper!.getBloodOxygen();
Result<dynamic> data = await watchHelper!.retrieveData(); Result<dynamic> data = await watchHelper!.retrieveData();
if(data.isError) { if(data.isError) {
print('Unable to get the data'); print('Unable to get the data');
} }
var response = jsonDecode(data.asValue?.value?.toString()?.trim().replaceAll("\n", "")??""); var response = jsonDecode(data.asValue?.value?.toString()?.trim().replaceAll("\n", "")??"");
VitalsWRTType vitals = VitalsWRTType.fromMap(response); VitalsWRTType vitals = VitalsWRTType.fromMap(response);
log("the data is ${vitals}"); log("the data is ${vitals}");
return vitals;
}catch(e){ }catch(e){
print('Error getting heart rate: $e'); print('Error getting heart rate: $e');
} }
return null;
} }
} }

@ -1,16 +1,19 @@
class Vitals { class Vitals {
final String value; String value;
final String timestamp; final String timestamp;
final String unitOfMeasure;
Vitals({ Vitals({
required this.value, required this.value,
required this.timestamp, required this.timestamp,
this.unitOfMeasure = "",
}); });
factory Vitals.fromMap(Map<dynamic, dynamic> map) { factory Vitals.fromMap(Map<dynamic, dynamic> map) {
return Vitals( return Vitals(
value: map['value'] ?? "", value: map['value'] ?? "",
timestamp: map['timestamp'] ?? "", timestamp: map['timeStamp'] ?? "",
unitOfMeasure: map['uom'] ?? "",
); );
} }
} }
@ -19,11 +22,19 @@ class VitalsWRTType {
final List<Vitals> heartRate; final List<Vitals> heartRate;
final List<Vitals> sleep; final List<Vitals> sleep;
final List<Vitals> step; final List<Vitals> step;
final List<Vitals> distance;
final List<Vitals> activity; final List<Vitals> activity;
final List<Vitals> bodyOxygen; final List<Vitals> bodyOxygen;
final List<Vitals> bodyTemperature; 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.bodyOxygen, required this.bodyTemperature, required this.heartRate, required this.sleep, required this.step, required this.activity});
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) { factory VitalsWRTType.fromMap(Map<dynamic, dynamic> map) {
List<Vitals> activity = []; List<Vitals> activity = [];
@ -31,26 +42,63 @@ class VitalsWRTType {
List<Vitals> sleeps = []; List<Vitals> sleeps = [];
List<Vitals> heartRate = []; List<Vitals> heartRate = [];
List<Vitals> bodyOxygen = []; List<Vitals> bodyOxygen = [];
List<Vitals> distance = [];
List<Vitals> bodyTemperature = []; List<Vitals> bodyTemperature = [];
map["activity"].forEach((element) { map["activity"].forEach((element) {
activity.add(Vitals.fromMap(element)); element["uom"] = "Kcal";
var data = Vitals.fromMap(element);
// data.value = (double.parse(data.value)/1000).toStringAsFixed(2);
activity.add(data);
}); });
map["steps"].forEach((element) { map["steps"].forEach((element) {
element["uom"] = "";
steps.add(Vitals.fromMap(element)); steps.add(Vitals.fromMap(element));
}); });
map["sleep"].forEach((element) { map["sleep"].forEach((element) {
element["uom"] = "hr";
sleeps.add(Vitals.fromMap(element)); sleeps.add(Vitals.fromMap(element));
}); });
map["heartRate"].forEach((element) { map["heartRate"].forEach((element) {
element["uom"] = "bpm";
heartRate.add(Vitals.fromMap(element)); heartRate.add(Vitals.fromMap(element));
}); });
map["bloodOxygen"].forEach((element) { map["bloodOxygen"].forEach((element) {
element["uom"] = "";
bodyOxygen.add(Vitals.fromMap(element));
});
map["distance"].forEach((element) {
element["uom"] = "m";
bodyOxygen.add(Vitals.fromMap(element)); bodyOxygen.add(Vitals.fromMap(element));
}); });
map["bodyTemperature"].forEach((element) { map["bodyTemperature"].forEach((element) {
element["uom"] = "C";
bodyTemperature.add(Vitals.fromMap(element)); bodyTemperature.add(Vitals.fromMap(element));
}); });
return VitalsWRTType(bodyTemperature: bodyTemperature, bodyOxygen: bodyOxygen, heartRate: heartRate, sleep: sleeps, step: steps, activity: activity); 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,
};
} }
} }

@ -1,4 +1,6 @@
import 'dart:async';
import 'package:async/async.dart'; import 'package:async/async.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class SamsungPlatformChannel { class SamsungPlatformChannel {
@ -77,4 +79,12 @@ class SamsungPlatformChannel {
return Result.error(e); return Result.error(e);
} }
} }
Future<FutureOr<void>> getDistance() async {
try{
return Result.value(await _channel.invokeMethod('distance'));
}catch(e){
return Result.error(e);
}
}
} }

@ -1,4 +1,5 @@
import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart'; import 'package:hmg_patient_app_new/core/common_models/smart_watch.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/samsung_health.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart';
@ -7,6 +8,8 @@ class CreateWatchHelper {
switch(watchType){ switch(watchType){
case SmartWatchTypes.samsung: case SmartWatchTypes.samsung:
return SamsungHealth(); return SamsungHealth();
case SmartWatchTypes.huawei:
return HuaweiHealthDataConnector();
default: default:
return SamsungHealth(); return SamsungHealth();
} }

@ -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();
}
}

@ -84,6 +84,15 @@ class SamsungHealth extends WatchHelper {
} }
} }
@override
FutureOr<void> getDistance() async{
try {
return await platformChannel.getDistance();
}catch(e){
print('Error getting heart rate: $e');
}
}
} }

@ -5,6 +5,7 @@ abstract class WatchHelper {
FutureOr<void> getHeartRate(); FutureOr<void> getHeartRate();
FutureOr<void> getSleep(); FutureOr<void> getSleep();
FutureOr<void> getSteps(); FutureOr<void> getSteps();
FutureOr<void> getDistance();
Future<void> getActivity(); Future<void> getActivity();
Future<dynamic> retrieveData(); Future<dynamic> retrieveData();
Future<dynamic> getBodyTemperature(); Future<dynamic> getBodyTemperature();

@ -303,11 +303,11 @@ class ServicesPage extends StatelessWidget {
true, true,
route: null, route: null,
onTap: () async { onTap: () async {
if (getIt.get<AppState>().isAuthenticated) { // if (getIt.get<AppState>().isAuthenticated) {
getIt.get<NavigationService>().pushPageRoute(AppRoutes.smartWatches); getIt.get<NavigationService>().pushPageRoute(AppRoutes.smartWatches);
} else { // } else {
await getIt.get<AuthenticationViewModel>().onLoginPressed(); // await getIt.get<AuthenticationViewModel>().onLoginPressed();
} // }
}, },
// route: AppRoutes.huaweiHealthExample, // route: AppRoutes.huaweiHealthExample,
), ),

@ -6,17 +6,25 @@ import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; import 'package:hmg_patient_app_new/core/common_models/data_points.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/graph/CustomBarGraph.dart'; import 'package:hmg_patient_app_new/widgets/graph/CustomBarGraph.dart';
import 'package:intl/intl.dart' show DateFormat; import 'package:intl/intl.dart' show DateFormat;
import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/HealthDataTransformation.dart' as durations;
import 'package:dartz/dartz.dart' show Tuple2;
import '../../core/utils/date_util.dart';
class ActivityDetails extends StatefulWidget { class ActivityDetails extends StatefulWidget {
final String selectedActivity; final String selectedActivity;
final String sectionName;
final String uom;
const ActivityDetails({super.key, required this.selectedActivity}); const ActivityDetails({super.key, required this.selectedActivity, required this.sectionName, required this.uom});
@override @override
State<ActivityDetails> createState() => _ActivityDetailsState(); State<ActivityDetails> createState() => _ActivityDetailsState();
@ -25,6 +33,11 @@ class ActivityDetails extends StatefulWidget {
class _ActivityDetailsState extends State<ActivityDetails> { class _ActivityDetailsState extends State<ActivityDetails> {
int index = 0; int index = 0;
@override
void initState() {
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -65,10 +78,24 @@ class _ActivityDetailsState extends State<ActivityDetails> {
], ],
shouldTabExpanded: true, shouldTabExpanded: true,
onTabChange: (index) { onTabChange: (index) {
//todo handle the selection from viewmodel switch (index) {
setState(() { case 0:
this.index = index; context.read<HealthProvider>().setDurations(durations.Durations.daily);
}); break;
case 1:
context.read<HealthProvider>().setDurations(durations.Durations.weekly);
break;
case 2:
context.read<HealthProvider>().setDurations(durations.Durations.monthly);
break;
case 3:
context.read<HealthProvider>().setDurations(durations.Durations.halfYearly);
break;
case 4:
context.read<HealthProvider>().setDurations(durations.Durations.yearly);
break;
}
context.read<HealthProvider>().fetchData();
}, },
), ),
), ),
@ -81,167 +108,235 @@ class _ActivityDetailsState extends State<ActivityDetails> {
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h, hasShadow: true), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h, hasShadow: true),
child: Column( child: Column(
spacing: 8.h, spacing: 8.h,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Column( Column(
children: [widget.selectedActivity.toText32(weight: FontWeight.w600, color: AppColors.textColor), "Average".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)], crossAxisAlignment: CrossAxisAlignment.start,
children: [
widget.sectionName.capitalizeFirstofEach.toText32(weight: FontWeight.w600, color: AppColors.textColor),
"Average".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)
],
), ),
Row( Selector<HealthProvider, Tuple2<double?, String?>>(
children: ["3232".toText24(color: AppColors.textGreenColor, fontWeight: FontWeight.w600), "(20-21)".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500)], selector: (_, model) => Tuple2(model.averageValue, model.averageValueString),
builder: (_, data, __) {
var averageAsDouble = data.value1;
var averageAsString = data.value2;
return Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
(averageAsDouble?.toStringAsFixed(2) ?? averageAsString ?? "N/A").toText24(color: AppColors.textGreenColor, fontWeight: FontWeight.w600),
Visibility(
visible: averageAsDouble != null || averageAsString != null,
child: widget.uom.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500)
) )
], ],
);
})
],
).paddingSymmetrical(16.w, 16.h)); ).paddingSymmetrical(16.w, 16.h));
} }
Widget activityGraph() { Widget activityGraph() {
final _random = Random(); // final _random = Random();
//
int randomBP() => 100 + _random.nextInt(51); // 100150 // int randomBP() => 100 + _random.nextInt(51); // 100150
final List<DataPoint> data6Months = List.generate(6, (index) { // final List<DataPoint> data6Months = List.generate(6, (index) {
final date = DateTime.now().subtract(Duration(days: 30 * (5 - index))); // final date = DateTime.now().subtract(Duration(days: 30 * (5 - index)));
//
final value = randomBP(); // final value = randomBP();
//
return DataPoint( // return DataPoint(
value: value.toDouble(), // value: value.toDouble(),
label: value.toString(), // label: value.toString(),
actualValue: value.toString(), // actualValue: value.toString(),
displayTime: DateFormat('MMM').format(date), // displayTime: DateFormat('MMM').format(date),
unitOfMeasurement: 'mmHg', // unitOfMeasurement: 'mmHg',
time: date, // time: date,
); // );
}); // });
final List<DataPoint> data12Months = List.generate(12, (index) { // final List<DataPoint> data12Months = List.generate(12, (index) {
final date = DateTime.now().subtract(Duration(days: 30 * (11 - index))); // final date = DateTime.now().subtract(Duration(days: 30 * (11 - index)));
//
final value = randomBP(); // final value = randomBP();
//
return DataPoint( // return DataPoint(
value: value.toDouble(), // value: value.toDouble(),
label: value.toString(), // label: value.toString(),
actualValue: value.toString(), // actualValue: value.toString(),
displayTime: DateFormat('MMM').format(date), // displayTime: DateFormat('MMM').format(date),
unitOfMeasurement: 'mmHg', // unitOfMeasurement: 'mmHg',
time: date, // time: date,
); // );
}); // });
//
List<DataPoint> data =[]; // List<DataPoint> data =[];
if(index == 0){ // if(index == 0){
data = data6Months; // data = data6Months;
} else if(index == 1){ // } else if(index == 1){
data = data12Months; // data = data12Months;
} else // } else
data = [ // data = [
DataPoint( // DataPoint(
value: 128, // value: 128,
label: "128", // label: "128",
actualValue: '128', // actualValue: '128',
displayTime: 'Sun', // displayTime: 'Sun',
unitOfMeasurement: 'mmHg', // unitOfMeasurement: 'mmHg',
time: DateTime.now().subtract(const Duration(days: 6)), // time: DateTime.now().subtract(const Duration(days: 6)),
), // ),
DataPoint( // DataPoint(
value: 135, // value: 135,
label: "135", // label: "135",
actualValue: '135', // actualValue: '135',
displayTime: 'Mon', // displayTime: 'Mon',
unitOfMeasurement: 'mmHg', // unitOfMeasurement: 'mmHg',
time: DateTime.now().subtract(const Duration(days: 5)), // time: DateTime.now().subtract(const Duration(days: 5)),
), // ),
DataPoint( // DataPoint(
value: 122, // value: 122,
label: "122", // label: "122",
actualValue: '122', // actualValue: '122',
displayTime: 'Tue', // displayTime: 'Tue',
unitOfMeasurement: 'mmHg', // unitOfMeasurement: 'mmHg',
time: DateTime.now().subtract(const Duration(days: 4)), // time: DateTime.now().subtract(const Duration(days: 4)),
), // ),
DataPoint( // DataPoint(
value: 140, // value: 140,
label: "140", // label: "140",
actualValue: '140', // actualValue: '140',
displayTime: 'Wed', // displayTime: 'Wed',
unitOfMeasurement: 'mmHg', // unitOfMeasurement: 'mmHg',
time: DateTime.now().subtract(const Duration(days: 3)), // time: DateTime.now().subtract(const Duration(days: 3)),
), // ),
DataPoint( // DataPoint(
value: 118, // value: 118,
label: "118", // label: "118",
actualValue: '118', // actualValue: '118',
displayTime: 'Thu', // displayTime: 'Thu',
unitOfMeasurement: 'mmHg', // unitOfMeasurement: 'mmHg',
time: DateTime.now().subtract(const Duration(days: 2)), // time: DateTime.now().subtract(const Duration(days: 2)),
), // ),
DataPoint( // DataPoint(
value: 125, // value: 125,
label: "125", // label: "125",
actualValue: '125', // actualValue: '125',
displayTime: 'Fri', // displayTime: 'Fri',
unitOfMeasurement: 'mmHg', // unitOfMeasurement: 'mmHg',
time: DateTime.now().subtract(const Duration(days: 1)), // time: DateTime.now().subtract(const Duration(days: 1)),
), // ),
DataPoint( // DataPoint(
value: 130, // value: 130,
label: "130", // label: "130",
actualValue: '130', // actualValue: '130',
displayTime: 'Sat', // displayTime: 'Sat',
unitOfMeasurement: 'mmHg', // unitOfMeasurement: 'mmHg',
time: DateTime.now(), // time: DateTime.now(),
), // ),23
]; // ];
return Selector<HealthProvider, Map<String, List<DataPoint>>?>(
selector: (_, model) => model.selectedData,
builder: (_, data, __) {
if (context.read<HealthProvider>().selectedData.values.toList().first?.isEmpty == true) return SizedBox();
return CustomBarChart( return CustomBarChart(
dataPoints: data, dataPoints: context.read<HealthProvider>().selectedData.values.toList().first,
height: 300.h, height: 300.h,
maxY: 150, maxY: 150,
barColor: AppColors.bgGreenColor, barColor: AppColors.bgGreenColor,
barWidth: 26.w, barWidth: context.read<HealthProvider>().selectedData.values.toList().first.length < 10 ? 26.w : 20.w,
barRadius: BorderRadius.circular(8), barRadius: BorderRadius.circular(8),
bottomLabelColor: Colors.black, bottomLabelColor: Colors.black,
bottomLabelSize: 12, bottomLabelSize: 12,
leftLabelInterval: .1, leftLabelInterval: .1,
leftLabelReservedSize: 40, leftLabelReservedSize: 20,
// Left axis label formatter (Y-axis) // Left axis label formatter (Y-axis)
leftLabelFormatter: (value) { leftLabelFormatter: (value) {
var labelValue = double.tryParse(value.toStringAsFixed(0)); var labelValue = double.tryParse(value.toStringAsFixed(0));
if (labelValue == null) return SizedBox.shrink(); if (labelValue == null) return SizedBox.shrink();
if (labelValue == 0 || labelValue == 150 / 2 || labelValue == 150) { // if (labelValue == 0 || labelValue == 150 / 2 || labelValue == 150) {
return Text( // return Text(
labelValue.toStringAsFixed(0), // labelValue.toStringAsFixed(0),
style: const TextStyle( // style: const TextStyle(
color: Colors.black26, // color: Colors.black26,
fontSize: 11, // fontSize: 11,
), // ),
); // );
} // }
return SizedBox.shrink(); return SizedBox.shrink();
}, },
/// for the handling of the sleep time
getTooltipItem: (widget.selectedActivity == "sleep")
? (data) {
return BarTooltipItem(
'${DateUtil. millisToHourMin(num.parse(data.actualValue).toInt())}\n${DateFormat('dd MMM, yyyy').format(data.time)}',
TextStyle(
color: Colors.black,
fontSize: 12.f,
fontWeight: FontWeight.w500,
),
);
}
: null,
// Bottom axis label formatter (X-axis - Days) // Bottom axis label formatter (X-axis - Days)
bottomLabelFormatter: (value, dataPoints) { bottomLabelFormatter: (value, dataPoints) {
final index = value.toInt(); final index = value.toInt();
if (index >= 0 && index < dataPoints.length) { print("value is $value");
print("the index is $index");
print("the dataPoints.length is ${dataPoints.length}");
var bottomText = "";
var date = dataPoints[index].time;
print("the time is ${date}");
switch (context.read<HealthProvider>().selectedDuration) {
case durations.Durations.daily:
bottomText = getHour(date).toString();
break;
case durations.Durations.weekly:
bottomText = getDayName(date)[0];
break;
case durations.Durations.monthly:
case durations.Durations.halfYearly:
case durations.Durations.yearly:
bottomText = getMonthName(date)[0];
}
return Text( return Text(
dataPoints[index].displayTime, bottomText,
style: const TextStyle( style: const TextStyle(
color: Colors.grey, color: Colors.grey,
fontSize: 11, fontSize: 11,
), ),
); );
}
return const Text(''); return const Text('');
}, },
verticalInterval: 1/data.length, verticalInterval: 1 / context.read<HealthProvider>().selectedData.values.toList().first.length,
getDrawingVerticalLine: (value) { getDrawingVerticalLine: (value) {
return FlLine( return FlLine(
color: AppColors.greyColor, color: AppColors.greyColor,
strokeWidth: 1, strokeWidth: 1,
); );
}, },
showGridLines: true, showGridLines: true);
); });
}
//todo remove these from here
String getDayName(DateTime date) {
return DateUtil.getWeekDayAsOfLang(date.weekday);
}
String getHour(DateTime date) {
return date.hour.toString().padLeft(2, '0').toString();
}
static String getMonthName(DateTime date) {
return DateUtil.getMonthDayAsOfLang(date.month);
} }
} }

@ -5,10 +5,15 @@ 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/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/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/presentation/smartwatches/activity_detail.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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 { class SmartWatchActivity extends StatelessWidget {
@override @override
@ -22,64 +27,164 @@ class SmartWatchActivity extends StatelessWidget {
children: [ children: [
resultItem( resultItem(
leadingIcon: AppAssets.watchActivity, leadingIcon: AppAssets.watchActivity,
title: "Activity".needTranslation, title: "Activity Calories".needTranslation,
description: "Activity rings give you a quick visual reference of how active you are each day. ".needTranslation, description: "Activity rings give you a quick visual reference of how active you are each day. ".needTranslation,
trailingIcon: AppAssets.watchActivityTrailing, trailingIcon: AppAssets.watchActivityTrailing,
result: "21", result: context.read<HealthProvider>().sumOfNonEmptyData(context.read<HealthProvider>().vitals?.activity??[]),
unitsOfMeasure: "Cal" unitsOfMeasure: "Cal"
).onPress((){ ).onPress((){
getIt.get<NavigationService>().pushPage(page: ActivityDetails( selectedActivity: "Steps")); // 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: "cal");
}), }),
resultItem( resultItem(
leadingIcon: AppAssets.watchSteps, leadingIcon: AppAssets.watchSteps,
title: "Steps".needTranslation, title: "Steps".needTranslation,
description: "Step count is the number of steps you take throughout the day.".needTranslation, description: "Step count is the number of steps you take throughout the day.".needTranslation,
trailingIcon: AppAssets.watchStepsTrailing, trailingIcon: AppAssets.watchStepsTrailing,
result: "21", result: context.read<HealthProvider>().sumOfNonEmptyData(context.read<HealthProvider>().vitals?.step??[]),
unitsOfMeasure: "Steps" 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( resultItem(
leadingIcon: AppAssets.watchSteps, leadingIcon: AppAssets.watchSteps,
title: "Walking + Running Distance".needTranslation, title: "Distance Covered".needTranslation,
description: "This will show you the total distance you covered in a day".needTranslation, description: "Step count is the distance you take throughout the day.".needTranslation,
trailingIcon: AppAssets.watchStepsTrailing, trailingIcon: AppAssets.watchStepsTrailing,
result: "21", result: context.read<HealthProvider>().sumOfNonEmptyData(context.read<HealthProvider>().vitals?.distance??[]),
unitsOfMeasure: "km" 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( resultItem(
leadingIcon: AppAssets.watchSleep, leadingIcon: AppAssets.watchSleep,
title: "Sleep Score".needTranslation, title: "Sleep Score".needTranslation,
description: "This will keep track of how much hours you sleep in a day".needTranslation, description: "This will keep track of how much hours you sleep in a day".needTranslation,
trailingIcon: AppAssets.watchSleepTrailing, trailingIcon: AppAssets.watchSleepTrailing,
result: "21", result: DateUtil.millisToHourMin(int.parse(context.read<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.sleep??[]))).split(" ")[0],
unitsOfMeasure: "hr", unitsOfMeasure: "hr",
resultSecondValue: "2", resultSecondValue: DateUtil.millisToHourMin(int.parse(context.read<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.sleep??[]))).split(" ")[2],
unitOfSecondMeasure: "min" unitOfSecondMeasure: "min"
), ).onPress((){
resultItem( // Map<String, List<Vitals>> getVitals() {
leadingIcon: AppAssets.watchBmi, // return {
title: "Body Mass Index".needTranslation, // "heartRate": heartRate ,
description: "This will calculate your weight and height ratio to analyze the ".needTranslation, // "sleep": sleep,
trailingIcon: AppAssets.watchBmiTrailing, // "steps": step,
result: "21", // "activity": activity,
unitsOfMeasure: "BMI" // "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( resultItem(
leadingIcon: AppAssets.watchWeight, leadingIcon: AppAssets.watchWeight,
title: "Weight".needTranslation, title: "Blood Oxygen".needTranslation,
description: "This will calculate your weight to keep track and update history".needTranslation, description: "This will calculate your Blood Oxygen to keep track and update history".needTranslation,
trailingIcon: AppAssets.watchWeightTrailing, trailingIcon: AppAssets.watchWeightTrailing,
result: "21", result: context.read<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.bodyOxygen??[], ),
unitsOfMeasure: "Kg" 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( resultItem(
leadingIcon: AppAssets.watchWeight, leadingIcon: AppAssets.watchWeight,
title: "Height".needTranslation, title: "Body temperature".needTranslation,
description: "This will calculate your height to keep track and update history".needTranslation, description: "This will calculate your Body temprerature to keep track and update history".needTranslation,
trailingIcon: AppAssets.watchWeightTrailing, trailingIcon: AppAssets.watchWeightTrailing,
result: "21", result: context.read<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.bodyTemperature??[]),
unitsOfMeasure: "ft" 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), ).paddingSymmetrical(24.w, 24.h),
)); ));

@ -63,6 +63,7 @@ class CustomBarChart extends StatelessWidget {
final double? minY; final double? minY;
final BorderRadius? barRadius; final BorderRadius? barRadius;
final double barWidth; final double barWidth;
final BarTooltipItem Function(DataPoint)? getTooltipItem;
/// Creates the left label and provides it to the chart /// Creates the left label and provides it to the chart
final Widget Function(double) leftLabelFormatter; final Widget Function(double) leftLabelFormatter;
@ -100,7 +101,9 @@ class CustomBarChart extends StatelessWidget {
this.verticalInterval, this.verticalInterval,
this.minY, this.minY,
this.barRadius, this.barRadius,
this.barWidth = 16}); this.barWidth = 16,
this.getTooltipItem
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -124,6 +127,10 @@ class CustomBarChart extends StatelessWidget {
getTooltipColor: (_)=>AppColors.tooltipColor, getTooltipColor: (_)=>AppColors.tooltipColor,
getTooltipItem: (group, groupIndex, rod, rodIndex) { getTooltipItem: (group, groupIndex, rod, rodIndex) {
final dataPoint = dataPoints[groupIndex]; final dataPoint = dataPoints[groupIndex];
if(getTooltipItem != null) {
return getTooltipItem!(dataPoint);
}
return BarTooltipItem( return BarTooltipItem(
'${dataPoint.actualValue} ${dataPoint.unitOfMeasurement ?? ""}\n${DateFormat('dd MMM, yyyy').format(dataPoint.time)}', '${dataPoint.actualValue} ${dataPoint.unitOfMeasurement ?? ""}\n${DateFormat('dd MMM, yyyy').format(dataPoint.time)}',
TextStyle( TextStyle(

@ -88,7 +88,7 @@ dependencies:
location: ^8.0.1 location: ^8.0.1
gms_check: ^1.0.4 gms_check: ^1.0.4
huawei_location: ^6.14.2+301 huawei_location: ^6.14.2+301
huawei_health: ^6.16.0+300 huawei_health: ^6.15.0+300
intl: ^0.20.2 intl: ^0.20.2
flutter_widget_from_html: ^0.17.1 flutter_widget_from_html: ^0.17.1
huawei_map: huawei_map:

Loading…
Cancel
Save