diff --git a/lib/src/client.dart b/lib/src/client.dart index 30372237..e6da4ba7 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -556,24 +556,100 @@ class Client extends MatrixApi { } /// Returns an existing direct room ID with this user or creates a new one. - /// Returns null on error. - Future startDirectChat(String mxid) async { + /// By default encryption will be enabled if the client supports encryption + /// and the other user has uploaded any encryption keys. + Future startDirectChat( + String mxid, { + bool? enableEncryption, + List? initialState, + bool waitForSync = true, + }) async { // Try to find an existing direct chat - var roomId = getDirectChatFromUserId(mxid); - if (roomId != null) return roomId; + final directChatRoomId = getDirectChatFromUserId(mxid); + if (directChatRoomId != null) return directChatRoomId; + + enableEncryption ??= await userOwnsEncryptionKeys(mxid); + if (enableEncryption) { + initialState ??= []; + initialState.add(StateEvent( + content: { + 'algorithm': supportedGroupEncryptionAlgorithms.first, + }, + type: EventTypes.Encryption, + )); + } // Start a new direct chat - roomId = await createRoom( + final roomId = await createRoom( invite: [mxid], isDirect: true, preset: CreateRoomPreset.trustedPrivateChat, + initialState: initialState, ); + if (waitForSync) { + if (getRoomById(roomId) == null) { + // Wait for room actually appears in sync + await onSync.stream.firstWhere( + (sync) => sync.rooms?.join?.containsKey(roomId) ?? false); + } + } + await Room(id: roomId, client: this).addToDirectChat(mxid); return roomId; } + /// Simplified method to create a new group chat. By default it is a private + /// chat. The encryption is enabled if this client supports encryption and + /// the preset is not a public chat. + Future createGroupChat({ + String? groupName, + bool? enableEncryption, + List? invite, + CreateRoomPreset preset = CreateRoomPreset.privateChat, + List? initialState, + bool waitForSync = true, + }) async { + enableEncryption ??= + encryptionEnabled && preset != CreateRoomPreset.publicChat; + if (enableEncryption) { + initialState ??= []; + initialState.add(StateEvent( + content: { + 'algorithm': supportedGroupEncryptionAlgorithms.first, + }, + type: EventTypes.Encryption, + )); + } + final roomId = await createRoom( + invite: invite, + preset: preset, + name: groupName, + initialState: initialState, + ); + + if (waitForSync) { + if (getRoomById(roomId) == null) { + // Wait for room actually appears in sync + await onSync.stream.firstWhere( + (sync) => sync.rooms?.join?.containsKey(roomId) ?? false); + } + } + return roomId; + } + + /// Checks if the given user has encryption keys. May query keys from the + /// server to answer this. + Future userOwnsEncryptionKeys(String userId) async { + if (userId == userID) return encryptionEnabled; + if (_userDeviceKeys.containsKey(userId)) { + return true; + } + final keys = await queryKeys({userId: []}); + return keys.deviceKeys?.isNotEmpty ?? false; + } + /// Creates a new space and returns the Room ID. The parameters are mostly /// the same like in [createRoom()]. /// Be aware that spaces appear in the [rooms] list. You should check if a diff --git a/lib/src/user.dart b/lib/src/user.dart index e980b64b..8147a0d7 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -148,7 +148,17 @@ class User extends Event { /// Returns an existing direct chat ID with this user or creates a new one. /// Returns null on error. - Future startDirectChat() async => room.client.startDirectChat(id); + Future startDirectChat({ + bool? enableEncryption, + List? initialState, + bool waitForSync = true, + }) async => + room.client.startDirectChat( + id, + enableEncryption: enableEncryption, + initialState: initialState, + waitForSync: waitForSync, + ); /// The newest presence of this user if there is any and null if not. Presence? get presence => room.client.presences[id]; diff --git a/test/client_test.dart b/test/client_test.dart index 09a720dd..7e11af25 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -560,6 +560,12 @@ void main() { bunnyContent); await client.dispose(closeDatabase: true); }); + test('startDirectChat', () async { + await matrix.startDirectChat('@alice:example.com', waitForSync: false); + }); + test('createGroupChat', () async { + await matrix.createGroupChat(groupName: 'Testgroup', waitForSync: false); + }); test('Test the fake store api', () async { final database = await getDatabase(null); final client1 = Client( diff --git a/test/user_test.dart b/test/user_test.dart index 61e09978..0518811c 100644 --- a/test/user_test.dart +++ b/test/user_test.dart @@ -118,7 +118,7 @@ void main() { await user1.setPower(50); }); test('startDirectChat', () async { - await user1.startDirectChat(); + await user1.startDirectChat(waitForSync: false); }); test('getPresence', () async { await client.handleSync(SyncUpdate.fromJson({