From 329b216876d318dcb8b5af1f8dd7d4a99b19202f Mon Sep 17 00:00:00 2001 From: Thatsaphorn Atchariyaphap Date: Sat, 27 Sep 2025 15:19:47 +0200 Subject: [PATCH] Feature: Add and implement i18n support for theme settings in German and English, refactor `AppSettings` to `DesignSettings`, and improve settings UI structure. --- finlog_app/app/assets/i18n/de.i18n.json | 9 +- finlog_app/app/assets/i18n/en.i18n.json | 9 +- .../app/lib/core/i18n/translations.g.dart | 4 +- .../app/lib/core/i18n/translations_de.g.dart | 26 ++++ .../app/lib/core/i18n/translations_en.g.dart | 33 ++++++ finlog_app/app/lib/modules/app_shell.dart | 3 +- .../modules/app/design_settings_view.dart | 112 ++++++++++++------ .../app/model/design_settings_view_model.dart | 4 +- 8 files changed, 154 insertions(+), 46 deletions(-) diff --git a/finlog_app/app/assets/i18n/de.i18n.json b/finlog_app/app/assets/i18n/de.i18n.json index cea4fd0..aba5643 100644 --- a/finlog_app/app/assets/i18n/de.i18n.json +++ b/finlog_app/app/assets/i18n/de.i18n.json @@ -25,7 +25,14 @@ "tooltipUserSettings": "Benutzer-Einstellungen", "tooltipCollapseRail": "Leiste verkleinern", "tooltipExpandRail": "Leiste erweitern", - "drawerSettings": "Einstellungen" + "drawerSettings": "Einstellungen", + "settings": { + "theme": { + "system": "System", + "light": "Hell", + "dark": "Dunkel" + } + } }, "settings": { "title": "Einstellungen", diff --git a/finlog_app/app/assets/i18n/en.i18n.json b/finlog_app/app/assets/i18n/en.i18n.json index bb99310..29f8d6d 100644 --- a/finlog_app/app/assets/i18n/en.i18n.json +++ b/finlog_app/app/assets/i18n/en.i18n.json @@ -25,7 +25,14 @@ "tooltipUserSettings": "User Settings", "tooltipCollapseRail": "Collapse Rail", "tooltipExpandRail": "Expand Rail", - "drawerSettings": "Settings" + "drawerSettings": "Settings", + "settings": { + "theme": { + "system": "System", + "light": "Light", + "dark": "Dark" + } + } }, "settings": { "title": "Settings", diff --git a/finlog_app/app/lib/core/i18n/translations.g.dart b/finlog_app/app/lib/core/i18n/translations.g.dart index 7514cac..a71e1dd 100644 --- a/finlog_app/app/lib/core/i18n/translations.g.dart +++ b/finlog_app/app/lib/core/i18n/translations.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 126 (63 per locale) +/// Strings: 132 (66 per locale) /// -/// Built on 2025-09-27 at 12:19 UTC +/// Built on 2025-09-27 at 12:40 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/finlog_app/app/lib/core/i18n/translations_de.g.dart b/finlog_app/app/lib/core/i18n/translations_de.g.dart index 4aa049c..441c887 100644 --- a/finlog_app/app/lib/core/i18n/translations_de.g.dart +++ b/finlog_app/app/lib/core/i18n/translations_de.g.dart @@ -98,6 +98,7 @@ class _TranslationsAppDe implements TranslationsAppEn { @override String get tooltipCollapseRail => 'Leiste verkleinern'; @override String get tooltipExpandRail => 'Leiste erweitern'; @override String get drawerSettings => 'Einstellungen'; + @override late final _TranslationsAppSettingsDe settings = _TranslationsAppSettingsDe._(_root); } // Path: settings @@ -131,6 +132,16 @@ class _TranslationsFeaturesDe implements TranslationsFeaturesEn { @override late final _TranslationsFeaturesReportsDe reports = _TranslationsFeaturesReportsDe._(_root); } +// Path: app.settings +class _TranslationsAppSettingsDe implements TranslationsAppSettingsEn { + _TranslationsAppSettingsDe._(this._root); + + final TranslationsDe _root; // ignore: unused_field + + // Translations + @override late final _TranslationsAppSettingsThemeDe theme = _TranslationsAppSettingsThemeDe._(_root); +} + // Path: settings.sections class _TranslationsSettingsSectionsDe implements TranslationsSettingsSectionsEn { _TranslationsSettingsSectionsDe._(this._root); @@ -281,6 +292,18 @@ class _TranslationsFeaturesReportsDe implements TranslationsFeaturesReportsEn { @override String get description => 'Statistiken von Ausgaben'; } +// Path: app.settings.theme +class _TranslationsAppSettingsThemeDe implements TranslationsAppSettingsThemeEn { + _TranslationsAppSettingsThemeDe._(this._root); + + final TranslationsDe _root; // ignore: unused_field + + // Translations + @override String get system => 'System'; + @override String get light => 'Hell'; + @override String get dark => 'Dunkel'; +} + /// Flat map(s) containing all translations. /// Only for edge cases! For simple maps, use the map function of this library. extension on TranslationsDe { @@ -306,6 +329,9 @@ extension on TranslationsDe { case 'app.tooltipCollapseRail': return 'Leiste verkleinern'; case 'app.tooltipExpandRail': return 'Leiste erweitern'; case 'app.drawerSettings': return 'Einstellungen'; + case 'app.settings.theme.system': return 'System'; + case 'app.settings.theme.light': return 'Hell'; + case 'app.settings.theme.dark': return 'Dunkel'; case 'settings.title': return 'Einstellungen'; case 'settings.sections.account': return 'Konto & Daten'; case 'settings.sections.app': return 'App'; diff --git a/finlog_app/app/lib/core/i18n/translations_en.g.dart b/finlog_app/app/lib/core/i18n/translations_en.g.dart index 373e32e..8c268bb 100644 --- a/finlog_app/app/lib/core/i18n/translations_en.g.dart +++ b/finlog_app/app/lib/core/i18n/translations_en.g.dart @@ -142,6 +142,8 @@ class TranslationsAppEn { /// en: 'Settings' String get drawerSettings => 'Settings'; + + late final TranslationsAppSettingsEn settings = TranslationsAppSettingsEn._(_root); } // Path: settings @@ -178,6 +180,16 @@ class TranslationsFeaturesEn { late final TranslationsFeaturesReportsEn reports = TranslationsFeaturesReportsEn._(_root); } +// Path: app.settings +class TranslationsAppSettingsEn { + TranslationsAppSettingsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final TranslationsAppSettingsThemeEn theme = TranslationsAppSettingsThemeEn._(_root); +} + // Path: settings.sections class TranslationsSettingsSectionsEn { TranslationsSettingsSectionsEn._(this._root); @@ -412,6 +424,24 @@ class TranslationsFeaturesReportsEn { String get description => 'Statistics of expenses'; } +// Path: app.settings.theme +class TranslationsAppSettingsThemeEn { + TranslationsAppSettingsThemeEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'System' + String get system => 'System'; + + /// en: 'Light' + String get light => 'Light'; + + /// en: 'Dark' + String get dark => 'Dark'; +} + /// Flat map(s) containing all translations. /// Only for edge cases! For simple maps, use the map function of this library. extension on Translations { @@ -437,6 +467,9 @@ extension on Translations { case 'app.tooltipCollapseRail': return 'Collapse Rail'; case 'app.tooltipExpandRail': return 'Expand Rail'; case 'app.drawerSettings': return 'Settings'; + case 'app.settings.theme.system': return 'System'; + case 'app.settings.theme.light': return 'Light'; + case 'app.settings.theme.dark': return 'Dark'; case 'settings.title': return 'Settings'; case 'settings.sections.account': return 'Account & Data'; case 'settings.sections.app': return 'App'; diff --git a/finlog_app/app/lib/modules/app_shell.dart b/finlog_app/app/lib/modules/app_shell.dart index d858845..f8d7047 100644 --- a/finlog_app/app/lib/modules/app_shell.dart +++ b/finlog_app/app/lib/modules/app_shell.dart @@ -295,8 +295,7 @@ class _DesktopDrawer extends StatelessWidget { child: ListView( padding: const EdgeInsets.symmetric(vertical: 8), children: [ - const _LogoHeader(), - const Divider(), + // const Divider(), for (var i = 0; i < visibleItems.length; i++) ListTile( selected: i == safeSelected, diff --git a/finlog_app/app/lib/modules/settings/modules/app/design_settings_view.dart b/finlog_app/app/lib/modules/settings/modules/app/design_settings_view.dart index d9ebf27..945ccc5 100644 --- a/finlog_app/app/lib/modules/settings/modules/app/design_settings_view.dart +++ b/finlog_app/app/lib/modules/settings/modules/app/design_settings_view.dart @@ -16,7 +16,7 @@ class DesignSettingsView extends StatelessWidget { final localeModel = context.read(); return ChangeNotifierProvider( - create: (_) => AppSettingsViewModel( + create: (_) => DesignSettingsViewModel( themeModel: themeModel, scaleModel: scaleModel, localeModel: localeModel, @@ -31,12 +31,11 @@ class _AppSettingsContent extends StatelessWidget { @override Widget build(BuildContext context) { - final vm = context.watch(); + final vm = context.watch(); final isLoading = vm.isLoading; return Column( children: [ - // const PanelHeader(title: 'App-Einstellungen'), if (isLoading) const Expanded(child: Center(child: CircularProgressIndicator())) else @@ -46,8 +45,8 @@ class _AppSettingsContent extends StatelessWidget { children: [ _SystemBackgroundSection(), const SizedBox(height: 16), - _TextScaleSection(), - const SizedBox(height: 16), + // _TextScaleSection(), + // const SizedBox(height: 16), _LanguageSection(), ], ), @@ -63,9 +62,42 @@ class _SystemBackgroundSection extends StatelessWidget { @override Widget build(BuildContext context) { final t = Translations.of(context); - final vm = context.watch(); + final vm = context.watch(); final selected = vm.themeMode; + final cs = Theme.of(context).colorScheme; + final dividerColor = Theme.of(context).dividerColor; + + Widget radioTile({ + required bool isSelected, + required String label, + required VoidCallback onTap, + }) { + return ColoredBox( + color: cs.surface, + child: ListTile( + // contentPadding: const EdgeInsets.symmetric( + // // horizontal: 7, + // vertical: 0, + // ), + leading: Icon( + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, + color: isSelected ? cs.primary : null, + ), + title: Text( + label, + style: isSelected + ? const TextStyle(fontWeight: FontWeight.w600) + : null, + ), + onTap: onTap, + selected: isSelected, + ), + ); + } + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -75,32 +107,24 @@ class _SystemBackgroundSection extends StatelessWidget { ), const SizedBox(height: 10), - ToggleButtons( - isSelected: [ - selected == ThemeMode.system, - selected == ThemeMode.dark, - selected == ThemeMode.light, - ], - onPressed: (i) { - switch (i) { - case 0: - vm.setThemeMode(ThemeMode.system); - break; - case 1: - vm.setThemeMode(ThemeMode.dark); - break; - case 2: - vm.setThemeMode(ThemeMode.light); - break; - } - }, - borderRadius: BorderRadius.circular(24), - constraints: const BoxConstraints(minHeight: 44, minWidth: 140), - children: [ - _SegItem(icon: Icons.phone_iphone, label: t.settings.app.systemDefault), - _SegItem(emoji: '🌙', label: t.settings.app.darkMode), - _SegItem(emoji: '☀️', label: t.settings.app.lightMode), - ], + radioTile( + isSelected: selected == ThemeMode.system, + label: t.settings.app.systemDefault, + onTap: () => vm.setThemeMode(ThemeMode.system), + ), + Divider(height: 1, thickness: 1, color: dividerColor), + + radioTile( + isSelected: selected == ThemeMode.dark, + label: t.settings.app.darkMode, + onTap: () => vm.setThemeMode(ThemeMode.dark), + ), + Divider(height: 1, thickness: 1, color: dividerColor), + + radioTile( + isSelected: selected == ThemeMode.light, + label: t.settings.app.lightMode, + onTap: () => vm.setThemeMode(ThemeMode.light), ), ], ); @@ -137,13 +161,16 @@ class _TextScaleSection extends StatelessWidget { @override Widget build(BuildContext context) { final t = Translations.of(context); - final vm = context.watch(); + final vm = context.watch(); final selected = vm.textScale; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(t.settings.app.textSize, style: const TextStyle(fontWeight: FontWeight.w600)), + Text( + t.settings.app.textSize, + style: const TextStyle(fontWeight: FontWeight.w600), + ), const SizedBox(height: 8), ToggleButtons( isSelected: [ @@ -188,13 +215,16 @@ class _LanguageSection extends StatelessWidget { @override Widget build(BuildContext context) { final t = Translations.of(context); - final vm = context.watch(); + final vm = context.watch(); final scheme = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(t.settings.app.language, style: Theme.of(context).textTheme.titleMedium), + Text( + t.settings.app.language, + style: Theme.of(context).textTheme.titleMedium, + ), const SizedBox(height: 8), DropdownButtonFormField( @@ -205,8 +235,14 @@ class _LanguageSection extends StatelessWidget { value: LanguagePref.system, child: Text(t.settings.app.systemDefault), ), - DropdownMenuItem(value: LanguagePref.de, child: Text(t.settings.app.german)), - DropdownMenuItem(value: LanguagePref.en, child: Text(t.settings.app.english)), + DropdownMenuItem( + value: LanguagePref.de, + child: Text(t.settings.app.german), + ), + DropdownMenuItem( + value: LanguagePref.en, + child: Text(t.settings.app.english), + ), ], decoration: InputDecoration( isDense: true, diff --git a/finlog_app/app/lib/modules/settings/modules/app/model/design_settings_view_model.dart b/finlog_app/app/lib/modules/settings/modules/app/model/design_settings_view_model.dart index 9278a3b..ea8bec0 100644 --- a/finlog_app/app/lib/modules/settings/modules/app/model/design_settings_view_model.dart +++ b/finlog_app/app/lib/modules/settings/modules/app/model/design_settings_view_model.dart @@ -3,14 +3,14 @@ import 'package:app/core/ui/controller/scale_controller.dart'; import 'package:app/core/ui/controller/theme.dart'; import 'package:flutter/material.dart'; -class AppSettingsViewModel extends ChangeNotifier { +class DesignSettingsViewModel extends ChangeNotifier { bool _isLoading = false; final ThemeController _theme; final ScaleController _scale; final LocaleController _locale; - AppSettingsViewModel({ + DesignSettingsViewModel({ required ThemeController themeModel, required ScaleController scaleModel, required LocaleController localeModel,