Add alt color schemes that can be picked in settings

This commit is contained in:
Ming Ming 2022-11-13 01:19:28 +08:00
parent 86cdc1f9c8
commit 597961c821
7 changed files with 237 additions and 80 deletions

View file

@ -1,69 +0,0 @@
import 'package:flutter/material.dart';
const lightColorScheme = ColorScheme(
brightness: Brightness.light,
primary: Color(0xFF0061A4),
onPrimary: Color(0xFFFFFFFF),
primaryContainer: Color(0xFFD1E4FF),
onPrimaryContainer: Color(0xFF001D36),
secondary: Color(0xFF535F70),
onSecondary: Color(0xFFFFFFFF),
secondaryContainer: Color(0xFFD7E3F7),
onSecondaryContainer: Color(0xFF101C2B),
tertiary: Color(0xFF6B5778),
onTertiary: Color(0xFFFFFFFF),
tertiaryContainer: Color(0xFFF2DAFF),
onTertiaryContainer: Color(0xFF251431),
error: Color(0xFFBA1A1A),
errorContainer: Color(0xFFFFDAD6),
onError: Color(0xFFFFFFFF),
onErrorContainer: Color(0xFF410002),
background: Color(0xFFFDFCFF),
onBackground: Color(0xFF1A1C1E),
surface: Color(0xFFFDFCFF),
onSurface: Color(0xFF1A1C1E),
surfaceVariant: Color(0xFFDFE2EB),
onSurfaceVariant: Color(0xFF43474E),
outline: Color(0xFF73777F),
onInverseSurface: Color(0xFFF1F0F4),
inverseSurface: Color(0xFF2F3033),
inversePrimary: Color(0xFF9ECAFF),
shadow: Color(0xFF000000),
surfaceTint: Color(0xFF0061A4),
// outlineVariant: Color(0xFFC3C7CF),
// scrim: Color(0xFF000000),
);
const darkColorScheme = ColorScheme(
brightness: Brightness.dark,
primary: Color(0xFF9ECAFF),
onPrimary: Color(0xFF003258),
primaryContainer: Color(0xFF00497D),
onPrimaryContainer: Color(0xFFD1E4FF),
secondary: Color(0xFFBBC7DB),
onSecondary: Color(0xFF253140),
secondaryContainer: Color(0xFF3B4858),
onSecondaryContainer: Color(0xFFD7E3F7),
tertiary: Color(0xFFD6BEE4),
onTertiary: Color(0xFF3B2948),
tertiaryContainer: Color(0xFF523F5F),
onTertiaryContainer: Color(0xFFF2DAFF),
error: Color(0xFFFFB4AB),
errorContainer: Color(0xFF93000A),
onError: Color(0xFF690005),
onErrorContainer: Color(0xFFFFDAD6),
background: Color(0xFF1A1C1E),
onBackground: Color(0xFFE2E2E6),
surface: Color(0xFF1A1C1E),
onSurface: Color(0xFFE2E2E6),
surfaceVariant: Color(0xFF43474E),
onSurfaceVariant: Color(0xFFC3C7CF),
outline: Color(0xFF8D9199),
onInverseSurface: Color(0xFF1A1C1E),
inverseSurface: Color(0xFFE2E2E6),
inversePrimary: Color(0xFF0061A4),
shadow: Color(0xFF000000),
surfaceTint: Color(0xFF9ECAFF),
// outlineVariant: Color(0xFF43474E),
// scrim: Color(0xFF000000),
);

View file

