mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 22:47:01 +01:00
739 -> 249 lines. As with note_editor, the ~600-line State class needed
decomposing rather than moving:
capture_draft.dart the note being captured and its write-through
to storage -- the model behind "every keystroke
is already persisted". Lazy row creation stays
here; the screen just logs that it happened.
capture_sync_runner.dart one sync pass, with no BuildContext. This also
de-duplicates the two callers: the manual button
and the background tick ran byte-identical
runSync blocks differing only in the snackbar
and the 'auto' log flag.
capture_status.dart SyncStatusStore (the persisted last outcome) and
describeSyncResult.
capture_app_bar.dart the app bar.
capture_meta_row.dart the priority/status pickers.
capture_status_footer.dart the two status lines.
capture_screen_{widget,sync}.dart
parts, for the same reason as note_editor's.
The mounted checks are preserved exactly, including the one in
SyncStatusStore.restore that must fire AFTER its await rather than before
(writing the notifier post-dispose is the bug it exists to prevent), which
is why it takes a stillLive callback instead of a bool.
Verified: 313 tests pass, analyze clean, every capture file at 100%
coverage. lib/ and bin/ are now down to three files over the cap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
104 lines
3.3 KiB
Dart
104 lines
3.3 KiB
Dart
/// Runs a sync pass for the capture screen and reports its outcome.
|
|
///
|
|
/// Split out of `capture_screen.dart` for file size, and it de-duplicates the
|
|
/// two callers at the same time: the manual button and the background tick
|
|
/// ran byte-identical `runSync` blocks that differed only in whether they
|
|
/// show a snackbar and what they log.
|
|
///
|
|
/// Deliberately has no `BuildContext` and no `setState`: the screen decides
|
|
/// what to show, this decides what to do.
|
|
library;
|
|
|
|
import 'package:crdt_sync/crdt_sync.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import 'package:todo/analytics/analytics_service.dart';
|
|
import 'package:todo/data/app_settings.dart';
|
|
import 'package:todo/data/note_repository.dart';
|
|
import 'package:todo/sync/run_sync.dart';
|
|
import 'package:todo/sync/sync_settings.dart';
|
|
import 'package:todo/ui/capture_status.dart';
|
|
|
|
/// What a sync attempt did, in the terms the capture screen cares about.
|
|
class CaptureSyncOutcome {
|
|
/// Creates an outcome.
|
|
const CaptureSyncOutcome({
|
|
required this.ok,
|
|
required this.detail,
|
|
this.reconciled,
|
|
});
|
|
|
|
/// Whether the pass completed without throwing.
|
|
final bool ok;
|
|
|
|
/// One-line summary: merged/skipped counts on success, the error otherwise.
|
|
final String detail;
|
|
|
|
/// Settings reconciled during the pass, when the pass succeeded.
|
|
final AppSettings? reconciled;
|
|
}
|
|
|
|
/// Runs sync passes against one repository and settings pair.
|
|
class CaptureSyncRunner {
|
|
/// Creates a runner over the screen's injected dependencies.
|
|
const CaptureSyncRunner({
|
|
required this.repository,
|
|
required this.status,
|
|
this.analytics,
|
|
this.httpClient,
|
|
this.firebaseFactory,
|
|
this.stateStore,
|
|
});
|
|
|
|
/// The store being synced.
|
|
final NoteRepository repository;
|
|
|
|
/// Where the outcome is recorded and persisted.
|
|
final SyncStatusStore status;
|
|
|
|
/// Optional analytics sink, passed straight through to [runSync].
|
|
final AnalyticsService? analytics;
|
|
|
|
/// Injected HTTP client, so tests run the real path without network.
|
|
final http.Client? httpClient;
|
|
|
|
/// Injected Firebase client factory, for the same reason.
|
|
final Future<FirebaseRestClient?> Function()? firebaseFactory;
|
|
|
|
/// Injected sync-state store.
|
|
final SyncStateStore? stateStore;
|
|
|
|
/// Runs one pass and records the outcome.
|
|
///
|
|
/// [live] is the screen's `mounted` at the moment of recording -- the pass
|
|
/// still finishes and still persists its outcome after the screen is gone,
|
|
/// it just must not write the notifier.
|
|
Future<CaptureSyncOutcome> run({
|
|
required SyncSettings settings,
|
|
required AppSettings appSettings,
|
|
required bool Function() live,
|
|
}) async {
|
|
try {
|
|
final result = await runSync(
|
|
repository,
|
|
settings,
|
|
appSettings: appSettings,
|
|
analytics: analytics,
|
|
httpClient: httpClient,
|
|
firebaseFactory: firebaseFactory,
|
|
stateStore: stateStore,
|
|
);
|
|
final detail = describeSyncResult(result.syncResult);
|
|
await status.record(ok: true, detail: detail, live: live());
|
|
return CaptureSyncOutcome(
|
|
ok: true,
|
|
detail: detail,
|
|
reconciled: result.appSettings,
|
|
);
|
|
} on Exception catch (e) {
|
|
await status.record(ok: false, detail: '$e', live: live());
|
|
return CaptureSyncOutcome(ok: false, detail: '$e');
|
|
}
|
|
}
|
|
}
|