Add support for creating chats and direct chats

This commit is contained in:
Christian Pauly 2019-06-11 13:09:26 +02:00
parent 4833b9027f
commit 037953be34
3 changed files with 132 additions and 82 deletions

View file

@ -26,12 +26,12 @@ import 'dart:core';
import 'responses/ErrorResponse.dart'; import 'responses/ErrorResponse.dart';
import 'Connection.dart'; import 'Connection.dart';
import 'Store.dart'; import 'Store.dart';
import 'User.dart';
/// Represents a Matrix client to communicate with a /// Represents a Matrix client to communicate with a
/// [Matrix](https://matrix.org) homeserver and is the entry point for this /// [Matrix](https://matrix.org) homeserver and is the entry point for this
/// SDK. /// SDK.
class Client { class Client {
/// Handles the connection for this client. /// Handles the connection for this client.
Connection connection; Connection connection;
@ -41,8 +41,7 @@ class Client {
Client(this.clientName) { Client(this.clientName) {
connection = Connection(this); connection = Connection(this);
if (this.clientName != "testclient") if (this.clientName != "testclient") store = Store(this);
store = Store(this);
connection.onLoginStateChanged.stream.listen((loginState) { connection.onLoginStateChanged.stream.listen((loginState) {
print("LoginState: ${loginState.toString()}"); print("LoginState: ${loginState.toString()}");
}); });
@ -142,9 +141,8 @@ class Client {
/// Handles the login and allows the client to call all APIs which require /// Handles the login and allows the client to call all APIs which require
/// authentication. Returns false if the login was not successful. /// authentication. Returns false if the login was not successful.
Future<bool> login(String username, String password) async { Future<bool> login(String username, String password) async {
final loginResp = await connection
final loginResp = .jsonRequest(type: "POST", action: "/client/r0/login", data: {
await connection.jsonRequest(type: "POST", action: "/client/r0/login", data: {
"type": "m.login.password", "type": "m.login.password",
"user": username, "user": username,
"identifier": { "identifier": {
@ -180,11 +178,24 @@ class Client {
/// Sends a logout command to the homeserver and clears all local data, /// Sends a logout command to the homeserver and clears all local data,
/// including all persistent data from the store. /// including all persistent data from the store.
Future<void> logout() async { Future<void> logout() async {
final dynamic resp = final dynamic resp = await connection.jsonRequest(
await connection.jsonRequest(type: "POST", action: "/client/r0/logout/all"); type: "POST", action: "/client/r0/logout/all");
if (resp == null) return; if (resp == null) return;
await connection.clear(); await connection.clear();
} }
/// Creates a new group chat and invites the given Users and returns the new
/// created room ID.
Future<String> createGroup(List<User> users) async {
List<String> inviteIDs = [];
for (int i = 0; i < users.length; i++) inviteIDs.add(users[i].id);
Map<String, dynamic> resp = await connection.jsonRequest(
type: "POST",
action: "/client/r0/createRoom",
data: {"invite": inviteIDs, "preset": "private_chat"});
return resp["room_id"];
}
} }

View file

@ -37,7 +37,6 @@ import 'Connection.dart';
/// Responsible to store all data persistent and to query objects from the /// Responsible to store all data persistent and to query objects from the
/// database. /// database.
class Store { class Store {
final Client client; final Client client;
Store(this.client) { Store(this.client) {
@ -56,8 +55,7 @@ class Store {
_db = await openDatabase(path, version: 4, _db = await openDatabase(path, version: 4,
onCreate: (Database db, int version) async { onCreate: (Database db, int version) async {
await createTables(db); await createTables(db);
}, }, onUpgrade: (Database db, int oldVersion, int newVersion) async {
onUpgrade: (Database db, int oldVersion, int newVersion) async{
print("Migrate databse from version $oldVersion to $newVersion"); print("Migrate databse from version $oldVersion to $newVersion");
if (oldVersion != newVersion) { if (oldVersion != newVersion) {
await db.execute("DROP TABLE IF EXISTS Rooms"); await db.execute("DROP TABLE IF EXISTS Rooms");
@ -82,14 +80,15 @@ class Store {
newDeviceName: clientList["device_name"], newDeviceName: clientList["device_name"],
newLazyLoadMembers: clientList["lazy_load_members"] == 1, newLazyLoadMembers: clientList["lazy_load_members"] == 1,
newMatrixVersions: clientList["matrix_versions"].toString().split(","), newMatrixVersions: clientList["matrix_versions"].toString().split(","),
newPrevBatch: clientList["prev_batch"].toString().isEmpty ? null : clientList["prev_batch"], newPrevBatch: clientList["prev_batch"].toString().isEmpty
? null
: clientList["prev_batch"],
); );
print("Restore client credentials of ${client.userID}"); print("Restore client credentials of ${client.userID}");
} else } else
client.connection.onLoginStateChanged.add(LoginState.loggedOut); client.connection.onLoginStateChanged.add(LoginState.loggedOut);
} }
Future<void> createTables(Database db) async { Future<void> createTables(Database db) async {
await db.execute(ClientsScheme); await db.execute(ClientsScheme);
await db.execute(RoomsScheme); await db.execute(RoomsScheme);
@ -98,7 +97,8 @@ class Store {
} }
Future<String> queryPrevBatch() async { Future<String> queryPrevBatch() async {
List<Map> list = await txn.rawQuery("SELECT prev_batch FROM Clients WHERE client=?", [client.clientName]); List<Map> list = await txn.rawQuery(
"SELECT prev_batch FROM Clients WHERE client=?", [client.clientName]);
return list[0]["prev_batch"]; return list[0]["prev_batch"];
} }
@ -121,7 +121,8 @@ class Store {
/// Clears all tables from the database. /// Clears all tables from the database.
Future<void> clear() async { Future<void> clear() async {
await _db.rawDelete("DELETE FROM Clients WHERE client=?", [client.clientName]); await _db
.rawDelete("DELETE FROM Clients WHERE client=?", [client.clientName]);
await _db.rawDelete("DELETE FROM Rooms"); await _db.rawDelete("DELETE FROM Rooms");
await _db.rawDelete("DELETE FROM User"); await _db.rawDelete("DELETE FROM User");
await _db.rawDelete("DELETE FROM Events"); await _db.rawDelete("DELETE FROM Events");
@ -211,6 +212,17 @@ class Store {
if (type == "history") return null; if (type == "history") return null;
switch (eventUpdate.eventType) { switch (eventUpdate.eventType) {
case "m.direct":
if (eventUpdate.content["content"] is Map<String, List<String>>) {
Map<String, List<String>> directMap = eventUpdate.content["content"];
directMap.forEach((String key, List<String> value) {
if (value.length > 0)
txn.rawUpdate(
"UPDATE Rooms SET direct_chat_matrix_id=? WHERE id=?",
[key, value[0]]);
});
}
break;
case "m.receipt": case "m.receipt":
if (eventContent["user"] == client.userID) { if (eventContent["user"] == client.userID) {
txn.rawUpdate("UPDATE Rooms SET unread=? WHERE id=?", txn.rawUpdate("UPDATE Rooms SET unread=? WHERE id=?",
@ -379,8 +391,7 @@ class Store {
} }
/// Returns a User object by a given Matrix ID and a Room. /// Returns a User object by a given Matrix ID and a Room.
Future<User> getUser( Future<User> getUser({String matrixID, Room room}) async {
{String matrixID, Room room}) async {
List<Map<String, dynamic>> res = await db.rawQuery( List<Map<String, dynamic>> res = await db.rawQuery(
"SELECT * FROM User WHERE matrix_id=? AND chat_id=?", "SELECT * FROM User WHERE matrix_id=? AND chat_id=?",
[matrixID, room.id]); [matrixID, room.id]);
@ -394,7 +405,8 @@ class Store {
"SELECT * FROM User WHERE matrix_id!=? GROUP BY matrix_id ORDER BY displayname", "SELECT * FROM User WHERE matrix_id!=? GROUP BY matrix_id ORDER BY displayname",
[client.userID]); [client.userID]);
List<User> userList = []; List<User> userList = [];
for (int i = 0; i < res.length; i++) userList.add(User.fromJson(res[i], null)); for (int i = 0; i < res.length; i++)
userList.add(User.fromJson(res[i], null));
return userList; return userList;
} }
@ -435,14 +447,18 @@ class Store {
} }
/// Returns all rooms, the client is participating. Excludes left rooms. /// Returns all rooms, the client is participating. Excludes left rooms.
Future<List<Room>> getRoomList({bool onlyLeft = false, bool onlyDirect = false, bool onlyGroups=false}) async { Future<List<Room>> getRoomList(
{bool onlyLeft = false,
bool onlyDirect = false,
bool onlyGroups = false}) async {
if (onlyDirect && onlyGroups) return []; if (onlyDirect && onlyGroups) return [];
List<Map<String, dynamic>> res = await db.rawQuery( List<Map<String, dynamic>> res = await db.rawQuery("SELECT * " +
"SELECT * " +
" FROM Rooms rooms LEFT JOIN Events events " + " FROM Rooms rooms LEFT JOIN Events events " +
" ON rooms.id=events.chat_id " + " ON rooms.id=events.chat_id " +
" WHERE rooms.id!='' " + " WHERE rooms.id!='' " +
" AND rooms.membership" + (onlyLeft ? "=" : "!=") +"'left' " + " AND rooms.membership" +
(onlyLeft ? "=" : "!=") +
"'left' " +
(onlyDirect ? " AND rooms.direct_chat_matrix_id!= '' " : "") + (onlyDirect ? " AND rooms.direct_chat_matrix_id!= '' " : "") +
(onlyGroups ? " AND rooms.direct_chat_matrix_id= '' " : "") + (onlyGroups ? " AND rooms.direct_chat_matrix_id= '' " : "") +
" GROUP BY rooms.id " + " GROUP BY rooms.id " +
@ -459,7 +475,6 @@ class Store {
return roomList; return roomList;
} }
/// Returns a room without events and participants. /// Returns a room without events and participants.
Future<Room> getRoomById(String id) async { Future<Room> getRoomById(String id) async {
List<Map<String, dynamic>> res = List<Map<String, dynamic>> res =
@ -469,8 +484,7 @@ class Store {
} }
/// Calculates and returns an avatar for a direct chat by a given [roomID]. /// Calculates and returns an avatar for a direct chat by a given [roomID].
Future<String> getAvatarFromSingleChat( Future<String> getAvatarFromSingleChat(String roomID) async {
String roomID) async {
String avatarStr = ""; String avatarStr = "";
List<Map<String, dynamic>> res = await db.rawQuery( List<Map<String, dynamic>> res = await db.rawQuery(
"SELECT avatar_url FROM User " + "SELECT avatar_url FROM User " +
@ -485,8 +499,7 @@ class Store {
/// Calculates a chat name for a groupchat without a name. The chat name will /// Calculates a chat name for a groupchat without a name. The chat name will
/// be the name of all users (excluding the user of this client) divided by /// be the name of all users (excluding the user of this client) divided by
/// ','. /// ','.
Future<String> getChatNameFromMemberNames( Future<String> getChatNameFromMemberNames(String roomID) async {
String roomID) async {
String displayname = 'Empty chat'; String displayname = 'Empty chat';
List<Map<String, dynamic>> rs = await db.rawQuery( List<Map<String, dynamic>> rs = await db.rawQuery(
"SELECT User.displayname, User.matrix_id, User.membership FROM User " + "SELECT User.displayname, User.matrix_id, User.membership FROM User " +
@ -509,6 +522,15 @@ class Store {
return displayname; return displayname;
} }
/// Returns the (first) room ID from the store which is a private chat with
/// the user [userID]. Returns null if there is none.
Future<String> getDirectChatRoomID(String userID) async {
List<Map<String, dynamic>> res = await db.rawQuery(
"SELECT id FROM Rooms WHERE direct_chat_matrix_id=?", [userID]);
if (res.length != 1) return null;
return res[0]["id"];
}
/// The database sheme for the Client class. /// The database sheme for the Client class.
static final String ClientsScheme = 'CREATE TABLE IF NOT EXISTS Clients(' + static final String ClientsScheme = 'CREATE TABLE IF NOT EXISTS Clients(' +
'client TEXT PRIMARY KEY, ' + 'client TEXT PRIMARY KEY, ' +

View file

@ -104,4 +104,21 @@ class User {
dynamic res = await room.unban(id); dynamic res = await room.unban(id);
return res; return res;
} }
/// Returns an existing direct chat with this user or creates a new one.
Future<String> startDirectChat() async {
// Try to find an existing direct chat
String roomID = await room.client?.store.getDirectChatRoomID(id);
if (roomID != null) return roomID;
// Start a new direct chat
Map<String,dynamic> resp = await room.client.connection.jsonRequest(type: "POST", action: "/client/r0/createRoom", data: {
"invite": [ id ],
"is_direct": true,
"preset": "trusted_private_chat"
});
return resp["room_id"];
}
} }