feat(learn): the library on shelves by recency, narrowed to a subject (ADR 0041, D8)

The library as Vaia's (the developer's decisions of 10 Nov 2026): the
starred on a shelf of their own, then those studied or changed in the
last 7 days, the last month and before, each under its heading, the
archive last. Recent is the later of a card's last rating
(StudyProgress.lastStudied) and the studyset's own last change. A
dropdown narrows the library to one of the subjects in use, kept on the
device (DeviceSettings.librarySubject). A card names its subject under
its title, with its icon, and a language course its language beside.

Whats-New: The library groups your studysets by when you last used them, and narrows them to one subject.
Whats-New-De: Die Bibliothek ordnet deine Lernsets danach, wann du sie zuletzt genutzt hast, und zeigt auf Wunsch nur ein Fach.

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

View file

@ -0,0 +1,125 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_editor/halkyon_editor.dart';
import 'package:halkyon_learn_flutter/src/feature/study/model/study_progress.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/model/library_shelves.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/model/subject.dart';
/// The library as Vaia's (ADR 0041, D8, the developer's decisions of 10 Nov
/// 2026): the starred on top, then by when each was last studied or changed,
/// in the last 7 days, the last month and before; narrowed to a subject.
void main() {
final now = DateTime.utc(2026, 11, 10, 12);
Studyset studyset(
String id, {
DateTime? updatedAt,
bool starred = false,
String? subject,
}) => Studyset(
id: id,
content: StudysetContent(
title: id,
description: RichContent.empty(),
subject: subject,
),
updatedAt: updatedAt,
starredAt: starred ? DateTime.utc(2026) : null,
);
StudyProgress studied(DateTime at) => StudyProgress(
cards: 1,
due: 0,
reviewsDue: 0,
mature: 0,
lastStudied: at,
);
test('shelves by the later of the last rating and the last change, the '
'most recent first in each shelf', () {
final shelves = shelvesOf(
[
studyset('changed-today', updatedAt: now),
studyset('studied-3-days-ago', updatedAt: DateTime.utc(2026, 1, 2)),
studyset('changed-2-weeks-ago', updatedAt: DateTime.utc(2026, 10, 27)),
studyset('old', updatedAt: DateTime.utc(2026, 1, 2)),
studyset('yesterday', updatedAt: now.subtract(const Duration(days: 1))),
],
progress: {
'studied-3-days-ago': studied(now.subtract(const Duration(days: 3))),
'old': studied(DateTime.utc(2026, 2)),
},
now: now,
);
List<String> ids(LibraryShelf shelf) => [
for (final studyset in shelves[shelf] ?? const <Studyset>[]) studyset.id,
];
expect(ids(LibraryShelf.lastWeek), [
'changed-today',
'yesterday',
'studied-3-days-ago',
]);
expect(ids(LibraryShelf.lastMonth), ['changed-2-weeks-ago']);
expect(ids(LibraryShelf.older), ['old']);
expect(shelves.keys, isNot(contains(LibraryShelf.starred)));
});
test('the starred stand on their own shelf on top, however long ago', () {
final shelves = shelvesOf(
[
studyset('recent', updatedAt: now),
studyset('starred-old', updatedAt: DateTime.utc(2025), starred: true),
],
progress: const {},
now: now,
);
expect(shelves.keys.first, LibraryShelf.starred);
expect(shelves[LibraryShelf.starred]!.single.id, 'starred-old');
expect(shelves[LibraryShelf.lastWeek]!.single.id, 'recent');
});
test('a studyset from before changes were recorded counts from when it '
'was made, its UUID v7 saying when', () {
final shelves = shelvesOf(
[
// Made at 2026-11-08 00:00 UTC.
studyset('01a1b874-6c00-7000-8000-000000000000'),
studyset('not-a-uuid'),
],
progress: const {},
now: now,
);
expect(shelves[LibraryShelf.lastWeek]!.single.id, startsWith('01a1'));
expect(shelves[LibraryShelf.older]!.single.id, 'not-a-uuid');
});
test("the subjects in use, in the list's order; one without a subject, "
'or one this app does not know, counts as Other', () {
final studysets = [
studyset('rust', subject: 'software-engineering'),
studyset('japanese', subject: 'languages'),
studyset('notes'),
studyset('origami', subject: 'paper-folding'),
studyset('korean', subject: 'languages'),
];
expect(subjectsInUse(studysets), [
Subject.softwareEngineering,
Subject.languages,
Subject.other,
]);
expect(
[
for (final studyset in studysets)
if (filedUnder(studyset, Subject.other)) studyset.id,
],
['notes', 'origami'],
);
expect(filedUnder(studysets.first, Subject.softwareEngineering), isTrue);
expect(filedUnder(studysets.first, Subject.languages), isFalse);
});
}