parent
3a8d977f11
commit
641341ed90
5 changed files with 667 additions and 144 deletions
41
lib/widgets/account_scope.dart
Normal file
41
lib/widgets/account_scope.dart
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import "package:flutter_hooks/flutter_hooks.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/controllers/active_account.dart";
|
||||
import "package:nexus/controllers/notification_request.dart";
|
||||
|
||||
/// Runs [child] as one account, in a [ProviderScope] of its own that's
|
||||
/// replaced when switching accounts, so no state leaks between them.
|
||||
class const AccountScope({
|
||||
required final String initialAccountId,
|
||||
required final Widget child,
|
||||
final List<ProviderObserver>? observers,
|
||||
final Duration? Function(int retryCount, Object error)? retry,
|
||||
super.key,
|
||||
}) extends HookWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final active = useState<AccountSwitch>((
|
||||
accountId: initialAccountId,
|
||||
notificationRowId: null,
|
||||
));
|
||||
|
||||
return ProviderScope(
|
||||
key: ValueKey(active.value.accountId),
|
||||
retry: retry,
|
||||
observers: observers,
|
||||
overrides: [
|
||||
ActiveAccountController.provider.overrideWith(
|
||||
() => ActiveAccountController(
|
||||
active.value.accountId,
|
||||
(next) => active.value = next,
|
||||
),
|
||||
),
|
||||
NotificationRequestController.provider.overrideWithBuild(
|
||||
(_, _) => active.value.notificationRowId,
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
125
lib/widgets/account_switcher.dart
Normal file
125
lib/widgets/account_switcher.dart
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import "package:collection/collection.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/controllers/accounts.dart";
|
||||
import "package:nexus/controllers/active_account.dart";
|
||||
import "package:nexus/helpers/extensions/log_out.dart";
|
||||
import "package:nexus/main.dart";
|
||||
import "package:nexus/models/account.dart";
|
||||
import "package:nexus/widgets/adaptive_menu_button.dart";
|
||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||
|
||||
/// Switches between, adds and logs out of accounts. With [onlyWithOthers], it's
|
||||
/// hidden when there's only one account.
|
||||
class const AccountSwitcher({final bool onlyWithOthers = false, super.key})
|
||||
extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final danger = Theme.of(context).colorScheme.error;
|
||||
final accounts = ref.watch(AccountsController.provider).value;
|
||||
final activeId = ref.watch(ActiveAccountController.provider);
|
||||
if (accounts == null || (onlyWithOthers && accounts.length < 2)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
String nameOf(Account account) =>
|
||||
account.displayName ?? account.userId ?? "New account";
|
||||
|
||||
Widget avatarOf(Account account, double dimension) => AvatarOrHash(
|
||||
account.avatar,
|
||||
nameOf(account),
|
||||
dimension: dimension,
|
||||
fallback: account.userId == null
|
||||
? Icon(Icons.account_circle, size: dimension)
|
||||
: null,
|
||||
);
|
||||
|
||||
final active = accounts.firstWhereOrNull(
|
||||
(account) => account.id == activeId,
|
||||
);
|
||||
|
||||
void switchTo(String accountId) => ref
|
||||
.read(ActiveAccountController.provider.notifier)
|
||||
.switchTo(accountId)
|
||||
.onError(showError);
|
||||
|
||||
return AdaptiveMenuButton(
|
||||
tooltip: "Switch account",
|
||||
icon: active == null
|
||||
? const Icon(Icons.account_circle)
|
||||
: avatarOf(active, 28),
|
||||
itemBuilder: (_) => [
|
||||
for (final account in accounts)
|
||||
PopupMenuItem(
|
||||
onTap: () => switchTo(account.id),
|
||||
child: ListTile(
|
||||
selected: account.id == activeId,
|
||||
leading: avatarOf(account, 32),
|
||||
title: Text(nameOf(account)),
|
||||
subtitle: switch (account) {
|
||||
Account(displayName: _?, :final userId?) => Text(userId),
|
||||
_ => null,
|
||||
},
|
||||
trailing: account.userId == null
|
||||
? null
|
||||
// Inside the menu, so it can close it
|
||||
: Builder(
|
||||
builder: (menuContext) => IconButton(
|
||||
tooltip: "Log out",
|
||||
icon: Icon(Icons.logout, color: danger),
|
||||
onPressed: () async {
|
||||
Navigator.of(menuContext).pop();
|
||||
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text("Log out of ${nameOf(account)}?"),
|
||||
content: Text(
|
||||
account.id == activeId
|
||||
? "You'll be switched to another account, or returned to the login page if this is your only one."
|
||||
: "You'll stop receiving notifications for this account.",
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(dialogContext).pop(false),
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(dialogContext).pop(true),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: danger,
|
||||
),
|
||||
child: const Text("Log out"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed != true) return;
|
||||
|
||||
await ref.logOut(account.id).onError(showError);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
final id = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
ref
|
||||
.read(AccountsController.provider.notifier)
|
||||
.save(.new(id: id))
|
||||
.then((_) => switchTo(id))
|
||||
.onError(showError);
|
||||
},
|
||||
child: const ListTile(
|
||||
leading: Icon(Icons.person_add),
|
||||
title: Text("Add account"),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import "package:nexus/models/gomuks_exception.dart";
|
|||
import "package:nexus/models/homeserver.dart";
|
||||
import "package:nexus/models/oauth_auth_code_response.dart";
|
||||
import "package:nexus/widgets/pages/settings.dart";
|
||||
import "package:nexus/widgets/account_switcher.dart";
|
||||
import "package:nexus/widgets/appbar.dart";
|
||||
import "package:nexus/widgets/divider_text.dart";
|
||||
|
||||
|
|
@ -122,6 +123,7 @@ class const SelectServerPage({super.key}) extends HookConsumerWidget {
|
|||
return Scaffold(
|
||||
appBar: Appbar(
|
||||
actions: .new([
|
||||
const AccountSwitcher(onlyWithOthers: true),
|
||||
IconButton(
|
||||
onPressed: () =>
|
||||
showDialog(context: context, builder: (_) => SettingsPage()),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import "package:nexus/models/client_state.dart";
|
|||
import "package:nexus/helpers/extensions/show_adaptive_sheet.dart";
|
||||
import "package:nexus/widgets/loading.dart";
|
||||
import "package:nexus/widgets/pages/settings.dart";
|
||||
import "package:nexus/widgets/account_switcher.dart";
|
||||
import "package:nexus/widgets/appbar.dart";
|
||||
import "package:nexus/helpers/required_validator_helper.dart";
|
||||
import "package:nexus/widgets/reset_encryption_sheet.dart";
|
||||
|
|
@ -50,6 +51,7 @@ class const VerifyPage({super.key}) extends HookConsumerWidget {
|
|||
return Scaffold(
|
||||
appBar: Appbar(
|
||||
actions: .new([
|
||||
const AccountSwitcher(onlyWithOthers: true),
|
||||
IconButton(
|
||||
onPressed: () =>
|
||||
showDialog(context: context, builder: (_) => SettingsPage()),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import "package:navigation_rail_m3e/navigation_rail_m3e.dart";
|
|||
import "package:nexus/helpers/extensions/get_all_child_rooms.dart";
|
||||
import "package:nexus/helpers/extensions/room_destinations.dart";
|
||||
import "package:nexus/helpers/extensions/show_adaptive_sheet.dart";
|
||||
import "package:nexus/widgets/account_switcher.dart";
|
||||
import "package:nexus/widgets/adaptive_menu_button.dart";
|
||||
import "package:nexus/models/nav_page.dart";
|
||||
import "package:nexus/widgets/appbar.dart";
|
||||
|
|
@ -192,6 +193,7 @@ class const SpacesRail({super.key}) extends ConsumerWidget {
|
|||
],
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
const AccountSwitcher(),
|
||||
const IconButton(
|
||||
tooltip: "Explore other rooms (Not yet implemented)",
|
||||
onPressed: null,
|
||||
|
|
|
|||
Loading…
Reference in a new issue