feat(studyset): make a studyset private or not, and what a locked one shows (ADR 0019)

- A top-level studyset's options make it private, with everything inside
  it, or non-private again, after a dialog saying what changes and that
  the instance's backups keep readable copies until they expire; a device
  without the account's keys says how to get them there.
- A locked studyset (its key doesn't open here) is titled Locked, says
  why when opened, and offers only Delete.
- Publishing names the person's tags as the device reads them, and says
  what to do when the studyset is private or a tag has no name.
- The export sheet says an archive is readable by whoever has the file.

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-21 21:40:27 +02:00
commit 0e10afcc68
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -0,0 +1,64 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_learn_flutter/l10n/app_localizations.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/widget/studyset_actions.dart';
/// Privacy in a studyset's options (ADR 0019): chosen at the top level,
/// and a locked studyset can only be deleted.
void main() {
final l10n = lookupAppLocalizations(const Locale('en'));
Set<StudysetOption> optionsOf(Studyset studyset) => {
for (final entry in studysetOptions(l10n, studyset)) entry.option,
};
test('a readable top-level studyset can be made private, a private one '
'non-private', () {
final readable = Studyset(
id: 's',
content: StudysetContent.titled('Diary'),
);
final private = Studyset(
id: 's',
content: StudysetContent.titled('Diary'),
isPrivate: true,
);
expect(optionsOf(readable), contains(StudysetOption.makePrivate));
expect(optionsOf(readable), isNot(contains(StudysetOption.makeReadable)));
expect(optionsOf(private), contains(StudysetOption.makeReadable));
expect(optionsOf(private), isNot(contains(StudysetOption.makePrivate)));
});
test("a topic's privacy is its tree's: it offers neither", () {
final topic = Studyset(
id: 't',
content: StudysetContent.titled('Kanji'),
parentId: 's',
isPrivate: true,
);
expect(
optionsOf(topic),
isNot(
anyOf(
contains(StudysetOption.makePrivate),
contains(StudysetOption.makeReadable),
),
),
);
});
test('a locked studyset offers nothing but deleting', () {
final locked = Studyset(
id: 's',
content: StudysetContent.titled(''),
isPrivate: true,
isLocked: true,
);
expect(optionsOf(locked), {StudysetOption.delete});
});
}