Stage 4: guided Player for both Modes and the Session summary
- buildSegments lays a Plan out as holds with Transitions (get ready, switch sides, next round, next exercise) or sets with count-up Rests; Supersets alternate members and rest per round unless asked otherwise - SessionRunner drives segments against an injected clock: auto-advance with a chime, pause, skip, back, rep sets confirmed with logged reps and kg, timed sets started on demand, early end - Player screen: countdown ring, accent pulse in the last 3 s, Flip Animation, next-up cards, Rest Screen, mute toggle, screen kept on - Sessions table (schema v2 migration) keeps a Plan snapshot and Set Logs; nothing is saved when no hold or set was done - Summary with time, exercises or sets logged, and every Set Log - Synthesised quiet cue sounds (tools/sounds), status bar per Mode Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bbdca95541
commit
971ff64117
28 files changed
+3465
-84
No files matched your search
@@ -0,0 +1,119 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:zenxercise/src/modes/mode.dart';
|
||||
import 'package:zenxercise/src/plans/models/plan.dart';
|
||||
import 'package:zenxercise/src/player/segments.dart';
|
||||
|
||||
Plan _plan(Mode mode, List<PlanItem> items, {int transition = 5}) => Plan(
|
||||
id: 'p',
|
||||
mode: mode,
|
||||
name: 'P',
|
||||
items: items,
|
||||
transitionSeconds: transition,
|
||||
createdAt: DateTime(2026),
|
||||
updatedAt: DateTime(2026),
|
||||
);
|
||||
|
||||
String _describe(Segment s) => switch (s) {
|
||||
HoldSegment(:final exerciseId, :final side, :final round) =>
|
||||
'hold $exerciseId${side == null ? '' : ' ${side.name}'} r$round',
|
||||
TransitionSegment(:final kind, :final seconds) => '${kind.name} ${seconds}s',
|
||||
SetSegment(:final exerciseId, :final set) => 'set $exerciseId $set',
|
||||
RestSegment() => 'rest',
|
||||
};
|
||||
|
||||
void main() {
|
||||
group('Stretching', () {
|
||||
test('holds every side and round with Transitions between, after a get-ready', () {
|
||||
final segments = buildSegments(
|
||||
_plan(Mode.stretching, const [
|
||||
StretchingStep(exerciseId: 'A', holdSeconds: 30, bothSides: true, rounds: 2),
|
||||
StretchingStep(exerciseId: 'B', holdSeconds: 20, bothSides: false, rounds: 1),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(segments.map(_describe), [
|
||||
'getReady 5s',
|
||||
'hold A left r1',
|
||||
'switchSides 5s',
|
||||
'hold A right r1',
|
||||
'nextRound 5s',
|
||||
'hold A left r2',
|
||||
'switchSides 5s',
|
||||
'hold A right r2',
|
||||
'nextExercise 5s',
|
||||
'hold B r1',
|
||||
]);
|
||||
});
|
||||
|
||||
test('a pause of 0 s means no Transitions at all', () {
|
||||
final segments = buildSegments(
|
||||
_plan(Mode.stretching, const [
|
||||
StretchingStep(exerciseId: 'A', holdSeconds: 30, bothSides: true, rounds: 1),
|
||||
], transition: 0),
|
||||
);
|
||||
|
||||
expect(segments.map(_describe), ['hold A left r1', 'hold A right r1']);
|
||||
});
|
||||
});
|
||||
|
||||
group('Workout', () {
|
||||
test('each set is followed by a Rest, except the very last', () {
|
||||
final segments = buildSegments(
|
||||
_plan(Mode.workout, const [
|
||||
WorkoutStep(exerciseId: 'A', sets: 2, target: WorkoutTarget.reps(10)),
|
||||
WorkoutStep(exerciseId: 'B', sets: 1, target: WorkoutTarget.seconds(30)),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(segments.map(_describe), ['set A 1', 'rest', 'set A 2', 'rest', 'set B 1']);
|
||||
});
|
||||
|
||||
test('a Superset alternates its members and rests after each round only', () {
|
||||
final segments = buildSegments(
|
||||
_plan(Mode.workout, const [
|
||||
Superset(
|
||||
sets: 2,
|
||||
restBetween: false,
|
||||
members: [
|
||||
SupersetMember(exerciseId: 'A', target: WorkoutTarget.reps(10)),
|
||||
SupersetMember(exerciseId: 'B', target: WorkoutTarget.reps(12)),
|
||||
],
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(segments.map(_describe), ['set A 1', 'set B 1', 'rest', 'set A 2', 'set B 2']);
|
||||
});
|
||||
|
||||
test('with rest between, a Superset also rests between its members', () {
|
||||
final segments = buildSegments(
|
||||
_plan(Mode.workout, const [
|
||||
Superset(
|
||||
sets: 1,
|
||||
restBetween: true,
|
||||
members: [
|
||||
SupersetMember(exerciseId: 'A', target: WorkoutTarget.reps(10)),
|
||||
SupersetMember(exerciseId: 'B', target: WorkoutTarget.reps(12)),
|
||||
],
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(segments.map(_describe), ['set A 1', 'rest', 'set B 1']);
|
||||
});
|
||||
|
||||
test('a set carries its target, weight and position in the Step', () {
|
||||
final set =
|
||||
buildSegments(
|
||||
_plan(Mode.workout, const [
|
||||
WorkoutStep(exerciseId: 'A', sets: 3, target: WorkoutTarget.reps(8), weightKg: 40),
|
||||
]),
|
||||
)[2]
|
||||
as SetSegment;
|
||||
|
||||
expect(set.target, const WorkoutTarget.reps(8));
|
||||
expect(set.weightKg, 40);
|
||||
expect((set.set, set.sets), (2, 3));
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user