parent
f6aaf49164
commit
7007c9af89
8 changed files with 345 additions and 32 deletions
|
|
@ -7,9 +7,9 @@ import "package:nexus/models/content/power_levels.dart";
|
||||||
import "package:nexus/models/event.dart";
|
import "package:nexus/models/event.dart";
|
||||||
import "package:nexus/models/room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
|
|
||||||
class ViaController(final Room room) extends Notifier<String> {
|
class ViaController(final Room room) extends Notifier<ISet<String>> {
|
||||||
@override
|
@override
|
||||||
String build() {
|
ISet<String> build() {
|
||||||
final powerLevelUsers =
|
final powerLevelUsers =
|
||||||
switch (room.state[EventType.powerLevels.type]?[""]) {
|
switch (room.state[EventType.powerLevels.type]?[""]) {
|
||||||
final rowId? => switch (room.events[rowId]?.content) {
|
final rowId? => switch (room.events[rowId]?.content) {
|
||||||
|
|
@ -35,7 +35,7 @@ class ViaController(final Room room) extends Notifier<String> {
|
||||||
.nonNulls
|
.nonNulls
|
||||||
.toIList();
|
.toIList();
|
||||||
|
|
||||||
final servers = [?ref.watch(ClientStateController.provider)?.userId]
|
return [?ref.watch(ClientStateController.provider)?.userId]
|
||||||
.toIList()
|
.toIList()
|
||||||
.addAll(powerLevelUsers)
|
.addAll(powerLevelUsers)
|
||||||
.addAll(joinedMembers)
|
.addAll(joinedMembers)
|
||||||
|
|
@ -43,13 +43,16 @@ class ViaController(final Room room) extends Notifier<String> {
|
||||||
.whereNoDuplicates()
|
.whereNoDuplicates()
|
||||||
.take(5)
|
.take(5)
|
||||||
.toISet();
|
.toISet();
|
||||||
|
|
||||||
return servers.isEmpty
|
|
||||||
? ""
|
|
||||||
: "?${servers.map((server) => "via=$server").join("&")}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static final provider = NotifierProvider.family<ViaController, String, Room>(
|
static final provider =
|
||||||
ViaController.new,
|
NotifierProvider.family<ViaController, ISet<String>, Room>(
|
||||||
);
|
ViaController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ViaQuery on ISet<String> {
|
||||||
|
/// These servers as a matrix: URI query, or empty if there are none.
|
||||||
|
String get viaQuery =>
|
||||||
|
isEmpty ? "" : "?${map((server) => "via=$server").join("&")}";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ extension BuildEventOptions on Event {
|
||||||
);
|
);
|
||||||
if (room == null) return;
|
if (room == null) return;
|
||||||
|
|
||||||
final vias = ref.read(ViaController.provider(room));
|
final vias = ref.read(ViaController.provider(room)).viaQuery;
|
||||||
|
|
||||||
await Clipboard.setData(
|
await Clipboard.setData(
|
||||||
ClipboardData(
|
ClipboardData(
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import "package:nexus/models/content/reaction.dart";
|
||||||
import "package:nexus/models/content/encrypted.dart";
|
import "package:nexus/models/content/encrypted.dart";
|
||||||
import "package:nexus/models/content/redaction.dart";
|
import "package:nexus/models/content/redaction.dart";
|
||||||
import "package:nexus/models/content/server_acl.dart";
|
import "package:nexus/models/content/server_acl.dart";
|
||||||
|
import "package:nexus/models/content/space_child.dart";
|
||||||
import "package:nexus/models/content/topic.dart";
|
import "package:nexus/models/content/topic.dart";
|
||||||
import "package:nexus/models/content/sticker.dart";
|
import "package:nexus/models/content/sticker.dart";
|
||||||
import "package:nexus/models/content/history_visibility.dart";
|
import "package:nexus/models/content/history_visibility.dart";
|
||||||
|
|
@ -65,6 +66,7 @@ enum EventType(
|
||||||
tombstone("m.room.tombstone", Content.fromJson),
|
tombstone("m.room.tombstone", Content.fromJson),
|
||||||
reaction("m.reaction", ReactionContent.fromJson),
|
reaction("m.reaction", ReactionContent.fromJson),
|
||||||
pinnedEvents("m.room.pinned_events", PinnedEventsContent.fromJson),
|
pinnedEvents("m.room.pinned_events", PinnedEventsContent.fromJson),
|
||||||
|
spaceChild("m.space.child", SpaceChildContent.fromJson),
|
||||||
imagePack("m.room.image_pack", ImagePackContent.fromJson),
|
imagePack("m.room.image_pack", ImagePackContent.fromJson),
|
||||||
legacyImagePack("im.ponies.room_emotes", ImagePackContent.fromJson),
|
legacyImagePack("im.ponies.room_emotes", ImagePackContent.fromJson),
|
||||||
pollStart("org.matrix.msc3381.poll.start", PollStartContent.fromJson),
|
pollStart("org.matrix.msc3381.poll.start", PollStartContent.fromJson),
|
||||||
|
|
|
||||||
20
lib/models/content/space_child.dart
Normal file
20
lib/models/content/space_child.dart
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
|
import "package:nexus/models/content/content.dart";
|
||||||
|
|
||||||
|
part "space_child.freezed.dart";
|
||||||
|
part "space_child.g.dart";
|
||||||
|
|
||||||
|
@Freezed(toJson: false, fromJson: false)
|
||||||
|
@JsonSerializable(includeIfNull: false)
|
||||||
|
class const SpaceChildContent({
|
||||||
|
final IList<String> via = const .empty(),
|
||||||
|
final String? order,
|
||||||
|
final bool suggested = false,
|
||||||
|
}) extends Content with _$SpaceChildContent {
|
||||||
|
@override
|
||||||
|
Map<String, Object?> toJson() => _$SpaceChildContentToJson(this);
|
||||||
|
|
||||||
|
factory SpaceChildContent.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$SpaceChildContentFromJson(json);
|
||||||
|
}
|
||||||
178
lib/widgets/add_space_rooms_sheet.dart
Normal file
178
lib/widgets/add_space_rooms_sheet.dart
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
import "package:collection/collection.dart";
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
|
import "package:material_ui/material_ui.dart";
|
||||||
|
import "package:nexus/controllers/client.dart";
|
||||||
|
import "package:nexus/controllers/client_state.dart";
|
||||||
|
import "package:nexus/controllers/rooms.dart";
|
||||||
|
import "package:nexus/controllers/space_hierarchy.dart";
|
||||||
|
import "package:nexus/controllers/via.dart";
|
||||||
|
import "package:nexus/helpers/extensions/get_localpart.dart";
|
||||||
|
import "package:nexus/helpers/extensions/with_progress_snackbar.dart";
|
||||||
|
import "package:nexus/main.dart";
|
||||||
|
import "package:nexus/models/content/content.dart";
|
||||||
|
import "package:nexus/models/content/space_child.dart";
|
||||||
|
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||||
|
import "package:nexus/widgets/settings/search_chooser.dart";
|
||||||
|
import "package:nexus/widgets/sheet_layout.dart";
|
||||||
|
|
||||||
|
/// Chooses rooms to add to the space [spaceId], then adds them.
|
||||||
|
class const AddSpaceRoomsSheet(
|
||||||
|
final String spaceId, {
|
||||||
|
required final String spaceName,
|
||||||
|
|
||||||
|
/// The rooms already in the space, which aren't offered.
|
||||||
|
required final ISet<String> existing,
|
||||||
|
super.key,
|
||||||
|
}) extends HookConsumerWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final chosen = useState(const ISet<String>.empty());
|
||||||
|
final error = useState<String?>(null);
|
||||||
|
final resolving = useState(false);
|
||||||
|
final controller = useTextEditingController();
|
||||||
|
final rooms = ref.watch(
|
||||||
|
RoomsController.provider.select(
|
||||||
|
(rooms) => IMap.fromEntries(
|
||||||
|
rooms.values
|
||||||
|
.map((room) => room.metadata)
|
||||||
|
.nonNulls
|
||||||
|
.map(
|
||||||
|
(metadata) => MapEntry(metadata.id, (
|
||||||
|
name: metadata.name,
|
||||||
|
avatar: metadata.avatar,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
String nameOf(String id) => rooms[id]?.name ?? id;
|
||||||
|
|
||||||
|
final suggestions = rooms.entries
|
||||||
|
.where((entry) => entry.key != spaceId && !existing.contains(entry.key))
|
||||||
|
.sortedBy((entry) => entry.value.name?.toLowerCase() ?? "")
|
||||||
|
.map((entry) => entry.key)
|
||||||
|
.toIList();
|
||||||
|
|
||||||
|
void add(String id) {
|
||||||
|
if (id == spaceId) {
|
||||||
|
error.value = "A space can't contain itself";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (existing.contains(id)) {
|
||||||
|
error.value = "This room is already in the space";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!chosen.value.contains(id)) chosen.value = chosen.value.add(id);
|
||||||
|
controller.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addRoom(String text) async {
|
||||||
|
if (text.startsWith("!")) return add(text);
|
||||||
|
|
||||||
|
resolving.value = true;
|
||||||
|
try {
|
||||||
|
final id = await ref
|
||||||
|
.read(ClientController.provider.notifier)
|
||||||
|
.resolveAlias(text);
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
if (id == null) {
|
||||||
|
error.value = "No room has this alias";
|
||||||
|
} else {
|
||||||
|
add(id);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) resolving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs after the sheet closes, so it can't use [ref]
|
||||||
|
Future<void> save(ProviderContainer container, ISet<String> roomIds) async {
|
||||||
|
final client = container.read(ClientController.provider.notifier);
|
||||||
|
final joined = container.read(RoomsController.provider);
|
||||||
|
final ownServer = container
|
||||||
|
.read(ClientStateController.provider)
|
||||||
|
?.userId
|
||||||
|
?.serverName;
|
||||||
|
|
||||||
|
await Future.wait(
|
||||||
|
roomIds.map(
|
||||||
|
(roomId) => client.setState(
|
||||||
|
.new(
|
||||||
|
roomId: spaceId,
|
||||||
|
type: EventType.spaceChild.type,
|
||||||
|
stateKey: roomId,
|
||||||
|
content: SpaceChildContent(
|
||||||
|
via: switch (joined[roomId]) {
|
||||||
|
final room? => container.read(ViaController.provider(room)),
|
||||||
|
// Rooms the user isn't in can only be routed through their
|
||||||
|
// own server
|
||||||
|
null => ISet([?ownServer]),
|
||||||
|
}.toIList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
container.invalidate(SpaceHierarchyController.provider(spaceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return SheetLayout(
|
||||||
|
title: Text("Add Rooms"),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")),
|
||||||
|
TextButton(
|
||||||
|
onPressed: chosen.value.isEmpty
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text("Add"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
children: [
|
||||||
|
Text("Choose rooms to add to \"$spaceName\"."),
|
||||||
|
SearchChooser(
|
||||||
|
controller: controller,
|
||||||
|
chosen: chosen.value,
|
||||||
|
options: suggestions,
|
||||||
|
nameOf: nameOf,
|
||||||
|
avatarOf: (id) => AvatarOrHash(
|
||||||
|
rooms[id]?.avatar,
|
||||||
|
nameOf(id),
|
||||||
|
fallback: Icon(Icons.numbers),
|
||||||
|
),
|
||||||
|
subtitleOf: (id) => rooms[id]?.name == null ? null : id,
|
||||||
|
onAdded: add,
|
||||||
|
onRemoved: (id) => chosen.value = chosen.value.remove(id),
|
||||||
|
onSubmitted: (text, topMatch) {
|
||||||
|
if (text.startsWith(RegExp("[!#]"))) {
|
||||||
|
addRoom(text).onError(showError);
|
||||||
|
} else if (topMatch != null) {
|
||||||
|
add(topMatch);
|
||||||
|
} else {
|
||||||
|
error.value = "Enter a room ID or alias";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChanged: (_) => error.value = null,
|
||||||
|
label: "Add room",
|
||||||
|
hint: "Search, or enter a room ID or alias",
|
||||||
|
removeTooltip: "Remove room",
|
||||||
|
error: error.value,
|
||||||
|
loading: resolving.value,
|
||||||
|
autofocus: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,7 +39,9 @@ class const RoomOverlay(
|
||||||
? null
|
? null
|
||||||
: Text(room.metadata!.topic!, maxLines: 1),
|
: Text(room.metadata!.topic!, maxLines: 1),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final vias = ref.watch(ViaController.provider(room));
|
final vias = ref
|
||||||
|
.watch(ViaController.provider(room))
|
||||||
|
.viaQuery;
|
||||||
addTag(
|
addTag(
|
||||||
id: "[#$name](matrix:roomid/${room.metadata?.id.substring(1)}$vias)",
|
id: "[#$name](matrix:roomid/${room.metadata?.id.substring(1)}$vias)",
|
||||||
name:
|
name:
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ final class const RoomMenuButton(
|
||||||
if (room != null) ...[
|
if (room != null) ...[
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final vias = ref.read(ViaController.provider(room!));
|
final vias = ref.read(ViaController.provider(room!)).viaQuery;
|
||||||
|
|
||||||
await Clipboard.setData(
|
await Clipboard.setData(
|
||||||
.new(
|
.new(
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,20 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
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/controllers/client.dart";
|
||||||
|
import "package:nexus/controllers/power_level.dart";
|
||||||
import "package:nexus/controllers/rooms.dart";
|
import "package:nexus/controllers/rooms.dart";
|
||||||
import "package:nexus/controllers/space_hierarchy.dart";
|
import "package:nexus/controllers/space_hierarchy.dart";
|
||||||
import "package:nexus/helpers/extensions/better_when.dart";
|
import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
import "package:nexus/helpers/extensions/join_room.dart";
|
import "package:nexus/helpers/extensions/join_room.dart";
|
||||||
import "package:nexus/helpers/extensions/show_adaptive_sheet.dart";
|
import "package:nexus/helpers/extensions/show_adaptive_sheet.dart";
|
||||||
import "package:nexus/helpers/extensions/show_leave_room_sheet.dart";
|
import "package:nexus/helpers/extensions/show_leave_room_sheet.dart";
|
||||||
|
import "package:nexus/helpers/extensions/with_progress_snackbar.dart";
|
||||||
import "package:nexus/main.dart";
|
import "package:nexus/main.dart";
|
||||||
|
import "package:nexus/models/content/content.dart";
|
||||||
import "package:nexus/models/content/create.dart";
|
import "package:nexus/models/content/create.dart";
|
||||||
import "package:nexus/models/space_hierarchy.dart";
|
import "package:nexus/models/space_hierarchy.dart";
|
||||||
|
import "package:nexus/widgets/add_space_rooms_sheet.dart";
|
||||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||||
import "package:nexus/widgets/expandable_image.dart";
|
import "package:nexus/widgets/expandable_image.dart";
|
||||||
import "package:nexus/widgets/knock_sheet.dart";
|
import "package:nexus/widgets/knock_sheet.dart";
|
||||||
|
|
@ -64,6 +69,60 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||||
child: const Text("Join"),
|
child: const Text("Join"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Widget removeButton(
|
||||||
|
String spaceId,
|
||||||
|
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"),
|
||||||
|
),
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
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.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
Widget level(
|
Widget level(
|
||||||
String spaceId,
|
String spaceId,
|
||||||
SpaceHierarchy hierarchy,
|
SpaceHierarchy hierarchy,
|
||||||
|
|
@ -71,11 +130,20 @@ 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 canManage = ref.watch(
|
||||||
|
PowerLevelController.provider(
|
||||||
|
.state(eventType: .spaceChild, roomId: spaceId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final byId = IMap.fromValues(
|
final byId = IMap.fromValues(
|
||||||
keyMapper: (room) => room.summary.roomId,
|
keyMapper: (room) => room.summary.roomId,
|
||||||
values: hierarchy.rooms,
|
values: hierarchy.rooms,
|
||||||
);
|
);
|
||||||
|
final spaceName =
|
||||||
|
byId[spaceId]?.summary.name ??
|
||||||
|
byId[spaceId]?.summary.canonicalAlias ??
|
||||||
|
"Unnamed Space";
|
||||||
final children = (byId[spaceId]?.children ?? const .empty())
|
final children = (byId[spaceId]?.children ?? const .empty())
|
||||||
.map(
|
.map(
|
||||||
(child) => switch (byId[child.roomId]) {
|
(child) => switch (byId[child.roomId]) {
|
||||||
|
|
@ -126,7 +194,19 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||||
overflow: .ellipsis,
|
overflow: .ellipsis,
|
||||||
),
|
),
|
||||||
onTap: () => showSummary(room, child),
|
onTap: () => showSummary(room, child),
|
||||||
trailing: membershipButton(room, child, name: name),
|
trailing: Row(
|
||||||
|
mainAxisSize: .min,
|
||||||
|
children: [
|
||||||
|
membershipButton(room, child, name: name),
|
||||||
|
if (canManage)
|
||||||
|
removeButton(
|
||||||
|
spaceId,
|
||||||
|
summary.roomId,
|
||||||
|
name: name,
|
||||||
|
spaceName: spaceName,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -172,6 +252,13 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||||
child: const Icon(Icons.expand_more),
|
child: const Icon(Icons.expand_more),
|
||||||
),
|
),
|
||||||
membershipButton(room, child, name: name),
|
membershipButton(room, child, name: name),
|
||||||
|
if (canManage)
|
||||||
|
removeButton(
|
||||||
|
spaceId,
|
||||||
|
summary.roomId,
|
||||||
|
name: name,
|
||||||
|
spaceName: spaceName,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -190,6 +277,7 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.betterWhen(
|
.betterWhen(
|
||||||
|
skipLoadingOnRefresh: true,
|
||||||
loading: () =>
|
loading: () =>
|
||||||
const Loading(dimension: 24),
|
const Loading(dimension: 24),
|
||||||
data: (hierarchy) => level(
|
data: (hierarchy) => level(
|
||||||
|
|
@ -205,24 +293,43 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (hierarchy.nextBatch != null)
|
if (hierarchy.nextBatch != null || canManage)
|
||||||
Center(
|
Row(
|
||||||
child: TextButton(
|
mainAxisAlignment: .center,
|
||||||
onPressed: loadingMore.value
|
spacing: 8,
|
||||||
? null
|
children: [
|
||||||
: () async {
|
if (hierarchy.nextBatch != null)
|
||||||
loadingMore.value = true;
|
TextButton(
|
||||||
await ref
|
onPressed: loadingMore.value
|
||||||
.read(
|
? null
|
||||||
SpaceHierarchyController.provider(spaceId)
|
: () async {
|
||||||
.notifier,
|
loadingMore.value = true;
|
||||||
)
|
await ref
|
||||||
.loadMore()
|
.read(
|
||||||
.onError(showError);
|
SpaceHierarchyController.provider(spaceId)
|
||||||
if (context.mounted) loadingMore.value = false;
|
.notifier,
|
||||||
},
|
)
|
||||||
child: const Text("Load more"),
|
.loadMore()
|
||||||
),
|
.onError(showError);
|
||||||
|
if (context.mounted) loadingMore.value = false;
|
||||||
|
},
|
||||||
|
child: const Text("Load more"),
|
||||||
|
),
|
||||||
|
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),
|
||||||
|
label: const Text("Add rooms"),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -232,6 +339,7 @@ class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||||
return ref
|
return ref
|
||||||
.watch(SpaceHierarchyController.provider(spaceId))
|
.watch(SpaceHierarchyController.provider(spaceId))
|
||||||
.betterWhen(
|
.betterWhen(
|
||||||
|
skipLoadingOnRefresh: true,
|
||||||
data: (hierarchy) {
|
data: (hierarchy) {
|
||||||
final space = hierarchy.rooms
|
final space = hierarchy.rooms
|
||||||
.where((room) => room.summary.roomId == spaceId)
|
.where((room) => room.summary.roomId == spaceId)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue