feat(instances): choose the instance, and keep accounts on several
A new install asks first which instance to keep the person's studysets on, as Mastodon's apps do: an address, checked to answer as an instance of Halkyon Learn, or the build's own (learn.halkyon.eu) in one tap. Nothing is made anywhere before that, not even an anonymous account. An install from before goes on with the build's instance without asking. As Moshidon does, the device keeps an account on each instance it was used with, each signed in with its own folder and session, one in use at a time. Settings › Switch account lists them: another one switches at once, anonymous accounts included; Add account chooses another instance; removing one not in use deletes what the device holds of it. Signing out ends only the account in use. The build's own instance keeps its data where installs kept it before (`people/`, Serverpod's own session key), so they keep everything; other instances get folders and keys of their own. What the device shares across instances (its settings, sounds, the Pomodoro timer's place in the system) is made once and outlives every switch. The F-Droid listing drops TetheredNet: the app is no longer tied to the project's instance. 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
89a3784df4
commit
7621afaa98
2 changed files with 1493 additions and 34 deletions
|
|
@ -0,0 +1,88 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/instance/instance.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/instance/instance_choice.dart';
|
||||
|
||||
import '../../support/memory_instance_store.dart';
|
||||
|
||||
void main() {
|
||||
final builtIn = Instance.parse('learn.halkyon.eu')!;
|
||||
final other = Instance.parse('learn.example.org')!;
|
||||
late MemoryInstanceStore store;
|
||||
late List<Instance> forgotten;
|
||||
|
||||
InstanceChoice choice() => InstanceChoice(
|
||||
store,
|
||||
builtIn: builtIn,
|
||||
forget: (instance) async {
|
||||
forgotten.add(instance);
|
||||
},
|
||||
);
|
||||
|
||||
setUp(() {
|
||||
store = MemoryInstanceStore();
|
||||
forgotten = [];
|
||||
});
|
||||
|
||||
test('a new install has no instance until one is chosen', () async {
|
||||
final instances = choice();
|
||||
await instances.load(usedBefore: false);
|
||||
|
||||
expect(instances.current, isNull);
|
||||
expect(instances.known, isEmpty);
|
||||
|
||||
await instances.use(other);
|
||||
expect(instances.current, other);
|
||||
expect(store.kept?.current, other);
|
||||
expect(store.kept?.known, [other]);
|
||||
});
|
||||
|
||||
test('an install from before there was a choice goes on with the '
|
||||
"build's instance", () async {
|
||||
final instances = choice();
|
||||
await instances.load(usedBefore: true);
|
||||
|
||||
expect(instances.current, builtIn);
|
||||
expect(store.kept?.current, builtIn);
|
||||
expect(store.kept?.known, [builtIn]);
|
||||
});
|
||||
|
||||
test('what was chosen is read back', () async {
|
||||
store = MemoryInstanceStore((current: other, known: [builtIn, other]));
|
||||
final instances = choice();
|
||||
await instances.load(usedBefore: true);
|
||||
|
||||
expect(instances.current, other);
|
||||
expect(instances.known, [builtIn, other]);
|
||||
});
|
||||
|
||||
test('switching keeps every account; removing one forgets what the '
|
||||
'device holds of it, never of the one in use', () async {
|
||||
final instances = choice();
|
||||
await instances.load(usedBefore: true);
|
||||
await instances.use(other);
|
||||
await instances.use(builtIn);
|
||||
expect(instances.known, [builtIn, other]);
|
||||
|
||||
await instances.remove(builtIn);
|
||||
expect(forgotten, isEmpty, reason: 'in use');
|
||||
|
||||
await instances.remove(other);
|
||||
expect(forgotten, [other]);
|
||||
expect(instances.known, [builtIn]);
|
||||
expect(store.kept?.current, builtIn);
|
||||
expect(store.kept?.known, [builtIn]);
|
||||
});
|
||||
|
||||
test('tells its listeners of every change', () async {
|
||||
final instances = choice();
|
||||
var changes = 0;
|
||||
instances.addListener(() => changes++);
|
||||
|
||||
await instances.load(usedBefore: false);
|
||||
await instances.use(other);
|
||||
await instances.use(builtIn);
|
||||
await instances.remove(other);
|
||||
|
||||
expect(changes, 4);
|
||||
});
|
||||
}
|
||||
51
halkyon_learn_flutter/test/core/instance/instance_test.dart
Normal file
51
halkyon_learn_flutter/test/core/instance/instance_test.dart
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/instance/instance.dart';
|
||||
|
||||
void main() {
|
||||
test('reads an address as people type it, HTTPS unless they say not', () {
|
||||
for (final address in [
|
||||
'learn.example.org',
|
||||
'https://learn.example.org',
|
||||
' HTTPS://Learn.Example.org/ ',
|
||||
]) {
|
||||
expect(
|
||||
Instance.parse(address)?.url,
|
||||
Uri.parse('https://learn.example.org/'),
|
||||
reason: address,
|
||||
);
|
||||
}
|
||||
expect(
|
||||
Instance.parse('http://localhost:8080')?.url,
|
||||
Uri.parse('http://localhost:8080/'),
|
||||
);
|
||||
expect(
|
||||
Instance.parse('example.org/learn?x=1#top')?.url,
|
||||
Uri.parse('https://example.org/learn/'),
|
||||
);
|
||||
});
|
||||
|
||||
test('refuses what is no address', () {
|
||||
for (final address in [
|
||||
'',
|
||||
' ',
|
||||
'learn example org',
|
||||
'ftp://example.org',
|
||||
'https://',
|
||||
'https://someone@example.org',
|
||||
]) {
|
||||
expect(Instance.parse(address), isNull, reason: address);
|
||||
}
|
||||
});
|
||||
|
||||
test('is named by its host, and its port if it has one', () {
|
||||
expect(Instance.parse('learn.halkyon.eu')!.name, 'learn.halkyon.eu');
|
||||
expect(Instance.parse('localhost:8080')!.name, 'localhost:8080');
|
||||
});
|
||||
|
||||
test('is the same instance however its address was written', () {
|
||||
expect(
|
||||
Instance.parse('learn.example.org'),
|
||||
Instance.parse('https://LEARN.example.org/'),
|
||||
);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue