59 lines
2.2 KiB
Dart
59 lines
2.2 KiB
Dart
|
|
// lib/editor/persistence/sidecar_flush_observer.dart
|
||
|
|
//
|
||
|
|
// Phase 6 / §F.3 of the file-based storage plan (docs/plans/2026-06-24-file-
|
||
|
|
// based-storage.md): app-lifecycle flush hardening.
|
||
|
|
//
|
||
|
|
// The per-file SidecarRepository debounces writes by 800 ms. That window is the
|
||
|
|
// data-loss gap on a Windows tablet: if the OS suspends or closes the app
|
||
|
|
// before the timer fires, the last strokes never reach disk. This observer
|
||
|
|
// listens for the app leaving the foreground and DRAINS every open repo's
|
||
|
|
// pending write before the process can be frozen, so "never lose the last
|
||
|
|
// strokes on app close" holds even when the editor's own dispose() doesn't run.
|
||
|
|
//
|
||
|
|
// Registered once in BadNoteApp; it delegates to
|
||
|
|
// [SidecarRepositoryRegistry.flushAll], which awaits every repo's flush().
|
||
|
|
|
||
|
|
import 'package:flutter/widgets.dart';
|
||
|
|
|
||
|
|
import 'sidecar_repository.dart';
|
||
|
|
|
||
|
|
/// A [WidgetsBindingObserver] that flushes all open sidecar repositories when
|
||
|
|
/// the app leaves the foreground (`inactive`/`paused`/`detached`/`hidden`).
|
||
|
|
class SidecarFlushObserver with WidgetsBindingObserver {
|
||
|
|
/// Whether the observer is currently registered with the binding.
|
||
|
|
bool get isAttached => _attached;
|
||
|
|
bool _attached = false;
|
||
|
|
|
||
|
|
/// Register with [WidgetsBinding.instance] so lifecycle changes are observed.
|
||
|
|
void attach() {
|
||
|
|
if (_attached) return;
|
||
|
|
WidgetsBinding.instance.addObserver(this);
|
||
|
|
_attached = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Stop observing lifecycle changes.
|
||
|
|
void detach() {
|
||
|
|
if (!_attached) return;
|
||
|
|
WidgetsBinding.instance.removeObserver(this);
|
||
|
|
_attached = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||
|
|
switch (state) {
|
||
|
|
// Any transition out of the foreground is a potential suspend/kill point:
|
||
|
|
// drain pending sidecar writes now (the editors' own dispose() may never
|
||
|
|
// run when the OS freezes the process).
|
||
|
|
case AppLifecycleState.inactive:
|
||
|
|
case AppLifecycleState.hidden:
|
||
|
|
case AppLifecycleState.paused:
|
||
|
|
case AppLifecycleState.detached:
|
||
|
|
// Fire-and-forget at the framework boundary, but each write is atomic
|
||
|
|
// and awaited inside flushAll, so a half-written sidecar is impossible.
|
||
|
|
SidecarRepositoryRegistry.flushAll();
|
||
|
|
case AppLifecycleState.resumed:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|