From cadf9c7410ee4c2478e2d13cd18ce84b7e8fc21f Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Mon, 24 Jul 2023 21:06:59 +0800 Subject: [PATCH] Support system theme color (aka Material You) --- app/lib/entity/pref/extension.dart | 10 +- app/lib/l10n/app_en.arb | 8 ++ app/lib/l10n/untranslated-messages.txt | 26 +++++ app/lib/material3.dart | 5 - app/lib/session_storage.dart | 3 + app/lib/theme.dart | 55 +++++----- app/lib/widget/my_app.dart | 82 +++++++------- app/lib/widget/settings/theme/bloc.dart | 9 +- .../widget/settings/theme/state_event.dart | 5 +- app/lib/widget/settings/theme_settings.dart | 102 ++++++++++++------ app/lib/widget/settings/theme_settings.g.dart | 9 +- app/pubspec.lock | 10 +- app/pubspec.yaml | 1 + 13 files changed, 216 insertions(+), 109 deletions(-) diff --git a/app/lib/entity/pref/extension.dart b/app/lib/entity/pref/extension.dart index c55a9310..0a16c345 100644 --- a/app/lib/entity/pref/extension.dart +++ b/app/lib/entity/pref/extension.dart @@ -251,8 +251,14 @@ extension PrefExtension on Pref { int? getSeedColor() => provider.getInt(PrefKey.seedColor); int getSeedColorOr(int def) => getSeedColor() ?? def; - Future setSeedColor(int value) => _set( - PrefKey.seedColor, value, (key, value) => provider.setInt(key, value)); + Future setSeedColor(int? value) { + if (value == null) { + return _remove(PrefKey.seedColor); + } else { + return _set(PrefKey.seedColor, value, + (key, value) => provider.setInt(key, value)); + } + } bool? isVideoPlayerMute() => provider.getBool(PrefKey.isVideoPlayerMute); bool isVideoPlayerMuteOr([bool def = false]) => isVideoPlayerMute() ?? def; diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index 4deff4b5..c080abff 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -383,10 +383,18 @@ "@settingsSeedColorDescription": { "description": "Customize the colors used in app" }, + "settingsSeedColorSystemColorDescription": "Use system color", + "@settingsSeedColorSystemColorDescription": { + "description": "Use color provided by the OS, i.e., Material You" + }, "settingsSeedColorPickerTitle": "Pick a color", "@settingsSeedColorPickerTitle": { "description": "Dialog to customize the colors used in app" }, + "settingsSeedColorPickerSystemColorButtonLabel": "USE SYSTEM COLOR", + "@settingsSeedColorPickerSystemColorButtonLabel": { + "description": "Use color provided by the OS, i.e., Material You" + }, "settingsUseBlackInDarkThemeTitle": "Darker theme", "@settingsUseBlackInDarkThemeTitle": { "description": "Make the dark theme darker" diff --git a/app/lib/l10n/untranslated-messages.txt b/app/lib/l10n/untranslated-messages.txt index a603a81d..02131753 100644 --- a/app/lib/l10n/untranslated-messages.txt +++ b/app/lib/l10n/untranslated-messages.txt @@ -2,6 +2,8 @@ "cs": [ "nameInputInvalidEmpty", "settingsPersonProviderTitle", + "settingsSeedColorSystemColorDescription", + "settingsSeedColorPickerSystemColorButtonLabel", "settingsServerVersionTitle", "searchLandingPeopleListEmptyText2", "createCollectionFailureNotification", @@ -49,7 +51,9 @@ "settingsImageEditSaveResultsToServerFalseDescription", "settingsSeedColorTitle", "settingsSeedColorDescription", + "settingsSeedColorSystemColorDescription", "settingsSeedColorPickerTitle", + "settingsSeedColorPickerSystemColorButtonLabel", "settingsMiscellaneousTitle", "settingsDoubleTapExitTitle", "settingsPhotosTabSortByNameTitle", @@ -242,7 +246,9 @@ "settingsImageEditSaveResultsToServerFalseDescription", "settingsSeedColorTitle", "settingsSeedColorDescription", + "settingsSeedColorSystemColorDescription", "settingsSeedColorPickerTitle", + "settingsSeedColorPickerSystemColorButtonLabel", "settingsDoubleTapExitTitle", "settingsExpertTitle", "settingsExpertWarningText", @@ -328,12 +334,16 @@ "es": [ "settingsPersonProviderTitle", + "settingsSeedColorSystemColorDescription", + "settingsSeedColorPickerSystemColorButtonLabel", "searchLandingPeopleListEmptyText2", "accountSettingsTooltip" ], "fi": [ "settingsPersonProviderTitle", + "settingsSeedColorSystemColorDescription", + "settingsSeedColorPickerSystemColorButtonLabel", "searchLandingPeopleListEmptyText2", "accountSettingsTooltip" ], @@ -362,7 +372,9 @@ "settingsImageEditSaveResultsToServerFalseDescription", "settingsSeedColorTitle", "settingsSeedColorDescription", + "settingsSeedColorSystemColorDescription", "settingsSeedColorPickerTitle", + "settingsSeedColorPickerSystemColorButtonLabel", "settingsMiscellaneousTitle", "settingsDoubleTapExitTitle", "settingsPhotosTabSortByNameTitle", @@ -495,7 +507,9 @@ "settingsFollowSystemThemeTitle", "settingsSeedColorTitle", "settingsSeedColorDescription", + "settingsSeedColorSystemColorDescription", "settingsSeedColorPickerTitle", + "settingsSeedColorPickerSystemColorButtonLabel", "settingsUseBlackInDarkThemeTitle", "settingsUseBlackInDarkThemeTrueDescription", "settingsUseBlackInDarkThemeFalseDescription", @@ -843,7 +857,9 @@ "settingsFollowSystemThemeTitle", "settingsSeedColorTitle", "settingsSeedColorDescription", + "settingsSeedColorSystemColorDescription", "settingsSeedColorPickerTitle", + "settingsSeedColorPickerSystemColorButtonLabel", "settingsUseBlackInDarkThemeTitle", "settingsUseBlackInDarkThemeTrueDescription", "settingsUseBlackInDarkThemeFalseDescription", @@ -1145,7 +1161,9 @@ "settingsImageEditSaveResultsToServerFalseDescription", "settingsSeedColorTitle", "settingsSeedColorDescription", + "settingsSeedColorSystemColorDescription", "settingsSeedColorPickerTitle", + "settingsSeedColorPickerSystemColorButtonLabel", "settingsMiscellaneousTitle", "settingsDoubleTapExitTitle", "settingsPhotosTabSortByNameTitle", @@ -1265,6 +1283,8 @@ "pt": [ "nameInputInvalidEmpty", "settingsPersonProviderTitle", + "settingsSeedColorSystemColorDescription", + "settingsSeedColorPickerSystemColorButtonLabel", "settingsServerVersionTitle", "searchLandingPeopleListEmptyText2", "createCollectionFailureNotification", @@ -1302,7 +1322,9 @@ "settingsImageEditSaveResultsToServerFalseDescription", "settingsSeedColorTitle", "settingsSeedColorDescription", + "settingsSeedColorSystemColorDescription", "settingsSeedColorPickerTitle", + "settingsSeedColorPickerSystemColorButtonLabel", "settingsMiscellaneousTitle", "settingsDoubleTapExitTitle", "settingsPhotosTabSortByNameTitle", @@ -1424,7 +1446,9 @@ "settingsImageEditSaveResultsToServerFalseDescription", "settingsSeedColorTitle", "settingsSeedColorDescription", + "settingsSeedColorSystemColorDescription", "settingsSeedColorPickerTitle", + "settingsSeedColorPickerSystemColorButtonLabel", "settingsMiscellaneousTitle", "settingsDoubleTapExitTitle", "settingsPhotosTabSortByNameTitle", @@ -1546,7 +1570,9 @@ "settingsImageEditSaveResultsToServerFalseDescription", "settingsSeedColorTitle", "settingsSeedColorDescription", + "settingsSeedColorSystemColorDescription", "settingsSeedColorPickerTitle", + "settingsSeedColorPickerSystemColorButtonLabel", "settingsMiscellaneousTitle", "settingsDoubleTapExitTitle", "settingsPhotosTabSortByNameTitle", diff --git a/app/lib/material3.dart b/app/lib/material3.dart index 48bc123f..55b9a2f8 100644 --- a/app/lib/material3.dart +++ b/app/lib/material3.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; class M3 extends ThemeExtension { const M3({ - required this.seed, required this.checkbox, required this.filterChip, required this.listTile, @@ -12,13 +11,11 @@ class M3 extends ThemeExtension { @override M3 copyWith({ - Color? seed, M3Checkbox? checkbox, M3FilterChip? filterChip, M3ListTile? listTile, }) => M3( - seed: seed ?? this.seed, checkbox: checkbox ?? this.checkbox, filterChip: filterChip ?? this.filterChip, listTile: listTile ?? this.listTile, @@ -30,14 +27,12 @@ class M3 extends ThemeExtension { return this; } return M3( - seed: Color.lerp(seed, other.seed, t)!, checkbox: checkbox.lerp(other.checkbox, t), filterChip: filterChip.lerp(other.filterChip, t), listTile: listTile.lerp(other.listTile, t), ); } - final Color seed; final M3Checkbox checkbox; final M3FilterChip filterChip; final M3ListTile listTile; diff --git a/app/lib/session_storage.dart b/app/lib/session_storage.dart index cbdea4d5..fc122920 100644 --- a/app/lib/session_storage.dart +++ b/app/lib/session_storage.dart @@ -12,5 +12,8 @@ class SessionStorage { /// Whether the drag to rearrange notification has been shown bool hasShowDragRearrangeNotification = false; + /// Whether the dynamic_color library is supported in this platform + bool isSupportDynamicColor = false; + static final _inst = SessionStorage._(); } diff --git a/app/lib/theme.dart b/app/lib/theme.dart index 36280bd4..3e1e71b3 100644 --- a/app/lib/theme.dart +++ b/app/lib/theme.dart @@ -3,6 +3,9 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:nc_photos/entity/pref.dart'; import 'package:nc_photos/material3.dart'; +import 'package:nc_photos/object_extension.dart'; + +const defaultSeedColor = 0xFF2196F3; extension ThemeExtension on ThemeData { double get widthLimitedContentMaxWidth => 550.0; @@ -90,38 +93,43 @@ ThemeData buildTheme(Brightness brightness) { : buildDarkTheme(); } -ThemeData buildLightTheme() { - final seedColor = getSeedColor(); - final colorScheme = ColorScheme.fromSeed( - seedColor: seedColor, - ); - return _applyColorScheme(colorScheme, seedColor); +ThemeData buildLightTheme([ColorScheme? dynamicScheme]) { + final colorScheme = _getColorScheme(dynamicScheme, Brightness.light); + return _applyColorScheme(colorScheme); } -ThemeData buildDarkTheme() { - final seedColor = getSeedColor(); - final colorScheme = ColorScheme.fromSeed( - seedColor: seedColor, - brightness: Brightness.dark, - ); +ThemeData buildDarkTheme([ColorScheme? dynamicScheme]) { + final colorScheme = _getColorScheme(dynamicScheme, Brightness.dark); if (Pref().isUseBlackInDarkThemeOr(false)) { - return _applyColorScheme( - colorScheme.copyWith( - background: Colors.black, - surface: Colors.grey[900], - ), - seedColor, - ); + return _applyColorScheme(colorScheme.copyWith( + background: Colors.black, + surface: Colors.grey[900], + )); } else { - return _applyColorScheme(colorScheme, seedColor); + return _applyColorScheme(colorScheme); } } -Color getSeedColor() { - return Color(Pref().getSeedColor() ?? 0xFF2196F3).withAlpha(0xFF); +Color? getSeedColor() { + return Pref().getSeedColor()?.run((c) => Color(c).withAlpha(0xFF)); } -ThemeData _applyColorScheme(ColorScheme colorScheme, Color seedColor) { +ColorScheme _getColorScheme(ColorScheme? dynamicScheme, Brightness brightness) { + var seedColor = Pref().getSeedColor(); + if (seedColor == null) { + if (dynamicScheme != null) { + return dynamicScheme; + } else { + seedColor = defaultSeedColor; + } + } + return ColorScheme.fromSeed( + seedColor: Color(seedColor), + brightness: brightness, + ); +} + +ThemeData _applyColorScheme(ColorScheme colorScheme) { return ThemeData( useMaterial3: true, brightness: colorScheme.brightness, @@ -212,7 +220,6 @@ ThemeData _applyColorScheme(ColorScheme colorScheme, Color seedColor) { ), extensions: [ M3( - seed: seedColor, checkbox: M3Checkbox( disabled: M3CheckboxDisabled( container: colorScheme.onSurface.withOpacity(.38), diff --git a/app/lib/widget/my_app.dart b/app/lib/widget/my_app.dart index 3e1760eb..a04e917d 100644 --- a/app/lib/widget/my_app.dart +++ b/app/lib/widget/my_app.dart @@ -1,3 +1,4 @@ +import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -13,6 +14,7 @@ import 'package:nc_photos/language_util.dart' as language_util; import 'package:nc_photos/legacy/connect.dart' as legacy; import 'package:nc_photos/legacy/sign_in.dart' as legacy; import 'package:nc_photos/navigation_manager.dart'; +import 'package:nc_photos/session_storage.dart'; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/stream_util.dart'; import 'package:nc_photos/theme.dart'; @@ -97,7 +99,7 @@ class _WrappedAppState extends State<_WrappedApp> } @override - build(BuildContext context) { + Widget build(BuildContext context) { final ThemeMode themeMode; if (Pref().isFollowSystemThemeOr(false)) { themeMode = ThemeMode.system; @@ -108,42 +110,50 @@ class _WrappedAppState extends State<_WrappedApp> final prefController = context.read(); return ValueStreamBuilder( stream: prefController.language, - builder: (context, snapshot) => MaterialApp( - onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle, - theme: buildLightTheme(), - darkTheme: buildDarkTheme(), - themeMode: themeMode, - initialRoute: Splash.routeName, - onGenerateRoute: _onGenerateRoute, - navigatorObservers: [MyApp.routeObserver], - navigatorKey: _navigatorKey, - scaffoldMessengerKey: _scaffoldMessengerKey, - locale: snapshot.requireData.locale, - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: const [ - // the order here doesn't matter, except for the first one, which must - // be en - Locale("en"), - Locale("el"), - Locale("es"), - Locale("fr"), - Locale("ru"), - Locale("de"), - Locale("cs"), - Locale("fi"), - Locale("pl"), - Locale("pt"), - Locale.fromSubtags(languageCode: "zh", scriptCode: "Hans"), - Locale.fromSubtags(languageCode: "zh", scriptCode: "Hant"), - Locale("it"), - Locale("nl"), - ], - builder: (context, child) { - MyApp._globalContext = context; - return child!; + builder: (context, snapshot) => DynamicColorBuilder( + builder: (lightDynamic, darkDynamic) { + if (lightDynamic != null) { + SessionStorage().isSupportDynamicColor = true; + } + return MaterialApp( + onGenerateTitle: (context) => + AppLocalizations.of(context)!.appTitle, + theme: buildLightTheme(lightDynamic), + darkTheme: buildDarkTheme(darkDynamic), + themeMode: themeMode, + initialRoute: Splash.routeName, + onGenerateRoute: _onGenerateRoute, + navigatorObservers: [MyApp.routeObserver], + navigatorKey: _navigatorKey, + scaffoldMessengerKey: _scaffoldMessengerKey, + locale: snapshot.requireData.locale, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: const [ + // the order here doesn't matter, except for the first one, which must + // be en + Locale("en"), + Locale("el"), + Locale("es"), + Locale("fr"), + Locale("ru"), + Locale("de"), + Locale("cs"), + Locale("fi"), + Locale("pl"), + Locale("pt"), + Locale.fromSubtags(languageCode: "zh", scriptCode: "Hans"), + Locale.fromSubtags(languageCode: "zh", scriptCode: "Hant"), + Locale("it"), + Locale("nl"), + ], + builder: (context, child) { + MyApp._globalContext = context; + return child!; + }, + debugShowCheckedModeBanner: false, + scrollBehavior: const _MyScrollBehavior(), + ); }, - debugShowCheckedModeBanner: false, - scrollBehavior: const _MyScrollBehavior(), ), ); } diff --git a/app/lib/widget/settings/theme/bloc.dart b/app/lib/widget/settings/theme/bloc.dart index 1b3b71e1..65b353b7 100644 --- a/app/lib/widget/settings/theme/bloc.dart +++ b/app/lib/widget/settings/theme/bloc.dart @@ -14,7 +14,7 @@ class _Bloc extends Bloc<_Event, _State> { super(_State( isFollowSystemTheme: c.pref.isFollowSystemThemeOr(false), isUseBlackInDarkTheme: c.pref.isUseBlackInDarkThemeOr(false), - seedColor: getSeedColor(), + seedColor: getSeedColor()?.value, )) { on<_SetFollowSystemTheme>(_onSetFollowSystemTheme); on<_SetUseBlackInDarkTheme>(_onSetUseBlackInDarkTheme); @@ -27,6 +27,7 @@ class _Bloc extends Bloc<_Event, _State> { Future _onSetFollowSystemTheme( _SetFollowSystemTheme ev, Emitter<_State> emit) async { + _log.info(ev); final oldValue = state.isFollowSystemTheme; emit(state.copyWith(isFollowSystemTheme: ev.value)); if (await _c.pref.setFollowSystemTheme(ev.value)) { @@ -40,6 +41,7 @@ class _Bloc extends Bloc<_Event, _State> { Future _onSetUseBlackInDarkTheme( _SetUseBlackInDarkTheme ev, Emitter<_State> emit) async { + _log.info(ev); final oldValue = state.isUseBlackInDarkTheme; emit(state.copyWith(isUseBlackInDarkTheme: ev.value)); if (await _c.pref.setUseBlackInDarkTheme(ev.value)) { @@ -54,9 +56,10 @@ class _Bloc extends Bloc<_Event, _State> { } Future _onSetSeedColor(_SetSeedColor ev, Emitter<_State> emit) async { + _log.info(ev); final oldValue = state.seedColor; - emit(state.copyWith(seedColor: ev.value)); - if (await _c.pref.setSeedColor(ev.value.withAlpha(0xFF).value)) { + emit(state.copyWith(seedColor: ev.value?.value)); + if (await _c.pref.setSeedColor(ev.value?.withAlpha(0xFF).value)) { KiwiContainer().resolve().fire(ThemeChangedEvent()); } else { _log.severe("[_onSetSeedColor] Failed writing pref"); diff --git a/app/lib/widget/settings/theme/state_event.dart b/app/lib/widget/settings/theme/state_event.dart index ecedc62b..e4d962a1 100644 --- a/app/lib/widget/settings/theme/state_event.dart +++ b/app/lib/widget/settings/theme/state_event.dart @@ -14,7 +14,8 @@ class _State { final bool isFollowSystemTheme; final bool isUseBlackInDarkTheme; - final Color seedColor; + // workaround analyzer bug where Color type can't be recognized + final int? seedColor; } abstract class _Event { @@ -49,5 +50,5 @@ class _SetSeedColor extends _Event { @override String toString() => _$toString(); - final Color value; + final Color? value; } diff --git a/app/lib/widget/settings/theme_settings.dart b/app/lib/widget/settings/theme_settings.dart index 9cf13086..f9a4400d 100644 --- a/app/lib/widget/settings/theme_settings.dart +++ b/app/lib/widget/settings/theme_settings.dart @@ -14,6 +14,7 @@ import 'package:nc_photos/event/event.dart'; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/mobile/android/android_info.dart'; import 'package:nc_photos/platform/k.dart' as platform_k; +import 'package:nc_photos/session_storage.dart'; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/theme.dart'; import 'package:np_codegen/np_codegen.dart'; @@ -23,6 +24,8 @@ part 'theme/bloc.dart'; part 'theme/state_event.dart'; part 'theme_settings.g.dart'; +typedef _BlocBuilder = BlocBuilder<_Bloc, _State>; + class ThemeSettings extends StatelessWidget { const ThemeSettings({super.key}); @@ -47,7 +50,7 @@ class _WrappedThemeSettingsState extends State<_WrappedThemeSettings> { @override void initState() { super.initState(); - _errorSubscription = context.read<_Bloc>().errorStream().listen((_) { + _errorSubscription = _bloc.errorStream().listen((_) { SnackBarManager().showSnackBar(SnackBar( content: Text(L10n.global().writePreferenceFailureNotification), duration: k.snackBarDurationNormal, @@ -80,25 +83,10 @@ class _WrappedThemeSettingsState extends State<_WrappedThemeSettings> { SliverList( delegate: SliverChildListDelegate( [ - BlocBuilder<_Bloc, _State>( - buildWhen: (previous, current) => - previous.seedColor != current.seedColor, - builder: (context, state) { - return ListTile( - title: Text(L10n.global().settingsSeedColorTitle), - subtitle: Text(L10n.global().settingsSeedColorDescription), - trailing: Icon( - Icons.circle, - size: 32, - color: state.seedColor, - ), - onTap: () => _onSeedColorPressed(context), - ); - }, - ), + const _SeedColorOption(), if (platform_k.isAndroid && AndroidInfo().sdkInt >= AndroidVersion.Q) - BlocBuilder<_Bloc, _State>( + _BlocBuilder( buildWhen: (previous, current) => previous.isFollowSystemTheme != current.isFollowSystemTheme, @@ -107,12 +95,12 @@ class _WrappedThemeSettingsState extends State<_WrappedThemeSettings> { title: Text(L10n.global().settingsFollowSystemThemeTitle), value: state.isFollowSystemTheme, onChanged: (value) { - context.read<_Bloc>().add(_SetFollowSystemTheme(value)); + _bloc.add(_SetFollowSystemTheme(value)); }, ); }, ), - BlocBuilder<_Bloc, _State>( + _BlocBuilder( buildWhen: (previous, current) => previous.isUseBlackInDarkTheme != current.isUseBlackInDarkTheme, @@ -126,7 +114,7 @@ class _WrappedThemeSettingsState extends State<_WrappedThemeSettings> { .settingsUseBlackInDarkThemeFalseDescription), value: state.isUseBlackInDarkTheme, onChanged: (value) { - context.read<_Bloc>().add( + _bloc.add( _SetUseBlackInDarkTheme(value, Theme.of(context))); }, ); @@ -139,20 +127,63 @@ class _WrappedThemeSettingsState extends State<_WrappedThemeSettings> { ); } + late final _bloc = context.read<_Bloc>(); + late final StreamSubscription _errorSubscription; +} + +class _SeedColorOption extends StatelessWidget { + const _SeedColorOption(); + + @override + Widget build(BuildContext context) { + return _BlocBuilder( + buildWhen: (previous, current) => previous.seedColor != current.seedColor, + builder: (context, state) { + if (SessionStorage().isSupportDynamicColor) { + return ListTile( + title: Text(L10n.global().settingsSeedColorTitle), + subtitle: Text(state.seedColor == null + ? L10n.global().settingsSeedColorSystemColorDescription + : L10n.global().settingsSeedColorDescription), + trailing: state.seedColor == null + ? null + : Icon( + Icons.circle, + size: 32, + color: Color(state.seedColor!), + ), + onTap: () => _onSeedColorPressed(context), + ); + } else { + return ListTile( + title: Text(L10n.global().settingsSeedColorTitle), + subtitle: Text(L10n.global().settingsSeedColorDescription), + trailing: Icon( + Icons.circle, + size: 32, + color: Color(state.seedColor ?? defaultSeedColor), + ), + onTap: () => _onSeedColorPressed(context), + ); + } + }, + ); + } + Future _onSeedColorPressed(BuildContext context) async { - final result = await showDialog( + final result = await showDialog( context: context, builder: (context) => const _SeedColorPicker(), ); if (result == null) { return; } - if (mounted) { - context.read<_Bloc>().add(_SetSeedColor(result)); + if (context.mounted) { + context + .read<_Bloc>() + .add(_SetSeedColor(result == -1 ? null : Color(result))); } } - - late final StreamSubscription _errorSubscription; } class _SeedColorPicker extends StatefulWidget { @@ -180,15 +211,24 @@ class _SeedColorPickerState extends State<_SeedColorPicker> { ] .map((c) => _SeedColorPickerItem( seedColor: c, - onSelected: () => _onItemSelected(context, c), + onSelected: () => _onItemSelected(context, c?.value), )) .toList(), ), + actions: SessionStorage().isSupportDynamicColor + ? [ + TextButton( + onPressed: () => _onItemSelected(context, -1), + child: Text(L10n.global() + .settingsSeedColorPickerSystemColorButtonLabel), + ), + ] + : null, ), ); } - Future _onItemSelected(BuildContext context, Color? seedColor) async { + Future _onItemSelected(BuildContext context, int? seedColor) async { if (seedColor != null) { Navigator.of(context).pop(seedColor); return; @@ -198,10 +238,10 @@ class _SeedColorPickerState extends State<_SeedColorPicker> { }); final color = await showDialog( context: context, - builder: (context) => const _SeedColorCustomPicker(), + builder: (_) => const _SeedColorCustomPicker(), barrierColor: Colors.transparent, ); - Navigator.of(context).pop(color); + Navigator.of(context).pop(color?.value); } var _isVisible = true; @@ -240,7 +280,7 @@ class _SeedColorCustomPickerState extends State<_SeedColorCustomPicker> { ); } - late Color _customColor = getSeedColor(); + late Color _customColor = getSeedColor() ?? const Color(defaultSeedColor); } class _SeedColorPickerItem extends StatelessWidget { diff --git a/app/lib/widget/settings/theme_settings.g.dart b/app/lib/widget/settings/theme_settings.g.dart index 12add8f7..a4a8a42f 100644 --- a/app/lib/widget/settings/theme_settings.g.dart +++ b/app/lib/widget/settings/theme_settings.g.dart @@ -14,9 +14,7 @@ part of 'theme_settings.dart'; abstract class $_StateCopyWithWorker { _State call( - {bool? isFollowSystemTheme, - bool? isUseBlackInDarkTheme, - Color? seedColor}); + {bool? isFollowSystemTheme, bool? isUseBlackInDarkTheme, int? seedColor}); } class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { @@ -26,13 +24,14 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { _State call( {dynamic isFollowSystemTheme, dynamic isUseBlackInDarkTheme, - dynamic seedColor}) { + dynamic seedColor = copyWithNull}) { return _State( isFollowSystemTheme: isFollowSystemTheme as bool? ?? that.isFollowSystemTheme, isUseBlackInDarkTheme: isUseBlackInDarkTheme as bool? ?? that.isUseBlackInDarkTheme, - seedColor: seedColor as Color? ?? that.seedColor); + seedColor: + seedColor == copyWithNull ? that.seedColor : seedColor as int?); } final _State that; diff --git a/app/pubspec.lock b/app/pubspec.lock index 6154c9a2..d152cc17 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -413,6 +413,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.8.0" + dynamic_color: + dependency: "direct main" + description: + name: dynamic_color + sha256: de4798a7069121aee12d5895315680258415de9b00e717723a1bd73d58f0126d + url: "https://pub.dev" + source: hosted + version: "1.6.6" equatable: dependency: "direct main" description: @@ -1779,4 +1787,4 @@ packages: version: "3.1.2" sdks: dart: ">=2.19.0 <3.0.0" - flutter: ">=3.3.0" + flutter: ">=3.4.0-17.0.pre" diff --git a/app/pubspec.yaml b/app/pubspec.yaml index a9a81348..5e0b7c54 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -53,6 +53,7 @@ dependencies: url: https://gitlab.com/nc-photos/flutter-draggable-scrollbar ref: v0.1.0-nc-photos-6 drift: 2.8.0 + dynamic_color: ^1.6.6 equatable: ^2.0.5 event_bus: ^2.0.0 exifdart: