Simplify theme management, integrate ToggleButtons for theme selection, and enhance settings view structure.
This commit is contained in:
@@ -25,10 +25,10 @@ class ThemeController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Sets theme and persists it in Preferences.
|
||||
Future<void> setTheme(ThemeMode mode) async {
|
||||
void setTheme(ThemeMode mode) {
|
||||
_themeMode = mode;
|
||||
notifyListeners();
|
||||
await _prefs.setString('theme', _toString(mode));
|
||||
_prefs.setString('theme', mode.name);
|
||||
}
|
||||
|
||||
ThemeMode _fromString(String value) {
|
||||
@@ -43,16 +43,5 @@ class ThemeController extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
String _toString(ThemeMode mode) {
|
||||
switch (mode) {
|
||||
case ThemeMode.light:
|
||||
return 'light';
|
||||
case ThemeMode.dark:
|
||||
return 'dark';
|
||||
case ThemeMode.system:
|
||||
return 'system';
|
||||
}
|
||||
}
|
||||
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:fluttery/fluttery.dart';
|
||||
import 'package:fluttery/logger.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
// Ensures that the Flutter engine and widget binding
|
||||
@@ -25,9 +26,12 @@ Future<void> main() async {
|
||||
await themeController.init();
|
||||
|
||||
runApp(
|
||||
FinlogApp(
|
||||
router: buildAppRouter(startRoute),
|
||||
themeController: themeController,
|
||||
MultiProvider(
|
||||
providers: [ChangeNotifierProvider(create: (context) => themeController)],
|
||||
child: FinlogApp(
|
||||
router: buildAppRouter(startRoute),
|
||||
themeController: themeController,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:app/core/app/theme.dart';
|
||||
import 'package:app/modules/settings/modules/app/model/app_settings_view_model.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -8,8 +8,9 @@ class AppSettingsView extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeModel = context.watch<ThemeController>();
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => AppSettingsViewModel()..load(),
|
||||
create: (_) => AppSettingsViewModel(themeModel: themeModel)..load(),
|
||||
child: const _AppSettingsContent(),
|
||||
);
|
||||
}
|
||||
@@ -38,8 +39,6 @@ class _AppSettingsContent extends StatelessWidget {
|
||||
_TextSizeSection(),
|
||||
const SizedBox(height: 16),
|
||||
_LanguageSection(),
|
||||
const SizedBox(height: 24),
|
||||
_SaveButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -49,106 +48,78 @@ class _AppSettingsContent extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _SystemBackgroundSection extends StatelessWidget {
|
||||
const _SystemBackgroundSection();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final vm = context.watch<AppSettingsViewModel>();
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
// A few pleasant presets; extend as needed.
|
||||
final presets = <Color>[
|
||||
const Color(0xFFFFFFFF),
|
||||
const Color(0xFFF5F5F5),
|
||||
const Color(0xFF121212),
|
||||
const Color(0xFF1E1E1E),
|
||||
Colors.blueGrey.shade50,
|
||||
Colors.blueGrey.shade900,
|
||||
];
|
||||
|
||||
final isSystem = vm.backgroundColorSystem == null;
|
||||
final selected = vm.backgroundColorSystem;
|
||||
|
||||
Widget chip({
|
||||
required Widget child,
|
||||
required bool selected,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
border: Border.all(
|
||||
color: selected ? scheme.primary : scheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
final selected = vm.themeMode;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
const Text(
|
||||
'System-Hintergrundfarbe',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
chip(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.phone_android, size: 18),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'Systemstandard',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
selected: isSystem,
|
||||
onTap: () => vm.setSystemBackgroundColor(null),
|
||||
),
|
||||
...presets.map((c) {
|
||||
final bool sel = selected == c;
|
||||
return GestureDetector(
|
||||
onTap: () => vm.setSystemBackgroundColor(c),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: c,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: sel ? scheme.primary : scheme.outlineVariant,
|
||||
width: sel ? 2 : 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
ToggleButtons(
|
||||
isSelected: [
|
||||
selected == ThemeMode.system,
|
||||
selected == ThemeMode.dark,
|
||||
selected == ThemeMode.light,
|
||||
],
|
||||
onPressed: (i) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
vm.setBackgroundPref(ThemeMode.system);
|
||||
break;
|
||||
case 1:
|
||||
vm.setBackgroundPref(ThemeMode.dark);
|
||||
break;
|
||||
case 2:
|
||||
vm.setBackgroundPref(ThemeMode.light);
|
||||
break;
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
constraints: const BoxConstraints(minHeight: 44, minWidth: 140),
|
||||
children: const [
|
||||
_SegItem(icon: Icons.phone_iphone, label: 'Systemstandard'),
|
||||
_SegItem(emoji: '🌙', label: 'Dark Mode'),
|
||||
_SegItem(emoji: '☀️', label: 'White Mode'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
isSystem
|
||||
? 'Aktuell: Systemstandard'
|
||||
: 'Aktuell: Benutzerdefinierte Farbe',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(color: scheme.outline),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SegItem extends StatelessWidget {
|
||||
final IconData? icon;
|
||||
final String? emoji;
|
||||
final String label;
|
||||
|
||||
const _SegItem({this.icon, this.emoji, required this.label});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (icon != null) Icon(icon, size: 18),
|
||||
if (emoji != null) Text(emoji!, style: const TextStyle(fontSize: 18)),
|
||||
const SizedBox(width: 8),
|
||||
Text(label),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TextSizeSection extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -212,31 +183,3 @@ class _LanguageSection extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SaveButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final vm = context.watch<AppSettingsViewModel>();
|
||||
|
||||
return FilledButton.icon(
|
||||
onPressed: vm.isSaving
|
||||
? null
|
||||
: () async {
|
||||
await vm.save();
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Einstellungen gespeichert')),
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: vm.isSaving
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.save_outlined),
|
||||
label: const Text('Speichern'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:app/core/app/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Domain-ish enums for clarity and easy (de)serialization.
|
||||
@@ -14,15 +15,10 @@ class AppSettingsViewModel extends ChangeNotifier {
|
||||
bool _isLoading = false;
|
||||
bool _isSaving = false;
|
||||
|
||||
Color? get backgroundColorSystem => _backgroundColorSystem;
|
||||
final ThemeController _theme;
|
||||
|
||||
AppTextSize get textSize => _textSize;
|
||||
|
||||
AppLanguage get language => _language;
|
||||
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
bool get isSaving => _isSaving;
|
||||
AppSettingsViewModel({required ThemeController themeModel})
|
||||
: _theme = themeModel;
|
||||
|
||||
/// Pretend to load from backend. Plug your repository here later.
|
||||
Future<void> load() async {
|
||||
@@ -41,6 +37,11 @@ class AppSettingsViewModel extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setBackgroundPref(ThemeMode mode) {
|
||||
_theme.setTheme(mode);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Save to backend (stub).
|
||||
Future<void> save() async {
|
||||
if (_isSaving) return;
|
||||
@@ -71,7 +72,6 @@ class AppSettingsViewModel extends ChangeNotifier {
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'backgroundColorSystem': _backgroundColorSystem?.value,
|
||||
// null => system
|
||||
'textSize': _textSize.name,
|
||||
'language': _language.name,
|
||||
};
|
||||
@@ -94,4 +94,16 @@ class AppSettingsViewModel extends ChangeNotifier {
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
ThemeMode get themeMode => _theme.themeMode;
|
||||
|
||||
Color? get backgroundColorSystem => _backgroundColorSystem;
|
||||
|
||||
AppTextSize get textSize => _textSize;
|
||||
|
||||
AppLanguage get language => _language;
|
||||
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
bool get isSaving => _isSaving;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user