feat(train): the Loop's training sessions as workouts (ADR 0030, D39)

Each read of the band lists the sessions it holds, reads those from the
read's first day on (`LoopDays.kt`, one line per exercise: its start
with the band's offset, length, Polar's sport ID, figures and heart
rate at its interval), and deletes those up to the day kept, as the
days. The app keeps each as a workout of origin Polar Loop with its
heart rate (`takeLoopSessions`), Polar's running and cycling as
Train's and every other sport as the new `Activity.other`, which a
workout's edit sheet now changes; a session overlapping a workout Train
recorded gives it its heart rate where it has none instead. The
sessions taken are remembered in `loop.json`, so one deleted in Train
isn't brought back. Health Connect writes the Loop's workouts as
Train's own.

The SDK gives sessions as its protobuf messages: protobuf-javalite
3.20.0, the SDK's own, is named for compiling only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-28 20:22:23 +01:00
commit 2c5467a282
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -517,6 +517,7 @@ void main() {
LoopStore(File('${folder.path}/loop.json')),
profile,
days: days,
workouts: WorkoutRepository(MemoryWorkoutGateway(), keys),
backgroundLines: File('${folder.path}/loop_days.txt'),
);
await start(
@ -1736,6 +1737,26 @@ void main() {
expect(saved.notes, 'Legs heavy.');
});
testWidgets("a workout's activity is changed on its page, as the Loop's "
'come as Other (D39)', (tester) async {
await saveRun(DateTime(2026, 10, 27, 7, 30));
await startWith(tester);
await tester.tap(find.text('Morning run'));
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Edit'));
await tester.pumpAndSettle();
await tester.tap(find.text('Running').last);
await tester.pumpAndSettle();
await tester.tap(find.text('Walking').last);
await tester.pumpAndSettle();
await tester.tap(find.text('Save'));
await tester.pumpAndSettle();
expect(find.text('Morning walk'), findsWidgets);
expect((await workouts.all()).single.content?.activity, Activity.walking);
});
testWidgets('deleting asks first, leaves the list at once and can be '
'undone for a moment', (tester) async {
await saveRun(DateTime(2026, 10, 27, 7, 30));