feat: add theme color selection in setting dialog
This commit is contained in:
parent
8197a352aa
commit
7032f22327
|
|
@ -396,82 +396,13 @@
|
|||
"link": null,
|
||||
"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",
|
||||
"type": "rectangle",
|
||||
"x": 561.2432261444915,
|
||||
"y": 505.261726567147,
|
||||
"width": 146.7306357461261,
|
||||
"height": 40.63309912969646,
|
||||
"y": 509.59787769019385,
|
||||
"width": 123.56657324612611,
|
||||
"height": 36.296948006649586,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1f2937",
|
||||
"backgroundColor": "#ffffff",
|
||||
|
|
@ -487,11 +418,11 @@
|
|||
"index": "aD",
|
||||
"roundness": null,
|
||||
"seed": 1608525080,
|
||||
"version": 101,
|
||||
"versionNonce": 679299830,
|
||||
"version": 114,
|
||||
"versionNonce": 1580272529,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1756647235527,
|
||||
"updated": 1758364887319,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
|
|
@ -500,7 +431,7 @@
|
|||
"type": "text",
|
||||
"x": 601.8763252741879,
|
||||
"y": 514.291304151524,
|
||||
"width": 39.54961113185798,
|
||||
"width": 45.983367919921875,
|
||||
"height": 24.379859477817877,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1f2937",
|
||||
|
|
@ -517,20 +448,20 @@
|
|||
"index": "aE",
|
||||
"roundness": null,
|
||||
"seed": 533447192,
|
||||
"version": 103,
|
||||
"versionNonce": 554272618,
|
||||
"version": 111,
|
||||
"versionNonce": 935775633,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 1756647235527,
|
||||
"updated": 1758364882876,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "Save",
|
||||
"text": "Close",
|
||||
"fontSize": 18.059155168753982,
|
||||
"fontFamily": 6,
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"containerId": null,
|
||||
"originalText": "Save",
|
||||
"originalText": "Close",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.35
|
||||
},
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ Route: root --> settings
|
|||
Design notes:
|
||||
- Opened via "Configure" button in the right of top bar.
|
||||
- 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:
|
||||
|
||||
|
|
|
|||
|
|
@ -108,15 +108,37 @@ class PreferencesStateNotifier extends StateNotifier<PreferencesState> {
|
|||
// (useful if some code persisted mat.toString()).
|
||||
for (final mc in Colors.primaries) {
|
||||
if (mc.toString() == v) {
|
||||
return Color(mc.value);
|
||||
return mc; // MaterialColor extends Color
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static String _toHex(Color c) =>
|
||||
'#${c.value.toRadixString(16).padLeft(8, '0').toUpperCase()}';
|
||||
static String _toHex(Color c) {
|
||||
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)
|
||||
: super(
|
||||
PreferencesState(
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ class SettingsDialog extends ConsumerStatefulWidget {
|
|||
|
||||
class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||
String? _theme;
|
||||
String? _themeColor;
|
||||
String? _language;
|
||||
// Page view removed; continuous-only
|
||||
double? _exportDpi;
|
||||
|
|
@ -22,7 +21,6 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
super.initState();
|
||||
final prefs = ref.read(preferencesRepositoryProvider);
|
||||
_theme = prefs.theme;
|
||||
_themeColor = prefs.theme_color;
|
||||
_language = prefs.language;
|
||||
_exportDpi = prefs.exportDpi;
|
||||
// pageView no longer configurable (continuous-only)
|
||||
|
|
@ -74,8 +72,8 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
error: (_, _) {
|
||||
final items =
|
||||
error: (_, __) {
|
||||
final tags =
|
||||
AppLocalizations.supportedLocales
|
||||
.map((loc) => toLanguageTag(loc))
|
||||
.toList()
|
||||
|
|
@ -85,19 +83,27 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
isExpanded: true,
|
||||
value: _language,
|
||||
items:
|
||||
items
|
||||
tags
|
||||
.map(
|
||||
(tag) => DropdownMenuItem(
|
||||
(tag) => DropdownMenuItem<String>(
|
||||
value: tag,
|
||||
child: Text(tag),
|
||||
),
|
||||
)
|
||||
.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) {
|
||||
final items =
|
||||
final tags =
|
||||
AppLocalizations.supportedLocales
|
||||
.map((loc) => toLanguageTag(loc))
|
||||
.toList()
|
||||
|
|
@ -107,7 +113,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
isExpanded: true,
|
||||
value: _language,
|
||||
items:
|
||||
items
|
||||
tags
|
||||
.map(
|
||||
(tag) => DropdownMenuItem<String>(
|
||||
value: tag,
|
||||
|
|
@ -115,7 +121,15 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
),
|
||||
)
|
||||
.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(),
|
||||
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),
|
||||
),
|
||||
],
|
||||
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
|
||||
.read(preferencesRepositoryProvider.notifier)
|
||||
.setThemeColor(value);
|
||||
setState(() => _themeColor = value);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
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),
|
||||
),
|
||||
],
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: FilledButton.tonal(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text(l.close),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -282,26 +289,49 @@ class _ThemeColorPickerDialog extends StatelessWidget {
|
|||
child: Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: Colors.primaries.map((mat) {
|
||||
final c = Color(mat.value);
|
||||
final selected = c.value == currentColor.value;
|
||||
// Store as ARGB hex string, e.g., #FF2196F3
|
||||
String hex(Color color) =>
|
||||
'#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}';
|
||||
return InkWell(
|
||||
key: Key('pick_${mat.value}'),
|
||||
onTap: () => Navigator.of(context).pop(hex(c)),
|
||||
customBorder: const CircleBorder(),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
_ColorDot(color: c, size: 32),
|
||||
if (selected)
|
||||
const Icon(Icons.check, color: Colors.white, size: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
children:
|
||||
Colors.primaries.map((mat) {
|
||||
final Color c = mat; // MaterialColor is a Color
|
||||
final selected = c == currentColor;
|
||||
// Store as ARGB hex string, e.g., #FF2196F3
|
||||
String hex(Color color) {
|
||||
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(
|
||||
key: Key('pick_${hex(c)}'),
|
||||
onTap: () => Navigator.of(context).pop(hex(c)),
|
||||
customBorder: const CircleBorder(),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
_ColorDot(color: c, size: 32),
|
||||
if (selected)
|
||||
const Icon(Icons.check, color: Colors.white, size: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
|
|
|
|||
Loading…
Reference in New Issue