perf(learn): local reads no longer wait on the instance

Every read of the person's content refreshes their keys, and a refresh
asked the instance whether the account has keys until it once answered
yes, which an account signing in with SimpleLogin alone never does. So
on a poor connection every read waited on a request: the library's
progress, a request per studyset in a row, took over five minutes, and
Home was slow too.

A device holding the keys already knows the account has them, so the
keys now open first and the instance is asked only while no other sign
says so (a device without the keys still asks at every refresh).

Whats-New: The library and Home open quickly again without a good connection.
Whats-New-De: Die Bibliothek und die Startseite öffnen sich ohne gute Verbindung wieder schnell.

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 22:39:10 +01:00
commit 1781553f1d
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
3 changed files with 44 additions and 137 deletions

View file

@ -65,6 +65,39 @@ void main() {
);
});
test('a device holding the keys never asks the instance whether the '
'account has them, which reads would otherwise wait on (offline: '
'minutes); one without asks at every refresh', () async {
var asked = 0;
Future<bool?> register() async {
asked++;
return false;
}
final laptop = ContentKeys(
synced,
FakeContentCipher(),
() async => alice,
registerHasKeys: register,
);
addTearDown(laptop.dispose);
await laptop.refresh();
await laptop.refresh();
expect(asked, 0);
expect(laptop.accountHasKeys, isTrue);
final keyless = ContentKeys(
FakeContentKeyGateway(),
FakeContentCipher(),
() async => null,
registerHasKeys: register,
);
addTearDown(keyless.dispose);
await keyless.refresh();
await keyless.refresh();
expect(asked, 2);
});
test("another account's device opens nothing of it", () async {
final laptop = await device(alice);
final keyId = await laptop.newStudysetKey('studyset-1');

View file

@ -1,92 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_content.dart';
import 'package:material_ui/material_ui.dart';
import '../../../support/fake_studyset_gateway.dart';
import '../../../support/memory_private_offer_store.dart';
import '../../../support/pump_app.dart';
import '../../../support/test_dependencies.dart';
/// The library's one offer to make the studysets from before private (ADR
/// 0019, D4).
void main() {
late FakeStudysetGateway gateway;
late MemoryPrivateOfferStore offer;
setUp(() {
gateway = FakeStudysetGateway()..canMakePrivate = true;
offer = MemoryPrivateOfferStore();
gateway
..receiveRemoteChange(
'japanese',
encodeStudysetPayload(StudysetContent.titled('Japanese')),
)
..receiveRemoteChange(
'kanji',
encodeStudysetPayload(StudysetContent.titled('Kanji')),
parentId: 'japanese',
)
..receiveRemoteChange(
'diary',
encodeStudysetPayload(StudysetContent.titled('Diary')),
isPrivate: true,
);
});
Future<void> showLibrary(WidgetTester tester) async {
tester.view.physicalSize = const Size(1200, 1600);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await pumpApp(
tester,
testDependencies(studysetGateway: gateway, privateOffer: offer),
location: RoutePaths.library,
);
await tester.pump();
await tester.pump();
}
testWidgets('makes every top-level studyset that is not private private '
'at once, then never asks again', (tester) async {
await showLibrary(tester);
expect(find.text('Make your studysets private?'), findsOneWidget);
await tester.tap(find.text('Make all private'));
await tester.pump();
await tester.pump();
expect(gateway.privacyChanges, [('japanese', true)]);
expect(offer.isAnswered, isTrue);
expect(find.text('Make your studysets private?'), findsNothing);
});
testWidgets('not now leaves them as they are, and asks no more', (
tester,
) async {
await showLibrary(tester);
await tester.tap(find.text('Not now'));
await tester.pump();
await tester.pump();
expect(gateway.privacyChanges, isEmpty);
expect(offer.isAnswered, isTrue);
expect(find.text('Make your studysets private?'), findsNothing);
});
testWidgets('is never shown once answered', (tester) async {
offer.isAnswered = true;
await showLibrary(tester);
expect(find.text('Make your studysets private?'), findsNothing);
});
testWidgets("is never shown on a device without the account's keys", (
tester,
) async {
gateway.canMakePrivate = false;
await showLibrary(tester);
expect(find.text('Make your studysets private?'), findsNothing);
});
}

View file

@ -1,13 +0,0 @@
import 'package:halkyon_learn_flutter/src/feature/studyset/data/private_offer_store.dart';
/// [PrivateOfferStore] in memory.
class MemoryPrivateOfferStore() implements PrivateOfferStore {
/// Whether the offer was answered.
bool isAnswered = false;
@override
Future<bool> answered() async => isAnswered;
@override
Future<void> answer() async => isAnswered = true;
}