fix(explore): a study button in a copy studies the copy's studyset
A study button names the studyset it studies by its id. Copied from a release or an archive, the studysets get new ids, and a button in the content kept naming the author's (or the archive's), which the person doesn't have: it studied nothing. The copier now writes the content after the studysets, each study button naming the studyset made of the one it named; only a study button's studyset, as a short id could also be a word of the content. A later release taken into a copy still brings the author's ids into its buttons: the copy updater doesn't rename them yet. 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>
This commit is contained in:
parent
ff31652a3a
commit
0d4f7a432d
2 changed files with 107 additions and 11 deletions
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:halkyon_learn_flutter/src/core/asset/data/asset_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/content_blocks.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/published_ref.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_payload.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
|
||||
|
|
@ -166,14 +167,26 @@ class ReleaseCopier(
|
|||
step();
|
||||
}
|
||||
|
||||
// Content naming a studyset of the release (a study button) names the
|
||||
// one made of it now, as it names the person's own files.
|
||||
final named = _read(
|
||||
release,
|
||||
assetIds,
|
||||
studysetIds: {
|
||||
for (final MapEntry(key: directory, value: item)
|
||||
in read.studysets.entries)
|
||||
item.id: studysetIds[directory]!,
|
||||
},
|
||||
);
|
||||
|
||||
final tagIds = <String, String>{};
|
||||
Future<Set<String>> tagsOf(List<String> names) async => {
|
||||
for (final name in names) tagIds[name] ??= await _tags.create(name),
|
||||
};
|
||||
|
||||
for (final directory in read.directories) {
|
||||
for (final directory in named.directories) {
|
||||
final studysetId = studysetIds[directory]!;
|
||||
final cards = _ordered(read.cards[directory]);
|
||||
final cards = _ordered(named.cards[directory]);
|
||||
final cardIds = await _cards.createAll(studysetId, [
|
||||
for (final card in cards)
|
||||
if (origin == null)
|
||||
|
|
@ -191,7 +204,7 @@ class ReleaseCopier(
|
|||
done += cards.length;
|
||||
onProgress?.call(done / total);
|
||||
|
||||
for (final note in _ordered(read.notes[directory])) {
|
||||
for (final note in _ordered(named.notes[directory])) {
|
||||
final (:noteId, revisionId: _) = await _notes.create(
|
||||
studysetId,
|
||||
origin == null
|
||||
|
|
@ -205,7 +218,7 @@ class ReleaseCopier(
|
|||
step();
|
||||
}
|
||||
|
||||
for (final path in _ordered(read.paths[directory])) {
|
||||
for (final path in _ordered(named.paths[directory])) {
|
||||
final pathId = ids[path.id] = await _paths.createPath(
|
||||
studysetId,
|
||||
origin == null
|
||||
|
|
@ -216,7 +229,7 @@ class ReleaseCopier(
|
|||
await _tags.setTagsOfPath(pathId, await tagsOf(path.tags));
|
||||
}
|
||||
step();
|
||||
for (final stepItem in _ordered(read.steps[path.id])) {
|
||||
for (final stepItem in _ordered(named.steps[path.id])) {
|
||||
await _paths.createStep(
|
||||
pathId,
|
||||
stepItem.kind!,
|
||||
|
|
@ -231,7 +244,7 @@ class ReleaseCopier(
|
|||
}
|
||||
}
|
||||
|
||||
for (final pdf in _ordered(read.pdfs[directory])) {
|
||||
for (final pdf in _ordered(named.pdfs[directory])) {
|
||||
await _pdfs.add(
|
||||
studysetId,
|
||||
origin == null
|
||||
|
|
@ -245,9 +258,15 @@ class ReleaseCopier(
|
|||
}
|
||||
|
||||
/// [release]'s items decoded, each file of the release named by the
|
||||
/// asset row [assetIds] gives its old one; throws [ReleaseUnreadable] for
|
||||
/// an item this app can't read.
|
||||
_Read _read(PublishedRelease release, Map<String, String> assetIds) {
|
||||
/// asset row [assetIds] gives its old one, and each studyset of it a
|
||||
/// study button names by the one [studysetIds] gives (the content's only
|
||||
/// reference to a studyset); throws [ReleaseUnreadable] for an item this
|
||||
/// app can't read.
|
||||
_Read _read(
|
||||
PublishedRelease release,
|
||||
Map<String, String> assetIds, {
|
||||
Map<String, String> studysetIds = const {},
|
||||
}) {
|
||||
final read = _Read();
|
||||
try {
|
||||
for (final MapEntry(key: path, value: json) in release.items.entries) {
|
||||
|
|
@ -269,7 +288,7 @@ class ReleaseCopier(
|
|||
tag! as String,
|
||||
];
|
||||
// The person's own files in place of the author's.
|
||||
var text = jsonEncode(json['payload']);
|
||||
var text = jsonEncode(_renamed(json['payload'], studysetIds));
|
||||
for (final MapEntry(key: old, value: own) in assetIds.entries) {
|
||||
text = text.replaceAll(old, own);
|
||||
}
|
||||
|
|
@ -348,6 +367,25 @@ class ReleaseCopier(
|
|||
|
||||
static const _kinds = {'cards', 'notes', 'paths', 'pdfs'};
|
||||
|
||||
/// [json] with every studyset a study button names by the one [names]
|
||||
/// gives in its place.
|
||||
static Object? _renamed(Object? json, Map<String, String> names) =>
|
||||
names.isEmpty
|
||||
? json
|
||||
: switch (json) {
|
||||
final List<Object?> list => [
|
||||
for (final item in list) _renamed(item, names),
|
||||
],
|
||||
final Map<String, Object?> map => {
|
||||
for (final MapEntry(:key, :value) in map.entries)
|
||||
key: switch ((key, value)) {
|
||||
(studyButtonStudysetKey, final String id) => names[id] ?? id,
|
||||
_ => _renamed(value, names),
|
||||
},
|
||||
},
|
||||
_ => json,
|
||||
};
|
||||
|
||||
/// The directory of the studyset [directory] is in.
|
||||
static String _parentOf(String directory) => switch (directory.lastIndexOf(
|
||||
'/',
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'dart:typed_data';
|
|||
import 'package:archive/archive.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/asset/data/asset_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/content_blocks.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/archive/data/archive_importer.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/archive/data/archive_reader.dart';
|
||||
|
|
@ -12,7 +13,12 @@ import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart
|
|||
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/dictionary/data/dictionary_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/explore/data/release_copier.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_payload.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/learning_path_step_payload.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/model/exercise.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/model/learning_path.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/learning_path/model/step_content.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/note/data/note_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/pdf_source/data/pdf_source_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/study/data/review_repository.dart';
|
||||
|
|
@ -163,13 +169,14 @@ void main() {
|
|||
late StudysetRepository studysets;
|
||||
late TagRepository tags;
|
||||
late ReviewRepository reviews;
|
||||
late LearningPathRepository paths;
|
||||
late ArchiveImporter importer;
|
||||
|
||||
setUp(() {
|
||||
studysets = StudysetRepository(FakeStudysetGateway());
|
||||
cards = CardRepository(FakeCardGateway());
|
||||
final notes = NoteRepository(FakeNoteGateway());
|
||||
final paths = LearningPathRepository(FakeLearningPathGateway());
|
||||
paths = LearningPathRepository(FakeLearningPathGateway());
|
||||
tags = TagRepository(
|
||||
FakeTagGateway(),
|
||||
cardChanges: cards.changes,
|
||||
|
|
@ -203,6 +210,57 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('content naming a studyset of the archive, as a study button '
|
||||
'does, names it as it is in the library', () async {
|
||||
final archive = {
|
||||
...japanese(),
|
||||
'paths/drill/path.json': {
|
||||
'id': 'drill',
|
||||
'position': 'a0',
|
||||
'payload': json(
|
||||
encodeLearningPathPayload(LearningPathContent.titled('Drill')),
|
||||
),
|
||||
},
|
||||
'paths/drill/steps/go.json': {
|
||||
'id': 'go',
|
||||
'position': 'a0',
|
||||
'kind': StepKind.info.value,
|
||||
'payload': json(
|
||||
encodeStepPayload(
|
||||
StepContent.empty(StepKind.info).withField(
|
||||
StepField.body,
|
||||
RichContent({
|
||||
'document': {
|
||||
'type': 'page',
|
||||
'children': [
|
||||
{
|
||||
'type': studyButtonBlockType,
|
||||
'data': {studyButtonStudysetKey: 'vocabulary'},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
await importer.import(readArchive(zip(archive)));
|
||||
|
||||
final vocabulary = (await studysets.all()).singleWhere(
|
||||
(s) => s.content.title == 'Vocabulary',
|
||||
);
|
||||
final path = (await paths.pathsOf(
|
||||
(await studysets.all()).singleWhere((s) => s.parentId == null).id,
|
||||
)).single;
|
||||
final step = (await paths.stepsOf(path.id)).single;
|
||||
expect(
|
||||
jsonEncode(step.content.body.toJson()),
|
||||
contains('"$studyButtonStudysetKey":"${vocabulary.id}"'),
|
||||
);
|
||||
});
|
||||
|
||||
test("brings the studysets and cards in as the person's own, a word "
|
||||
'from the dictionary as adding it makes it, the reviews onto the '
|
||||
'cards they were of; an entry the instance lacks is left out and '
|
||||
|
|
|
|||
Loading…
Reference in a new issue