import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/models/theme_settings.dart'; import 'package:spotiflac_android/providers/theme_provider.dart'; import 'package:spotiflac_android/screens/settings/appearance_settings_page.dart'; import 'package:spotiflac_android/theme/dynamic_color_wrapper.dart'; import 'package:spotiflac_android/theme/mornye_theme.dart'; void main() { test('custom accents keep text readable in both appearances', () { double contrast(Color first, Color second) { final a = first.computeLuminance(); final b = second.computeLuminance(); return a > b ? (a + 0.05) / (b + 0.05) : (b + 0.05) / (a + 0.05); } for (final brightness in Brightness.values) { for (final accent in MornyeAccent.values.where( (accent) => accent != MornyeAccent.red, )) { final scheme = MornyeTheme.build( brightness, accent: accent, ).colorScheme; expect( contrast(scheme.primary, scheme.surface), greaterThanOrEqualTo(4.5), reason: '$accent on $brightness', ); expect( contrast(scheme.onPrimary, scheme.primary), greaterThanOrEqualTo(4.5), reason: 'Button text for $accent on $brightness', ); } } }); Future openSettings( WidgetTester tester, SharedPreferences prefs, ) async { tester.view.physicalSize = const Size(390, 1000); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); await tester.pumpWidget( ProviderScope( overrides: [ initialThemeSettingsProvider.overrideWithValue( loadBootstrapThemeSettings(prefs), ), ], child: DynamicColorWrapper( builder: (light, dark, mode) => MaterialApp( theme: light, darkTheme: dark, themeMode: mode, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: const MornyeSettingsTheme(child: AppearanceSettingsPage()), ), ), ), ); await tester.pumpAndSettle(); } for (final mode in [ThemeMode.light, ThemeMode.dark]) { testWidgets('Mornye accent updates live and survives restart ($mode)', ( tester, ) async { SharedPreferences.setMockInitialValues({ kThemeStyleKey: 'mornye', kThemeModeKey: mode.name, kSeedColorKey: 0xff123456, }); final prefs = await SharedPreferences.getInstance(); await openSettings(tester, prefs); final page = find.byType(AppearanceSettingsPage); final initial = Theme.of(tester.element(page)); expect( initial.colorScheme.primary, MornyeTheme.accentColor(MornyeAccent.red, initial.brightness), ); final blue = find.byTooltip('Blue'); await tester.ensureVisible(blue); await tester.tap(blue); await tester.pumpAndSettle(); final theme = Theme.of(tester.element(page)); expect( theme.colorScheme.primary, MornyeTheme.accentColor(MornyeAccent.blue, theme.brightness), ); expect(prefs.getString(kMornyeAccentKey), 'blue'); expect(prefs.getInt(kSeedColorKey), 0xff123456); final overlay = MornyeTheme.fromContext( tester.element(page), brightness: Brightness.dark, ); expect(overlay.extension()!.accent, MornyeAccent.blue); expect( overlay.colorScheme.primary, MornyeTheme.accentColor(MornyeAccent.blue, Brightness.dark), ); await tester.pumpWidget(const SizedBox()); await openSettings(tester, prefs); expect( Theme.of(tester.element(page)).colorScheme.primary, theme.colorScheme.primary, ); await tester.ensureVisible(find.byTooltip('Red (default)')); await tester.tap(find.byTooltip('Red (default)')); await tester.pumpAndSettle(); expect( Theme.of(tester.element(page)).colorScheme.primary, initial.colorScheme.primary, ); expect(tester.takeException(), isNull); }); } for (final style in AppThemeStyle.values) { testWidgets( 'system font persists and restores the default ($style)', (tester) async { SharedPreferences.setMockInitialValues({ kThemeStyleKey: style.name, kMornyeAccentKey: 'blue', kUseDynamicColorKey: false, }); final prefs = await SharedPreferences.getInstance(); await openSettings(tester, prefs); final page = find.byType(AppearanceSettingsPage); final original = Theme.of( tester.element(page), ).textTheme.bodyLarge!.fontFamily; final setting = find.text('Use system font'); await tester.ensureVisible(setting); await tester.pumpAndSettle(); await tester.tap(setting); await tester.pumpAndSettle(); final theme = Theme.of(tester.element(page)); final systemFamily = theme.textTheme.bodyLarge!.fontFamily; expect(systemFamily, isNot('Inter')); expect(systemFamily, isNot('Google Sans Flex')); expect(prefs.getBool(kUseSystemFontKey), isTrue); if (style == AppThemeStyle.mornye) { final overlay = MornyeTheme.fromContext( tester.element(page), brightness: Brightness.dark, ); expect(overlay.extension()!.useSystemFont, isTrue); expect(overlay.extension()!.accent, MornyeAccent.blue); expect(overlay.textTheme.bodyLarge!.fontFamily, systemFamily); } await tester.pumpWidget(const SizedBox()); await openSettings(tester, prefs); expect( Theme.of(tester.element(page)).textTheme.bodyLarge!.fontFamily, systemFamily, ); await tester.ensureVisible(setting); await tester.pumpAndSettle(); await tester.tap(setting); await tester.pumpAndSettle(); expect(prefs.getBool(kUseSystemFontKey), isFalse); expect( Theme.of(tester.element(page)).textTheme.bodyLarge!.fontFamily, original, ); expect(tester.takeException(), isNull); }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.iOS, }), ); } }