@@ -22,6 +22,10 @@ import 'package:pyble/pble/pble.dart';
2222import 'package:pyble/theme/theme.dart' ;
2323
2424import 'editor_document.dart' ;
25+ import 'board_target_guard.dart' ;
26+ import 'draft_recovery.dart' ;
27+ import 'recovery_gate.dart' ;
28+ import 'recovery_panel.dart' ;
2529import 'editor_display_settings.dart' ;
2630import 'editor_surface.dart' ;
2731import 'editor_surface_factory.dart' ;
@@ -103,6 +107,7 @@ class _EditorViewState extends ConsumerState<EditorView> {
103107 // present, and it fixes on tap — the buffer is never rewritten behind
104108 // the user's back.
105109 const _SmartPunctuationBanner (),
110+ const RecoveryStatusBar (),
106111 // The editable code well — a recessed monospace surface (§1.4).
107112 Expanded (
108113 child: surfaceBuilder (
@@ -267,6 +272,8 @@ class _EditorHeader extends ConsumerWidget {
267272 try {
268273 final String path = await ref.read (saveControllerProvider).save ();
269274 messenger.showSnackBar (SnackBar (content: Text (l10n.editorSaved (path))));
275+ } on BoardActionCancelled {
276+ return ;
270277 } on PbleException catch (e) {
271278 // The typed §8 status name (EBusy / ENoSpc / ECrc …) is a PBLE/1
272279 // technical identifier: rendered verbatim, never localized (FR-I18N-4).
@@ -291,27 +298,77 @@ class _EditorHeader extends ConsumerWidget {
291298 /// no way to get it back (the document is volatile by design).
292299 Future <void > _newDocument (BuildContext context, WidgetRef ref) async {
293300 final AppLocalizations l10n = AppLocalizations .of (context);
294- if (ref.read (editorDocumentProvider).dirty) {
295- final bool ? discard = await showDialog <bool >(
296- context: context,
297- builder: (BuildContext ctx) => AlertDialog (
298- title: Text (l10n.editorDiscardTitle),
299- content: Text (l10n.editorDiscardDetail),
300- actions: < Widget > [
301- TextButton (
302- onPressed: () => Navigator .of (ctx).pop (false ),
303- child: Text (l10n.commonCancel),
304- ),
305- TextButton (
306- onPressed: () => Navigator .of (ctx).pop (true ),
307- child: Text (l10n.editorDiscardConfirm),
308- ),
309- ],
310- ),
311- );
312- if (discard != true ) return ;
301+ final int epoch = ref.read (editorDocumentEpochProvider);
302+ final EditorDocument document = ref.read (editorDocumentProvider);
303+ final DraftRecoveryState recovery = ref.read (draftRecoveryProvider);
304+ final bool meaningful =
305+ document.content.isNotEmpty ||
306+ document.name != kDefaultDocumentName ||
307+ document.boardPath != null ;
308+ final bool needsChoice =
309+ document.dirty ||
310+ (recovery.enabled &&
311+ meaningful &&
312+ recovery.status != DraftRecoveryStatus .saved);
313+ if (needsChoice) {
314+ if (recovery.enabled) {
315+ if (! await confirmRecoveryReplacement (context)) return ;
316+ } else {
317+ final bool ? discard = await showDialog <bool >(
318+ context: context,
319+ builder: (BuildContext ctx) => AlertDialog (
320+ scrollable: true ,
321+ title: Text (l10n.editorDiscardTitle),
322+ content: Text (l10n.editorDiscardDetail),
323+ actions: < Widget > [
324+ TextButton (
325+ onPressed: () => Navigator .of (ctx).pop (false ),
326+ child: Text (l10n.commonCancel),
327+ ),
328+ TextButton (
329+ onPressed: () => Navigator .of (ctx).pop (true ),
330+ child: Text (l10n.editorDiscardConfirm),
331+ ),
332+ ],
333+ ),
334+ );
335+ if (discard != true ) return ;
336+ }
337+ }
338+ if (! context.mounted) return ;
339+ if (ref.read (editorDocumentEpochProvider) != epoch) {
340+ ScaffoldMessenger .of (
341+ context,
342+ ).showSnackBar (SnackBar (content: Text (l10n.recoveryChanged)));
343+ return ;
344+ }
345+ if (! recovery.enabled) {
346+ ref.read (editorDocumentProvider.notifier).newDocument ();
347+ return ;
348+ }
349+ final bool replaced = await ref
350+ .read (draftRecoveryProvider.notifier)
351+ .replaceDocument (const EditorDocument .untitled (), expectedEpoch: epoch);
352+ if (! replaced && context.mounted) {
353+ if (ref.read (editorDocumentEpochProvider) != epoch) {
354+ ScaffoldMessenger .of (
355+ context,
356+ ).showSnackBar (SnackBar (content: Text (l10n.recoveryChanged)));
357+ return ;
358+ }
359+ if (! await confirmVolatileReplacement (context)) return ;
360+ if (! context.mounted || ref.read (editorDocumentEpochProvider) != epoch) {
361+ return ;
362+ }
363+ ref.read (draftRecoveryProvider.notifier).continueWithoutRecovery ();
364+ await ref
365+ .read (draftRecoveryProvider.notifier)
366+ .replaceDocument (
367+ const EditorDocument .untitled (),
368+ expectedEpoch: epoch,
369+ allowVolatile: true ,
370+ );
313371 }
314- ref.read (editorDocumentProvider.notifier).newDocument ();
315372 }
316373}
317374
0 commit comments