Merge branch 'train-days' into integration

# Conflicts:
#	apps/train/halkyon_train_flutter/lib/src/app/train_paths.dart
#	docs/adr/0030-halkyon-train.md
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-03 23:14:02 +01:00
commit 81adeb91cc
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -55,6 +55,8 @@ import 'package:halkyon_train_flutter/src/feature/strength/model/exercise_catalo
import 'package:halkyon_train_flutter/src/feature/strength/model/strength_plan.dart';
import 'package:halkyon_train_flutter/src/feature/weather/data/weather_gateway.dart';
import 'package:halkyon_train_flutter/src/feature/weather/model/workout_weather.dart';
import 'package:halkyon_train_flutter/src/feature/weight/data/weight_repository.dart';
import 'package:halkyon_train_flutter/src/feature/weight/widget/weight_card.dart';
import 'package:halkyon_train_flutter/src/feature/workout/data/workout_repository.dart';
import 'package:halkyon_train_flutter/src/feature/workout/model/series_chunk.dart';
import 'package:halkyon_train_flutter/src/feature/workout/model/workout.dart';
@ -85,6 +87,7 @@ import '../support/memory_plan_gateway.dart';
import '../support/memory_profile_gateway.dart';
import '../support/memory_route_gateway.dart';
import '../support/memory_strength_plan_gateway.dart';
import '../support/memory_weight_gateway.dart';
import '../support/memory_workout_gateway.dart';
void main() {
@ -121,6 +124,7 @@ void main() {
PlanRepository? plans,
RouteRepository? routes,
PlaceRepository? places,
WeightRepository? weights,
StrengthPlanRepository? strengthPlans,
StrengthSessionStore? strengthSession,
RoutingGateway? routing,
@ -193,6 +197,10 @@ void main() {
opened.add(person);
return TrainPerson(
person,
sync: SyncService(
({required onMerge}) async {},
debounce: const Duration(days: 1),
),
workouts:
workouts ??
WorkoutRepository(
@ -255,6 +263,16 @@ void main() {
),
),
chosenPlace: ChosenPlaceStore(),
weights:
weights ??
WeightRepository(
MemoryWeightGateway(),
TrainKeys(
MemoryContentKeyGateway(),
FakeContentCipher(),
() async => FakeAccountCrypto().keysNumbered(1),
),
),
strengthPlans:
strengthPlans ??
StrengthPlanRepository(
@ -505,14 +523,15 @@ void main() {
}
});
testWidgets('Settings groups the training first, then the account, About '
'Halkyon Train last and the version small at the very foot', (
tester,
) async {
testWidgets('Settings groups where sync stands first, with Sync now, then '
'the training, then the account, About Halkyon Train last and the '
'version small at the very foot', (tester) async {
await start(tester, usedBefore: true);
await openSettings(tester);
expect(find.text('Sync now'), findsOneWidget);
final order = [
'Sync',
'Your body',
'Account',
'Sign out',
@ -961,6 +980,25 @@ void main() {
await tester.pumpAndSettle();
}
testWidgets("the switches' explanations wait behind their question marks "
'(D64)', (tester) async {
await openMovement(tester);
const hint =
'When the phone counted hardly a step for a while; never while '
'recording or in a car.';
expect(find.text(hint), findsNothing);
await tester.tap(
find.descendant(
of: find.widgetWithText(SwitchListTile, 'Remind me to move'),
matching: find.byType(HelpButton),
),
);
await tester.pumpAndSettle();
expect(find.text(hint), findsOneWidget);
expect(platform.configured, [null], reason: 'nothing switched');
});
testWidgets('reminders turned on ask for the permissions, and the job '
'gets the settings and the words, each change again', (tester) async {
await openMovement(tester);
@ -1194,6 +1232,57 @@ void main() {
expect(find.text('50'), findsOneWidget);
});
testWidgets('a weight entered on Progress shows on its card and in Your '
'body, and goes again from All (D64)', (tester) async {
await start(tester, usedBefore: true, profile: profile);
await openProgress(tester);
await tester.runAsync(pumpEventQueue);
await tester.pump();
final card = find.byType(WeightCard);
expect(
find.descendant(
of: card,
matching: find.textContaining('Add your weight'),
),
findsOneWidget,
);
await tester.tap(
find.descendant(of: card, matching: find.byTooltip('Add weight')),
);
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), '1000');
await tester.tap(find.text('Save'));
await tester.pumpAndSettle();
expect(find.text('A weight between 20 and 400 kg'), findsOneWidget);
await tester.enterText(find.byType(TextField), '71,4');
await tester.tap(find.text('Save'));
await tester.runAsync(pumpEventQueue);
await tester.pumpAndSettle();
await tester.runAsync(pumpEventQueue);
await tester.pumpAndSettle();
expect(
find.descendant(of: card, matching: find.text('71.4 kg')),
findsOneWidget,
);
expect(
find.descendant(
of: find.byType(BodyCard),
matching: find.textContaining('71.4 kg'),
),
findsOneWidget,
reason: "the latest weight is the body's",
);
await tester.tap(find.descendant(of: card, matching: find.text('All')));
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Delete'));
await tester.runAsync(pumpEventQueue);
await tester.pumpAndSettle();
expect(find.text('71.4 kg'), findsNothing);
});
testWidgets("without figures, Profile's card says what they're for", (
tester,
) async {
@ -2835,6 +2924,8 @@ void main() {
'recovery': 4,
'rmssd': 48,
'baselineRmssd': 45,
// Polar's mark for a status it has none of.
'ansStatus': -100.0,
}),
),
);
@ -2872,16 +2963,36 @@ void main() {
await tester.tap(find.text('Tuesday, October 27'));
await tester.pumpAndSettle();
// Inside Profile, the tabs beside it (D60).
expect(find.byType(NavigationBar), findsOneWidget);
// A screen of its own over the tabs, the way back at its top left
// (the developer's review of 3 Nov 2026).
expect(find.byType(NavigationBar), findsNothing);
expect(find.byType(BackButton), findsOneWidget);
expect(find.text('Heart rate'), findsOneWidget);
expect(find.text('The night'), findsOneWidget);
expect(find.text('You slept well'), findsOneWidget);
await tester.scrollUntilVisible(find.text('Nightly Recharge'), 200);
expect(
find.text('Heart rate variability 48 ms, your usual 45 ms'),
findsOneWidget,
// The person's figures in rows, each with their usual; Polar's mark
// for no value shown nowhere, the explanation behind the question
// mark.
expect(find.text('Recovery'), findsOneWidget);
expect(find.text('4 of 6'), findsOneWidget);
expect(find.text('Heart rate variability'), findsOneWidget);
expect(find.text('48 ms'), findsOneWidget);
expect(find.text('usually 45 ms'), findsOneWidget);
expect(find.textContaining('100'), findsNothing);
expect(find.text('Autonomic nervous system'), findsNothing);
expect(find.textContaining('from −10 to +10'), findsNothing);
await tester.tap(
find.descendant(
of: find.ancestor(
of: find.text('Nightly Recharge'),
matching: find.byType(Row),
),
matching: find.byType(HelpButton),
),
);
await tester.pumpAndSettle();
expect(find.textContaining('from −10 to +10'), findsOneWidget);
});
testWidgets("Home shows today's figures from the band, even before a "