Stage 1: project setup, Exercise Library, both Mode designs and the Mode switch

- Flutter Android app on CookIT's stack (provider, shared_preferences, gen-l10n)
- tools/exercise-import turns free-exercise-db into 876 bundled Exercises
  with 1,746 WebP images (20 MB); stretching -> Stretching, rest -> Workout
- ZenStyle tokens per Mode: calm rounded sage/teal with breath ripples vs.
  dark ember-to-red with floor-tape slashes; the theme morphs at each
  Mode's own pace
- Remembered global Mode switch, Plans / Library / History tabs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
CedricandClaude Opus 5.5 committed 2026-10-28 23:18:03 +01:00
1 parent 5dfa995d55
commit 1289fadb98
1822 files changed
+2819

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:zenxercise/src/exercises/exercise_library.dart';
import 'package:zenxercise/src/modes/mode.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const json = '''[
{"id":"Calf_Stretch","name":"Calf Stretch","mode":"stretching","category":"stretching","level":"beginner",
"equipment":null,"force":"static","mechanic":null,"primaryMuscles":["calves"],"secondaryMuscles":[],
"instructions":["Lean forward."],"imageCount":2},
{"id":"Barbell_Squat","name":"Barbell Squat","mode":"workout","category":"strength","level":"intermediate",
"equipment":"barbell","force":"push","mechanic":"compound","primaryMuscles":["quadriceps"],
"secondaryMuscles":["glutes","hamstrings"],"instructions":["Squat."],"imageCount":0}
]''';
test('splits Exercises by Mode and finds them by id', () {
final library = ExerciseLibrary.fromJson(json);
expect(library.forMode(Mode.stretching).map((e) => e.id), ['Calf_Stretch']);
expect(library.forMode(Mode.workout).map((e) => e.id), ['Barbell_Squat']);
expect(library.byId('Barbell_Squat')!.secondaryMuscles, ['glutes', 'hamstrings']);
expect(library.byId('Missing'), isNull);
});
test('an Exercise points at its flattened WebP images', () {
final calf = ExerciseLibrary.fromJson(json).byId('Calf_Stretch')!;
expect(calf.images, ['assets/exercises/images/Calf_Stretch_0.webp', 'assets/exercises/images/Calf_Stretch_1.webp']);
});
test('the bundled library holds 123 stretches and 753 workout Exercises whose images exist', () async {
final library = await ExerciseLibrary.load(rootBundle);
expect(library.forMode(Mode.stretching), hasLength(123));
expect(library.forMode(Mode.workout), hasLength(753));
final anyImage = library.forMode(Mode.stretching).firstWhere((e) => e.imageCount == 2).images.last;
expect((await rootBundle.load(anyImage)).lengthInBytes, greaterThan(0));
});
}
@@ -0,0 +1,25 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:zenxercise/src/modes/mode.dart';
import 'package:zenxercise/src/settings/settings_controller.dart';
void main() {
test('starts in Stretching on first launch', () async {
SharedPreferences.setMockInitialValues({});
final settings = SettingsController(SettingsService());
await settings.loadSettings();
expect(settings.mode, Mode.stretching);
});
test('remembers the last-used Mode across launches', () async {
SharedPreferences.setMockInitialValues({});
await SettingsController(SettingsService()).updateMode(Mode.workout);
final relaunched = SettingsController(SettingsService());
await relaunched.loadSettings();
expect(relaunched.mode, Mode.workout);
});
}
+49
View File
@@ -0,0 +1,49 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:zenxercise/src/app.dart';
import 'package:zenxercise/src/exercises/exercise.dart';
import 'package:zenxercise/src/exercises/exercise_library.dart';
import 'package:zenxercise/src/modes/mode.dart';
import 'package:zenxercise/src/settings/settings_controller.dart';
Exercise exercise(String id, Mode mode, {String? name, int imageCount = 0}) => Exercise(
id: id,
name: name ?? id.replaceAll('_', ' '),
mode: mode,
category: mode == Mode.stretching ? 'stretching' : 'strength',
level: 'beginner',
equipment: 'body only',
primaryMuscles: const ['hamstrings'],
secondaryMuscles: const [],
instructions: const ['Breathe.'],
imageCount: imageCount,
);
final sampleLibrary = ExerciseLibrary([
exercise('Hamstring_Stretch', Mode.stretching),
exercise('Calf_Stretch', Mode.stretching),
exercise('Barbell_Squat', Mode.workout),
]);
/// The whole app over [library], with preferences starting from [prefs].
Future<SettingsController> pumpApp(
WidgetTester tester, {
ExerciseLibrary? library,
Map<String, Object> prefs = const {},
}) async {
SharedPreferences.setMockInitialValues(prefs);
final settings = SettingsController(SettingsService());
await settings.loadSettings();
await tester.pumpWidget(
MultiProvider(
providers: [
ChangeNotifierProvider.value(value: settings),
Provider.value(value: library ?? sampleLibrary),
],
child: const ZenXerciseApp(),
),
);
await tester.pumpAndSettle();
return settings;
}
+44
View File
@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:zenxercise/src/design/zen_style.dart';
import 'package:zenxercise/src/home/home_view.dart';
import 'package:zenxercise/src/modes/mode.dart';
import '../support/test_app.dart';
void main() {
ZenStyle styleOf(WidgetTester tester) => ZenStyle.of(tester.element(find.byType(HomeView)));
testWidgets('switching Mode changes design and Library together', (tester) async {
final settings = await pumpApp(tester);
await tester.tap(find.byTooltip('Library'));
await tester.pumpAndSettle();
expect(styleOf(tester).mode, Mode.stretching);
expect(find.text('2 stretches'), findsOneWidget);
expect(find.text('Hamstring Stretch'), findsOneWidget);
await tester.tap(find.text('Workout'));
await tester.pumpAndSettle();
expect(settings.mode, Mode.workout);
expect(styleOf(tester).mode, Mode.workout);
expect(styleOf(tester).brightness, Brightness.dark);
expect(find.text('1 exercise'), findsOneWidget);
expect(find.text('Barbell Squat'), findsOneWidget);
expect(find.text('Hamstring Stretch'), findsNothing);
});
testWidgets('opens in the last-used Mode', (tester) async {
await pumpApp(tester, prefs: {'mode': 'workout'});
expect(styleOf(tester).mode, Mode.workout);
});
testWidgets('an empty Plans tab invites building a Plan from the library', (tester) async {
await pumpApp(tester);
expect(find.text('No plans yet'), findsOneWidget);
expect(find.textContaining('stretch routine'), findsOneWidget);
});
}