Merge branch 'event-feature-add-replies' into 'master'

[Event] Add support for replies

See merge request famedly/famedlysdk!184
This commit is contained in:
Christian Pauly 2020-02-11 11:06:54 +00:00
commit fb9d8613ee
3 changed files with 52 additions and 12 deletions

View file

@ -23,6 +23,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:famedlysdk/src/utils/matrix_id_string_extension.dart';
import 'package:famedlysdk/src/utils/receipt.dart'; import 'package:famedlysdk/src/utils/receipt.dart';
import './room.dart'; import './room.dart';
@ -366,6 +367,23 @@ class Event {
/// Redacts this event. Returns [ErrorResponse] on error. /// Redacts this event. Returns [ErrorResponse] on error.
Future<dynamic> redact({String reason, String txid}) => Future<dynamic> redact({String reason, String txid}) =>
room.redactEvent(eventId, reason: reason, txid: txid); room.redactEvent(eventId, reason: reason, txid: txid);
/// Whether this event is in reply to another event.
bool get isReply =>
content['m.relates_to'] is Map<String, dynamic> &&
content['m.relates_to']['m.in_reply_to'] is Map<String, dynamic> &&
content['m.relates_to']['m.in_reply_to']['event_id'] is String &&
(content['m.relates_to']['m.in_reply_to']['event_id'] as String)
.isValidMatrixId &&
(content['m.relates_to']['m.in_reply_to']['event_id'] as String).sigil ==
"\$";
/// Searches for the reply event in the given timeline.
Future<Event> getReplyEvent(Timeline timeline) async {
if (!isReply) return null;
final String replyEventId = content['m.relates_to']['m.in_reply_to'];
return await timeline.getEventById(replyEventId);
}
} }
enum MessageTypes { enum MessageTypes {

View file

@ -290,13 +290,15 @@ class Room {
return res["event_id"]; return res["event_id"];
} }
Future<String> sendTextEvent(String message, {String txid}) => Future<String> sendTextEvent(String message,
sendEvent({"msgtype": "m.text", "body": message}, txid: txid); {String txid, Event inReplyTo}) =>
sendEvent({"msgtype": "m.text", "body": message},
txid: txid, inReplyTo: inReplyTo);
/// Sends a [file] to this room after uploading it. The [msgType] is optional /// Sends a [file] to this room after uploading it. The [msgType] is optional
/// and will be detected by the mimetype of the file. /// and will be detected by the mimetype of the file.
Future<String> sendFileEvent(MatrixFile file, Future<String> sendFileEvent(MatrixFile file,
{String msgType = "m.file", String txid}) async { {String msgType = "m.file", String txid, Event inReplyTo}) async {
if (msgType == "m.image") return sendImageEvent(file); if (msgType == "m.image") return sendImageEvent(file);
if (msgType == "m.audio") return sendVideoEvent(file); if (msgType == "m.audio") return sendVideoEvent(file);
if (msgType == "m.video") return sendAudioEvent(file); if (msgType == "m.video") return sendAudioEvent(file);
@ -315,11 +317,11 @@ class Room {
"size": file.size, "size": file.size,
} }
}; };
return await sendEvent(content, txid: txid); return await sendEvent(content, txid: txid, inReplyTo: inReplyTo);
} }
Future<String> sendAudioEvent(MatrixFile file, Future<String> sendAudioEvent(MatrixFile file,
{String txid, int width, int height}) async { {String txid, int width, int height, Event inReplyTo}) async {
String fileName = file.path.split("/").last; String fileName = file.path.split("/").last;
final String uploadResp = await client.upload(file); final String uploadResp = await client.upload(file);
Map<String, dynamic> content = { Map<String, dynamic> content = {
@ -332,11 +334,11 @@ class Room {
"size": file.size, "size": file.size,
} }
}; };
return await sendEvent(content, txid: txid); return await sendEvent(content, txid: txid, inReplyTo: inReplyTo);
} }
Future<String> sendImageEvent(MatrixFile file, Future<String> sendImageEvent(MatrixFile file,
{String txid, int width, int height}) async { {String txid, int width, int height, Event inReplyTo}) async {
String fileName = file.path.split("/").last; String fileName = file.path.split("/").last;
final String uploadResp = await client.upload(file); final String uploadResp = await client.upload(file);
Map<String, dynamic> content = { Map<String, dynamic> content = {
@ -350,7 +352,7 @@ class Room {
"h": height, "h": height,
}, },
}; };
return await sendEvent(content, txid: txid); return await sendEvent(content, txid: txid, inReplyTo: inReplyTo);
} }
Future<String> sendVideoEvent(MatrixFile file, Future<String> sendVideoEvent(MatrixFile file,
@ -360,7 +362,8 @@ class Room {
int duration, int duration,
MatrixFile thumbnail, MatrixFile thumbnail,
int thumbnailWidth, int thumbnailWidth,
int thumbnailHeight}) async { int thumbnailHeight,
Event inReplyTo}) async {
String fileName = file.path.split("/").last; String fileName = file.path.split("/").last;
final String uploadResp = await client.upload(file); final String uploadResp = await client.upload(file);
Map<String, dynamic> content = { Map<String, dynamic> content = {
@ -396,10 +399,11 @@ class Room {
content["info"]["thumbnail_info"]["h"] = thumbnailHeight; content["info"]["thumbnail_info"]["h"] = thumbnailHeight;
} }
} }
return await sendEvent(content, txid: txid); return await sendEvent(content, txid: txid, inReplyTo: inReplyTo);
} }
Future<String> sendEvent(Map<String, dynamic> content, {String txid}) async { Future<String> sendEvent(Map<String, dynamic> content,
{String txid, Event inReplyTo}) async {
final String type = "m.room.message"; final String type = "m.room.message";
// Create new transaction id // Create new transaction id
@ -411,6 +415,23 @@ class Room {
messageID = txid; messageID = txid;
} }
if (inReplyTo != null) {
String replyText = "<${inReplyTo.senderId}> " + inReplyTo.body;
List<String> replyTextLines = replyText.split("\n");
for (int i = 0; i < replyTextLines.length; i++) {
replyTextLines[i] = "> " + replyTextLines[i];
}
replyText = replyTextLines.join("\n");
content["format"] = "org.matrix.custom.html";
content["formatted_body"] = '<mx-reply><blockquote><a href="https://matrix.to/#/${inReplyTo.room.id}/${inReplyTo.eventId}">In reply to</a> <a href="https://matrix.to/#/${inReplyTo.senderId}">${inReplyTo.senderId}</a><br>${inReplyTo.body}</blockquote></mx-reply>${content["formatted_body"] ?? content["body"]}';
content["body"] = replyText + "\n\n${content["body"] ?? ""}";
content["m.relates_to"] = {
"m.in_reply_to": {
"event_id": inReplyTo.eventId,
},
};
}
// Display a *sending* event and store it. // Display a *sending* event and store it.
EventUpdate eventUpdate = EventUpdate eventUpdate =
EventUpdate(type: "timeline", roomID: id, eventType: type, content: { EventUpdate(type: "timeline", roomID: id, eventType: type, content: {

View file

@ -41,7 +41,7 @@ void main() {
final String formatted_body = "<b>Hello</b> World"; final String formatted_body = "<b>Hello</b> World";
final String contentJson = final String contentJson =
'{"msgtype":"$msgtype","body":"$body","formatted_body":"$formatted_body"}'; '{"msgtype":"$msgtype","body":"$body","formatted_body":"$formatted_body","m.relates_to":{"m.in_reply_to":{"event_id":"\$1234:example.com"}}}';
Map<String, dynamic> jsonObj = { Map<String, dynamic> jsonObj = {
"event_id": id, "event_id": id,
@ -67,6 +67,7 @@ void main() {
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.isReply, true);
jsonObj["state_key"] = ""; jsonObj["state_key"] = "";
Event state = Event.fromJson(jsonObj, null); Event state = Event.fromJson(jsonObj, null);
expect(state.eventId, id); expect(state.eventId, id);