From c11c2c9b6361037263dba934eaf9527fd747643d Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 14 Jan 2026 15:17:09 -0500 Subject: [PATCH] Add room joining --- README.md | 4 +- lib/widgets/chat_page/room_chat.dart | 1 + lib/widgets/chat_page/room_menu.dart | 3 +- lib/widgets/chat_page/sidebar.dart | 175 ++++++++++++++++++++++++++- 4 files changed, 175 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f248d05..4b47df4 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [x] Mark as read button on rooms and spaces - [ ] Searching - [ ] Creating (Rooms, Spaces, and DMs) - - [ ] Joining - - [ ] Using alias + - [x] Joining + - [x] Using alias/id/link - [ ] From space - [ ] Exploring - [x] Leaving diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart index 849c1d7..b353992 100644 --- a/lib/widgets/chat_page/room_chat.dart +++ b/lib/widgets/chat_page/room_chat.dart @@ -97,6 +97,7 @@ class RoomChat extends HookConsumerWidget { title: Text("Delete Message"), content: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Are you sure you want to delete this message? This can not be reversed.", diff --git a/lib/widgets/chat_page/room_menu.dart b/lib/widgets/chat_page/room_menu.dart index 9dee21d..ea123fd 100644 --- a/lib/widgets/chat_page/room_menu.dart +++ b/lib/widgets/chat_page/room_menu.dart @@ -80,6 +80,7 @@ class RoomMenu extends StatelessWidget { 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.", @@ -90,7 +91,7 @@ class RoomMenu extends StatelessWidget { required: false, capitalize: true, controller: reasonController, - title: "Reason for deletion (optional)", + title: "Reason for report (optional)", ), ], ), diff --git a/lib/widgets/chat_page/sidebar.dart b/lib/widgets/chat_page/sidebar.dart index d4bd89d..2bd6cf8 100644 --- a/lib/widgets/chat_page/sidebar.dart +++ b/lib/widgets/chat_page/sidebar.dart @@ -1,6 +1,9 @@ import "package:collection/collection.dart"; import "package:flutter/material.dart"; +import "package:flutter_hooks/flutter_hooks.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/selected_space_controller.dart"; import "package:nexus/controllers/spaces_controller.dart"; @@ -9,6 +12,7 @@ import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/pages/settings_page.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/chat_page/room_menu.dart"; +import "package:nexus/widgets/form_text_input.dart"; class Sidebar extends HookConsumerWidget { const Sidebar({super.key}); @@ -83,11 +87,172 @@ class Sidebar extends HookConsumerWidget { child: Column( spacing: 8, children: [ - IconButton( - onPressed: () => Navigator.of(context).push( - // TODO: join or create room/space - MaterialPageRoute(builder: (_) => SettingsPage()), - ), + PopupMenuButton( + itemBuilder: (_) => [ + PopupMenuItem( + onTap: () => showDialog( + context: context, + builder: (alertContext) => HookBuilder( + builder: (_) { + final roomAlias = + useTextEditingController(); + return AlertDialog( + title: Text("Join a Room"), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Enter the room alias, ID, or a Matrix.to link.", + ), + SizedBox(height: 12), + FormTextInput( + required: false, + capitalize: true, + controller: roomAlias, + title: "#room:server", + ), + ], + ), + actions: [ + TextButton( + onPressed: Navigator.of( + context, + ).pop, + child: Text("Cancel"), + ), + TextButton( + onPressed: () async { + final parsed = roomAlias.text + .parseIdentifierIntoParts(); + final alias = + parsed?.primaryIdentifier ?? + roomAlias.text; + + Navigator.of(alertContext).pop(); + + final scaffoldMessenger = + ScaffoldMessenger.of(context); + + final snackbar = scaffoldMessenger + .showSnackBar( + SnackBar( + content: Text( + "Joining room...", + ), + duration: Duration( + days: 999, + ), + ), + ); + + final client = await ref.watch( + ClientController + .provider + .future, + ); + + try { + final id = await client + .joinRoom( + alias, + via: parsed?.via.toList(), + ); + + snackbar.close(); + + scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + "Room successfully joined.", + ), + action: SnackBarAction( + label: "Open", + onPressed: () async { + final spaces = await ref + .watch( + SpacesController + .provider + .future, + ); + + final space = spaces + .firstWhereOrNull( + (space) => + space.id == + id, + ); + + await selectedSpaceNotifier.set( + space?.id ?? + spaces + .firstWhere( + (space) => + space.children.firstWhereOrNull( + ( + child, + ) => + child.roomData.id == + id, + ) != + null, + ) + .id, + ); + + if (space == null) { + await selectedRoomNotifier + .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, + ), + ), + ), + ); + } + } + }, + child: Text("Join"), + ), + ], + ); + }, + ), + ), + child: ListTile( + title: Text( + "Join an existing room (or space)", + ), + leading: Icon(Icons.numbers), + ), + ), + PopupMenuItem( + onTap: () {}, + child: ListTile( + title: Text("Create a new room"), + leading: Icon(Icons.add), + ), + ), + ], icon: Icon(Icons.add), ), IconButton(