Support system theme color (aka Material You)

This commit is contained in:
Ming Ming 2023-07-24 21:06:59 +08:00
parent 2c50e06991
commit cadf9c7410
13 changed files with 216 additions and 109 deletions

View file

@ -251,8 +251,14 @@ extension PrefExtension on Pref {
int? getSeedColor() => provider.getInt(PrefKey.seedColor); int? getSeedColor() => provider.getInt(PrefKey.seedColor);
int getSeedColorOr(int def) => getSeedColor() ?? def; int getSeedColorOr(int def) => getSeedColor() ?? def;
Future<bool> setSeedColor(int value) => _set<int>( Future<bool> setSeedColor(int? value) {
PrefKey.seedColor, value, (key, value) => provider.setInt(key, value)); if (value == null) {
return _remove(PrefKey.seedColor);
} else {
return _set<int>(PrefKey.seedColor, value,
(key, value) => provider.setInt(key, value));
}
}
bool? isVideoPlayerMute() => provider.getBool(PrefKey.isVideoPlayerMute); bool? isVideoPlayerMute() => provider.getBool(PrefKey.isVideoPlayerMute);
bool isVideoPlayerMuteOr([bool def = false]) => isVideoPlayerMute() ?? def; bool isVideoPlayerMuteOr([bool def = false]) => isVideoPlayerMute() ?? def;

View file

@ -383,10 +383,18 @@
"@settingsSeedColorDescription": { "@settingsSeedColorDescription": {
"description": "Customize the colors used in app" "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": "Pick a color",
"@settingsSeedColorPickerTitle": { "@settingsSeedColorPickerTitle": {
"description": "Dialog to customize the colors used in app" "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": "Darker theme",
"@settingsUseBlackInDarkThemeTitle": { "@settingsUseBlackInDarkThemeTitle": {
"description": "Make the dark theme darker" "description": "Make the dark theme darker"

View file

@ -2,6 +2,8 @@
"cs": [ "cs": [
"nameInputInvalidEmpty", "nameInputInvalidEmpty",
"settingsPersonProviderTitle", "settingsPersonProviderTitle",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerSystemColorButtonLabel",
"settingsServerVersionTitle", "settingsServerVersionTitle",
"searchLandingPeopleListEmptyText2", "searchLandingPeopleListEmptyText2",
"createCollectionFailureNotification", "createCollectionFailureNotification",
@ -49,7 +51,9 @@
"settingsImageEditSaveResultsToServerFalseDescription", "settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle", "settingsSeedColorTitle",
"settingsSeedColorDescription", "settingsSeedColorDescription",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerTitle", "settingsSeedColorPickerTitle",
"settingsSeedColorPickerSystemColorButtonLabel",
"settingsMiscellaneousTitle", "settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle", "settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle", "settingsPhotosTabSortByNameTitle",
@ -242,7 +246,9 @@
"settingsImageEditSaveResultsToServerFalseDescription", "settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle", "settingsSeedColorTitle",
"settingsSeedColorDescription", "settingsSeedColorDescription",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerTitle", "settingsSeedColorPickerTitle",
"settingsSeedColorPickerSystemColorButtonLabel",
"settingsDoubleTapExitTitle", "settingsDoubleTapExitTitle",
"settingsExpertTitle", "settingsExpertTitle",
"settingsExpertWarningText", "settingsExpertWarningText",
@ -328,12 +334,16 @@
"es": [ "es": [
"settingsPersonProviderTitle", "settingsPersonProviderTitle",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerSystemColorButtonLabel",
"searchLandingPeopleListEmptyText2", "searchLandingPeopleListEmptyText2",
"accountSettingsTooltip" "accountSettingsTooltip"
], ],
"fi": [ "fi": [
"settingsPersonProviderTitle", "settingsPersonProviderTitle",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerSystemColorButtonLabel",
"searchLandingPeopleListEmptyText2", "searchLandingPeopleListEmptyText2",
"accountSettingsTooltip" "accountSettingsTooltip"
], ],
@ -362,7 +372,9 @@
"settingsImageEditSaveResultsToServerFalseDescription", "settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle", "settingsSeedColorTitle",
"settingsSeedColorDescription", "settingsSeedColorDescription",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerTitle", "settingsSeedColorPickerTitle",
"settingsSeedColorPickerSystemColorButtonLabel",
"settingsMiscellaneousTitle", "settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle", "settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle", "settingsPhotosTabSortByNameTitle",
@ -495,7 +507,9 @@
"settingsFollowSystemThemeTitle", "settingsFollowSystemThemeTitle",
"settingsSeedColorTitle", "settingsSeedColorTitle",
"settingsSeedColorDescription", "settingsSeedColorDescription",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerTitle", "settingsSeedColorPickerTitle",
"settingsSeedColorPickerSystemColorButtonLabel",
"settingsUseBlackInDarkThemeTitle", "settingsUseBlackInDarkThemeTitle",
"settingsUseBlackInDarkThemeTrueDescription", "settingsUseBlackInDarkThemeTrueDescription",
"settingsUseBlackInDarkThemeFalseDescription", "settingsUseBlackInDarkThemeFalseDescription",
@ -843,7 +857,9 @@
"settingsFollowSystemThemeTitle", "settingsFollowSystemThemeTitle",
"settingsSeedColorTitle", "settingsSeedColorTitle",
"settingsSeedColorDescription", "settingsSeedColorDescription",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerTitle", "settingsSeedColorPickerTitle",
"settingsSeedColorPickerSystemColorButtonLabel",
"settingsUseBlackInDarkThemeTitle", "settingsUseBlackInDarkThemeTitle",
"settingsUseBlackInDarkThemeTrueDescription", "settingsUseBlackInDarkThemeTrueDescription",
"settingsUseBlackInDarkThemeFalseDescription", "settingsUseBlackInDarkThemeFalseDescription",
@ -1145,7 +1161,9 @@
"settingsImageEditSaveResultsToServerFalseDescription", "settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle", "settingsSeedColorTitle",
"settingsSeedColorDescription", "settingsSeedColorDescription",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerTitle", "settingsSeedColorPickerTitle",
"settingsSeedColorPickerSystemColorButtonLabel",
"settingsMiscellaneousTitle", "settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle", "settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle", "settingsPhotosTabSortByNameTitle",
@ -1265,6 +1283,8 @@
"pt": [ "pt": [
"nameInputInvalidEmpty", "nameInputInvalidEmpty",
"settingsPersonProviderTitle", "settingsPersonProviderTitle",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerSystemColorButtonLabel",
"settingsServerVersionTitle", "settingsServerVersionTitle",
"searchLandingPeopleListEmptyText2", "searchLandingPeopleListEmptyText2",
"createCollectionFailureNotification", "createCollectionFailureNotification",
@ -1302,7 +1322,9 @@
"settingsImageEditSaveResultsToServerFalseDescription", "settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle", "settingsSeedColorTitle",
"settingsSeedColorDescription", "settingsSeedColorDescription",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerTitle", "settingsSeedColorPickerTitle",
"settingsSeedColorPickerSystemColorButtonLabel",
"settingsMiscellaneousTitle", "settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle", "settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle", "settingsPhotosTabSortByNameTitle",
@ -1424,7 +1446,9 @@
"settingsImageEditSaveResultsToServerFalseDescription", "settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle", "settingsSeedColorTitle",
"settingsSeedColorDescription", "settingsSeedColorDescription",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerTitle", "settingsSeedColorPickerTitle",
"settingsSeedColorPickerSystemColorButtonLabel",
"settingsMiscellaneousTitle", "settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle", "settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle", "settingsPhotosTabSortByNameTitle",
@ -1546,7 +1570,9 @@
"settingsImageEditSaveResultsToServerFalseDescription", "settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle", "settingsSeedColorTitle",
"settingsSeedColorDescription", "settingsSeedColorDescription",
"settingsSeedColorSystemColorDescription",
"settingsSeedColorPickerTitle", "settingsSeedColorPickerTitle",
"settingsSeedColorPickerSystemColorButtonLabel",
"settingsMiscellaneousTitle", "settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle", "settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle", "settingsPhotosTabSortByNameTitle",

View file

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
class M3 extends ThemeExtension<M3> { class M3 extends ThemeExtension<M3> {
const M3({ const M3({
required this.seed,
required this.checkbox, required this.checkbox,
required this.filterChip, required this.filterChip,
required this.listTile, required this.listTile,
@ -12,13 +11,11 @@ class M3 extends ThemeExtension<M3> {
@override @override
M3 copyWith({ M3 copyWith({
Color? seed,
M3Checkbox? checkbox, M3Checkbox? checkbox,
M3FilterChip? filterChip, M3FilterChip? filterChip,
M3ListTile? listTile, M3ListTile? listTile,
}) => }) =>
M3( M3(
seed: seed ?? this.seed,
checkbox: checkbox ?? this.checkbox, checkbox: checkbox ?? this.checkbox,
filterChip: filterChip ?? this.filterChip, filterChip: filterChip ?? this.filterChip,
listTile: listTile ?? this.listTile, listTile: listTile ?? this.listTile,
@ -30,14 +27,12 @@ class M3 extends ThemeExtension<M3> {
return this; return this;
} }
return M3( return M3(
seed: Color.lerp(seed, other.seed, t)!,
checkbox: checkbox.lerp(other.checkbox, t), checkbox: checkbox.lerp(other.checkbox, t),
filterChip: filterChip.lerp(other.filterChip, t), filterChip: filterChip.lerp(other.filterChip, t),
listTile: listTile.lerp(other.listTile, t), listTile: listTile.lerp(other.listTile, t),
); );
} }
final Color seed;
final M3Checkbox checkbox; final M3Checkbox checkbox;
final M3FilterChip filterChip; final M3FilterChip filterChip;
final M3ListTile listTile; final M3ListTile listTile;

View file

@ -12,5 +12,8 @@ class SessionStorage {
/// Whether the drag to rearrange notification has been shown /// Whether the drag to rearrange notification has been shown
bool hasShowDragRearrangeNotification = false; bool hasShowDragRearrangeNotification = false;
/// Whether the dynamic_color library is supported in this platform
bool isSupportDynamicColor = false;
static final _inst = SessionStorage._(); static final _inst = SessionStorage._();
} }

View file

@ -3,6 +3,9 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:nc_photos/entity/pref.dart'; import 'package:nc_photos/entity/pref.dart';
import 'package:nc_photos/material3.dart'; import 'package:nc_photos/material3.dart';
import 'package:nc_photos/object_extension.dart';
const defaultSeedColor = 0xFF2196F3;
extension ThemeExtension on ThemeData { extension ThemeExtension on ThemeData {
double get widthLimitedContentMaxWidth => 550.0; double get widthLimitedContentMaxWidth => 550.0;
@ -90,38 +93,43 @@ ThemeData buildTheme(Brightness brightness) {
: buildDarkTheme(); : buildDarkTheme();
} }
ThemeData buildLightTheme() { ThemeData buildLightTheme([ColorScheme? dynamicScheme]) {
final seedColor = getSeedColor(); final colorScheme = _getColorScheme(dynamicScheme, Brightness.light);
final colorScheme = ColorScheme.fromSeed( return _applyColorScheme(colorScheme);
seedColor: seedColor,
);
return _applyColorScheme(colorScheme, seedColor);
} }
ThemeData buildDarkTheme() { ThemeData buildDarkTheme([ColorScheme? dynamicScheme]) {
final seedColor = getSeedColor(); final colorScheme = _getColorScheme(dynamicScheme, Brightness.dark);
final colorScheme = ColorScheme.fromSeed(
seedColor: seedColor,
brightness: Brightness.dark,
);
if (Pref().isUseBlackInDarkThemeOr(false)) { if (Pref().isUseBlackInDarkThemeOr(false)) {
return _applyColorScheme( return _applyColorScheme(colorScheme.copyWith(
colorScheme.copyWith(
background: Colors.black, background: Colors.black,
surface: Colors.grey[900], surface: Colors.grey[900],
), ));
seedColor,
);
} else { } else {
return _applyColorScheme(colorScheme, seedColor); return _applyColorScheme(colorScheme);
} }
} }
Color getSeedColor() { Color? getSeedColor() {
return Color(Pref().getSeedColor() ?? 0xFF2196F3).withAlpha(0xFF); 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( return ThemeData(
useMaterial3: true, useMaterial3: true,
brightness: colorScheme.brightness, brightness: colorScheme.brightness,
@ -212,7 +220,6 @@ ThemeData _applyColorScheme(ColorScheme colorScheme, Color seedColor) {
), ),
extensions: [ extensions: [
M3( M3(
seed: seedColor,
checkbox: M3Checkbox( checkbox: M3Checkbox(
disabled: M3CheckboxDisabled( disabled: M3CheckboxDisabled(
container: colorScheme.onSurface.withOpacity(.38), container: colorScheme.onSurface.withOpacity(.38),

View file

@ -1,3 +1,4 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/connect.dart' as legacy;
import 'package:nc_photos/legacy/sign_in.dart' as legacy; import 'package:nc_photos/legacy/sign_in.dart' as legacy;
import 'package:nc_photos/navigation_manager.dart'; 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/snack_bar_manager.dart';
import 'package:nc_photos/stream_util.dart'; import 'package:nc_photos/stream_util.dart';
import 'package:nc_photos/theme.dart'; import 'package:nc_photos/theme.dart';
@ -97,7 +99,7 @@ class _WrappedAppState extends State<_WrappedApp>
} }
@override @override
build(BuildContext context) { Widget build(BuildContext context) {
final ThemeMode themeMode; final ThemeMode themeMode;
if (Pref().isFollowSystemThemeOr(false)) { if (Pref().isFollowSystemThemeOr(false)) {
themeMode = ThemeMode.system; themeMode = ThemeMode.system;
@ -108,10 +110,16 @@ class _WrappedAppState extends State<_WrappedApp>
final prefController = context.read<PrefController>(); final prefController = context.read<PrefController>();
return ValueStreamBuilder<language_util.AppLanguage>( return ValueStreamBuilder<language_util.AppLanguage>(
stream: prefController.language, stream: prefController.language,
builder: (context, snapshot) => MaterialApp( builder: (context, snapshot) => DynamicColorBuilder(
onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle, builder: (lightDynamic, darkDynamic) {
theme: buildLightTheme(), if (lightDynamic != null) {
darkTheme: buildDarkTheme(), SessionStorage().isSupportDynamicColor = true;
}
return MaterialApp(
onGenerateTitle: (context) =>
AppLocalizations.of(context)!.appTitle,
theme: buildLightTheme(lightDynamic),
darkTheme: buildDarkTheme(darkDynamic),
themeMode: themeMode, themeMode: themeMode,
initialRoute: Splash.routeName, initialRoute: Splash.routeName,
onGenerateRoute: _onGenerateRoute, onGenerateRoute: _onGenerateRoute,
@ -144,6 +152,8 @@ class _WrappedAppState extends State<_WrappedApp>
}, },
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
scrollBehavior: const _MyScrollBehavior(), scrollBehavior: const _MyScrollBehavior(),
);
},
), ),
); );
} }

View file

@ -14,7 +14,7 @@ class _Bloc extends Bloc<_Event, _State> {
super(_State( super(_State(
isFollowSystemTheme: c.pref.isFollowSystemThemeOr(false), isFollowSystemTheme: c.pref.isFollowSystemThemeOr(false),
isUseBlackInDarkTheme: c.pref.isUseBlackInDarkThemeOr(false), isUseBlackInDarkTheme: c.pref.isUseBlackInDarkThemeOr(false),
seedColor: getSeedColor(), seedColor: getSeedColor()?.value,
)) { )) {
on<_SetFollowSystemTheme>(_onSetFollowSystemTheme); on<_SetFollowSystemTheme>(_onSetFollowSystemTheme);
on<_SetUseBlackInDarkTheme>(_onSetUseBlackInDarkTheme); on<_SetUseBlackInDarkTheme>(_onSetUseBlackInDarkTheme);
@ -27,6 +27,7 @@ class _Bloc extends Bloc<_Event, _State> {
Future<void> _onSetFollowSystemTheme( Future<void> _onSetFollowSystemTheme(
_SetFollowSystemTheme ev, Emitter<_State> emit) async { _SetFollowSystemTheme ev, Emitter<_State> emit) async {
_log.info(ev);
final oldValue = state.isFollowSystemTheme; final oldValue = state.isFollowSystemTheme;
emit(state.copyWith(isFollowSystemTheme: ev.value)); emit(state.copyWith(isFollowSystemTheme: ev.value));
if (await _c.pref.setFollowSystemTheme(ev.value)) { if (await _c.pref.setFollowSystemTheme(ev.value)) {
@ -40,6 +41,7 @@ class _Bloc extends Bloc<_Event, _State> {
Future<void> _onSetUseBlackInDarkTheme( Future<void> _onSetUseBlackInDarkTheme(
_SetUseBlackInDarkTheme ev, Emitter<_State> emit) async { _SetUseBlackInDarkTheme ev, Emitter<_State> emit) async {
_log.info(ev);
final oldValue = state.isUseBlackInDarkTheme; final oldValue = state.isUseBlackInDarkTheme;
emit(state.copyWith(isUseBlackInDarkTheme: ev.value)); emit(state.copyWith(isUseBlackInDarkTheme: ev.value));
if (await _c.pref.setUseBlackInDarkTheme(ev.value)) { if (await _c.pref.setUseBlackInDarkTheme(ev.value)) {
@ -54,9 +56,10 @@ class _Bloc extends Bloc<_Event, _State> {
} }
Future<void> _onSetSeedColor(_SetSeedColor ev, Emitter<_State> emit) async { Future<void> _onSetSeedColor(_SetSeedColor ev, Emitter<_State> emit) async {
_log.info(ev);
final oldValue = state.seedColor; final oldValue = state.seedColor;
emit(state.copyWith(seedColor: ev.value)); emit(state.copyWith(seedColor: ev.value?.value));
if (await _c.pref.setSeedColor(ev.value.withAlpha(0xFF).value)) { if (await _c.pref.setSeedColor(ev.value?.withAlpha(0xFF).value)) {
KiwiContainer().resolve<EventBus>().fire(ThemeChangedEvent()); KiwiContainer().resolve<EventBus>().fire(ThemeChangedEvent());
} else { } else {
_log.severe("[_onSetSeedColor] Failed writing pref"); _log.severe("[_onSetSeedColor] Failed writing pref");

View file

@ -14,7 +14,8 @@ class _State {
final bool isFollowSystemTheme; final bool isFollowSystemTheme;
final bool isUseBlackInDarkTheme; final bool isUseBlackInDarkTheme;
final Color seedColor; // workaround analyzer bug where Color type can't be recognized
final int? seedColor;
} }
abstract class _Event { abstract class _Event {
@ -49,5 +50,5 @@ class _SetSeedColor extends _Event {
@override @override
String toString() => _$toString(); String toString() => _$toString();
final Color value; final Color? value;
} }

View file

@ -14,6 +14,7 @@ import 'package:nc_photos/event/event.dart';
import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/android/android_info.dart'; import 'package:nc_photos/mobile/android/android_info.dart';
import 'package:nc_photos/platform/k.dart' as platform_k; 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/snack_bar_manager.dart';
import 'package:nc_photos/theme.dart'; import 'package:nc_photos/theme.dart';
import 'package:np_codegen/np_codegen.dart'; import 'package:np_codegen/np_codegen.dart';
@ -23,6 +24,8 @@ part 'theme/bloc.dart';
part 'theme/state_event.dart'; part 'theme/state_event.dart';
part 'theme_settings.g.dart'; part 'theme_settings.g.dart';
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
class ThemeSettings extends StatelessWidget { class ThemeSettings extends StatelessWidget {
const ThemeSettings({super.key}); const ThemeSettings({super.key});
@ -47,7 +50,7 @@ class _WrappedThemeSettingsState extends State<_WrappedThemeSettings> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_errorSubscription = context.read<_Bloc>().errorStream().listen((_) { _errorSubscription = _bloc.errorStream().listen((_) {
SnackBarManager().showSnackBar(SnackBar( SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().writePreferenceFailureNotification), content: Text(L10n.global().writePreferenceFailureNotification),
duration: k.snackBarDurationNormal, duration: k.snackBarDurationNormal,
@ -80,25 +83,10 @@ class _WrappedThemeSettingsState extends State<_WrappedThemeSettings> {
SliverList( SliverList(
delegate: SliverChildListDelegate( delegate: SliverChildListDelegate(
[ [
BlocBuilder<_Bloc, _State>( const _SeedColorOption(),
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),
);
},
),
if (platform_k.isAndroid && if (platform_k.isAndroid &&
AndroidInfo().sdkInt >= AndroidVersion.Q) AndroidInfo().sdkInt >= AndroidVersion.Q)
BlocBuilder<_Bloc, _State>( _BlocBuilder(
buildWhen: (previous, current) => buildWhen: (previous, current) =>
previous.isFollowSystemTheme != previous.isFollowSystemTheme !=
current.isFollowSystemTheme, current.isFollowSystemTheme,
@ -107,12 +95,12 @@ class _WrappedThemeSettingsState extends State<_WrappedThemeSettings> {
title: Text(L10n.global().settingsFollowSystemThemeTitle), title: Text(L10n.global().settingsFollowSystemThemeTitle),
value: state.isFollowSystemTheme, value: state.isFollowSystemTheme,
onChanged: (value) { onChanged: (value) {
context.read<_Bloc>().add(_SetFollowSystemTheme(value)); _bloc.add(_SetFollowSystemTheme(value));
}, },
); );
}, },
), ),
BlocBuilder<_Bloc, _State>( _BlocBuilder(
buildWhen: (previous, current) => buildWhen: (previous, current) =>
previous.isUseBlackInDarkTheme != previous.isUseBlackInDarkTheme !=
current.isUseBlackInDarkTheme, current.isUseBlackInDarkTheme,
@ -126,7 +114,7 @@ class _WrappedThemeSettingsState extends State<_WrappedThemeSettings> {
.settingsUseBlackInDarkThemeFalseDescription), .settingsUseBlackInDarkThemeFalseDescription),
value: state.isUseBlackInDarkTheme, value: state.isUseBlackInDarkTheme,
onChanged: (value) { onChanged: (value) {
context.read<_Bloc>().add( _bloc.add(
_SetUseBlackInDarkTheme(value, Theme.of(context))); _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<void> _onSeedColorPressed(BuildContext context) async { Future<void> _onSeedColorPressed(BuildContext context) async {
final result = await showDialog<Color>( final result = await showDialog<int>(
context: context, context: context,
builder: (context) => const _SeedColorPicker(), builder: (context) => const _SeedColorPicker(),
); );
if (result == null) { if (result == null) {
return; return;
} }
if (mounted) { if (context.mounted) {
context.read<_Bloc>().add(_SetSeedColor(result)); context
.read<_Bloc>()
.add(_SetSeedColor(result == -1 ? null : Color(result)));
} }
} }
late final StreamSubscription _errorSubscription;
} }
class _SeedColorPicker extends StatefulWidget { class _SeedColorPicker extends StatefulWidget {
@ -180,15 +211,24 @@ class _SeedColorPickerState extends State<_SeedColorPicker> {
] ]
.map((c) => _SeedColorPickerItem( .map((c) => _SeedColorPickerItem(
seedColor: c, seedColor: c,
onSelected: () => _onItemSelected(context, c), onSelected: () => _onItemSelected(context, c?.value),
)) ))
.toList(), .toList(),
), ),
actions: SessionStorage().isSupportDynamicColor
? [
TextButton(
onPressed: () => _onItemSelected(context, -1),
child: Text(L10n.global()
.settingsSeedColorPickerSystemColorButtonLabel),
),
]
: null,
), ),
); );
} }
Future<void> _onItemSelected(BuildContext context, Color? seedColor) async { Future<void> _onItemSelected(BuildContext context, int? seedColor) async {
if (seedColor != null) { if (seedColor != null) {
Navigator.of(context).pop(seedColor); Navigator.of(context).pop(seedColor);
return; return;
@ -198,10 +238,10 @@ class _SeedColorPickerState extends State<_SeedColorPicker> {
}); });
final color = await showDialog<Color>( final color = await showDialog<Color>(
context: context, context: context,
builder: (context) => const _SeedColorCustomPicker(), builder: (_) => const _SeedColorCustomPicker(),
barrierColor: Colors.transparent, barrierColor: Colors.transparent,
); );
Navigator.of(context).pop(color); Navigator.of(context).pop(color?.value);
} }
var _isVisible = true; 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 { class _SeedColorPickerItem extends StatelessWidget {

View file

@ -14,9 +14,7 @@ part of 'theme_settings.dart';
abstract class $_StateCopyWithWorker { abstract class $_StateCopyWithWorker {
_State call( _State call(
{bool? isFollowSystemTheme, {bool? isFollowSystemTheme, bool? isUseBlackInDarkTheme, int? seedColor});
bool? isUseBlackInDarkTheme,
Color? seedColor});
} }
class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
@ -26,13 +24,14 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
_State call( _State call(
{dynamic isFollowSystemTheme, {dynamic isFollowSystemTheme,
dynamic isUseBlackInDarkTheme, dynamic isUseBlackInDarkTheme,
dynamic seedColor}) { dynamic seedColor = copyWithNull}) {
return _State( return _State(
isFollowSystemTheme: isFollowSystemTheme:
isFollowSystemTheme as bool? ?? that.isFollowSystemTheme, isFollowSystemTheme as bool? ?? that.isFollowSystemTheme,
isUseBlackInDarkTheme: isUseBlackInDarkTheme:
isUseBlackInDarkTheme as bool? ?? that.isUseBlackInDarkTheme, isUseBlackInDarkTheme as bool? ?? that.isUseBlackInDarkTheme,
seedColor: seedColor as Color? ?? that.seedColor); seedColor:
seedColor == copyWithNull ? that.seedColor : seedColor as int?);
} }
final _State that; final _State that;

View file

@ -413,6 +413,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.8.0" 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: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1779,4 +1787,4 @@ packages:
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=2.19.0 <3.0.0" dart: ">=2.19.0 <3.0.0"
flutter: ">=3.3.0" flutter: ">=3.4.0-17.0.pre"

View file

@ -53,6 +53,7 @@ dependencies:
url: https://gitlab.com/nc-photos/flutter-draggable-scrollbar url: https://gitlab.com/nc-photos/flutter-draggable-scrollbar
ref: v0.1.0-nc-photos-6 ref: v0.1.0-nc-photos-6
drift: 2.8.0 drift: 2.8.0
dynamic_color: ^1.6.6
equatable: ^2.0.5 equatable: ^2.0.5
event_bus: ^2.0.0 event_bus: ^2.0.0
exifdart: exifdart: