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:
parent
06cc39025b
commit
2c5467a282
1 changed files with 794 additions and 32 deletions
|
|
@ -11,11 +11,14 @@ import 'package:halkyon_train_flutter/src/feature/loop/model/loop_figures.dart';
|
|||
import 'package:halkyon_train_flutter/src/feature/profile/data/profile_repository.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/profile/model/profile.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/recording/data/recorder_platform.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/data/workout_repository.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/model/workout.dart';
|
||||
|
||||
import '../../support/fake_loop_platform.dart';
|
||||
import '../../support/memory_content_key_gateway.dart';
|
||||
import '../../support/memory_day_gateway.dart';
|
||||
import '../../support/memory_profile_gateway.dart';
|
||||
import '../../support/memory_workout_gateway.dart';
|
||||
|
||||
/// The person's Loop on this phone (ADR 0030, D34, D35): set up with their
|
||||
/// profile, remembered, connected while the app is open, its clock set at
|
||||
|
|
@ -38,6 +41,7 @@ void main() {
|
|||
late TrainKeys keys;
|
||||
late ProfileRepository profile;
|
||||
late DayRepository days;
|
||||
late WorkoutRepository workouts;
|
||||
late FakeLoopPlatform platform;
|
||||
var now = DateTime.utc(2026, 10, 28, 9);
|
||||
final opened = <PolarLoop>[];
|
||||
|
|
@ -62,6 +66,7 @@ void main() {
|
|||
store(),
|
||||
profile,
|
||||
days: days,
|
||||
workouts: workouts,
|
||||
backgroundLines: File('${folder.path}/loop_days.txt'),
|
||||
today: () => today,
|
||||
now: () => now,
|
||||
|
|
@ -90,6 +95,7 @@ void main() {
|
|||
);
|
||||
await profile.save(body);
|
||||
days = DayRepository(MemoryDayGateway(), keys, person: 'person');
|
||||
workouts = WorkoutRepository(MemoryWorkoutGateway(), keys);
|
||||
now = DateTime.utc(2026, 10, 28, 9);
|
||||
platform = FakeLoopPlatform()..inReach.add('A1');
|
||||
});
|
||||
|
|
@ -101,6 +107,7 @@ void main() {
|
|||
opened.clear();
|
||||
await profile.dispose();
|
||||
await days.dispose();
|
||||
await workouts.dispose();
|
||||
keys.dispose();
|
||||
folder.deleteSync(recursive: true);
|
||||
});
|
||||
|
|
@ -378,6 +385,39 @@ void main() {
|
|||
expect(loop.status.loop?.keptUntil, before?.keptUntil);
|
||||
});
|
||||
|
||||
test('a session the band recorded becomes a workout once, and is '
|
||||
'remembered, so that a read again brings it no more (D39)', () async {
|
||||
final line = {
|
||||
'kind': 'session',
|
||||
'date': '2026-10-27',
|
||||
'id': '/U/0/20261027/E/173000#0',
|
||||
'start': '2026-10-27T17:30+01:00',
|
||||
'seconds': 2700,
|
||||
'sport': 1,
|
||||
'interval': 1,
|
||||
'heart': [120, 125],
|
||||
};
|
||||
platform.days.addAll([
|
||||
[line],
|
||||
[line],
|
||||
]);
|
||||
final loop = await setUpBand();
|
||||
await settled(loop);
|
||||
final [workout] = await workouts.all();
|
||||
expect(workout.content?.activity, Activity.running);
|
||||
expect(loop.status.loop?.takenSessions.keys, [
|
||||
'/U/0/20261027/E/173000#0',
|
||||
]);
|
||||
|
||||
await workouts.delete(workout.id);
|
||||
await loop.readDays();
|
||||
|
||||
expect(await workouts.all(), isEmpty);
|
||||
expect(store().read()?.takenSessions, {
|
||||
'/U/0/20261027/E/173000#0': DateTime.utc(2026, 10, 27),
|
||||
});
|
||||
});
|
||||
|
||||
test('letting the band go stops the background job', () async {
|
||||
final loop = await setUpBand();
|
||||
await settled(loop);
|
||||
|
|
|
|||
Loading…
Reference in a new issue