m3mo 28c5380d31 Add mode switching and fix logout flow in settings
- Display current app mode in settings
- Add dialog to switch between local and online modes
- Fix logout to reset setup state and return to setup screen
2026-02-03 14:22:48 +01:00

377 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'package:agenda_tasks/l10n/app_localizations.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../../../auth/presentation/viewmodels/auth_viewmodel.dart';
import '../../domain/enums/app_mode.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.appMode),
ListTile(
leading: Icon(
settingsVm.isLocalMode ? Icons.smartphone : Icons.cloud_sync,
),
title: Text(settingsVm.isLocalMode ? l10n.localMode : l10n.onlineMode),
subtitle: Text(
settingsVm.isLocalMode
? l10n.useLocallyDesc
: l10n.syncOnlineDesc,
),
trailing: const Icon(Icons.chevron_right),
onTap: () => _showModeSwitchDialog(context, settingsVm, l10n),
),
const Divider(),
_SectionHeader(title: l10n.general),
ListTile(
leading: const Icon(Icons.language),
title: Text(l10n.language),
subtitle: Text(settingsVm.getLanguageName(settingsVm.locale!)),
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'),
),
// Only show logout button in online mode when authenticated
if (settingsVm.isOnlineMode) ...[
const Divider(),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SizedBox(
height: 56,
child: OutlinedButton.icon(
onPressed: () => _showLogoutConfirmation(context, l10n),
icon: const Icon(Icons.logout),
label: Text(l10n.logout),
style: OutlinedButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.error,
side: BorderSide(color: Theme.of(context).colorScheme.error),
),
),
),
),
],
const SizedBox(height: 32),
],
),
);
}
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: (dialogContext) => _LanguageDialog(
vm: vm,
l10n: l10n,
),
);
}
void _showThemeDialog(
BuildContext context,
SettingsViewModel vm,
AppLocalizations l10n,
) {
showDialog(
context: context,
builder: (dialogContext) => _ThemeDialog(
vm: vm,
l10n: l10n,
),
);
}
void _showModeSwitchDialog(
BuildContext context,
SettingsViewModel settingsVm,
AppLocalizations l10n,
) {
final isLocalMode = settingsVm.isLocalMode;
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(isLocalMode ? l10n.switchToOnline : l10n.switchToLocal),
content: Text(l10n.switchModeWarning),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: Text(l10n.cancel),
),
FilledButton(
onPressed: () async {
Navigator.pop(dialogContext);
if (isLocalMode) {
// Switching from local to online
// First, ask if user wants to upload local tasks
final uploadTasks = await _showUploadTasksDialog(context, l10n);
if (uploadTasks == true) {
// TODO: Implement task upload to server after login
// For now, just switch mode and go to login
}
await settingsVm.setAppMode(AppMode.online);
if (context.mounted) {
context.go('/login');
}
} else {
// Switching from online to local
await context.read<AuthViewModel>().logout();
await settingsVm.setAppMode(AppMode.local);
if (context.mounted) {
context.go('/');
}
}
},
child: Text(isLocalMode ? l10n.switchToOnline : l10n.switchToLocal),
),
],
),
);
}
Future<bool?> _showUploadTasksDialog(
BuildContext context,
AppLocalizations l10n,
) async {
return showDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(l10n.uploadTasksQuestion),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: Text(l10n.no),
),
FilledButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: Text(l10n.yes),
),
],
),
);
}
void _showLogoutConfirmation(BuildContext context, AppLocalizations l10n) {
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(l10n.logout),
content: Text('Are you sure you want to logout?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: Text(l10n.cancel),
),
FilledButton(
onPressed: () async {
final authVm = context.read<AuthViewModel>();
final settingsVm = context.read<SettingsViewModel>();
Navigator.pop(dialogContext);
await authVm.logout();
// Reset setup so user returns to setup screen on next launch
await settingsVm.setSetupCompleted(false);
await settingsVm.setOnboardingShown(false);
if (context.mounted) {
context.go('/setup');
}
},
style: FilledButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.error,
),
child: Text(l10n.logout),
),
],
),
);
}
}
class _LanguageDialog extends StatefulWidget {
final SettingsViewModel vm;
final AppLocalizations l10n;
const _LanguageDialog({required this.vm, required this.l10n});
@override
State<_LanguageDialog> createState() => _LanguageDialogState();
}
class _LanguageDialogState extends State<_LanguageDialog> {
late String _selectedLanguage;
@override
void initState() {
super.initState();
_selectedLanguage = widget.vm.locale!.languageCode;
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(widget.l10n.language),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
...SettingsViewModel.supportedLocales.map((locale) {
return ListTile(
title: Text(widget.vm.getLanguageName(locale)),
leading: Radio<String>(
value: locale.languageCode,
groupValue: _selectedLanguage,
onChanged: (value) {
setState(() => _selectedLanguage = value!);
widget.vm.setLocale(locale);
Navigator.pop(context);
},
),
onTap: () {
setState(() => _selectedLanguage = locale.languageCode);
widget.vm.setLocale(locale);
Navigator.pop(context);
},
);
}),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(widget.l10n.cancel),
),
],
);
}
}
class _ThemeDialog extends StatefulWidget {
final SettingsViewModel vm;
final AppLocalizations l10n;
const _ThemeDialog({required this.vm, required this.l10n});
@override
State<_ThemeDialog> createState() => _ThemeDialogState();
}
class _ThemeDialogState extends State<_ThemeDialog> {
late ThemeMode _selectedMode;
@override
void initState() {
super.initState();
_selectedMode = widget.vm.themeMode;
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(widget.l10n.darkMode),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildThemeOption(ThemeMode.system, widget.l10n.systemDefault),
_buildThemeOption(ThemeMode.light, widget.l10n.lightMode),
_buildThemeOption(ThemeMode.dark, widget.l10n.darkModeOption),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(widget.l10n.cancel),
),
],
);
}
Widget _buildThemeOption(ThemeMode mode, String label) {
return ListTile(
title: Text(label),
leading: Radio<ThemeMode>(
value: mode,
groupValue: _selectedMode,
onChanged: (value) {
setState(() => _selectedMode = value!);
widget.vm.setThemeMode(value!);
Navigator.pop(context);
},
),
onTap: () {
setState(() => _selectedMode = mode);
widget.vm.setThemeMode(mode);
Navigator.pop(context);
},
);
}
}
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,
),
),
);
}
}