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:collection/collection.dart";
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.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:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/controllers/client.dart";
|
import "package:nexus/controllers/client.dart";
|
||||||
import "package:nexus/controllers/client_state.dart";
|
import "package:nexus/controllers/client_state.dart";
|
||||||
|
|
@ -115,6 +116,10 @@ class InvitesController extends Notifier<IList<RoomSummary>> {
|
||||||
Future<void> declineAll() =>
|
Future<void> declineAll() =>
|
||||||
Future.wait(state.map((invite) => decline(invite.roomId)));
|
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 =
|
static final provider =
|
||||||
NotifierProvider<InvitesController, IList<RoomSummary>>(
|
NotifierProvider<InvitesController, IList<RoomSummary>>(
|
||||||
InvitesController.new,
|
InvitesController.new,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import "package:flutter_riverpod/experimental/mutation.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:nexus/controllers/client.dart";
|
import "package:nexus/controllers/client.dart";
|
||||||
import "package:nexus/models/space_hierarchy.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
|
static final provider = AsyncNotifierProvider.family
|
||||||
.autoDispose<SpaceHierarchyController, SpaceHierarchy, String>(
|
.autoDispose<SpaceHierarchyController, SpaceHierarchy, String>(
|
||||||
SpaceHierarchyController.new,
|
SpaceHierarchyController.new,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ import "package:nexus/controllers/unified_push.dart";
|
||||||
import "package:nexus/main.dart";
|
import "package:nexus/main.dart";
|
||||||
|
|
||||||
extension LogOut on MutationTarget {
|
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 accounts = await container.read(AccountsController.provider.future);
|
||||||
final account = accounts.firstWhereOrNull(
|
final account = accounts.firstWhereOrNull(
|
||||||
(account) => account.id == accountId,
|
(account) => account.id == accountId,
|
||||||
|
|
@ -49,5 +51,5 @@ extension LogOut on MutationTarget {
|
||||||
await container
|
await container
|
||||||
.read(ActiveAccountController.provider.notifier)
|
.read(ActiveAccountController.provider.notifier)
|
||||||
.switchTo(other.id);
|
.switchTo(other.id);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,17 @@ class const AccountSwitcher({final bool onlyWithOthers = false, super.key})
|
||||||
.switchTo(accountId)
|
.switchTo(accountId)
|
||||||
.onError(showError);
|
.onError(showError);
|
||||||
|
|
||||||
|
final loggingOut = ref.watch(LogOut.mutation).isPending;
|
||||||
|
|
||||||
return AdaptiveMenuButton(
|
return AdaptiveMenuButton(
|
||||||
tooltip: "Switch account",
|
tooltip: loggingOut ? "Logging out..." : "Switch account",
|
||||||
icon: active == null
|
enabled: !loggingOut,
|
||||||
|
icon: loggingOut
|
||||||
|
? const SizedBox.square(
|
||||||
|
dimension: 24,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
)
|
||||||
|
: active == null
|
||||||
? const Icon(Icons.account_circle)
|
? const Icon(Icons.account_circle)
|
||||||
: avatarOf(active, 28),
|
: avatarOf(active, 28),
|
||||||
itemBuilder: (_) => [
|
itemBuilder: (_) => [
|
||||||
|
|
|
||||||
|
|
@ -131,9 +131,12 @@ class const AddSpaceRoomsSheet(
|
||||||
: () {
|
: () {
|
||||||
final container = ProviderScope.containerOf(context);
|
final container = ProviderScope.containerOf(context);
|
||||||
final roomIds = chosen.value;
|
final roomIds = chosen.value;
|
||||||
context.withProgressSnackbar(
|
SpaceHierarchyController.addChildrenMutation(spaceId).run(
|
||||||
|
container,
|
||||||
|
(_) => context.withProgressSnackbar(
|
||||||
"Adding ${roomIds.length == 1 ? nameOf(roomIds.single) : "${roomIds.length} rooms"} to $spaceName...",
|
"Adding ${roomIds.length == 1 ? nameOf(roomIds.single) : "${roomIds.length} rooms"} to $spaceName...",
|
||||||
() => save(container, roomIds),
|
() => save(container, roomIds),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:material_ui/material_ui.dart";
|
import "package:material_ui/material_ui.dart";
|
||||||
import "package:nexus/helpers/extensions/show_adaptive_sheet.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/avatar_or_hash.dart";
|
||||||
import "package:nexus/widgets/room_summary_sheet.dart";
|
import "package:nexus/widgets/room_summary_sheet.dart";
|
||||||
|
|
||||||
class const InvitesList({super.key})
|
class const InvitesList({super.key}) extends ConsumerWidget implements NavPage {
|
||||||
extends HookConsumerWidget
|
|
||||||
implements NavPage {
|
|
||||||
@override
|
@override
|
||||||
String get title => "Invites";
|
String get title => "Invites";
|
||||||
|
|
||||||
|
|
@ -32,7 +29,9 @@ class const InvitesList({super.key})
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final invites = ref.watch(InvitesController.provider);
|
final invites = ref.watch(InvitesController.provider);
|
||||||
final decliningAll = useState(false);
|
final decliningAll = ref
|
||||||
|
.watch(InvitesController.declineAllMutation)
|
||||||
|
.isPending;
|
||||||
|
|
||||||
Future<void> declineAll() async {
|
Future<void> declineAll() async {
|
||||||
final confirmed = await context.showAdaptiveSheet<bool>(
|
final confirmed = await context.showAdaptiveSheet<bool>(
|
||||||
|
|
@ -57,12 +56,12 @@ class const InvitesList({super.key})
|
||||||
);
|
);
|
||||||
if (confirmed != true) return;
|
if (confirmed != true) return;
|
||||||
|
|
||||||
decliningAll.value = true;
|
await InvitesController.declineAllMutation
|
||||||
await ref
|
.run(
|
||||||
.read(InvitesController.provider.notifier)
|
ref,
|
||||||
.declineAll()
|
(_) => ref.read(InvitesController.provider.notifier).declineAll(),
|
||||||
|
)
|
||||||
.onError(showError);
|
.onError(showError);
|
||||||
if (context.mounted) decliningAll.value = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
@ -70,8 +69,8 @@ class const InvitesList({super.key})
|
||||||
floatingActionButton: invites.isEmpty
|
floatingActionButton: invites.isEmpty
|
||||||
? null
|
? null
|
||||||
: FloatingActionButton.extended(
|
: FloatingActionButton.extended(
|
||||||
onPressed: decliningAll.value ? null : declineAll,
|
onPressed: decliningAll ? null : declineAll,
|
||||||
icon: decliningAll.value
|
icon: decliningAll
|
||||||
? const SizedBox.square(
|
? const SizedBox.square(
|
||||||
dimension: 24,
|
dimension: 24,
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
|
|
@ -96,9 +95,12 @@ class const InvitesList({super.key})
|
||||||
}
|
}
|
||||||
|
|
||||||
class const InviteCard(final RoomSummary invite, {super.key})
|
class const InviteCard(final RoomSummary invite, {super.key})
|
||||||
extends StatelessWidget {
|
extends ConsumerWidget {
|
||||||
@override
|
@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 name = invite.name ?? invite.canonicalAlias ?? invite.roomId;
|
||||||
final inviterName =
|
final inviterName =
|
||||||
invite.inviterProfile?.displayName ?? invite.invitedBy?.localpart;
|
invite.inviterProfile?.displayName ?? invite.invitedBy?.localpart;
|
||||||
|
|
@ -106,7 +108,15 @@ class const InviteCard(final RoomSummary invite, {super.key})
|
||||||
return Card(
|
return Card(
|
||||||
clipBehavior: .antiAlias,
|
clipBehavior: .antiAlias,
|
||||||
child: ListTile(
|
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(
|
leading: AvatarOrHash(
|
||||||
invite.avatarUrl,
|
invite.avatarUrl,
|
||||||
name,
|
name,
|
||||||
|
|
|
||||||
|
|
@ -65,9 +65,13 @@ class const RoomSummarySheet(
|
||||||
final container = ProviderScope.containerOf(context);
|
final container = ProviderScope.containerOf(context);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
container
|
InvitesController.respondMutation(summary.roomId)
|
||||||
|
.run(
|
||||||
|
container,
|
||||||
|
(_) => container
|
||||||
.read(InvitesController.provider.notifier)
|
.read(InvitesController.provider.notifier)
|
||||||
.decline(summary.roomId)
|
.decline(summary.roomId),
|
||||||
|
)
|
||||||
.onError(showError);
|
.onError(showError);
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(foregroundColor: colorScheme.error),
|
style: TextButton.styleFrom(foregroundColor: colorScheme.error),
|
||||||
|
|
@ -77,18 +81,19 @@ class const RoomSummarySheet(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final container = ProviderScope.containerOf(context);
|
final container = ProviderScope.containerOf(context);
|
||||||
|
|
||||||
context
|
InvitesController.respondMutation(summary.roomId)
|
||||||
.joinRoom(
|
.run(container, (_) async {
|
||||||
|
final roomId = await context.joinRoom(
|
||||||
.new(roomIdOrAlias: summary.roomId, fromInvite: true),
|
.new(roomIdOrAlias: summary.roomId, fromInvite: true),
|
||||||
name: name,
|
name: name,
|
||||||
)
|
);
|
||||||
.then((roomId) {
|
|
||||||
if (roomId != null) {
|
if (roomId != null) {
|
||||||
container
|
container
|
||||||
.read(InvitesController.provider.notifier)
|
.read(InvitesController.provider.notifier)
|
||||||
.remove(summary.roomId);
|
.remove(summary.roomId);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.onError(showError);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: Text("Accept"),
|
child: Text("Accept"),
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,23 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||||
String roomId, {
|
String roomId, {
|
||||||
required String name,
|
required String name,
|
||||||
required String spaceName,
|
required String spaceName,
|
||||||
}) => IconButton(
|
}) => 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(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IconButton(
|
||||||
tooltip: "Remove from space",
|
tooltip: "Remove from space",
|
||||||
icon: const Icon(Icons.remove_circle_outline),
|
icon: const Icon(Icons.remove_circle_outline),
|
||||||
onPressed: () => context.showAdaptiveSheet(
|
onPressed: () => context.showAdaptiveSheet(
|
||||||
|
|
@ -88,7 +104,9 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final container = ProviderScope.containerOf(context);
|
final container = ProviderScope.containerOf(context);
|
||||||
context.withProgressSnackbar(
|
mutation.run(
|
||||||
|
container,
|
||||||
|
(_) => context.withProgressSnackbar(
|
||||||
"Removing $name from $spaceName...",
|
"Removing $name from $spaceName...",
|
||||||
() async {
|
() async {
|
||||||
await container
|
await container
|
||||||
|
|
@ -105,6 +123,7 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||||
SpaceHierarchyController.provider(spaceId),
|
SpaceHierarchyController.provider(spaceId),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
|
@ -122,6 +141,8 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Widget level(
|
Widget level(
|
||||||
String spaceId,
|
String spaceId,
|
||||||
|
|
@ -130,6 +151,9 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||||
) => HookConsumer(
|
) => HookConsumer(
|
||||||
builder: (context, ref, _) {
|
builder: (context, ref, _) {
|
||||||
final loadingMore = useState(false);
|
final loadingMore = useState(false);
|
||||||
|
final adding = ref
|
||||||
|
.watch(SpaceHierarchyController.addChildrenMutation(spaceId))
|
||||||
|
.isPending;
|
||||||
final canManage = ref.watch(
|
final canManage = ref.watch(
|
||||||
PowerLevelController.provider(
|
PowerLevelController.provider(
|
||||||
.state(eventType: .spaceChild, roomId: spaceId),
|
.state(eventType: .spaceChild, roomId: spaceId),
|
||||||
|
|
@ -317,16 +341,24 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||||
),
|
),
|
||||||
if (canManage)
|
if (canManage)
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () => context.showAdaptiveSheet(
|
onPressed: adding
|
||||||
|
? null
|
||||||
|
: () => context.showAdaptiveSheet(
|
||||||
(_) => AddSpaceRoomsSheet(
|
(_) => AddSpaceRoomsSheet(
|
||||||
spaceId,
|
spaceId,
|
||||||
spaceName: spaceName,
|
spaceName: spaceName,
|
||||||
existing: (byId[spaceId]?.children ?? const .empty())
|
existing:
|
||||||
|
(byId[spaceId]?.children ?? const .empty())
|
||||||
.map((child) => child.roomId)
|
.map((child) => child.roomId)
|
||||||
.toISet(),
|
.toISet(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.add),
|
icon: adding
|
||||||
|
? const SizedBox.square(
|
||||||
|
dimension: 18,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.add),
|
||||||
label: const Text("Add rooms"),
|
label: const Text("Add rooms"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue