parent
8d944831f5
commit
b71a7f0bd6
10 changed files with 519 additions and 98 deletions
|
|
@ -28,6 +28,7 @@ import "package:nexus/models/requests/get_event_context.dart";
|
|||
import "package:nexus/models/requests/get_mentions.dart";
|
||||
import "package:nexus/models/requests/get_related_events.dart";
|
||||
import "package:nexus/models/requests/get_room_state.dart";
|
||||
import "package:nexus/models/requests/get_space_hierarchy.dart";
|
||||
import "package:nexus/models/requests/join_room.dart";
|
||||
import "package:nexus/models/profile_response.dart";
|
||||
import "package:nexus/models/requests/oauth/exchange_token.dart";
|
||||
|
|
@ -47,6 +48,7 @@ import "package:nexus/models/requests/upload_media.dart";
|
|||
import "package:nexus/models/room.dart";
|
||||
import "package:nexus/models/room_metadata.dart";
|
||||
import "package:nexus/models/room_summary.dart";
|
||||
import "package:nexus/models/space_hierarchy.dart";
|
||||
import "package:nexus/models/spec_versions_response.dart";
|
||||
import "package:nexus/src/third_party/gomuks.g.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
|
|
@ -194,6 +196,11 @@ class ClientController extends AsyncNotifier<int> {
|
|||
Future<String> createRoom(CreateRoomRequest request) async =>
|
||||
(await _sendCommand("create_room", request.toJson()))["room_id"];
|
||||
|
||||
Future<SpaceHierarchy> getSpaceHierarchy(
|
||||
GetSpaceHierarchyRequest request,
|
||||
) async =>
|
||||
.fromJson(await _sendCommand("get_space_hierarchy", request.toJson()));
|
||||
|
||||
Future<RoomSummary> getRoomSummary(JoinRoomRequest request) async =>
|
||||
.fromJson(await _sendCommand("get_room_summary", request.toJson()));
|
||||
|
||||
|
|
|
|||
34
lib/controllers/space_hierarchy.dart
Normal file
34
lib/controllers/space_hierarchy.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/models/space_hierarchy.dart";
|
||||
|
||||
class SpaceHierarchyController(final String spaceId)
|
||||
extends AsyncNotifier<SpaceHierarchy> {
|
||||
@override
|
||||
Future<SpaceHierarchy> build() => ref
|
||||
.read(ClientController.provider.notifier)
|
||||
.getSpaceHierarchy(.new(roomId: spaceId, maxDepth: 1));
|
||||
|
||||
Future<void> loadMore() async {
|
||||
final current = await future;
|
||||
if (current.nextBatch == null) return;
|
||||
|
||||
final next = await ref
|
||||
.read(ClientController.provider.notifier)
|
||||
.getSpaceHierarchy(
|
||||
.new(roomId: spaceId, maxDepth: 1, from: current.nextBatch),
|
||||
);
|
||||
|
||||
state = AsyncData(
|
||||
current.copyWith(
|
||||
rooms: current.rooms.addAll(next.rooms),
|
||||
nextBatch: next.nextBatch,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static final provider = AsyncNotifierProvider.family
|
||||
.autoDispose<SpaceHierarchyController, SpaceHierarchy, String>(
|
||||
SpaceHierarchyController.new,
|
||||
);
|
||||
}
|
||||
39
lib/helpers/extensions/show_leave_room_sheet.dart
Normal file
39
lib/helpers/extensions/show_leave_room_sheet.dart
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/helpers/extensions/show_adaptive_sheet.dart";
|
||||
import "package:nexus/helpers/extensions/with_progress_snackbar.dart";
|
||||
import "package:nexus/widgets/sheet_layout.dart";
|
||||
|
||||
extension ShowLeaveRoomSheet on BuildContext {
|
||||
/// Asks the user to confirm leaving the room, then leaves it.
|
||||
Future<void> showLeaveRoomSheet(String roomId, {required String name}) =>
|
||||
showAdaptiveSheet(
|
||||
(context) => SheetLayout(
|
||||
title: const Text("Leave Room"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final client = ProviderScope.containerOf(
|
||||
context,
|
||||
).read(ClientController.provider.notifier);
|
||||
context.withProgressSnackbar(
|
||||
"Leaving $name...",
|
||||
() => client.leaveRoom(roomId),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
child: const Text("Leave"),
|
||||
),
|
||||
],
|
||||
children: [Text("Are you sure you want to leave \"$name\"?")],
|
||||
),
|
||||
);
|
||||
}
|
||||
19
lib/models/requests/get_space_hierarchy.dart
Normal file
19
lib/models/requests/get_space_hierarchy.dart
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
|
||||
part "get_space_hierarchy.freezed.dart";
|
||||
part "get_space_hierarchy.g.dart";
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable(includeIfNull: false)
|
||||
class const GetSpaceHierarchyRequest({
|
||||
required final String roomId,
|
||||
final String? from,
|
||||
final int limit = 50,
|
||||
final int? maxDepth,
|
||||
final bool? suggestedOnly,
|
||||
}) with _$GetSpaceHierarchyRequest {
|
||||
Map<String, Object?> toJson() => _$GetSpaceHierarchyRequestToJson(this);
|
||||
|
||||
factory GetSpaceHierarchyRequest.fromJson(Map<String, Object?> json) =>
|
||||
_$GetSpaceHierarchyRequestFromJson(json);
|
||||
}
|
||||
45
lib/models/space_hierarchy.dart
Normal file
45
lib/models/space_hierarchy.dart
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
import "package:nexus/models/room_summary.dart";
|
||||
|
||||
part "space_hierarchy.freezed.dart";
|
||||
part "space_hierarchy.g.dart";
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable(createToJson: false)
|
||||
class const SpaceHierarchy({
|
||||
final IList<SpaceHierarchyRoom> rooms = const .empty(),
|
||||
final String? nextBatch,
|
||||
}) with _$SpaceHierarchy {
|
||||
factory SpaceHierarchy.fromJson(Map<String, Object?> json) =>
|
||||
_$SpaceHierarchyFromJson(json);
|
||||
}
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable(createToJson: false)
|
||||
class const SpaceHierarchyRoom({
|
||||
@JsonKey(readValue: SpaceHierarchyRoom.readEntry)
|
||||
required final RoomSummary summary,
|
||||
@JsonKey(name: "children_state")
|
||||
final IList<SpaceHierarchyChild> children = const .empty(),
|
||||
}) with _$SpaceHierarchyRoom {
|
||||
static Object? readEntry(Map json, String _) => json;
|
||||
|
||||
factory SpaceHierarchyRoom.fromJson(Map<String, Object?> json) =>
|
||||
_$SpaceHierarchyRoomFromJson(json);
|
||||
}
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable(createToJson: false)
|
||||
class const SpaceHierarchyChild({
|
||||
@JsonKey(name: "state_key") required final String roomId,
|
||||
@JsonKey(readValue: SpaceHierarchyChild.readContent)
|
||||
final IList<String> via = const .empty(),
|
||||
@JsonKey(readValue: SpaceHierarchyChild.readContent)
|
||||
final bool suggested = false,
|
||||
}) with _$SpaceHierarchyChild {
|
||||
static Object? readContent(Map json, String key) => json["content"]?[key];
|
||||
|
||||
factory SpaceHierarchyChild.fromJson(Map<String, Object?> json) =>
|
||||
_$SpaceHierarchyChildFromJson(json);
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import "dart:math";
|
|||
|
||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:m3e_design/m3e_design.dart";
|
||||
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";
|
||||
|
|
@ -16,6 +17,7 @@ import "package:nexus/widgets/error_dialog.dart";
|
|||
import "package:nexus/widgets/pages/settings.dart";
|
||||
import "package:nexus/widgets/join_sheet.dart";
|
||||
import "package:nexus/widgets/room_menu_button.dart";
|
||||
import "package:nexus/widgets/space_hierarchy_sheet.dart";
|
||||
import "package:nexus/controllers/key.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/controllers/spaces.dart";
|
||||
|
|
@ -252,74 +254,97 @@ class const RoomsPane({super.key}) extends ConsumerWidget {
|
|||
),
|
||||
body: MaterialUiCompatibilityBridge(
|
||||
child: Builder(
|
||||
builder: (context) => old_mat.Theme(
|
||||
data: old_mat.Theme.of(context).copyWith(
|
||||
extensions: [
|
||||
NavigationRailM3ETheme(
|
||||
itemExpandedHeight: 48,
|
||||
iconLabelGap: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: NavigationRailM3E(
|
||||
expandedWidth: double.infinity,
|
||||
scrollable: true,
|
||||
background: Colors.transparent,
|
||||
type: .alwaysExpand,
|
||||
selectedIndex: max(
|
||||
0,
|
||||
selectedSpace.allChildRooms.indexWhere(
|
||||
(room) => room.metadata?.id == selectedRoomId,
|
||||
),
|
||||
builder: (context) {
|
||||
final theme = old_mat.Theme.of(context);
|
||||
return old_mat.Theme(
|
||||
data: theme.copyWith(
|
||||
extensions: [
|
||||
NavigationRailM3ETheme(
|
||||
itemExpandedHeight: 48,
|
||||
iconLabelGap: 16,
|
||||
sectionHeaderSpacingTop: 0,
|
||||
),
|
||||
// Otherwise the FAB label falls back to Roboto
|
||||
M3ETheme.defaults(theme.colorScheme).copyWith(
|
||||
typography: .defaultFor(
|
||||
theme.brightness,
|
||||
baseOverride: theme.textTheme,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
sections: [
|
||||
.new(
|
||||
header: selectedSpace.room == null
|
||||
? null
|
||||
: const DividerWidget(Text("Rooms")),
|
||||
destinations: selectedSpace.children.toDestinations(
|
||||
isDm: isDm,
|
||||
child: NavigationRailM3E(
|
||||
fab: selectedSpace.room == null
|
||||
? null
|
||||
: .new(
|
||||
icon: const Icon(Icons.explore),
|
||||
label: "Browse space",
|
||||
tooltip: "Browse and join rooms in this space",
|
||||
onPressed: () => context.showAdaptiveSheet(
|
||||
(_) => SpaceHierarchySheet(selectedSpace.id),
|
||||
),
|
||||
),
|
||||
expandedWidth: double.infinity,
|
||||
scrollable: true,
|
||||
background: Colors.transparent,
|
||||
type: .alwaysExpand,
|
||||
selectedIndex: max(
|
||||
0,
|
||||
selectedSpace.allChildRooms.indexWhere(
|
||||
(room) => room.metadata?.id == selectedRoomId,
|
||||
),
|
||||
),
|
||||
...selectedSpace.subSpaces.map(
|
||||
(subSpace) => NavigationRailM3ESection(
|
||||
header: DividerWidget(
|
||||
Row(
|
||||
mainAxisSize: .min,
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (subSpace.room.metadata?.avatar != null)
|
||||
AvatarOrHash(
|
||||
subSpace.room.metadata?.avatar,
|
||||
subSpace.room.metadata?.name ?? "Unnamed Room",
|
||||
dimension: 16,
|
||||
sections: [
|
||||
.new(
|
||||
header: selectedSpace.room == null
|
||||
? null
|
||||
: const DividerWidget(Text("Rooms")),
|
||||
destinations: selectedSpace.children.toDestinations(
|
||||
isDm: isDm,
|
||||
),
|
||||
),
|
||||
...selectedSpace.subSpaces.map(
|
||||
(subSpace) => NavigationRailM3ESection(
|
||||
header: DividerWidget(
|
||||
Row(
|
||||
mainAxisSize: .min,
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (subSpace.room.metadata?.avatar != null)
|
||||
AvatarOrHash(
|
||||
subSpace.room.metadata?.avatar,
|
||||
subSpace.room.metadata?.name ?? "Unnamed Room",
|
||||
dimension: 16,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
subSpace.room.metadata?.name ?? "Unnamed Space",
|
||||
maxLines: 1,
|
||||
overflow: .ellipsis,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
subSpace.room.metadata?.name ?? "Unnamed Space",
|
||||
maxLines: 1,
|
||||
overflow: .ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
destinations: subSpace.children.toDestinations(
|
||||
isDm: isDm,
|
||||
),
|
||||
),
|
||||
destinations: subSpace.children.toDestinations(isDm: isDm),
|
||||
),
|
||||
),
|
||||
],
|
||||
onDestinationSelected: (value) {
|
||||
ref
|
||||
.read(
|
||||
KeyController.provider(KeyController.roomKey).notifier,
|
||||
)
|
||||
.set(selectedSpace.allChildRooms[value].metadata?.id);
|
||||
if (Navigator.of(context).canPop()) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
onDestinationSelected: (value) {
|
||||
ref
|
||||
.read(
|
||||
KeyController.provider(KeyController.roomKey).notifier,
|
||||
)
|
||||
.set(selectedSpace.allChildRooms[value].metadata?.id);
|
||||
if (Navigator.of(context).canPop()) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/helpers/extensions/get_localpart.dart";
|
||||
import "package:nexus/helpers/extensions/with_progress_snackbar.dart";
|
||||
import "package:nexus/helpers/extensions/show_leave_room_sheet.dart";
|
||||
import "package:nexus/helpers/extensions/show_adaptive_sheet.dart";
|
||||
import "package:nexus/widgets/sheet_layout.dart";
|
||||
import "package:flutter/services.dart";
|
||||
|
|
@ -123,31 +123,9 @@ final class const RoomMenuButton(
|
|||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => context.showAdaptiveSheet(
|
||||
(context) => SheetLayout(
|
||||
title: Text("Leave Room"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: Text("Cancel"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.withProgressSnackbar(
|
||||
"Leaving room...",
|
||||
() => client.leaveRoom(room!.metadata!.id),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text("Leave"),
|
||||
),
|
||||
],
|
||||
children: [
|
||||
Text(
|
||||
"Are you sure you want to leave \"${room!.metadata?.name ?? "Unnamed Room"}\"?",
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => context.showLeaveRoomSheet(
|
||||
room!.metadata!.id,
|
||||
name: room!.metadata?.name ?? "Unnamed Room",
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.logout, color: danger),
|
||||
|
|
|
|||
263
lib/widgets/space_hierarchy_sheet.dart
Normal file
263
lib/widgets/space_hierarchy_sheet.dart
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
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/rooms.dart";
|
||||
import "package:nexus/controllers/space_hierarchy.dart";
|
||||
import "package:nexus/helpers/extensions/better_when.dart";
|
||||
import "package:nexus/helpers/extensions/join_room.dart";
|
||||
import "package:nexus/helpers/extensions/show_adaptive_sheet.dart";
|
||||
import "package:nexus/helpers/extensions/show_leave_room_sheet.dart";
|
||||
import "package:nexus/main.dart";
|
||||
import "package:nexus/models/content/create.dart";
|
||||
import "package:nexus/models/space_hierarchy.dart";
|
||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||
import "package:nexus/widgets/expandable_image.dart";
|
||||
import "package:nexus/widgets/loading.dart";
|
||||
import "package:nexus/widgets/room_summary_sheet.dart";
|
||||
import "package:nexus/widgets/sheet_layout.dart";
|
||||
|
||||
class const SpaceHierarchySheet(final String spaceId, {super.key})
|
||||
extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final joinedIds = ref.watch(
|
||||
RoomsController.provider.select((rooms) => rooms.keys.toISet()),
|
||||
);
|
||||
|
||||
void showSummary(SpaceHierarchyRoom room, SpaceHierarchyChild child) =>
|
||||
context.showAdaptiveSheet(
|
||||
(_) => RoomSummarySheet(
|
||||
room.summary,
|
||||
via: joinedIds.contains(room.summary.roomId) ? null : child.via,
|
||||
),
|
||||
);
|
||||
|
||||
Widget membershipButton(
|
||||
SpaceHierarchyRoom room,
|
||||
SpaceHierarchyChild child, {
|
||||
required String name,
|
||||
}) => joinedIds.contains(room.summary.roomId)
|
||||
? TextButton(
|
||||
onPressed: () =>
|
||||
context.showLeaveRoomSheet(room.summary.roomId, name: name),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
child: const Text("Leave"),
|
||||
)
|
||||
: TextButton(
|
||||
onPressed: () => context.joinRoom(
|
||||
.new(roomIdOrAlias: room.summary.roomId, via: child.via),
|
||||
name: name,
|
||||
),
|
||||
child: const Text("Join"),
|
||||
);
|
||||
|
||||
Widget level(
|
||||
String spaceId,
|
||||
SpaceHierarchy hierarchy,
|
||||
ISet<String> ancestors,
|
||||
) => HookConsumer(
|
||||
builder: (context, ref, _) {
|
||||
final loadingMore = useState(false);
|
||||
|
||||
final byId = IMap.fromValues(
|
||||
keyMapper: (room) => room.summary.roomId,
|
||||
values: hierarchy.rooms,
|
||||
);
|
||||
final children = (byId[spaceId]?.children ?? const .empty())
|
||||
.map(
|
||||
(child) => switch (byId[child.roomId]) {
|
||||
// Skip spaces that contain themselves somewhere up the tree.
|
||||
final room? when !ancestors.contains(child.roomId) => (
|
||||
child,
|
||||
room,
|
||||
),
|
||||
_ => null,
|
||||
},
|
||||
)
|
||||
.nonNulls
|
||||
// Suggested rooms first, then sorted by member count
|
||||
.sorted((a, b) {
|
||||
if (a.$1.suggested != b.$1.suggested) {
|
||||
return a.$1.suggested ? -1 : 1;
|
||||
}
|
||||
return (b.$2.summary.joinedMembers ?? 0).compareTo(
|
||||
a.$2.summary.joinedMembers ?? 0,
|
||||
);
|
||||
});
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: .stretch,
|
||||
children: [
|
||||
for (final (child, room) in children)
|
||||
if (room.summary.roomType != RoomType.space)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final summary = room.summary;
|
||||
final name =
|
||||
summary.name ??
|
||||
summary.canonicalAlias ??
|
||||
"Unnamed Room";
|
||||
|
||||
return ListTile(
|
||||
leading: AvatarOrHash(
|
||||
summary.avatarUrl,
|
||||
name,
|
||||
fallback: const Icon(Icons.numbers),
|
||||
),
|
||||
title: Text(name, maxLines: 1, overflow: .ellipsis),
|
||||
subtitle: summary.topic == null
|
||||
? null
|
||||
: Text(
|
||||
summary.topic!,
|
||||
maxLines: 2,
|
||||
overflow: .ellipsis,
|
||||
),
|
||||
onTap: () => showSummary(room, child),
|
||||
trailing: membershipButton(room, child, name: name),
|
||||
);
|
||||
},
|
||||
),
|
||||
for (final (child, room) in children)
|
||||
if (room.summary.roomType == RoomType.space)
|
||||
HookConsumer(
|
||||
builder: (context, ref, _) {
|
||||
final expanded = useState(false);
|
||||
final summary = room.summary;
|
||||
final name =
|
||||
summary.name ??
|
||||
summary.canonicalAlias ??
|
||||
"Unnamed Space";
|
||||
final joined = joinedIds.contains(summary.roomId);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: .stretch,
|
||||
children: [
|
||||
ListTile(
|
||||
onTap: joined
|
||||
? () => expanded.value = !expanded.value
|
||||
: () => showSummary(room, child),
|
||||
leading: AvatarOrHash(
|
||||
summary.avatarUrl,
|
||||
name,
|
||||
fallback: const Icon(Icons.workspaces),
|
||||
),
|
||||
title: Text(name, maxLines: 1, overflow: .ellipsis),
|
||||
subtitle: summary.topic == null
|
||||
? null
|
||||
: Text(
|
||||
summary.topic!,
|
||||
maxLines: 2,
|
||||
overflow: .ellipsis,
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
if (joined)
|
||||
AnimatedRotation(
|
||||
turns: expanded.value ? 0.5 : 0,
|
||||
duration: Durations.medium2,
|
||||
child: const Icon(Icons.expand_more),
|
||||
),
|
||||
membershipButton(room, child, name: name),
|
||||
],
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: Durations.medium2,
|
||||
curve: Curves.easeInOut,
|
||||
alignment: .topCenter,
|
||||
child: joined && expanded.value
|
||||
? Padding(
|
||||
// Indented so it's clear which rooms are in which space
|
||||
padding: const .only(left: 16),
|
||||
child: ref
|
||||
.watch(
|
||||
SpaceHierarchyController.provider(
|
||||
summary.roomId,
|
||||
),
|
||||
)
|
||||
.betterWhen(
|
||||
loading: () =>
|
||||
const Loading(dimension: 24),
|
||||
data: (hierarchy) => level(
|
||||
summary.roomId,
|
||||
hierarchy,
|
||||
ancestors.add(child.roomId),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox(width: .infinity),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
if (hierarchy.nextBatch != null)
|
||||
Center(
|
||||
child: TextButton(
|
||||
onPressed: loadingMore.value
|
||||
? null
|
||||
: () async {
|
||||
loadingMore.value = true;
|
||||
await ref
|
||||
.read(
|
||||
SpaceHierarchyController.provider(spaceId)
|
||||
.notifier,
|
||||
)
|
||||
.loadMore()
|
||||
.onError(showError);
|
||||
if (context.mounted) loadingMore.value = false;
|
||||
},
|
||||
child: const Text("Load more"),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return ref
|
||||
.watch(SpaceHierarchyController.provider(spaceId))
|
||||
.betterWhen(
|
||||
data: (hierarchy) {
|
||||
final space = hierarchy.rooms
|
||||
.where((room) => room.summary.roomId == spaceId)
|
||||
.firstOrNull
|
||||
?.summary;
|
||||
|
||||
return SheetLayout(
|
||||
title: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
if (space?.avatarUrl case final avatarUrl?)
|
||||
ExpandableImage(
|
||||
.new(mxc: avatarUrl),
|
||||
child: AvatarOrHash(avatarUrl, "", dimension: 32),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
space?.name ?? space?.canonicalAlias ?? "Unnamed Space",
|
||||
overflow: .ellipsis,
|
||||
maxLines: 3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: const Text("Close"),
|
||||
),
|
||||
],
|
||||
children: [
|
||||
level(spaceId, hierarchy, .new([spaceId])),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue