diff --git a/README.md b/README.md index 4f73220..1299c71 100644 --- a/README.md +++ b/README.md @@ -15,115 +15,113 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S ## Progress -- [ ] New logo -- [ ] Make context menus appear as bottom sheets on mobile -- [x] Move from the Dart SDK to the Gomuks SDK with Dart bindings: https://git.federated.nexus/Henry-Hiles/nexus/pulls/2 - - [ ] Allow using remote gomuks over websocket -- [ ] Platform Support - - [x] Linux - - [x] Windows - - [ ] MacOS - - [ ] Android - - [ ] iOS - - [ ] Web (may not be possible) -- [x] Login - - [x] Username / password auth - - [ ] OAuth / OIDC - - [x] Improve initial sync experience -- [x] Rooms / Spaces - - [x] Displaying and choosing - - [x] Reading, showing unread - - [x] Mark as read button on rooms and spaces - - [ ] Searching - - [ ] Creating (Rooms, Spaces, and DMs) - - [x] Joining - - [ ] Parse vias - - [x] Using a text/uri/link - - [x] Plain text - - [x] `matrix:` Uri - - [x] Matrix.to link - - [ ] From space - - [ ] Exploring - - [x] Leaving - - [x] Subspaces -- [x] Messages - - [x] Encryption - - [x] Restoring crypto identity from a recovery passphrase/key - - [x] Sending - - [x] Plain text - - [x] HTML/Markdown - - [x] Replies - - [x] Choose ping on/off - - [ ] Per message profiles - - [ ] Attachments - - [ ] Commands with [MSC4391](https://github.com/matrix-org/matrix-spec-proposals/pull/4391) - - [x] Mentions - - [x] Users - - [x] Rooms - - [ ] Inline emoji picker (Putting this here since it'll be implemented the same way as mentions) - - [ ] Custom emojis/stickers - - [ ] GIFs using Gomuks' GIF proxies - - [x] Recieving - - [x] Plain text - - [x] Per message profiles - - [x] HTML - - [x] Replies - - [x] Viewing - - [ ] Jump to original message - - [x] In loaded timeline - - [ ] Out of loaded timeline - - [x] Edits - - [x] Attachments - - [x] Unencrypted - - [ ] Encrypted - - [x] Blurhashing - - [ ] Downloading attachments - - [x] Opening attachments in their own view - - [ ] Polls: Waiting on https://github.com/SwanFlutter/dynamic_polls/issues/1 - - [x] Mentions - - [x] Users - - [x] Rooms - - [ ] Plain text (not sure if I want to add this or not, I probably won't unless there's interest) - - [x] Matrix URIs - - [x] Matrix.to links - - [ ] Do some fancy fetching to get nice names - - [ ] Make clickable - - [x] Custom emojis/stickers - - [x] History loading - - [x] Backwards - - [ ] Forwards - - [x] Editing - - [x] Deleting -- [ ] Reactions: Waiting on https://github.com/flyerhq/flutter_chat_ui/pull/838 or me doing a custom impl -- [ ] Pins - - [ ] Displaying - - [ ] Creating -- [ ] Threads -- [ ] Profile popouts -- [ ] Copy link to [room, space] -- [ ] Reporting - - [x] Events - - [ ] Rooms -- [ ] Notifications using UnifiedPush -- [ ] Group calls using [MSC4195](https://github.com/matrix-org/matrix-spec-proposals/pull/4195) -- [ ] Invites -- [ ] Settings - - [ ] Light/Dark mode - - [ ] SSD or CSD - - [ ] Show media by default - - [ ] Dynamic Theming - - [ ] Devices - - [ ] Viewing devices - - [ ] Verifying devices - - [ ] URL preview: Server / Client / None - - [ ] Account changes - - [ ] Display name - - [ ] Profile picture - - [ ] Timezone - - [ ] Pronouns - - [ ] Password - - [ ] About - - [x] Log Out +- [ ] New logo +- [ ] Make context menus appear as bottom sheets on mobile +- [x] Move from the Dart SDK to the Gomuks SDK with Dart bindings: https://git.federated.nexus/Henry-Hiles/nexus/pulls/2 + - [ ] Allow using remote gomuks over websocket +- [ ] Platform Support + - [x] Linux + - [x] Windows + - [ ] MacOS + - [ ] Android + - [ ] iOS + - [ ] Web (may not be possible) +- [x] Login + - [x] Username / password auth + - [ ] OAuth / OIDC + - [x] Improve initial sync experience +- [x] Rooms / Spaces + - [x] Displaying and choosing + - [x] Reading, showing unread + - [x] Mark as read button on rooms and spaces + - [ ] Searching + - [ ] Creating (Rooms, Spaces, and DMs) + - [x] Joining + - [ ] Parse vias + - [x] Using a text/uri/link + - [x] Plain text + - [x] `matrix:` Uri + - [x] Matrix.to link + - [ ] From space + - [ ] Exploring + - [x] Leaving + - [x] Subspaces +- [x] Messages + - [x] Encryption + - [x] Restoring crypto identity from a recovery passphrase/key + - [x] Sending + - [x] Plain text + - [x] HTML/Markdown + - [x] Replies + - [x] Choose ping on/off + - [ ] Attachments + - [ ] Commands with [MSC4391](https://github.com/matrix-org/matrix-spec-proposals/pull/4391) + - [x] Mentions + - [x] Users + - [x] Rooms + - [ ] Inline emoji picker (Putting this here since it'll be implemented the same way as mentions) + - [ ] Custom emojis/stickers + - [ ] GIFs using Gomuks' GIF proxies + - [x] Recieving + - [x] Plain text + - [x] HTML + - [x] Replies + - [x] Viewing + - [ ] Jump to original message + - [x] In loaded timeline + - [ ] Out of loaded timeline + - [x] Edits + - [x] Attachments + - [x] Unencrypted + - [ ] Encrypted + - [x] Blurhashing + - [ ] Downloading attachments + - [x] Opening attachments in their own view + - [ ] Polls: Waiting on https://github.com/SwanFlutter/dynamic_polls/issues/1 + - [x] Mentions + - [x] Users + - [x] Rooms + - [ ] Plain text (not sure if I want to add this or not, I probably won't unless there's interest) + - [x] Matrix URIs + - [x] Matrix.to links + - [ ] Do some fancy fetching to get nice names + - [ ] Make clickable + - [x] Custom emojis/stickers + - [x] History loading + - [x] Backwards + - [ ] Forwards + - [x] Editing + - [x] Deleting +- [ ] Reactions: Waiting on https://github.com/flyerhq/flutter_chat_ui/pull/838 or me doing a custom impl +- [ ] Pins + - [ ] Displaying + - [ ] Creating +- [ ] Threads +- [ ] Profile popouts +- [ ] Copy link to [room, space] +- [ ] Reporting + - [x] Events + - [ ] Rooms +- [ ] Notifications using UnifiedPush +- [ ] Group calls using [MSC4195](https://github.com/matrix-org/matrix-spec-proposals/pull/4195) +- [ ] Invites +- [ ] Settings + - [ ] Light/Dark mode + - [ ] SSD or CSD + - [ ] Show media by default + - [ ] Dynamic Theming + - [ ] Devices + - [ ] Viewing devices + - [ ] Verifying devices + - [ ] URL preview: Server / Client / None + - [ ] Account changes + - [ ] Display name + - [ ] Profile picture + - [ ] Timezone + - [ ] Pronouns + - [ ] Password + - [ ] About + - [x] Log Out ## Build Instructions @@ -138,8 +136,8 @@ cd nexus #### Linux -- With Nix: Either use direnv, or `nix flake develop` -- Without Nix: Install Flutter, Go, Olm, Git, Clang, and GLibc. +- With Nix: Either use direnv, or `nix flake develop` +- Without Nix: Install Flutter, Go, Olm, Git, Clang, and GLibc. #### Windows / MacOS diff --git a/lib/controllers/author_controller.dart b/lib/controllers/author_controller.dart deleted file mode 100644 index fac80e5..0000000 --- a/lib/controllers/author_controller.dart +++ /dev/null @@ -1,44 +0,0 @@ -import "dart:async"; -import "package:collection/collection.dart"; -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/members_controller.dart"; -import "package:nexus/models/configs/author_config.dart"; -import "package:nexus/models/membership.dart"; - -class AuthorController extends AsyncNotifier { - final AuthorConfig config; - AuthorController(this.config); - - @override - Future build() async { - var member = ref.watch( - MembersController.provider(config.room).select( - (value) => value.firstWhereOrNull( - (membership) => membership.userId == config.message.authorId, - ), - ), - ); - - final pmp = config.message.metadata?["pmp"] == null - ? null - : Membership.fromContent( - IMap(config.message.metadata?["pmp"]), - config.message.authorId, - ); - - return Membership( - avatarUrl: pmp?.avatarUrl ?? member?.avatarUrl, - displayName: - pmp?.displayName ?? - member?.displayName ?? - config.message.authorId.substring(1).split(":").first, - userId: config.message.authorId, - ); - } - - static final provider = AsyncNotifierProvider.family - .autoDispose( - AuthorController.new, - ); -} diff --git a/lib/controllers/members_controller.dart b/lib/controllers/members_controller.dart index 90d9b76..268a30d 100644 --- a/lib/controllers/members_controller.dart +++ b/lib/controllers/members_controller.dart @@ -1,53 +1,25 @@ import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/client_controller.dart"; import "package:nexus/models/event.dart"; -import "package:nexus/models/membership.dart"; -import "package:nexus/models/requests/get_room_state_request.dart"; import "package:nexus/models/room.dart"; -class MembersController extends Notifier> { +class MembersController extends Notifier> { final Room room; MembersController(this.room); @override - IList build() { - IList membersFromState(IList members) => members.nonNulls - .where((member) => member.content["membership"] == "join") - .map( - (membership) => - Membership.fromContent(membership.content, membership.stateKey!), - ) - .toIList(); + IList build() => (room.state["m.room.member"]?.values ?? []) + .map( + (eventRowId) => + room.events.firstWhereOrNull((event) => event.rowId == eventRowId), + ) + .nonNulls + .where((member) => member.content["membership"] == "join") + .toIList(); - if (room.metadata != null) { - ref - .watch(ClientController.provider.notifier) - .getRoomState( - GetRoomStateRequest( - roomId: room.metadata!.id, - fetchMembers: room.metadata!.hasMemberList == false, - includeMembers: true, - ), - ) - .then((value) => state = membersFromState(value)); - } - - return membersFromState( - (room.state["m.room.members"]?.values ?? []) - .map( - (eventRowId) => room.events.firstWhereOrNull( - (event) => event.rowId == eventRowId, - ), - ) - .nonNulls - .toIList(), - ); - } - - static final provider = - NotifierProvider.family, Room>( + static final provider = NotifierProvider.family + .autoDispose, Room>( MembersController.new, ); } diff --git a/lib/controllers/message_controller.dart b/lib/controllers/message_controller.dart index d84aabb..f3ef13b 100644 --- a/lib/controllers/message_controller.dart +++ b/lib/controllers/message_controller.dart @@ -2,8 +2,9 @@ import "package:collection/collection.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client_state_controller.dart"; +import "package:nexus/controllers/members_controller.dart"; import "package:nexus/helpers/extensions/mxc_to_https.dart"; -import "package:nexus/models/configs/message_config.dart"; +import "package:nexus/models/message_config.dart"; class MessageController extends AsyncNotifier { final MessageConfig config; @@ -26,6 +27,12 @@ class MessageController extends AsyncNotifier { if (!ref.mounted) return null; + final members = ref.read(MembersController.provider(config.room)); + final author = members.firstWhereOrNull( + (member) => member.stateKey == event.authorId, + ); + if (!ref.mounted) return null; + final content = (event.decrypted ?? event.content); final type = (config.event.decryptedType ?? config.event.type); final newContent = content["m.new_content"] as Map?; @@ -45,11 +52,14 @@ class MessageController extends AsyncNotifier { "timelineId": event.timelineRowId, "big": event.localContent?.bigEmoji == true, "eventType": type, - "pmp": event.content["com.beeper.per_message_profile"], + "avatarUrl": author?.content["avatar_url"], "editSource": event.localContent?.editSource ?? newContent?["body"] ?? content["body"], + "displayName": author?.content["displayname"]?.isNotEmpty == true + ? author?.content["displayname"] + : event.authorId.substring(1).split(":")[0], "txnId": config.event.transactionId, }; diff --git a/lib/controllers/messages_controller.dart b/lib/controllers/messages_controller.dart index 28885fb..83bd815 100644 --- a/lib/controllers/messages_controller.dart +++ b/lib/controllers/messages_controller.dart @@ -2,8 +2,8 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/message_controller.dart"; -import "package:nexus/models/configs/message_config.dart"; -import "package:nexus/models/configs/messages_config.dart"; +import "package:nexus/models/message_config.dart"; +import "package:nexus/models/messages_config.dart"; class MessagesController extends AsyncNotifier> { final MessagesConfig config; diff --git a/lib/controllers/room_chat_controller.dart b/lib/controllers/room_chat_controller.dart index d737154..4a4dba2 100644 --- a/lib/controllers/room_chat_controller.dart +++ b/lib/controllers/room_chat_controller.dart @@ -1,4 +1,5 @@ import "dart:async"; + import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; @@ -10,8 +11,8 @@ import "package:nexus/controllers/message_controller.dart"; import "package:nexus/controllers/messages_controller.dart"; import "package:nexus/controllers/new_events_controller.dart"; import "package:nexus/controllers/rooms_controller.dart"; -import "package:nexus/models/configs/messages_config.dart"; -import "package:nexus/models/configs/message_config.dart"; +import "package:nexus/models/message_config.dart"; +import "package:nexus/models/messages_config.dart"; import "package:nexus/models/requests/get_room_state_request.dart"; import "package:nexus/models/requests/paginate_request.dart"; import "package:nexus/models/requests/redact_event_request.dart"; @@ -30,7 +31,11 @@ class RoomChatController extends AsyncNotifier { if (room == null) return InMemoryChatController(); final state = await client.getRoomState( - GetRoomStateRequest(roomId: roomId), + GetRoomStateRequest( + roomId: roomId, + fetchMembers: room.metadata?.hasMemberList == false, + includeMembers: true, + ), ); ref diff --git a/lib/controllers/rooms_controller.dart b/lib/controllers/rooms_controller.dart index 3c6e287..0945644 100644 --- a/lib/controllers/rooms_controller.dart +++ b/lib/controllers/rooms_controller.dart @@ -36,7 +36,6 @@ class RoomsController extends Notifier> { return acc.add( roomId, existing?.copyWith( - hasMore: incoming.hasMore, metadata: incoming.metadata ?? existing.metadata, events: events!, state: incoming.state.entries.fold( diff --git a/lib/models/configs/author_config.dart b/lib/models/configs/author_config.dart deleted file mode 100644 index af63c63..0000000 --- a/lib/models/configs/author_config.dart +++ /dev/null @@ -1,14 +0,0 @@ -import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:freezed_annotation/freezed_annotation.dart"; -import "package:nexus/models/room.dart"; -part "author_config.freezed.dart"; -part "author_config.g.dart"; - -@freezed -abstract class AuthorConfig with _$AuthorConfig { - const factory AuthorConfig({required Message message, required Room room}) = - _AuthorConfig; - - factory AuthorConfig.fromJson(Map json) => - _$AuthorConfigFromJson(json); -} diff --git a/lib/models/membership.dart b/lib/models/membership.dart deleted file mode 100644 index ec18be7..0000000 --- a/lib/models/membership.dart +++ /dev/null @@ -1,22 +0,0 @@ -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:freezed_annotation/freezed_annotation.dart"; -part "membership.freezed.dart"; - -@freezed -abstract class Membership with _$Membership { - const Membership._(); - const factory Membership({ - required Uri? avatarUrl, - required String displayName, - required String userId, - }) = _Membership; - - factory Membership.fromContent( - IMap content, - String userId, - ) => Membership( - avatarUrl: Uri.tryParse(content["avatar_url"] ?? ""), - userId: userId, - displayName: content["displayname"] ?? userId.substring(1).split(":").first, - ); -} diff --git a/lib/models/configs/message_config.dart b/lib/models/message_config.dart similarity index 100% rename from lib/models/configs/message_config.dart rename to lib/models/message_config.dart diff --git a/lib/models/configs/messages_config.dart b/lib/models/messages_config.dart similarity index 100% rename from lib/models/configs/messages_config.dart rename to lib/models/messages_config.dart diff --git a/lib/models/requests/get_room_state_request.dart b/lib/models/requests/get_room_state_request.dart index de66b72..a154d5f 100644 --- a/lib/models/requests/get_room_state_request.dart +++ b/lib/models/requests/get_room_state_request.dart @@ -6,7 +6,7 @@ part "get_room_state_request.g.dart"; abstract class GetRoomStateRequest with _$GetRoomStateRequest { const factory GetRoomStateRequest({ required String roomId, - @Default(false) bool fetchMembers, + required bool fetchMembers, @Default(false) bool includeMembers, }) = _GetRoomStateRequest; diff --git a/lib/widgets/chat_page/chat_box.dart b/lib/widgets/chat_page/chat_box.dart index c1b8c5e..b9e7dbb 100644 --- a/lib/widgets/chat_page/chat_box.dart +++ b/lib/widgets/chat_page/chat_box.dart @@ -86,11 +86,10 @@ class ChatBox extends HookConsumerWidget { child: Column( children: [ RelationPreview( - relatedMessage, - room: room, shouldMention: shouldMention.value, toggleShouldMention: () => shouldMention.value = !shouldMention.value, + relatedMessage: relatedMessage, relationType: relationType, onDismiss: onDismiss, ), diff --git a/lib/widgets/chat_page/html/html.dart b/lib/widgets/chat_page/html/html.dart index dcc1d49..1e1ab82 100644 --- a/lib/widgets/chat_page/html/html.dart +++ b/lib/widgets/chat_page/html/html.dart @@ -22,10 +22,6 @@ class Html extends ConsumerWidget { html, textStyle: textStyle, customWidgetBuilder: (element) { - if (element.attributes.keys.contains("data-mx-profile-fallback")) { - return SizedBox.shrink(); - } - if (element.attributes.keys.contains("data-mx-spoiler")) { return InlineCustomWidget(child: SpoilerText(text: element.text)); } diff --git a/lib/widgets/chat_page/lazy_loading/message_avatar.dart b/lib/widgets/chat_page/lazy_loading/message_avatar.dart deleted file mode 100644 index 71fcf84..0000000 --- a/lib/widgets/chat_page/lazy_loading/message_avatar.dart +++ /dev/null @@ -1,30 +0,0 @@ -import "package:flutter/widgets.dart"; -import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/author_controller.dart"; -import "package:nexus/helpers/extensions/better_when.dart"; -import "package:nexus/models/configs/author_config.dart"; -import "package:nexus/models/room.dart"; -import "package:nexus/widgets/avatar_or_hash.dart"; - -class MessageAvatar extends ConsumerWidget { - final Message message; - final Room room; - final double height; - const MessageAvatar(this.message, this.room, {this.height = 16, super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) => ref - .watch( - AuthorController.provider(AuthorConfig(room: room, message: message)), - ) - .betterWhen( - data: (membership) => AvatarOrHash( - membership.avatarUrl, - membership.displayName, - height: height, - ), - loading: () => - AvatarOrHash(null, message.authorId.substring(1), height: height), - ); -} diff --git a/lib/widgets/chat_page/lazy_loading/message_displayname.dart b/lib/widgets/chat_page/lazy_loading/message_displayname.dart deleted file mode 100644 index 7053655..0000000 --- a/lib/widgets/chat_page/lazy_loading/message_displayname.dart +++ /dev/null @@ -1,28 +0,0 @@ -import "package:flutter/widgets.dart"; -import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/author_controller.dart"; -import "package:nexus/helpers/extensions/better_when.dart"; -import "package:nexus/models/configs/author_config.dart"; -import "package:nexus/models/room.dart"; - -class MessageDisplayname extends ConsumerWidget { - final Message message; - final Room room; - final TextStyle? style; - const MessageDisplayname(this.message, this.room, {this.style, super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) => ref - .watch( - AuthorController.provider(AuthorConfig(room: room, message: message)), - ) - .betterWhen( - data: (membership) => Text( - "${membership.displayName} ${message.metadata?["pmp"] == null ? "" : "(via ${message.authorId})"}", - style: style, - overflow: TextOverflow.ellipsis, - ), - loading: SizedBox.shrink, - ); -} diff --git a/lib/widgets/chat_page/member_list.dart b/lib/widgets/chat_page/member_list.dart index 08785c6..24d22e4 100644 --- a/lib/widgets/chat_page/member_list.dart +++ b/lib/widgets/chat_page/member_list.dart @@ -36,9 +36,18 @@ class MemberList extends ConsumerWidget { builder: (context) => Dialog(child: Text("TODO: Open member popover")), ), - leading: AvatarOrHash(member.avatarUrl, member.displayName), - title: Text(member.displayName, overflow: TextOverflow.ellipsis), - subtitle: Text(member.userId, overflow: TextOverflow.ellipsis), + leading: AvatarOrHash( + Uri.tryParse(member.content["avatar_url"] ?? ""), + member.content["displayname"].toString(), + ), + title: Text( + member.content["displayname"].toString(), + overflow: TextOverflow.ellipsis, + ), + subtitle: Text( + member.stateKey ?? "Unknown User", + overflow: TextOverflow.ellipsis, + ), ), ), ], diff --git a/lib/widgets/chat_page/mention_overlay.dart b/lib/widgets/chat_page/mention_overlay.dart index 75473f0..9858574 100644 --- a/lib/widgets/chat_page/mention_overlay.dart +++ b/lib/widgets/chat_page/mention_overlay.dart @@ -40,29 +40,39 @@ class MentionOverlay extends ConsumerWidget { ? members : members.where( (member) => - member.userId.toLowerCase().contains( + member.stateKey?.toLowerCase().contains( query.toLowerCase(), ) == true || - member.displayName.toLowerCase().contains( - query.toLowerCase(), - ) == + (member.content["displayname"] as String?) + ?.toLowerCase() + .contains(query.toLowerCase()) == true, )) .map( (member) => ListTile( leading: AvatarOrHash( - member.avatarUrl, - member.displayName, + Uri.tryParse( + member.content["avatar_url"] ?? "", + ), + member.content["displayname"] ?? "", ), - title: Text(member.displayName), - subtitle: Text(member.userId), + title: Text( + member.content["displayname"] as String? ?? + member.stateKey ?? + "Unknown User", + ), + subtitle: member.stateKey != null + ? Text(member.stateKey!) + : null, onTap: () => addTag( - id: "[@${member.displayName}](https://matrix.to/#/${member.userId})", - name: member.userId - .substring(1) - .split(":") - .first, + id: "[@${member.content["displayname"]}](https://matrix.to/#/${member.stateKey})", + name: + member.stateKey + ?.substring(1) + .split(":") + .first ?? + "Unknown User", ), ), ) diff --git a/lib/widgets/chat_page/message_wrapper.dart b/lib/widgets/chat_page/message_wrapper.dart index 1be6c2b..da53be0 100644 --- a/lib/widgets/chat_page/message_wrapper.dart +++ b/lib/widgets/chat_page/message_wrapper.dart @@ -1,21 +1,12 @@ import "package:flutter/material.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:nexus/models/room.dart"; -import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart"; -import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart"; +import "package:nexus/widgets/avatar_or_hash.dart"; class MessageWrapper extends StatelessWidget { final Message message; final Widget child; - final Room room; final MessageGroupStatus? groupStatus; - const MessageWrapper( - this.message, - this.child, - this.groupStatus, - this.room, { - super.key, - }); + const MessageWrapper(this.message, this.child, this.groupStatus, {super.key}); @override Widget build(BuildContext context) => ClipRRect( @@ -33,7 +24,11 @@ class MessageWrapper extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ groupStatus?.isFirst != false - ? MessageAvatar(message, room, height: 40) + ? AvatarOrHash( + Uri.parse(message.metadata?["avatarUrl"] ?? ""), + height: 40, + message.metadata?["displayName"] ?? "", + ) : SizedBox(width: 40), Expanded( child: Column( @@ -41,9 +36,9 @@ class MessageWrapper extends StatelessWidget { spacing: 4, children: [ if (groupStatus?.isFirst != false) - MessageDisplayname( - message, - room, + Text( + message.metadata?["displayName"] ?? message.authorId, + overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), diff --git a/lib/widgets/chat_page/relation_preview.dart b/lib/widgets/chat_page/relation_preview.dart index 7fded20..7aa3ae8 100644 --- a/lib/widgets/chat_page/relation_preview.dart +++ b/lib/widgets/chat_page/relation_preview.dart @@ -2,9 +2,7 @@ import "package:flutter/material.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/models/relation_type.dart"; -import "package:nexus/models/room.dart"; -import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart"; -import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart"; +import "package:nexus/widgets/avatar_or_hash.dart"; class RelationPreview extends ConsumerWidget { final Message? relatedMessage; @@ -12,11 +10,8 @@ class RelationPreview extends ConsumerWidget { final VoidCallback onDismiss; final bool shouldMention; final VoidCallback toggleShouldMention; - final Room room; - - const RelationPreview( - this.relatedMessage, { - required this.room, + const RelationPreview({ + required this.relatedMessage, required this.relationType, required this.onDismiss, required this.shouldMention, @@ -41,10 +36,14 @@ class RelationPreview extends ConsumerWidget { "Editing message:", style: TextStyle(fontWeight: FontWeight.bold), ), - MessageAvatar(relatedMessage!, room), - MessageDisplayname( - relatedMessage!, - room, + AvatarOrHash( + Uri.tryParse(relatedMessage?.metadata?["avatarUrl"] ?? ""), + relatedMessage?.metadata?["displayName"]?.toString() ?? "", + height: 16, + ), + Text( + relatedMessage!.metadata?["displayName"] ?? + relatedMessage!.authorId, style: theme.textTheme.labelMedium?.copyWith( fontWeight: FontWeight.bold, ), diff --git a/lib/widgets/chat_page/reply_widget.dart b/lib/widgets/chat_page/reply_widget.dart index b9fa2e1..cd30acc 100644 --- a/lib/widgets/chat_page/reply_widget.dart +++ b/lib/widgets/chat_page/reply_widget.dart @@ -1,15 +1,15 @@ +import "dart:math"; import "package:flutter/material.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/event_controller.dart"; import "package:nexus/controllers/message_controller.dart"; import "package:nexus/helpers/extensions/better_when.dart"; -import "package:nexus/models/configs/message_config.dart"; +import "package:nexus/models/message_config.dart"; import "package:nexus/models/requests/get_event_request.dart"; import "package:nexus/models/room.dart"; +import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/chat_page/html/quoted.dart"; -import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart"; -import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart"; typedef OnTapReply = void Function(Message message)?; @@ -61,28 +61,73 @@ class ReplyWidget extends ConsumerWidget { return SizedBox.shrink(); } + final smallerText = + message is TextMessage && + replyMessage.metadata?["body"] != null + ? replyMessage.metadata!["body"].substring( + 0, + min( + max( + max( + (message as TextMessage) + .text + .length - + (replyMessage + .metadata?["displayName"] + as String) + .length - + 5, + message + .metadata?["displayName"] + .length, + ), + 5, + ), + replyMessage.metadata!["body"].length, + ), + ) + : null; + final replyText = + (smallerText == null || + smallerText.length == + replyMessage + .metadata!["body"] + .length) + ? replyMessage.metadata!["body"] + : "$smallerText..."; + return InkWell( onTap: () => onTapReply?.call(replyMessage), child: Row( mainAxisSize: MainAxisSize.min, spacing: 8, children: [ - MessageAvatar(replyMessage, room), + AvatarOrHash( + Uri.tryParse( + replyMessage.metadata?["avatarUrl"] ?? + "", + ), + replyMessage.metadata?["displayName"] ?? + "", + height: 16, + ), Flexible( - child: MessageDisplayname( - replyMessage, - room, + child: Text( + replyMessage + .metadata?["displayName"] ?? + replyMessage.authorId, style: Theme.of(context) .textTheme .labelMedium ?.copyWith( fontWeight: FontWeight.bold, ), + overflow: TextOverflow.ellipsis, ), ), Flexible( child: Text( - replyMessage.metadata!["body"], + replyText, overflow: TextOverflow.ellipsis, style: Theme.of( context, diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart index 1627573..839109f 100644 --- a/lib/widgets/chat_page/room_chat.dart +++ b/lib/widgets/chat_page/room_chat.dart @@ -320,7 +320,6 @@ class RoomChat extends HookConsumerWidget { ), ), groupStatus, - room, ), systemMessageBuilder: diff --git a/lib/widgets/chat_page/text_message_wrapper.dart b/lib/widgets/chat_page/text_message_wrapper.dart index 814deb4..9734a34 100644 --- a/lib/widgets/chat_page/text_message_wrapper.dart +++ b/lib/widgets/chat_page/text_message_wrapper.dart @@ -109,7 +109,6 @@ class TextMessageWrapper extends StatelessWidget { ), ), groupStatus, - room, ); } }