diff --git a/flutter_client/lib/player/album_cover_cache.dart b/flutter_client/lib/player/album_cover_cache.dart index 4ddd1270..23443807 100644 --- a/flutter_client/lib/player/album_cover_cache.dart +++ b/flutter_client/lib/player/album_cover_cache.dart @@ -10,7 +10,7 @@ import 'package:path_provider/path_provider.dart'; /// and doesn't carry our Bearer header, so we pre-fetch via the /// authenticated dio and hand the system a local file path instead. /// -/// Cache layout: /album_covers/.jpg. +/// Cache layout: `{applicationCacheDirectory}/album_covers/{albumId}.jpg`. /// No explicit eviction — covers are tiny and OS clears app cache when /// space is tight. class AlbumCoverCache { diff --git a/flutter_client/lib/settings/settings_screen.dart b/flutter_client/lib/settings/settings_screen.dart index cccf59ba..9202ad7c 100644 --- a/flutter_client/lib/settings/settings_screen.dart +++ b/flutter_client/lib/settings/settings_screen.dart @@ -503,26 +503,32 @@ class _AppearanceSection extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final fs = Theme.of(context).extension()!; final current = ref.watch(themeModeProvider).value ?? AppThemeMode.system; - return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const _SectionHeader('Appearance'), - for (final mode in AppThemeMode.values) - RadioListTile( - key: Key('appearance_${mode.name}'), - title: Text(_label(mode), style: TextStyle(color: fs.parchment)), - subtitle: mode == AppThemeMode.system - ? Text('Match the device setting', - style: TextStyle(color: fs.ash)) - : null, - value: mode, - groupValue: current, - activeColor: fs.accent, - onChanged: (m) { - if (m != null) { - ref.read(themeModeProvider.notifier).set(m); - } - }, - ), - ]); + // RadioGroup is the post-Flutter-3.32 API: the ancestor owns + // groupValue/onChanged so individual RadioListTiles don't have + // to repeat them. Pre-3.32 RadioListTile.groupValue/onChanged + // are deprecated. + return RadioGroup( + groupValue: current, + onChanged: (m) { + if (m != null) { + ref.read(themeModeProvider.notifier).set(m); + } + }, + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + const _SectionHeader('Appearance'), + for (final mode in AppThemeMode.values) + RadioListTile( + key: Key('appearance_${mode.name}'), + title: Text(_label(mode), style: TextStyle(color: fs.parchment)), + subtitle: mode == AppThemeMode.system + ? Text('Match the device setting', + style: TextStyle(color: fs.ash)) + : null, + value: mode, + activeColor: fs.accent, + ), + ]), + ); } String _label(AppThemeMode m) => switch (m) { diff --git a/flutter_client/lib/theme/theme_extension.dart b/flutter_client/lib/theme/theme_extension.dart index 7ddd46c2..0f82e168 100644 --- a/flutter_client/lib/theme/theme_extension.dart +++ b/flutter_client/lib/theme/theme_extension.dart @@ -30,7 +30,7 @@ class FabledSwordTheme extends ThemeExtension { final Color warning, error, info; final TextStyle display, body, mono; - factory FabledSwordTheme.dark() => FabledSwordTheme( + factory FabledSwordTheme.dark() => const FabledSwordTheme( accent: FabledSwordFlatTokens.accent, obsidian: FabledSwordDarkTokens.obsidian, iron: FabledSwordDarkTokens.iron, @@ -50,7 +50,7 @@ class FabledSwordTheme extends ThemeExtension { mono: TextStyle(fontFamily: FabledSwordFlatTokens.fontMono), ); - factory FabledSwordTheme.light() => FabledSwordTheme( + factory FabledSwordTheme.light() => const FabledSwordTheme( accent: FabledSwordFlatTokens.accent, obsidian: FabledSwordLightTokens.obsidian, iron: FabledSwordLightTokens.iron, diff --git a/flutter_client/test/settings/appearance_section_test.dart b/flutter_client/test/settings/appearance_section_test.dart index f3792463..81f6a183 100644 --- a/flutter_client/test/settings/appearance_section_test.dart +++ b/flutter_client/test/settings/appearance_section_test.dart @@ -49,11 +49,15 @@ void main() { expect(find.byKey(const Key('appearance_light')), findsOneWidget); expect(find.byKey(const Key('appearance_dark')), findsOneWidget); - // The dark radio should be selected. + // The RadioGroup ancestor owns groupValue post-Flutter-3.32; the + // selected mode is read off it rather than off individual tiles. + final group = t.widget>( + find.byType(RadioGroup), + ); + expect(group.groupValue, AppThemeMode.dark); final darkRadio = t.widget>( find.byKey(const Key('appearance_dark')), ); - expect(darkRadio.groupValue, AppThemeMode.dark); expect(darkRadio.value, AppThemeMode.dark); });