Skip to content

Commit e7a00d5

Browse files
authored
Feat/enhance activity details (#88)
# 🚀 Pull Request ## Brief Description This PR enhances the ui of the activity details ## Linked Issues <!-- Link related issues with the format: Fixes #123, Resolves #456, Closes #1337 --> - closes #61 - closes #69 ## Screenshots <!-- Add screenshots if UI changes were made. Either new screens only or before/after --> |Activity Details | | |----------| ---------| |![image](https://github.com/user-attachments/assets/21ffc913-3b32-4a78-bf80-dbe9cf8b3aa9) | | ## GitHub Copilot Text This pull request includes significant updates to the health tracking functionality, focusing on enhancements to data processing, user interface improvements, and localization updates. ### Data Processing Improvements: * Updated `BaseLocalHealthRepoImpl` to handle `DISTANCE_DELTA` type in addition to `WORKOUT` type for prioritizing data from Movetopia. * Fixed calculation of `totalDistance` by ensuring proper type conversion in `BaseLocalHealthRepoImpl`. * Replaced direct distance calculations with the `metresToKilometres` utility function in `HealthServiceImpl`. [[1]](diffhunk://#diff-3e53fc5b1dfa9fdd52f251a640e34d6e0d9885b33745f4471df5785f1d0dabe0L213-R219) [[2]](diffhunk://#diff-3e53fc5b1dfa9fdd52f251a640e34d6e0d9885b33745f4471df5785f1d0dabe0L276-R276) ### User Interface Enhancements: * Added `HearthFrequencyGraph` to visualize heart rate data in `ActivityDetailsScreen`. [[1]](diffhunk://#diff-7de7ecaa5049c970d5d1936f0651b260dc61f35c6a4d3b9850684be7e0f41602R66-R68) [[2]](diffhunk://#diff-7de7ecaa5049c970d5d1936f0651b260dc61f35c6a4d3b9850684be7e0f41602R91-R103) * Created a generic `GraphVisualization` widget to support various types of data visualizations. * Updated `WorkoutDetails` to display additional metrics such as steps and distance. [[1]](diffhunk://#diff-06dd21a1c7a21a0959b7a22bb6a06f7fd84693be4ab00873b94d012eb0297f42L2-R20) [[2]](diffhunk://#diff-06dd21a1c7a21a0959b7a22bb6a06f7fd84693be4ab00873b94d012eb0297f42L32-R56) ### Localization Updates: * Added new localization strings for activity duration, heart frequency, and time in both German and English localization files. [[1]](diffhunk://#diff-36252c65ab82cbff4774b4983cb9027a2bef4cb738d5ea656c0b903939b3871aR14-R19) [[2]](diffhunk://#diff-9796fde3771f42a3a759ccc941731d83f96037a661e47dde27ce81d3447a69c2R17-R21) ### Code and Dependency Updates: * Imported necessary utility functions and updated dependencies in various files to support new functionality. [[1]](diffhunk://#diff-3e53fc5b1dfa9fdd52f251a640e34d6e0d9885b33745f4471df5785f1d0dabe0R13) [[2]](diffhunk://#diff-7de7ecaa5049c970d5d1936f0651b260dc61f35c6a4d3b9850684be7e0f41602R7) * Simplified cache invalidation logic in `HealthServiceImpl`. These changes collectively enhance the application's ability to process and display health data more accurately and comprehensively.
2 parents 22540d1 + 35406bf commit e7a00d5

13 files changed

Lines changed: 522 additions & 48 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import 'package:flutter/material.dart';
2+
3+
enum HeartFrequencyZone {
4+
light(60, 100, Colors.green),
5+
moderate(100, 120, Colors.lightGreen),
6+
cardio(120, 140, Colors.yellow),
7+
intense(140, 160, Colors.orange),
8+
peak(160, 220, Colors.red);
9+
10+
final int lowerBound;
11+
final int upperBound;
12+
final Color color;
13+
14+
const HeartFrequencyZone(this.lowerBound, this.upperBound, this.color);
15+
16+
factory HeartFrequencyZone.fromBpm(int bpm) {
17+
for (var zone in HeartFrequencyZone.values) {
18+
if (bpm >= zone.lowerBound && bpm < zone.upperBound) {
19+
return zone;
20+
}
21+
}
22+
return HeartFrequencyZone.peak; // Default for very high values
23+
}
24+
}

lib/data/repositories/base_local_health_impl.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ interface class BaseLocalHealthRepoImpl extends BaseLocalHealthRepository {
6363

6464
// If this is from movetopia, prioritize it
6565
if (point.sourceName.startsWith('de.movetopia') &&
66-
point.type == HealthDataType.WORKOUT) {
66+
(point.type == HealthDataType.WORKOUT ||
67+
point.type == HealthDataType.DISTANCE_DELTA)) {
6768
nonOverlapping[i] = point;
6869
break;
6970
}
@@ -152,7 +153,7 @@ interface class BaseLocalHealthRepoImpl extends BaseLocalHealthRepository {
152153
(element) => element.name == preview.activityType?.name),
153154
start: startTime,
154155
end: endTime,
155-
totalDistance: (preview.distance ?? 0).toInt() * 1000,
156+
totalDistance: ((preview.distance ?? 0) * 1000).toInt(),
156157
totalDistanceUnit: HealthDataUnit.METER);
157158
} catch (e) {
158159
log.info(e);

lib/data/service/health_service_impl.dart

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:movetopia/data/repositories/base_local_health_impl.dart';
1010
import 'package:movetopia/domain/repositories/local_health.dart';
1111
import 'package:movetopia/domain/service/health_service.dart';
1212
import 'package:movetopia/utils/time_range.dart';
13+
import 'package:movetopia/utils/unit_utils.dart';
1314

1415
final healthService = Provider<HealthServiceImpl>((ref) {
1516
// Get the base implementation
@@ -210,18 +211,12 @@ class HealthServiceImpl implements HealthService {
210211
var current = rawWorkouts[i];
211212
WorkoutHealthValue healthValue = current.value as WorkoutHealthValue;
212213

213-
var kilometres = healthValue.totalDistance != null
214-
? (healthValue.totalDistance! / 1000)
215-
: 0.0;
216-
// Round kilometres to 2 decimal places
217-
kilometres = (((kilometres * 100).toInt()) / 100).toDouble();
218-
219214
ActivityPreview preview = ActivityPreview(
220215
activityType: healthValue.workoutActivityType,
221216
caloriesBurnt: healthValue.totalEnergyBurned ?? 0,
222217
start: current.dateFrom,
223218
end: current.dateTo,
224-
distance: kilometres,
219+
distance: metresToKilometres(healthValue.totalDistance),
225220
sourceId: current.sourceName);
226221
parsedWorkouts.add(preview);
227222
}
@@ -273,14 +268,12 @@ class HealthServiceImpl implements HealthService {
273268
var lastWorkout = workouts.last;
274269
WorkoutHealthValue healthValue =
275270
lastWorkout.value as WorkoutHealthValue;
276-
int calories = await getCaloriesBurnedInInterval(
277-
lastWorkout.dateFrom, lastWorkout.dateTo);
278271
return ActivityPreview(
279272
activityType: healthValue.workoutActivityType,
280-
caloriesBurnt: calories,
273+
caloriesBurnt: healthValue.totalEnergyBurned ?? 0,
281274
start: lastWorkout.dateFrom,
282275
end: lastWorkout.dateTo,
283-
distance: (healthValue.totalDistance ?? 0).toDouble(),
276+
distance: metresToKilometres(healthValue.totalDistance),
284277
sourceId: lastWorkout.sourceName);
285278
}
286279
} catch (e) {
@@ -322,11 +315,7 @@ class HealthServiceImpl implements HealthService {
322315

323316
void _invalidateRelatedCaches(DateTime start, DateTime end) {
324317
final keysToInvalidate = _cacheTimestamps.keys.where((key) {
325-
return key.contains('getActivities') ||
326-
key.contains('getLastActivity') ||
327-
key.contains('getStepsInInterval') ||
328-
key.contains('getDistanceInInterval') ||
329-
key.contains('getCaloriesBurnedInInterval');
318+
return key.contains('getHealthDataInterval');
330319
}).toList();
331320

332321
for (final key in keysToInvalidate) {

lib/l10n/app_de.arb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,19 @@
99
"activity_no_activities_found": "Keine Aktivitäten gefunden",
1010
"activity_last_activity": "Letzte Aktivität",
1111
"activity_details": "{distance}km in {minutes} Minuten",
12-
"activity_details_minutes": "{minutes} Minuten",
12+
"activities_minutes": "{minutes} Minuten",
13+
"activities_minute": "{minutes} Minute",
14+
"activities_hours": "{hours} Stunden",
15+
"activities_hour": "{hours} Stunde",
16+
"activities_hours_minutes": "{hours} Stunden {minutes} Minuten",
1317
"activity_distance": "Distanz",
18+
"activity_duration": "Dauer",
19+
"activity_duration_text_with_hours": "{hours} Std. {minutes} Min. {seconds} Sek.",
20+
"activity_duration_text": "{minutes} Min. {seconds} Sek.",
1421
"activity_steps": "Schritte",
1522
"activity_duration": "Dauer",
23+
"activity_heart_frequency": "Herzfrequenz",
24+
"activity_time": "Zeit",
1625
"activity_current_speed": "Geschwindigkeit",
1726
"activity_current_pace": "Tempo",
1827
"activity_avg_speed": "Durchschnittliche Geschwindigkeit",
@@ -355,7 +364,6 @@
355364
"requesting_health_historical_permissions": "Zugriff auf historische Daten wird angefordert...",
356365
"health_historical_granted": "Zugriff auf historische Gesundheitsdaten gewährt",
357366
"health_historical_denied": "Zugriff auf historische Gesundheitsdaten nicht gewährt",
358-
359367
"logs_title": "App-Protokolle",
360368
"logs_clear_tooltip": "Logs löschen",
361369
"logs_copy_tooltip": "Logs in Zwischenablage kopieren",

lib/l10n/app_en.arb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,18 @@
1212
"activity_no_activities_found": "No activities found",
1313
"activity_last_activity": "Last Activity",
1414
"activity_details": "{distance}km in {minutes} minutes",
15-
"activity_details_minutes": "{minutes} minutes",
15+
"activities_minutes": "{minutes} Minuten",
16+
"activities_minute": "{minutes} Minute",
17+
"activities_hours": "{hours} Stunden",
18+
"activities_hour": "{hours} Stunde",
19+
"activities_hours_minutes": "{hours} hours {minutes} minutes",
1620
"activity_distance": "Distance",
21+
"activity_heart_frequency": "Heart Rate",
1722
"activity_steps": "Steps",
23+
"activity_time": "Time",
1824
"activity_duration": "Duration",
25+
"activity_duration_text_with_hours": "{hours} h. {minutes} min. {seconds} sec.",
26+
"activity_duration_text": "{minutes} min. {seconds} sec.",
1927
"activity_current_speed": "Speed",
2028
"activity_current_pace": "Pace",
2129
"activity_avg_speed": "Average Speed",

lib/presentation/activities/details/screen/activity_details.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
44
import 'package:logging/logging.dart';
55
import 'package:movetopia/core/health_authorized_view_model.dart';
66
import 'package:movetopia/data/model/activity.dart';
7+
import 'package:movetopia/presentation/activities/details/widget/hearth_frequency_graph.dart';
78
import 'package:movetopia/utils/health_utils.dart';
89

910
import '../view_model/activity_detail_state.dart';
@@ -62,6 +63,9 @@ class ActivityDetailsScreen extends HookConsumerWidget {
6263
children: [
6364
_buildHeaderDetails(context, activityState),
6465
_buildWorkoutDetails(activityState),
66+
if (activityState.activity.heartRates != null &&
67+
activityState.activity.heartRates!.isNotEmpty)
68+
_buildGraphDetails(context, activityState),
6569
],
6670
),
6771
);
@@ -84,6 +88,19 @@ class ActivityDetailsScreen extends HookConsumerWidget {
8488
averageHeartBeat: activityState.getAverageHeartBeat(),
8589
duration: activityPreview.getDuration(),
8690
caloriesBurnt: activityPreview.caloriesBurnt,
91+
steps: activityState.activity.steps,
92+
distance: activityState.activity.distance,
93+
);
94+
}
95+
96+
Widget _buildGraphDetails(
97+
BuildContext context, ActivityDetailState activityState) {
98+
var start = activityState.activity.start;
99+
var end = activityState.activity.end;
100+
return HearthFrequencyGraph(
101+
data: activityState.activity.heartRates ?? [],
102+
activityEndTime: end,
103+
activityStartTime: start,
87104
);
88105
}
89106
}

0 commit comments

Comments
 (0)