feat(studyset): a studyset's marker says what the instance sees of it (ADR 0019)

The developer's decision of 21 Oct, on the library's cards and the
studyset's Share in its action row alike:
- a lock: private, end-to-end encrypted;
- an open lock: readable by the instance, not published;
- a globe: published.

The lock and the open lock open one dialog saying what that means and
changing it (Make non-private; Make private, or Publish…, which opens the
Publish sheet, while it isn't private); a topic's dialog says its privacy
is its tree's. The globe opens the Publish sheet, whose status for a
studyset not published now says so ("Not published") with an open lock,
as "Private" means something else now. Texts in en and de.

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 23:07:31 +02:00
commit 9fb1ab2f6c
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -16,6 +16,7 @@ import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_conten
import 'package:halkyon_learn_flutter/src/feature/studyset/widget/deleted_studyset_card.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/widget/studyset_card.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:material_ui/material_ui.dart';
import '../../../support/fake_card_gateway.dart';
@ -85,6 +86,7 @@ void main() {
DateTime? archivedAt,
DateTime? starredAt,
String? publishedAt,
bool private = false,
}) => gateway.receiveRemoteChange(
id,
encodeStudysetPayload(
@ -100,6 +102,7 @@ void main() {
),
archivedAt: archivedAt,
starredAt: starredAt,
isPrivate: private,
);
/// [finder] within the card of the studyset titled [title].
@ -796,11 +799,13 @@ void main() {
expect(onCard('Kanji', find.text('By @alice')), findsOneWidget);
});
testWidgets('a lock while private, a globe once out on Explore, as the '
'instance says', (tester) async {
testWidgets('a lock while private, an open lock while the instance can '
'read it and it is not published, a globe once out on Explore, as '
'the instance says', (tester) async {
stored('japanese', 'Japanese');
stored('kanji', 'Kanji');
stored('kana', 'Kana');
stored('diary', 'Diary', private: true);
final store = MemoryPublishedStore();
await showLibrary(
@ -815,9 +820,25 @@ void main() {
publishedStore: store,
);
expect(
onCard('Japanese', find.byIcon(LucideIcons.globe)),
findsOneWidget,
);
expect(onCard('Japanese', find.byTooltip('Public')), findsOneWidget);
expect(onCard('Kanji', find.byTooltip('Private')), findsOneWidget);
expect(onCard('Kana', find.byTooltip('Private')), findsOneWidget);
expect(
onCard('Kanji', find.byIcon(LucideIcons.lockOpen)),
findsOneWidget,
);
expect(
onCard('Kanji', find.byTooltip('Not private, not published')),
findsOneWidget,
);
expect(
onCard('Kana', find.byTooltip('Not private, not published')),
findsOneWidget,
);
expect(onCard('Diary', find.byIcon(LucideIcons.lock)), findsOneWidget);
expect(onCard('Diary', find.byTooltip('Private')), findsOneWidget);
// Remembered for when the instance can't be reached.
expect(store.kept, {'japanese'});
});
@ -835,21 +856,74 @@ void main() {
);
expect(onCard('Japanese', find.byTooltip('Public')), findsOneWidget);
expect(onCard('Kanji', find.byTooltip('Private')), findsOneWidget);
expect(
onCard('Kanji', find.byTooltip('Not private, not published')),
findsOneWidget,
);
});
testWidgets('the lock opens the Publish sheet', (tester) async {
testWidgets('the open lock says the instance can read it, and leads to '
'the Publish sheet or makes it private', (tester) async {
stored('japanese', 'Japanese');
gateway.canMakePrivate = true;
await showLibrary(tester);
await tester.tap(onCard('Japanese', find.byTooltip('Private')));
await tester.tap(
onCard('Japanese', find.byTooltip('Not private, not published')),
);
await tester.pumpAndSettle();
expect(find.text("“Japanese” isn't private"), findsOneWidget);
await tester.tap(find.text('Publish…'));
await tester.pumpAndSettle();
expect(find.text('Publish'), findsOneWidget);
expect(
find.text('Sign in to have a public profile and publish.'),
findsOneWidget,
);
await tester.tapAt(Offset.zero);
await tester.pumpAndSettle();
await tester.tap(
onCard('Japanese', find.byTooltip('Not private, not published')),
);
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(FilledButton, 'Make private'));
await tester.pump();
await tester.pump();
expect(gateway.privacyChanges, [('japanese', true)]);
});
testWidgets('the lock says it is private, offers no publishing, and makes '
'it non-private', (tester) async {
stored('diary', 'Diary', private: true);
await showLibrary(tester);
await tester.tap(onCard('Diary', find.byTooltip('Private')));
await tester.pumpAndSettle();
expect(find.text('“Diary” is private'), findsOneWidget);
expect(find.text('Publish…'), findsNothing);
await tester.tap(find.widgetWithText(FilledButton, 'Make non-private'));
await tester.pump();
await tester.pump();
expect(gateway.privacyChanges, [('diary', false)]);
});
testWidgets('the globe opens the Publish sheet', (tester) async {
stored('japanese', 'Japanese');
await showLibrary(
tester,
identified: true,
publishing: FakePublishGateway(
publications: {'japanese': publication()},
),
publishedStore: MemoryPublishedStore({'japanese'}),
);
await tester.tap(onCard('Japanese', find.byTooltip('Public')));
await tester.pumpAndSettle();
expect(find.text('Publish'), findsOneWidget);
expect(find.textContaining('halkyon.eu/@raisondetredev'), findsWidgets);
});
});
}