Gomuks SDK Rewrite #2

Closed
Henry-Hiles wants to merge 34 commits from go into main
13 changed files with 491 additions and 436 deletions
Showing only changes of commit 85d96b80bc - Show all commits

working sidebar

Henry Hiles 2026-01-27 14:14:04 +00:00
No known key found for this signature in database

View file

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

View file

@ -2,24 +2,23 @@ import "package:collection/collection.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/key_controller.dart"; import "package:nexus/controllers/key_controller.dart";
import "package:nexus/controllers/selected_space_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 @override
Future<FullRoom?> build() async { Room? build() {
final space = await ref.watch(SelectedSpaceController.provider.future); final space = ref.watch(SelectedSpaceController.provider);
final selectedRoomId = ref.watch( final selectedRoomId = ref.watch(
KeyController.provider(KeyController.roomKey), KeyController.provider(KeyController.roomKey),
); );
return space.children.firstWhereOrNull( return space.children.firstWhereOrNull(
(room) => room.roomData.id == selectedRoomId, (room) => room.metadata?.id == selectedRoomId,
) ?? ) ??
space.children.firstOrNull; space.children.firstOrNull;
} }
static final provider = static final provider = NotifierProvider<SelectedRoomController, Room?>(
AsyncNotifierProvider<SelectedRoomController, FullRoom?>(
SelectedRoomController.new, 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/controllers/spaces_controller.dart";
import "package:nexus/models/space.dart"; import "package:nexus/models/space.dart";
class SelectedSpaceController extends AsyncNotifier<Space> { class SelectedSpaceController extends Notifier<Space> {
@override @override
Future<Space> build() async { Space build() {
final spaces = await ref.watch( final spaces = ref.watch(SpacesController.provider);
SpacesController.provider.selectAsync((data) => data),
);
final selectedSpaceId = ref.watch( final selectedSpaceId = ref.watch(
KeyController.provider(KeyController.spaceKey), KeyController.provider(KeyController.spaceKey),
); );
@ -18,7 +16,7 @@ class SelectedSpaceController extends AsyncNotifier<Space> {
spaces.first; spaces.first;
} }
static final provider = AsyncNotifierProvider<SelectedSpaceController, Space>( static final provider = NotifierProvider<SelectedSpaceController, Space>(
SelectedSpaceController.new, 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:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.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/rooms_controller.dart";
import "package:nexus/controllers/top_level_spaces_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/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 @override
Future<IList<Space>> build() async { IList<Space> build() {
final topLevelSpaceIds = ref.watch(TopLevelSpacesController.provider);
final rooms = ref.watch(RoomsController.provider); final rooms = ref.watch(RoomsController.provider);
final topLevelSpaceIds = ref.watch(TopLevelSpacesController.provider);
final spaceEdges = ref.watch(SpaceEdgesController.provider);
final topLevelSpaces = topLevelSpaceIds ISet<String> collectChildIds(String spaceId) {
.map((id) => rooms[id]) ISet<String> result = ISet<String>();
.nonNulls void walk(String currentId) {
.toIList(); 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 final dmRooms = rooms.values
.where((room) => room.metadata?.dmUserId != null) .where((room) => room.metadata?.dmUserId != null)
.toIList(); .toIList();
final topLevelRooms = rooms.values final homeRooms = rooms.entries
.where((room) => room.metadata?.dmUserId == null)
.where( .where(
(room) => spaceRooms.every( (e) =>
(space) => e.value.metadata?.dmUserId == null &&
space.spaceChildren.every((child) => child.roomId != room.id), !allNestedRoomIds.contains(e.key) &&
), !topLevelSpaceIds.contains(e.key),
) )
.map((e) => e.value)
.toIList(); .toIList();
// 4 Combine all into a single IList final topLevelSpacesList = topLevelSpaceIds
return IList([ .map((id) {
Space( final room = rooms[id];
id: "home", if (room == null) return null;
title: "Home",
children: topLevelRooms, final children = spaceIdToChildren[id] ?? IList<Room>();
icon: Icons.home, 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( Space(
id: "dms", id: "dms",
title: "Direct Messages", title: "Direct Messages",
icon: Icons.people,
children: dmRooms, children: dmRooms,
icon: Icons.person,
), ),
...topLevelSpaces, ...topLevelSpacesList,
]); ].toIList();
} }
static final provider = AsyncNotifierProvider<SpacesController, IList<Space>>( static final provider = NotifierProvider<SpacesController, IList<Space>>(
SpacesController.new, SpacesController.new,
); );
} }

View file

@ -1,87 +1,87 @@
import "package:collection/collection.dart"; import "package:collection/collection.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.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/key_controller.dart";
import "package:nexus/controllers/spaces_controller.dart"; import "package:nexus/controllers/spaces_controller.dart";
extension JoinRoomWithSnackbars on Client { extension JoinRoomWithSnackbars on ClientController {
Future<void> joinRoomWithSnackBars( Future<void> joinRoomWithSnackBars(
BuildContext context, BuildContext context,
String roomAlias, String roomAlias,
WidgetRef ref, WidgetRef ref,
) async { ) async {
final parsed = roomAlias.parseIdentifierIntoParts(); // final parsed = roomAlias.parseIdentifierIntoParts();
final alias = parsed?.primaryIdentifier ?? roomAlias; // final alias = parsed?.primaryIdentifier ?? roomAlias;
final scaffoldMessenger = ScaffoldMessenger.of(context); // final scaffoldMessenger = ScaffoldMessenger.of(context);
final snackbar = scaffoldMessenger.showSnackBar( // final snackbar = scaffoldMessenger.showSnackBar(
SnackBar( // SnackBar(
content: Text("Joining room $alias."), // content: Text("Joining room $alias."),
duration: Duration(days: 999), // duration: Duration(days: 999),
), // ),
); // );
try { // try {
final id = await joinRoom(alias, via: parsed?.via.toList()); // final id = await joinRoom(alias, via: parsed?.via.toList());
snackbar.close(); // snackbar.close();
scaffoldMessenger.showSnackBar( // scaffoldMessenger.showSnackBar(
SnackBar( // SnackBar(
content: Text("Room $alias successfully joined."), // content: Text("Room $alias successfully joined."),
action: SnackBarAction( // action: SnackBarAction(
label: "Open", // label: "Open",
onPressed: () async { // onPressed: () async {
final spaces = await ref.refresh( // final spaces = await ref.refresh(
SpacesController.provider.future, // SpacesController.provider.future,
); // );
final space = spaces.firstWhereOrNull((space) => space.id == id); // final space = spaces.firstWhereOrNull((space) => space.id == id);
await ref // await ref
.watch( // .watch(
KeyController.provider(KeyController.spaceKey).notifier, // KeyController.provider(KeyController.spaceKey).notifier,
) // )
.set( // .set(
space?.id ?? // space?.id ??
spaces // spaces
.firstWhere( // .firstWhere(
(space) => // (space) =>
space.children.firstWhereOrNull( // space.children.firstWhereOrNull(
(child) => child.roomData.id == id, // (child) => child.roomData.id == id,
) != // ) !=
null, // null,
) // )
.id, // .id,
); // );
if (space == null) { // if (space == null) {
await ref // await ref
.watch( // .watch(
KeyController.provider(KeyController.roomKey).notifier, // KeyController.provider(KeyController.roomKey).notifier,
) // )
.set(id); // .set(id);
} // }
}, // },
), // ),
), // ),
); // );
} catch (error) { // } catch (error) {
snackbar.close(); // snackbar.close();
if (context.mounted) { // if (context.mounted) {
scaffoldMessenger.showSnackBar( // scaffoldMessenger.showSnackBar(
SnackBar( // SnackBar(
backgroundColor: Theme.of(context).colorScheme.errorContainer, // backgroundColor: Theme.of(context).colorScheme.errorContainer,
content: Text( // content: Text(
error.toString(), // error.toString(),
style: TextStyle( // style: TextStyle(
color: Theme.of(context).colorScheme.onErrorContainer, // 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:flutter/widgets.dart";
import "package:freezed_annotation/freezed_annotation.dart"; import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/room.dart"; import "package:nexus/models/room.dart";
@ -10,6 +11,6 @@ abstract class Space with _$Space {
required String title, required String title,
IconData? icon, IconData? icon,
Room? room, Room? room,
required List<Room> children, required IList<Room> children,
}) = _Space; }) = _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:fast_immutable_collections/fast_immutable_collections.dart";
import "package:freezed_annotation/freezed_annotation.dart"; import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/room.dart"; import "package:nexus/models/room.dart";
import "package:nexus/models/space_edge.dart";
part "sync_data.freezed.dart"; part "sync_data.freezed.dart";
part "sync_data.g.dart"; part "sync_data.g.dart";
@ -12,8 +13,8 @@ abstract class SyncData with _$SyncData {
@Default(IMap.empty()) IMap<String, Room> rooms, @Default(IMap.empty()) IMap<String, Room> rooms,
@Default(ISet.empty()) ISet<String> leftRooms, @Default(ISet.empty()) ISet<String> leftRooms,
// required IList<InvitedRoom> invitedRooms, // required IList<InvitedRoom> invitedRooms,
// required IList<SpaceEdge> spaceEdges, IMap<String, IList<SpaceEdge>>? spaceEdges,
@Default(IList.empty()) IList<String> topLevelSpaces, IList<String>? topLevelSpaces,
}) = _SyncData; }) = _SyncData;
factory SyncData.fromJson(Map<String, Object?> json) => factory SyncData.fromJson(Map<String, Object?> json) =>

View file

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

View file

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

View file

@ -1,122 +1,124 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter/services.dart"; import "package:flutter/services.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:matrix/matrix.dart"; import "package:nexus/models/room.dart";
import "package:nexus/helpers/extensions/room_to_children.dart";
import "package:nexus/widgets/form_text_input.dart"; import "package:nexus/widgets/form_text_input.dart";
class RoomMenu extends StatelessWidget { class RoomMenu extends StatelessWidget {
final Room room; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final danger = Theme.of(context).colorScheme.error; final danger = Theme.of(context).colorScheme.error;
void markRead(String roomId) async { void markRead(String roomId) async {
for (final child in await room.getAllChildren()) { // TODO: Set parent read
await child.roomData.setReadMarker( for (final child in children) {
child.roomData.lastEvent?.eventId, // await child.setReadMarker( TODO: Set children read
mRead: child.roomData.lastEvent?.eventId, // child.roomData.lastEvent?.eventId,
); // mRead: child.roomData.lastEvent?.eventId,
// );
} }
} }
return PopupMenuButton( return PopupMenuButton(
itemBuilder: (_) => [ itemBuilder: (_) => [
PopupMenuItem( // PopupMenuItem(
onTap: () async { // onTap: () async {
final link = await room.matrixToInviteLink(); // final link = await room.matrixToInviteLink();
await Clipboard.setData(ClipboardData(text: link.toString())); // await Clipboard.setData(ClipboardData(text: link.toString()));
}, // },
child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")), // child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")),
), // ),
PopupMenuItem( // PopupMenuItem(
onTap: () => markRead(room.id), // onTap: () => markRead(room.id),
child: ListTile( // child: ListTile(
leading: Icon(Icons.check), // leading: Icon(Icons.check),
title: Text("Mark as Read"), // title: Text("Mark as Read"),
), // ),
), // ),
PopupMenuItem( // PopupMenuItem(
onTap: () => showDialog( // onTap: () => showDialog(
context: context, // context: context,
builder: (context) => AlertDialog( // builder: (context) => AlertDialog(
title: Text("Leave Room"), // title: Text("Leave Room"),
content: Text( // content: Text(
"Are you sure you want to leave \"${room.getLocalizedDisplayname()}\"?", // "Are you sure you want to leave \"${room.getLocalizedDisplayname()}\"?",
), // ),
actions: [ // actions: [
TextButton( // TextButton(
onPressed: Navigator.of(context).pop, // onPressed: Navigator.of(context).pop,
child: Text("Cancel"), // child: Text("Cancel"),
), // ),
TextButton( // TextButton(
onPressed: () async { // onPressed: () async {
Navigator.of(context).pop(); // Navigator.of(context).pop();
final snackbar = ScaffoldMessenger.of( // final snackbar = ScaffoldMessenger.of(
context, // context,
).showSnackBar(SnackBar(content: Text("Leaving room..."))); // ).showSnackBar(SnackBar(content: Text("Leaving room...")));
await room.leave(); // await room.leave();
snackbar.close(); // snackbar.close();
}, // },
child: Text("Leave"), // child: Text("Leave"),
), // ),
], // ],
), // ),
), // ),
child: ListTile( // child: ListTile(
leading: Icon(Icons.logout, color: danger), // leading: Icon(Icons.logout, color: danger),
title: Text("Leave", style: TextStyle(color: danger)), // title: Text("Leave", style: TextStyle(color: danger)),
), // ),
), // ),
PopupMenuItem( // PopupMenuItem(
onTap: () => showDialog( // onTap: () => showDialog(
context: context, // context: context,
builder: (context) => HookBuilder( // builder: (context) => HookBuilder(
builder: (_) { // builder: (_) {
final reasonController = useTextEditingController(); // final reasonController = useTextEditingController();
return AlertDialog( // return AlertDialog(
title: Text("Report"), // title: Text("Report"),
content: Column( // content: Column(
mainAxisSize: MainAxisSize.min, // mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, // crossAxisAlignment: CrossAxisAlignment.start,
children: [ // children: [
Text( // Text(
"Report this room to your server administrators, who can take action like banning this room.", // "Report this room to your server administrators, who can take action like banning this room.",
), // ),
SizedBox(height: 12), // SizedBox(height: 12),
FormTextInput( // FormTextInput(
required: false, // required: false,
capitalize: true, // capitalize: true,
controller: reasonController, // controller: reasonController,
title: "Reason for report (optional)", // title: "Reason for report (optional)",
), // ),
], // ],
), // ),
actions: [ // actions: [
TextButton( // TextButton(
onPressed: Navigator.of(context).pop, // onPressed: Navigator.of(context).pop,
child: Text("Cancel"), // child: Text("Cancel"),
), // ),
TextButton( // TextButton(
onPressed: () { // onPressed: () {
room.client.reportRoom(room.id, reasonController.text); // room.client.reportRoom(room.id, reasonController.text);
Navigator.of(context).pop(); // Navigator.of(context).pop();
}, // },
child: Text("Report"), // child: Text("Report"),
), // ),
], // ],
); // );
}, // },
), // ),
), // ),
child: ListTile( // child: ListTile(
leading: Icon(Icons.report, color: danger), // leading: Icon(Icons.report, color: danger),
title: Text("Report", style: TextStyle(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/material.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";
@ -6,8 +5,6 @@ import "package:nexus/controllers/client_controller.dart";
import "package:nexus/controllers/key_controller.dart"; import "package:nexus/controllers/key_controller.dart";
import "package:nexus/controllers/selected_space_controller.dart"; import "package:nexus/controllers/selected_space_controller.dart";
import "package:nexus/controllers/spaces_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/helpers/extensions/join_room_with_snackbars.dart";
import "package:nexus/pages/settings_page.dart"; import "package:nexus/pages/settings_page.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
@ -22,58 +19,57 @@ class Sidebar extends HookConsumerWidget {
final selectedSpaceProvider = KeyController.provider( final selectedSpaceProvider = KeyController.provider(
KeyController.spaceKey, KeyController.spaceKey,
); );
final selectedSpace = ref.watch(selectedSpaceProvider); final selectedSpaceId = ref.watch(selectedSpaceProvider);
final selectedSpaceNotifier = ref.watch(selectedSpaceProvider.notifier); final selectedSpaceIdNotifier = ref.watch(selectedSpaceProvider.notifier);
final selectedRoomController = KeyController.provider( final selectedRoomController = KeyController.provider(
KeyController.roomKey, KeyController.roomKey,
); );
final selectedRoom = ref.watch(selectedRoomController); final selectedRoomId = ref.watch(selectedRoomController);
final selectedRoomNotifier = ref.watch(selectedRoomController.notifier); 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( return Drawer(
shape: Border(), shape: Border(),
child: Row( child: Row(
children: [ children: [
ref NavigationRail(
.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(
scrollable: true, scrollable: true,
onDestinationSelected: (value) { onDestinationSelected: (value) {
selectedSpaceNotifier.set(spaces[value].id); selectedSpaceIdNotifier.set(spaces[value].id);
selectedRoomNotifier.set( selectedRoomIdNotifier.set(
spaces[value].children.firstOrNull?.roomData.id, spaces[value].children.firstOrNull?.metadata?.id,
); );
}, },
destinations: spaces destinations: spaces
.map( .map(
(space) => NavigationRailDestination( (space) => NavigationRailDestination(
icon: AvatarOrHash( icon: AvatarOrHash(
space.avatar, null, // TODO: Url
fallback: space.icon == null fallback: space.icon == null ? null : Icon(space.icon),
? null
: Icon(space.icon),
space.title, space.title,
headers: space.client.headers, headers: {}, // TODO
hasBadge: hasBadge: false,
space.children.firstWhereOrNull( // space.children.firstWhereOrNull( TODO
(room) => room.roomData.hasNewMessages, // (room) => room.roomData.hasNewMessages,
) != // ) !=
null, // null,
), ),
label: Text(space.title), label: Text(space.title),
padding: EdgeInsets.only(top: 4), padding: EdgeInsets.only(top: 4),
@ -94,14 +90,12 @@ class Sidebar extends HookConsumerWidget {
context: context, context: context,
builder: (alertContext) => HookBuilder( builder: (alertContext) => HookBuilder(
builder: (_) { builder: (_) {
final roomAlias = final roomAlias = useTextEditingController();
useTextEditingController();
return AlertDialog( return AlertDialog(
title: Text("Join a Room"), title: Text("Join a Room"),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: crossAxisAlignment: CrossAxisAlignment.start,
CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Enter the room alias, ID, or a Matrix.to link.", "Enter the room alias, ID, or a Matrix.to link.",
@ -117,19 +111,15 @@ class Sidebar extends HookConsumerWidget {
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: Navigator.of( onPressed: Navigator.of(context).pop,
context,
).pop,
child: Text("Cancel"), child: Text("Cancel"),
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
Navigator.of(alertContext).pop(); Navigator.of(alertContext).pop();
final client = await ref.watch( final client = ref.watch(
ClientController ClientController.provider.notifier,
.provider
.future,
); );
if (context.mounted) { if (context.mounted) {
client.joinRoomWithSnackBars( client.joinRoomWithSnackBars(
@ -147,9 +137,7 @@ class Sidebar extends HookConsumerWidget {
), ),
), ),
child: ListTile( child: ListTile(
title: Text( title: Text("Join an existing room (or space)"),
"Join an existing room (or space)",
),
leading: Icon(Icons.numbers), leading: Icon(Icons.numbers),
), ),
), ),
@ -166,83 +154,72 @@ class Sidebar extends HookConsumerWidget {
IconButton( IconButton(
onPressed: () => showDialog( onPressed: () => showDialog(
context: context, context: context,
builder: (context) => builder: (context) => AlertDialog(title: Text("To-do")),
AlertDialog(title: Text("To-do")),
), ),
icon: Icon(Icons.explore), icon: Icon(Icons.explore),
), ),
IconButton( IconButton(
onPressed: () => Navigator.of(context).push( onPressed: () => Navigator.of(
MaterialPageRoute(builder: (_) => SettingsPage()), context,
), ).push(MaterialPageRoute(builder: (_) => SettingsPage())),
icon: Icon(Icons.settings), icon: Icon(Icons.settings),
), ),
], ],
), ),
), ),
);
},
), ),
Expanded( Expanded(
child: ref child: Scaffold(
.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(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
appBar: AppBar( appBar: AppBar(
leading: AvatarOrHash( leading: AvatarOrHash(
space.avatar, null,
fallback: space.icon == null // space.avatar, TODO
fallback: selectedSpace.icon == null
? null ? null
: Icon(space.icon), : Icon(selectedSpace.icon),
space.title,
headers: space.client.headers, selectedSpace.title, // TODO RM
headers: {},
// space.client.headers, TODO
), ),
title: Text( title: Text(
space.title, selectedSpace.room?.metadata?.avatar.toString() ??
selectedSpace.title,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
actions: [ actions: [
if (space.roomData != null) RoomMenu(space.roomData!), if (selectedSpace.room != null) RoomMenu(selectedSpace.room!),
], ],
), ),
body: NavigationRail( body: NavigationRail(
scrollable: true, scrollable: true,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
extended: true, extended: true,
selectedIndex: selectedIndex, selectedIndex: selectedRoomIndex,
destinations: space.children destinations: selectedSpace.children
.map( .map(
(room) => NavigationRailDestination( (room) => NavigationRailDestination(
label: Text(room.title), label: Text(room.metadata?.name ?? "Unnamed Room"),
icon: AvatarOrHash( icon: AvatarOrHash(
hasBadge: room.roomData.hasNewMessages, // hasBadge: room.roomData.hasNewMessages, TODO
room.avatar, null,
room.title, // room.avatar, TODO
fallback: selectedSpace == "dms" room.metadata?.name ?? "Unnamed Room",
fallback: selectedSpaceId == "dms"
? null ? null
: Icon(Icons.numbers), : Icon(Icons.numbers),
headers: space.client.headers, headers: {},
// space.client.headers,
), ),
), ),
) )
.toList(), .toList(),
onDestinationSelected: (value) => selectedRoomNotifier onDestinationSelected: (value) => selectedRoomIdNotifier.set(
.set(space.children[value].roomData.id), selectedSpace.children[value].metadata?.id,
),
), ),
);
},
), ),
), ),
], ],