improve reply handling

This commit is contained in:
Henry Hiles 2026-03-10 19:22:41 -04:00
commit 0769ab4dbb
No known key found for this signature in database
4 changed files with 83 additions and 49 deletions

View file

@ -47,25 +47,21 @@ class MessageController extends AsyncNotifier<Message?> {
final type = (config.event.decryptedType ?? config.event.type); final type = (config.event.decryptedType ?? config.event.type);
final newContent = content["m.new_content"] as Map?; final newContent = content["m.new_content"] as Map?;
final metadata = { final metadata = {
"timelineId": event.timelineRowId, "body": config.event.redactedBy == null
"formatted": ? (newContent?["body"] ?? content["body"] ?? "")
newContent?["formatted_body"] ?? : "Deleted Message",
newContent?["body"] ??
content["formatted_body"] ??
content["body"] ??
"",
if (replyEvent != null) if (replyEvent != null)
"reply": await ref.watch( "reply": await ref.watch(
MessageController.provider( MessageController.provider(
MessageConfig( MessageConfig(
event: replyEvent, event: replyEvent,
room: config.room, room: config.room,
mustBeText: true, alwaysReturn: true,
), ),
).future, ).future,
), ),
"timelineId": event.timelineRowId,
"big": event.localContent?.bigEmoji == true, "big": event.localContent?.bigEmoji == true,
"body": newContent?["body"] ?? content["body"],
"eventType": type, "eventType": type,
"avatarUrl": author?.content["avatar_url"], "avatarUrl": author?.content["avatar_url"],
"editSource": "editSource":
@ -82,7 +78,7 @@ class MessageController extends AsyncNotifier<Message?> {
final editedAt = event.relationType == "m.replace" ? event.timestamp : null; final editedAt = event.relationType == "m.replace" ? event.timestamp : null;
if ((event.redactedBy != null && !config.mustBeText) || if ((event.redactedBy != null && !config.alwaysReturn) ||
(!config.includeEdits && (config.event.relationType == "m.replace"))) { (!config.includeEdits && (config.event.relationType == "m.replace"))) {
return null; return null;
} }
@ -98,17 +94,18 @@ class MessageController extends AsyncNotifier<Message?> {
metadata: metadata, metadata: metadata,
id: config.event.eventId, id: config.event.eventId,
authorId: event.authorId, authorId: event.authorId,
text: config.event.redactedBy == null text:
? content["body"] ?? "" newContent?["formatted_body"] ??
: "This message has been deleted...", newContent?["body"] ??
content["formatted_body"] ??
content["body"] ??
"",
replyToMessageId: replyId, replyToMessageId: replyId,
deliveredAt: config.event.timestamp, deliveredAt: config.event.timestamp,
editedAt: editedAt, editedAt: editedAt,
) )
as TextMessage; as TextMessage;
if (config.mustBeText) return asText;
final homeserver = ref.read(ClientStateController.provider)?.homeserverUrl; final homeserver = ref.read(ClientStateController.provider)?.homeserverUrl;
final source = homeserver == null || content["url"] == null final source = homeserver == null || content["url"] == null
? "null" ? "null"
@ -117,7 +114,7 @@ class MessageController extends AsyncNotifier<Message?> {
return switch (type) { return switch (type) {
"m.room.encrypted" => asText.copyWith( "m.room.encrypted" => asText.copyWith(
text: "Unable to decrypt message.", text: "Unable to decrypt message.",
metadata: {...metadata, "formatted": "Unable to decrypt message."}, metadata: {...metadata, "body": "Unable to decrypt message."},
), ),
// "org.matrix.msc3381.poll.start" => Message.custom( // "org.matrix.msc3381.poll.start" => Message.custom(
// metadata: { // metadata: {
@ -134,7 +131,11 @@ class MessageController extends AsyncNotifier<Message?> {
id: config.event.eventId, id: config.event.eventId,
metadata: metadata, metadata: metadata,
authorId: event.authorId, authorId: event.authorId,
text: event.localContent?.sanitizedHtml, text:
newContent?["formatted_body"] ??
newContent?["body"] ??
content["formatted_body"] ??
content["body"],
source: source, source: source,
replyToMessageId: replyId, replyToMessageId: replyId,
deliveredAt: config.event.timestamp, deliveredAt: config.event.timestamp,
@ -156,7 +157,18 @@ class MessageController extends AsyncNotifier<Message?> {
content["membership"] == event.unsigned["prev_content"]?["membership"] content["membership"] == event.unsigned["prev_content"]?["membership"]
? null ? null
: Message.system( : Message.system(
metadata: metadata, metadata: {
...metadata,
"body":
"${content["displayname"] ?? event.stateKey} ${switch (content["membership"]) {
"invite" => "was invited to",
"join" => "joined",
"leave" => "left",
"knock" => "asked to join",
"ban" => "was banned from",
_ => "did something relating to",
}} the room.",
},
id: config.event.eventId, id: config.event.eventId,
authorId: event.authorId, authorId: event.authorId,
deliveredAt: config.event.timestamp, deliveredAt: config.event.timestamp,
@ -170,8 +182,20 @@ class MessageController extends AsyncNotifier<Message?> {
_ => "did something relating to", _ => "did something relating to",
}} the room.", }} the room.",
), ),
"m.room.redaction" => null,
"m.room.redaction" =>
config.alwaysReturn
? asText.copyWith(
metadata: {
...(asText.metadata ?? {}),
"body": "Deleted Message",
},
)
: null,
_ => _ =>
config.alwaysReturn
? asText
: (
// Turn this on for debugging purposes // Turn this on for debugging purposes
false false
// ignore: dead_code // ignore: dead_code
@ -181,7 +205,7 @@ class MessageController extends AsyncNotifier<Message?> {
authorId: event.authorId, authorId: event.authorId,
replyToMessageId: replyId, replyToMessageId: replyId,
) )
: null, : null),
}; };
} }

