- Datenmodell: users/{uid}, households/{id} mit memberUids, plants/locations als Subcollections
- StreamProvider liefern Live-Daten, Repositories kapseln Schreibzugriffe
- Login-Screen mit Registrierung (legt Haushalt automatisch an), Logout in Einstellungen
- Router-Redirect bei Login/Logout, firestore.rules mit Haushalts-Prinzip
- Tests auf firebase_auth_mocks + fake_cloud_firestore umgestellt
- Verifiziert: analyze/test grün, Android-Debug-Build erfolgreich
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
127 lines
4.2 KiB
Dart
127 lines
4.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../../core/firebase/firebase_providers.dart';
|
|
import '../../../core/settings/settings_provider.dart';
|
|
import '../../../core/widgets/app_drawer.dart';
|
|
import '../../../l10n/generated/app_localizations.dart';
|
|
import '../../auth/data/auth_repository.dart';
|
|
|
|
class SettingsScreen extends ConsumerWidget {
|
|
const SettingsScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final l10n = AppLocalizations.of(context);
|
|
final settings = ref.watch(settingsProvider);
|
|
final notifier = ref.read(settingsProvider.notifier);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: Text(l10n.settingsTitle)),
|
|
drawer: const AppDrawer(),
|
|
body: ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
children: [
|
|
_SectionTitle(l10n.settingsAppearance),
|
|
SegmentedButton<ThemeMode>(
|
|
segments: [
|
|
ButtonSegment(
|
|
value: ThemeMode.system,
|
|
label: Text(l10n.themeSystem),
|
|
icon: const Icon(Icons.brightness_auto),
|
|
),
|
|
ButtonSegment(
|
|
value: ThemeMode.light,
|
|
label: Text(l10n.themeLight),
|
|
icon: const Icon(Icons.light_mode),
|
|
),
|
|
ButtonSegment(
|
|
value: ThemeMode.dark,
|
|
label: Text(l10n.themeDark),
|
|
icon: const Icon(Icons.dark_mode),
|
|
),
|
|
],
|
|
selected: {settings.themeMode},
|
|
onSelectionChanged: (selection) =>
|
|
notifier.setThemeMode(selection.first),
|
|
),
|
|
const SizedBox(height: 24),
|
|
_SectionTitle(l10n.settingsTextSize),
|
|
SegmentedButton<AppTextSize>(
|
|
segments: [
|
|
ButtonSegment(
|
|
value: AppTextSize.normal,
|
|
label: Text(l10n.textSizeNormal),
|
|
),
|
|
ButtonSegment(
|
|
value: AppTextSize.large,
|
|
label: Text(l10n.textSizeLarge),
|
|
),
|
|
ButtonSegment(
|
|
value: AppTextSize.extraLarge,
|
|
label: Text(l10n.textSizeXLarge),
|
|
),
|
|
],
|
|
selected: {settings.textSize},
|
|
onSelectionChanged: (selection) =>
|
|
notifier.setTextSize(selection.first),
|
|
),
|
|
const SizedBox(height: 16),
|
|
SwitchListTile(
|
|
title: Text(l10n.settingsHighContrast),
|
|
value: settings.highContrast,
|
|
onChanged: notifier.setHighContrast,
|
|
contentPadding: EdgeInsets.zero,
|
|
),
|
|
const Divider(height: 32),
|
|
_SectionTitle(l10n.settingsReminderTime),
|
|
ListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
leading: const Icon(Icons.notifications_active),
|
|
title: Text(
|
|
settings.reminderTime.format(context),
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
),
|
|
subtitle: Text(l10n.settingsReminderTimeHint),
|
|
trailing: const Icon(Icons.chevron_right),
|
|
onTap: () async {
|
|
final picked = await showTimePicker(
|
|
context: context,
|
|
initialTime: settings.reminderTime,
|
|
);
|
|
if (picked != null) {
|
|
notifier.setReminderTime(picked);
|
|
}
|
|
},
|
|
),
|
|
const Divider(height: 32),
|
|
_SectionTitle(l10n.settingsAccount),
|
|
ListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
leading: const Icon(Icons.person),
|
|
title: Text(
|
|
ref.watch(authStateProvider).value?.email ?? '',
|
|
),
|
|
subtitle: Text(l10n.logout),
|
|
trailing: const Icon(Icons.logout),
|
|
onTap: () => ref.read(authRepositoryProvider).signOut(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SectionTitle extends StatelessWidget {
|
|
const _SectionTitle(this.title);
|
|
|
|
final String title;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 12),
|
|
child: Text(title, style: Theme.of(context).textTheme.titleMedium),
|
|
);
|
|
}
|
|
}
|