Feature: Add and implement i18n support for theme settings in German and English, refactor AppSettings to DesignSettings, and improve settings UI structure.

This commit is contained in:
2025-09-27 15:19:47 +02:00
parent 465f7153a4
commit 329b216876
8 changed files with 154 additions and 46 deletions

View File

@@ -25,7 +25,14 @@
"tooltipUserSettings": "Benutzer-Einstellungen", "tooltipUserSettings": "Benutzer-Einstellungen",
"tooltipCollapseRail": "Leiste verkleinern", "tooltipCollapseRail": "Leiste verkleinern",
"tooltipExpandRail": "Leiste erweitern", "tooltipExpandRail": "Leiste erweitern",
"drawerSettings": "Einstellungen" "drawerSettings": "Einstellungen",
"settings": {
"theme": {
"system": "System",
"light": "Hell",
"dark": "Dunkel"
}
}
}, },
"settings": { "settings": {
"title": "Einstellungen", "title": "Einstellungen",

View File

@@ -25,7 +25,14 @@
"tooltipUserSettings": "User Settings", "tooltipUserSettings": "User Settings",
"tooltipCollapseRail": "Collapse Rail", "tooltipCollapseRail": "Collapse Rail",
"tooltipExpandRail": "Expand Rail", "tooltipExpandRail": "Expand Rail",
"drawerSettings": "Settings" "drawerSettings": "Settings",
"settings": {
"theme": {
"system": "System",
"light": "Light",
"dark": "Dark"
}
}
}, },
"settings": { "settings": {
"title": "Settings", "title": "Settings",

View File

@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang` /// To regenerate, run: `dart run slang`
/// ///
/// Locales: 2 /// 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 // coverage:ignore-file
// ignore_for_file: type=lint, unused_import // ignore_for_file: type=lint, unused_import

View File

@@ -98,6 +98,7 @@ class _TranslationsAppDe implements TranslationsAppEn {
@override String get tooltipCollapseRail => 'Leiste verkleinern'; @override String get tooltipCollapseRail => 'Leiste verkleinern';
@override String get tooltipExpandRail => 'Leiste erweitern'; @override String get tooltipExpandRail => 'Leiste erweitern';
@override String get drawerSettings => 'Einstellungen'; @override String get drawerSettings => 'Einstellungen';
@override late final _TranslationsAppSettingsDe settings = _TranslationsAppSettingsDe._(_root);
} }
// Path: settings // Path: settings
@@ -131,6 +132,16 @@ class _TranslationsFeaturesDe implements TranslationsFeaturesEn {
@override late final _TranslationsFeaturesReportsDe reports = _TranslationsFeaturesReportsDe._(_root); @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 // Path: settings.sections
class _TranslationsSettingsSectionsDe implements TranslationsSettingsSectionsEn { class _TranslationsSettingsSectionsDe implements TranslationsSettingsSectionsEn {
_TranslationsSettingsSectionsDe._(this._root); _TranslationsSettingsSectionsDe._(this._root);
@@ -281,6 +292,18 @@ class _TranslationsFeaturesReportsDe implements TranslationsFeaturesReportsEn {
@override String get description => 'Statistiken von Ausgaben'; @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. /// Flat map(s) containing all translations.
/// Only for edge cases! For simple maps, use the map function of this library. /// Only for edge cases! For simple maps, use the map function of this library.
extension on TranslationsDe { extension on TranslationsDe {
@@ -306,6 +329,9 @@ extension on TranslationsDe {
case 'app.tooltipCollapseRail': return 'Leiste verkleinern'; case 'app.tooltipCollapseRail': return 'Leiste verkleinern';
case 'app.tooltipExpandRail': return 'Leiste erweitern'; case 'app.tooltipExpandRail': return 'Leiste erweitern';
case 'app.drawerSettings': return 'Einstellungen'; 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.title': return 'Einstellungen';
case 'settings.sections.account': return 'Konto & Daten'; case 'settings.sections.account': return 'Konto & Daten';
case 'settings.sections.app': return 'App'; case 'settings.sections.app': return 'App';

View File

@@ -142,6 +142,8 @@ class TranslationsAppEn {
/// en: 'Settings' /// en: 'Settings'
String get drawerSettings => 'Settings'; String get drawerSettings => 'Settings';
late final TranslationsAppSettingsEn settings = TranslationsAppSettingsEn._(_root);
} }
// Path: settings // Path: settings
@@ -178,6 +180,16 @@ class TranslationsFeaturesEn {
late final TranslationsFeaturesReportsEn reports = TranslationsFeaturesReportsEn._(_root); 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 // Path: settings.sections
class TranslationsSettingsSectionsEn { class TranslationsSettingsSectionsEn {
TranslationsSettingsSectionsEn._(this._root); TranslationsSettingsSectionsEn._(this._root);
@@ -412,6 +424,24 @@ class TranslationsFeaturesReportsEn {
String get description => 'Statistics of expenses'; 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. /// Flat map(s) containing all translations.
/// Only for edge cases! For simple maps, use the map function of this library. /// Only for edge cases! For simple maps, use the map function of this library.
extension on Translations { extension on Translations {
@@ -437,6 +467,9 @@ extension on Translations {
case 'app.tooltipCollapseRail': return 'Collapse Rail'; case 'app.tooltipCollapseRail': return 'Collapse Rail';
case 'app.tooltipExpandRail': return 'Expand Rail'; case 'app.tooltipExpandRail': return 'Expand Rail';
case 'app.drawerSettings': return 'Settings'; 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.title': return 'Settings';
case 'settings.sections.account': return 'Account & Data'; case 'settings.sections.account': return 'Account & Data';
case 'settings.sections.app': return 'App'; case 'settings.sections.app': return 'App';

View File

@@ -295,8 +295,7 @@ class _DesktopDrawer extends StatelessWidget {
child: ListView( child: ListView(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
children: [ children: [
const _LogoHeader(), // const Divider(),
const Divider(),
for (var i = 0; i < visibleItems.length; i++) for (var i = 0; i < visibleItems.length; i++)
ListTile( ListTile(
selected: i == safeSelected, selected: i == safeSelected,

View File

@@ -16,7 +16,7 @@ class DesignSettingsView extends StatelessWidget {
final localeModel = context.read<LocaleController>(); final localeModel = context.read<LocaleController>();
return ChangeNotifierProvider( return ChangeNotifierProvider(
create: (_) => AppSettingsViewModel( create: (_) => DesignSettingsViewModel(
themeModel: themeModel, themeModel: themeModel,
scaleModel: scaleModel, scaleModel: scaleModel,
localeModel: localeModel, localeModel: localeModel,
@@ -31,12 +31,11 @@ class _AppSettingsContent extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final vm = context.watch<AppSettingsViewModel>(); final vm = context.watch<DesignSettingsViewModel>();
final isLoading = vm.isLoading; final isLoading = vm.isLoading;
return Column( return Column(
children: [ children: [
// const PanelHeader(title: 'App-Einstellungen'),
if (isLoading) if (isLoading)
const Expanded(child: Center(child: CircularProgressIndicator())) const Expanded(child: Center(child: CircularProgressIndicator()))
else else
@@ -46,8 +45,8 @@ class _AppSettingsContent extends StatelessWidget {
children: [ children: [
_SystemBackgroundSection(), _SystemBackgroundSection(),
const SizedBox(height: 16), const SizedBox(height: 16),
_TextScaleSection(), // _TextScaleSection(),
const SizedBox(height: 16), // const SizedBox(height: 16),
_LanguageSection(), _LanguageSection(),
], ],
), ),
@@ -63,9 +62,42 @@ class _SystemBackgroundSection extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final t = Translations.of(context); final t = Translations.of(context);
final vm = context.watch<AppSettingsViewModel>(); final vm = context.watch<DesignSettingsViewModel>();
final selected = vm.themeMode; 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( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -75,32 +107,24 @@ class _SystemBackgroundSection extends StatelessWidget {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
ToggleButtons( radioTile(
isSelected: [ isSelected: selected == ThemeMode.system,
selected == ThemeMode.system, label: t.settings.app.systemDefault,
selected == ThemeMode.dark, onTap: () => vm.setThemeMode(ThemeMode.system),
selected == ThemeMode.light, ),
], Divider(height: 1, thickness: 1, color: dividerColor),
onPressed: (i) {
switch (i) { radioTile(
case 0: isSelected: selected == ThemeMode.dark,
vm.setThemeMode(ThemeMode.system); label: t.settings.app.darkMode,
break; onTap: () => vm.setThemeMode(ThemeMode.dark),
case 1: ),
vm.setThemeMode(ThemeMode.dark); Divider(height: 1, thickness: 1, color: dividerColor),
break;
case 2: radioTile(
vm.setThemeMode(ThemeMode.light); isSelected: selected == ThemeMode.light,
break; label: t.settings.app.lightMode,
} onTap: () => vm.setThemeMode(ThemeMode.light),
},
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),
],
), ),
], ],
); );
@@ -137,13 +161,16 @@ class _TextScaleSection extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final t = Translations.of(context); final t = Translations.of(context);
final vm = context.watch<AppSettingsViewModel>(); final vm = context.watch<DesignSettingsViewModel>();
final selected = vm.textScale; final selected = vm.textScale;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ 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), const SizedBox(height: 8),
ToggleButtons( ToggleButtons(
isSelected: [ isSelected: [
@@ -188,13 +215,16 @@ class _LanguageSection extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final t = Translations.of(context); final t = Translations.of(context);
final vm = context.watch<AppSettingsViewModel>(); final vm = context.watch<DesignSettingsViewModel>();
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ 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), const SizedBox(height: 8),
DropdownButtonFormField<LanguagePref>( DropdownButtonFormField<LanguagePref>(
@@ -205,8 +235,14 @@ class _LanguageSection extends StatelessWidget {
value: LanguagePref.system, value: LanguagePref.system,
child: Text(t.settings.app.systemDefault), child: Text(t.settings.app.systemDefault),
), ),
DropdownMenuItem(value: LanguagePref.de, child: Text(t.settings.app.german)), DropdownMenuItem(
DropdownMenuItem(value: LanguagePref.en, child: Text(t.settings.app.english)), value: LanguagePref.de,
child: Text(t.settings.app.german),
),
DropdownMenuItem(
value: LanguagePref.en,
child: Text(t.settings.app.english),
),
], ],
decoration: InputDecoration( decoration: InputDecoration(
isDense: true, isDense: true,

View File

@@ -3,14 +3,14 @@ import 'package:app/core/ui/controller/scale_controller.dart';
import 'package:app/core/ui/controller/theme.dart'; import 'package:app/core/ui/controller/theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AppSettingsViewModel extends ChangeNotifier { class DesignSettingsViewModel extends ChangeNotifier {
bool _isLoading = false; bool _isLoading = false;
final ThemeController _theme; final ThemeController _theme;
final ScaleController _scale; final ScaleController _scale;
final LocaleController _locale; final LocaleController _locale;
AppSettingsViewModel({ DesignSettingsViewModel({
required ThemeController themeModel, required ThemeController themeModel,
required ScaleController scaleModel, required ScaleController scaleModel,
required LocaleController localeModel, required LocaleController localeModel,