From efa2ed53796ec56323c7258466f4943402d373b6 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Tue, 11 Jun 2019 10:51:45 +0200 Subject: [PATCH] Refactor Room class --- lib/src/Room.dart | 230 +++++++++++++++++++++++++++++++++------------ lib/src/Store.dart | 28 ++++++ 2 files changed, 200 insertions(+), 58 deletions(-) diff --git a/lib/src/Room.dart b/lib/src/Room.dart index dda989d..c051908 100644 --- a/lib/src/Room.dart +++ b/lib/src/Room.dart @@ -30,131 +30,220 @@ import './User.dart'; /// Represents a Matrix room. class Room { - final String roomID; + /// The full qualified Matrix ID for the room in the format '!localid:server.abc'. + final String id; + + /// Membership status of the user for this room. + String membership; + + /// The name of the room if set by a participant. String name; - String lastMessage; - MxContent avatar; - ChatTime timeCreated; - int notificationCount; - int highlightCount; + + /// The topic of the room if set by a participant. String topic; - User user; - final Client matrix; + + /// The avatar of the room if set by a participant. + MxContent avatar; + + /// The count of unread notifications. + int notificationCount; + + /// The count of highlighted notifications. + int highlightCount; + + String prev_batch; + + String draft; + + /// Time when the user has last read the chat. + ChatTime unread; + + /// ID of the fully read marker event. + String fullyRead; + + /// The address in the format: #roomname:homeserver.org. + String canonicalAlias; + + /// If this room is a direct chat, this is the matrix ID of the user + String directChatMatrixID; + + /// Must be one of [all, mention] + String notificationSettings; + + /// Are guest users allowed? + String guestAccess; + + /// Who can see the history of this room? + String historyVisibility; + + /// Who is allowed to join this room? + String joinRules; + + /// The needed power levels for all actions. + Map powerLevels = {}; + + /// The list of events in this room. If the room is created by the + /// [getRoomList()] of the [Store], this will contain only the last event. List events = []; + /// The list of participants in this room. If the room is created by the + /// [getRoomList()] of the [Store], this will contain only the sender of the + /// last event. + List participants = []; + + /// Your current client instance. + final Client client; + + @Deprecated("Rooms.roomID is deprecated! Use Rooms.id instead!") + String get roomID =>this.id; + + @Deprecated("Rooms.matrix is deprecated! Use Rooms.client instead!") + Client get matrix => this.client; + + @Deprecated("Rooms.status is deprecated! Use Rooms.membership instead!") + String get status => this.membership; + Room({ - this.roomID, + this.id, + this.membership, this.name, - this.lastMessage, + this.topic, this.avatar, - this.timeCreated, this.notificationCount, this.highlightCount, - this.topic, - this.user, - this.matrix, + this.prev_batch, + this.draft, + this.unread, + this.fullyRead, + this.canonicalAlias, + this.directChatMatrixID, + this.notificationSettings, + this.guestAccess, + this.historyVisibility, + this.joinRules, + this.powerLevels, this.events, + this.participants, + this.client, }); - String get status { - if (this.user != null) { - return this.user.status; - } - return this.topic; + /// The last message sent to this room. + String get lastMessage { + if (events.length > 0) + return events[0].getBody(); + else return ""; } + /// When the last message received. + ChatTime get timeCreated { + if (events.length > 0) + return events[0].time; + else return ChatTime.now(); + } + + /// Call the Matrix API to change the name of this room. Future setName(String newName) async{ - dynamic res = await matrix.connection.jsonRequest( + dynamic res = await client.connection.jsonRequest( type: "PUT", action: - "/client/r0/rooms/${roomID}/send/m.room.name/${new DateTime.now()}", + "/client/r0/rooms/${id}/send/m.room.name/${new DateTime.now()}", data: {"name": newName}); - if (res is ErrorResponse) matrix.connection.onError.add(res); + if (res is ErrorResponse) client.connection.onError.add(res); return res; } + /// Call the Matrix API to change the topic of this room. Future setDescription(String newName) async{ - dynamic res = await matrix.connection.jsonRequest( + dynamic res = await client.connection.jsonRequest( type: "PUT", action: - "/client/r0/rooms/${roomID}/send/m.room.topic/${new DateTime.now()}", + "/client/r0/rooms/${id}/send/m.room.topic/${new DateTime.now()}", data: {"topic": newName}); - if (res is ErrorResponse) matrix.connection.onError.add(res); + if (res is ErrorResponse) client.connection.onError.add(res); return res; } + @Deprecated("Use the client.connection streams instead!") Stream> get eventsStream { return Stream>.fromIterable(Iterable>.generate( this.events.length, (int index) => this.events)).asBroadcastStream(); } + /// Call the Matrix API to send a simple text message. Future sendText(String message) async { - dynamic res = await matrix.connection.jsonRequest( + dynamic res = await client.connection.jsonRequest( type: "PUT", action: - "/client/r0/rooms/${roomID}/send/m.room.message/${new DateTime.now()}", + "/client/r0/rooms/${id}/send/m.room.message/${new DateTime.now()}", data: {"msgtype": "m.text", "body": message}); - if (res["errcode"] == "M_LIMIT_EXCEEDED") matrix.connection.onError.add(res["error"]); + if (res["errcode"] == "M_LIMIT_EXCEEDED") client.connection.onError.add(res["error"]); } + /// Call the Matrix API to leave this room. Future leave() async { - dynamic res = await matrix.connection.jsonRequest( + dynamic res = await client.connection.jsonRequest( type: "POST", action: - "/client/r0/rooms/${roomID}/leave"); - if (res is ErrorResponse) matrix.connection.onError.add(res); + "/client/r0/rooms/${id}/leave"); + if (res is ErrorResponse) client.connection.onError.add(res); return res; } + /// Call the Matrix API to forget this room if you already left it. Future forget() async { - dynamic res = await matrix.connection.jsonRequest( + dynamic res = await client.connection.jsonRequest( type: "POST", action: - "/client/r0/rooms/${roomID}/forget"); - if (res is ErrorResponse) matrix.connection.onError.add(res); + "/client/r0/rooms/${id}/forget"); + if (res is ErrorResponse) client.connection.onError.add(res); return res; } + /// Call the Matrix API to kick a user from this room. Future kick(String userID) async { - dynamic res = await matrix.connection.jsonRequest( + dynamic res = await client.connection.jsonRequest( type: "POST", action: - "/client/r0/rooms/${roomID}/kick", + "/client/r0/rooms/${id}/kick", data: {"user_id": userID}); - if (res is ErrorResponse) matrix.connection.onError.add(res); + if (res is ErrorResponse) client.connection.onError.add(res); return res; } + /// Call the Matrix API to ban a user from this room. Future ban(String userID) async { - dynamic res = await matrix.connection.jsonRequest( + dynamic res = await client.connection.jsonRequest( type: "POST", action: - "/client/r0/rooms/${roomID}/ban", + "/client/r0/rooms/${id}/ban", data: {"user_id": userID}); - if (res is ErrorResponse) matrix.connection.onError.add(res); + if (res is ErrorResponse) client.connection.onError.add(res); return res; } + /// Call the Matrix API to unban a banned user from this room. Future unban(String userID) async { - dynamic res = await matrix.connection.jsonRequest( + dynamic res = await client.connection.jsonRequest( type: "POST", action: - "/client/r0/rooms/${roomID}/unban", + "/client/r0/rooms/${id}/unban", data: {"user_id": userID}); - if (res is ErrorResponse) matrix.connection.onError.add(res); + if (res is ErrorResponse) client.connection.onError.add(res); return res; } + /// Call the Matrix API to invite a user to this room. Future invite(String userID) async { - dynamic res = await matrix.connection.jsonRequest( + dynamic res = await client.connection.jsonRequest( type: "POST", action: - "/client/r0/rooms/${roomID}/invite", + "/client/r0/rooms/${id}/invite", data: {"user_id": userID}); - if (res is ErrorResponse) matrix.connection.onError.add(res); + if (res is ErrorResponse) client.connection.onError.add(res); return res; } + /// Returns a Room from a json String which comes normally from the store. static Future getRoomFromTableRow( Map row, Client matrix) async { String name = row["topic"]; @@ -170,37 +259,60 @@ class Room { avatarMxcUrl = await matrix.store.getAvatarFromSingleChat(row["id"]); return Room( - roomID: row["id"], + id: row["id"], name: name, - lastMessage: content_body, avatar: MxContent(avatarMxcUrl), - timeCreated: ChatTime(row["origin_server_ts"]), notificationCount: row["notification_count"], highlightCount: row["highlight_count"], topic: "", - matrix: matrix, + client: matrix, events: [], + participants: [], ); } + @Deprecated("Use client.store.getRoomById(String id) instead!") static Future getRoomById(String id, Client matrix) async { - List> res = - await matrix.store.db.rawQuery("SELECT * FROM Rooms WHERE id=?", [id]); - if (res.length != 1) return null; - return getRoomFromTableRow(res[0], matrix); + Room room = await matrix.store.getRoomById(id); + return room; } + /// Load a room from the store including all room events. static Future loadRoomEvents(String id, Client matrix) async { - Room room = await Room.getRoomById(id, matrix); - room.events = await Event.getEventList(matrix, id); + Room room = await matrix.store.getRoomById(id); + await room.loadEvents(); return room; } + /// Load all events for a given room from the store. This includes all + /// senders of those events, who will be added to the participants list. + Future> loadEvents() async { + this.events = await client.store.getEventList(id); + + Map participantMap = {}; + for (num i = 0; i < events.length; i++) { + if (!participantMap.containsKey(events[i].sender.mxid)) { + participants.add(events[i].sender); + participantMap[events[i].sender.mxid] = true; + } + } + + return this.events; + } + + /// Load all participants for a given room from the store. + Future> loadParticipants() async { + this.participants = await client.store.loadParticipants(id); + return this.participants; + } + + /// Request the full list of participants from the server. The local list + /// from the store is not complete if the client uses lazy loading. Future> requestParticipants(Client matrix) async { List participants = []; dynamic res = await matrix.connection.jsonRequest( - type: "GET", action: "/client/r0/rooms/${roomID}/members"); + type: "GET", action: "/client/r0/rooms/${id}/members"); if (res is ErrorResponse || !(res["chunk"] is List)) return participants; @@ -214,6 +326,8 @@ class Room { if (newUser.status != "leave") participants.add(newUser); } - return participants; + this.participants = participants; + + return this.participants; } } diff --git a/lib/src/Store.dart b/lib/src/Store.dart index b5089d5..34b66d0 100644 --- a/lib/src/Store.dart +++ b/lib/src/Store.dart @@ -31,6 +31,7 @@ import 'sync/RoomUpdate.dart'; import 'Client.dart'; import 'User.dart'; import 'Room.dart'; +import 'Event.dart'; import 'Connection.dart'; /// Responsible to store all data persistent and to query objects from the @@ -414,6 +415,24 @@ class Store { return participants; } + /// Returns a list of events for the given room and sets all participants. + Future> getEventList(String roomID) async{ + List> eventRes = await db.rawQuery( + "SELECT * " + + " FROM Events events, Participants participants " + + " WHERE events.chat_id=?" + + " AND events.sender=participants.matrix_id " + + " GROUP BY events.id " + + " ORDER BY origin_server_ts DESC", + [roomID]); + + List eventList = []; + + for (num i = 0; i < eventRes.length; i++) + eventList.add(Event.fromJson(eventRes[i])); + return eventList; + } + /// Returns all rooms, the client is participating. Excludes left rooms. Future> getRoomList() async { List> res = await db.rawQuery( @@ -436,6 +455,15 @@ class Store { return roomList; } + + /// Returns a room without events and participants. + Future getRoomById(String id) async { + List> res = + await db.rawQuery("SELECT * FROM Rooms WHERE id=?", [id]); + if (res.length != 1) return null; + return Room.getRoomFromTableRow(res[0], client); + } + /// Calculates and returns an avatar for a direct chat by a given [roomID]. Future getAvatarFromSingleChat( String roomID) async {