Merge pull request #2179 from famedly/krille/fix-replies-in-threads

fix: Replies in threads
This commit is contained in:
Krille-chan 2025-11-12 11:02:57 +01:00 committed by GitHub
commit 420346e103
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 48 additions and 53 deletions

View File

@ -32,7 +32,6 @@ import 'package:matrix/src/utils/markdown.dart';
import 'package:matrix/src/utils/multipart_request_progress.dart'; import 'package:matrix/src/utils/multipart_request_progress.dart';
abstract class RelationshipTypes { abstract class RelationshipTypes {
static const String reply = 'm.in_reply_to';
static const String edit = 'm.replace'; static const String edit = 'm.replace';
static const String reaction = 'm.annotation'; static const String reaction = 'm.annotation';
static const String reference = 'm.reference'; static const String reference = 'm.reference';
@ -490,31 +489,13 @@ class Event extends MatrixEvent {
/// event fallback if the relationship type is `m.thread`. /// event fallback if the relationship type is `m.thread`.
/// https://spec.matrix.org/v1.14/client-server-api/#fallback-for-unthreaded-clients /// https://spec.matrix.org/v1.14/client-server-api/#fallback-for-unthreaded-clients
Future<Event?> getReplyEvent(Timeline timeline) async { Future<Event?> getReplyEvent(Timeline timeline) async {
switch (relationshipType) { final relationshipEventId = content
case RelationshipTypes.reply: .tryGetMap<String, Object?>('m.relates_to')
final relationshipEventId = this.relationshipEventId; ?.tryGetMap<String, Object?>('m.in_reply_to')
return relationshipEventId == null ?.tryGet<String>('event_id');
? null return relationshipEventId == null
: await timeline.getEventById(relationshipEventId); ? null
: await timeline.getEventById(relationshipEventId);
case RelationshipTypes.thread:
final relationshipContent =
content.tryGetMap<String, Object?>('m.relates_to');
if (relationshipContent == null) return null;
final String? relationshipEventId;
if (relationshipContent.tryGet<bool>('is_falling_back') == true) {
relationshipEventId = relationshipContent
.tryGetMap<String, Object?>('m.in_reply_to')
?.tryGet<String>('event_id');
} else {
relationshipEventId = this.relationshipEventId;
}
return relationshipEventId == null
? null
: await timeline.getEventById(relationshipEventId);
default:
return null;
}
} }
/// If this event is encrypted and the decryption was not successful because /// If this event is encrypted and the decryption was not successful because
@ -1021,30 +1002,30 @@ class Event extends MatrixEvent {
return transactionId == search; return transactionId == search;
} }
/// Get the relationship type of an event. `null` if there is none /// Get the relationship type of an event. `null` if there is none.
String? get relationshipType { String? get relationshipType => content
final mRelatesTo = content.tryGetMap<String, Object?>('m.relates_to'); .tryGetMap<String, Object?>('m.relates_to')
if (mRelatesTo == null) { ?.tryGet<String>('rel_type');
return null;
}
final relType = mRelatesTo.tryGet<String>('rel_type');
if (relType == RelationshipTypes.thread) {
return RelationshipTypes.thread;
}
if (mRelatesTo.containsKey('m.in_reply_to')) { /// Get the event ID that this relationship will reference and `null` if there
return RelationshipTypes.reply; /// is none. This could for example be the thread root, the original event for
} /// an edit or the event, this is an reaction for. For replies please use
return relType; /// `Event.inReplyToEventId()` instead!
} String? get relationshipEventId => content
.tryGetMap<String, Object?>('m.relates_to')
?.tryGet<String>('event_id');
/// Get the event ID that this relationship will reference. `null` if there is none /// If this event is in reply to another event, this returns the event ID or
String? get relationshipEventId { /// null if this event is not a reply.
final relatesToMap = content.tryGetMap<String, Object?>('m.relates_to'); String? inReplyToEventId({bool includingFallback = true}) {
return relatesToMap?.tryGet<String>('event_id') ?? final isFallback = content
relatesToMap .tryGetMap<String, Object?>('m.relates_to')
?.tryGetMap<String, Object?>('m.in_reply_to') ?.tryGet<bool>('is_falling_back');
?.tryGet<String>('event_id'); if (isFallback == true && !includingFallback) return null;
return content
.tryGetMap<String, Object?>('m.relates_to')
?.tryGetMap<String, Object?>('m.in_reply_to')
?.tryGet<String>('event_id');
} }
/// Get whether this event has aggregated events from a certain [type] /// Get whether this event has aggregated events from a certain [type]

View File

@ -79,7 +79,7 @@ void main() async {
expect(event.formattedText, formatted_body); expect(event.formattedText, formatted_body);
expect(event.body, body); expect(event.body, body);
expect(event.type, EventTypes.Message); expect(event.type, EventTypes.Message);
expect(event.relationshipType, RelationshipTypes.reply); expect(event.inReplyToEventId(), '\$1234:example.com');
jsonObj['state_key'] = ''; jsonObj['state_key'] = '';
final state = Event.fromJson(jsonObj, room); final state = Event.fromJson(jsonObj, room);
expect(state.eventId, id); expect(state.eventId, id);
@ -178,8 +178,8 @@ void main() async {
}; };
event = Event.fromJson(jsonObj, room); event = Event.fromJson(jsonObj, room);
expect(event.messageType, MessageTypes.Text); expect(event.messageType, MessageTypes.Text);
expect(event.relationshipType, RelationshipTypes.reply); expect(event.inReplyToEventId(), '1234');
expect(event.relationshipEventId, '1234'); expect(event.relationshipEventId, null);
}); });
test('relationship types', () async { test('relationship types', () async {
@ -212,8 +212,22 @@ void main() async {
}, },
}; };
event = Event.fromJson(jsonObj, room); event = Event.fromJson(jsonObj, room);
expect(event.relationshipType, RelationshipTypes.reply); expect(event.inReplyToEventId(), 'def');
expect(event.relationshipEventId, 'def'); expect(event.relationshipEventId, null);
jsonObj['content']['m.relates_to'] = {
'rel_type': 'm.thread',
'event_id': '\$root',
'm.in_reply_to': {
'event_id': '\$target',
},
'is_falling_back': true,
};
event = Event.fromJson(jsonObj, room);
expect(event.relationshipType, RelationshipTypes.thread);
expect(event.inReplyToEventId(), '\$target');
expect(event.inReplyToEventId(includingFallback: false), null);
expect(event.relationshipEventId, '\$root');
}); });
test('redact', () async { test('redact', () async {