Skip to content

Commit 564e25b

Browse files
committed
Load the logs from the DB directly
This makes sure the past logs list is updated immediately
1 parent 54e0634 commit 564e25b

10 files changed

Lines changed: 442 additions & 24 deletions

lib/providers/workout_logs_notifier.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,28 @@
1717
*/
1818

1919
import 'package:flutter_riverpod/flutter_riverpod.dart';
20+
import 'package:riverpod_annotation/riverpod_annotation.dart';
2021
import 'package:wger/models/workouts/log.dart';
2122
import 'package:wger/providers/workout_logs_repository.dart';
2223

24+
part 'workout_logs_notifier.g.dart';
25+
2326
final workoutLogProvider = Provider<WorkoutLogMutations>((ref) {
2427
return WorkoutLogMutations(ref.read(workoutLogRepositoryProvider));
2528
});
2629

30+
/// Streams the past logs for [exerciseId] within [routineId], newest first.
31+
@riverpod
32+
Stream<List<Log>> pastExerciseLogs(
33+
Ref ref, {
34+
required int routineId,
35+
required int exerciseId,
36+
}) {
37+
return ref
38+
.read(workoutLogRepositoryProvider)
39+
.watchLogsByExerciseDrift(routineId: routineId, exerciseId: exerciseId);
40+
}
41+
2742
class WorkoutLogMutations {
2843
final WorkoutLogRepository _repo;
2944

lib/providers/workout_logs_notifier.g.dart

Lines changed: 116 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/providers/workout_logs_repository.dart

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* Repository for body weight network operations.
2121
*/
2222

23-
import 'package:drift/drift.dart' show BooleanExpressionOperators;
23+
import 'package:drift/drift.dart';
2424
import 'package:flutter_riverpod/flutter_riverpod.dart';
2525
import 'package:logging/logging.dart';
2626
import 'package:wger/models/workouts/log.dart';
@@ -33,15 +33,58 @@ final workoutLogRepositoryProvider = Provider<WorkoutLogRepository>((ref) {
3333
return WorkoutLogRepository(db);
3434
});
3535

36-
/// Write-side access to the local `workout_log` table. Reading logs happens
37-
/// through `WorkoutSessionRepository.watchAllDrift`, which joins logs onto
38-
/// their parent session.
36+
/// Local access to the `workout_log` table: writes (add/update/delete) plus a
37+
/// per-exercise read stream. Reading logs grouped under their parent session
38+
/// happens through `WorkoutSessionRepository.watchAllDrift`.
3939
class WorkoutLogRepository {
4040
final _logger = Logger('WorkoutLogRepository');
4141
final DriftPowersyncDatabase _db;
4242

4343
WorkoutLogRepository(this._db);
4444

45+
/// Streams the logs for a single exercise within a routine, newest first,
46+
/// with their repetition and weight units attached.
47+
Stream<List<Log>> watchLogsByExerciseDrift({
48+
required int routineId,
49+
required int exerciseId,
50+
}) {
51+
_logger.finer('Watching local logs for routine $routineId, exercise $exerciseId');
52+
53+
final query =
54+
_db.select(_db.workoutLogTable).join([
55+
leftOuterJoin(
56+
_db.routineRepetitionUnitTable,
57+
_db.routineRepetitionUnitTable.id.equalsExp(_db.workoutLogTable.repetitionsUnitId),
58+
),
59+
leftOuterJoin(
60+
_db.routineWeightUnitTable,
61+
_db.routineWeightUnitTable.id.equalsExp(_db.workoutLogTable.weightUnitId),
62+
),
63+
])
64+
..where(
65+
_db.workoutLogTable.routineId.equals(routineId) &
66+
_db.workoutLogTable.exerciseId.equals(exerciseId),
67+
)
68+
..orderBy([
69+
OrderingTerm(expression: _db.workoutLogTable.date, mode: OrderingMode.desc),
70+
]);
71+
72+
return query.watch().map((rows) {
73+
return rows.map((row) {
74+
final log = row.readTable(_db.workoutLogTable);
75+
final repetitionUnit = row.readTableOrNull(_db.routineRepetitionUnitTable);
76+
if (repetitionUnit != null) {
77+
log.repetitionUnit = repetitionUnit;
78+
}
79+
final weightUnit = row.readTableOrNull(_db.routineWeightUnitTable);
80+
if (weightUnit != null) {
81+
log.weightUnit = weightUnit;
82+
}
83+
return log;
84+
}).toList();
85+
});
86+
}
87+
4588
Future<void> deleteLocalDrift(String id) async {
4689
_logger.finer('Deleting local workout log entry $id');
4790
await (_db.delete(_db.workoutLogTable)..where((t) => t.id.equals(id))).go();

lib/widgets/routines/gym_mode/log_page.dart

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import 'package:wger/providers/plate_weights.dart';
3333
import 'package:wger/providers/workout_logs_notifier.dart';
3434
import 'package:wger/screens/settings_plates_screen.dart';
3535
import 'package:wger/widgets/core/core.dart';
36+
import 'package:wger/widgets/core/error.dart';
3637
import 'package:wger/widgets/routines/forms/repetitions.dart';
3738
import 'package:wger/widgets/routines/forms/rir.dart';
3839
import 'package:wger/widgets/routines/forms/weight.dart';
@@ -71,6 +72,15 @@ class LogPage extends ConsumerWidget {
7172
}
7273
final setConfigData = slotEntryPage.setConfigData!;
7374

75+
// Past logs come straight from the local DB (not the gym-mode routine
76+
// snapshot) so a set logged during this workout shows up right away.
77+
final pastLogs = ref.watch(
78+
pastExerciseLogsProvider(
79+
routineId: gymState.routine.id!,
80+
exerciseId: setConfigData.exerciseId,
81+
),
82+
);
83+
7484
// Mark done sets
7585
final decorationStyle = slotEntryPage.logDone
7686
? TextDecoration.lineThrough
@@ -125,13 +135,7 @@ class LogPage extends ConsumerWidget {
125135
if (slotEntryPage.setConfigData!.comment.isNotEmpty)
126136
Text(slotEntryPage.setConfigData!.comment, textAlign: TextAlign.center),
127137
const SizedBox(height: 10),
128-
Expanded(
129-
child: (gymState.routine.filterLogsByExercise(setConfigData.exerciseId).isNotEmpty)
130-
? LogsPastLogsWidget(
131-
pastLogs: gymState.routine.filterLogsByExercise(setConfigData.exerciseId),
132-
)
133-
: Container(),
134-
),
138+
Expanded(child: _buildPastLogs(pastLogs)),
135139

136140
Padding(
137141
padding: const EdgeInsets.all(10),
@@ -152,6 +156,19 @@ class LogPage extends ConsumerWidget {
152156
],
153157
);
154158
}
159+
160+
/// Renders the previous logs for this exercise
161+
Widget _buildPastLogs(AsyncValue<List<Log>> pastLogs) {
162+
if (pastLogs.hasError) {
163+
_logger.warning('Could not load past logs', pastLogs.error, pastLogs.stackTrace);
164+
// Scroll-wrap so the indicator fits this slim slot instead of overflowing.
165+
return SingleChildScrollView(
166+
child: StreamErrorIndicator(pastLogs.error!, stacktrace: pastLogs.stackTrace),
167+
);
168+
}
169+
final logs = pastLogs.value ?? const <Log>[];
170+
return logs.isEmpty ? const SizedBox.shrink() : LogsPastLogsWidget(pastLogs: logs);
171+
}
155172
}
156173

157174
class LogsPlatesWidget extends ConsumerWidget {

test/routine/gym_mode/gym_mode_test.dart

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import 'package:wger/providers/network_provider.dart';
3737
import 'package:wger/providers/routines_notifier.dart';
3838
import 'package:wger/providers/routines_repository.dart';
3939
import 'package:wger/providers/trophy_repository.dart';
40+
import 'package:wger/providers/workout_logs_repository.dart';
4041
import 'package:wger/providers/workout_session_repository.dart';
4142
import 'package:wger/screens/gym_mode.dart';
4243
import 'package:wger/screens/routine_screen.dart';
@@ -54,7 +55,13 @@ import '../../../test_data/routines.dart';
5455
import '../../fake_connectivity.dart';
5556
import 'gym_mode_test.mocks.dart';
5657

57-
@GenerateMocks([WorkoutSessionRepository, ExerciseRepository, RoutinesRepository, TrophyRepository])
58+
@GenerateMocks([
59+
WorkoutSessionRepository,
60+
ExerciseRepository,
61+
RoutinesRepository,
62+
TrophyRepository,
63+
WorkoutLogRepository,
64+
])
5865
void main() {
5966
installFakeConnectivity();
6067

@@ -66,6 +73,7 @@ void main() {
6673
final mockSessionRepo = MockWorkoutSessionRepository();
6774
final mockExerciseRepo = MockExerciseRepository();
6875
final mockRoutinesRepo = MockRoutinesRepository();
76+
final mockLogRepo = MockWorkoutLogRepository();
6977

7078
setUp(() {
7179
SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty();
@@ -89,6 +97,18 @@ void main() {
8997
when(
9098
mockRoutinesRepo.fetchAndSetRoutineFullServer(any),
9199
).thenAnswer((_) async => testRoutine);
100+
101+
// Past logs on the log page come from this stream (per exercise). Reuse the
102+
// test routine's logs so the assertions on previous entries keep working.
103+
when(
104+
mockLogRepo.watchLogsByExerciseDrift(
105+
routineId: anyNamed('routineId'),
106+
exerciseId: anyNamed('exerciseId'),
107+
),
108+
).thenAnswer((invocation) {
109+
final exerciseId = invocation.namedArguments[#exerciseId] as int;
110+
return Stream.value(testRoutine.filterLogsByExercise(exerciseId));
111+
});
92112
});
93113

94114
Widget renderGymMode({
@@ -102,6 +122,7 @@ void main() {
102122
routinesRepositoryProvider.overrideWithValue(mockRoutinesRepo),
103123
exerciseRepositoryProvider.overrideWithValue(mockExerciseRepo),
104124
workoutSessionRepositoryProvider.overrideWithValue(mockSessionRepo),
125+
workoutLogRepositoryProvider.overrideWithValue(mockLogRepo),
105126
...extraOverrides,
106127
// The repetition + weight unit catalogues are tiny direct-Drift
107128
// stream providers, overriding them inline is the established

0 commit comments

Comments
 (0)