From a9993013f5808d3bae508a0db9be89d796020aa0 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 11 Aug 2026 20:34:43 -0400 Subject: [PATCH] add rooms to mention chips Needs handling for event links, plus clickability --- lib/controllers/client.dart | 10 ++++--- lib/controllers/room_summary.dart | 18 ++++++++++++ lib/controllers/settings.dart | 6 ++-- lib/models/requests/join_room.dart | 2 +- lib/models/room_summary.dart | 23 +++++++++++++++ lib/widgets/html/mention_chip.dart | 46 ++++++++++++++++++++++-------- lib/widgets/room_chat.dart | 2 +- lib/widgets/room_menu.dart | 3 +- 8 files changed, 87 insertions(+), 23 deletions(-) create mode 100644 lib/controllers/room_summary.dart create mode 100644 lib/models/room_summary.dart diff --git a/lib/controllers/client.dart b/lib/controllers/client.dart index 33af1b5..b7d1eb4 100644 --- a/lib/controllers/client.dart +++ b/lib/controllers/client.dart @@ -38,6 +38,7 @@ import "package:nexus/models/requests/set_membership.dart"; import "package:nexus/models/requests/set_state.dart"; import "package:nexus/models/requests/upload_media.dart"; import "package:nexus/models/room.dart"; +import "package:nexus/models/room_summary.dart"; import "package:nexus/models/spec_versions_response.dart"; import "package:nexus/models/sync_data.dart"; import "package:nexus/src/third_party/gomuks.g.dart"; @@ -213,10 +214,11 @@ class ClientController extends AsyncNotifier { } } - Future joinRoom(JoinRoomRequest request) async { - final response = await _sendCommand("join_room", request.toJson()); - return response["room_id"]; - } + Future joinRoom(JoinRoomRequest request) async => + (await _sendCommand("join_room", request.toJson()))["room_id"]; + + Future getRoomSummary(JoinRoomRequest request) async => + .fromJson(await _sendCommand("get_room_summary", request.toJson())); Future leaveRoom(Room room) async { if (room.metadata == null) return; diff --git a/lib/controllers/room_summary.dart b/lib/controllers/room_summary.dart new file mode 100644 index 0000000..d47d8ef --- /dev/null +++ b/lib/controllers/room_summary.dart @@ -0,0 +1,18 @@ +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:nexus/controllers/client.dart"; +import "package:nexus/models/requests/join_room.dart"; +import "package:nexus/models/room_summary.dart"; + +class RoomSummaryController extends AsyncNotifier { + final JoinRoomRequest request; + RoomSummaryController(this.request); + + @override + Future build() => + ref.watch(ClientController.provider.notifier).getRoomSummary(request); + + static final provider = AsyncNotifierProvider.family + .autoDispose( + RoomSummaryController.new, + ); +} diff --git a/lib/controllers/settings.dart b/lib/controllers/settings.dart index 572d2e9..8c107e3 100644 --- a/lib/controllers/settings.dart +++ b/lib/controllers/settings.dart @@ -9,14 +9,14 @@ class SettingsController extends AsyncNotifier { final file = await ref.watch(SettingsFileController.provider.future); try { - return Settings.fromJson(json.decode(await file.readAsString())); + return .fromJson(json.decode(await file.readAsString())); } catch (_) { - return Settings(); + return .new(); } } Future set(Settings settings) async { - state = AsyncData(settings); + state = .data(settings); final file = await ref.watch(SettingsFileController.provider.future); await file.writeAsString(json.encode(settings.toJson())); } diff --git a/lib/models/requests/join_room.dart b/lib/models/requests/join_room.dart index 8487298..72cce7f 100644 --- a/lib/models/requests/join_room.dart +++ b/lib/models/requests/join_room.dart @@ -7,7 +7,7 @@ part "join_room.g.dart"; abstract class JoinRoomRequest with _$JoinRoomRequest { const factory JoinRoomRequest({ required String roomIdOrAlias, - required IList via, + @Default(IList.empty()) IList via, }) = _JoinRoomRequest; factory JoinRoomRequest.fromJson(Map json) => diff --git a/lib/models/room_summary.dart b/lib/models/room_summary.dart new file mode 100644 index 0000000..d0ee96b --- /dev/null +++ b/lib/models/room_summary.dart @@ -0,0 +1,23 @@ +import "package:freezed_annotation/freezed_annotation.dart"; +import "package:nexus/models/content/create.dart"; +import "package:nexus/models/join_rule.dart"; +part "room_summary.freezed.dart"; +part "room_summary.g.dart"; + +@freezed +abstract class RoomSummary with _$RoomSummary { + const factory RoomSummary({ + required String roomId, + @JsonKey(name: "num_joined_members") required int joinedMembers, + JoinRule? joinRule, + String? name, + Uri? avatarUrl, + String? canonicalAlias, + String? topic, + String? roomVersion, + @JsonKey(unknownEnumValue: RoomType.room) RoomType? roomType, + }) = _RoomSummary; + + factory RoomSummary.fromJson(Map json) => + _$RoomSummaryFromJson(json); +} diff --git a/lib/widgets/html/mention_chip.dart b/lib/widgets/html/mention_chip.dart index f6105e3..57e23b4 100644 --- a/lib/widgets/html/mention_chip.dart +++ b/lib/widgets/html/mention_chip.dart @@ -1,8 +1,11 @@ import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/controllers/room_summary.dart"; import "package:nexus/controllers/user.dart"; import "package:nexus/helpers/extensions/link_to_mention.dart"; import "package:nexus/helpers/extensions/show_user_popover.dart"; +import "package:nexus/models/content/membership.dart"; +import "package:nexus/models/room_summary.dart"; class MentionChip extends ConsumerWidget { final String? roomId; @@ -12,29 +15,48 @@ class MentionChip extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final mention = content.mention; - final membership = mention?.startsWith("@") == true - ? ref - .watch( - UserController.provider(.new(roomId: roomId, userId: mention!)), - ) - .whenOrNull(data: (data) => data) - : null; + final data = switch (mention?.characters.firstOrNull) { + "@" => + ref + .watch( + UserController.provider(.new(roomId: roomId, userId: mention!)), + ) + .whenOrNull(data: (data) => data), + + "#" || "!" => + ref + .watch( + RoomSummaryController.provider(.new(roomIdOrAlias: mention!)), + ) + .whenOrNull(data: (data) => data), + + _ => null, + }; return mention == null ? SizedBox.shrink() : InkWell( onTap: () { - if (membership != null) { + if (data case MembershipContent membership) { context.showUserPopover(membership, mention, roomId: roomId); + } else if (data case RoomSummary summary) { + // TODO: Handle summary } }, child: IgnorePointer( child: Chip( label: Text( - (membership?.displayName == null - ? null - : "@${membership!.displayName}") ?? - mention, + switch (data) { + RoomSummary summary => + (summary.name == null ? null : "#${summary.name}") ?? + summary.canonicalAlias ?? + summary.roomId, + MembershipContent membership => + membership.displayName == null + ? mention + : "@${membership.displayName}", + _ => mention, + }, style: .new( fontWeight: .bold, color: Theme.of(context).colorScheme.onPrimary, diff --git a/lib/widgets/room_chat.dart b/lib/widgets/room_chat.dart index 07cf26d..01908a8 100644 --- a/lib/widgets/room_chat.dart +++ b/lib/widgets/room_chat.dart @@ -311,7 +311,7 @@ class RoomChat extends HookConsumerWidget { await Clipboard.setData( ClipboardData( text: - "matrix:roomid/${room.metadata?.id.substring(1)}/e/${event.eventId}$vias)", + "matrix:roomid/${room.metadata?.id.substring(1)}/e/${event.eventId}$vias", ), ); }, diff --git a/lib/widgets/room_menu.dart b/lib/widgets/room_menu.dart index 95936df..e99991f 100644 --- a/lib/widgets/room_menu.dart +++ b/lib/widgets/room_menu.dart @@ -35,8 +35,7 @@ class RoomMenu extends ConsumerWidget { await Clipboard.setData( .new( - text: - "matrix:roomid/${room!.metadata?.id.substring(1)}$vias)", + text: "matrix:roomid/${room!.metadata?.id.substring(1)}$vias", ), ); },