working sidebar

This commit is contained in:
Henry Hiles 2026-01-27 14:14:04 +00:00
commit 85d96b80bc
No known key found for this signature in database
13 changed files with 491 additions and 436 deletions

View file

@ -5,6 +5,7 @@ import "package:ffi/ffi.dart";
import "package:flutter/foundation.dart";
import "package:nexus/controllers/client_state_controller.dart";
import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/controllers/space_edges_controller.dart";
import "package:nexus/controllers/sync_status_controller.dart";
import "package:nexus/controllers/top_level_spaces_controller.dart";
import "package:nexus/helpers/extensions/gomuks_buffer.dart";
@ -52,9 +53,16 @@ class ClientController extends AsyncNotifier<int> {
ref
.watch(roomProvider.notifier)
.update(syncData.rooms, syncData.leftRooms);
if (syncData.topLevelSpaces != null) {
ref
.watch(TopLevelSpacesController.provider.notifier)
.set(syncData.topLevelSpaces);
.set(syncData.topLevelSpaces!);
}
if (syncData.spaceEdges != null) {
ref
.watch(SpaceEdgesController.provider.notifier)
.set(syncData.spaceEdges!);
}
// ref
// .watch(SyncStatusController.provider.notifier)

View file

@ -2,24 +2,23 @@ import "package:collection/collection.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/key_controller.dart";
import "package:nexus/controllers/selected_space_controller.dart";
import "package:nexus/models/full_room.dart";
import "package:nexus/models/room.dart";
class SelectedRoomController extends AsyncNotifier<FullRoom?> {
class SelectedRoomController extends Notifier<Room?> {
@override
Future<FullRoom?> build() async {
final space = await ref.watch(SelectedSpaceController.provider.future);
Room? build() {
final space = ref.watch(SelectedSpaceController.provider);
final selectedRoomId = ref.watch(
KeyController.provider(KeyController.roomKey),
);
return space.children.firstWhereOrNull(
(room) => room.roomData.id == selectedRoomId,
(room) => room.metadata?.id == selectedRoomId,
) ??
space.children.firstOrNull;
}
static final provider =
AsyncNotifierProvider<SelectedRoomController, FullRoom?>(
static final provider = NotifierProvider<SelectedRoomController, Room?>(
SelectedRoomController.new,
);
}

View file

@ -4,12 +4,10 @@ import "package:nexus/controllers/key_controller.dart";
import "package:nexus/controllers/spaces_controller.dart";
import "package:nexus/models/space.dart";
class SelectedSpaceController extends AsyncNotifier<Space> {
class SelectedSpaceController extends Notifier<Space> {
@override
Future<Space> build() async {
final spaces = await ref.watch(
SpacesController.provider.selectAsync((data) => data),
);
Space build() {
final spaces = ref.watch(SpacesController.provider);
final selectedSpaceId = ref.watch(
KeyController.provider(KeyController.spaceKey),
);
@ -18,7 +16,7 @@ class SelectedSpaceController extends AsyncNotifier<Space> {
spaces.first;
}
static final provider = AsyncNotifierProvider<SelectedSpaceController, Space>(
static final provider = NotifierProvider<SelectedSpaceController, Space>(
SelectedSpaceController.new,
);
}

View file

@ -0,0 +1,15 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/models/space_edge.dart";
class SpaceEdgesController extends Notifier<IMap<String, IList<SpaceEdge>>> {
@override
IMap<String, IList<SpaceEdge>> build() => const IMap.empty();
void set(IMap<String, IList<SpaceEdge>> newEdges) => state = newEdges;
static final provider =
NotifierProvider<SpaceEdgesController, IMap<String, IList<SpaceEdge>>>(
SpaceEdgesController.new,
);
}

View file

@ -1,55 +1,94 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_controller.dart";
import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/controllers/top_level_spaces_controller.dart";
import "package:nexus/controllers/space_edges_controller.dart";
import "package:nexus/models/space.dart";
import "package:nexus/models/room.dart";
import "package:nexus/models/space_edge.dart";
class SpacesController extends AsyncNotifier<IList<Space>> {
class SpacesController extends Notifier<IList<Space>> {
@override
Future<IList<Space>> build() async {
final topLevelSpaceIds = ref.watch(TopLevelSpacesController.provider);
IList<Space> build() {
final rooms = ref.watch(RoomsController.provider);
final topLevelSpaceIds = ref.watch(TopLevelSpacesController.provider);
final spaceEdges = ref.watch(SpaceEdgesController.provider);
final topLevelSpaces = topLevelSpaceIds
.map((id) => rooms[id])
.nonNulls
.toIList();
ISet<String> collectChildIds(String spaceId) {
ISet<String> result = ISet<String>();
void walk(String currentId) {
final children = spaceEdges[currentId] ?? IList<SpaceEdge>();
for (final edge in children) {
final childId = edge.childId;
if (!result.contains(childId)) {
result = result.add(childId);
walk(childId);
}
}
}
walk(spaceId);
return result;
}
final spaceIdToChildren = IMap.fromEntries(
topLevelSpaceIds.map((spaceId) {
final children = collectChildIds(
spaceId,
).map((id) => rooms[id]).nonNulls.toIList();
return MapEntry(spaceId, children);
}),
);
final allNestedRoomIds = spaceIdToChildren.values
.expand((l) => l)
.map((r) => rooms.entries.firstWhere((e) => e.value == r).key)
.toISet();
final dmRooms = rooms.values
.where((room) => room.metadata?.dmUserId != null)
.toIList();
final topLevelRooms = rooms.values
.where((room) => room.metadata?.dmUserId == null)
final homeRooms = rooms.entries
.where(
(room) => spaceRooms.every(
(space) =>
space.spaceChildren.every((child) => child.roomId != room.id),
),
(e) =>
e.value.metadata?.dmUserId == null &&
!allNestedRoomIds.contains(e.key) &&
!topLevelSpaceIds.contains(e.key),
)
.map((e) => e.value)
.toIList();
// 4 Combine all into a single IList
return IList([
Space(
id: "home",
title: "Home",
children: topLevelRooms,
icon: Icons.home,
),
final topLevelSpacesList = topLevelSpaceIds
.map((id) {
final room = rooms[id];
if (room == null) return null;
final children = spaceIdToChildren[id] ?? IList<Room>();
return Space(
id: id,
title: room.metadata?.name ?? "Unnamed Room",
room: room,
children: children,
);
})
.nonNulls
.toIList();
return <Space>[
Space(id: "home", title: "Home", icon: Icons.home, children: homeRooms),
Space(
id: "dms",
title: "Direct Messages",
icon: Icons.people,
children: dmRooms,
icon: Icons.person,
),
...topLevelSpaces,
]);
...topLevelSpacesList,
].toIList();
}
static final provider = AsyncNotifierProvider<SpacesController, IList<Space>>(
static final provider = NotifierProvider<SpacesController, IList<Space>>(
SpacesController.new,
);
}

View file

@ -1,87 +1,87 @@
import "package:collection/collection.dart";
import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:matrix/matrix.dart";
import "package:nexus/controllers/client_controller.dart";
import "package:nexus/controllers/key_controller.dart";
import "package:nexus/controllers/spaces_controller.dart";
extension JoinRoomWithSnackbars on Client {
extension JoinRoomWithSnackbars on ClientController {
Future<void> joinRoomWithSnackBars(
BuildContext context,
String roomAlias,
WidgetRef ref,
) async {
final parsed = roomAlias.parseIdentifierIntoParts();
final alias = parsed?.primaryIdentifier ?? roomAlias;
// final parsed = roomAlias.parseIdentifierIntoParts();
// final alias = parsed?.primaryIdentifier ?? roomAlias;
final scaffoldMessenger = ScaffoldMessenger.of(context);
// final scaffoldMessenger = ScaffoldMessenger.of(context);
final snackbar = scaffoldMessenger.showSnackBar(
SnackBar(
content: Text("Joining room $alias."),
duration: Duration(days: 999),
),
);
// final snackbar = scaffoldMessenger.showSnackBar(
// SnackBar(
// content: Text("Joining room $alias."),
// duration: Duration(days: 999),
// ),
// );
try {
final id = await joinRoom(alias, via: parsed?.via.toList());
// try {
// final id = await joinRoom(alias, via: parsed?.via.toList());
snackbar.close();
// snackbar.close();
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text("Room $alias successfully joined."),
action: SnackBarAction(
label: "Open",
onPressed: () async {
final spaces = await ref.refresh(
SpacesController.provider.future,
);
final space = spaces.firstWhereOrNull((space) => space.id == id);
// scaffoldMessenger.showSnackBar(
// SnackBar(
// content: Text("Room $alias successfully joined."),
// action: SnackBarAction(
// label: "Open",
// onPressed: () async {
// final spaces = await ref.refresh(
// SpacesController.provider.future,
// );
// final space = spaces.firstWhereOrNull((space) => space.id == id);
await ref
.watch(
KeyController.provider(KeyController.spaceKey).notifier,
)
.set(
space?.id ??
spaces
.firstWhere(
(space) =>
space.children.firstWhereOrNull(
(child) => child.roomData.id == id,
) !=
null,
)
.id,
);
// await ref
// .watch(
// KeyController.provider(KeyController.spaceKey).notifier,
// )
// .set(
// space?.id ??
// spaces
// .firstWhere(
// (space) =>
// space.children.firstWhereOrNull(
// (child) => child.roomData.id == id,
// ) !=
// null,
// )
// .id,
// );
if (space == null) {
await ref
.watch(
KeyController.provider(KeyController.roomKey).notifier,
)
.set(id);
}
},
),
),
);
} catch (error) {
snackbar.close();
if (context.mounted) {
scaffoldMessenger.showSnackBar(
SnackBar(
backgroundColor: Theme.of(context).colorScheme.errorContainer,
content: Text(
error.toString(),
style: TextStyle(
color: Theme.of(context).colorScheme.onErrorContainer,
),
),
),
);
}
}
// if (space == null) {
// await ref
// .watch(
// KeyController.provider(KeyController.roomKey).notifier,
// )
// .set(id);
// }
// },
// ),
// ),
// );
// } catch (error) {
// snackbar.close();
// if (context.mounted) {
// scaffoldMessenger.showSnackBar(
// SnackBar(
// backgroundColor: Theme.of(context).colorScheme.errorContainer,
// content: Text(
// error.toString(),
// style: TextStyle(
// color: Theme.of(context).colorScheme.onErrorContainer,
// ),
// ),
// ),
// );
// }
// }
}
}

View file

@ -1,3 +1,4 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/widgets.dart";
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/room.dart";
@ -10,6 +11,6 @@ abstract class Space with _$Space {
required String title,
IconData? icon,
Room? room,
required List<Room> children,
required IList<Room> children,
}) = _Space;
}

View file

@ -0,0 +1,14 @@
import "package:freezed_annotation/freezed_annotation.dart";
part "space_edge.freezed.dart";
part "space_edge.g.dart";
@freezed
abstract class SpaceEdge with _$SpaceEdge {
const factory SpaceEdge({
required String childId,
@Default(false) bool suggested,
}) = _SpaceEdge;
factory SpaceEdge.fromJson(Map<String, Object?> json) =>
_$SpaceEdgeFromJson(json);
}

View file

@ -1,6 +1,7 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/room.dart";
import "package:nexus/models/space_edge.dart";
part "sync_data.freezed.dart";
part "sync_data.g.dart";
@ -12,8 +13,8 @@ abstract class SyncData with _$SyncData {
@Default(IMap.empty()) IMap<String, Room> rooms,
@Default(ISet.empty()) ISet<String> leftRooms,
// required IList<InvitedRoom> invitedRooms,
// required IList<SpaceEdge> spaceEdges,
@Default(IList.empty()) IList<String> topLevelSpaces,
IMap<String, IList<SpaceEdge>>? spaceEdges,
IList<String>? topLevelSpaces,
}) = _SyncData;
factory SyncData.fromJson(Map<String, Object?> json) =>

View file

@ -1,6 +1,6 @@
import "package:flutter/material.dart";
import "package:nexus/widgets/chat_page/sidebar.dart";
// import "package:nexus/widgets/chat_page/room_chat.dart";
// import "package:nexus/widgets/chat_page/sidebar.dart";
class ChatPage extends StatelessWidget {
const ChatPage({super.key});
@ -10,23 +10,23 @@ class ChatPage extends StatelessWidget {
builder: (context, constraints) {
final isDesktop = constraints.maxWidth > 650;
final showMembersByDefault = constraints.maxWidth > 1000;
return Placeholder();
// return Scaffold(
// body: Builder(
// builder: (context) => Row(
// children: [
// if (isDesktop) Sidebar(),
return Scaffold(
body: Builder(
builder: (context) => Row(
children: [
if (isDesktop) Sidebar(),
// Expanded(
// child: RoomChat(
// isDesktop: isDesktop,
// showMembersByDefault: showMembersByDefault,
// ),
// ),
// ],
// ),
// ),
// drawer: isDesktop ? null : Sidebar(),
// );
],
),
),
drawer: isDesktop ? null : Sidebar(),
);
},
);
}

View file

@ -1,4 +1,5 @@
import "dart:io";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:window_manager/window_manager.dart";
@ -7,7 +8,7 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget {
final Widget? title;
final Color? backgroundColor;
final double? scrolledUnderElevation;
final List<Widget> actions;
final IList<Widget> actions;
const Appbar({
super.key,
@ -15,7 +16,7 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget {
this.backgroundColor,
this.scrolledUnderElevation,
this.leading,
this.actions = const [],
this.actions = const IList.empty(),
});
@override

View file

@ -1,122 +1,124 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:matrix/matrix.dart";
import "package:nexus/helpers/extensions/room_to_children.dart";
import "package:nexus/models/room.dart";
import "package:nexus/widgets/form_text_input.dart";
class RoomMenu extends StatelessWidget {
final Room room;
const RoomMenu(this.room, {super.key});
final IList<Room> children;
const RoomMenu(this.room, {this.children = const IList.empty(), super.key});
@override
Widget build(BuildContext context) {
final danger = Theme.of(context).colorScheme.error;
void markRead(String roomId) async {
for (final child in await room.getAllChildren()) {
await child.roomData.setReadMarker(
child.roomData.lastEvent?.eventId,
mRead: child.roomData.lastEvent?.eventId,
);
// TODO: Set parent read
for (final child in children) {
// await child.setReadMarker( TODO: Set children read
// child.roomData.lastEvent?.eventId,
// mRead: child.roomData.lastEvent?.eventId,
// );
}
}
return PopupMenuButton(
itemBuilder: (_) => [
PopupMenuItem(
onTap: () async {
final link = await room.matrixToInviteLink();
await Clipboard.setData(ClipboardData(text: link.toString()));
},
child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")),
),
PopupMenuItem(
onTap: () => markRead(room.id),
child: ListTile(
leading: Icon(Icons.check),
title: Text("Mark as Read"),
),
),
PopupMenuItem(
onTap: () => showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("Leave Room"),
content: Text(
"Are you sure you want to leave \"${room.getLocalizedDisplayname()}\"?",
),
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: Text("Cancel"),
),
TextButton(
onPressed: () async {
Navigator.of(context).pop();
final snackbar = ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text("Leaving room...")));
await room.leave();
snackbar.close();
},
child: Text("Leave"),
),
],
),
),
child: ListTile(
leading: Icon(Icons.logout, color: danger),
title: Text("Leave", style: TextStyle(color: danger)),
),
),
PopupMenuItem(
onTap: () => showDialog(
context: context,
builder: (context) => HookBuilder(
builder: (_) {
final reasonController = useTextEditingController();
return AlertDialog(
title: Text("Report"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Report this room to your server administrators, who can take action like banning this room.",
),
// PopupMenuItem(
// onTap: () async {
// final link = await room.matrixToInviteLink();
// await Clipboard.setData(ClipboardData(text: link.toString()));
// },
// child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")),
// ),
// PopupMenuItem(
// onTap: () => markRead(room.id),
// child: ListTile(
// leading: Icon(Icons.check),
// title: Text("Mark as Read"),
// ),
// ),
// PopupMenuItem(
// onTap: () => showDialog(
// context: context,
// builder: (context) => AlertDialog(
// title: Text("Leave Room"),
// content: Text(
// "Are you sure you want to leave \"${room.getLocalizedDisplayname()}\"?",
// ),
// actions: [
// TextButton(
// onPressed: Navigator.of(context).pop,
// child: Text("Cancel"),
// ),
// TextButton(
// onPressed: () async {
// Navigator.of(context).pop();
// final snackbar = ScaffoldMessenger.of(
// context,
// ).showSnackBar(SnackBar(content: Text("Leaving room...")));
// await room.leave();
// snackbar.close();
// },
// child: Text("Leave"),
// ),
// ],
// ),
// ),
// child: ListTile(
// leading: Icon(Icons.logout, color: danger),
// title: Text("Leave", style: TextStyle(color: danger)),
// ),
// ),
// PopupMenuItem(
// onTap: () => showDialog(
// context: context,
// builder: (context) => HookBuilder(
// builder: (_) {
// final reasonController = useTextEditingController();
// return AlertDialog(
// title: Text("Report"),
// content: Column(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// "Report this room to your server administrators, who can take action like banning this room.",
// ),
SizedBox(height: 12),
FormTextInput(
required: false,
capitalize: true,
controller: reasonController,
title: "Reason for report (optional)",
),
],
),
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: Text("Cancel"),
),
TextButton(
onPressed: () {
room.client.reportRoom(room.id, reasonController.text);
Navigator.of(context).pop();
},
child: Text("Report"),
),
],
);
},
),
),
child: ListTile(
leading: Icon(Icons.report, color: danger),
title: Text("Report", style: TextStyle(color: danger)),
),
),
// SizedBox(height: 12),
// FormTextInput(
// required: false,
// capitalize: true,
// controller: reasonController,
// title: "Reason for report (optional)",
// ),
// ],
// ),
// actions: [
// TextButton(
// onPressed: Navigator.of(context).pop,
// child: Text("Cancel"),
// ),
// TextButton(
// onPressed: () {
// room.client.reportRoom(room.id, reasonController.text);
// Navigator.of(context).pop();
// },
// child: Text("Report"),
// ),
// ],
// );
// },
// ),
// ),
// child: ListTile(
// leading: Icon(Icons.report, color: danger),
// title: Text("Report", style: TextStyle(color: danger)),
// ),
// ),
],
);
}

View file

@ -1,4 +1,3 @@
import "package:collection/collection.dart";
import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
@ -6,8 +5,6 @@ import "package:nexus/controllers/client_controller.dart";
import "package:nexus/controllers/key_controller.dart";
import "package:nexus/controllers/selected_space_controller.dart";
import "package:nexus/controllers/spaces_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/join_room_with_snackbars.dart";
import "package:nexus/pages/settings_page.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
@ -22,58 +19,57 @@ class Sidebar extends HookConsumerWidget {
final selectedSpaceProvider = KeyController.provider(
KeyController.spaceKey,
);
final selectedSpace = ref.watch(selectedSpaceProvider);
final selectedSpaceNotifier = ref.watch(selectedSpaceProvider.notifier);
final selectedSpaceId = ref.watch(selectedSpaceProvider);
final selectedSpaceIdNotifier = ref.watch(selectedSpaceProvider.notifier);
final selectedRoomController = KeyController.provider(
KeyController.roomKey,
);
final selectedRoom = ref.watch(selectedRoomController);
final selectedRoomNotifier = ref.watch(selectedRoomController.notifier);
final selectedRoomId = ref.watch(selectedRoomController);
final selectedRoomIdNotifier = ref.watch(selectedRoomController.notifier);
final spaces = ref.watch(SpacesController.provider);
final indexOfSelected = spaces.indexWhere(
(space) => space.id == selectedSpaceId,
);
final selectedIndex = indexOfSelected == -1 ? 0 : indexOfSelected;
final selectedSpace = ref.watch(SelectedSpaceController.provider);
final indexOfSelectedRoom = selectedSpace.children.indexWhere(
(room) => room.metadata?.id == selectedRoomId,
);
final selectedRoomIndex = indexOfSelected == -1
? selectedSpace.children.isEmpty
? null
: 0
: indexOfSelectedRoom;
return Drawer(
shape: Border(),
child: Row(
children: [
ref
.watch(SpacesController.provider)
.when(
loading: SizedBox.shrink,
error: (error, stack) {
debugPrintStack(label: error.toString(), stackTrace: stack);
throw error;
},
data: (spaces) {
final indexOfSelected = spaces.indexWhere(
(space) => space.id == selectedSpace,
);
final selectedIndex = indexOfSelected == -1
? 0
: indexOfSelected;
return NavigationRail(
NavigationRail(
scrollable: true,
onDestinationSelected: (value) {
selectedSpaceNotifier.set(spaces[value].id);
selectedRoomNotifier.set(
spaces[value].children.firstOrNull?.roomData.id,
selectedSpaceIdNotifier.set(spaces[value].id);
selectedRoomIdNotifier.set(
spaces[value].children.firstOrNull?.metadata?.id,
);
},
destinations: spaces
.map(
(space) => NavigationRailDestination(
icon: AvatarOrHash(
space.avatar,
fallback: space.icon == null
? null
: Icon(space.icon),
null, // TODO: Url
fallback: space.icon == null ? null : Icon(space.icon),
space.title,
headers: space.client.headers,
hasBadge:
space.children.firstWhereOrNull(
(room) => room.roomData.hasNewMessages,
) !=
null,
headers: {}, // TODO
hasBadge: false,
// space.children.firstWhereOrNull( TODO
// (room) => room.roomData.hasNewMessages,
// ) !=
// null,
),
label: Text(space.title),
padding: EdgeInsets.only(top: 4),
@ -94,14 +90,12 @@ class Sidebar extends HookConsumerWidget {
context: context,
builder: (alertContext) => HookBuilder(
builder: (_) {
final roomAlias =
useTextEditingController();
final roomAlias = useTextEditingController();
return AlertDialog(
title: Text("Join a Room"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Enter the room alias, ID, or a Matrix.to link.",
@ -117,19 +111,15 @@ class Sidebar extends HookConsumerWidget {
),
actions: [
TextButton(
onPressed: Navigator.of(
context,
).pop,
onPressed: Navigator.of(context).pop,
child: Text("Cancel"),
),
TextButton(
onPressed: () async {
Navigator.of(alertContext).pop();
final client = await ref.watch(
ClientController
.provider
.future,
final client = ref.watch(
ClientController.provider.notifier,
);
if (context.mounted) {
client.joinRoomWithSnackBars(
@ -147,9 +137,7 @@ class Sidebar extends HookConsumerWidget {
),
),
child: ListTile(
title: Text(
"Join an existing room (or space)",
),
title: Text("Join an existing room (or space)"),
leading: Icon(Icons.numbers),
),
),
@ -166,83 +154,72 @@ class Sidebar extends HookConsumerWidget {
IconButton(
onPressed: () => showDialog(
context: context,
builder: (context) =>
AlertDialog(title: Text("To-do")),
builder: (context) => AlertDialog(title: Text("To-do")),
),
icon: Icon(Icons.explore),
),
IconButton(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => SettingsPage()),
),
onPressed: () => Navigator.of(
context,
).push(MaterialPageRoute(builder: (_) => SettingsPage())),
icon: Icon(Icons.settings),
),
],
),
),
);
},
),
Expanded(
child: ref
.watch(SelectedSpaceController.provider)
.betterWhen(
data: (space) {
final indexOfSelected = space.children.indexWhere(
(room) => room.roomData.id == selectedRoom,
);
final selectedIndex = indexOfSelected == -1
? space.children.isEmpty
? null
: 0
: indexOfSelected;
return Scaffold(
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
leading: AvatarOrHash(
space.avatar,
fallback: space.icon == null
null,
// space.avatar, TODO
fallback: selectedSpace.icon == null
? null
: Icon(space.icon),
space.title,
headers: space.client.headers,
: Icon(selectedSpace.icon),
selectedSpace.title, // TODO RM
headers: {},
// space.client.headers, TODO
),
title: Text(
space.title,
selectedSpace.room?.metadata?.avatar.toString() ??
selectedSpace.title,
overflow: TextOverflow.ellipsis,
),
backgroundColor: Colors.transparent,
actions: [
if (space.roomData != null) RoomMenu(space.roomData!),
if (selectedSpace.room != null) RoomMenu(selectedSpace.room!),
],
),
body: NavigationRail(
scrollable: true,
backgroundColor: Colors.transparent,
extended: true,
selectedIndex: selectedIndex,
destinations: space.children
selectedIndex: selectedRoomIndex,
destinations: selectedSpace.children
.map(
(room) => NavigationRailDestination(
label: Text(room.title),
label: Text(room.metadata?.name ?? "Unnamed Room"),
icon: AvatarOrHash(
hasBadge: room.roomData.hasNewMessages,
room.avatar,
room.title,
fallback: selectedSpace == "dms"
// hasBadge: room.roomData.hasNewMessages, TODO
null,
// room.avatar, TODO
room.metadata?.name ?? "Unnamed Room",
fallback: selectedSpaceId == "dms"
? null
: Icon(Icons.numbers),
headers: space.client.headers,
headers: {},
// space.client.headers,
),
),
)
.toList(),
onDestinationSelected: (value) => selectedRoomNotifier
.set(space.children[value].roomData.id),
onDestinationSelected: (value) => selectedRoomIdNotifier.set(
selectedSpace.children[value].metadata?.id,
),
),
);
},
),
),
],