View file

@ -7,7 +7,7 @@ part "message_config.g.dart";
@freezed @freezed
abstract class MessageConfig with _$MessageConfig { abstract class MessageConfig with _$MessageConfig {
const factory MessageConfig({ const factory MessageConfig({
@Default(false) bool mustBeText, @Default(false) bool alwaysReturn,
@Default(false) bool includeEdits, @Default(false) bool includeEdits,
required Room room, required Room room,
required Event event, required Event event,

View file

@ -395,8 +395,7 @@ class RoomChat extends HookConsumerWidget {
message.metadata?["big"] == true message.metadata?["big"] == true
? TextStyle(fontSize: 32) ? TextStyle(fontSize: 32)
: null, : null,
(message.metadata?["formatted"] message.text
as String)
.replaceAllMapped( .replaceAllMapped(
RegExp( RegExp(
"(<a\\b[^>]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)", "(<a\\b[^>]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)",
@ -466,6 +465,7 @@ class RoomChat extends HookConsumerWidget {
: CrossAxisAlignment.start, : CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 12), SizedBox(height: 12),
if (message.text?.isNotEmpty == true)
FlyerChatTextMessage( FlyerChatTextMessage(
topWidget: TopWidget( topWidget: TopWidget(
message, message,
@ -475,11 +475,19 @@ class RoomChat extends HookConsumerWidget {
message: TextMessage( message: TextMessage(
id: "${message.id}-text", id: "${message.id}-text",
authorId: message.authorId, authorId: message.authorId,
text: message.metadata?["formatted"], text: message.text!,
), ),
index: index, index: index,
), ),
FlyerChatImageMessage( FlyerChatImageMessage(
topWidget:
message.text?.isNotEmpty == true
? null
: TopWidget(
message,
groupStatus: groupStatus,
alwaysShow: true,
),
customImageProvider: CachedNetworkImage( customImageProvider: CachedNetworkImage(
message.source, message.source,
ref.watch( ref.watch(

View file

@ -22,11 +22,13 @@ class TopWidget extends ConsumerWidget {
children: [ children: [
Builder( Builder(
builder: (_) { builder: (_) {
final replyMessage = message.metadata?["reply"] as TextMessage?; final replyMessage = message.metadata?["reply"] as Message?;
if (replyMessage == null) return SizedBox.shrink(); if (replyMessage == null) return SizedBox.shrink();
final smallerText = message is TextMessage
? replyMessage.text.substring( final smallerText =
message is TextMessage && replyMessage.metadata!["body"] != null
? replyMessage.metadata!["body"].substring(
0, 0,
min( min(
max( max(
@ -39,14 +41,14 @@ class TopWidget extends ConsumerWidget {
), ),
5, 5,
), ),
replyMessage.text.length, replyMessage.metadata!["body"].length,
), ),
) )
: null; : null;
final replyText = final replyText =
(smallerText == null || (smallerText == null ||
smallerText.length == replyMessage.text.length) smallerText.length == replyMessage.metadata!["body"].length)
? replyMessage.text ? replyMessage.metadata!["body"]
: "$smallerText..."; : "$smallerText...";
return Padding( return Padding(