refactor to use mutations where helpful
This commit is contained in:
parent
641341ed90
commit
e9b009d1b9
8 changed files with 157 additions and 85 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import "package:collection/collection.dart";
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter_riverpod/experimental/mutation.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/controllers/client_state.dart";
|
||||
|
|
@ -115,6 +116,10 @@ class InvitesController extends Notifier<IList<RoomSummary>> {
|
|||
Future<void> declineAll() =>
|
||||
Future.wait(state.map((invite) => decline(invite.roomId)));
|
||||
|
||||
/// Accepting or declining an invite, keyed by its room ID
|
||||
static final respondMutation = Mutation<void>();
|
||||
static final declineAllMutation = Mutation<void>();
|
||||
|
||||
static final provider =
|
||||
NotifierProvider<InvitesController, IList<RoomSummary>>(
|
||||
InvitesController.new,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import "package:flutter_riverpod/experimental/mutation.dart";
|
||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/models/space_hierarchy.dart";
|
||||
|
|
@ -27,6 +28,12 @@ class SpaceHierarchyController(final String spaceId)
|
|||
);
|
||||
}
|
||||
|
||||
/// Adding rooms to a space, keyed by its ID
|
||||
static final addChildrenMutation = Mutation<void>();
|
||||
|
||||
/// Removing a room from a space, keyed by (space ID, room ID)
|
||||
static final removeChildMutation = Mutation<void>();
|
||||
|
||||
static final provider = AsyncNotifierProvider.family
|
||||
.autoDispose<SpaceHierarchyController, SpaceHierarchy, String>(
|
||||
SpaceHierarchyController.new,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import "package:nexus/controllers/unified_push.dart";
|
|||
import "package:nexus/main.dart";
|
||||
|
||||
extension LogOut on MutationTarget {
|
||||
Future<void> logOut(String accountId) async {
|
||||
static final mutation = Mutation<void>();
|
||||
|
||||
Future<void> logOut(String accountId) => mutation.run(this, (_) async {
|
||||
final accounts = await container.read(AccountsController.provider.future);
|
||||
final account = accounts.firstWhereOrNull(
|
||||
(account) => account.id == accountId,
|
||||
|
|
@ -49,5 +51,5 @@ extension LogOut on MutationTarget {
|
|||
await container
|
||||
.read(ActiveAccountController.provider.notifier)
|
||||
.switchTo(other.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,9 +43,17 @@ class const AccountSwitcher({final bool onlyWithOthers = false, super.key})
|
|||
.switchTo(accountId)
|
||||
.onError(showError);
|
||||
|
||||
final loggingOut = ref.watch(LogOut.mutation).isPending;
|
||||
|
||||
return AdaptiveMenuButton(
|
||||
tooltip: "Switch account",
|
||||
icon: active == null
|
||||
tooltip: loggingOut ? "Logging out..." : "Switch account",
|
||||
enabled: !loggingOut,
|
||||
icon: loggingOut
|
||||
? const SizedBox.square(
|
||||
dimension: 24,
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: active == null
|
||||
? const Icon(Icons.account_circle)
|
||||
: avatarOf(active, 28),
|
||||
itemBuilder: (_) => [
|
||||
|
|
|
|||
|
|
@ -131,9 +131,12 @@ class const AddSpaceRoomsSheet(
|
|||
: () {
|
||||
final container = ProviderScope.containerOf(context);
|
||||
final roomIds = chosen.value;
|
||||
context.withProgressSnackbar(
|
||||
"Adding ${roomIds.length == 1 ? nameOf(roomIds.single) : "${roomIds.length} rooms"} to $spaceName...",
|
||||
() => save(container, roomIds),
|
||||
SpaceHierarchyController.addChildrenMutation(spaceId).run(
|
||||
container,
|
||||
(_) => context.withProgressSnackbar(
|
||||
"Adding ${roomIds.length == 1 ? nameOf(roomIds.single) : "${roomIds.length} rooms"} to $spaceName...",
|
||||
() => save(container, roomIds),
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import "package:flutter_hooks/flutter_hooks.dart";
|
||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/helpers/extensions/show_adaptive_sheet.dart";
|
||||
|
|
@ -12,9 +11,7 @@ import "package:nexus/models/room_summary.dart";
|
|||
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||
import "package:nexus/widgets/room_summary_sheet.dart";
|
||||
|
||||
class const InvitesList({super.key})
|
||||
extends HookConsumerWidget
|
||||
implements NavPage {
|
||||
class const InvitesList({super.key}) extends ConsumerWidget implements NavPage {
|
||||
@override
|
||||
String get title => "Invites";
|
||||
|
||||
|
|
@ -32,7 +29,9 @@ class const InvitesList({super.key})
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final invites = ref.watch(InvitesController.provider);
|
||||
final decliningAll = useState(false);
|
||||
final decliningAll = ref
|
||||
.watch(InvitesController.declineAllMutation)
|
||||
.isPending;
|
||||
|
||||
Future<void> declineAll() async {
|
||||
final confirmed = await context.showAdaptiveSheet<bool>(
|
||||
|
|
@ -57,12 +56,12 @@ class const InvitesList({super.key})
|
|||
);
|
||||
if (confirmed != true) return;
|
||||
|
||||
decliningAll.value = true;
|
||||
await ref
|
||||
.read(InvitesController.provider.notifier)
|
||||
.declineAll()
|
||||
await InvitesController.declineAllMutation
|
||||
.run(
|
||||
ref,
|
||||
(_) => ref.read(InvitesController.provider.notifier).declineAll(),
|
||||
)
|
||||
.onError(showError);
|
||||
if (context.mounted) decliningAll.value = false;
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
|
|
@ -70,8 +69,8 @@ class const InvitesList({super.key})
|
|||
floatingActionButton: invites.isEmpty
|
||||
? null
|
||||
: FloatingActionButton.extended(
|
||||
onPressed: decliningAll.value ? null : declineAll,
|
||||
icon: decliningAll.value
|
||||
onPressed: decliningAll ? null : declineAll,
|
||||
icon: decliningAll
|
||||
? const SizedBox.square(
|
||||
dimension: 24,
|
||||
child: CircularProgressIndicator(),
|
||||
|
|
@ -96,9 +95,12 @@ class const InvitesList({super.key})
|
|||
}
|
||||
|
||||
class const InviteCard(final RoomSummary invite, {super.key})
|
||||
extends StatelessWidget {
|
||||
extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final responding = ref
|
||||
.watch(InvitesController.respondMutation(invite.roomId))
|
||||
.isPending;
|
||||
final name = invite.name ?? invite.canonicalAlias ?? invite.roomId;
|
||||
final inviterName =
|
||||
invite.inviterProfile?.displayName ?? invite.invitedBy?.localpart;
|
||||
|
|
@ -106,7 +108,15 @@ class const InviteCard(final RoomSummary invite, {super.key})
|
|||
return Card(
|
||||
clipBehavior: .antiAlias,
|
||||
child: ListTile(
|
||||
onTap: () => context.showAdaptiveSheet((_) => RoomSummarySheet(invite)),
|
||||
onTap: responding
|
||||
? null
|
||||
: () => context.showAdaptiveSheet((_) => RoomSummarySheet(invite)),
|
||||
trailing: responding
|
||||
? const SizedBox.square(
|
||||
dimension: 24,
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: null,
|
||||
leading: AvatarOrHash(
|
||||
invite.avatarUrl,
|
||||
name,
|
||||
|
|
|
|||
|
|
@ -65,9 +65,13 @@ class const RoomSummarySheet(
|
|||
final container = ProviderScope.containerOf(context);
|
||||
Navigator.of(context).pop();
|
||||
|
||||
container
|
||||
.read(InvitesController.provider.notifier)
|
||||
.decline(summary.roomId)
|
||||
InvitesController.respondMutation(summary.roomId)
|
||||
.run(
|
||||
container,
|
||||
(_) => container
|
||||
.read(InvitesController.provider.notifier)
|
||||
.decline(summary.roomId),
|
||||
)
|
||||
.onError(showError);
|
||||
},
|
||||
style: TextButton.styleFrom(foregroundColor: colorScheme.error),
|
||||
|
|
@ -77,18 +81,19 @@ class const RoomSummarySheet(
|
|||
onPressed: () {
|
||||
final container = ProviderScope.containerOf(context);
|
||||
|
||||
context
|
||||
.joinRoom(
|
||||
.new(roomIdOrAlias: summary.roomId, fromInvite: true),
|
||||
name: name,
|
||||
)
|
||||
.then((roomId) {
|
||||
InvitesController.respondMutation(summary.roomId)
|
||||
.run(container, (_) async {
|
||||
final roomId = await context.joinRoom(
|
||||
.new(roomIdOrAlias: summary.roomId, fromInvite: true),
|
||||
name: name,
|
||||
);
|
||||
if (roomId != null) {
|
||||
container
|
||||
.read(InvitesController.provider.notifier)
|
||||
.remove(summary.roomId);
|
||||
}
|
||||
});
|
||||
})
|
||||
.onError(showError);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text("Accept"),
|
||||
|
|
|
|||
|
|
@ -74,53 +74,74 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
|||
String roomId, {
|
||||
required String name,
|
||||
required String spaceName,
|
||||
}) => IconButton(
|
||||
tooltip: "Remove from space",
|
||||
icon: const Icon(Icons.remove_circle_outline),
|
||||
onPressed: () => context.showAdaptiveSheet(
|
||||
(context) => SheetLayout(
|
||||
title: const Text("Remove Room"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: const Text("Cancel"),
|
||||
}) => Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final mutation = SpaceHierarchyController.removeChildMutation((
|
||||
spaceId,
|
||||
roomId,
|
||||
));
|
||||
if (ref.watch(mutation).isPending) {
|
||||
return const Padding(
|
||||
padding: .all(12),
|
||||
child: SizedBox.square(
|
||||
dimension: 24,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final container = ProviderScope.containerOf(context);
|
||||
context.withProgressSnackbar(
|
||||
"Removing $name from $spaceName...",
|
||||
() async {
|
||||
await container
|
||||
.read(ClientController.provider.notifier)
|
||||
.setState(
|
||||
.new(
|
||||
roomId: spaceId,
|
||||
type: EventType.spaceChild.type,
|
||||
stateKey: roomId,
|
||||
content: const Content(),
|
||||
),
|
||||
);
|
||||
container.invalidate(
|
||||
SpaceHierarchyController.provider(spaceId),
|
||||
);
|
||||
}
|
||||
|
||||
return IconButton(
|
||||
tooltip: "Remove from space",
|
||||
icon: const Icon(Icons.remove_circle_outline),
|
||||
onPressed: () => context.showAdaptiveSheet(
|
||||
(context) => SheetLayout(
|
||||
title: const Text("Remove Room"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final container = ProviderScope.containerOf(context);
|
||||
mutation.run(
|
||||
container,
|
||||
(_) => context.withProgressSnackbar(
|
||||
"Removing $name from $spaceName...",
|
||||
() async {
|
||||
await container
|
||||
.read(ClientController.provider.notifier)
|
||||
.setState(
|
||||
.new(
|
||||
roomId: spaceId,
|
||||
type: EventType.spaceChild.type,
|
||||
stateKey: roomId,
|
||||
content: const Content(),
|
||||
),
|
||||
);
|
||||
container.invalidate(
|
||||
SpaceHierarchyController.provider(spaceId),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
child: const Text("Remove"),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
child: const Text("Remove"),
|
||||
),
|
||||
],
|
||||
children: [
|
||||
Text(
|
||||
"Are you sure you want to remove \"$name\" from \"$spaceName\"? Its members will stay in it.",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
children: [
|
||||
Text(
|
||||
"Are you sure you want to remove \"$name\" from \"$spaceName\"? Its members will stay in it.",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Widget level(
|
||||
|
|
@ -130,6 +151,9 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
|||
) => HookConsumer(
|
||||
builder: (context, ref, _) {
|
||||
final loadingMore = useState(false);
|
||||
final adding = ref
|
||||
.watch(SpaceHierarchyController.addChildrenMutation(spaceId))
|
||||
.isPending;
|
||||
final canManage = ref.watch(
|
||||
PowerLevelController.provider(
|
||||
.state(eventType: .spaceChild, roomId: spaceId),
|
||||
|
|
@ -317,16 +341,24 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
|||
),
|
||||
if (canManage)
|
||||
TextButton.icon(
|
||||
onPressed: () => context.showAdaptiveSheet(
|
||||
(_) => AddSpaceRoomsSheet(
|
||||
spaceId,
|
||||
spaceName: spaceName,
|
||||
existing: (byId[spaceId]?.children ?? const .empty())
|
||||
.map((child) => child.roomId)
|
||||
.toISet(),
|
||||
),
|
||||
),
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: adding
|
||||
? null
|
||||
: () => context.showAdaptiveSheet(
|
||||
(_) => AddSpaceRoomsSheet(
|
||||
spaceId,
|
||||
spaceName: spaceName,
|
||||
existing:
|
||||
(byId[spaceId]?.children ?? const .empty())
|
||||
.map((child) => child.roomId)
|
||||
.toISet(),
|
||||
),
|
||||
),
|
||||
icon: adding
|
||||
? const SizedBox.square(
|
||||
dimension: 18,
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: const Icon(Icons.add),
|
||||
label: const Text("Add rooms"),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in a new issue