From 483264b652db40846f486bf5a6f91390d40cbb56 Mon Sep 17 00:00:00 2001 From: Brenno de Winter Date: Sat, 13 Jun 2026 07:49:07 +0200 Subject: [PATCH] Add "discard" option to the unsaved-changes close dialog Closing a presentation only offered Cancel or "Save and close", so unsaved edits could only be dropped by force-quitting. That left an autosave recovery snapshot behind, creating version ambiguity between the saved file on disk and the restored unsaved copy on next launch. Add a third "Niet opslaan" (discard) choice to the close/quit dialog. Discarding closes without saving; closeTab() and _destroy() already clear the recovery files, so no shadow version remains and there is a single unambiguous version afterwards. Register the "Niet opslaan" string for all 8 supported languages. Co-Authored-By: Claude Opus 4.8 --- lib/l10n/app_localizations.dart | 7 +++++ lib/widgets/app_shell.dart | 56 +++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index be0b7b6..ed9be67 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -783,6 +783,7 @@ const _dutchSourceStrings = { 'Verwijderen': 'Delete', 'Herstellen': 'Restore', 'Opslaan en sluiten': 'Save and close', + 'Niet opslaan': "Don't save", 'Niet-opgeslagen werk herstellen?': 'Restore unsaved work?', 'Niet-opgeslagen wijzigingen': 'Unsaved changes', 'Er is een presentatie met niet-opgeslagen wijzigingen gevonden van een vorige sessie:': @@ -1161,6 +1162,7 @@ const _dutchSourceStrings = { 'Verwijderen': 'Elimina', 'Herstellen': 'Ripristina', 'Opslaan en sluiten': 'Salva e chiudi', + 'Niet opslaan': 'Non salvare', 'Importeren via URL': 'Importa da URL', 'Ophalen': 'Recupera', 'Titelpagina': 'Slide titolo', @@ -1372,6 +1374,7 @@ const _dutchSourceStrings = { 'Verwijderen': 'Löschen', 'Herstellen': 'Wiederherstellen', 'Opslaan en sluiten': 'Speichern und schließen', + 'Niet opslaan': 'Nicht speichern', 'Importeren via URL': 'Von URL importieren', 'Ophalen': 'Abrufen', 'Titelpagina': 'Titelfolie', @@ -1584,6 +1587,7 @@ const _dutchSourceStrings = { 'Verwijderen': 'Supprimer', 'Herstellen': 'Restaurer', 'Opslaan en sluiten': 'Enregistrer et fermer', + 'Niet opslaan': 'Ne pas enregistrer', 'Importeren via URL': 'Importer depuis une URL', 'Ophalen': 'Récupérer', 'Titelpagina': 'Diapositive de titre', @@ -1795,6 +1799,7 @@ const _dutchSourceStrings = { 'Verwijderen': 'Eliminar', 'Herstellen': 'Restaurar', 'Opslaan en sluiten': 'Guardar y cerrar', + 'Niet opslaan': 'No guardar', 'Importeren via URL': 'Importar desde URL', 'Ophalen': 'Obtener', 'Titelpagina': 'Diapositiva de título', @@ -2007,6 +2012,7 @@ const _dutchSourceStrings = { 'Verwijderen': 'Fuortsmite', 'Herstellen': 'Weromsette', 'Opslaan en sluiten': 'Bewarje en slute', + 'Niet opslaan': 'Net bewarje', 'Importeren via URL': 'Ymportearje fan URL', 'Ophalen': 'Ophelje', 'Titelpagina': 'Titelslide', @@ -2219,6 +2225,7 @@ const _dutchSourceStrings = { 'Verwijderen': 'Kita', 'Herstellen': 'Restorá', 'Opslaan en sluiten': 'Warda i sera', + 'Niet opslaan': 'No warda', 'Importeren via URL': 'Importá for di URL', 'Ophalen': 'Tuma', 'Titelpagina': 'Slide di título', diff --git a/lib/widgets/app_shell.dart b/lib/widgets/app_shell.dart index 567e99a..90d4818 100644 --- a/lib/widgets/app_shell.dart +++ b/lib/widgets/app_shell.dart @@ -39,6 +39,9 @@ part 'shell/welcome_screen.dart'; part 'shell/status_bar.dart'; part 'shell/shell_overlays.dart'; +/// Keuze uit de "niet-opgeslagen wijzigingen"-dialoog bij het sluiten. +enum _CloseChoice { cancel, discard, save } + class AppShell extends ConsumerStatefulWidget { const AppShell({super.key}); @@ -128,14 +131,21 @@ class _AppShellState extends ConsumerState with WindowListener { @override void onWindowClose() async { if (ref.read(tabsProvider).anyDirty) { - final shouldSave = await _confirmSaveBeforeClose( + final choice = await _confirmSaveBeforeClose( context.l10n.d( 'Er zijn presentaties met niet-opgeslagen wijzigingen. Sla ze op voordat de app sluit.', ), ); - if (!shouldSave) return; - final saved = await _saveAllDirtyTabs(); - if (saved) await _destroy(); + switch (choice) { + case _CloseChoice.cancel: + return; + case _CloseChoice.discard: + // Wijzigingen verwerpen: herstelbestanden weg, niets opslaan. + await _destroy(); + case _CloseChoice.save: + final saved = await _saveAllDirtyTabs(); + if (saved) await _destroy(); + } } else { await _destroy(); } @@ -147,9 +157,9 @@ class _AppShellState extends ConsumerState with WindowListener { await windowManager.destroy(); } - Future _confirmSaveBeforeClose(String message) async { - if (!mounted) return false; - return await showDialog( + Future<_CloseChoice> _confirmSaveBeforeClose(String message) async { + if (!mounted) return _CloseChoice.cancel; + return await showDialog<_CloseChoice>( context: context, barrierDismissible: false, builder: (ctx) { @@ -159,18 +169,25 @@ class _AppShellState extends ConsumerState with WindowListener { content: Text(message), actions: [ TextButton( - onPressed: () => Navigator.pop(ctx, false), + onPressed: () => Navigator.pop(ctx, _CloseChoice.cancel), child: Text(l10n.t('cancel')), ), + TextButton( + onPressed: () => Navigator.pop(ctx, _CloseChoice.discard), + style: TextButton.styleFrom( + foregroundColor: Theme.of(ctx).colorScheme.error, + ), + child: Text(l10n.d('Niet opslaan')), + ), ElevatedButton( - onPressed: () => Navigator.pop(ctx, true), + onPressed: () => Navigator.pop(ctx, _CloseChoice.save), child: Text(l10n.d('Opslaan en sluiten')), ), ], ); }, ) ?? - false; + _CloseChoice.cancel; } Future _saveAllDirtyTabs() async { @@ -186,16 +203,23 @@ class _AppShellState extends ConsumerState with WindowListener { Future _onCloseTab(int index) async { final tab = ref.read(tabsProvider).tabs[index]; if (tab.isDirty) { - final shouldSave = await _confirmSaveBeforeClose( + final choice = await _confirmSaveBeforeClose( context.l10n.d( 'Deze presentatie heeft niet-opgeslagen wijzigingen. Sla de presentatie op voordat het tabblad sluit.', ), ); - if (!shouldSave) return; - final saved = await tab.deckNotifier.save( - initialDirectory: ref.read(settingsProvider).homeDirectory, - ); - if (!saved) return; + switch (choice) { + case _CloseChoice.cancel: + return; + case _CloseChoice.discard: + // Wijzigingen verwerpen: closeTab() ruimt ook het herstelbestand op. + break; + case _CloseChoice.save: + final saved = await tab.deckNotifier.save( + initialDirectory: ref.read(settingsProvider).homeDirectory, + ); + if (!saved) return; + } } ref.read(tabsProvider.notifier).closeTab(index); }