feat(app): the home dashboard's data: activity, streak, goal, layout

What the dashboard shows, worked out from this device's data: cards
rated (crammed included, fresh starts not) and cards written per local
day; the streak, up to today or yesterday, rest days counting; the cards
falling due per day, but those resting in archived studysets; the study
goal's progress in its day or week. The goal, the rest days and the
dashboard's arrangement live in the synced settings (payload schema 3).
Cards tell when they were made by their UUID v7 ids; reviews report
their changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-01 21:59:52 +02:00
commit d846ee4582
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:clock/clock.dart';
import 'package:halkyon_learn_flutter/src/core/content/payload.dart';
import 'package:halkyon_learn_flutter/src/feature/card/data/card_gateway.dart';
@ -8,7 +9,8 @@ import 'package:halkyon_learn_flutter/src/feature/card/data/card_gateway.dart';
/// are `card-0001`, `card-0002`, …, revisions `rev-0001`, …;
/// [receiveRemoteRevision] plays a sync that brought another device's edit.
class FakeCardGateway() implements CardGateway {
final _cards = <String, ({String studysetId, String position})>{};
final _cards =
<String, ({String studysetId, String position, DateTime createdAt})>{};
final _revisions = <String, CardRevisionRow>{};
final _remoteChanges = StreamController<void>.broadcast();
var _nextId = 1;
@ -50,6 +52,12 @@ class FakeCardGateway() implements CardGateway {
if (card.studysetId == studysetId) (id: id, position: card.position),
];
@override
Future<List<CardStamp>> allCards() async => [
for (final MapEntry(key: id, value: card) in _cards.entries)
(id: id, studysetId: card.studysetId, createdAt: card.createdAt),
];
@override
Future<List<CardRevisionRow>> revisionsOf(Set<String> cardIds) async => [
for (final revision in _revisions.values)
@ -66,7 +74,11 @@ class FakeCardGateway() implements CardGateway {
throw error;
}
final id = _newId('card');
_cards[id] = (studysetId: studysetId, position: position);
_cards[id] = (
studysetId: studysetId,
position: position,
createdAt: clock.now().toUtc(),
);
await insertRevision(cardId: id, parents: const {}, payload: payload);
return id;
}
@ -93,7 +105,11 @@ class FakeCardGateway() implements CardGateway {
@override
Future<void> updatePosition(String cardId, String position) async {
if (_cards[cardId] case final card?) {
_cards[cardId] = (studysetId: card.studysetId, position: position);
_cards[cardId] = (
studysetId: card.studysetId,
position: position,
createdAt: card.createdAt,
);
}
}