diff --git a/CHANGES.md b/CHANGES.md index d91be2ef2b..26418c75f1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,31 @@ -Changes in Element 1.0.10 (2020-XX-XX) +Changes in Element 1.0.11 (2020-XX-XX) =================================================== Features ✨: - +Improvements 🙌: + - Open an existing DM instead of creating a new one (#2319) + +Bugfix 🐛: + - Fix issue when updating the avatar of a room (new avatar vanishing) + - Discard change dialog displayed by mistake when avatar has been updated + +Translations 🗣: + - + +SDK API changes ⚠️: + - AccountService now exposes suspendable function instead of using MatrixCallback (#2354). Note: We will incrementally migrate all the SDK API in a near future. + +Build 🧱: + - + +Other changes: + - Upgrade Realm dependency to 10.0.0 + +Changes in Element 1.0.10 (2020-11-04) +=================================================== + Improvements 🙌: - Rework sending Event management (#154) - New room creation screen: set topic and avatar in the room creation form (#2078) @@ -28,18 +50,6 @@ Bugfix 🐛: - KeysBackup: Avoid using `!!` (#2262) - Two elements in the task switcher (#2299) -Translations 🗣: - - - -SDK API changes ⚠️: - - - -Build 🧱: - - - -Other changes: - - - Changes in Element 1.0.9 (2020-10-16) =================================================== diff --git a/fastlane/metadata/android/en-US/changelogs/40100100.txt b/fastlane/metadata/android/en-US/changelogs/40100100.txt index 0ffdd02fcb..c1821d2475 100644 --- a/fastlane/metadata/android/en-US/changelogs/40100100.txt +++ b/fastlane/metadata/android/en-US/changelogs/40100100.txt @@ -1 +1,2 @@ -// TODO \ No newline at end of file +This new version mainly contains bug fixes and improvements. Sending a message is now much faster. +Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.0.10 \ No newline at end of file diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index e4a8b8884a..29c709844a 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -9,7 +9,7 @@ buildscript { jcenter() } dependencies { - classpath "io.realm:realm-gradle-plugin:6.1.0" + classpath "io.realm:realm-gradle-plugin:10.0.0" } } @@ -149,7 +149,7 @@ dependencies { implementation 'androidx.exifinterface:exifinterface:1.3.0' // Database - implementation 'com.github.Zhuinden:realm-monarchy:0.5.1' + implementation 'com.github.Zhuinden:realm-monarchy:0.7.1' kapt 'dk.ilios:realmfieldnameshelper:1.1.1' // Work diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt index ec5477f976..103b638c39 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt @@ -43,8 +43,8 @@ class ChangePasswordTest : InstrumentedTest { val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false)) // Change password - commonTestHelper.doSync { - session.changePassword(TestConstants.PASSWORD, NEW_PASSWORD, it) + commonTestHelper.runBlockingTest { + session.changePassword(TestConstants.PASSWORD, NEW_PASSWORD) } // Try to login with the previous password, it will fail diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt index a6fbfd9b7a..9996eef0a8 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt @@ -43,8 +43,8 @@ class DeactivateAccountTest : InstrumentedTest { val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false)) // Deactivate the account - commonTestHelper.doSync { - session.deactivateAccount(TestConstants.PASSWORD, false, it) + commonTestHelper.runBlockingTest { + session.deactivateAccount(TestConstants.PASSWORD, false) } // Try to login on the previous account, it will fail (M_USER_DEACTIVATED) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index cbe4cca8a3..0e7088a6a5 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -40,6 +40,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue @@ -343,6 +344,14 @@ class CommonTestHelper(context: Context) { await(latch, timeout) } + fun runBlockingTest(timeout: Long = TestConstants.timeOutMillis, block: suspend () -> T): T { + return runBlocking { + withTimeout(timeout) { + block() + } + } + } + // Transform a method with a MatrixCallback to a synchronous method inline fun doSync(timeout: Long? = TestConstants.timeOutMillis, block: (MatrixCallback) -> Unit): T { val lock = CountDownLatch(1) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt index 75c7ad4d53..a81f503e77 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt @@ -555,7 +555,7 @@ class SASTest : InstrumentedTest { mTestHelper.waitWithLatch { mTestHelper.retryPeriodicallyWithLatch(it) { - val prAlicePOV = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId)?.firstOrNull() + val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull() requestID = prAlicePOV?.transactionId Log.v("TEST", "== alicePOV is $prAlicePOV") prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId @@ -566,7 +566,7 @@ class SASTest : InstrumentedTest { mTestHelper.waitWithLatch { mTestHelper.retryPeriodicallyWithLatch(it) { - val prBobPOV = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId)?.firstOrNull() + val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull() Log.v("TEST", "== prBobPOV is $prBobPOV") prBobPOV?.transactionId == requestID } @@ -581,7 +581,7 @@ class SASTest : InstrumentedTest { // wait for alice to get the ready mTestHelper.waitWithLatch { mTestHelper.retryPeriodicallyWithLatch(it) { - val prAlicePOV = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId)?.firstOrNull() + val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull() Log.v("TEST", "== prAlicePOV is $prAlicePOV") prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt index b05f0036b2..8915202f35 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt @@ -16,9 +16,6 @@ package org.matrix.android.sdk.api.session.account -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - /** * This interface defines methods to manage the account. It's implemented at the session level. */ @@ -28,7 +25,7 @@ interface AccountService { * @param password Current password. * @param newPassword New password */ - fun changePassword(password: String, newPassword: String, callback: MatrixCallback): Cancelable + suspend fun changePassword(password: String, newPassword: String) /** * Deactivate the account. @@ -46,5 +43,5 @@ interface AccountService { * @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see * an incomplete view of conversations */ - fun deactivateAccount(password: String, eraseAllData: Boolean, callback: MatrixCallback): Cancelable + suspend fun deactivateAccount(password: String, eraseAllData: Boolean) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt index 65c6a1f1f7..2413786ea9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt @@ -41,7 +41,7 @@ interface VerificationService { fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? - fun getExistingVerificationRequest(otherUserId: String): List? + fun getExistingVerificationRequests(otherUserId: String): List fun getExistingVerificationRequest(otherUserId: String, tid: String?): PendingVerificationRequest? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index a8580bab8e..a786ebd4b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -123,6 +123,7 @@ internal abstract class CryptoModule { } .name("crypto_store.realm") .modules(RealmCryptoStoreModule()) + .allowWritesOnUiThread(true) .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) .migration(realmCryptoStoreMigration) .build() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt index 7f02750359..29ddd92213 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt @@ -537,11 +537,10 @@ internal class DefaultVerificationService @Inject constructor( // If there is a corresponding request, we can auto accept // as we are the one requesting in first place (or we accepted the request) // I need to check if the pending request was related to this device also - val autoAccept = getExistingVerificationRequest(otherUserId)?.any { + val autoAccept = getExistingVerificationRequests(otherUserId).any { it.transactionId == startReq.transactionId && (it.requestInfo?.fromDevice == this.deviceId || it.readyInfo?.fromDevice == this.deviceId) } - ?: false val tx = DefaultIncomingSASDefaultVerificationTransaction( // this, setDeviceVerificationAction, @@ -837,8 +836,8 @@ internal class DefaultVerificationService @Inject constructor( // SAS do not care for now? } - // Now transactions are udated, let's also update Requests - val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneReq.transactionId } + // Now transactions are updated, let's also update Requests + val existingRequest = getExistingVerificationRequests(senderId).find { it.transactionId == doneReq.transactionId } if (existingRequest == null) { Timber.e("## SAS Received Done for unknown request txId:${doneReq.transactionId}") return @@ -892,7 +891,7 @@ internal class DefaultVerificationService @Inject constructor( private fun handleReadyReceived(senderId: String, readyReq: ValidVerificationInfoReady, transportCreator: (DefaultVerificationTransaction) -> VerificationTransport) { - val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionId } + val existingRequest = getExistingVerificationRequests(senderId).find { it.transactionId == readyReq.transactionId } if (existingRequest == null) { Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionId} fromDevice ${readyReq.fromDevice}") return @@ -1041,9 +1040,9 @@ internal class DefaultVerificationService @Inject constructor( } } - override fun getExistingVerificationRequest(otherUserId: String): List? { + override fun getExistingVerificationRequests(otherUserId: String): List { synchronized(lock = pendingRequests) { - return pendingRequests[otherUserId] + return pendingRequests[otherUserId].orEmpty() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt index 6c430e8591..0e3a7a2c49 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database import android.content.Context import android.util.Base64 import androidx.core.content.edit +import io.realm.Realm import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.internal.session.securestorage.SecretStoringUtils import io.realm.RealmConfiguration @@ -46,7 +47,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context, private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.keys", Context.MODE_PRIVATE) private fun generateKeyForRealm(): ByteArray { - val keyForRealm = ByteArray(RealmConfiguration.KEY_LENGTH) + val keyForRealm = ByteArray(Realm.ENCRYPTION_KEY_LENGTH) rng.nextBytes(keyForRealm) return keyForRealm } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index 3324520d29..244fe3432a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -69,6 +69,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( .apply { realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) } + .allowWritesOnUiThread(true) .modules(SessionRealmModule()) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) .migration(migration) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt index 31a39d45e5..1165d2116b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt @@ -16,30 +16,17 @@ package org.matrix.android.sdk.internal.session.account -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.account.AccountService -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import javax.inject.Inject internal class DefaultAccountService @Inject constructor(private val changePasswordTask: ChangePasswordTask, - private val deactivateAccountTask: DeactivateAccountTask, - private val taskExecutor: TaskExecutor) : AccountService { + private val deactivateAccountTask: DeactivateAccountTask) : AccountService { - override fun changePassword(password: String, newPassword: String, callback: MatrixCallback): Cancelable { - return changePasswordTask - .configureWith(ChangePasswordTask.Params(password, newPassword)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun changePassword(password: String, newPassword: String) { + changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword)) } - override fun deactivateAccount(password: String, eraseAllData: Boolean, callback: MatrixCallback): Cancelable { - return deactivateAccountTask - .configureWith(DeactivateAccountTask.Params(password, eraseAllData)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun deactivateAccount(password: String, eraseAllData: Boolean) { + deactivateAccountTask.execute(DeactivateAccountTask.Params(password, eraseAllData)) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt index 1bb57b47cd..e140cc19f3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt @@ -66,6 +66,7 @@ internal abstract class IdentityModule { .apply { realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) } + .allowWritesOnUiThread(true) .modules(IdentityRealmModule()) .build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 65d375e176..3463b26c8a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import org.matrix.android.sdk.internal.util.awaitCallback internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, private val stateEventDataSource: StateEventDataSource, @@ -132,23 +133,23 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback): Cancelable { return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg") - sendStateEvent( - eventType = EventType.STATE_ROOM_AVATAR, - body = mapOf("url" to response.contentUri), - callback = callback, - stateKey = null - ) + awaitCallback { + sendStateEvent( + eventType = EventType.STATE_ROOM_AVATAR, + body = mapOf("url" to response.contentUri), + callback = it, + stateKey = null + ) + } } } override fun deleteAvatar(callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - sendStateEvent( - eventType = EventType.STATE_ROOM_AVATAR, - body = emptyMap(), - callback = callback, - stateKey = null - ) - } + return sendStateEvent( + eventType = EventType.STATE_ROOM_AVATAR, + body = emptyMap(), + callback = callback, + stateKey = null + ) } } diff --git a/matrix-sdk-android/src/main/res/values-it/strings_sas.xml b/matrix-sdk-android/src/main/res/values-it/strings_sas.xml new file mode 100644 index 0000000000..b66c22bc6b --- /dev/null +++ b/matrix-sdk-android/src/main/res/values-it/strings_sas.xml @@ -0,0 +1,68 @@ + + + + Cane + Gatto + Leone + Cavallo + Unicorno + Maiale + Elefante + Coniglio + Panda + Gallo + Pinguino + Tartaruga + Pesce + Polpo + Farfalla + Fiore + Albero + Cactus + Fungo + Globo + Luna + Nuvola + Fuoco + Banana + Mela + Fragola + Mais + Pizza + Torta + Cuore + Faccina sorridente + Robot + Cappello + Occhiali + Chiave inglese + Babbo Natale + Pollice alzato + Ombrello + Clessidra + Orologio + Regalo + Lampadina + Libro + Matita + Graffetta + Forbici + Lucchetto + Chiave + Martello + Telefono + Bandiera + Treno + Bicicletta + Aeroplano + Razzo + Trofeo + Palla + Chitarra + Trombetta + Campana + Ancora + Cuffie + Cartella + Puntina + diff --git a/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt index 3f24a28c28..64df788e53 100644 --- a/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt +++ b/multipicker/src/main/java/im/vector/lib/multipicker/CameraPicker.kt @@ -94,19 +94,21 @@ class CameraPicker { return Intent(MediaStore.ACTION_IMAGE_CAPTURE) } - private fun createPhotoUri(context: Context): Uri { - val file = createImageFile(context) - val authority = context.packageName + ".multipicker.fileprovider" - return FileProvider.getUriForFile(context, authority, file) - } + companion object { + fun createPhotoUri(context: Context): Uri { + val file = createImageFile(context) + val authority = context.packageName + ".multipicker.fileprovider" + return FileProvider.getUriForFile(context, authority, file) + } - private fun createImageFile(context: Context): File { - val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - val storageDir: File = context.filesDir - return File.createTempFile( - "${timeStamp}_", /* prefix */ - ".jpg", /* suffix */ - storageDir /* directory */ - ) + private fun createImageFile(context: Context): File { + val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val storageDir: File = context.filesDir + return File.createTempFile( + "${timeStamp}_", /* prefix */ + ".jpg", /* suffix */ + storageDir /* directory */ + ) + } } } diff --git a/vector/build.gradle b/vector/build.gradle index 96b4994a7a..ca7cb12e31 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -17,7 +17,7 @@ androidExtensions { // Note: 2 digits max for each value ext.versionMajor = 1 ext.versionMinor = 0 -ext.versionPatch = 10 +ext.versionPatch = 11 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' diff --git a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt index 7198cdb4a2..8cb122d5bb 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt @@ -23,6 +23,7 @@ import androidx.core.net.toUri import androidx.fragment.app.Fragment import com.yalantis.ucrop.UCrop import im.vector.app.R +import im.vector.app.core.extensions.insertBeforeLast import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO @@ -86,7 +87,7 @@ class GalleryOrCameraDialogHelper( } private fun startUCrop(image: MultiPickerImageType) { - val destinationFile = File(activity.cacheDir, "${image.displayName}_e_${System.currentTimeMillis()}") + val destinationFile = File(activity.cacheDir, image.displayName.insertBeforeLast("_e_${System.currentTimeMillis()}")) val uri = image.contentUri createUCropWithDefaultSettings(colorProvider, uri, destinationFile.toUri(), fragment.getString(R.string.rotate_and_crop_screen_title)) .withAspectRatio(1f, 1f) @@ -116,7 +117,7 @@ class GalleryOrCameraDialogHelper( when (type) { Type.Camera -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, activity, takePhotoPermissionActivityResultLauncher)) { - avatarCameraUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(activity, takePhotoActivityResultLauncher) + doOpenCamera() } Type.Gallery -> MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher) diff --git a/vector/src/main/java/im/vector/app/core/extensions/BasicExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/BasicExtensions.kt index c3e6520a46..07a684abef 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/BasicExtensions.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/BasicExtensions.kt @@ -48,3 +48,21 @@ fun CharSequence.isMsisdn(): Boolean { false } } + +/** + * Useful to append a String at the end of a filename but before the extension if any + * Ex: + * - "file.txt".insertBeforeLast("_foo") will return "file_foo.txt" + * - "file".insertBeforeLast("_foo") will return "file_foo" + * - "fi.le.txt".insertBeforeLast("_foo") will return "fi.le_foo.txt" + * - null.insertBeforeLast("_foo") will return "_foo" + */ +fun String?.insertBeforeLast(insert: String, delimiter: String = ".") : String { + if (this == null) return insert + val idx = lastIndexOf(delimiter) + return if (idx == -1) { + this + insert + } else { + replaceRange(idx, idx, insert) + } +} diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt index 9f3ba39bbe..ba0250724c 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt @@ -38,6 +38,7 @@ import com.airbnb.mvrx.withState import com.yalantis.ucrop.UCrop import im.vector.app.R import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.insertBeforeLast import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider @@ -170,7 +171,7 @@ class AttachmentsPreviewFragment @Inject constructor( private fun handleEditAction() = withState(viewModel) { val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState - val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}") + val destinationFile = File(requireContext().cacheDir, currentAttachment.name.insertBeforeLast("_edited_image_${System.currentTimeMillis()}")) val uri = currentAttachment.queryUri createUCropWithDefaultSettings(colorProvider, uri, destinationFile.toUri(), currentAttachment.name) .getIntent(requireContext()) diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt index f6fc3fed5b..ce91761fdd 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt @@ -20,5 +20,8 @@ import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.userdirectory.PendingInvitee sealed class CreateDirectRoomAction : VectorViewModelAction { - data class CreateRoomAndInviteSelectedUsers(val invitees: Set) : CreateDirectRoomAction() + data class CreateRoomAndInviteSelectedUsers( + val invitees: Set, + val existingDmRoomId: String? + ) : CreateDirectRoomAction() } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index a1bb12a84b..10ab1673e4 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -91,7 +91,8 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { KnownUsersFragment::class.java, KnownUsersFragmentArgs( title = getString(R.string.fab_menu_create_chat), - menuResId = R.menu.vector_create_direct_room + menuResId = R.menu.vector_create_direct_room, + isCreatingRoom = true ) ) } @@ -121,7 +122,10 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) { if (action.itemId == R.id.action_create_direct_room) { - viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(action.invitees)) + viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers( + action.invitees, + action.existingDmRoomId + )) } } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt index c42e10686f..be9449b77a 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt @@ -19,6 +19,7 @@ package im.vector.app.features.createdirect import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject @@ -56,7 +57,22 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted override fun handle(action: CreateDirectRoomAction) { when (action) { - is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> createRoomAndInviteSelectedUsers(action.invitees) + is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> onSubmitInvitees(action) + }.exhaustive + } + + /** + * If users already have a DM room then navigate to it instead of creating a new room. + */ + private fun onSubmitInvitees(action: CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers) { + if (action.existingDmRoomId != null) { + // Do not create a new DM, just tell that the creation is successful by passing the existing roomId + setState { + copy(createAndInviteState = Success(action.existingDmRoomId)) + } + } else { + // Create the DM + createRoomAndInviteSelectedUsers(action.invitees) } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index 951ff4ede6..c7533cd3df 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -140,6 +140,12 @@ class SharedSecureStorageViewModel @AssistedInject constructor( } private fun handleDoResetAll() { + // as we are going to reset, we'd better cancel all outgoing requests + // if not they could be accepted in the middle of the reset process + // and cause strange use cases + session.cryptoService().verificationService().getExistingVerificationRequests(session.myUserId).forEach { + session.cryptoService().verificationService().cancelVerificationRequest(it) + } _viewEvents.post(SharedSecureStorageViewEvent.ShowResetBottomSheet) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt index b7c689f41f..47e373ed0a 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -237,7 +237,7 @@ class BootstrapCrossSigningTask @Inject constructor( Timber.d("## BootstrapCrossSigningTask: Creating 4S - Checking megolm backup") // First ensure that in sync - val serverVersion = awaitCallback { + var serverVersion = awaitCallback { session.cryptoService().keysBackupService().getCurrentVersion(it) } @@ -247,6 +247,16 @@ class BootstrapCrossSigningTask @Inject constructor( || (params.setupMode == SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET && !isMegolmBackupSecretKnown) || (params.setupMode == SetupMode.HARD_RESET) if (shouldCreateKeyBackup) { + // clear all existing backups + while (serverVersion != null) { + awaitCallback { + session.cryptoService().keysBackupService().deleteBackup(serverVersion!!.version, it) + } + serverVersion = awaitCallback { + session.cryptoService().keysBackupService().getCurrentVersion(it) + } + } + Timber.d("## BootstrapCrossSigningTask: Creating 4S - Create megolm backup") val creationInfo = awaitCallback { session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt index e42eb6de6f..7d98b7c2a5 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt @@ -164,7 +164,7 @@ class IncomingVerificationRequestHandler @Inject constructor( override fun verificationRequestUpdated(pr: PendingVerificationRequest) { // If an incoming request is readied (by another device?) we should discard the alert - if (pr.isIncoming && (pr.isReady || pr.handledByOtherSession)) { + if (pr.isIncoming && (pr.isReady || pr.handledByOtherSession || pr.cancelConclusion != null)) { popupAlertManager.cancelAlert(uniqueIdForVerificationRequest(pr)) } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index 2d09974687..aa20a9a992 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -99,8 +99,8 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( val pr = if (selfVerificationMode) { // See if active tx for this user and take it - session.cryptoService().verificationService().getExistingVerificationRequest(args.otherUserId) - ?.lastOrNull { !it.isFinished } + session.cryptoService().verificationService().getExistingVerificationRequests(args.otherUserId) + .lastOrNull { !it.isFinished } ?.also { verificationRequest -> if (verificationRequest.isIncoming && !verificationRequest.isReady) { // auto ready in this case, as we are waiting diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 32d8f043c3..4e540f867e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -153,20 +153,20 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun handleSetAvatarAction(action: RoomSettingsAction.SetAvatarAction) { - deletePendingAvatar() - setState { copy(avatarAction = action.avatarAction) } - } - - private fun deletePendingAvatar() { - // Maybe delete the pending avatar - withState { - (it.avatarAction as? RoomSettingsViewState.AvatarAction.UpdateAvatar) - ?.let { tryOrNull { it.newAvatarUri.toFile().delete() } } + setState { + deletePendingAvatar(this) + copy(avatarAction = action.avatarAction) } } + private fun deletePendingAvatar(state: RoomSettingsViewState) { + // Maybe delete the pending avatar + (state.avatarAction as? RoomSettingsViewState.AvatarAction.UpdateAvatar) + ?.let { tryOrNull { it.newAvatarUri.toFile().delete() } } + } + private fun cancel() { - deletePendingAvatar() + withState { deletePendingAvatar(it) } _viewEvents.post(RoomSettingsViewEvents.GoBack) } @@ -180,7 +180,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: when (val avatarAction = state.avatarAction) { RoomSettingsViewState.AvatarAction.None -> Unit - RoomSettingsViewState.AvatarAction.DeleteAvatar -> { + RoomSettingsViewState.AvatarAction.DeleteAvatar -> { operationList.add(room.rx().deleteAvatar()) } is RoomSettingsViewState.AvatarAction.UpdateAvatar -> { @@ -209,8 +209,13 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: .subscribe( { postLoading(false) - setState { copy(newHistoryVisibility = null) } - deletePendingAvatar() + setState { + deletePendingAvatar(this) + copy( + avatarAction = RoomSettingsViewState.AvatarAction.None, + newHistoryVisibility = null + ) + } _viewEvents.post(RoomSettingsViewEvents.Success) }, { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index d8171bd30d..b1ccabfb76 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -27,6 +27,7 @@ import android.widget.ImageView import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import androidx.preference.EditTextPreference import androidx.preference.Preference import androidx.preference.PreferenceCategory @@ -451,28 +452,25 @@ class VectorSettingsGeneralFragment @Inject constructor( val newPwd = newPasswordText.text.toString() showPasswordLoadingView(true) - session.changePassword(oldPwd, newPwd, object : MatrixCallback { - override fun onSuccess(data: Unit) { - if (!isAdded) { - return - } - showPasswordLoadingView(false) + lifecycleScope.launch { + val result = runCatching { + session.changePassword(oldPwd, newPwd) + } + if (!isAdded) { + return@launch + } + showPasswordLoadingView(false) + result.fold({ dialog.dismiss() activity.toast(R.string.settings_password_updated) - } - - override fun onFailure(failure: Throwable) { - if (!isAdded) { - return - } - showPasswordLoadingView(false) + }, { failure -> if (failure.isInvalidPassword()) { oldPasswordTil.error = getString(R.string.settings_fail_to_update_password_invalid_current_password) } else { oldPasswordTil.error = getString(R.string.settings_fail_to_update_password) } - } - }) + }) + } } } dialog.show() diff --git a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt index e08147d54f..211559c657 100644 --- a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt @@ -15,6 +15,7 @@ */ package im.vector.app.features.settings.account.deactivation +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory @@ -24,9 +25,10 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.session.Session +import java.lang.Exception data class DeactivateAccountViewState( val passwordShown: Boolean = false @@ -67,19 +69,20 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v _viewEvents.post(DeactivateAccountViewEvents.Loading()) - session.deactivateAccount(action.password, action.eraseAllData, object : MatrixCallback { - override fun onSuccess(data: Unit) { - _viewEvents.post(DeactivateAccountViewEvents.Done) - } - - override fun onFailure(failure: Throwable) { + viewModelScope.launch { + val event = try { + session.deactivateAccount(action.password, action.eraseAllData) + DeactivateAccountViewEvents.Done + } catch (failure: Exception) { if (failure.isInvalidPassword()) { - _viewEvents.post(DeactivateAccountViewEvents.InvalidPassword) + DeactivateAccountViewEvents.InvalidPassword } else { - _viewEvents.post(DeactivateAccountViewEvents.OtherFailure(failure)) + DeactivateAccountViewEvents.OtherFailure(failure) } } - }) + + _viewEvents.post(event) + } } companion object : MvRxViewModelFactory { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt index c832fe4833..0ca46cd154 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt @@ -91,13 +91,20 @@ class KnownUsersFragment @Inject constructor( val showMenuItem = it.pendingInvitees.isNotEmpty() menu.forEach { menuItem -> menuItem.isVisible = showMenuItem + if (args.isCreatingRoom) { + menuItem.setTitle(if (it.existingDmRoomId != null) R.string.action_open else R.string.create_room_action_create) + } } } super.onPrepareOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) { - sharedActionViewModel.post(UserDirectorySharedAction.OnMenuItemSelected(item.itemId, it.pendingInvitees)) + sharedActionViewModel.post(UserDirectorySharedAction.OnMenuItemSelected( + item.itemId, + it.pendingInvitees, + it.existingDmRoomId + )) return@withState true } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragmentArgs.kt b/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragmentArgs.kt index 65fd6681bf..c20aedb803 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragmentArgs.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragmentArgs.kt @@ -23,5 +23,6 @@ import kotlinx.android.parcel.Parcelize data class KnownUsersFragmentArgs( val title: String, val menuResId: Int, - val excludedUserIds: Set? = null + val excludedUserIds: Set? = null, + val isCreatingRoom: Boolean = false ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedAction.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedAction.kt index a4ae0d1be2..14daa67f25 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedAction.kt @@ -23,5 +23,7 @@ sealed class UserDirectorySharedAction : VectorSharedAction { object OpenPhoneBook : UserDirectorySharedAction() object Close : UserDirectorySharedAction() object GoBack : UserDirectorySharedAction() - data class OnMenuItemSelected(val itemId: Int, val invitees: Set) : UserDirectorySharedAction() + data class OnMenuItemSelected(val itemId: Int, + val invitees: Set, + val existingDmRoomId: String?) : UserDirectorySharedAction() } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt index b8c42a0f08..0a24b85ce2 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt @@ -87,14 +87,32 @@ class UserDirectoryViewModel @AssistedInject constructor(@Assisted private fun handleRemoveSelectedUser(action: UserDirectoryAction.RemovePendingInvitee) = withState { state -> val selectedUsers = state.pendingInvitees.minus(action.pendingInvitee) - setState { copy(pendingInvitees = selectedUsers) } + setState { + copy( + pendingInvitees = selectedUsers, + existingDmRoomId = getExistingDmRoomId(selectedUsers) + ) + } } private fun handleSelectUser(action: UserDirectoryAction.SelectPendingInvitee) = withState { state -> // Reset the filter asap directoryUsersSearch.accept("") val selectedUsers = state.pendingInvitees.toggle(action.pendingInvitee) - setState { copy(pendingInvitees = selectedUsers) } + setState { + copy( + pendingInvitees = selectedUsers, + existingDmRoomId = getExistingDmRoomId(selectedUsers) + ) + } + } + + private fun getExistingDmRoomId(selectedUsers: Set): String? { + return selectedUsers + .takeIf { it.size == 1 } + ?.filterIsInstance(PendingInvitee.UserPendingInvitee::class.java) + ?.firstOrNull() + ?.let { invitee -> session.getExistingDirectRoomWithUser(invitee.user.userId) } } private fun observeDirectoryUsers() = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewState.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewState.kt index 8bb555e0fa..fe79a8ab37 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewState.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewState.kt @@ -30,7 +30,8 @@ data class UserDirectoryViewState( val pendingInvitees: Set = emptySet(), val createAndInviteState: Async = Uninitialized, val directorySearchTerm: String = "", - val filterKnownUsersValue: Option = Option.empty() + val filterKnownUsersValue: Option = Option.empty(), + val existingDmRoomId: String? = null ) : MvRxState { constructor(args: KnownUsersFragmentArgs) : this(excludedUserIds = args.excludedUserIds) diff --git a/vector/src/main/res/xml/vector_settings_security_privacy.xml b/vector/src/main/res/xml/vector_settings_security_privacy.xml index fa26107edb..5dfde2d1df 100644 --- a/vector/src/main/res/xml/vector_settings_security_privacy.xml +++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml @@ -115,7 +115,9 @@ - +