diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 523a9f8e..6d1321f6 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -169,6 +169,8 @@ dependencies { implementation(files("libs/Penguin.aar")) implementation(files("libs/PenguinRenderer.aar")) api(files("libs/samsung-health-data-api.aar")) + implementation("com.huawei.hms:health:6.11.0.300") + implementation("com.huawei.hms:hmscoreinstaller:6.6.0.300") implementation("com.github.kittinunf.fuel:fuel:2.3.1") implementation("com.github.kittinunf.fuel:fuel-android:2.3.1") diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8c1388b5..6ad5e539 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -134,6 +134,9 @@ android:showOnLockScreen="true" android:usesCleartextTraffic="true" tools:replace="android:label"> + = result.authAccount.authorizedScopes +// if(authorizedScopes.isNotEmpty()) { +// huaweiWatch?.getHealthAppAuthorization() +// } +// } +// } else { +// Log.w("MainActivty", "authorization fail, errorCode:" + result.getErrorCode()) +// } +// } +// } } diff --git a/android/app/src/main/kotlin/com/ejada/hmg/samsung_watch/SamsungWatch.kt b/android/app/src/main/kotlin/com/ejada/hmg/watch/samsung_watch/SamsungWatch.kt similarity index 79% rename from android/app/src/main/kotlin/com/ejada/hmg/samsung_watch/SamsungWatch.kt rename to android/app/src/main/kotlin/com/ejada/hmg/watch/samsung_watch/SamsungWatch.kt index 09efa394..09aafff2 100644 --- a/android/app/src/main/kotlin/com/ejada/hmg/samsung_watch/SamsungWatch.kt +++ b/android/app/src/main/kotlin/com/ejada/hmg/watch/samsung_watch/SamsungWatch.kt @@ -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 io.flutter.embedding.engine.FlutterEngine 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.HealthDataStore 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.ACTIVITY_SUMMARY, 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.EXERCISE, AccessType.READ), // Permission.of(DataTypes.SKIN_TEMPERATURE, AccessType.READ), // Permission.of(DataTypes.NUTRITION, AccessType.READ), @@ -160,7 +160,8 @@ class SamsungWatch( 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_CALORIES_BURNED.requestBuilder + val readRequest = DataType.ActivitySummaryType.TOTAL_ACTIVE_CALORIES_BURNED + .requestBuilder .setLocalTimeFilterWithGroup(localTimeFilter, localTimeGroup) .setOrdering(Ordering.DESC) .build() @@ -172,6 +173,24 @@ class SamsungWatch( 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"->{ @@ -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"->{ if(vitals.isEmpty()){ result.error("NoDataFound", "No Data was obtained", null) @@ -221,7 +258,8 @@ class SamsungWatch( "sleep": ${vitals["sleep"]}, "activity": ${vitals["activity"]}, "bloodOxygen": ${vitals["bloodOxygen"]}, - "bodyTemperature": ${vitals["bodyTemperature"]} + "bodyTemperature": ${vitals["bodyTemperature"]}, + "distance": ${vitals["distance"]} } """.trimIndent()) } @@ -239,6 +277,18 @@ class SamsungWatch( } } + private fun CoroutineScope.processDistance(activityResult: List>) { + vitals["distance"] = mutableListOf() + activityResult.forEach { stepData -> + val vitalData = Vitals().apply { + + value = stepData.value.toString() + timeStamp = stepData.startTime.toString() + } + (vitals["distance"] as MutableList).add(vitalData) + } + } + private fun CoroutineScope.processBodyTemperature( bodyTemperatureList :List) { vitals["bodyTemperature"] = mutableListOf() bodyTemperatureList.forEach { stepData -> @@ -262,16 +312,43 @@ class SamsungWatch( } +// private fun CoroutineScope.processActivity(activityResult: List>) { +// +// vitals["activity"] = mutableListOf() +// activityResult.forEach { stepData -> +// val vitalData = Vitals().apply { +// +// value = stepData.value.toString() +// timeStamp = stepData.startTime.toString() +// } +// (vitals["activity"] as MutableList).add(vitalData) +// } +// } private fun CoroutineScope.processActivity(activityResult: List>) { vitals["activity"] = mutableListOf() activityResult.forEach { stepData -> val vitalData = Vitals().apply { + value = stepData.value.toString() - timeStamp = stepData.endTime.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>) { @@ -294,7 +371,7 @@ class SamsungWatch( (vitals["sleep"] as MutableList).add( Vitals().apply { timeStamp = it.startTime.toString() - value = (it.getValue(DataType.SleepType.DURATION)?.toString().toString()) + value = (it.getValue(DataType.SleepType.DURATION)?.toMillis().toString()) } ) } diff --git a/android/app/src/main/kotlin/com/ejada/hmg/samsung_watch/model/Vitals.kt b/android/app/src/main/kotlin/com/ejada/hmg/watch/samsung_watch/model/Vitals.kt similarity index 81% rename from android/app/src/main/kotlin/com/ejada/hmg/samsung_watch/model/Vitals.kt rename to android/app/src/main/kotlin/com/ejada/hmg/watch/samsung_watch/model/Vitals.kt index 6ab7d37c..3b5cdfe4 100644 --- a/android/app/src/main/kotlin/com/ejada/hmg/samsung_watch/model/Vitals.kt +++ b/android/app/src/main/kotlin/com/ejada/hmg/watch/samsung_watch/model/Vitals.kt @@ -1,4 +1,4 @@ -package com.ejada.hmg.samsung_watch.model +package com.ejada.hmg.watch.huawei.samsung_watch.model data class Vitals( var value : String = "", diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index 2b134735..f15214a1 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -1,7 +1,10 @@ import 'package:device_calendar/device_calendar.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:intl/intl.dart'; +import '../app_state.dart' show AppState; + class DateUtil { /// convert String To Date function /// [date] String we want to convert @@ -198,6 +201,10 @@ class DateUtil { } } + static getMonthDayAsOfLang(int month){ + return getIt.get().isArabic()?getMonthArabic(month):getMonth(month); + } + /// get month by /// [month] convert month number in to month name in Arabic static getMonthArabic(int month) { @@ -268,6 +275,10 @@ class DateUtil { return date ?? DateTime.now(); } + static getWeekDayAsOfLang(int weekDay){ + return getIt.get().isArabic()?getWeekDayArabic(weekDay):getWeekDayEnglish(weekDay); + } + /// get month by /// [weekDay] convert week day in int to week day name static getWeekDay(int weekDay) { @@ -580,6 +591,14 @@ class DateUtil { return weekDayName; // Return as-is if not recognized } } + + static String millisToHourMin(int milliseconds) { + int totalMinutes = (milliseconds / 60000).floor(); // convert ms → min + int hours = totalMinutes ~/ 60; // integer division + int minutes = totalMinutes % 60; // remaining minutes + + return '${hours} hr ${minutes} min'; + } } extension OnlyDate on DateTime { diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 6a009428..2deefa78 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -18,6 +18,7 @@ extension CapExtension on String { String get needTranslation => this; String get capitalizeFirstofEach => trim().isNotEmpty ? trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; + } extension EmailValidator on String { diff --git a/lib/features/smartwatch_health_data/HealthDataTransformation.dart b/lib/features/smartwatch_health_data/HealthDataTransformation.dart new file mode 100644 index 00000000..d389135d --- /dev/null +++ b/lib/features/smartwatch_health_data/HealthDataTransformation.dart @@ -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> transformVitalsToDataPoints(VitalsWRTType vitals, String filterType, String selectedSection,) { + final Map> dataPointMap = {}; + Map> data = vitals.getVitals(); + // Group data based on the filter type + Map> groupedData = {}; + // List > items = data.values.toList(); + List keys = data.keys.toList(); + var count = 0; + List item = data[selectedSection] ?? []; + // for(var item in items) { + List dataPoints = []; + + for (var vital in item) { + String key = ""; + if (vital.value == "" || vital.timestamp == "") continue; + var parseDate = DateTime.parse(vital.timestamp); + var currentDate = normalizeToStartOfDay(DateTime.now()); + if (filterType == Durations.daily.value) { + if(isBetweenInclusive(parseDate, currentDate, DateTime.now())) { + key = DateFormat('yyyy-MM-dd HH').format(DateTime.parse(vital.timestamp)); + groupedData.putIfAbsent(key, () => []).add(vital); + }// Group by hour + } else if (filterType == Durations.weekly.value) { + if(isBetweenInclusive(parseDate, currentDate.subtract(Duration(days: 7)), DateTime.now())) { + key = DateFormat('yyyy-MM-dd').format(DateTime.parse(vital.timestamp)); + groupedData.putIfAbsent(key, () => []).add(vital); + + } // Group by day + } else if (filterType == Durations.monthly.value) { + if(isBetweenInclusive(parseDate, currentDate.subtract(Duration(days: 30)), DateTime.now())) { + 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); + } + + +} \ No newline at end of file diff --git a/lib/features/smartwatch_health_data/health_provider.dart b/lib/features/smartwatch_health_data/health_provider.dart index 7a175d8e..905c8d5a 100644 --- a/lib/features/smartwatch_health_data/health_provider.dart +++ b/lib/features/smartwatch_health_data/health_provider.dart @@ -1,13 +1,18 @@ import 'package:flutter/foundation.dart'; import 'package:health/health.dart'; import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/loading_utils.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_service.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import '../../core/common_models/data_points.dart'; import '../../core/dependencies.dart'; +import '../../presentation/smartwatches/activity_detail.dart' show ActivityDetails; import '../../presentation/smartwatches/smart_watch_activity.dart' show SmartWatchActivity; import '../../services/navigation_service.dart' show NavigationService; +import 'HealthDataTransformation.dart'; +import 'model/Vitals.dart'; class HealthProvider with ChangeNotifier { final HealthService _healthService = HealthService(); @@ -19,6 +24,19 @@ class HealthProvider with ChangeNotifier { SmartWatchTypes? selectedWatchType ; String selectedWatchURL = 'assets/images/png/smartwatches/apple-watch-5.jpg'; + HealthDataTransformation healthDataTransformation = HealthDataTransformation(); + + String selectedSection = ""; + Map> daily = {}; + Map> weekly = {}; + Map> monthly = {}; + Map> halgyearly = {}; + Map> yearly = {}; + Map> selectedData = {}; + Durations selectedDuration = Durations.daily; + VitalsWRTType? vitals; + double? averageValue; + String? averageValueString; setSelectedWatchType(SmartWatchTypes type, String imageURL) { selectedWatchType = type; @@ -109,18 +127,167 @@ class HealthProvider with ChangeNotifier { if (result.isError) { error = 'Error initializing device: ${result.asError}'; } else { - getVitals(); + LoaderBottomSheet.hideLoader(); + + LoaderBottomSheet.showLoader(); + await getVitals(); + LoaderBottomSheet.hideLoader(); + await Future.delayed(Duration(seconds: 5)); getIt.get().pushPage(page: SmartWatchActivity()); print('Device initialized successfully'); } notifyListeners(); } - void getVitals() async{ - isLoading = true; - notifyListeners(); + Future getVitals() async { + 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(); + } + + void navigateToDetails(String value, {required String sectionName, required String uom}) { + getIt.get().pushPage(page: ActivityDetails(selectedActivity: value, sectionName:sectionName, uom: uom,)); + } + + void saveSelectedSection(String value) { + // if(selectedSection == value) return; + selectedSection = value; + } + + void deleteDataIfSectionIsDifferent(String value) { + // if(selectedSection == value){ + // return; + // } + daily.clear(); + weekly.clear(); + halgyearly.clear(); + monthly.clear(); + yearly.clear(); + selectedSection = ""; + selectedSection = ""; + averageValue = null; + averageValueString = null; + selectedDuration = Durations.daily; + } + + void fetchData() { + // if(selectedSection == value) return; + mapValuesForFilters(selectedDuration, selectedSection); + getAverageForData(); + transformValueIfSleepIsSelected(); + } + + void setDurations(Durations duration) { + selectedDuration = duration; + } + + void getAverageForData() { + if (selectedData.isEmpty) { + averageValue = 0.0; + notifyListeners(); + return; + } + double total = 0; + int count = 0; + selectedData.forEach((key, dataPoints) { + for (var dataPoint in dataPoints) { + total += num.parse(dataPoint.actualValue).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 dataPoints) { + try { + return dataPoints.firstWhere((dp) => dp.value != null && dp.value!.trim().isNotEmpty).value; + } catch (e) { + return "0"; // no non-empty value found + } + } + + String sumOfNonEmptyData(List list) { + final now = DateTime.now().toLocal(); + final today = DateTime(now.year, now.month, now.day); + + var data = double.parse(list + .where((dp) { + final localDate = DateTime.parse(dp.timestamp); + final normalized = DateTime(localDate.year, localDate.month, localDate.day); + + return normalized.isAtSameMomentAs(today); + }) + .fold("0", (sum, dp) => (double.parse(sum) + double.parse(dp.value)).toString()) + .toString()); + var formattedString = data.toStringAsFixed(2); + + if (formattedString.endsWith('.00')) { + return formattedString.substring(0, formattedString.length - 3); + } + return formattedString; + } } diff --git a/lib/features/smartwatch_health_data/health_service.dart b/lib/features/smartwatch_health_data/health_service.dart index 4d532a5a..1cc42014 100644 --- a/lib/features/smartwatch_health_data/health_service.dart +++ b/lib/features/smartwatch_health_data/health_service.dart @@ -183,31 +183,31 @@ class HealthService { return await watchHelper!.initDevice(); } - FutureOr getVitals() async { + Future getVitals() async { if (watchHelper == null) { print('No watch helper found'); - return; + return null; } try { + await watchHelper!.getActivity(); await watchHelper!.getHeartRate(); await watchHelper!.getSleep(); await watchHelper!.getSteps(); await watchHelper!.getActivity(); await watchHelper!.getBodyTemperature(); + await watchHelper!.getDistance(); await watchHelper!.getBloodOxygen(); Result data = await watchHelper!.retrieveData(); - if(data.isError) { print('Unable to get the data'); } - - var response = jsonDecode(data.asValue?.value?.toString()?.trim().replaceAll("\n", "")??""); VitalsWRTType vitals = VitalsWRTType.fromMap(response); log("the data is ${vitals}"); + return vitals; }catch(e){ print('Error getting heart rate: $e'); } - + return null; } } diff --git a/lib/features/smartwatch_health_data/model/Vitals.dart b/lib/features/smartwatch_health_data/model/Vitals.dart index bbcf1035..cea1df4f 100644 --- a/lib/features/smartwatch_health_data/model/Vitals.dart +++ b/lib/features/smartwatch_health_data/model/Vitals.dart @@ -1,16 +1,19 @@ class Vitals { - final String value; + String value; final String timestamp; + final String unitOfMeasure; Vitals({ required this.value, required this.timestamp, + this.unitOfMeasure = "", }); factory Vitals.fromMap(Map map) { return Vitals( value: map['value'] ?? "", - timestamp: map['timestamp'] ?? "", + timestamp: map['timeStamp'] ?? "", + unitOfMeasure: map['uom'] ?? "", ); } } @@ -19,11 +22,19 @@ class VitalsWRTType { final List heartRate; final List sleep; final List step; + final List distance; final List activity; final List bodyOxygen; final List bodyTemperature; + double maxHeartRate = double.negativeInfinity; + double maxSleep = double.negativeInfinity; + double maxStep= double.negativeInfinity; + double maxActivity = double.negativeInfinity; + double maxBloodOxygen = double.negativeInfinity; + double maxBodyTemperature = double.negativeInfinity; - VitalsWRTType({required this.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 map) { List activity = []; @@ -31,26 +42,63 @@ class VitalsWRTType { List sleeps = []; List heartRate = []; List bodyOxygen = []; + List distance = []; List bodyTemperature = []; 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) { + 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["distance"].forEach((element) { + element["uom"] = "m"; + bodyOxygen.add(Vitals.fromMap(element)); }); map["bodyTemperature"].forEach((element) { + element["uom"] = "C"; 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> getVitals() { + return { + "heartRate": heartRate , + "sleep": sleep, + "steps": step, + "activity": activity, + "bodyOxygen": bodyOxygen, + "bodyTemperature": bodyTemperature, + "distance": distance, + }; } } diff --git a/lib/features/smartwatch_health_data/platform_channel/samsung_platform_channel.dart b/lib/features/smartwatch_health_data/platform_channel/samsung_platform_channel.dart index 1e3ef6df..a36cda39 100644 --- a/lib/features/smartwatch_health_data/platform_channel/samsung_platform_channel.dart +++ b/lib/features/smartwatch_health_data/platform_channel/samsung_platform_channel.dart @@ -1,4 +1,6 @@ +import 'dart:async'; + import 'package:async/async.dart'; import 'package:flutter/services.dart'; class SamsungPlatformChannel { @@ -77,4 +79,12 @@ class SamsungPlatformChannel { return Result.error(e); } } + + Future> getDistance() async { + try{ + return Result.value(await _channel.invokeMethod('distance')); + }catch(e){ + return Result.error(e); + } + } } \ No newline at end of file diff --git a/lib/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart b/lib/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart index 9bc6d84d..72f97b69 100644 --- a/lib/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart +++ b/lib/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart @@ -1,4 +1,5 @@ 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/watch_helper.dart'; @@ -7,6 +8,8 @@ class CreateWatchHelper { switch(watchType){ case SmartWatchTypes.samsung: return SamsungHealth(); + case SmartWatchTypes.huawei: + return HuaweiHealthDataConnector(); default: return SamsungHealth(); } diff --git a/lib/features/smartwatch_health_data/watch_connectors/huawei_watch_connecter.dart b/lib/features/smartwatch_health_data/watch_connectors/huawei_watch_connecter.dart new file mode 100644 index 00000000..a8300e2d --- /dev/null +++ b/lib/features/smartwatch_health_data/watch_connectors/huawei_watch_connecter.dart @@ -0,0 +1,86 @@ +import 'dart:async'; + +import 'package:async/src/result/result.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart'; +import 'package:huawei_health/huawei_health.dart'; + +class HuaweiHealthDataConnector extends WatchHelper{ + final MethodChannel _channel = MethodChannel('huawei_watch'); + @override + Future> initDevice() async{ + try{ + await _channel.invokeMethod('init'); + + }catch(e){ + + } + // List of scopes to ask for authorization. + // Note: These scopes should also be authorized on the Huawei Developer Console. + List scopes = [ + Scope.HEALTHKIT_STEP_READ, Scope.HEALTHKIT_OXYGEN_SATURATION_READ, // View and store height and weight data in Health Service Kit. + Scope.HEALTHKIT_HEARTRATE_READ, Scope.HEALTHKIT_SLEEP_READ, + Scope.HEALTHKIT_BODYTEMPERATURE_READ, Scope.HEALTHKIT_CALORIES_READ + ]; + try { + bool? result = await SettingController.getHealthAppAuthorization(); + debugPrint( + 'Granted Scopes for result == is $result}', + ); + return Result.value(true); + } catch (e) { + debugPrint('Error on authorization, Error:${e.toString()}'); + return Result.error(false); + } + } + + @override + Future getActivity() async { + DataType dataTypeResult = await SettingController.readDataType( + DataType.DT_CONTINUOUS_STEPS_DELTA.name + ); + + + } + + @override + Future getBloodOxygen() { + throw UnimplementedError(); + + } + + @override + Future getBodyTemperature() { + + throw UnimplementedError(); + } + + @override + FutureOr getHeartRate() { + throw UnimplementedError(); + } + + @override + FutureOr getSleep() { + throw UnimplementedError(); + } + + @override + FutureOr getSteps() { + throw UnimplementedError(); + } + + + @override + Future retrieveData() { + throw UnimplementedError(); + } + + @override + FutureOr getDistance() { + // TODO: implement getDistance + throw UnimplementedError(); + } + +} \ No newline at end of file diff --git a/lib/features/smartwatch_health_data/watch_connectors/samsung_health.dart b/lib/features/smartwatch_health_data/watch_connectors/samsung_health.dart index c2e8fd3e..d3cbcfc9 100644 --- a/lib/features/smartwatch_health_data/watch_connectors/samsung_health.dart +++ b/lib/features/smartwatch_health_data/watch_connectors/samsung_health.dart @@ -84,6 +84,15 @@ class SamsungHealth extends WatchHelper { } } + @override + FutureOr getDistance() async{ + try { + return await platformChannel.getDistance(); + }catch(e){ + print('Error getting heart rate: $e'); + } + } + } \ No newline at end of file diff --git a/lib/features/smartwatch_health_data/watch_connectors/watch_helper.dart b/lib/features/smartwatch_health_data/watch_connectors/watch_helper.dart index fe62fdc0..a09064d8 100644 --- a/lib/features/smartwatch_health_data/watch_connectors/watch_helper.dart +++ b/lib/features/smartwatch_health_data/watch_connectors/watch_helper.dart @@ -5,6 +5,7 @@ abstract class WatchHelper { FutureOr getHeartRate(); FutureOr getSleep(); FutureOr getSteps(); + FutureOr getDistance(); Future getActivity(); Future retrieveData(); Future getBodyTemperature(); diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 9aaf1591..00c78721 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -303,11 +303,11 @@ class ServicesPage extends StatelessWidget { true, route: null, onTap: () async { - if (getIt.get().isAuthenticated) { + // if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.smartWatches); - } else { - await getIt.get().onLoginPressed(); - } + // } else { + // await getIt.get().onLoginPressed(); + // } }, // route: AppRoutes.huaweiHealthExample, ), diff --git a/lib/presentation/smartwatches/activity_detail.dart b/lib/presentation/smartwatches/activity_detail.dart index cc006a61..21d168ad 100644 --- a/lib/presentation/smartwatches/activity_detail.dart +++ b/lib/presentation/smartwatches/activity_detail.dart @@ -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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/graph/CustomBarGraph.dart'; import 'package:intl/intl.dart' show DateFormat; +import 'package:provider/provider.dart'; +import 'package:hmg_patient_app_new/features/smartwatch_health_data/HealthDataTransformation.dart' as durations; +import 'package:dartz/dartz.dart' show Tuple2; + +import '../../core/utils/date_util.dart'; class ActivityDetails extends StatefulWidget { final String selectedActivity; + final String sectionName; + final String uom; - const ActivityDetails({super.key, required this.selectedActivity}); + const ActivityDetails({super.key, required this.selectedActivity, required this.sectionName, required this.uom}); @override State createState() => _ActivityDetailsState(); @@ -25,6 +33,11 @@ class ActivityDetails extends StatefulWidget { class _ActivityDetailsState extends State { int index = 0; + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -65,10 +78,24 @@ class _ActivityDetailsState extends State { ], shouldTabExpanded: true, onTabChange: (index) { - //todo handle the selection from viewmodel - setState(() { - this.index = index; - }); + switch (index) { + case 0: + context.read().setDurations(durations.Durations.daily); + break; + case 1: + context.read().setDurations(durations.Durations.weekly); + break; + case 2: + context.read().setDurations(durations.Durations.monthly); + break; + case 3: + context.read().setDurations(durations.Durations.halfYearly); + break; + case 4: + context.read().setDurations(durations.Durations.yearly); + break; + } + context.read().fetchData(); }, ), ), @@ -81,167 +108,235 @@ class _ActivityDetailsState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h, hasShadow: true), child: Column( spacing: 8.h, + crossAxisAlignment: CrossAxisAlignment.start, children: [ 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( - children: ["3232".toText24(color: AppColors.textGreenColor, fontWeight: FontWeight.w600), "(20-21)".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500)], - ) + Selector>( + selector: (_, model) => Tuple2(model.averageValue, model.averageValueString), + builder: (_, data, __) { + var averageAsDouble = data.value1; + var averageAsString = data.value2; + return Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + (averageAsDouble?.toStringAsFixed(2) ?? averageAsString ?? "N/A").toText24(color: AppColors.textGreenColor, fontWeight: FontWeight.w600), + Visibility( + visible: averageAsDouble != null || averageAsString != null, + child: widget.uom.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500) + ) + ], + ); + }) ], ).paddingSymmetrical(16.w, 16.h)); } Widget activityGraph() { - final _random = Random(); - - int randomBP() => 100 + _random.nextInt(51); // 100–150 - final List data6Months = List.generate(6, (index) { - final date = DateTime.now().subtract(Duration(days: 30 * (5 - index))); - - final value = randomBP(); - - return DataPoint( - value: value.toDouble(), - label: value.toString(), - actualValue: value.toString(), - displayTime: DateFormat('MMM').format(date), - unitOfMeasurement: 'mmHg', - time: date, - ); - }); - final List data12Months = List.generate(12, (index) { - final date = DateTime.now().subtract(Duration(days: 30 * (11 - index))); - - final value = randomBP(); - - return DataPoint( - value: value.toDouble(), - label: value.toString(), - actualValue: value.toString(), - displayTime: DateFormat('MMM').format(date), - unitOfMeasurement: 'mmHg', - time: date, - ); - }); - - List data =[]; - if(index == 0){ - data = data6Months; - } else if(index == 1){ - data = data12Months; - } else - data = [ - DataPoint( - value: 128, - label: "128", - actualValue: '128', - displayTime: 'Sun', - unitOfMeasurement: 'mmHg', - time: DateTime.now().subtract(const Duration(days: 6)), - ), - DataPoint( - value: 135, - label: "135", - actualValue: '135', - displayTime: 'Mon', - unitOfMeasurement: 'mmHg', - time: DateTime.now().subtract(const Duration(days: 5)), - ), - DataPoint( - value: 122, - label: "122", - actualValue: '122', - displayTime: 'Tue', - unitOfMeasurement: 'mmHg', - time: DateTime.now().subtract(const Duration(days: 4)), - ), - DataPoint( - value: 140, - label: "140", - actualValue: '140', - displayTime: 'Wed', - unitOfMeasurement: 'mmHg', - time: DateTime.now().subtract(const Duration(days: 3)), - ), - DataPoint( - value: 118, - label: "118", - actualValue: '118', - displayTime: 'Thu', - unitOfMeasurement: 'mmHg', - time: DateTime.now().subtract(const Duration(days: 2)), - ), - DataPoint( - value: 125, - label: "125", - actualValue: '125', - displayTime: 'Fri', - unitOfMeasurement: 'mmHg', - time: DateTime.now().subtract(const Duration(days: 1)), - ), - DataPoint( - value: 130, - label: "130", - actualValue: '130', - displayTime: 'Sat', - unitOfMeasurement: 'mmHg', - time: DateTime.now(), - ), - ]; - - return CustomBarChart( - dataPoints: data, - height: 300.h, - maxY: 150, - barColor: AppColors.bgGreenColor, - barWidth: 26.w, - barRadius: BorderRadius.circular(8), - bottomLabelColor: Colors.black, - bottomLabelSize: 12, - leftLabelInterval: .1, - leftLabelReservedSize: 40, - // Left axis label formatter (Y-axis) - leftLabelFormatter: (value) { - var labelValue = double.tryParse(value.toStringAsFixed(0)); - - if (labelValue == null) return SizedBox.shrink(); - if (labelValue == 0 || labelValue == 150 / 2 || labelValue == 150) { - return Text( - labelValue.toStringAsFixed(0), - style: const TextStyle( - color: Colors.black26, - fontSize: 11, - ), - ); - } + // final _random = Random(); + // + // int randomBP() => 100 + _random.nextInt(51); // 100–150 + // final List data6Months = List.generate(6, (index) { + // final date = DateTime.now().subtract(Duration(days: 30 * (5 - index))); + // + // final value = randomBP(); + // + // return DataPoint( + // value: value.toDouble(), + // label: value.toString(), + // actualValue: value.toString(), + // displayTime: DateFormat('MMM').format(date), + // unitOfMeasurement: 'mmHg', + // time: date, + // ); + // }); + // final List data12Months = List.generate(12, (index) { + // final date = DateTime.now().subtract(Duration(days: 30 * (11 - index))); + // + // final value = randomBP(); + // + // return DataPoint( + // value: value.toDouble(), + // label: value.toString(), + // actualValue: value.toString(), + // displayTime: DateFormat('MMM').format(date), + // unitOfMeasurement: 'mmHg', + // time: date, + // ); + // }); + // + // List data =[]; + // if(index == 0){ + // data = data6Months; + // } else if(index == 1){ + // data = data12Months; + // } else + // data = [ + // DataPoint( + // value: 128, + // label: "128", + // actualValue: '128', + // displayTime: 'Sun', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now().subtract(const Duration(days: 6)), + // ), + // DataPoint( + // value: 135, + // label: "135", + // actualValue: '135', + // displayTime: 'Mon', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now().subtract(const Duration(days: 5)), + // ), + // DataPoint( + // value: 122, + // label: "122", + // actualValue: '122', + // displayTime: 'Tue', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now().subtract(const Duration(days: 4)), + // ), + // DataPoint( + // value: 140, + // label: "140", + // actualValue: '140', + // displayTime: 'Wed', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now().subtract(const Duration(days: 3)), + // ), + // DataPoint( + // value: 118, + // label: "118", + // actualValue: '118', + // displayTime: 'Thu', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now().subtract(const Duration(days: 2)), + // ), + // DataPoint( + // value: 125, + // label: "125", + // actualValue: '125', + // displayTime: 'Fri', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now().subtract(const Duration(days: 1)), + // ), + // DataPoint( + // value: 130, + // label: "130", + // actualValue: '130', + // displayTime: 'Sat', + // unitOfMeasurement: 'mmHg', + // time: DateTime.now(), + // ),23 + // ]; + return Selector>?>( + selector: (_, model) => model.selectedData, + builder: (_, data, __) { + if (context.read().selectedData.values.toList().first?.isEmpty == true) return SizedBox(); - return SizedBox.shrink(); - }, + return CustomBarChart( + dataPoints: context.read().selectedData.values.toList().first, + height: 300.h, + maxY: 150, + barColor: AppColors.bgGreenColor, + barWidth: context.read().selectedData.values.toList().first.length < 10 ? 26.w : 20.w, + barRadius: BorderRadius.circular(8), + bottomLabelColor: Colors.black, + bottomLabelSize: 12, + leftLabelInterval: .1, + leftLabelReservedSize: 20, + // Left axis label formatter (Y-axis) + leftLabelFormatter: (value) { + var labelValue = double.tryParse(value.toStringAsFixed(0)); + + if (labelValue == null) return SizedBox.shrink(); + // if (labelValue == 0 || labelValue == 150 / 2 || labelValue == 150) { + // return Text( + // labelValue.toStringAsFixed(0), + // style: const TextStyle( + // color: Colors.black26, + // fontSize: 11, + // ), + // ); + // } - // Bottom axis label formatter (X-axis - Days) + return SizedBox.shrink(); + }, + + /// for the handling of the sleep time + getTooltipItem: (widget.selectedActivity == "sleep") + ? (data) { + return BarTooltipItem( + '${DateUtil. millisToHourMin(num.parse(data.actualValue).toInt())}\n${DateFormat('dd MMM, yyyy').format(data.time)}', + TextStyle( + color: Colors.black, + fontSize: 12.f, + fontWeight: FontWeight.w500, + ), + ); + } + : null, + + // Bottom axis label formatter (X-axis - Days) bottomLabelFormatter: (value, dataPoints) { final index = value.toInt(); - if (index >= 0 && index < dataPoints.length) { - return Text( - dataPoints[index].displayTime, - style: const TextStyle( - color: Colors.grey, - fontSize: 11, - ), - ); - } - return const Text(''); - }, - verticalInterval: 1/data.length, - getDrawingVerticalLine: (value) { - return FlLine( - color: AppColors.greyColor, - strokeWidth: 1, + print("value is $value"); + print("the index is $index"); + print("the dataPoints.length is ${dataPoints.length}"); + + var bottomText = ""; + var date = dataPoints[index].time; + print("the time is ${date}"); + switch (context.read().selectedDuration) { + case durations.Durations.daily: + bottomText = getHour(date).toString(); + break; + case durations.Durations.weekly: + bottomText = getDayName(date)[0]; + break; + case durations.Durations.monthly: + case durations.Durations.halfYearly: + case durations.Durations.yearly: + bottomText = getMonthName(date)[0]; + } + + return Text( + bottomText, + style: const TextStyle( + color: Colors.grey, + fontSize: 11, + ), + ); + return const Text(''); + }, + verticalInterval: 1 / context.read().selectedData.values.toList().first.length, + getDrawingVerticalLine: (value) { + return FlLine( + color: AppColors.greyColor, + strokeWidth: 1, ); }, - showGridLines: true, - ); + 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); } } diff --git a/lib/presentation/smartwatches/smart_watch_activity.dart b/lib/presentation/smartwatches/smart_watch_activity.dart index 531139b8..cffb802d 100644 --- a/lib/presentation/smartwatches/smart_watch_activity.dart +++ b/lib/presentation/smartwatches/smart_watch_activity.dart @@ -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/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 @@ -22,64 +27,164 @@ class SmartWatchActivity extends StatelessWidget { children: [ resultItem( 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, trailingIcon: AppAssets.watchActivityTrailing, - result: "21", + result: context.read().sumOfNonEmptyData(context.read().vitals?.activity??[]), unitsOfMeasure: "Cal" ).onPress((){ - getIt.get().pushPage(page: ActivityDetails( selectedActivity: "Steps")); + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + + context.read().deleteDataIfSectionIsDifferent("activity"); + context.read().saveSelectedSection("activity"); + context.read().fetchData(); + context.read().navigateToDetails("activity", sectionName:"Activity Calories", uom: "cal"); + }), 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: "21", + result: context.read().sumOfNonEmptyData(context.read().vitals?.step??[]), unitsOfMeasure: "Steps" - ), + ).onPress((){ + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + + context.read().deleteDataIfSectionIsDifferent("steps"); + context.read().saveSelectedSection("steps"); + context.read().fetchData(); + context.read().navigateToDetails("steps", sectionName: "Steps", uom: "Steps"); + + }), resultItem( leadingIcon: AppAssets.watchSteps, - title: "Walking + Running Distance".needTranslation, - description: "This will show you the total distance you covered in a day".needTranslation, + title: "Distance Covered".needTranslation, + description: "Step count is the distance you take throughout the day.".needTranslation, trailingIcon: AppAssets.watchStepsTrailing, - result: "21", - unitsOfMeasure: "km" - ), + result: context.read().sumOfNonEmptyData(context.read().vitals?.distance??[]), + unitsOfMeasure: "Km" + ).onPress((){ + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + + context.read().deleteDataIfSectionIsDifferent("distance"); + context.read().saveSelectedSection("distance"); + context.read().fetchData(); + context.read().navigateToDetails("distance", sectionName: "Distance Covered", uom: "km"); + + }), + resultItem( leadingIcon: AppAssets.watchSleep, title: "Sleep Score".needTranslation, description: "This will keep track of how much hours you sleep in a day".needTranslation, trailingIcon: AppAssets.watchSleepTrailing, - result: "21", + result: DateUtil.millisToHourMin(int.parse(context.read().firstNonEmptyValue(context.read().vitals?.sleep??[]))).split(" ")[0], unitsOfMeasure: "hr", - resultSecondValue: "2", + resultSecondValue: DateUtil.millisToHourMin(int.parse(context.read().firstNonEmptyValue(context.read().vitals?.sleep??[]))).split(" ")[2], unitOfSecondMeasure: "min" - ), - resultItem( - leadingIcon: AppAssets.watchBmi, - title: "Body Mass Index".needTranslation, - description: "This will calculate your weight and height ratio to analyze the ".needTranslation, - trailingIcon: AppAssets.watchBmiTrailing, - result: "21", - unitsOfMeasure: "BMI" - ), + ).onPress((){ + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + + context.read().deleteDataIfSectionIsDifferent("sleep"); + context.read().saveSelectedSection("sleep"); + context.read().fetchData(); + context.read().navigateToDetails("sleep", sectionName:"Sleep Score",uom:""); + + }), + resultItem( leadingIcon: AppAssets.watchWeight, - title: "Weight".needTranslation, - description: "This will calculate your weight to keep track and update history".needTranslation, + title: "Blood Oxygen".needTranslation, + description: "This will calculate your Blood Oxygen to keep track and update history".needTranslation, trailingIcon: AppAssets.watchWeightTrailing, - result: "21", - unitsOfMeasure: "Kg" - ), + result: context.read().firstNonEmptyValue(context.read().vitals?.bodyOxygen??[], ), + unitsOfMeasure: "%" + ).onPress((){ + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + + context.read().deleteDataIfSectionIsDifferent("bodyOxygen"); + context.read().saveSelectedSection("bodyOxygen"); + context.read().fetchData(); + context.read().navigateToDetails("bodyOxygen", uom: "%", sectionName:"Blood Oxygen" ); + + }), resultItem( leadingIcon: AppAssets.watchWeight, - title: "Height".needTranslation, - description: "This will calculate your height to keep track and update history".needTranslation, + title: "Body temperature".needTranslation, + description: "This will calculate your Body temprerature to keep track and update history".needTranslation, trailingIcon: AppAssets.watchWeightTrailing, - result: "21", - unitsOfMeasure: "ft" - ) + result: context.read().firstNonEmptyValue(context.read().vitals?.bodyTemperature??[]), + unitsOfMeasure: "C" + ).onPress((){ + // Map> getVitals() { + // return { + // "heartRate": heartRate , + // "sleep": sleep, + // "steps": step, + // "activity": activity, + // "bodyOxygen": bodyOxygen, + // "bodyTemperature": bodyTemperature, + // }; + // } + context.read().setDurations(durations.Durations.daily); + + context.read().deleteDataIfSectionIsDifferent("bodyTemperature"); + context.read().saveSelectedSection("bodyTemperature"); + context.read().fetchData(); + context.read().navigateToDetails("bodyTemperature" , sectionName: "Body temperature".capitalizeFirstofEach, uom: "C"); + + }), ], ).paddingSymmetrical(24.w, 24.h), )); diff --git a/lib/widgets/graph/CustomBarGraph.dart b/lib/widgets/graph/CustomBarGraph.dart index b90ddb00..bacdf0c7 100644 --- a/lib/widgets/graph/CustomBarGraph.dart +++ b/lib/widgets/graph/CustomBarGraph.dart @@ -63,6 +63,7 @@ class CustomBarChart extends StatelessWidget { 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; @@ -100,7 +101,9 @@ class CustomBarChart extends StatelessWidget { this.verticalInterval, this.minY, this.barRadius, - this.barWidth = 16}); + this.barWidth = 16, + this.getTooltipItem + }); @override Widget build(BuildContext context) { @@ -124,6 +127,10 @@ class CustomBarChart extends StatelessWidget { getTooltipColor: (_)=>AppColors.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( diff --git a/pubspec.yaml b/pubspec.yaml index 19acdf31..17860c67 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -88,7 +88,7 @@ dependencies: location: ^8.0.1 gms_check: ^1.0.4 huawei_location: ^6.14.2+301 - huawei_health: ^6.16.0+300 + huawei_health: ^6.15.0+300 intl: ^0.20.2 flutter_widget_from_html: ^0.17.1 huawei_map: