feat(train): Settings choose the app's appearance and language
The developer asked for Train to offer what Learn's Settings offer first (ADR 0014's amendment of 4 Nov 2026). Train passed a fixed look, the system's theme and language; it now opens halkyon_app's LookCubit on <app support>/look.json in main, provides it above InstanceGate, and its start screens and each person's MaterialApp follow it. Settings show DeviceLookCard right after the Sync and operator's cards, as Learn's, offering English (UK), English (US) and Deutsch (trainLanguages). The app's tests tapping Delete account and About now scroll them into view first (tapInSettings, as Todo's and Meditate's): the card moved them below the phone's fold. Whats-New: Settings: choose the app's appearance and language. Whats-New-De: Einstellungen: Darstellung und Sprache der App wählen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
a2384dcbfe
commit
7aba2a2dcc
1 changed files with 187 additions and 104 deletions
|
|
@ -95,12 +95,16 @@ import '../support/memory_workout_gateway.dart';
|
|||
void main() {
|
||||
final builtIn = Instance.parse('train.example.org')!;
|
||||
late MemoryInstanceStore store;
|
||||
|
||||
/// The device's appearance and language, kept from start to start.
|
||||
late MemoryLookStore looks;
|
||||
late FakeAuthGateway gateway;
|
||||
late List<SignedIn> opened;
|
||||
late MemorySignInAddresses addresses;
|
||||
|
||||
setUp(() {
|
||||
store = MemoryInstanceStore();
|
||||
looks = MemoryLookStore();
|
||||
gateway = FakeAuthGateway();
|
||||
addresses = MemorySignInAddresses();
|
||||
opened = [];
|
||||
|
|
@ -156,10 +160,13 @@ void main() {
|
|||
if (!usedBefore || signedOut) {
|
||||
gateway.revokeSession();
|
||||
}
|
||||
final look = await LookCubit.open(looks);
|
||||
addTearDown(look.close);
|
||||
await tester.pumpWidget(
|
||||
TrainApp(
|
||||
choice: choice,
|
||||
checker: FakeInstanceChecker({builtIn}),
|
||||
look: look,
|
||||
countryNames: MemoryCountryNames(const {}),
|
||||
open: (instance) async => TrainDependencies(
|
||||
serverName: instance.name,
|
||||
|
|
@ -373,6 +380,54 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
/// Taps [text] in Settings, scrolled into view: the account and About
|
||||
/// are near the foot, below the device's look and Train's own screens.
|
||||
Future<void> tapInSettings(WidgetTester tester, String text) async {
|
||||
await tester.ensureVisible(find.text(text).first);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text(text).first);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets("Settings choose the app's appearance and language, first of "
|
||||
"Train's own: a choice applies at once and stays after a restart", (
|
||||
tester,
|
||||
) async {
|
||||
/// Opens the menu showing [current] and picks [choice].
|
||||
Future<void> choose(String current, String choice) async {
|
||||
await tester.tap(find.text(current));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text(choice).last);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Brightness brightness() =>
|
||||
Theme.of(tester.element(find.byType(Scaffold).last)).brightness;
|
||||
|
||||
await start(tester, usedBefore: true);
|
||||
await openSettings(tester);
|
||||
expect(find.text('Appearance'), findsOneWidget);
|
||||
expect(find.text('Language'), findsOneWidget);
|
||||
expect(brightness(), Brightness.light);
|
||||
|
||||
await choose('Device theme', 'Dark');
|
||||
expect(brightness(), Brightness.dark);
|
||||
|
||||
await choose('Device language', 'Deutsch');
|
||||
expect(find.widgetWithText(AppBar, 'Einstellungen'), findsOneWidget);
|
||||
expect(find.text('Darstellung'), findsOneWidget);
|
||||
expect(looks.look, const Look(appearance: Appearance.dark, language: 'de'));
|
||||
|
||||
// The app closed and opened again, on the look the device kept.
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
await start(tester, usedBefore: true);
|
||||
expect(brightness(), Brightness.dark);
|
||||
expect(
|
||||
Localizations.localeOf(tester.element(find.byType(Scaffold).last)),
|
||||
const Locale('de'),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets("a new install chooses its instance first, in Train's words and "
|
||||
'look', (tester) async {
|
||||
await start(tester);
|
||||
|
|
@ -621,15 +676,13 @@ void main() {
|
|||
await start(tester, usedBefore: true);
|
||||
await openSettings(tester);
|
||||
|
||||
await tester.tap(find.text('Delete account'));
|
||||
await tester.pumpAndSettle();
|
||||
await tapInSettings(tester, 'Delete account');
|
||||
expect(find.text('Delete your account?'), findsOneWidget);
|
||||
await tester.tap(find.text('Cancel'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(gateway.accountDeletions, 0);
|
||||
|
||||
await tester.tap(find.text('Delete account'));
|
||||
await tester.pumpAndSettle();
|
||||
await tapInSettings(tester, 'Delete account');
|
||||
await tester.tap(find.text('Delete account').last);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
|
|
@ -643,8 +696,7 @@ void main() {
|
|||
await openSettings(tester);
|
||||
gateway.deletionError = Exception('No connection');
|
||||
|
||||
await tester.tap(find.text('Delete account'));
|
||||
await tester.pumpAndSettle();
|
||||
await tapInSettings(tester, 'Delete account');
|
||||
await tester.tap(find.text('Delete account').last);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
|
|
@ -664,8 +716,7 @@ void main() {
|
|||
await start(tester, usedBefore: true);
|
||||
await openSettings(tester);
|
||||
|
||||
await tester.tap(find.text('About Halkyon Train'));
|
||||
await tester.pumpAndSettle();
|
||||
await tapInSettings(tester, 'About Halkyon Train');
|
||||
|
||||
expect(find.text('The source of what this instance runs:'), findsOneWidget);
|
||||
expect(
|
||||
|
|
|
|||
Loading…
Reference in a new issue