@ -406,6 +406,14 @@
"@settingsFollowSystemThemeTitle": {
"description": "Respect the system dark mode settings introduced on Android 10"
},
"settingsSeedColorTitle": "Theme color",
"@settingsSeedColorTitle": {
"description": "Customize the colors used in app"
},
"settingsSeedColorPickerTitle": "Pick a color",
"@settingsSeedColorPickerTitle": {
"description": "Dialog to customize the colors used in app"
},
"settingsUseBlackInDarkThemeTitle": "Darker theme",
"@settingsUseBlackInDarkThemeTitle": {
"description": "Make the dark theme darker"

View file

@ -29,6 +29,8 @@
"settingsImageEditSaveResultsToServerTitle",
"settingsImageEditSaveResultsToServerTrueDescription",
"settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle",
"settingsSeedColorPickerTitle",
"settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle",
@ -207,6 +209,8 @@
"settingsImageEditSaveResultsToServerTitle",
"settingsImageEditSaveResultsToServerTrueDescription",
"settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle",
"settingsSeedColorPickerTitle",
"settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle",
@ -382,6 +386,8 @@
"settingsImageEditSaveResultsToServerTitle",
"settingsImageEditSaveResultsToServerTrueDescription",
"settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle",
"settingsSeedColorPickerTitle",
"settingsDoubleTapExitTitle",
"slideshowSetupDialogReverseTitle",
"shareMethodPreviewTitle",
@ -449,10 +455,17 @@
"es": [
"settingsEnhanceMaxResolutionTitle2",
"settingsSeedColorTitle",
"settingsSeedColorPickerTitle",
"rootPickerSkipConfirmationDialogContent2",
"slideshowSetupDialogReverseTitle"
],
"fi": [
"settingsSeedColorTitle",
"settingsSeedColorPickerTitle"
],
"fr": [
"collectionsTooltip",
"settingsLanguageOptionSystemDefaultLabel",
@ -470,6 +483,8 @@
"settingsImageEditSaveResultsToServerTitle",
"settingsImageEditSaveResultsToServerTrueDescription",
"settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle",
"settingsSeedColorPickerTitle",
"settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle",
@ -569,6 +584,8 @@
"settingsImageEditSaveResultsToServerTitle",
"settingsImageEditSaveResultsToServerTrueDescription",
"settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle",
"settingsSeedColorPickerTitle",
"settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle",
@ -686,6 +703,8 @@
"settingsImageEditSaveResultsToServerTitle",
"settingsImageEditSaveResultsToServerTrueDescription",
"settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle",
"settingsSeedColorPickerTitle",
"settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle",
@ -782,6 +801,8 @@
"settingsImageEditSaveResultsToServerTitle",
"settingsImageEditSaveResultsToServerTrueDescription",
"settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle",
"settingsSeedColorPickerTitle",
"settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle",
@ -878,6 +899,8 @@
"settingsImageEditSaveResultsToServerTitle",
"settingsImageEditSaveResultsToServerTrueDescription",
"settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle",
"settingsSeedColorPickerTitle",
"settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle",
@ -974,6 +997,8 @@
"settingsImageEditSaveResultsToServerTitle",
"settingsImageEditSaveResultsToServerTrueDescription",
"settingsImageEditSaveResultsToServerFalseDescription",
"settingsSeedColorTitle",
"settingsSeedColorPickerTitle",
"settingsMiscellaneousTitle",
"settingsDoubleTapExitTitle",
"settingsPhotosTabSortByNameTitle",

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
class M3 extends ThemeExtension<M3> {
const M3({
required this.seed,
required this.checkbox,
required this.filterChip,
required this.listTile,
@ -11,11 +12,13 @@ class M3 extends ThemeExtension<M3> {
@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,
@ -27,12 +30,14 @@ class M3 extends ThemeExtension<M3> {
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;

View file

@ -274,6 +274,11 @@ class Pref {
value,
(key, value) => provider.setBool(key, value));
int? getSeedColor() => provider.getInt(PrefKey.seedColor);
int getSeedColorOr(int def) => getSeedColor() ?? def;
Future<bool> setSeedColor(int value) => _set<int>(
PrefKey.seedColor, value, (key, value) => provider.setInt(key, value));
Future<bool> _set<T>(PrefKey key, T value,
Future<bool> Function(PrefKey key, T value) setFn) async {
if (await setFn(key, value)) {
@ -589,6 +594,7 @@ enum PrefKey {
saveEditResultToServer,
hasShownSaveEditResultDialog,
isSlideshowReverse,
seedColor,
// account pref
isEnableFaceRecognitionApp,
@ -666,6 +672,8 @@ extension on PrefKey {
return "hasShownSaveEditResultDialog";
case PrefKey.isSlideshowReverse:
return "isSlideshowReverse";
case PrefKey.seedColor:
return "seedColor";
// account pref
case PrefKey.isEnableFaceRecognitionApp:

View file

@ -1,8 +1,32 @@
import 'package:flutter/material.dart';
import 'package:nc_photos/color_schemes.g.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/material3.dart';
import 'package:nc_photos/pref.dart';
enum SeedColor {
// the order must NOT change
amber,
blue,
green,
purple,
red;
Color toColor() {
switch (this) {
case amber:
return const Color(0xFFFFC107);
case blue:
return const Color(0xFF2196F3);
case green:
return const Color(0xFF4CAF50);
case purple:
return const Color(0xFF9C27B0);
case red:
return const Color(0xFFF44336);
}
}
}
extension ThemeExtension on ThemeData {
double get widthLimitedContentMaxWidth => 550.0;
@ -56,21 +80,32 @@ ThemeData buildTheme(Brightness brightness) {
}
ThemeData buildLightTheme() {
final theme = _applyColorScheme(lightColorScheme);
return theme;
final seedColor = getSeedColor();
final color = seedColor.toColor();
final colorScheme = ColorScheme.fromSeed(
seedColor: color,
);
return _applyColorScheme(colorScheme, color);
}
ThemeData buildDarkTheme() {
final ThemeData theme;
final seedColor = getSeedColor();
final color = seedColor.toColor();
final colorScheme = ColorScheme.fromSeed(
seedColor: color,
brightness: Brightness.dark,
);
if (Pref().isUseBlackInDarkThemeOr(false)) {
theme = _applyColorScheme(darkColorScheme.copyWith(
background: Colors.black,
surface: Colors.grey[900],
));
return _applyColorScheme(
colorScheme.copyWith(
background: Colors.black,
surface: Colors.grey[900],
),
color,
);
} else {
theme = _applyColorScheme(darkColorScheme);
return _applyColorScheme(colorScheme, color);
}
return theme;
}
ThemeData buildDarkModeSwitchTheme(BuildContext context) {
@ -83,7 +118,23 @@ ThemeData buildDarkModeSwitchTheme(BuildContext context) {
);
}
ThemeData _applyColorScheme(ColorScheme colorScheme) {
SeedColor getSeedColor() {
final index = Pref().getSeedColor();
if (index == null) {
return SeedColor.blue;
} else {
SeedColor seedColor;
try {
seedColor = SeedColor.values[index];
} catch (e, stackTrace) {
_log.severe("[getSeedColor] Uncaught exception", e, stackTrace);
seedColor = SeedColor.blue;
}
return seedColor;
}
}
ThemeData _applyColorScheme(ColorScheme colorScheme, Color seedColor) {
return ThemeData(
useMaterial3: true,
brightness: colorScheme.brightness,
@ -173,6 +224,7 @@ ThemeData _applyColorScheme(ColorScheme colorScheme) {
),
extensions: [
M3(
seed: seedColor,
checkbox: M3Checkbox(
disabled: M3CheckboxDisabled(
container: colorScheme.onSurface.withOpacity(.38),
@ -194,3 +246,5 @@ ThemeData _applyColorScheme(ColorScheme colorScheme) {
],
);
}
final _log = Logger("theme");

View file

@ -22,6 +22,7 @@ import 'package:nc_photos/platform/notification.dart';
import 'package:nc_photos/pref.dart';
import 'package:nc_photos/service.dart';
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/url_launcher_util.dart';
import 'package:nc_photos/widget/fancy_option_picker.dart';
import 'package:nc_photos/widget/gps_map.dart';
@ -1473,6 +1474,7 @@ class _ThemeSettingsState extends State<_ThemeSettings> {
super.initState();
_isFollowSystemTheme = Pref().isFollowSystemThemeOr(false);
_isUseBlackInDarkTheme = Pref().isUseBlackInDarkThemeOr(false);
_seedColor = getSeedColor();
}
@override
@ -1494,6 +1496,15 @@ class _ThemeSettingsState extends State<_ThemeSettings> {
SliverList(
delegate: SliverChildListDelegate(
[
ListTile(
title: Text(L10n.global().settingsSeedColorTitle),
trailing: Icon(
Icons.circle,
size: 32,
color: _seedColor.toColor(),
),
onTap: () => _onSeedColorPressed(context),
),
if (platform_k.isAndroid &&
AndroidInfo().sdkInt >= AndroidVersion.Q)
SwitchListTile(
@ -1559,12 +1570,127 @@ class _ThemeSettingsState extends State<_ThemeSettings> {
}
}
Future<void> _onSeedColorPressed(BuildContext context) async {
final result = await showDialog<SeedColor>(
context: context,
builder: (context) => _SeedColorPicker(
initialColor: _seedColor,
),
);
if (result == null) {
return;
}
final oldValue = _seedColor;
setState(() {
_seedColor = result;
});
if (await Pref().setSeedColor(result.index)) {
KiwiContainer().resolve<EventBus>().fire(ThemeChangedEvent());
} else {
_log.severe("[_onSeedColorPressed] Failed writing pref");
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().writePreferenceFailureNotification),
duration: k.snackBarDurationNormal,
));
setState(() {
_seedColor = oldValue;
});
}
}
late bool _isFollowSystemTheme;
late bool _isUseBlackInDarkTheme;
late SeedColor _seedColor;
static final _log = Logger("widget.settings._ThemeSettingsState");
}
class _SeedColorPicker extends StatefulWidget {
const _SeedColorPicker({
required this.initialColor,
});
@override
State<StatefulWidget> createState() => _SeedColorPickerState();
final SeedColor initialColor;
}
class _SeedColorPickerState extends State<_SeedColorPicker> {
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(L10n.global().settingsSeedColorPickerTitle),
content: Wrap(
children: const [
SeedColor.red,
SeedColor.purple,
SeedColor.blue,
SeedColor.green,
SeedColor.amber,
]
.map((c) => _SeedColorPickerItem(
seedColor: c,
isSelected: c == widget.initialColor,
onSelected: () => _onItemSelected(context, c),
))
.toList(),
),
);
}
void _onItemSelected(BuildContext context, SeedColor seedColor) {
Navigator.of(context).pop(seedColor);
}
}
class _SeedColorPickerItem extends StatelessWidget {
const _SeedColorPickerItem({
required this.seedColor,
required this.isSelected,
this.onSelected,
});
@override
Widget build(BuildContext context) {
final content = SizedBox.square(
dimension: _size,
child: Stack(
alignment: Alignment.center,
children: [
if (isSelected)
Icon(
Icons.circle,
size: _size,
color: Theme.of(context).colorScheme.onSurface,
),
Icon(
Icons.circle,
size: _size - _size * .2,
color: seedColor.toColor(),
),
],
),
);
if (onSelected != null) {
return InkWell(
customBorder: const CircleBorder(),
onTap: onSelected,
child: content,
);
} else {
return content;
}
}
final SeedColor seedColor;
final bool isSelected;
final VoidCallback? onSelected;
static const _size = 56.0;
}
class _MiscSettings extends StatefulWidget {
const _MiscSettings({Key? key}) : super(key: key);