diff --git a/docs/wireframe.assets/with_configure_screen.excalidraw b/docs/wireframe.assets/with_configure_screen.excalidraw index d8fb0af..3bf29cf 100644 --- a/docs/wireframe.assets/with_configure_screen.excalidraw +++ b/docs/wireframe.assets/with_configure_screen.excalidraw @@ -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 }, diff --git a/docs/wireframe.md b/docs/wireframe.md index 846c575..0c23453 100644 --- a/docs/wireframe.md +++ b/docs/wireframe.md @@ -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: diff --git a/lib/data/repositories/preferences_repository.dart b/lib/data/repositories/preferences_repository.dart index a6d28e6..2980047 100644 --- a/lib/data/repositories/preferences_repository.dart +++ b/lib/data/repositories/preferences_repository.dart @@ -108,15 +108,37 @@ class PreferencesStateNotifier extends StateNotifier { // (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( diff --git a/lib/ui/features/preferences/widgets/settings_screen.dart b/lib/ui/features/preferences/widgets/settings_screen.dart index 1242dd3..e71c4d9 100644 --- a/lib/ui/features/preferences/widgets/settings_screen.dart +++ b/lib/ui/features/preferences/widgets/settings_screen.dart @@ -12,7 +12,6 @@ class SettingsDialog extends ConsumerStatefulWidget { class _SettingsDialogState extends ConsumerState { String? _theme; - String? _themeColor; String? _language; // Page view removed; continuous-only double? _exportDpi; @@ -22,7 +21,6 @@ class _SettingsDialogState extends ConsumerState { 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 { child: CircularProgressIndicator(), ), ), - error: (_, _) { - final items = + error: (_, __) { + final tags = AppLocalizations.supportedLocales .map((loc) => toLanguageTag(loc)) .toList() @@ -85,19 +83,27 @@ class _SettingsDialogState extends ConsumerState { isExpanded: true, value: _language, items: - items + tags .map( - (tag) => DropdownMenuItem( + (tag) => DropdownMenuItem( 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 { isExpanded: true, value: _language, items: - items + tags .map( (tag) => DropdownMenuItem( value: tag, @@ -115,7 +121,15 @@ class _SettingsDialogState extends ConsumerState { ), ) .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 { ), ) .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 { 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 { 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: [