From f7c6c3bb6a466cb48a2c3cf7605338a4f38bec2f Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 3 Dec 2025 13:50:06 -0500 Subject: [PATCH] Add ability to copy links for rooms and spaces --- README.md | 2 +- lib/controllers/spaces_controller.dart | 3 +-- lib/models/space.dart | 2 +- lib/widgets/chat_page/room_appbar.dart | 3 +++ lib/widgets/chat_page/room_menu.dart | 33 ++++++++++++++++++++++++++ lib/widgets/chat_page/sidebar.dart | 4 ++++ pubspec.lock | 8 +++++++ pubspec.yaml | 1 + 8 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 lib/widgets/chat_page/room_menu.dart diff --git a/README.md b/README.md index a3d4f56..b483786 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [ ] Creating - [ ] Threads - [ ] Profile popouts -- [ ] Copy link to [message, room, space] +- [x] Copy link to [room, space] - [ ] Reporting - [ ] Notifications using UnifiedPush - [ ] Group calls using [MSC4195](https://github.com/matrix-org/matrix-spec-proposals/pull/4195) diff --git a/lib/controllers/spaces_controller.dart b/lib/controllers/spaces_controller.dart index 733770c..eb7d36c 100644 --- a/lib/controllers/spaces_controller.dart +++ b/lib/controllers/spaces_controller.dart @@ -35,7 +35,6 @@ class SpacesController extends AsyncNotifier> { title: "Home", children: topLevelRooms, icon: Icon(Icons.home), - fake: true, ), Space( client: client, @@ -46,7 +45,6 @@ class SpacesController extends AsyncNotifier> { .map((room) => room.fullRoom), ), icon: Icon(Icons.person), - fake: true, ), ...(await Future.wait( topLevelSpaces.map( @@ -54,6 +52,7 @@ class SpacesController extends AsyncNotifier> { client: client, title: space.title, avatar: space.avatar, + roomData: space.roomData, children: await Future.wait( space.roomData.spaceChildren .map( diff --git a/lib/models/space.dart b/lib/models/space.dart index 7feeba5..047bac6 100644 --- a/lib/models/space.dart +++ b/lib/models/space.dart @@ -10,7 +10,7 @@ abstract class Space with _$Space { required String title, required List children, required Client client, - @Default(false) bool fake, + Room? roomData, Uri? avatar, Icon? icon, }) = _Space; diff --git a/lib/widgets/chat_page/room_appbar.dart b/lib/widgets/chat_page/room_appbar.dart index dbeb825..17696dd 100644 --- a/lib/widgets/chat_page/room_appbar.dart +++ b/lib/widgets/chat_page/room_appbar.dart @@ -3,6 +3,7 @@ import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/models/full_room.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; +import "package:nexus/widgets/chat_page/room_menu.dart"; class RoomAppbar extends StatelessWidget implements PreferredSizeWidget { final bool isDesktop; @@ -48,10 +49,12 @@ class RoomAppbar extends StatelessWidget implements PreferredSizeWidget { ], ), actions: [ + IconButton(onPressed: () {}, icon: Icon(Icons.push_pin)), IconButton( onPressed: () => onOpenMemberList(context), icon: Icon(Icons.people), ), + RoomMenu(room.roomData), ], ); } diff --git a/lib/widgets/chat_page/room_menu.dart b/lib/widgets/chat_page/room_menu.dart new file mode 100644 index 0000000..10edc26 --- /dev/null +++ b/lib/widgets/chat_page/room_menu.dart @@ -0,0 +1,33 @@ +import "package:clipboard/clipboard.dart"; +import "package:flutter/material.dart"; +import "package:matrix/matrix.dart"; + +class RoomMenu extends StatelessWidget { + final Room room; + const RoomMenu(this.room, {super.key}); + + @override + Widget build(BuildContext context) { + final danger = Theme.of(context).colorScheme.error; + + return PopupMenuButton( + itemBuilder: (_) => [ + PopupMenuItem( + onTap: () async { + final link = await room.matrixToInviteLink(); + await FlutterClipboard.copy(link.toString()); + }, + child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")), + ), + PopupMenuItem( + onTap: () => + showDialog(context: context, builder: (context) => AlertDialog()), + child: ListTile( + leading: Icon(Icons.logout, color: danger), + title: Text("Leave", style: TextStyle(color: danger)), + ), + ), + ], + ); + } +} diff --git a/lib/widgets/chat_page/sidebar.dart b/lib/widgets/chat_page/sidebar.dart index d8d579e..34abea9 100644 --- a/lib/widgets/chat_page/sidebar.dart +++ b/lib/widgets/chat_page/sidebar.dart @@ -9,6 +9,7 @@ import "package:nexus/helpers/extensions/better_when.dart"; 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"; class Sidebar extends HookConsumerWidget { const Sidebar({super.key}); @@ -116,6 +117,9 @@ class Sidebar extends HookConsumerWidget { overflow: TextOverflow.ellipsis, ), backgroundColor: Colors.transparent, + actions: [ + if (space.roomData != null) RoomMenu(space.roomData!), + ], ), body: NavigationRail( scrollable: true, diff --git a/pubspec.lock b/pubspec.lock index b88213e..f4b6c61 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -201,6 +201,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.2" + clipboard: + dependency: "direct main" + description: + name: clipboard + sha256: "1920c0337f8808be4166c5f1b236301ff381ef69633b0757c502d97f1f740102" + url: "https://pub.dev" + source: hosted + version: "2.0.2" clock: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 97090bb..95441fe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,6 +63,7 @@ dependencies: simple_secure_storage: ^0.3.6 json_annotation: ^4.9.0 vodozemac: ^0.4.0 + clipboard: ^2.0.2 dev_dependencies: build_runner: ^2.4.11