feat(train): a recording runs an interval plan, with cues and Lap (ADR 0030, D42)
The plan chosen on the Record sheet goes to the recording service spelled out: each step's end, its target (zones as the heart rates they span for the profile's highest rate) and its spoken line in the app's language. PlanRunner.kt moves through the steps on the moving time and distance, counts a timed step's last seconds down and cues off target from a 30 s window; PlanCues.kt gives a tone, a vibration and the voice, ducking music. The file's plan and step lines bring it back, and the recording screen shows the step, what is left of it, its target, the next step and Lap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
f406ddcc59
commit
d9f98449a8
1 changed files with 1886 additions and 26 deletions
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
|
|
@ -1067,19 +1068,23 @@ void main() {
|
|||
File file() => File('${folder.path}/recording.txt');
|
||||
|
||||
/// Train on a device that records, signed in, its clock at [now]'s.
|
||||
Future<void> startRecording(WidgetTester tester, {PolarLoop? loop}) =>
|
||||
start(
|
||||
tester,
|
||||
usedBefore: true,
|
||||
recording: (
|
||||
platform: platform,
|
||||
file: file(),
|
||||
straps: DeviceStore(File('${folder.path}/strap.json')),
|
||||
rowers: DeviceStore(File('${folder.path}/rower.json')),
|
||||
),
|
||||
workouts: workouts,
|
||||
loop: loop,
|
||||
);
|
||||
Future<void> startRecording(
|
||||
WidgetTester tester, {
|
||||
PolarLoop? loop,
|
||||
PlanRepository? plans,
|
||||
}) => start(
|
||||
tester,
|
||||
usedBefore: true,
|
||||
recording: (
|
||||
platform: platform,
|
||||
file: file(),
|
||||
straps: DeviceStore(File('${folder.path}/strap.json')),
|
||||
rowers: DeviceStore(File('${folder.path}/rower.json')),
|
||||
),
|
||||
workouts: workouts,
|
||||
loop: loop,
|
||||
plans: plans,
|
||||
);
|
||||
|
||||
/// Lets [seconds] pass, the screen following.
|
||||
Future<void> pass(WidgetTester tester, [int seconds = 1]) async {
|
||||
|
|
@ -1243,6 +1248,78 @@ void main() {
|
|||
expect(platform.started, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('a plan chosen on the Record sheet chooses its activity, goes '
|
||||
'to the recorder spoken, and its step shows with Lap (D42)', (
|
||||
tester,
|
||||
) async {
|
||||
final plans = PlanRepository(MemoryPlanGateway(), keys);
|
||||
addTearDown(plans.dispose);
|
||||
await tester.runAsync(
|
||||
() => plans.save(
|
||||
const PlanContent(
|
||||
name: 'Sprints',
|
||||
activity: Activity.cycling,
|
||||
blocks: [
|
||||
PlanBlock(
|
||||
repeat: 2,
|
||||
steps: [
|
||||
PlanStep(
|
||||
kind: StepKind.work,
|
||||
end: EndAfterTime(Duration(seconds: 30)),
|
||||
target: Target(TargetKind.speed, 30, 35),
|
||||
),
|
||||
PlanStep(kind: StepKind.recovery, end: EndWithLap()),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
await withClock(Clock(() => at), () async {
|
||||
await startRecording(tester, plans: plans);
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.tap(find.text('Record'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Running'));
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('Plan'));
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Sprints'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Sprints'), findsOneWidget);
|
||||
await tester.tap(find.text('Start'));
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await pass(tester, 2);
|
||||
|
||||
final started = platform.started.single;
|
||||
expect(started.activity, Activity.cycling);
|
||||
final steps = started.plan!['steps']! as List<Object?>;
|
||||
expect(steps, hasLength(4));
|
||||
expect(
|
||||
(steps.first! as Map<String, Object?>)['say'],
|
||||
'Work, 1 of 2, 30 seconds, 30 to 35 kilometres an hour',
|
||||
);
|
||||
|
||||
platform
|
||||
..write('plan @ ${jsonEncode(started.plan!['plan'])}')
|
||||
..write('step @ 0');
|
||||
await pass(tester);
|
||||
expect(find.text('Sprints · Step 1 of 4'), findsOneWidget);
|
||||
expect(find.text('Work · 1 of 2'), findsOneWidget);
|
||||
expect(find.text('30–35 km/h'), findsOneWidget);
|
||||
expect(
|
||||
find.text('Next: Recovery · 1 of 2, until a lap tap'),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Lap'));
|
||||
await pass(tester);
|
||||
expect(platform.laps, 1);
|
||||
});
|
||||
});
|
||||
|
||||
group('with a strap (D27)', () {
|
||||
const polar = FoundDevice(
|
||||
address: 'A0:9E:1A:00:00:01',
|
||||
|
|
|
|||
Loading…
Reference in a new issue