diff --git a/lib/src/Room.dart b/lib/src/Room.dart index d63ef2f..9e10dea 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -165,45 +165,65 @@ class Room { type: "PUT", action: "/client/r0/rooms/${id}/send/m.room.message/$txid", data: {"msgtype": "m.text", "body": message}); - if (res["errcode"] == "M_LIMIT_EXCEEDED") - client.connection.onError.add(res["error"]); + if (res is ErrorResponse) client.connection.onError.add(res); return res; } - Future sendTextEvent(String message) async { + Future sendTextEvent(String message, {String txid = null}) async { final String type = "m.room.message"; + String messageID; final int now = DateTime.now().millisecondsSinceEpoch; - final String messageID = "msg$now"; + if (txid == null) { + messageID = "msg$now"; + } else + messageID = txid; + // Display a *sending* event and store it. EventUpdate eventUpdate = EventUpdate(type: "timeline", roomID: id, eventType: type, content: { "type": type, - "id": messageID, + "id": null, "sender": client.userID, "status": 0, "origin_server_ts": now, "content": { "msgtype": "m.text", "body": message, + "txid": messageID, } }); client.connection.onEvent.add(eventUpdate); - await client.store.transaction(() { + await client.store?.transaction(() { client.store.storeEventUpdate(eventUpdate); }); + + // Send the text and on success, store and display a *sent* event. final dynamic res = await sendText(message, txid: messageID); + if (res is ErrorResponse) { - client.store.db - .rawUpdate("UPDATE Events SET status=-1 WHERE id=?", [messageID]); + // On error, set status to -1 + eventUpdate.content["status"] = -1; + client.connection.onEvent.add(eventUpdate); + client.store?.db + ?.rawUpdate("UPDATE Events SET status=-1 WHERE id=?", [messageID]); } else { final String newEventID = res["event_id"]; - final List> event = await client.store.db - .rawQuery("SELECT * FROM Events WHERE id=?", [newEventID]); - if (event.length > 0) { - client.store.db.rawDelete("DELETE FROM Events WHERE id=?", [messageID]); - } else { - client.store.db.rawUpdate("UPDATE Events SET id=?, status=1 WHERE id=?", - [newEventID, messageID]); + eventUpdate.content["status"] = 1; + eventUpdate.content["id"] = newEventID; + client.connection.onEvent.add(eventUpdate); + + // Store the result in database + if (client.store != null) { + final List> eventQuery = await client.store.db + .rawQuery("SELECT * FROM Events WHERE id=?", [newEventID]); + if (eventQuery.length > 0) { + client.store.db + .rawDelete("DELETE FROM Events WHERE id=?", [messageID]); + } else { + client.store.db.rawUpdate( + "UPDATE Events SET id=?, status=1 WHERE id=?", + [newEventID, messageID]); + } } return newEventID; } diff --git a/lib/src/Timeline.dart b/lib/src/Timeline.dart index 911465a..81b55c8 100644 --- a/lib/src/Timeline.dart +++ b/lib/src/Timeline.dart @@ -37,6 +37,8 @@ class Timeline { final onTimelineUpdateCallback onUpdate; final onTimelineInsertCallback onInsert; + Set waitToReplace = {}; + StreamSubscription sub; Timeline({this.room, this.events, this.onUpdate, this.onInsert}) { @@ -47,20 +49,40 @@ class Timeline { try { if (eventUpdate.roomID != room.id) return; if (eventUpdate.type == "timeline" || eventUpdate.type == "history") { - if (!eventUpdate.content.containsKey("id")) - eventUpdate.content["id"] = eventUpdate.content["event_id"]; + // Is this event already in the timeline? + if (eventUpdate.content["status"] == 1 || + eventUpdate.content["status"] == -1 || + waitToReplace.contains(eventUpdate.content["id"])) { + int i; + for (i = 0; i < events.length; i++) { + if (events[i].content.containsKey("txid") && + events[i].content["txid"] == + eventUpdate.content["content"]["txid"] || + events[i].id == eventUpdate.content["id"]) break; + } + if (i < events.length) { + events[i] = Event.fromJson(eventUpdate.content, room); + if (eventUpdate.content["content"]["txid"] is String) + waitToReplace.add(eventUpdate.content["id"]); + else + waitToReplace.remove(eventUpdate.content["id"]); + } + } else { + if (!eventUpdate.content.containsKey("id")) + eventUpdate.content["id"] = eventUpdate.content["event_id"]; - User user = await room.client.store - ?.getUser(matrixID: eventUpdate.content["sender"], room: room); - if (user != null) { - eventUpdate.content["displayname"] = user.displayName; - eventUpdate.content["avatar_url"] = user.avatarUrl.mxc; + User user = await room.client.store + ?.getUser(matrixID: eventUpdate.content["sender"], room: room); + if (user != null) { + eventUpdate.content["displayname"] = user.displayName; + eventUpdate.content["avatar_url"] = user.avatarUrl.mxc; + } + + Event newEvent = Event.fromJson(eventUpdate.content, room); + + events.insert(0, newEvent); + if (onInsert != null) onInsert(0); } - - Event newEvent = Event.fromJson(eventUpdate.content, room); - - events.insert(0, newEvent); - if (onInsert != null) onInsert(0); } sortAndUpdate(); } catch (e) { diff --git a/test/FakeMatrixApi.dart b/test/FakeMatrixApi.dart index 2b35ae8..c1dad41 100644 --- a/test/FakeMatrixApi.dart +++ b/test/FakeMatrixApi.dart @@ -376,7 +376,12 @@ class FakeMatrixApi extends MockClient { }, "/client/r0/rooms/!localpart:server.abc/read_markers": (var reqI) => {}, }, - "PUT": {}, + "PUT": { + "/client/r0/rooms/!1234:example.com/send/m.room.message/1234": + (var reqI) => { + "event_id": "42", + }, + }, "DELETE": { "/unknown/token": (var req) => {"errcode": "M_UNKNOWN_TOKEN"}, }, diff --git a/test/Timeline_test.dart b/test/Timeline_test.dart index 2576aca..88f0c52 100644 --- a/test/Timeline_test.dart +++ b/test/Timeline_test.dart @@ -27,6 +27,7 @@ import 'package:famedlysdk/src/Room.dart'; import 'package:famedlysdk/src/Timeline.dart'; import 'package:famedlysdk/src/sync/EventUpdate.dart'; import 'package:famedlysdk/src/utils/ChatTime.dart'; +import 'FakeMatrixApi.dart'; void main() { /// All Tests related to the MxContent @@ -36,21 +37,22 @@ void main() { int updateCount = 0; List insertList = []; + Client client = Client("testclient", debug: true); + client.connection.httpClient = FakeMatrixApi(); + client.homeserver = "https://fakeServer.notExisting"; + + Room room = Room(id: roomID, client: client); + Timeline timeline = Timeline( + room: room, + events: [], + onUpdate: () { + updateCount++; + }, + onInsert: (int insertID) { + insertList.add(insertID); + }); + test("Create", () async { - Client client = Client("testclient"); - client.homeserver = "https://testserver.abc"; - - Room room = Room(id: roomID, client: client); - Timeline timeline = Timeline( - room: room, - events: [], - onUpdate: () { - updateCount++; - }, - onInsert: (int insertID) { - insertList.add(insertID); - }); - client.connection.onEvent.add(EventUpdate( type: "timeline", roomID: roomID, @@ -91,5 +93,37 @@ void main() { expect(timeline.events[0].getBody(), "Testcase"); expect(timeline.events[0].time > timeline.events[1].time, true); }); + + test("Send message", () async { + room.sendTextEvent("test", txid: "1234"); + + await new Future.delayed(new Duration(milliseconds: 50)); + + expect(updateCount, 4); + expect(insertList, [0, 0, 0]); + expect(timeline.events[0].content["txid"], "1234"); + expect(timeline.events[0].id, "42"); + expect(timeline.events[0].status, 1); + + client.connection.onEvent.add(EventUpdate( + type: "timeline", + roomID: roomID, + eventType: "m.room.message", + content: { + "type": "m.room.message", + "content": {"msgtype": "m.text", "body": "test"}, + "sender": "@alice:example.com", + "status": 2, + "id": "42", + "origin_server_ts": DateTime.now().millisecondsSinceEpoch + })); + + await new Future.delayed(new Duration(milliseconds: 50)); + + expect(updateCount, 5); + expect(insertList, [0, 0, 0]); + expect(timeline.events[0].id, "42"); + expect(timeline.events[0].status, 2); + }); }); }