Stage 4 of the public tier (ADR 0005, rule 6), its server and the app's data. A learner writes a report or a suggestion on a published studyset, a card, note or step of it, in their own synced space, so it works offline; replies and the author's decisions (closed, hidden, accepted, declined) likewise, in the writer's or the author's space. The instance gathers them: public to everyone signed in, but for what the author hides; for the author's inbox and the writer's. What counts is decided as rows are read, since a modified app could write anything: threads from people with an identity and a public profile the author hasn't blocked, replies from the writer and the author alone, decisions from the author alone, no suggestion under "no derivatives". The author gets the files a suggestion names from its writer's, and the next release credits the writers of the suggestions accepted since the last (Co-authored-by). Deleting a person deletes their blocks and what they read; what they wrote goes with their space. In the app, the repository writes reports and suggestions (a card, note or step as the learner has it, without what is theirs alone, with the files it names) and takes an accepted suggestion into the author's own card, note or step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
116 lines
3.2 KiB
Dart
116 lines
3.2 KiB
Dart
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:halkyon_learn_flutter/src/feature/feedback/data/feedback_gateway.dart';
|
|
import 'package:halkyon_learn_flutter/src/feature/feedback/model/feedback.dart';
|
|
|
|
/// A [FeedbackWriteGateway] in memory: what was written, in order.
|
|
class FakeFeedbackWriteGateway() implements FeedbackWriteGateway {
|
|
/// The threads written: their published studyset, kind and payload JSON.
|
|
final threads =
|
|
<
|
|
({String publicationId, FeedbackKind kind, Map<String, Object?> json})
|
|
>[];
|
|
|
|
/// The replies written, by thread.
|
|
final messages = <({String threadId, Map<String, Object?> json})>[];
|
|
|
|
/// The decisions written, by thread.
|
|
final decisions = <({String threadId, FeedbackDecision decision})>[];
|
|
|
|
@override
|
|
Future<String> insertThread({
|
|
required String publicationId,
|
|
required FeedbackKind kind,
|
|
required ByteData payload,
|
|
}) async {
|
|
threads.add((
|
|
publicationId: publicationId,
|
|
kind: kind,
|
|
json: _json(payload),
|
|
));
|
|
return 'thread-${threads.length}';
|
|
}
|
|
|
|
@override
|
|
Future<void> insertMessage(String threadId, ByteData payload) async =>
|
|
messages.add((threadId: threadId, json: _json(payload)));
|
|
|
|
@override
|
|
Future<void> insertDecision(
|
|
String threadId,
|
|
FeedbackDecision decision,
|
|
) async => decisions.add((threadId: threadId, decision: decision));
|
|
|
|
static Map<String, Object?> _json(ByteData payload) =>
|
|
jsonDecode(utf8.decode(Uint8List.sublistView(payload)))
|
|
as Map<String, Object?>;
|
|
}
|
|
|
|
/// A [FeedbackServerGateway] in memory: [threads] as the instance gathers
|
|
/// them (all public, for the inbox too), [files] by SHA-256.
|
|
class FakeFeedbackServerGateway({
|
|
final List<FeedbackThread> threads = const [],
|
|
final Map<String, Uint8List> files = const {},
|
|
}) implements FeedbackServerGateway {
|
|
late var _threads = [...threads];
|
|
|
|
/// The threads read, and the people blocked.
|
|
final seen = <String>[];
|
|
final blocked = <String>[];
|
|
|
|
/// When set, every request fails with it.
|
|
Exception? fail;
|
|
|
|
/// Replaces what the instance gathers.
|
|
set gathered(List<FeedbackThread> threads) => _threads = [...threads];
|
|
|
|
Future<void> _maybeFail() async {
|
|
if (fail case final error?) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<List<FeedbackThread>> threadsOf(String publicId) async {
|
|
await _maybeFail();
|
|
return [
|
|
for (final thread in _threads)
|
|
if (thread.publicId == publicId) thread,
|
|
];
|
|
}
|
|
|
|
@override
|
|
Future<List<FeedbackThread>> inbox() async {
|
|
await _maybeFail();
|
|
return [
|
|
for (final thread in _threads)
|
|
if (thread.mine || thread.forMe) thread,
|
|
];
|
|
}
|
|
|
|
@override
|
|
Future<int> unseen() async =>
|
|
(await inbox()).where((thread) => thread.unseen).length;
|
|
|
|
@override
|
|
Future<void> markSeen(String threadId) async {
|
|
await _maybeFail();
|
|
seen.add(threadId);
|
|
}
|
|
|
|
@override
|
|
Future<void> block(String handle) async => blocked.add(handle);
|
|
|
|
@override
|
|
Future<void> unblock(String handle) async => blocked.remove(handle);
|
|
|
|
@override
|
|
Future<List<String>> blockedPeople() async => [...blocked];
|
|
|
|
@override
|
|
Future<Uint8List> file(String threadId, String sha256) async {
|
|
await _maybeFail();
|
|
return files[sha256]!;
|
|
}
|
|
}
|