Skip to content

Commit 8fd67ce

Browse files
authored
Enhance activity list (#74)
This pull request includes several changes to the `lib/data/model/activity.dart` and related files to add new functionality and improve the existing codebase. The most important changes include adding new fields to the `Activity` and `ActivityPreview` classes, updating repository methods to handle these new fields, and modifying the UI to display the new data. ### Enhancements to `Activity` and `ActivityPreview` classes: * [`lib/data/model/activity.dart`](diffhunk://#diff-c46ef297286fd151fb4d7ff8bd3a19deeb179a317d9405054f4fd3ce2efb2dcbL15-R19): Added `steps` and `icon` fields to the `Activity` and `ActivityPreview` classes. Updated constructors and methods to handle these new fields. [[1]](diffhunk://#diff-c46ef297286fd151fb4d7ff8bd3a19deeb179a317d9405054f4fd3ce2efb2dcbL15-R19) [[2]](diffhunk://#diff-c46ef297286fd151fb4d7ff8bd3a19deeb179a317d9405054f4fd3ce2efb2dcbR34-R49) [[3]](diffhunk://#diff-c46ef297286fd151fb4d7ff8bd3a19deeb179a317d9405054f4fd3ce2efb2dcbR61) [[4]](diffhunk://#diff-c46ef297286fd151fb4d7ff8bd3a19deeb179a317d9405054f4fd3ce2efb2dcbR73) ### Repository updates: * [`lib/data/repositories/local_health_impl.dart`](diffhunk://#diff-99442f8cf53d535dc947e240c47d72949d679badbe5c1f253d9d99974446d774L53-R53): Updated methods in `LocalHealthRepoImpl` to handle the new `steps` field and removed unnecessary code. [[1]](diffhunk://#diff-99442f8cf53d535dc947e240c47d72949d679badbe5c1f253d9d99974446d774L53-R53) [[2]](diffhunk://#diff-99442f8cf53d535dc947e240c47d72949d679badbe5c1f253d9d99974446d774L139-R152) [[3]](diffhunk://#diff-99442f8cf53d535dc947e240c47d72949d679badbe5c1f253d9d99974446d774L162-L164) [[4]](diffhunk://#diff-99442f8cf53d535dc947e240c47d72949d679badbe5c1f253d9d99974446d774R183-R189) [[5]](diffhunk://#diff-99442f8cf53d535dc947e240c47d72949d679badbe5c1f253d9d99974446d774R214) [[6]](diffhunk://#diff-99442f8cf53d535dc947e240c47d72949d679badbe5c1f253d9d99974446d774R296) ### UI modifications: * [`lib/presentation/activities/details/screen/activity_details.dart`](diffhunk://#diff-7de7ecaa5049c970d5d1936f0651b260dc61f35c6a4d3b9850684be7e0f41602L4): Removed the `setIcon` method and updated the `_buildHeaderDetails` method to use the new `icon` field from `Activity`. [[1]](diffhunk://#diff-7de7ecaa5049c970d5d1936f0651b260dc61f35c6a4d3b9850684be7e0f41602L4) [[2]](diffhunk://#diff-7de7ecaa5049c970d5d1936f0651b260dc61f35c6a4d3b9850684be7e0f41602L33-L49) [[3]](diffhunk://#diff-7de7ecaa5049c970d5d1936f0651b260dc61f35c6a4d3b9850684be7e0f41602L59) [[4]](diffhunk://#diff-7de7ecaa5049c970d5d1936f0651b260dc61f35c6a4d3b9850684be7e0f41602L92-R73) * [`lib/presentation/activities/details/view_model/activity_detail_state.dart`](diffhunk://#diff-0a5ffcd8a4a95363181af5fa767b76fb921d5bdb7bfaf5a403f44020e53ac87fL1-R32): Removed the `icon` field and updated the `ActivityDetailState` class accordingly. * [`lib/presentation/activities/details/view_model/activity_detail_view_model.dart`](diffhunk://#diff-18cd0d0c1811259fd4ba54fdd013026e4407fcfd8396025bae89e1ebe8e96557L1-R4): Updated the `ActivityDetailedViewModel` to handle the new `steps` field and fetch the workout icon if not provided. [[1]](diffhunk://#diff-18cd0d0c1811259fd4ba54fdd013026e4407fcfd8396025bae89e1ebe8e96557L1-R4) [[2]](diffhunk://#diff-18cd0d0c1811259fd4ba54fdd013026e4407fcfd8396025bae89e1ebe8e96557L27-L46) ### Localization updates: * `lib/l10n/app_de.arb` and `lib/l10n/app_en.arb`: Added new localization strings for activity details. [[1]](diffhunk://#diff-36252c65ab82cbff4774b4983cb9027a2bef4cb738d5ea656c0b903939b3871aR12) [[2]](diffhunk://#diff-9796fde3771f42a3a759ccc941731d83f96037a661e47dde27ce81d3447a69c2R15) ### Miscellaneous changes: * [`lib/presentation/activities/list/screen/activities_screen.dart`](diffhunk://#diff-88f7691689bff6b9ae5559a39398280e2b845d3fe6c13d6a14e5387fb374e83cR6): Added a scroll controller to handle infinite scrolling and updated the UI to display grouped activities with steps count. [[1]](diffhunk://#diff-88f7691689bff6b9ae5559a39398280e2b845d3fe6c13d6a14e5387fb374e83cR6) [[2]](diffhunk://#diff-88f7691689bff6b9ae5559a39398280e2b845d3fe6c13d6a14e5387fb374e83cR24) [[3]](diffhunk://#diff-88f7691689bff6b9ae5559a39398280e2b845d3fe6c13d6a14e5387fb374e83cR34-L33) [[4]](diffhunk://#diff-88f7691689bff6b9ae5559a39398280e2b845d3fe6c13d6a14e5387fb374e83cL44-R79) [[5]](diffhunk://#diff-88f7691689bff6b9ae5559a39398280e2b845d3fe6c13d6a14e5387fb374e83cL70-R147) Closes #60
2 parents 520a61f + 64cffdb commit 8fd67ce

14 files changed

Lines changed: 296 additions & 145 deletions

File tree

lib/data/model/activity.dart

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:typed_data';
2+
13
import 'package:health/health.dart';
24

35
interface class Data<T> {
@@ -11,9 +13,11 @@ interface class Data<T> {
1113
interface class Activity extends ActivityPreview {
1214
final List<Data<int>>? heartRates;
1315
final List<Data<double>>? speed;
16+
final int steps;
1417

15-
const Activity({
18+
Activity({
1619
required super.distance,
20+
required this.steps,
1721
required super.activityType,
1822
required super.caloriesBurnt,
1923
required super.start,
@@ -31,14 +35,17 @@ interface class ActivityPreview {
3135
final DateTime start;
3236
final DateTime end;
3337
final String sourceId;
38+
Uint8List? icon;
3439

35-
const ActivityPreview(
36-
{required this.activityType,
37-
required this.caloriesBurnt,
38-
required this.distance,
39-
required this.start,
40-
required this.end,
41-
required this.sourceId});
40+
ActivityPreview({
41+
required this.activityType,
42+
required this.caloriesBurnt,
43+
required this.distance,
44+
required this.start,
45+
required this.end,
46+
required this.sourceId,
47+
Uint8List? icon,
48+
});
4249

4350
int getDuration() {
4451
return start.difference(end).inSeconds.abs();

lib/data/repositories/local_health_impl.dart

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ interface class LocalHealthRepoImpl extends LocalHealthRepository {
5050
/// Get the total distance covered in the given interval
5151
@override
5252
Future<double> getDistanceInInterval(DateTime start, DateTime end) async {
53-
var distanceTypes;
53+
List<HealthDataType> distanceTypes;
5454
if (Platform.isAndroid) {
5555
distanceTypes = [HealthDataType.DISTANCE_DELTA];
5656
} else if (Platform.isIOS) {
@@ -136,16 +136,19 @@ interface class LocalHealthRepoImpl extends LocalHealthRepository {
136136
for (var i = 0; i < rawWorkouts!.length; i++) {
137137
var current = rawWorkouts[i];
138138
WorkoutHealthValue healthValue = current.value as WorkoutHealthValue;
139-
var calories =
140-
await getCaloriesBurnedInInterval(current.dateFrom, current.dateTo);
139+
140+
var kilometres = healthValue.totalDistance != null
141+
? (healthValue.totalDistance! / 1000)
142+
: 0.0;
143+
// Round kilometres to 2 decimal places
144+
kilometres = (((kilometres * 100).toInt()) / 100).toDouble();
145+
141146
ActivityPreview preview = ActivityPreview(
142147
activityType: healthValue.workoutActivityType,
143-
caloriesBurnt: calories,
148+
caloriesBurnt: healthValue.totalEnergyBurned ?? 0,
144149
start: current.dateFrom,
145150
end: current.dateTo,
146-
distance: healthValue.totalDistance != null
147-
? (healthValue.totalDistance! / 1000).toDouble()
148-
: 0.0,
151+
distance: kilometres,
149152
sourceId: current.sourceName);
150153
parsedWorkouts.add(preview);
151154
}
@@ -159,9 +162,6 @@ interface class LocalHealthRepoImpl extends LocalHealthRepository {
159162
@override
160163
Future<Activity?> getActivityDetailed(ActivityPreview preview) async {
161164
try {
162-
List<HealthDataPoint>? data = await getHealthDataInInterval(
163-
preview.start, preview.end, [HealthDataType.WORKOUT]);
164-
165165
List<HealthDataPoint>? heartFrequency = await getHealthDataInInterval(
166166
preview.start, preview.end, [HealthDataType.HEART_RATE]);
167167
if (heartFrequency == null) {
@@ -177,13 +177,15 @@ interface class LocalHealthRepoImpl extends LocalHealthRepository {
177177
}
178178

179179
return Activity(
180-
caloriesBurnt: preview.caloriesBurnt,
181-
distance: preview.distance,
182-
end: preview.end,
183-
start: preview.start,
184-
activityType: preview.activityType,
185-
heartRates: heartRates,
186-
sourceId: preview.sourceId);
180+
caloriesBurnt: preview.caloriesBurnt,
181+
distance: preview.distance,
182+
steps: await getStepsInInterval(preview.start, preview.end),
183+
end: preview.end,
184+
start: preview.start,
185+
activityType: preview.activityType,
186+
heartRates: heartRates,
187+
sourceId: preview.sourceId,
188+
);
187189
} catch (e) {
188190
log.info(e);
189191
return null;

lib/l10n/app_de.arb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
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",
1213
"activity_distance": "Distanz",
1314
"activity_duration": "Dauer",
1415
"activity_steps": "Schritte",

lib/l10n/app_en.arb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
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",
1516
"activity_distance": "Distance",
1617
"activity_steps": "Steps",
1718
"activity_duration": "Duration",

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

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_hooks/flutter_hooks.dart';
33
import 'package:hooks_riverpod/hooks_riverpod.dart';
4-
import 'package:installed_apps/installed_apps.dart';
54
import 'package:logging/logging.dart';
65
import 'package:movetopia/core/health_authorized_view_model.dart';
76
import 'package:movetopia/data/model/activity.dart';
@@ -30,23 +29,6 @@ class ActivityDetailsScreen extends HookConsumerWidget {
3029
.fetchActivityDetailed(activityPreview);
3130
}
3231

33-
Future<void> setIcon() async {
34-
if (activityPreview.sourceId.isNotEmpty) {
35-
try {
36-
var appIcon = (await InstalledApps.getAppInfo(
37-
activityPreview.sourceId.toString()))
38-
?.icon;
39-
if (appIcon != null && appIcon.isNotEmpty) {
40-
ref
41-
.read(activityDetailedViewModelProvider.notifier)
42-
.setIcon(appIcon);
43-
}
44-
} catch (_) {
45-
log.info("App icon fetching failed");
46-
}
47-
}
48-
}
49-
5032
useEffect(() {
5133
Future(
5234
() async {
@@ -56,7 +38,6 @@ class ActivityDetailsScreen extends HookConsumerWidget {
5638
if (authState == HealthAuthViewModelState.authorized) {
5739
await fetchDetailedActivity();
5840
}
59-
await setIcon();
6041

6142
activityDetailNotifier.setLoading(false);
6243
return null;
@@ -89,7 +70,7 @@ class ActivityDetailsScreen extends HookConsumerWidget {
8970
Widget _buildHeaderDetails(
9071
BuildContext context, ActivityDetailState activityState) {
9172
return HeaderDetails(
92-
platformIcon: activityState.icon,
73+
platformIcon: activityState.activity.icon,
9374
title: getTranslatedActivityType(
9475
context, activityState.activity.activityType),
9576
start: activityState.activity.start,

lib/presentation/activities/details/view_model/activity_detail_state.dart

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,35 @@
1-
import 'dart:typed_data';
2-
31
import 'package:health/health.dart';
42
import 'package:logging/logging.dart';
53
import 'package:movetopia/data/model/activity.dart';
64

75
class ActivityDetailState {
86
Activity activity;
9-
Uint8List icon;
107
bool isLoading;
118
final log = Logger('activityDetailedState');
129

13-
ActivityDetailState(
14-
{required this.activity, required this.isLoading, required this.icon});
10+
ActivityDetailState({required this.activity, required this.isLoading});
1511

1612
factory ActivityDetailState.initial() {
1713
return ActivityDetailState(
18-
activity: Activity(
19-
distance: 0,
20-
activityType: HealthWorkoutActivityType.OTHER,
21-
caloriesBurnt: 0,
22-
start: DateTime.now(),
23-
end: DateTime.now(),
24-
heartRates: [],
25-
speed: [],
26-
sourceId: ""),
27-
isLoading: true,
28-
icon: Uint8List(0));
14+
activity: Activity(
15+
distance: 0,
16+
activityType: HealthWorkoutActivityType.OTHER,
17+
caloriesBurnt: 0,
18+
steps: 0,
19+
start: DateTime.now(),
20+
end: DateTime.now(),
21+
heartRates: [],
22+
speed: [],
23+
sourceId: "",
24+
),
25+
isLoading: true,
26+
);
2927
}
3028

31-
ActivityDetailState copyWith(
32-
{Activity? newActivity, bool? loading, Uint8List? newIcon}) {
29+
ActivityDetailState copyWith({Activity? newActivity, bool? loading}) {
3330
//if (newIcon == null && icon != null) newIcon = icon;
3431
return ActivityDetailState(
35-
activity: newActivity ?? activity,
36-
isLoading: loading ?? isLoading,
37-
icon: newIcon ?? icon);
32+
activity: newActivity ?? activity, isLoading: loading ?? isLoading);
3833
}
3934

4035
int getAverageHeartBeat() {
Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import 'dart:typed_data';
2-
31
import 'package:logging/logging.dart';
42
import 'package:movetopia/data/model/activity.dart';
53
import 'package:movetopia/data/repositories/local_health_impl.dart';
4+
import 'package:movetopia/utils/system_utils.dart';
65
import 'package:riverpod/riverpod.dart';
76

87
import 'activity_detail_state.dart';
@@ -24,24 +23,27 @@ class ActivityDetailedViewModel extends StateNotifier<ActivityDetailState> {
2423
.read(localHealthRepositoryProvider)
2524
.getActivityDetailed(preview);
2625
if (activityDetailed != null) {
26+
var activity = Activity(
27+
distance: activityDetailed.distance,
28+
steps: activityDetailed.steps,
29+
activityType: activityDetailed.activityType,
30+
caloriesBurnt: activityDetailed.caloriesBurnt,
31+
start: activityDetailed.start,
32+
end: activityDetailed.end,
33+
heartRates: activityDetailed.heartRates,
34+
speed: activityDetailed.speed,
35+
// icon: preview.icon,
36+
sourceId: activityDetailed.sourceId);
37+
activity.icon = preview.icon != null && preview.icon!.isNotEmpty
38+
? preview.icon
39+
: await getInstalledAppIcon(preview.sourceId);
2740
state = state.copyWith(
28-
newActivity: Activity(
29-
distance: activityDetailed.distance,
30-
activityType: activityDetailed.activityType,
31-
caloriesBurnt: activityDetailed.caloriesBurnt,
32-
start: activityDetailed.start,
33-
end: activityDetailed.end,
34-
heartRates: activityDetailed.heartRates,
35-
speed: activityDetailed.speed,
36-
sourceId: activityDetailed.sourceId));
41+
newActivity: activity,
42+
);
3743
}
3844
}
3945

4046
void setLoading(bool loading) {
4147
state = state.copyWith(loading: loading);
4248
}
43-
44-
void setIcon(Uint8List? icon) {
45-
state = state.copyWith(newIcon: icon);
46-
}
4749
}

0 commit comments

Comments
 (0)