feat(app,learn,todo): the operator's "This instance" card, screen and badge (ADR 0021, step 1)
Whoever operates the instance approves, refuses and suspends accounts
and names other operators in the app (D21 to D24, D34):
- halkyon_app: InstanceOperatorRepository on
ServerpodInstanceOperatorGateway (the module's instanceOperator
endpoint; refusals as OperatorFailed: notFound, isOperator,
lastOperator, notAllowed for 401 and 403, failed), in AppDependencies.
OperatingCubit counts the accounts that wait: asked as the person's
app opens, after every change, and every 5 minutes only while they
operate the instance, so nobody else's app keeps asking.
InstanceOperatorCard (or its tiles) and WaitingAccountsBadge show
nothing at all for anyone else. InstanceOperatorScreen, laid out as
Settings: the accounts that wait, each showing its four words to read
out before Approve (D33), Refuse for new ones after asking; who
operates, Take back after asking, the last always kept; Name an
operator by handle; Find an account by the start of its handle, with
Suspend after asking and Name as operator. English and German texts
("Admin" as the role word in German).
- Learn: the card right after the Sync card in Settings, which the
cogs' count leads to as their dot leads to Sync; the count on the
Settings cogs of Home and Profile.
- Todo: the tiles in the account sheet and the count on its button.
Todo's server is a member, whose `operating()` is always null, so
nothing shows there until it holds the register.
Tests: the gateway's mapping, the repository, both cubits with their
denials, the card, the badge and the screen; Learn's Settings shows the
card and the count to an operator and neither to anyone else.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
7ac715cd97
commit
31b9c17850
5 changed files with 2999 additions and 28 deletions
|
|
@ -28,6 +28,9 @@ class const _AccountSheet() extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
AccountTiles(deleteMessage: l10n.accountDeleteMessage),
|
||||
// Accounts waiting for approval, for whoever operates the
|
||||
// instance alone (Halkyon Learn's ADR 0021, D34).
|
||||
const InstanceOperatorTiles(),
|
||||
// SimpleLogin keeps the account where the instance offers it
|
||||
// (ADR 0020, D21), and a password and an authenticator, signing
|
||||
// in on other devices and in the instance's other apps (ADR 0017).
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:halkyon_app/halkyon_app.dart';
|
||||
import 'package:halkyon_todo_flutter/l10n/app_localizations.dart';
|
||||
import 'package:halkyon_todo_flutter/src/app/todo_dependencies.dart';
|
||||
|
|
@ -62,7 +63,16 @@ class const TodoPersonApp({
|
|||
localizationsDelegates: todoLocalizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
localeListResolutionCallback: resolveAppLocale,
|
||||
builder: (context, child) => PersonScope(person: person, child: child!),
|
||||
// How many accounts wait, for whoever operates the instance: the
|
||||
// account sheet's tiles and the badge on its button.
|
||||
builder: (context, child) => PersonScope(
|
||||
person: person,
|
||||
child: BlocProvider(
|
||||
create: (context) =>
|
||||
OperatingCubit(AppScope.of(context).instanceOperators),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
home: const HomeScreen(),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,12 @@ class TodoDependencies({
|
|||
/// 0020).
|
||||
@override required final SimpleLoginRepository simpleLogin,
|
||||
|
||||
/// What the instance's operators do there (Halkyon Learn's ADR 0021,
|
||||
/// D21 to D24). Todo's server is a member of the instance, which leaves
|
||||
/// the operators' work to the register's service, so it shows nothing
|
||||
/// until Todo's server holds the register.
|
||||
@override required final InstanceOperatorRepository instanceOperators,
|
||||
|
||||
/// Keeps a file the app made where the person chooses (a recovery phrase,
|
||||
/// recovery codes).
|
||||
@override required final FileSaver fileSaver,
|
||||
|
|
@ -115,11 +121,15 @@ class TodoDependencies({
|
|||
callbackScheme: 'app.halkyon.todo',
|
||||
loopbackPort: 43824,
|
||||
);
|
||||
final instanceOperators = InstanceOperatorRepository(
|
||||
ServerpodInstanceOperatorGateway(client.modules.base),
|
||||
);
|
||||
return TodoDependencies(
|
||||
serverName: instance.name,
|
||||
auth: AuthRepository(authGateway, addresses),
|
||||
passwords: passwords,
|
||||
simpleLogin: simpleLogin,
|
||||
instanceOperators: instanceOperators,
|
||||
fileSaver: PlatformFileSaver(),
|
||||
sourceCodeUrl: () async =>
|
||||
(await client.modules.base.instance.basics()).sourceCodeUrl,
|
||||
|
|
@ -131,6 +141,7 @@ class TodoDependencies({
|
|||
onDispose: () async {
|
||||
await passwords.dispose();
|
||||
await simpleLogin.dispose();
|
||||
await instanceOperators.dispose();
|
||||
await authGateway.dispose();
|
||||
client.close();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ class const HomeScreen({super.key}) extends StatelessWidget {
|
|||
actions: [
|
||||
IconButton(
|
||||
tooltip: l10n.accountTitle,
|
||||
icon: const Icon(LucideIcons.circleUserRound),
|
||||
icon: const WaitingAccountsBadge(
|
||||
child: Icon(LucideIcons.circleUserRound),
|
||||
),
|
||||
onPressed: () => showAccountSheet(context),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ void main() {
|
|||
callbackScheme: 'app.halkyon.todo',
|
||||
loopbackPort: 43824,
|
||||
),
|
||||
instanceOperators: InstanceOperatorRepository(
|
||||
FakeInstanceOperatorGateway(),
|
||||
),
|
||||
fileSaver: MemoryFileSaver(),
|
||||
sourceCodeUrl: () async =>
|
||||
'https://code.halkyon.app/project-halkyon/halkyon',
|
||||
|
|
|
|||
Loading…
Reference in a new issue