import 'dart:async'; import 'dart:typed_data'; import 'package:halkyon_learn_flutter/src/core/content/payload.dart'; import 'package:halkyon_learn_flutter/src/feature/card/data/card_gateway.dart'; /// A [CardGateway] in memory. Ids sort by creation, as UUID v7 ids do: cards /// are `card-0001`, `card-0002`, …, revisions `rev-0001`, …; /// [receiveRemoteRevision] plays a sync that brought another device's edit. class FakeCardGateway() implements CardGateway { final _cards = {}; final _revisions = {}; final _remoteChanges = StreamController.broadcast(); var _nextId = 1; /// Every revision written on this device, in order. final written = []; /// The deletion steps, in order: `blank `, `delete `. final deletions = []; /// When set, storing a card or a revision fails with it. Exception? failWrites; /// The payload each revision holds now, by revision id. Map get payloads => { for (final revision in _revisions.values) revision.id: revision.payload, }; /// Stores a revision as if another device had written it and a sync had /// brought it here. void receiveRemoteRevision({ required String id, required String cardId, required Set parents, required ByteData payload, }) { _revisions[id] = ( id: id, cardId: cardId, parents: parents, payload: payload, ); _remoteChanges.add(null); } @override Future> cardsOf(String studysetId) async => [ for (final MapEntry(key: id, value: card) in _cards.entries) if (card.studysetId == studysetId) (id: id, position: card.position), ]; @override Future> revisionsOf(Set cardIds) async => [ for (final revision in _revisions.values) if (cardIds.contains(revision.cardId)) revision, ]; @override Future insertCard({ required String studysetId, required String position, required ByteData payload, }) async { if (failWrites case final error?) { throw error; } final id = _newId('card'); _cards[id] = (studysetId: studysetId, position: position); await insertRevision(cardId: id, parents: const {}, payload: payload); return id; } @override Future insertRevision({ required String cardId, required Set parents, required ByteData payload, }) async { if (failWrites case final error?) { throw error; } final revision = ( id: _newId('rev'), cardId: cardId, parents: parents, payload: payload, ); _revisions[revision.id] = revision; written.add(revision); } @override Future updatePosition(String cardId, String position) async { if (_cards[cardId] case final card?) { _cards[cardId] = (studysetId: card.studysetId, position: position); } } @override Future blankRevisions(String cardId) async { deletions.add('blank $cardId'); for (final revision in [..._revisions.values]) { if (revision.cardId == cardId) { _revisions[revision.id] = ( id: revision.id, cardId: cardId, parents: revision.parents, payload: redactedPayload, ); } } } @override Future deleteCard(String cardId) async { deletions.add('delete $cardId'); _cards.remove(cardId); } @override Stream get remoteChanges => _remoteChanges.stream; String _newId(String kind) => '$kind-${(_nextId++).toString().padLeft(4, '0')}'; }