Some checks failed
CI / Windows build (push) Has been cancelled
Phase 6 (final storage phase). - SidecarRepositoryRegistry tracks every open repo; SidecarFlushObserver (a WidgetsBindingObserver in main) flushes them all on inactive/hidden/paused/detached, awaiting each flush — the last strokes can't be lost on app close, not just on the 800ms timer. - VaultSearchIndex rebuilds by scanning vault sidecars (the source of truth) — note titles, OCR text and document names — and search_provider queries it, so search spans notes + PDFs. Rebuilt on launch / after import. The vault file-based storage migration (Phases 0-6) is complete: annotations travel with the file, picked vault folder, atomic autosave, one Import-file entry, SQLite migrated to sidecars. analyze clean, tests green.
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;
|
|
}
|
|
}
|
|
}
|