- Flutter frontend with Provider state management - FastAPI backend with SQLAlchemy ORM - Internationalization support (EN/DE) - Clean Architecture folder structure - GoRouter for navigation - GetIt for dependency injection
182 lines
5.1 KiB
Dart
182 lines
5.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../viewmodels/settings_viewmodel.dart';
|
|
|
|
class SettingsPage extends StatelessWidget {
|
|
const SettingsPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final l10n = AppLocalizations.of(context)!;
|
|
final settingsVm = context.watch<SettingsViewModel>();
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(l10n.settings),
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back),
|
|
onPressed: () => context.pop(),
|
|
),
|
|
),
|
|
body: ListView(
|
|
children: [
|
|
_SectionHeader(title: l10n.general),
|
|
ListTile(
|
|
leading: const Icon(Icons.language),
|
|
title: Text(l10n.language),
|
|
subtitle: Text(
|
|
settingsVm.locale != null
|
|
? settingsVm.getLanguageName(settingsVm.locale!)
|
|
: l10n.systemDefault,
|
|
),
|
|
trailing: const Icon(Icons.chevron_right),
|
|
onTap: () => _showLanguageDialog(context, settingsVm, l10n),
|
|
),
|
|
const Divider(),
|
|
_SectionHeader(title: l10n.appearance),
|
|
ListTile(
|
|
leading: const Icon(Icons.dark_mode),
|
|
title: Text(l10n.darkMode),
|
|
subtitle: Text(_getThemeModeLabel(settingsVm.themeMode, l10n)),
|
|
trailing: const Icon(Icons.chevron_right),
|
|
onTap: () => _showThemeDialog(context, settingsVm, l10n),
|
|
),
|
|
const Divider(),
|
|
_SectionHeader(title: l10n.about),
|
|
ListTile(
|
|
leading: const Icon(Icons.info_outline),
|
|
title: Text(l10n.version),
|
|
subtitle: const Text('1.0.0'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getThemeModeLabel(ThemeMode mode, AppLocalizations l10n) {
|
|
switch (mode) {
|
|
case ThemeMode.system:
|
|
return l10n.systemDefault;
|
|
case ThemeMode.light:
|
|
return l10n.lightMode;
|
|
case ThemeMode.dark:
|
|
return l10n.darkModeOption;
|
|
}
|
|
}
|
|
|
|
void _showLanguageDialog(
|
|
BuildContext context,
|
|
SettingsViewModel vm,
|
|
AppLocalizations l10n,
|
|
) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text(l10n.language),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: SettingsViewModel.supportedLocales.map((locale) {
|
|
return RadioListTile<Locale>(
|
|
title: Text(vm.getLanguageName(locale)),
|
|
value: locale,
|
|
groupValue: vm.locale,
|
|
onChanged: (value) {
|
|
if (value != null) {
|
|
vm.setLocale(value);
|
|
Navigator.pop(context);
|
|
}
|
|
},
|
|
);
|
|
}).toList(),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: Text(l10n.cancel),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showThemeDialog(
|
|
BuildContext context,
|
|
SettingsViewModel vm,
|
|
AppLocalizations l10n,
|
|
) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text(l10n.darkMode),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
RadioListTile<ThemeMode>(
|
|
title: Text(l10n.systemDefault),
|
|
value: ThemeMode.system,
|
|
groupValue: vm.themeMode,
|
|
onChanged: (value) {
|
|
if (value != null) {
|
|
vm.setThemeMode(value);
|
|
Navigator.pop(context);
|
|
}
|
|
},
|
|
),
|
|
RadioListTile<ThemeMode>(
|
|
title: Text(l10n.lightMode),
|
|
value: ThemeMode.light,
|
|
groupValue: vm.themeMode,
|
|
onChanged: (value) {
|
|
if (value != null) {
|
|
vm.setThemeMode(value);
|
|
Navigator.pop(context);
|
|
}
|
|
},
|
|
),
|
|
RadioListTile<ThemeMode>(
|
|
title: Text(l10n.darkModeOption),
|
|
value: ThemeMode.dark,
|
|
groupValue: vm.themeMode,
|
|
onChanged: (value) {
|
|
if (value != null) {
|
|
vm.setThemeMode(value);
|
|
Navigator.pop(context);
|
|
}
|
|
},
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: Text(l10n.cancel),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SectionHeader extends StatelessWidget {
|
|
final String title;
|
|
|
|
const _SectionHeader({required this.title});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
|
child: Text(
|
|
title.toUpperCase(),
|
|
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
|
color: Theme.of(context).colorScheme.primary,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|