feat: add theme color selection in setting dialog
This commit is contained in:
parent
8197a352aa
commit
7032f22327
|
|
@ -396,82 +396,13 @@
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false
|
"locked": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "Q0v5ejctIV2msui0iDFEg",
|
|
||||||
"type": "rectangle",
|
|
||||||
"x": 414.5125903983653,
|
|
||||||
"y": 505.261726567147,
|
|
||||||
"width": 124.15669178518363,
|
|
||||||
"height": 40.63309912969646,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#1f2937",
|
|
||||||
"backgroundColor": "#ffffff",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [
|
|
||||||
"nQmqS53zA9IffPy8AAZwV"
|
|
||||||
],
|
|
||||||
"frameId": null,
|
|
||||||
"index": "aB",
|
|
||||||
"roundness": null,
|
|
||||||
"seed": 625347352,
|
|
||||||
"version": 101,
|
|
||||||
"versionNonce": 1373172150,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1756647235527,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "QSD6mQUNvCKRLZtin0AHX",
|
|
||||||
"type": "text",
|
|
||||||
"x": 442.73002034954345,
|
|
||||||
"y": 514.291304151524,
|
|
||||||
"width": 55.13471219456543,
|
|
||||||
"height": 24.379859477817877,
|
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#1f2937",
|
|
||||||
"backgroundColor": "#ffffff",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"groupIds": [
|
|
||||||
"nQmqS53zA9IffPy8AAZwV"
|
|
||||||
],
|
|
||||||
"frameId": null,
|
|
||||||
"index": "aC",
|
|
||||||
"roundness": null,
|
|
||||||
"seed": 1267001368,
|
|
||||||
"version": 103,
|
|
||||||
"versionNonce": 162573482,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1756647235527,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"text": "Cancel",
|
|
||||||
"fontSize": 18.059155168753982,
|
|
||||||
"fontFamily": 6,
|
|
||||||
"textAlign": "left",
|
|
||||||
"verticalAlign": "top",
|
|
||||||
"containerId": null,
|
|
||||||
"originalText": "Cancel",
|
|
||||||
"autoResize": true,
|
|
||||||
"lineHeight": 1.35
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "fmP0hKBOaNa5Ge12TEwyD",
|
"id": "fmP0hKBOaNa5Ge12TEwyD",
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"x": 561.2432261444915,
|
"x": 561.2432261444915,
|
||||||
"y": 505.261726567147,
|
"y": 509.59787769019385,
|
||||||
"width": 146.7306357461261,
|
"width": 123.56657324612611,
|
||||||
"height": 40.63309912969646,
|
"height": 36.296948006649586,
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"strokeColor": "#1f2937",
|
"strokeColor": "#1f2937",
|
||||||
"backgroundColor": "#ffffff",
|
"backgroundColor": "#ffffff",
|
||||||
|
|
@ -487,11 +418,11 @@
|
||||||
"index": "aD",
|
"index": "aD",
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"seed": 1608525080,
|
"seed": 1608525080,
|
||||||
"version": 101,
|
"version": 114,
|
||||||
"versionNonce": 679299830,
|
"versionNonce": 1580272529,
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
"updated": 1756647235527,
|
"updated": 1758364887319,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false
|
"locked": false
|
||||||
},
|
},
|
||||||
|
|
@ -500,7 +431,7 @@
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"x": 601.8763252741879,
|
"x": 601.8763252741879,
|
||||||
"y": 514.291304151524,
|
"y": 514.291304151524,
|
||||||
"width": 39.54961113185798,
|
"width": 45.983367919921875,
|
||||||
"height": 24.379859477817877,
|
"height": 24.379859477817877,
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"strokeColor": "#1f2937",
|
"strokeColor": "#1f2937",
|
||||||
|
|
@ -517,20 +448,20 @@
|
||||||
"index": "aE",
|
"index": "aE",
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"seed": 533447192,
|
"seed": 533447192,
|
||||||
"version": 103,
|
"version": 111,
|
||||||
"versionNonce": 554272618,
|
"versionNonce": 935775633,
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
"updated": 1756647235527,
|
"updated": 1758364882876,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"text": "Save",
|
"text": "Close",
|
||||||
"fontSize": 18.059155168753982,
|
"fontSize": 18.059155168753982,
|
||||||
"fontFamily": 6,
|
"fontFamily": 6,
|
||||||
"textAlign": "left",
|
"textAlign": "left",
|
||||||
"verticalAlign": "top",
|
"verticalAlign": "top",
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
"originalText": "Save",
|
"originalText": "Close",
|
||||||
"autoResize": true,
|
"autoResize": true,
|
||||||
"lineHeight": 1.35
|
"lineHeight": 1.35
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ Route: root --> settings
|
||||||
Design notes:
|
Design notes:
|
||||||
- Opened via "Configure" button in the right of top bar.
|
- Opened via "Configure" button in the right of top bar.
|
||||||
- Model with simple sections (e.g., General, Display).
|
- Model with simple sections (e.g., General, Display).
|
||||||
- Primary action to save, secondary to cancel.
|
- When select option, option will take effect immediately.
|
||||||
|
- A button to close the dialog and return to the previous screen.
|
||||||
|
|
||||||
Illustration:
|
Illustration:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,15 +108,37 @@ class PreferencesStateNotifier extends StateNotifier<PreferencesState> {
|
||||||
// (useful if some code persisted mat.toString()).
|
// (useful if some code persisted mat.toString()).
|
||||||
for (final mc in Colors.primaries) {
|
for (final mc in Colors.primaries) {
|
||||||
if (mc.toString() == v) {
|
if (mc.toString() == v) {
|
||||||
return Color(mc.value);
|
return mc; // MaterialColor extends Color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String _toHex(Color c) =>
|
static String _toHex(Color c) {
|
||||||
'#${c.value.toRadixString(16).padLeft(8, '0').toUpperCase()}';
|
final a =
|
||||||
|
((c.a * 255.0).round() & 0xff)
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
final r =
|
||||||
|
((c.r * 255.0).round() & 0xff)
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
final g =
|
||||||
|
((c.g * 255.0).round() & 0xff)
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
final b =
|
||||||
|
((c.b * 255.0).round() & 0xff)
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
return '#$a$r$g$b';
|
||||||
|
}
|
||||||
|
|
||||||
PreferencesStateNotifier(this.prefs)
|
PreferencesStateNotifier(this.prefs)
|
||||||
: super(
|
: super(
|
||||||
PreferencesState(
|
PreferencesState(
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ class SettingsDialog extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
String? _theme;
|
String? _theme;
|
||||||
String? _themeColor;
|
|
||||||
String? _language;
|
String? _language;
|
||||||
// Page view removed; continuous-only
|
// Page view removed; continuous-only
|
||||||
double? _exportDpi;
|
double? _exportDpi;
|
||||||
|
|
@ -22,7 +21,6 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
super.initState();
|
super.initState();
|
||||||
final prefs = ref.read(preferencesRepositoryProvider);
|
final prefs = ref.read(preferencesRepositoryProvider);
|
||||||
_theme = prefs.theme;
|
_theme = prefs.theme;
|
||||||
_themeColor = prefs.theme_color;
|
|
||||||
_language = prefs.language;
|
_language = prefs.language;
|
||||||
_exportDpi = prefs.exportDpi;
|
_exportDpi = prefs.exportDpi;
|
||||||
// pageView no longer configurable (continuous-only)
|
// pageView no longer configurable (continuous-only)
|
||||||
|
|
@ -74,8 +72,8 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
error: (_, _) {
|
error: (_, __) {
|
||||||
final items =
|
final tags =
|
||||||
AppLocalizations.supportedLocales
|
AppLocalizations.supportedLocales
|
||||||
.map((loc) => toLanguageTag(loc))
|
.map((loc) => toLanguageTag(loc))
|
||||||
.toList()
|
.toList()
|
||||||
|
|
@ -85,19 +83,27 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
value: _language,
|
value: _language,
|
||||||
items:
|
items:
|
||||||
items
|
tags
|
||||||
.map(
|
.map(
|
||||||
(tag) => DropdownMenuItem(
|
(tag) => DropdownMenuItem<String>(
|
||||||
value: tag,
|
value: tag,
|
||||||
child: Text(tag),
|
child: Text(tag),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (v) => setState(() => _language = v),
|
onChanged: (v) async {
|
||||||
|
if (v == null) return;
|
||||||
|
setState(() => _language = v);
|
||||||
|
await ref
|
||||||
|
.read(
|
||||||
|
preferencesRepositoryProvider.notifier,
|
||||||
|
)
|
||||||
|
.setLanguage(v);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
data: (names) {
|
data: (names) {
|
||||||
final items =
|
final tags =
|
||||||
AppLocalizations.supportedLocales
|
AppLocalizations.supportedLocales
|
||||||
.map((loc) => toLanguageTag(loc))
|
.map((loc) => toLanguageTag(loc))
|
||||||
.toList()
|
.toList()
|
||||||
|
|
@ -107,7 +113,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
value: _language,
|
value: _language,
|
||||||
items:
|
items:
|
||||||
items
|
tags
|
||||||
.map(
|
.map(
|
||||||
(tag) => DropdownMenuItem<String>(
|
(tag) => DropdownMenuItem<String>(
|
||||||
value: tag,
|
value: tag,
|
||||||
|
|
@ -115,7 +121,15 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (v) => setState(() => _language = v),
|
onChanged: (v) async {
|
||||||
|
if (v == null) return;
|
||||||
|
setState(() => _language = v);
|
||||||
|
await ref
|
||||||
|
.read(
|
||||||
|
preferencesRepositoryProvider.notifier,
|
||||||
|
)
|
||||||
|
.setLanguage(v);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -140,7 +154,13 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (v) => setState(() => _exportDpi = v),
|
onChanged: (v) async {
|
||||||
|
if (v == null) return;
|
||||||
|
setState(() => _exportDpi = v);
|
||||||
|
await ref
|
||||||
|
.read(preferencesRepositoryProvider.notifier)
|
||||||
|
.setExportDpi(v);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -171,7 +191,13 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
child: Text(l.themeSystem),
|
child: Text(l.themeSystem),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
onChanged: (v) => setState(() => _theme = v),
|
onChanged: (v) async {
|
||||||
|
if (v == null) return;
|
||||||
|
setState(() => _theme = v);
|
||||||
|
await ref
|
||||||
|
.read(preferencesRepositoryProvider.notifier)
|
||||||
|
.setTheme(v);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -187,37 +213,18 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
await ref
|
await ref
|
||||||
.read(preferencesRepositoryProvider.notifier)
|
.read(preferencesRepositoryProvider.notifier)
|
||||||
.setThemeColor(value);
|
.setThemeColor(value);
|
||||||
setState(() => _themeColor = value);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Row(
|
Align(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
alignment: Alignment.centerRight,
|
||||||
children: [
|
child: FilledButton.tonal(
|
||||||
OutlinedButton(
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
child: Text(l.close),
|
||||||
child: Text(l.cancel),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: () async {
|
|
||||||
final n = ref.read(
|
|
||||||
preferencesRepositoryProvider.notifier,
|
|
||||||
);
|
|
||||||
if (_theme != null) await n.setTheme(_theme!);
|
|
||||||
if (_themeColor != null)
|
|
||||||
await n.setThemeColor(_themeColor!);
|
|
||||||
if (_language != null) await n.setLanguage(_language!);
|
|
||||||
if (_exportDpi != null) await n.setExportDpi(_exportDpi!);
|
|
||||||
// pageView not configurable anymore
|
|
||||||
if (mounted) Navigator.of(context).pop(true);
|
|
||||||
},
|
|
||||||
child: Text(l.save),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -282,14 +289,37 @@ class _ThemeColorPickerDialog extends StatelessWidget {
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
spacing: 12,
|
spacing: 12,
|
||||||
runSpacing: 12,
|
runSpacing: 12,
|
||||||
children: Colors.primaries.map((mat) {
|
children:
|
||||||
final c = Color(mat.value);
|
Colors.primaries.map((mat) {
|
||||||
final selected = c.value == currentColor.value;
|
final Color c = mat; // MaterialColor is a Color
|
||||||
|
final selected = c == currentColor;
|
||||||
// Store as ARGB hex string, e.g., #FF2196F3
|
// Store as ARGB hex string, e.g., #FF2196F3
|
||||||
String hex(Color color) =>
|
String hex(Color color) {
|
||||||
'#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}';
|
final a =
|
||||||
|
((color.a * 255.0).round() & 0xff)
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
final r =
|
||||||
|
((color.r * 255.0).round() & 0xff)
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
final g =
|
||||||
|
((color.g * 255.0).round() & 0xff)
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
final b =
|
||||||
|
((color.b * 255.0).round() & 0xff)
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0')
|
||||||
|
.toUpperCase();
|
||||||
|
return '#$a$r$g$b';
|
||||||
|
}
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
key: Key('pick_${mat.value}'),
|
key: Key('pick_${hex(c)}'),
|
||||||
onTap: () => Navigator.of(context).pop(hex(c)),
|
onTap: () => Navigator.of(context).pop(hex(c)),
|
||||||
customBorder: const CircleBorder(),
|
customBorder: const CircleBorder(),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue