Merge branch 'typingread-enhance-new-features' into 'master'
[Ephemerals] Add new features See merge request famedly/famedlysdk!104
This commit is contained in:
commit
6c2251ed7a
|
@ -422,9 +422,9 @@ class Connection {
|
|||
room["timeline"]["events"] is List<dynamic>)
|
||||
_handleRoomEvents(id, room["timeline"]["events"], "timeline");
|
||||
|
||||
if (room["ephemetal"] is Map<String, dynamic> &&
|
||||
room["ephemetal"]["events"] is List<dynamic>)
|
||||
_handleEphemerals(id, room["ephemetal"]["events"]);
|
||||
if (room["ephemeral"] is Map<String, dynamic> &&
|
||||
room["ephemeral"]["events"] is List<dynamic>)
|
||||
_handleEphemerals(id, room["ephemeral"]["events"]);
|
||||
|
||||
if (room["account_data"] is Map<String, dynamic> &&
|
||||
room["account_data"]["events"] is List<dynamic>)
|
||||
|
@ -434,32 +434,48 @@ class Connection {
|
|||
|
||||
void _handleEphemerals(String id, List<dynamic> events) {
|
||||
for (num i = 0; i < events.length; i++) {
|
||||
if (!(events[i]["type"] is String &&
|
||||
events[i]["content"] is Map<String, dynamic>)) continue;
|
||||
_handleEvent(events[i], id, "ephemeral");
|
||||
|
||||
// Receipt events are deltas between two states. We will create a
|
||||
// fake room account data event for this and store the difference
|
||||
// there.
|
||||
if (events[i]["type"] == "m.receipt") {
|
||||
events[i]["content"].forEach((String e, dynamic value) {
|
||||
if (!(events[i]["content"][e] is Map<String, dynamic> &&
|
||||
events[i]["content"][e]["m.read"] is Map<String, dynamic>))
|
||||
return;
|
||||
events[i]["content"][e]["m.read"]
|
||||
.forEach((String user, dynamic value) async {
|
||||
if (!(events[i]["content"][e]["m.read"]["user"]
|
||||
is Map<String, dynamic> &&
|
||||
events[i]["content"][e]["m.read"]["ts"] is num)) return;
|
||||
Room room = client.roomList.getRoomById(id);
|
||||
if (room == null) room = Room(id: id);
|
||||
|
||||
_handleEvent(events[i], id, "ephemeral");
|
||||
});
|
||||
});
|
||||
} else if (events[i]["type"] == "m.typing") {
|
||||
if (!(events[i]["content"]["user_ids"] is List<String>)) continue;
|
||||
Map<String, dynamic> receiptStateContent =
|
||||
room.roomAccountData["m.receipt"]?.content ?? {};
|
||||
for (var eventEntry in events[i]["content"].entries) {
|
||||
final String eventID = eventEntry.key;
|
||||
if (events[i]["content"][eventID]["m.read"] != null) {
|
||||
final Map<String, dynamic> userTimestampMap =
|
||||
events[i]["content"][eventID]["m.read"];
|
||||
for (var userTimestampMapEntry in userTimestampMap.entries) {
|
||||
final String mxid = userTimestampMapEntry.key;
|
||||
|
||||
List<String> user_ids = events[i]["content"]["user_ids"];
|
||||
// Remove previous receipt event from this user
|
||||
for (var entry in receiptStateContent.entries) {
|
||||
if (entry.value["m.read"] is Map<String, dynamic> &&
|
||||
entry.value["m.read"].containsKey(mxid)) {
|
||||
entry.value["m.read"].remove(mxid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// If the user is typing, remove his id from the list of typing users
|
||||
var ownTyping = user_ids.indexOf(client.userID);
|
||||
if (ownTyping != -1) user_ids.removeAt(1);
|
||||
|
||||
_handleEvent(events[i], id, "ephemeral");
|
||||
if (userTimestampMap[mxid]["ts"] is int) {
|
||||
if (receiptStateContent[eventID] == null)
|
||||
receiptStateContent[eventID] = {"m.read": {}};
|
||||
else if (receiptStateContent[eventID]["m.read"])
|
||||
receiptStateContent[eventID]["m.read"] = {};
|
||||
receiptStateContent[eventID]["m.read"][mxid] = {
|
||||
"ts": userTimestampMap[mxid]["ts"],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
events[i]["content"] = receiptStateContent;
|
||||
_handleEvent(events[i], id, "account_data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,10 @@
|
|||
import 'package:famedlysdk/src/RoomState.dart';
|
||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||
import 'package:famedlysdk/src/utils/Receipt.dart';
|
||||
|
||||
import './Room.dart';
|
||||
import 'User.dart';
|
||||
|
||||
/// Defines a timeline event for a room.
|
||||
class Event extends RoomState {
|
||||
|
@ -97,6 +99,21 @@ class Event extends RoomState {
|
|||
return "$type";
|
||||
}
|
||||
|
||||
/// Returns a list of [Receipt] instances for this event.
|
||||
List<Receipt> get receipts {
|
||||
if (!(room.roomAccountData.containsKey("m.receipt") &&
|
||||
room.roomAccountData["m.receipt"].content.containsKey(eventId)))
|
||||
return [];
|
||||
List<Receipt> receiptsList = [];
|
||||
for (var entry in room
|
||||
.roomAccountData["m.receipt"].content[eventId]["m.read"].entries) {
|
||||
receiptsList.add(Receipt(
|
||||
room.states[entry.key]?.asUser ?? User(entry.key),
|
||||
ChatTime(entry.value["ts"])));
|
||||
}
|
||||
return receiptsList;
|
||||
}
|
||||
|
||||
/// Removes this event if the status is < 1. This event will just be removed
|
||||
/// from the database and the timelines. Returns false if not removed.
|
||||
Future<bool> remove() async {
|
||||
|
|
|
@ -69,6 +69,9 @@ class Room {
|
|||
/// Key-Value store for room states.
|
||||
Map<String, RoomState> states = {};
|
||||
|
||||
/// Key-Value store for ephemerals.
|
||||
Map<String, RoomAccountData> ephemerals = {};
|
||||
|
||||
/// Key-Value store for private account data only visible for this user.
|
||||
Map<String, RoomAccountData> roomAccountData = {};
|
||||
|
||||
|
@ -161,6 +164,17 @@ class Room {
|
|||
return lastEvent;
|
||||
}
|
||||
|
||||
/// Returns a list of all current typing users.
|
||||
List<User> get typingUsers {
|
||||
if (!ephemerals.containsKey("m.typing")) return [];
|
||||
List<dynamic> typingMxid = ephemerals["m.typing"].content["user_ids"];
|
||||
List<User> typingUsers = [];
|
||||
for (int i = 0; i < typingMxid.length; i++)
|
||||
typingUsers.add(
|
||||
states[typingMxid[i]]?.asUser ?? User(typingMxid[i], room: this));
|
||||
return typingUsers;
|
||||
}
|
||||
|
||||
/// Your current client instance.
|
||||
final Client client;
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import 'package:famedlysdk/famedlysdk.dart';
|
|||
import 'package:famedlysdk/src/AccountData.dart';
|
||||
import 'package:famedlysdk/src/RoomState.dart';
|
||||
|
||||
/// Stripped down events for account data and ephemrals of a room.
|
||||
class RoomAccountData extends AccountData {
|
||||
/// The user who has sent this event if it is not a global account data event.
|
||||
final String roomId;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:core';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/src/RoomState.dart';
|
||||
|
||||
import 'Client.dart';
|
||||
|
@ -147,9 +148,6 @@ class RoomList {
|
|||
}
|
||||
|
||||
void _handleEventUpdate(EventUpdate eventUpdate) {
|
||||
if (eventUpdate.type != "timeline" &&
|
||||
eventUpdate.type != "state" &&
|
||||
eventUpdate.type != "invite_state") return;
|
||||
// Search the room in the rooms
|
||||
num j = 0;
|
||||
for (j = 0; j < rooms.length; j++) {
|
||||
|
@ -157,11 +155,20 @@ class RoomList {
|
|||
}
|
||||
final bool found = (j < rooms.length && rooms[j].id == eventUpdate.roomID);
|
||||
if (!found) return;
|
||||
|
||||
RoomState stateEvent = RoomState.fromJson(eventUpdate.content, rooms[j]);
|
||||
if (rooms[j].states[stateEvent.key] != null &&
|
||||
rooms[j].states[stateEvent.key].time > stateEvent.time) return;
|
||||
rooms[j].states[stateEvent.key] = stateEvent;
|
||||
if (eventUpdate.type == "timeline" ||
|
||||
eventUpdate.type == "state" ||
|
||||
eventUpdate.type == "invite_state") {
|
||||
RoomState stateEvent = RoomState.fromJson(eventUpdate.content, rooms[j]);
|
||||
if (rooms[j].states[stateEvent.key] != null &&
|
||||
rooms[j].states[stateEvent.key].time > stateEvent.time) return;
|
||||
rooms[j].states[stateEvent.key] = stateEvent;
|
||||
} else if (eventUpdate.type == "account_data") {
|
||||
rooms[j].roomAccountData[eventUpdate.eventType] =
|
||||
RoomAccountData.fromJson(eventUpdate.content, rooms[j]);
|
||||
} else if (eventUpdate.type == "ephemeral") {
|
||||
rooms[j].ephemerals[eventUpdate.eventType] =
|
||||
RoomAccountData.fromJson(eventUpdate.content, rooms[j]);
|
||||
}
|
||||
if (rooms[j].onUpdate != null) rooms[j].onUpdate();
|
||||
sortAndUpdate();
|
||||
}
|
||||
|
|
11
lib/src/utils/Receipt.dart
Normal file
11
lib/src/utils/Receipt.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||
import '../User.dart';
|
||||
|
||||
/// Represents a receipt.
|
||||
/// This [user] has read an event at the given [time].
|
||||
class Receipt {
|
||||
final User user;
|
||||
final ChatTime time;
|
||||
|
||||
const Receipt(this.user, this.time);
|
||||
}
|
|
@ -103,6 +103,7 @@ void main() {
|
|||
newDeviceID: resp["device_id"],
|
||||
newMatrixVersions: matrix.matrixVersions,
|
||||
newLazyLoadMembers: matrix.lazyLoadMembers);
|
||||
await new Future.delayed(new Duration(milliseconds: 50));
|
||||
|
||||
expect(matrix.accessToken == resp["access_token"], true);
|
||||
expect(matrix.deviceName == "Text Matrix Client", true);
|
||||
|
@ -123,6 +124,15 @@ void main() {
|
|||
expect(matrix.roomList.rooms[1].directChatMatrixID, "@bob:example.com");
|
||||
expect(matrix.directChats, matrix.accountData["m.direct"].content);
|
||||
expect(matrix.presences.length, 1);
|
||||
expect(matrix.roomList.rooms[1].ephemerals.length, 2);
|
||||
expect(matrix.roomList.rooms[1].typingUsers.length, 1);
|
||||
expect(matrix.roomList.rooms[1].typingUsers[0].id, "@alice:example.com");
|
||||
expect(matrix.roomList.rooms[1].roomAccountData.length, 3);
|
||||
expect(
|
||||
matrix.roomList.rooms[1].roomAccountData["m.receipt"]
|
||||
.content["7365636s6r6432:example.com"]["m.read"]
|
||||
["@alice:example.com"]["ts"],
|
||||
1436451550453);
|
||||
expect(matrix.roomList.rooms.length, 2);
|
||||
expect(matrix.roomList.rooms[1].canonicalAlias,
|
||||
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}");
|
||||
|
@ -223,7 +233,7 @@ void main() {
|
|||
|
||||
List<EventUpdate> eventUpdateList = await eventUpdateListFuture;
|
||||
|
||||
expect(eventUpdateList.length, 9);
|
||||
expect(eventUpdateList.length, 12);
|
||||
|
||||
expect(eventUpdateList[0].eventType, "m.room.member");
|
||||
expect(eventUpdateList[0].roomID, "!726s6s6q:example.com");
|
||||
|
@ -241,21 +251,33 @@ void main() {
|
|||
expect(eventUpdateList[3].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[3].type, "timeline");
|
||||
|
||||
expect(eventUpdateList[4].eventType, "m.tag");
|
||||
expect(eventUpdateList[4].eventType, "m.typing");
|
||||
expect(eventUpdateList[4].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[4].type, "account_data");
|
||||
expect(eventUpdateList[4].type, "ephemeral");
|
||||
|
||||
expect(eventUpdateList[5].eventType, "org.example.custom.room.config");
|
||||
expect(eventUpdateList[5].eventType, "m.receipt");
|
||||
expect(eventUpdateList[5].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[5].type, "account_data");
|
||||
expect(eventUpdateList[5].type, "ephemeral");
|
||||
|
||||
expect(eventUpdateList[6].eventType, "m.room.name");
|
||||
expect(eventUpdateList[6].roomID, "!696r7674:example.com");
|
||||
expect(eventUpdateList[6].type, "invite_state");
|
||||
expect(eventUpdateList[6].eventType, "m.receipt");
|
||||
expect(eventUpdateList[6].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[6].type, "account_data");
|
||||
|
||||
expect(eventUpdateList[7].eventType, "m.room.member");
|
||||
expect(eventUpdateList[7].roomID, "!696r7674:example.com");
|
||||
expect(eventUpdateList[7].type, "invite_state");
|
||||
expect(eventUpdateList[7].eventType, "m.tag");
|
||||
expect(eventUpdateList[7].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[7].type, "account_data");
|
||||
|
||||
expect(eventUpdateList[8].eventType, "org.example.custom.room.config");
|
||||
expect(eventUpdateList[8].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[8].type, "account_data");
|
||||
|
||||
expect(eventUpdateList[9].eventType, "m.room.name");
|
||||
expect(eventUpdateList[9].roomID, "!696r7674:example.com");
|
||||
expect(eventUpdateList[9].type, "invite_state");
|
||||
|
||||
expect(eventUpdateList[10].eventType, "m.room.member");
|
||||
expect(eventUpdateList[10].roomID, "!696r7674:example.com");
|
||||
expect(eventUpdateList[10].type, "invite_state");
|
||||
});
|
||||
|
||||
test('User Update Test', () async {
|
||||
|
|
|
@ -443,6 +443,17 @@ class FakeMatrixApi extends MockClient {
|
|||
"content": {
|
||||
"user_ids": ["@alice:example.com"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"7365636s6r6432:example.com": {
|
||||
"m.read": {
|
||||
"@alice:example.com": {"ts": 1436451550453}
|
||||
}
|
||||
}
|
||||
},
|
||||
"room_id": "!726s6s6q:example.com",
|
||||
"type": "m.receipt"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/src/RoomAccountData.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:famedlysdk/src/Client.dart';
|
||||
import 'package:famedlysdk/src/Room.dart';
|
||||
|
@ -97,6 +98,24 @@ void main() {
|
|||
expect(timeline.events[0].time.toTimeStamp(), testTimeStamp);
|
||||
expect(timeline.events[0].getBody(), "Testcase");
|
||||
expect(timeline.events[0].time > timeline.events[1].time, true);
|
||||
expect(timeline.events[0].receipts, []);
|
||||
|
||||
room.roomAccountData["m.receipt"] = RoomAccountData.fromJson({
|
||||
"type": "m.receipt",
|
||||
"content": {
|
||||
"1": {
|
||||
"m.read": {
|
||||
"@alice:example.com": {"ts": 1436451550453}
|
||||
}
|
||||
}
|
||||
},
|
||||
"room_id": roomID,
|
||||
}, room);
|
||||
|
||||
await new Future.delayed(new Duration(milliseconds: 50));
|
||||
|
||||
expect(timeline.events[0].receipts.length, 1);
|
||||
expect(timeline.events[0].receipts[0].user.id, "@alice:example.com");
|
||||
});
|
||||
|
||||
test("Send message", () async {
|
||||
|
|
Loading…
Reference in a new issue