feat(app): studysets, offline first and synced on triggers
The first synced feature. Every person gets their own local database (<app support>/people/<authUserId>/sync.sqlite): PersonScope opens it inside SessionGate's per-person subtree through Dependencies.openPerson, together with its sync and the person's repositories (PersonDependencies), and closes it when the person changes. SessionGate then also closes pushed screens, which live above the person's subtree; a screen's route() hands its bloc the repository it needs. SyncStudysetGateway reads and writes the studyset table through the sync session, names the payload column in every update and asks for a sync after every write. StudysetRepository encodes and decodes the payload (studyset_payload.dart: UTF-8 JSON with a schema version; a newer schema or garbage is skipped with a log), re-reads after its own writes and after merges, and blanks the payload before deleting, since the engine keeps deleted rows until the space is deleted (ADR 0002, rule 2). A failed read reaches the list as an error, and the next change reads again. SyncService syncs on triggers: at start, 2 s after the last local write, on resume and every 5 minutes, one sync at a time, and lets a running sync finish before the database closes. Screens: the list, now the home screen in place of the editor playground, creates studysets through a dialog; a studyset's screen edits its title and description, saves on request and asks before deleting. Tests: the payload codec, the repository against a fake gateway (including a failed read and rows from a newer app), the sync triggers under fakeAsync, both blocs' sequences, and the screens, including that cancelling the delete keeps the studyset. The session tests now settle until the person's data is open. AGENTS.md describes the per-person graph, synced data and the envelope as it now is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
6769598df5
commit
9387e0a827
1 changed files with 1594 additions and 82 deletions
|
|
@ -0,0 +1,64 @@
|
|||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_gateway.dart';
|
||||
|
||||
/// A [StudysetGateway] in memory: ids are `studyset-1`, `studyset-2`, …, and
|
||||
/// [receiveRemoteChange] plays a sync that brought changes.
|
||||
class FakeStudysetGateway() implements StudysetGateway {
|
||||
final _rows = <String, ByteData>{};
|
||||
final _remoteChanges = StreamController<void>.broadcast();
|
||||
var _nextId = 1;
|
||||
|
||||
/// Every payload written, in order, as (id, payload).
|
||||
final writes = <(String, ByteData)>[];
|
||||
|
||||
/// The ids of the deleted rows, in order.
|
||||
final deleted = <String>[];
|
||||
|
||||
/// When set, reading the studysets fails with it.
|
||||
Exception? failReads;
|
||||
|
||||
/// Stores [payload] as if another device had written it and a sync had
|
||||
/// brought it here.
|
||||
void receiveRemoteChange(String id, ByteData payload) {
|
||||
_rows[id] = payload;
|
||||
_remoteChanges.add(null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<StudysetRow>> all() async {
|
||||
if (failReads case final error?) {
|
||||
throw error;
|
||||
}
|
||||
return [
|
||||
for (final MapEntry(key: id, value: payload) in _rows.entries)
|
||||
(id: id, payload: payload),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> insert(ByteData payload) async {
|
||||
final id = 'studyset-${_nextId++}';
|
||||
_rows[id] = payload;
|
||||
writes.add((id, payload));
|
||||
return id;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updatePayload(String id, ByteData payload) async {
|
||||
if (_rows.containsKey(id)) {
|
||||
_rows[id] = payload;
|
||||
writes.add((id, payload));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(String id) async {
|
||||
_rows.remove(id);
|
||||
deleted.add(id);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<void> get remoteChanges => _remoteChanges.stream;
|
||||
}
|
||||
Loading…
Reference in a new issue