feat(train): where sync stands in Settings, with Sync now, and the cog's dot
As Learn's and Todo's: halkyon_app's Sync card first in Settings, and the dot on the cog while sync fails; the person's SyncService is now part of TrainPerson, which was the only reason Train had neither (the developer's review of 3 Nov 2026). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
55fc245859
commit
adbb2c4f2f
4 changed files with 29 additions and 6 deletions
|
|
@ -106,6 +106,12 @@ class _TrainPersonAppState() extends State<TrainPersonApp> {
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
OperatingCubit(AppScope.of(context).instanceOperators),
|
OperatingCubit(AppScope.of(context).instanceOperators),
|
||||||
),
|
),
|
||||||
|
// Where sync stands: Settings' card and the dot on the cog, as
|
||||||
|
// Learn's and Todo's.
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) =>
|
||||||
|
SyncStatusBloc(PersonScope.of<TrainPerson>(context).sync),
|
||||||
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) {
|
create: (context) {
|
||||||
final train = PersonScope.of<TrainPerson>(context);
|
final train = PersonScope.of<TrainPerson>(context);
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,10 @@ class TrainPerson(
|
||||||
/// Whose they are.
|
/// Whose they are.
|
||||||
final SignedIn person, {
|
final SignedIn person, {
|
||||||
|
|
||||||
|
/// What sends their changes and brings their other devices' (D14):
|
||||||
|
/// Settings' Sync card and the cog's dot follow it.
|
||||||
|
required final SyncService sync,
|
||||||
|
|
||||||
/// Their workouts, sealed and synced.
|
/// Their workouts, sealed and synced.
|
||||||
required final WorkoutRepository workouts,
|
required final WorkoutRepository workouts,
|
||||||
|
|
||||||
|
|
@ -496,6 +500,7 @@ Future<TrainPerson> _openPersonData(
|
||||||
: AppLifecycleListener(onResume: () => unawaited(keptMusic.sync()));
|
: AppLifecycleListener(onResume: () => unawaited(keptMusic.sync()));
|
||||||
return TrainPerson(
|
return TrainPerson(
|
||||||
person,
|
person,
|
||||||
|
sync: sync,
|
||||||
workouts: workouts,
|
workouts: workouts,
|
||||||
profile: profile,
|
profile: profile,
|
||||||
days: days,
|
days: days,
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,16 @@ class const SettingsButton({super.key}) extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => IconButton(
|
Widget build(BuildContext context) => IconButton(
|
||||||
tooltip: AppLocalizations.of(context).settingsTitle,
|
tooltip: AppLocalizations.of(context).settingsTitle,
|
||||||
icon: const WaitingAccountsBadge(child: Icon(LucideIcons.settings)),
|
// The dot of a failing sync, which the Sync card explains, as Learn's.
|
||||||
|
icon: const SyncTroubleBadge(
|
||||||
|
child: WaitingAccountsBadge(child: Icon(LucideIcons.settings)),
|
||||||
|
),
|
||||||
onPressed: () => context.push(TrainPaths.settings),
|
onPressed: () => context.push(TrainPaths.settings),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Settings, over the tabs (ADR 0030, D60), grouped in cards as Learn's:
|
/// Settings, over the tabs (ADR 0030, D60), grouped in cards as Learn's:
|
||||||
/// the operator's card for whoever operates the instance; the training's
|
/// where sync stands, with Sync now; the operator's card for whoever operates the instance; the training's
|
||||||
/// screens (the body's figures, Health Connect, the Polar Loop, music,
|
/// screens (the body's figures, Health Connect, the Polar Loop, music,
|
||||||
/// those the device has); the account, near the foot (halkyon_app's
|
/// those the device has); the account, near the foot (halkyon_app's
|
||||||
/// sections: whose it is on which instance, switching, signing out and
|
/// sections: whose it is on which instance, switching, signing out and
|
||||||
|
|
@ -63,6 +66,10 @@ class const SettingsScreen({super.key}) extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
|
// Where sync stands, first, as Learn's and Todo's: the
|
||||||
|
// cog's dot leads here.
|
||||||
|
const SyncStatusCard(),
|
||||||
|
const SizedBox(height: 12),
|
||||||
// Accounts waiting for approval, for whoever operates
|
// Accounts waiting for approval, for whoever operates
|
||||||
// the instance alone (Halkyon Learn's ADR 0021, D34),
|
// the instance alone (Halkyon Learn's ADR 0021, D34),
|
||||||
// first, as the cog's count leads here.
|
// first, as the cog's count leads here.
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,10 @@ void main() {
|
||||||
opened.add(person);
|
opened.add(person);
|
||||||
return TrainPerson(
|
return TrainPerson(
|
||||||
person,
|
person,
|
||||||
|
sync: SyncService(
|
||||||
|
({required onMerge}) async {},
|
||||||
|
debounce: const Duration(days: 1),
|
||||||
|
),
|
||||||
workouts:
|
workouts:
|
||||||
workouts ??
|
workouts ??
|
||||||
WorkoutRepository(
|
WorkoutRepository(
|
||||||
|
|
@ -488,14 +492,15 @@ void main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Settings groups the training first, then the account, About '
|
testWidgets('Settings groups where sync stands first, with Sync now, then '
|
||||||
'Halkyon Train last and the version small at the very foot', (
|
'the training, then the account, About Halkyon Train last and the '
|
||||||
tester,
|
'version small at the very foot', (tester) async {
|
||||||
) async {
|
|
||||||
await start(tester, usedBefore: true);
|
await start(tester, usedBefore: true);
|
||||||
await openSettings(tester);
|
await openSettings(tester);
|
||||||
|
|
||||||
|
expect(find.text('Sync now'), findsOneWidget);
|
||||||
final order = [
|
final order = [
|
||||||
|
'Sync',
|
||||||
'Your body',
|
'Your body',
|
||||||
'Account',
|
'Account',
|
||||||
'Sign out',
|
'Sign out',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue