Fix calendar navigation, task deletion, and language defaults

- Add date parameter support to DailyAgendaPage for calendar navigation
- Add popup menu to task tile with delete and reschedule options
- Set default locale to English when none is saved
- Update API base URL comment for desktop vs Android
This commit is contained in:
m3mo 2026-02-02 21:07:27 +01:00
parent 820b35f5e6
commit 864560ef2e
6 changed files with 93 additions and 35 deletions

View File

@ -27,11 +27,7 @@ class SettingsPage extends StatelessWidget {
ListTile( ListTile(
leading: const Icon(Icons.language), leading: const Icon(Icons.language),
title: Text(l10n.language), title: Text(l10n.language),
subtitle: Text( subtitle: Text(settingsVm.getLanguageName(settingsVm.locale!)),
settingsVm.locale != null
? settingsVm.getLanguageName(settingsVm.locale!)
: l10n.systemDefault,
),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
onTap: () => _showLanguageDialog(context, settingsVm, l10n), onTap: () => _showLanguageDialog(context, settingsVm, l10n),
), ),
@ -107,12 +103,12 @@ class _LanguageDialog extends StatefulWidget {
} }
class _LanguageDialogState extends State<_LanguageDialog> { class _LanguageDialogState extends State<_LanguageDialog> {
late Locale? _selectedLocale; late String _selectedLanguage;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_selectedLocale = widget.vm.locale; _selectedLanguage = widget.vm.locale!.languageCode;
} }
@override @override
@ -121,27 +117,27 @@ class _LanguageDialogState extends State<_LanguageDialog> {
title: Text(widget.l10n.language), title: Text(widget.l10n.language),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: SettingsViewModel.supportedLocales.map((locale) { children: [
return ListTile( ...SettingsViewModel.supportedLocales.map((locale) {
title: Text(widget.vm.getLanguageName(locale)), return ListTile(
leading: Radio<Locale>( title: Text(widget.vm.getLanguageName(locale)),
value: locale, leading: Radio<String>(
groupValue: _selectedLocale, value: locale.languageCode,
onChanged: (value) { groupValue: _selectedLanguage,
setState(() => _selectedLocale = value); onChanged: (value) {
if (value != null) { setState(() => _selectedLanguage = value!);
widget.vm.setLocale(value); widget.vm.setLocale(locale);
Navigator.pop(context); Navigator.pop(context);
} },
),
onTap: () {
setState(() => _selectedLanguage = locale.languageCode);
widget.vm.setLocale(locale);
Navigator.pop(context);
}, },
), );
onTap: () { }),
setState(() => _selectedLocale = locale); ],
widget.vm.setLocale(locale);
Navigator.pop(context);
},
);
}).toList(),
), ),
actions: [ actions: [
TextButton( TextButton(

View File

@ -26,7 +26,7 @@ class SettingsViewModel extends ChangeNotifier {
]; ];
void _loadSettings() { void _loadSettings() {
_locale = dataSource.getLocale(); _locale = dataSource.getLocale() ?? const Locale('en');
_themeMode = dataSource.getThemeMode(); _themeMode = dataSource.getThemeMode();
logger.info('Settings loaded: locale=$_locale, themeMode=$_themeMode'); logger.info('Settings loaded: locale=$_locale, themeMode=$_themeMode');
} }

View File

@ -21,7 +21,8 @@ class TaskRemoteDataSourceImpl implements TaskRemoteDataSource {
final AppLogger logger; final AppLogger logger;
final http.Client _client; final http.Client _client;
static const String _baseUrl = 'http://10.0.2.2:8000'; // Use 10.0.2.2 for Android emulator, localhost for desktop/web
static const String _baseUrl = 'http://localhost:8000';
TaskRemoteDataSourceImpl({ TaskRemoteDataSourceImpl({
required this.logger, required this.logger,

View File

@ -10,12 +10,25 @@ import '../widgets/task_tile.dart';
import '../widgets/filter_chips.dart'; import '../widgets/filter_chips.dart';
class DailyAgendaPage extends StatelessWidget { class DailyAgendaPage extends StatelessWidget {
const DailyAgendaPage({super.key}); final String? initialDate;
const DailyAgendaPage({super.key, this.initialDate});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChangeNotifierProvider( return ChangeNotifierProvider(
create: (_) => getIt<DailyTasksViewModel>()..loadTasks(), create: (_) {
final vm = getIt<DailyTasksViewModel>();
if (initialDate != null) {
final date = DateTime.tryParse(initialDate!);
if (date != null) {
vm.setSelectedDate(date);
return vm;
}
}
vm.loadTasks();
return vm;
},
child: const _DailyAgendaView(), child: const _DailyAgendaView(),
); );
} }

View File

@ -118,10 +118,55 @@ class TaskTile extends StatelessWidget {
], ],
), ),
), ),
IconButton( PopupMenuButton<String>(
icon: const Icon(Icons.schedule), onSelected: (value) async {
onPressed: onReschedule, if (value == 'reschedule') {
tooltip: l10n.rescheduleToTomorrow, onReschedule();
} else if (value == 'delete') {
final confirm = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(l10n.deleteTask),
content: Text(l10n.deleteTaskConfirm),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text(l10n.cancel),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text(l10n.delete),
),
],
),
);
if (confirm == true) {
onDelete();
}
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'reschedule',
child: Row(
children: [
const Icon(Icons.schedule),
const SizedBox(width: 8),
Text(l10n.rescheduleToTomorrow),
],
),
),
PopupMenuItem(
value: 'delete',
child: Row(
children: [
Icon(Icons.delete, color: Theme.of(context).colorScheme.error),
const SizedBox(width: 8),
Text(l10n.delete, style: TextStyle(color: Theme.of(context).colorScheme.error)),
],
),
),
],
), ),
], ],
), ),

View File

@ -12,7 +12,10 @@ class AppRouter {
GoRoute( GoRoute(
path: '/', path: '/',
name: 'daily', name: 'daily',
builder: (context, state) => const DailyAgendaPage(), builder: (context, state) {
final dateStr = state.uri.queryParameters['date'];
return DailyAgendaPage(initialDate: dateStr);
},
), ),
GoRoute( GoRoute(
path: '/calendar', path: '/calendar',