From 099725063f1295904d55b41fa5b3ef67241b82dd Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Fri, 30 Jan 2026 16:50:25 +0100 Subject: [PATCH] Working images --- lib/controllers/avatar_controller.dart | 17 --- lib/controllers/members_controller.dart | 1 + .../message_controller.dart} | 114 +++++++++++------- lib/controllers/messages_controller.dart | 25 ++++ lib/controllers/room_chat_controller.dart | 41 ++++--- lib/controllers/thumbnail_controller.dart | 22 ---- lib/helpers/extensions/list_to_messages.dart | 10 -- lib/helpers/extensions/mxc_to_https.dart | 4 + lib/models/client_state.dart | 1 + lib/models/message_config.dart | 16 +++ lib/widgets/avatar_or_hash.dart | 32 +++-- lib/widgets/chat_page/html/html.dart | 80 +++++------- lib/widgets/chat_page/html/mention_chip.dart | 2 +- lib/widgets/chat_page/member_list.dart | 85 ++++++------- lib/widgets/chat_page/mention_overlay.dart | 30 ++--- lib/widgets/chat_page/room_appbar.dart | 14 +-- lib/widgets/chat_page/room_chat.dart | 4 +- lib/widgets/chat_page/sidebar.dart | 12 +- lib/widgets/chat_page/top_widget.dart | 17 +-- 19 files changed, 270 insertions(+), 257 deletions(-) delete mode 100644 lib/controllers/avatar_controller.dart rename lib/{helpers/extensions/event_to_message.dart => controllers/message_controller.dart} (58%) create mode 100644 lib/controllers/messages_controller.dart delete mode 100644 lib/controllers/thumbnail_controller.dart delete mode 100644 lib/helpers/extensions/list_to_messages.dart create mode 100644 lib/helpers/extensions/mxc_to_https.dart create mode 100644 lib/models/message_config.dart diff --git a/lib/controllers/avatar_controller.dart b/lib/controllers/avatar_controller.dart deleted file mode 100644 index 1bb4c72..0000000 --- a/lib/controllers/avatar_controller.dart +++ /dev/null @@ -1,17 +0,0 @@ -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:matrix/matrix.dart"; -import "package:nexus/controllers/client_controller.dart"; - -class AvatarController extends AsyncNotifier { - final String mxc; - AvatarController(this.mxc); - @override - Future build() async => Uri.parse(mxc).getThumbnailUri( - await ref.watch(ClientController.provider.future), - width: 24, - height: 24, - ); - - static final provider = AsyncNotifierProvider.family - .autoDispose(AvatarController.new); -} diff --git a/lib/controllers/members_controller.dart b/lib/controllers/members_controller.dart index 5f88f2b..2a250a2 100644 --- a/lib/controllers/members_controller.dart +++ b/lib/controllers/members_controller.dart @@ -17,6 +17,7 @@ class MembersController extends AsyncNotifier> { ), ) .nonNulls + .where((member) => member.content["membership"] == "join") .toIList(); static final provider = AsyncNotifierProvider.family diff --git a/lib/helpers/extensions/event_to_message.dart b/lib/controllers/message_controller.dart similarity index 58% rename from lib/helpers/extensions/event_to_message.dart rename to lib/controllers/message_controller.dart index 231774b..b695ec1 100644 --- a/lib/helpers/extensions/event_to_message.dart +++ b/lib/controllers/message_controller.dart @@ -1,41 +1,51 @@ import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client_controller.dart"; +import "package:nexus/controllers/client_state_controller.dart"; import "package:nexus/controllers/profile_controller.dart"; -import "package:nexus/models/event.dart"; +import "package:nexus/helpers/extensions/mxc_to_https.dart"; +import "package:nexus/models/message_config.dart"; import "package:nexus/models/requests/get_event_request.dart"; import "package:nexus/models/requests/get_related_events_request.dart"; -extension EventToMessage on Event { - Future toMessage( - Ref ref, { - bool mustBeText = false, - bool includeEdits = false, - }) async { - if (relationType == "m.replace" && !includeEdits) return null; +class MessageController extends AsyncNotifier { + final MessageConfig config; + MessageController(this.config); + + @override + Future build() async { + if (config.event.relationType == "m.replace" && !config.includeEdits) { + return null; + } final client = ref.watch(ClientController.provider.notifier); final newEvents = await client.getRelatedEvents( GetRelatedEventsRequest( - roomId: roomId, - eventId: eventId, + roomId: config.event.roomId, + eventId: config.event.eventId, relationType: "m.replace", ), ); - final event = newEvents?.lastOrNull ?? this; + if (!ref.mounted) return null; + final event = newEvents?.lastOrNull ?? config.event; - final replyId = this.content["m.relates_to"]?["m.in_reply_to"]?["event_id"]; + final replyId = + config.event.content["m.relates_to"]?["m.in_reply_to"]?["event_id"]; final replyEvent = replyId == null ? null : await client.getEvent( - GetEventRequest(roomId: roomId, eventId: replyId), + GetEventRequest(roomId: config.event.roomId, eventId: replyId), ); - final author = await ref.watch( + if (!ref.mounted) return null; + + final author = await ref.read( ProfileController.provider(event.authorId).future, ); - final content = (decrypted ?? this.content); - final type = (decryptedType ?? this.type); + 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?; final metadata = { "timelineId": event.timelineRowId, @@ -45,18 +55,25 @@ extension EventToMessage on Event { content["formatted_body"] ?? content["body"] ?? "", - "reply": await replyEvent?.toMessage(ref, mustBeText: true), + if (replyEvent != null) + "reply": await ref.read( + MessageController.provider( + MessageConfig(event: replyEvent, mustBeText: true), + ).future, + ), "body": newContent?["body"] ?? content["body"], "eventType": type, "avatarUrl": author.avatarUrl, - "displayName": author.displayName ?? authorId, - "txnId": transactionId, + "displayName": author.displayName ?? event.authorId, + "txnId": config.event.transactionId, }; + if (!ref.mounted) return null; + final editedAt = event.relationType == "m.replace" ? event.timestamp : null; - if ((event.redactedBy != null && !mustBeText) || - (!includeEdits && (relationType == "m.replace"))) { + if ((event.redactedBy != null && !config.mustBeText) || + (!config.includeEdits && (config.event.relationType == "m.replace"))) { return null; } @@ -69,18 +86,24 @@ extension EventToMessage on Event { final asText = Message.text( metadata: metadata, - id: eventId, - authorId: authorId, - text: redactedBy == null + id: config.event.eventId, + authorId: event.authorId, + text: config.event.redactedBy == null ? content["body"] ?? "" : "This message has been deleted...", replyToMessageId: replyId, - deliveredAt: timestamp, + deliveredAt: config.event.timestamp, editedAt: editedAt, ) as TextMessage; - if (mustBeText) return asText; + if (config.mustBeText) return asText; + + final homeserver = ref.read(ClientStateController.provider)?.homeserverUrl; + final source = homeserver == null || content["url"] == null + ? "null" + : Uri.parse(content["url"]).mxcToHttps(homeserver).toString(); + return switch (type) { "m.room.encrypted" => asText.copyWith( text: "Unable to decrypt message.", @@ -98,42 +121,42 @@ extension EventToMessage on Event { // ), ("m.sticker" || "m.room.message") => switch (content["msgtype"]) { ("m.sticker" || "m.image") => Message.image( - id: eventId, + id: config.event.eventId, metadata: metadata, - authorId: authorId, + authorId: event.authorId, text: event.localContent?.sanitizedHtml, - source: "(await getAttachmentUri()).toString()", // TODO + source: source, replyToMessageId: replyId, - deliveredAt: timestamp, + deliveredAt: config.event.timestamp, blurhash: (content["info"] as Map?)?["xyz.amorgan.blurhash"], ), "m.audio" => Message.audio( - id: eventId, + id: config.event.eventId, metadata: metadata, - authorId: authorId, + authorId: event.authorId, text: content["body"], replyToMessageId: replyId, - source: "(await event.getAttachmentUri()).toString()", // TODO - deliveredAt: timestamp, + source: source, + deliveredAt: config.event.timestamp, // TODO: See if we can figure out duration duration: Duration(hours: 1), ), "m.file" => Message.file( name: content["filename"].toString(), metadata: metadata, - id: eventId, - authorId: authorId, - source: "(await event.getAttachmentUri()).toString()", // TODO + id: config.event.eventId, + authorId: event.authorId, + source: source, replyToMessageId: replyId, - deliveredAt: timestamp, + deliveredAt: config.event.timestamp, ), _ => asText, }, "m.room.member" => Message.system( metadata: metadata, - id: eventId, - authorId: authorId, - deliveredAt: timestamp, + id: config.event.eventId, + authorId: event.authorId, + deliveredAt: config.event.timestamp, text: "${content["displayname"] ?? event.stateKey} ${switch (content["membership"]) { "invite" => "was invited to", @@ -151,11 +174,16 @@ extension EventToMessage on Event { // ignore: dead_code ? Message.unsupported( metadata: metadata, - id: eventId, - authorId: authorId, + id: config.event.eventId, + authorId: event.authorId, replyToMessageId: replyId, ) : null, }; } + + static final provider = AsyncNotifierProvider.family + .autoDispose( + MessageController.new, + ); } diff --git a/lib/controllers/messages_controller.dart b/lib/controllers/messages_controller.dart new file mode 100644 index 0000000..3edc8ab --- /dev/null +++ b/lib/controllers/messages_controller.dart @@ -0,0 +1,25 @@ +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/event.dart"; +import "package:nexus/models/message_config.dart"; + +class MessagesController extends AsyncNotifier> { + final IList events; + MessagesController(this.events); + + @override + Future> build() async => (await Future.wait( + events.map( + (event) => ref.watch( + MessageController.provider(MessageConfig(event: event)).future, + ), + ), + )).nonNulls.toIList(); + + static final provider = AsyncNotifierProvider.family + .autoDispose, IList>( + MessagesController.new, + ); +} diff --git a/lib/controllers/room_chat_controller.dart b/lib/controllers/room_chat_controller.dart index 791a63c..3fa5c7f 100644 --- a/lib/controllers/room_chat_controller.dart +++ b/lib/controllers/room_chat_controller.dart @@ -3,13 +3,14 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_chat_core/flutter_chat_core.dart" as chat; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:fluttertagger/fluttertagger.dart" as tagger; +import "package:fluttertagger/fluttertagger.dart"; import "package:nexus/controllers/client_controller.dart"; +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/controllers/selected_room_controller.dart"; -import "package:nexus/helpers/extensions/event_to_message.dart"; -import "package:nexus/helpers/extensions/list_to_messages.dart"; +import "package:nexus/models/message_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"; @@ -27,15 +28,19 @@ class RoomChatController extends AsyncNotifier { final room = ref.read(SelectedRoomController.provider); if (room == null) return InMemoryChatController(); - final messages = await room.timeline - .map( - (timelineRowTuple) => room.events.firstWhereOrNull( - (event) => event.rowId == timelineRowTuple.eventRowId, - ), - ) - .nonNulls - .toMessages(ref); - final controller = InMemoryChatController(messages: messages); + final messages = await ref.watch( + MessagesController.provider( + room.timeline + .map( + (timelineRowTuple) => room.events.firstWhereOrNull( + (event) => event.rowId == timelineRowTuple.eventRowId, + ), + ) + .nonNulls + .toIList(), + ).future, + ); + final controller = InMemoryChatController(messages: messages.toList()); ref.onDispose( ref.listen(NewEventsController.provider(roomId), (_, next) async { @@ -49,7 +54,11 @@ class RoomChatController extends AsyncNotifier { await controller.removeMessage(message); } else { - final message = await event.toMessage(ref, includeEdits: true); + final message = await ref.read( + MessageController.provider( + MessageConfig(event: event, includeEdits: true), + ).future, + ); if (event.relationType == "m.replace") { final controller = await future; final oldMessage = controller.messages.firstWhereOrNull( @@ -180,7 +189,9 @@ class RoomChatController extends AsyncNotifier { const ISet.empty(), ); - final messages = await response.events.reversed.toMessages(ref); + final messages = await ref.watch( + MessagesController.provider(response.events.reversed).future, + ); await controller.insertAllMessages( messages .where( @@ -198,7 +209,7 @@ class RoomChatController extends AsyncNotifier { Future send( String message, { - required Iterable tags, + required Iterable tags, required RelationType relationType, Message? relation, }) async { diff --git a/lib/controllers/thumbnail_controller.dart b/lib/controllers/thumbnail_controller.dart deleted file mode 100644 index 4500523..0000000 --- a/lib/controllers/thumbnail_controller.dart +++ /dev/null @@ -1,22 +0,0 @@ -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:matrix/matrix.dart"; -import "package:nexus/controllers/client_controller.dart"; -import "package:nexus/models/image_data.dart"; - -class ThumbnailController extends AsyncNotifier { - ThumbnailController(this.data); - final ImageData data; - - @override - Future build({String? from}) async { - final client = await ref.watch(ClientController.provider.future); - final uri = await Uri.tryParse(data.uri)?.getDownloadUri(client); - - return uri.toString(); - } - - static final provider = AsyncNotifierProvider.family - .autoDispose( - ThumbnailController.new, - ); -} diff --git a/lib/helpers/extensions/list_to_messages.dart b/lib/helpers/extensions/list_to_messages.dart deleted file mode 100644 index 78648ba..0000000 --- a/lib/helpers/extensions/list_to_messages.dart +++ /dev/null @@ -1,10 +0,0 @@ -import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/helpers/extensions/event_to_message.dart"; -import "package:nexus/models/event.dart"; - -extension ListToMessages on Iterable { - Future> toMessages(Ref ref) async => (await Future.wait( - map((event) => event.toMessage(ref)), - )).nonNulls.toList(); -} diff --git a/lib/helpers/extensions/mxc_to_https.dart b/lib/helpers/extensions/mxc_to_https.dart new file mode 100644 index 0000000..468da12 --- /dev/null +++ b/lib/helpers/extensions/mxc_to_https.dart @@ -0,0 +1,4 @@ +extension MxcToHttps on Uri { + Uri mxcToHttps(String homeserver) => + Uri.parse("${homeserver}_matrix/client/v1/media/download/$host$path"); +} diff --git a/lib/models/client_state.dart b/lib/models/client_state.dart index 4063cc5..1e15136 100644 --- a/lib/models/client_state.dart +++ b/lib/models/client_state.dart @@ -9,6 +9,7 @@ abstract class ClientState with _$ClientState { required bool isLoggedIn, required bool isVerified, required String? userId, + required String? homeserverUrl, }) = _ClientState; factory ClientState.fromJson(Map json) => diff --git a/lib/models/message_config.dart b/lib/models/message_config.dart new file mode 100644 index 0000000..4e5ff71 --- /dev/null +++ b/lib/models/message_config.dart @@ -0,0 +1,16 @@ +import "package:freezed_annotation/freezed_annotation.dart"; +import "package:nexus/models/event.dart"; +part "message_config.freezed.dart"; +part "message_config.g.dart"; + +@freezed +abstract class MessageConfig with _$MessageConfig { + const factory MessageConfig({ + @Default(false) bool mustBeText, + @Default(false) bool includeEdits, + required Event event, + }) = _MessageConfig; + + factory MessageConfig.fromJson(Map json) => + _$MessageConfigFromJson(json); +} diff --git a/lib/widgets/avatar_or_hash.dart b/lib/widgets/avatar_or_hash.dart index 809d5d2..a47bbb5 100644 --- a/lib/widgets/avatar_or_hash.dart +++ b/lib/widgets/avatar_or_hash.dart @@ -1,14 +1,19 @@ import "package:color_hash/color_hash.dart"; +import "package:cross_cache/cross_cache.dart"; import "package:flutter/material.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/controllers/client_state_controller.dart"; +import "package:nexus/controllers/cross_cache_controller.dart"; +import "package:nexus/helpers/extensions/get_headers.dart"; +import "package:nexus/helpers/extensions/mxc_to_https.dart"; -class AvatarOrHash extends StatelessWidget { +class AvatarOrHash extends ConsumerWidget { final Uri? avatar; final String title; final Widget? fallback; final bool hasBadge; final int badgeNumber; final double height; - final Map headers; const AvatarOrHash( this.avatar, this.title, { @@ -16,15 +21,14 @@ class AvatarOrHash extends StatelessWidget { this.badgeNumber = 0, this.hasBadge = false, this.height = 24, - required this.headers, super.key, }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final box = ColoredBox( color: ColorHash(title).color, - child: Center(child: Text(title[0])), + child: Center(child: Text(title.isEmpty ? "" : title[0])), ); return SizedBox( width: height, @@ -42,9 +46,21 @@ class AvatarOrHash extends StatelessWidget { height: height, child: avatar == null ? fallback ?? box - : Image.network( - avatar.toString(), - headers: headers, + : Image( + image: CachedNetworkImage( + avatar! + .mxcToHttps( + ref.watch( + ClientStateController.provider.select( + (value) => value?.homeserverUrl, + ), + ) ?? + "", + ) + .toString(), + ref.watch(CrossCacheController.provider), + headers: ref.headers, + ), fit: BoxFit.contain, errorBuilder: (_, _, _) => box, ), diff --git a/lib/widgets/chat_page/html/html.dart b/lib/widgets/chat_page/html/html.dart index 769da8f..18edf4a 100644 --- a/lib/widgets/chat_page/html/html.dart +++ b/lib/widgets/chat_page/html/html.dart @@ -2,7 +2,10 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart"; +import "package:nexus/controllers/client_state_controller.dart"; +import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/helpers/extensions/link_to_mention.dart"; +import "package:nexus/helpers/extensions/mxc_to_https.dart"; import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/widgets/chat_page/html/mention_chip.dart"; import "package:nexus/widgets/chat_page/html/spoiler_text.dart"; @@ -40,53 +43,36 @@ class Html extends ConsumerWidget { ? null : InlineCustomWidget(child: MentionChip(element.text)), - // "img" => TODO: Img support - // element.attributes["src"] == null - // ? null - // : Consumer( - // builder: (_, ref, _) => ref - // .watch( - // ThumbnailController.provider( - // ImageData( - // uri: element.attributes["src"]!, - // height: height, - // width: width, - // ), - // ), - // ) - // .when( - // data: (uri) { - // if (uri == null) return SizedBox.shrink(); - - // return InlineCustomWidget( - // child: Image.network( - // uri, - // headers: client.headers, - // errorBuilder: (_, error, _) => Text( - // "Image Failed to Load", - // style: TextStyle( - // color: Theme.of(context).colorScheme.error, - // ), - // ), - // height: height.toDouble(), - // width: width?.toDouble(), - // loadingBuilder: (_, child, loadingProgress) => - // loadingProgress == null - // ? child - // : CircularProgressIndicator(), - // ), - // ); - // }, - // error: ErrorDialog.new, - // loading: () => InlineCustomWidget( - // child: SizedBox( - // width: width?.toDouble(), - // height: height.toDouble(), - // child: CircularProgressIndicator(), - // ), - // ), - // ), - // ), + "img" => + element.attributes["src"] == null + ? null + : InlineCustomWidget( + child: Image.network( + Uri.parse(element.attributes["src"]!) + .mxcToHttps( + ref.watch( + ClientStateController.provider.select( + (value) => value?.homeserverUrl, + ), + ) ?? + "", + ) + .toString(), + headers: ref.headers, + errorBuilder: (_, error, _) => Text( + "Image Failed to Load", + style: TextStyle( + color: Theme.of(context).colorScheme.error, + ), + ), + height: height.toDouble(), + width: width?.toDouble(), + loadingBuilder: (_, child, loadingProgress) => + loadingProgress == null + ? child + : CircularProgressIndicator(), + ), + ), ("del" || "h1" || "h2" || diff --git a/lib/widgets/chat_page/html/mention_chip.dart b/lib/widgets/chat_page/html/mention_chip.dart index 67d2877..c2b832d 100644 --- a/lib/widgets/chat_page/html/mention_chip.dart +++ b/lib/widgets/chat_page/html/mention_chip.dart @@ -19,7 +19,7 @@ class MentionChip extends StatelessWidget { context: context, builder: (_) => Dialog( child: Text("TODO: Open room or join room dialog, or user popover"), - ), // TODO + ), ), ); } diff --git a/lib/widgets/chat_page/member_list.dart b/lib/widgets/chat_page/member_list.dart index a1f842d..5e1f3bf 100644 --- a/lib/widgets/chat_page/member_list.dart +++ b/lib/widgets/chat_page/member_list.dart @@ -3,6 +3,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/members_controller.dart"; import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/models/room.dart"; +import "package:nexus/widgets/avatar_or_hash.dart"; class MemberList extends ConsumerWidget { final Room room; @@ -14,58 +15,44 @@ class MemberList extends ConsumerWidget { child: ref .watch(MembersController.provider(room)) .betterWhen( - data: (members) { - final joined = members.where( - (membership) => - membership.content["membership"] == - "join", // TODO: Show invites seperately - ); - return ListView( - children: [ - AppBar( - scrolledUnderElevation: 0, - leading: Icon(Icons.people), - title: Text("Members (${joined.length})"), - actionsPadding: EdgeInsets.only(right: 4), - actions: [ - if (Scaffold.of(context).hasEndDrawer) - IconButton( - onPressed: Scaffold.of(context).closeEndDrawer, - icon: Icon(Icons.close), - ), - ], - ), - ...joined.map( - (member) => ListTile( - onTap: () => showDialog( - context: context, - builder: (context) => - Dialog(child: Text("TODO: Open member popover")), - ), - // leading: AvatarOrHash( TODO - // ref - // .watch( - // AvatarController.provider( - // member.content["avatar_url"].toString(), - // ), - // ) - // .whenOrNull(data: (data) => data), - // member.content["displayname"].toString(), - // headers: room.client.headers, - // ), - title: Text( - member.content["displayname"].toString(), - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - member.authorId, - overflow: TextOverflow.ellipsis, + data: (members) => ListView( + children: [ + AppBar( + scrolledUnderElevation: 0, + leading: Icon(Icons.people), + title: Text("Members (${members.length})"), + actionsPadding: EdgeInsets.only(right: 4), + actions: [ + if (Scaffold.of(context).hasEndDrawer) + IconButton( + onPressed: Scaffold.of(context).closeEndDrawer, + icon: Icon(Icons.close), ), + ], + ), + ...members.map( + (member) => ListTile( + onTap: () => showDialog( + context: context, + builder: (context) => + Dialog(child: Text("TODO: Open member popover")), + ), + leading: AvatarOrHash( + Uri.tryParse(member.content["avatar_url"] ?? ""), + member.content["displayname"].toString(), + ), + title: Text( + member.content["displayname"].toString(), + overflow: TextOverflow.ellipsis, + ), + subtitle: Text( + member.authorId, + overflow: TextOverflow.ellipsis, ), ), - ], - ); - }, + ), + ], + ), ), ); } diff --git a/lib/widgets/chat_page/mention_overlay.dart b/lib/widgets/chat_page/mention_overlay.dart index 7ea9109..b2f2d9d 100644 --- a/lib/widgets/chat_page/mention_overlay.dart +++ b/lib/widgets/chat_page/mention_overlay.dart @@ -4,6 +4,7 @@ import "package:nexus/controllers/members_controller.dart"; import "package:nexus/controllers/rooms_controller.dart"; import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/models/room.dart"; +import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/loading.dart"; class MentionOverlay extends ConsumerWidget { @@ -54,18 +55,12 @@ class MentionOverlay extends ConsumerWidget { )) .map( (member) => ListTile( - // leading: AvatarOrHash( TODO: Images - // ref - // .watch( - // AvatarController.provider( - // member.content["avatar_url"] - // .toString(), - // ), - // ) - // .whenOrNull(data: (data) => data), - // member.content["displayname"].toString(), - // headers: room.client.headers, - // ), + leading: AvatarOrHash( + Uri.tryParse( + member.content["avatar_url"] ?? "", + ), + member.content["displayname"] ?? "", + ), title: Text( member.content["displayname"] as String? ?? member.authorId, @@ -93,12 +88,11 @@ class MentionOverlay extends ConsumerWidget { )) .map( (room) => ListTile( - // leading: AvatarOrHash( TODO: Images - // room.avatar, - // room.title, - // fallback: Icon(Icons.numbers), - // headers: room.roomData.client.headers, - // ), + leading: AvatarOrHash( + room.metadata?.avatar, + room.metadata?.name ?? "Unnamed Room", + fallback: Icon(Icons.numbers), + ), title: Text(room.metadata?.name ?? "Unnamed Room"), subtitle: room.metadata?.topic == null ? null diff --git a/lib/widgets/chat_page/room_appbar.dart b/lib/widgets/chat_page/room_appbar.dart index 10090b6..21aa4ae 100644 --- a/lib/widgets/chat_page/room_appbar.dart +++ b/lib/widgets/chat_page/room_appbar.dart @@ -24,14 +24,12 @@ class RoomAppbar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) => Appbar( leading: isDesktop - ? null - // AvatarOrHash( TODO: Images - // room.avatar, - // room.title, - // height: 24, - // fallback: Icon(Icons.numbers), - // headers: room.roomData.client.headers, - // ) + ? AvatarOrHash( + room.metadata?.avatar, + room.metadata?.name ?? "Unnamed Rooms", + height: 24, + fallback: Icon(Icons.numbers), + ) : DrawerButton(onPressed: () => onOpenDrawer(context)), scrolledUnderElevation: 0, title: Column( diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart index f7e6bc4..6adc013 100644 --- a/lib/widgets/chat_page/room_chat.dart +++ b/lib/widgets/chat_page/room_chat.dart @@ -488,9 +488,7 @@ class RoomChat extends HookConsumerWidget { onTap: () => showDialog( context: context, builder: (_) => Dialog( - child: Text( - "TODO: Download Attachments", // TODO - ), + child: Text("TODO: Download Attachments"), ), ), child: FlyerChatFileMessage( diff --git a/lib/widgets/chat_page/sidebar.dart b/lib/widgets/chat_page/sidebar.dart index cd394e3..341dd60 100644 --- a/lib/widgets/chat_page/sidebar.dart +++ b/lib/widgets/chat_page/sidebar.dart @@ -61,10 +61,9 @@ class Sidebar extends HookConsumerWidget { .map( (space) => NavigationRailDestination( icon: AvatarOrHash( - null, // TODO: Url + space.room?.metadata?.avatar, fallback: space.icon == null ? null : Icon(space.icon), space.title, - headers: {}, // TODO hasBadge: space.children.any( (room) => room.metadata?.unreadMessages != 0, ), @@ -177,15 +176,12 @@ class Sidebar extends HookConsumerWidget { backgroundColor: Colors.transparent, appBar: AppBar( leading: AvatarOrHash( - null, - // space.avatar, TODO + selectedSpace.room?.metadata?.avatar, fallback: selectedSpace.icon == null ? null : Icon(selectedSpace.icon), selectedSpace.title, - headers: {}, - // space.client.headers, TODO ), title: Text( selectedSpace.title, @@ -210,15 +206,13 @@ class Sidebar extends HookConsumerWidget { (room) => NavigationRailDestination( label: Text(room.metadata?.name ?? "Unnamed Room"), icon: AvatarOrHash( - null, + room.metadata?.avatar, hasBadge: room.metadata?.unreadMessages != 0, badgeNumber: room.metadata?.unreadNotifications ?? 0, - // room.avatar, TODO room.metadata?.name ?? "Unnamed Room", fallback: selectedSpaceId == "dms" ? null : Icon(Icons.numbers), - headers: {}, // space.client.headers, ), ), diff --git a/lib/widgets/chat_page/top_widget.dart b/lib/widgets/chat_page/top_widget.dart index 0cef0fa..cfba6fc 100644 --- a/lib/widgets/chat_page/top_widget.dart +++ b/lib/widgets/chat_page/top_widget.dart @@ -1,8 +1,8 @@ import "dart:math"; import "package:flutter/material.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:flutter_chat_ui/flutter_chat_ui.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/chat_page/html/quoted.dart"; class TopWidget extends ConsumerWidget { @@ -60,11 +60,11 @@ class TopWidget extends ConsumerWidget { mainAxisSize: MainAxisSize.min, spacing: 8, children: [ - // Avatar( TODO: images - // userId: replyMessage.authorId, - // headers: headers, - // size: 16, - // ), + AvatarOrHash( + Uri.tryParse(replyMessage.metadata?["avatarUrl"] ?? ""), + replyMessage.metadata?["displayName"] ?? "", + height: 16, + ), Flexible( child: Text( replyMessage.metadata?["displayName"] ?? @@ -102,7 +102,10 @@ class TopWidget extends ConsumerWidget { mainAxisSize: MainAxisSize.min, spacing: 8, children: [ - // Avatar(userId: message.authorId, headers: headers), TODO: images + AvatarOrHash( + Uri.parse(message.metadata?["avatarUrl"] ?? ""), + message.metadata?["displayName"] ?? "", + ), Flexible( child: Text( message.metadata?["displayName"] ?? message.authorId,