diff --git a/CHANGES.md b/CHANGES.md index 13661e5612..e8d68d0181 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Features โœจ: - Cross-Signing | Verify new session from existing session (#1134) - Cross-Signing | Bootstraping cross signing with 4S from mobile (#985) + Improvements ๐Ÿ™Œ: - Verification DM / Handle concurrent .start after .ready (#794) - Reimplementation of multiple attachment picker @@ -15,6 +16,10 @@ Improvements ๐Ÿ™Œ: - Cross-Signing | Complete security new session design update (#1135) - Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201) - Cross-Signing | Gossip key backup recovery key (#1200) + - Show room encryption status as a bubble tile (#1078) + - Cross-Signing | Restore history after recover from passphrase (#1214) + - Cross-Sign | QR code scan confirmation screens design update (#1187) + - Emoji Verification | It's not the same butterfly! (#1220) Bugfix ๐Ÿ›: - Missing avatar/displayname after verification request message (#841) @@ -22,6 +27,7 @@ Bugfix ๐Ÿ›: - RiotX can't restore cross signing keys saved by web in SSSS (#1174) - Cross- Signing | After signin in new session, verification paper trail in DM is off (#1191) - Failed to encrypt message in room (message stays in red), [thanks to pwr22] (#925) + - Cross-Signing | web <-> riotX After QR code scan, gossiping fails (#1210) Translations ๐Ÿ—ฃ: - diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt index 7ac92ed74c..bb6e020d89 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt @@ -282,7 +282,7 @@ class KeyShareTests : InstrumentedTest { val keysBackupService = aliceSession2.cryptoService().keysBackupService() mTestHelper.retryPeriodicallyWithLatch(latch) { Log.d("#TEST", "Recovery :${ keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}") - keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey != creationInfo.recoveryKey + keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/EmojiRepresentation.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/EmojiRepresentation.kt index 9ee7c92788..7d18d9bd70 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/EmojiRepresentation.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/EmojiRepresentation.kt @@ -16,7 +16,10 @@ package im.vector.matrix.android.api.session.crypto.verification +import androidx.annotation.DrawableRes import androidx.annotation.StringRes data class EmojiRepresentation(val emoji: String, - @StringRes val nameResId: Int) + @StringRes val nameResId: Int, + @DrawableRes val drawableRes: Int? = null +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationTxState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationTxState.kt index aaaf227187..868ec5a3e2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationTxState.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationTxState.kt @@ -43,6 +43,7 @@ sealed class VerificationTxState { // Will be used to ask the user if the other user has correctly scanned object QrScannedByOther : VerificationQrTxState() + object WaitingOtherReciprocateConfirm : VerificationQrTxState() // Terminal states abstract class TerminalTxState : VerificationTxState() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index dc68fa6b76..77dcc483bd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -22,6 +22,9 @@ import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService +import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.verification.CancelCode import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction @@ -60,6 +63,7 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationDone import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationReady @@ -109,6 +113,10 @@ internal class DefaultVerificationService @Inject constructor( // map [sender : [transaction]] private val txMap = HashMap>() + // we need to keep track of finished transaction + // It will be used for gossiping (to send request after request is completed and 'done' by other) + private val pastTransactions = HashMap>() + /** * Map [sender: [PendingVerificationRequest]] * For now we keep all requests (even terminated ones) during the lifetime of the app. @@ -137,6 +145,9 @@ internal class DefaultVerificationService @Inject constructor( EventType.KEY_VERIFICATION_READY -> { onReadyReceived(event) } + EventType.KEY_VERIFICATION_DONE -> { + onDoneReceived(event) + } MessageType.MSGTYPE_VERIFICATION_REQUEST -> { onRequestReceived(event) } @@ -635,9 +646,7 @@ internal class DefaultVerificationService @Inject constructor( )) } - if (existingTransaction is SASDefaultVerificationTransaction) { - existingTransaction.state = VerificationTxState.Cancelled(safeValueOf(cancelReq.code), false) - } + existingTransaction?.state = VerificationTxState.Cancelled(safeValueOf(cancelReq.code), false) } private fun onRoomAcceptReceived(event: Event) { @@ -778,6 +787,58 @@ internal class DefaultVerificationService @Inject constructor( } } + private fun onDoneReceived(event: Event) { + Timber.v("## onDoneReceived") + val doneReq = event.getClearContent().toModel()?.asValidObject() + if (doneReq == null || event.senderId == null) { + // ignore + Timber.e("## SAS Received invalid done request") + return + } + + handleDoneReceived(event.senderId, doneReq) + + if (event.senderId == userId) { + // We only send gossiping request when the other sent us a done + // We can ask without checking too much thinks (like trust), because we will check validity of secret on reception + getExistingTransaction(userId, doneReq.transactionId) + ?: getOldTransaction(userId, doneReq.transactionId) + ?.let { vt -> + val otherDeviceId = vt.otherDeviceId + if (!crossSigningService.canCrossSign()) { + outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId + ?: "*"))) + outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId + ?: "*"))) + } + outgoingGossipingRequestManager.sendSecretShareRequest(KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId + ?: "*"))) + } + } + } + + private fun handleDoneReceived(senderId: String, doneReq: ValidVerificationDone) { + Timber.v("## SAS Done receieved $doneReq") + val existing = getExistingTransaction(senderId, doneReq.transactionId) + if (existing == null) { + Timber.e("## SAS Received invalid Done request") + return + } + if (existing is DefaultQrCodeVerificationTransaction) { + existing.onDoneReceived() + } else { + // SAS do not care for now? + } + + // Now transactions are udated, let's also update Requests + val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneReq.transactionId } + if (existingRequest == null) { + Timber.e("## SAS Received Done for unknown request txId:${doneReq.transactionId}") + return + } + updatePendingRequest(existingRequest.copy(isSuccessful = true)) + } + private fun onRoomDoneReceived(event: Event) { val doneReq = event.getClearContent().toModel() ?.copy( @@ -957,14 +1018,14 @@ internal class DefaultVerificationService @Inject constructor( ) } - private fun handleDoneReceived(senderId: String, doneInfo: ValidVerificationDone) { - val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionId } - if (existingRequest == null) { - Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionId}") - return - } - updatePendingRequest(existingRequest.copy(isSuccessful = true)) - } +// private fun handleDoneReceived(senderId: String, doneInfo: ValidVerificationDone) { +// val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionId } +// if (existingRequest == null) { +// Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionId}") +// return +// } +// updatePendingRequest(existingRequest.copy(isSuccessful = true)) +// } // TODO All this methods should be delegated to a TransactionStore override fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? { @@ -1003,7 +1064,11 @@ internal class DefaultVerificationService @Inject constructor( private fun removeTransaction(otherUser: String, tid: String) { synchronized(txMap) { - txMap[otherUser]?.remove(tid)?.removeListener(this) + txMap[otherUser]?.remove(tid)?.also { + it.removeListener(this) + } + }?.let { + rememberOldTransaction(it) } } @@ -1016,6 +1081,20 @@ internal class DefaultVerificationService @Inject constructor( } } + private fun rememberOldTransaction(tx: DefaultVerificationTransaction) { + synchronized(pastTransactions) { + pastTransactions.getOrPut(tx.otherUserId) { HashMap() }[tx.transactionId] = tx + } + } + + private fun getOldTransaction(userId: String, tid: String?): DefaultVerificationTransaction? { + return tid?.let { + synchronized(pastTransactions) { + pastTransactions[userId]?.get(it) + } + } + } + override fun beginKeyVerification(method: VerificationMethod, otherUserId: String, otherDeviceId: String, transactionId: String?): String? { val txID = transactionId?.takeIf { it.isNotEmpty() } ?: createUniqueIDForTransaction(otherUserId, otherDeviceId) // should check if already one (and cancel it) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt index 5b8191fc99..eb78aee42d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt @@ -17,9 +17,6 @@ package im.vector.matrix.android.internal.crypto.verification import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService -import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME -import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME -import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager @@ -60,7 +57,7 @@ internal abstract class DefaultVerificationTransaction( protected fun trust(canTrustOtherUserMasterKey: Boolean, toVerifyDeviceIds: List, - eventuallyMarkMyMasterKeyAsTrusted: Boolean) { + eventuallyMarkMyMasterKeyAsTrusted: Boolean, autoDone : Boolean = true) { Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds") Timber.d("## Verification: trust Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted") @@ -100,15 +97,10 @@ internal abstract class DefaultVerificationTransaction( }) } - transport.done(transactionId) { - if (otherUserId == userId && !crossSigningService.canCrossSign()) { - outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) - outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) - outgoingGossipingRequestManager.sendSecretShareRequest(KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) - } + if (autoDone) { + state = VerificationTxState.Verified + transport.done(transactionId) {} } - - state = VerificationTxState.Verified } private fun setDeviceVerified(userId: String, deviceId: String) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt index a878ad06eb..2a502730fa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -15,7 +15,6 @@ */ package im.vector.matrix.android.internal.crypto.verification -import android.os.Build import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.crypto.verification.CancelCode @@ -74,13 +73,9 @@ internal abstract class SASDefaultVerificationTransaction( // ordered by preferred order val KNOWN_MACS = listOf(SAS_MAC_SHA256, SAS_MAC_SHA256_LONGKDF) - // older devices have limited support of emoji, so reply with decimal - val KNOWN_SHORT_CODES = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - listOf(SasMode.EMOJI, SasMode.DECIMAL) - } else { - listOf(SasMode.DECIMAL) - } + // older devices have limited support of emoji but SDK offers images for the 64 verification emojis + // so always send that we support EMOJI + val KNOWN_SHORT_CODES = listOf(SasMode.EMOJI, SasMode.DECIMAL) } override var state: VerificationTxState = VerificationTxState.None diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationEmoji.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationEmoji.kt index eb9acd045a..396b609f41 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationEmoji.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationEmoji.kt @@ -20,69 +20,69 @@ import im.vector.matrix.android.api.session.crypto.verification.EmojiRepresentat internal fun getEmojiForCode(code: Int): EmojiRepresentation { return when (code % 64) { - 0 -> EmojiRepresentation("๐Ÿถ", R.string.verification_emoji_dog) - 1 -> EmojiRepresentation("๐Ÿฑ", R.string.verification_emoji_cat) - 2 -> EmojiRepresentation("๐Ÿฆ", R.string.verification_emoji_lion) - 3 -> EmojiRepresentation("๐ŸŽ", R.string.verification_emoji_horse) - 4 -> EmojiRepresentation("๐Ÿฆ„", R.string.verification_emoji_unicorn) - 5 -> EmojiRepresentation("๐Ÿท", R.string.verification_emoji_pig) - 6 -> EmojiRepresentation("๐Ÿ˜", R.string.verification_emoji_elephant) - 7 -> EmojiRepresentation("๐Ÿฐ", R.string.verification_emoji_rabbit) - 8 -> EmojiRepresentation("๐Ÿผ", R.string.verification_emoji_panda) - 9 -> EmojiRepresentation("๐Ÿ“", R.string.verification_emoji_rooster) - 10 -> EmojiRepresentation("๐Ÿง", R.string.verification_emoji_penguin) - 11 -> EmojiRepresentation("๐Ÿข", R.string.verification_emoji_turtle) - 12 -> EmojiRepresentation("๐ŸŸ", R.string.verification_emoji_fish) - 13 -> EmojiRepresentation("๐Ÿ™", R.string.verification_emoji_octopus) - 14 -> EmojiRepresentation("๐Ÿฆ‹", R.string.verification_emoji_butterfly) - 15 -> EmojiRepresentation("๐ŸŒท", R.string.verification_emoji_flower) - 16 -> EmojiRepresentation("๐ŸŒณ", R.string.verification_emoji_tree) - 17 -> EmojiRepresentation("๐ŸŒต", R.string.verification_emoji_cactus) - 18 -> EmojiRepresentation("๐Ÿ„", R.string.verification_emoji_mushroom) - 19 -> EmojiRepresentation("๐ŸŒ", R.string.verification_emoji_globe) - 20 -> EmojiRepresentation("๐ŸŒ™", R.string.verification_emoji_moon) - 21 -> EmojiRepresentation("โ˜๏ธ", R.string.verification_emoji_cloud) - 22 -> EmojiRepresentation("๐Ÿ”ฅ", R.string.verification_emoji_fire) - 23 -> EmojiRepresentation("๐ŸŒ", R.string.verification_emoji_banana) - 24 -> EmojiRepresentation("๐ŸŽ", R.string.verification_emoji_apple) - 25 -> EmojiRepresentation("๐Ÿ“", R.string.verification_emoji_strawberry) - 26 -> EmojiRepresentation("๐ŸŒฝ", R.string.verification_emoji_corn) - 27 -> EmojiRepresentation("๐Ÿ•", R.string.verification_emoji_pizza) - 28 -> EmojiRepresentation("๐ŸŽ‚", R.string.verification_emoji_cake) - 29 -> EmojiRepresentation("โค๏ธ", R.string.verification_emoji_heart) - 30 -> EmojiRepresentation("๐Ÿ˜€", R.string.verification_emoji_smiley) - 31 -> EmojiRepresentation("๐Ÿค–", R.string.verification_emoji_robot) - 32 -> EmojiRepresentation("๐ŸŽฉ", R.string.verification_emoji_hat) - 33 -> EmojiRepresentation("๐Ÿ‘“", R.string.verification_emoji_glasses) - 34 -> EmojiRepresentation("๐Ÿ”ง", R.string.verification_emoji_wrench) - 35 -> EmojiRepresentation("๐ŸŽ…", R.string.verification_emoji_santa) - 36 -> EmojiRepresentation("๐Ÿ‘", R.string.verification_emoji_thumbsup) - 37 -> EmojiRepresentation("โ˜‚๏ธ", R.string.verification_emoji_umbrella) - 38 -> EmojiRepresentation("โŒ›", R.string.verification_emoji_hourglass) - 39 -> EmojiRepresentation("โฐ", R.string.verification_emoji_clock) - 40 -> EmojiRepresentation("๐ŸŽ", R.string.verification_emoji_gift) - 41 -> EmojiRepresentation("๐Ÿ’ก", R.string.verification_emoji_lightbulb) - 42 -> EmojiRepresentation("๐Ÿ“•", R.string.verification_emoji_book) - 43 -> EmojiRepresentation("โœ๏ธ", R.string.verification_emoji_pencil) - 44 -> EmojiRepresentation("๐Ÿ“Ž", R.string.verification_emoji_paperclip) - 45 -> EmojiRepresentation("โœ‚๏ธ", R.string.verification_emoji_scissors) - 46 -> EmojiRepresentation("๐Ÿ”’", R.string.verification_emoji_lock) - 47 -> EmojiRepresentation("๐Ÿ”‘", R.string.verification_emoji_key) - 48 -> EmojiRepresentation("๐Ÿ”จ", R.string.verification_emoji_hammer) - 49 -> EmojiRepresentation("โ˜Ž๏ธ", R.string.verification_emoji_telephone) - 50 -> EmojiRepresentation("๐Ÿ", R.string.verification_emoji_flag) - 51 -> EmojiRepresentation("๐Ÿš‚", R.string.verification_emoji_train) - 52 -> EmojiRepresentation("๐Ÿšฒ", R.string.verification_emoji_bicycle) - 53 -> EmojiRepresentation("โœˆ๏ธ", R.string.verification_emoji_airplane) - 54 -> EmojiRepresentation("๐Ÿš€", R.string.verification_emoji_rocket) - 55 -> EmojiRepresentation("๐Ÿ†", R.string.verification_emoji_trophy) - 56 -> EmojiRepresentation("โšฝ", R.string.verification_emoji_ball) - 57 -> EmojiRepresentation("๐ŸŽธ", R.string.verification_emoji_guitar) - 58 -> EmojiRepresentation("๐ŸŽบ", R.string.verification_emoji_trumpet) - 59 -> EmojiRepresentation("๐Ÿ””", R.string.verification_emoji_bell) - 60 -> EmojiRepresentation("โš“", R.string.verification_emoji_anchor) - 61 -> EmojiRepresentation("๐ŸŽง", R.string.verification_emoji_headphone) - 62 -> EmojiRepresentation("๐Ÿ“", R.string.verification_emoji_folder) - /* 63 */ else -> EmojiRepresentation("๐Ÿ“Œ", R.string.verification_emoji_pin) + 0 -> EmojiRepresentation("๐Ÿถ", R.string.verification_emoji_dog, R.drawable.ic_verification_dog) + 1 -> EmojiRepresentation("๐Ÿฑ", R.string.verification_emoji_cat, R.drawable.ic_verification_cat) + 2 -> EmojiRepresentation("๐Ÿฆ", R.string.verification_emoji_lion, R.drawable.ic_verification_lion) + 3 -> EmojiRepresentation("๐ŸŽ", R.string.verification_emoji_horse, R.drawable.ic_verification_horse) + 4 -> EmojiRepresentation("๐Ÿฆ„", R.string.verification_emoji_unicorn, R.drawable.ic_verification_unicorn) + 5 -> EmojiRepresentation("๐Ÿท", R.string.verification_emoji_pig, R.drawable.ic_verification_pig) + 6 -> EmojiRepresentation("๐Ÿ˜", R.string.verification_emoji_elephant, R.drawable.ic_verification_elephant) + 7 -> EmojiRepresentation("๐Ÿฐ", R.string.verification_emoji_rabbit, R.drawable.ic_verification_rabbit) + 8 -> EmojiRepresentation("๐Ÿผ", R.string.verification_emoji_panda, R.drawable.ic_verification_panda) + 9 -> EmojiRepresentation("๐Ÿ“", R.string.verification_emoji_rooster, R.drawable.ic_verification_rooster) + 10 -> EmojiRepresentation("๐Ÿง", R.string.verification_emoji_penguin, R.drawable.ic_verification_penguin) + 11 -> EmojiRepresentation("๐Ÿข", R.string.verification_emoji_turtle, R.drawable.ic_verification_turtle) + 12 -> EmojiRepresentation("๐ŸŸ", R.string.verification_emoji_fish, R.drawable.ic_verification_fish) + 13 -> EmojiRepresentation("๐Ÿ™", R.string.verification_emoji_octopus, R.drawable.ic_verification_octopus) + 14 -> EmojiRepresentation("๐Ÿฆ‹", R.string.verification_emoji_butterfly, R.drawable.ic_verification_butterfly) + 15 -> EmojiRepresentation("๐ŸŒท", R.string.verification_emoji_flower, R.drawable.ic_verification_flower) + 16 -> EmojiRepresentation("๐ŸŒณ", R.string.verification_emoji_tree, R.drawable.ic_verification_tree) + 17 -> EmojiRepresentation("๐ŸŒต", R.string.verification_emoji_cactus, R.drawable.ic_verification_cactus) + 18 -> EmojiRepresentation("๐Ÿ„", R.string.verification_emoji_mushroom, R.drawable.ic_verification_mushroom) + 19 -> EmojiRepresentation("๐ŸŒ", R.string.verification_emoji_globe, R.drawable.ic_verification_globe) + 20 -> EmojiRepresentation("๐ŸŒ™", R.string.verification_emoji_moon, R.drawable.ic_verification_moon) + 21 -> EmojiRepresentation("โ˜๏ธ", R.string.verification_emoji_cloud, R.drawable.ic_verification_cloud) + 22 -> EmojiRepresentation("๐Ÿ”ฅ", R.string.verification_emoji_fire, R.drawable.ic_verification_fire) + 23 -> EmojiRepresentation("๐ŸŒ", R.string.verification_emoji_banana, R.drawable.ic_verification_banana) + 24 -> EmojiRepresentation("๐ŸŽ", R.string.verification_emoji_apple, R.drawable.ic_verification_apple) + 25 -> EmojiRepresentation("๐Ÿ“", R.string.verification_emoji_strawberry, R.drawable.ic_verification_strawberry) + 26 -> EmojiRepresentation("๐ŸŒฝ", R.string.verification_emoji_corn, R.drawable.ic_verification_corn) + 27 -> EmojiRepresentation("๐Ÿ•", R.string.verification_emoji_pizza, R.drawable.ic_verification_pizza) + 28 -> EmojiRepresentation("๐ŸŽ‚", R.string.verification_emoji_cake, R.drawable.ic_verification_cake) + 29 -> EmojiRepresentation("โค๏ธ", R.string.verification_emoji_heart, R.drawable.ic_verification_heart) + 30 -> EmojiRepresentation("๐Ÿ™‚", R.string.verification_emoji_smiley, R.drawable.ic_verification_smiley) + 31 -> EmojiRepresentation("๐Ÿค–", R.string.verification_emoji_robot, R.drawable.ic_verification_robot) + 32 -> EmojiRepresentation("๐ŸŽฉ", R.string.verification_emoji_hat, R.drawable.ic_verification_hat) + 33 -> EmojiRepresentation("๐Ÿ‘“", R.string.verification_emoji_glasses, R.drawable.ic_verification_glasses) + 34 -> EmojiRepresentation("๐Ÿ”ง", R.string.verification_emoji_wrench, R.drawable.ic_verification_wrench) + 35 -> EmojiRepresentation("๐ŸŽ…", R.string.verification_emoji_santa, R.drawable.ic_verification_santa) + 36 -> EmojiRepresentation("๐Ÿ‘", R.string.verification_emoji_thumbsup, R.drawable.ic_verification_thumbs_up) + 37 -> EmojiRepresentation("โ˜‚๏ธ", R.string.verification_emoji_umbrella, R.drawable.ic_verification_umbrella) + 38 -> EmojiRepresentation("โŒ›", R.string.verification_emoji_hourglass, R.drawable.ic_verification_hourglass) + 39 -> EmojiRepresentation("โฐ", R.string.verification_emoji_clock, R.drawable.ic_verification_clock) + 40 -> EmojiRepresentation("๐ŸŽ", R.string.verification_emoji_gift, R.drawable.ic_verification_gift) + 41 -> EmojiRepresentation("๐Ÿ’ก", R.string.verification_emoji_lightbulb, R.drawable.ic_verification_light_bulb) + 42 -> EmojiRepresentation("๐Ÿ“•", R.string.verification_emoji_book, R.drawable.ic_verification_book) + 43 -> EmojiRepresentation("โœ๏ธ", R.string.verification_emoji_pencil, R.drawable.ic_verification_pencil) + 44 -> EmojiRepresentation("๐Ÿ“Ž", R.string.verification_emoji_paperclip, R.drawable.ic_verification_paperclip) + 45 -> EmojiRepresentation("โœ‚๏ธ", R.string.verification_emoji_scissors, R.drawable.ic_verification_scissors) + 46 -> EmojiRepresentation("๐Ÿ”’", R.string.verification_emoji_lock, R.drawable.ic_verification_lock) + 47 -> EmojiRepresentation("๐Ÿ”‘", R.string.verification_emoji_key, R.drawable.ic_verification_key) + 48 -> EmojiRepresentation("๐Ÿ”จ", R.string.verification_emoji_hammer, R.drawable.ic_verification_hammer) + 49 -> EmojiRepresentation("โ˜Ž๏ธ", R.string.verification_emoji_telephone, R.drawable.ic_verification_phone) + 50 -> EmojiRepresentation("๐Ÿ", R.string.verification_emoji_flag, R.drawable.ic_verification_flag) + 51 -> EmojiRepresentation("๐Ÿš‚", R.string.verification_emoji_train, R.drawable.ic_verification_train) + 52 -> EmojiRepresentation("๐Ÿšฒ", R.string.verification_emoji_bicycle, R.drawable.ic_verification_bicycle) + 53 -> EmojiRepresentation("โœˆ๏ธ", R.string.verification_emoji_airplane, R.drawable.ic_verification_airplane) + 54 -> EmojiRepresentation("๐Ÿš€", R.string.verification_emoji_rocket, R.drawable.ic_verification_rocket) + 55 -> EmojiRepresentation("๐Ÿ†", R.string.verification_emoji_trophy, R.drawable.ic_verification_trophy) + 56 -> EmojiRepresentation("โšฝ", R.string.verification_emoji_ball, R.drawable.ic_verification_ball) + 57 -> EmojiRepresentation("๐ŸŽธ", R.string.verification_emoji_guitar, R.drawable.ic_verification_guitar) + 58 -> EmojiRepresentation("๐ŸŽบ", R.string.verification_emoji_trumpet, R.drawable.ic_verification_trumpet) + 59 -> EmojiRepresentation("๐Ÿ””", R.string.verification_emoji_bell, R.drawable.ic_verification_bell) + 60 -> EmojiRepresentation("โš“", R.string.verification_emoji_anchor, R.drawable.ic_verification_anchor) + 61 -> EmojiRepresentation("๐ŸŽง", R.string.verification_emoji_headphone, R.drawable.ic_verification_headphone) + 62 -> EmojiRepresentation("๐Ÿ“", R.string.verification_emoji_folder, R.drawable.ic_verification_folder) + /* 63 */ else -> EmojiRepresentation("๐Ÿ“Œ", R.string.verification_emoji_pin, R.drawable.ic_verification_pin) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoDone.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoDone.kt index abb1141355..8cf96d7d65 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoDone.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoDone.kt @@ -15,14 +15,12 @@ */ package im.vector.matrix.android.internal.crypto.verification -internal interface VerificationInfoDone : VerificationInfo { +import im.vector.matrix.android.api.session.room.model.message.ValidVerificationDone - override fun asValidObject(): ValidVerificationInfoDone? { - if (transactionId.isNullOrEmpty()) { - return null - } - return ValidVerificationInfoDone +internal interface VerificationInfoDone : VerificationInfo { + + override fun asValidObject(): ValidVerificationDone? { + val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null + return ValidVerificationDone(validTransactionId) } } - -internal object ValidVerificationInfoDone diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index 41d8ce7f44..59ee23cc62 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -187,9 +187,12 @@ internal class DefaultQrCodeVerificationTransaction( // qrCodeData.sharedSecret will be used to send the start request start(otherQrCodeData.sharedSecret) - trust(canTrustOtherUserMasterKey, - toVerifyDeviceIds.distinct(), - eventuallyMarkMyMasterKeyAsTrusted = true) + trust( + canTrustOtherUserMasterKey = canTrustOtherUserMasterKey, + toVerifyDeviceIds = toVerifyDeviceIds.distinct(), + eventuallyMarkMyMasterKeyAsTrusted = true, + autoDone = false + ) } private fun start(remoteSecret: String, onDone: (() -> Unit)? = null) { @@ -199,6 +202,7 @@ internal class DefaultQrCodeVerificationTransaction( throw IllegalStateException("Interactive Key verification already started") } + state = VerificationTxState.Started val startMessage = transport.createStartForQrCode( deviceId, transactionId, @@ -208,7 +212,7 @@ internal class DefaultQrCodeVerificationTransaction( transport.sendToOther( EventType.KEY_VERIFICATION_START, startMessage, - VerificationTxState.Started, + VerificationTxState.WaitingOtherReciprocateConfirm, CancelCode.User, onDone ) @@ -244,6 +248,15 @@ internal class DefaultQrCodeVerificationTransaction( } } + fun onDoneReceived() { + if (state != VerificationTxState.WaitingOtherReciprocateConfirm) { + cancel(CancelCode.UnexpectedMessage) + return + } + state = VerificationTxState.Verified + transport.done(transactionId) {} + } + override fun otherUserScannedMyQrCode() { when (qrCodeData) { is QrCodeData.VerifyingAnotherUser -> { @@ -265,6 +278,6 @@ internal class DefaultQrCodeVerificationTransaction( override fun otherUserDidNotScannedMyQrCode() { // What can I do then? // At least remove the transaction... - state = VerificationTxState.Cancelled(CancelCode.MismatchedKeys, true) + cancel(CancelCode.MismatchedKeys) } } diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_airplane.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_airplane.xml new file mode 100644 index 0000000000..72026cd7a0 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_airplane.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_anchor.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_anchor.xml new file mode 100644 index 0000000000..b89d033b9e --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_anchor.xml @@ -0,0 +1,12 @@ + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_apple.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_apple.xml new file mode 100644 index 0000000000..54e0f9a3c0 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_apple.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_ball.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_ball.xml new file mode 100644 index 0000000000..b12c6d245b --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_ball.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_banana.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_banana.xml new file mode 100644 index 0000000000..cdd3cb1b9f --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_banana.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_bell.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_bell.xml new file mode 100644 index 0000000000..2f29828bcf --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_bell.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_bicycle.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_bicycle.xml new file mode 100644 index 0000000000..1427e793c5 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_bicycle.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_book.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_book.xml new file mode 100644 index 0000000000..8e3ecc00c0 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_book.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_butterfly.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_butterfly.xml new file mode 100644 index 0000000000..d4b557a7ed --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_butterfly.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_cactus.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_cactus.xml new file mode 100644 index 0000000000..ce8aff0657 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_cactus.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_cake.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_cake.xml new file mode 100644 index 0000000000..9ebb3c0904 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_cake.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_cat.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_cat.xml new file mode 100644 index 0000000000..b34cf63d98 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_cat.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_clock.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_clock.xml new file mode 100644 index 0000000000..48d7150c36 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_clock.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_cloud.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_cloud.xml new file mode 100644 index 0000000000..d390bd6e87 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_cloud.xml @@ -0,0 +1,12 @@ + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_corn.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_corn.xml new file mode 100644 index 0000000000..d863d03c2a --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_corn.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_dog.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_dog.xml new file mode 100644 index 0000000000..8346a5ebee --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_dog.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_elephant.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_elephant.xml new file mode 100644 index 0000000000..d0a2de42cb --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_elephant.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_fire.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_fire.xml new file mode 100644 index 0000000000..ebf42039b1 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_fire.xml @@ -0,0 +1,12 @@ + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_fish.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_fish.xml new file mode 100644 index 0000000000..30907f2496 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_fish.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_flag.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_flag.xml new file mode 100644 index 0000000000..250388dc4a --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_flag.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_flower.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_flower.xml new file mode 100644 index 0000000000..8a91221a80 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_flower.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_folder.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_folder.xml new file mode 100644 index 0000000000..9320766492 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_folder.xml @@ -0,0 +1,12 @@ + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_gift.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_gift.xml new file mode 100644 index 0000000000..d18c6e860a --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_gift.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_glasses.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_glasses.xml new file mode 100644 index 0000000000..8913d1ffd7 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_glasses.xml @@ -0,0 +1,12 @@ + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_globe.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_globe.xml new file mode 100644 index 0000000000..2a07829cb3 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_globe.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_guitar.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_guitar.xml new file mode 100644 index 0000000000..2622fbe416 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_guitar.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_hammer.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_hammer.xml new file mode 100644 index 0000000000..7b70654d52 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_hammer.xml @@ -0,0 +1,12 @@ + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_hat.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_hat.xml new file mode 100644 index 0000000000..15f980bdb1 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_hat.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_headphone.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_headphone.xml new file mode 100644 index 0000000000..cbc43e7601 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_headphone.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_heart.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_heart.xml new file mode 100644 index 0000000000..d37bcc33d1 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_heart.xml @@ -0,0 +1,9 @@ + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_horse.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_horse.xml new file mode 100644 index 0000000000..bedf0f6f46 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_horse.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_hourglass.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_hourglass.xml new file mode 100644 index 0000000000..8bb37a35bb --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_hourglass.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_key.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_key.xml new file mode 100644 index 0000000000..4cd1d033f7 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_key.xml @@ -0,0 +1,9 @@ + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_light_bulb.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_light_bulb.xml new file mode 100644 index 0000000000..18f3149500 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_light_bulb.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_lion.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_lion.xml new file mode 100644 index 0000000000..b97a508fc2 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_lion.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_lock.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_lock.xml new file mode 100644 index 0000000000..de3979434f --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_lock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_moon.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_moon.xml new file mode 100644 index 0000000000..3f5abe6ae3 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_moon.xml @@ -0,0 +1,12 @@ + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_mushroom.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_mushroom.xml new file mode 100644 index 0000000000..72f7036856 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_mushroom.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_octopus.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_octopus.xml new file mode 100644 index 0000000000..054760f3b8 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_octopus.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_panda.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_panda.xml new file mode 100644 index 0000000000..ab1e718c44 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_panda.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_paperclip.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_paperclip.xml new file mode 100644 index 0000000000..e8f89859d6 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_paperclip.xml @@ -0,0 +1,9 @@ + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_pencil.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_pencil.xml new file mode 100644 index 0000000000..3b9f51fca5 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_pencil.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_penguin.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_penguin.xml new file mode 100644 index 0000000000..fb2e05760f --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_penguin.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_phone.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_phone.xml new file mode 100644 index 0000000000..7beda09c4e --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_phone.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_pig.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_pig.xml new file mode 100644 index 0000000000..c31bd06c52 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_pig.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_pin.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_pin.xml new file mode 100644 index 0000000000..f10e4606a9 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_pin.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_pizza.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_pizza.xml new file mode 100644 index 0000000000..a514aeb3d6 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_pizza.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_rabbit.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_rabbit.xml new file mode 100644 index 0000000000..c8ff75c999 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_rabbit.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_robot.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_robot.xml new file mode 100644 index 0000000000..a53cfe99c0 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_robot.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_rocket.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_rocket.xml new file mode 100644 index 0000000000..4097ed9030 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_rocket.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_rooster.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_rooster.xml new file mode 100644 index 0000000000..cb7ad563f0 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_rooster.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_santa.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_santa.xml new file mode 100644 index 0000000000..4f7bc1a24f --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_santa.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_scissors.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_scissors.xml new file mode 100644 index 0000000000..98e68c2071 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_scissors.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_smiley.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_smiley.xml new file mode 100644 index 0000000000..087adc8c6d --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_smiley.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_strawberry.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_strawberry.xml new file mode 100644 index 0000000000..0eeb290d9d --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_strawberry.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_thumbs_up.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_thumbs_up.xml new file mode 100644 index 0000000000..9761204ab6 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_thumbs_up.xml @@ -0,0 +1,12 @@ + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_train.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_train.xml new file mode 100644 index 0000000000..e317ce1642 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_train.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_tree.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_tree.xml new file mode 100644 index 0000000000..c5acc19a72 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_tree.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_trophy.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_trophy.xml new file mode 100644 index 0000000000..631da7320d --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_trophy.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_trumpet.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_trumpet.xml new file mode 100644 index 0000000000..84f95a8592 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_trumpet.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_turtle.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_turtle.xml new file mode 100644 index 0000000000..1cedc1b6ad --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_turtle.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_umbrella.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_umbrella.xml new file mode 100644 index 0000000000..ac1267cd3b --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_umbrella.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_unicorn.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_unicorn.xml new file mode 100644 index 0000000000..19cef5d339 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_unicorn.xml @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/res/drawable/ic_verification_wrench.xml b/matrix-sdk-android/src/main/res/drawable/ic_verification_wrench.xml new file mode 100644 index 0000000000..ba3c4313a3 --- /dev/null +++ b/matrix-sdk-android/src/main/res/drawable/ic_verification_wrench.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/sas/SasEmojiItem.kt b/vector/src/debug/java/im/vector/riotx/features/debug/sas/SasEmojiItem.kt index 7403ead43c..cf9ca1032c 100644 --- a/vector/src/debug/java/im/vector/riotx/features/debug/sas/SasEmojiItem.kt +++ b/vector/src/debug/java/im/vector/riotx/features/debug/sas/SasEmojiItem.kt @@ -18,11 +18,14 @@ package im.vector.riotx.features.debug.sas import android.annotation.SuppressLint import android.widget.TextView +import androidx.core.content.ContextCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.matrix.android.api.session.crypto.verification.EmojiRepresentation import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyModel +import me.gujun.android.span.image +import me.gujun.android.span.span @EpoxyModelClass(layout = im.vector.riotx.R.layout.item_sas_emoji) abstract class SasEmojiItem : VectorEpoxyModel() { @@ -36,7 +39,12 @@ abstract class SasEmojiItem : VectorEpoxyModel() { override fun bind(holder: Holder) { super.bind(holder) holder.indexView.text = "$index:" - holder.emojiView.text = emojiRepresentation.emoji + holder.emojiView.text = span { + +emojiRepresentation.emoji + emojiRepresentation.drawableRes?.let { + image(ContextCompat.getDrawable(holder.view.context, it)!!) + } + } holder.textView.setText(emojiRepresentation.nameResId) holder.idView.text = holder.idView.resources.getResourceEntryName(emojiRepresentation.nameResId) } diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index e4fdc2f5ae..3af564aaca 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -23,6 +23,7 @@ } + @@ -566,5 +567,13 @@ Apache License of your accepting any such warranty or additional liability. +
+    CC-BY 4.0
+    
  • + Twitter/twemoji Graphics +
    +
  • +
    + diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index 9f3cdba683..c68972cdd4 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -37,6 +37,7 @@ import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFrag import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment +import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQRWaitingFragment import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment import im.vector.riotx.features.grouplist.GroupListFragment @@ -339,6 +340,11 @@ interface FragmentModule { @FragmentKey(VerificationQrScannedByOtherFragment::class) fun bindVerificationQrScannedByOtherFragment(fragment: VerificationQrScannedByOtherFragment): Fragment + @Binds + @IntoMap + @FragmentKey(VerificationQRWaitingFragment::class) + fun bindVerificationQRWaitingFragment(fragment: VerificationQRWaitingFragment): Fragment + @Binds @IntoMap @FragmentKey(VerificationConclusionFragment::class) diff --git a/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt index 367e04445d..d82134caf5 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt @@ -192,6 +192,6 @@ fun Context.toast(resId: Int) { } // Not in KTX anymore -fun Context.toast(message: String?) { - Toast.makeText(this, message ?: getString(R.string.generic_exception_error), Toast.LENGTH_SHORT).show() +fun Context.toast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt index 4b27010f8f..530e0934cb 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.crypto.quads +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory @@ -34,9 +35,9 @@ import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.resources.StringProvider import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import timber.log.Timber import java.io.ByteArrayOutputStream data class SharedSecureStorageViewState( @@ -77,7 +78,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( private fun handleSubmitPassphrase(action: SharedSecureStorageAction.SubmitPassphrase) { val decryptedSecretMap = HashMap() - GlobalScope.launch(Dispatchers.IO) { + viewModelScope.launch(Dispatchers.IO) { runCatching { _viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading) val passphrase = action.passphrase @@ -116,14 +117,18 @@ class SharedSecureStorageViewModel @AssistedInject constructor( withContext(Dispatchers.IO) { args.requestedSecrets.forEach { - val res = awaitCallback { callback -> - session.sharedSecretStorageService.getSecret( - name = it, - keyId = keyInfo.id, - secretKey = keySpec, - callback = callback) + if (session.getAccountDataEvent(it) != null) { + val res = awaitCallback { callback -> + session.sharedSecretStorageService.getSecret( + name = it, + keyId = keyInfo.id, + secretKey = keySpec, + callback = callback) + } + decryptedSecretMap[it] = res + } else { + Timber.w("## Cannot find secret $it in SSSS, skip") } - decryptedSecretMap[it] = res } } }.fold({ diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index cd91cc3712..695716d386 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -32,6 +32,7 @@ import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME @@ -49,6 +50,7 @@ import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFrag import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment +import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQRWaitingFragment import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment import im.vector.riotx.features.home.AvatarRenderer @@ -108,7 +110,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { startActivityForResult(SharedSecureStorageActivity.newIntent( requireContext(), null, // use default key - listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME), + listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME), SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS ), SECRET_REQUEST_CODE) } @@ -243,6 +245,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { showFragment(VerificationQrScannedByOtherFragment::class, Bundle()) return@withState } + is VerificationTxState.Started, + is VerificationTxState.WaitingOtherReciprocateConfirm -> { + showFragment(VerificationQRWaitingFragment::class, Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationQRWaitingFragment.Args(state.isMe, state.otherUserMxItem?.getBestName() ?: "")) + }) + return@withState + } is VerificationTxState.Verified -> { showFragment(VerificationConclusionFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index a932d0cc5f..f5f92c381d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -15,6 +15,7 @@ */ package im.vector.riotx.features.crypto.verification +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -28,6 +29,7 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME @@ -46,10 +48,15 @@ import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64 import im.vector.matrix.android.internal.crypto.crosssigning.isVerified +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult +import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey +import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult +import im.vector.matrix.android.internal.util.awaitCallback import im.vector.riotx.R import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider +import kotlinx.coroutines.launch import timber.log.Timber data class VerificationBottomSheetViewState( @@ -337,40 +344,83 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( _viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore) } is VerificationAction.GotResultFromSsss -> { - try { - action.cypherData.fromBase64().inputStream().use { ins -> - val res = session.loadSecureSecret>(ins, action.alias) - val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys( - res?.get(MASTER_KEY_SSSS_NAME), - res?.get(USER_SIGNING_KEY_SSSS_NAME), - res?.get(SELF_SIGNING_KEY_SSSS_NAME) - ) - if (trustResult.isVerified()) { - // Sign this device and upload the signature - session.sessionParams.credentials.deviceId?.let { deviceId -> - session.cryptoService() - .crossSigningService().trustDevice(deviceId, object : MatrixCallback { - override fun onFailure(failure: Throwable) { - Timber.w(failure, "Failed to sign my device after recovery") - } - }) - } - - setState { - copy(verifiedFromPrivateKeys = true) - } - } else { - // POP UP something - _viewEvents.post(VerificationBottomSheetViewEvents.ModalError("Failed to import keys")) - } - } - } catch (failure: Throwable) { - _viewEvents.post(VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage ?: stringProvider.getString(R.string.unexpected_error))) - } + handleSecretBackFromSSSS(action) } }.exhaustive } + private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) { + try { + action.cypherData.fromBase64().inputStream().use { ins -> + val res = session.loadSecureSecret>(ins, action.alias) + val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys( + res?.get(MASTER_KEY_SSSS_NAME), + res?.get(USER_SIGNING_KEY_SSSS_NAME), + res?.get(SELF_SIGNING_KEY_SSSS_NAME) + ) + if (trustResult.isVerified()) { + // Sign this device and upload the signature + session.sessionParams.credentials.deviceId?.let { deviceId -> + session.cryptoService() + .crossSigningService().trustDevice(deviceId, object : MatrixCallback { + override fun onFailure(failure: Throwable) { + Timber.w(failure, "Failed to sign my device after recovery") + } + }) + } + + setState { + copy(verifiedFromPrivateKeys = true) + } + + // try to get keybackup key + } else { + // POP UP something + _viewEvents.post(VerificationBottomSheetViewEvents.ModalError(stringProvider.getString(R.string.error_failed_to_import_keys))) + } + + // try the keybackup + tentativeRestoreBackup(res) + Unit + } + } catch (failure: Throwable) { + _viewEvents.post( + VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage ?: stringProvider.getString(R.string.unexpected_error))) + } + } + + private fun tentativeRestoreBackup(res: Map?) { + viewModelScope.launch { + try { + val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also { + Timber.v("## Keybackup secret not restored from SSSS") + } + + val version = awaitCallback { + session.cryptoService().keysBackupService().getCurrentVersion(it) + } ?: return@launch + + awaitCallback { + session.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( + version, + computeRecoveryKey(secret.fromBase64()), + null, + null, + null, + it + ) + } + + awaitCallback { + session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true, it) + } + } catch (failure: Throwable) { + // Just ignore for now + Timber.v("## Failed to restore backup after SSSS recovery") + } + } + } + override fun transactionCreated(tx: VerificationTransaction) { transactionUpdated(tx) } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationEmojisItem.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationEmojisItem.kt index 7d72486ccd..be14c7df3d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationEmojisItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationEmojisItem.kt @@ -16,14 +16,21 @@ */ package im.vector.riotx.features.crypto.verification.epoxy +import android.content.Context import android.view.ViewGroup +import android.widget.ImageView import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.matrix.android.api.session.crypto.verification.EmojiRepresentation import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyModel +import me.gujun.android.span.Span +import me.gujun.android.span.image +import me.gujun.android.span.span /** * A emoji list for bottom sheet. @@ -40,26 +47,36 @@ abstract class BottomSheetVerificationEmojisItem : VectorEpoxyModel(R.id.item_emoji_tv).text = emojiRepresentation0.emoji - holder.emoji0View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation0.nameResId) + bindEmojiView(holder.emoji0View, emojiRepresentation0) + bindEmojiView(holder.emoji1View, emojiRepresentation1) + bindEmojiView(holder.emoji2View, emojiRepresentation2) + bindEmojiView(holder.emoji3View, emojiRepresentation3) + bindEmojiView(holder.emoji4View, emojiRepresentation4) + bindEmojiView(holder.emoji5View, emojiRepresentation5) + bindEmojiView(holder.emoji6View, emojiRepresentation6) + } - holder.emoji1View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation1.emoji - holder.emoji1View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation1.nameResId) + private fun spanForRepresentation(context: Context, rep: EmojiRepresentation): Span { + return span { + if (rep.drawableRes != null) { + ContextCompat.getDrawable(context, rep.drawableRes!!)?.let { image(it) } + } else { + +rep.emoji + } + } + } - holder.emoji2View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation2.emoji - holder.emoji2View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation2.nameResId) - - holder.emoji3View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation3.emoji - holder.emoji3View.findViewById(R.id.item_emoji_name_tv)?.setText(emojiRepresentation3.nameResId) - - holder.emoji4View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation4.emoji - holder.emoji4View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation4.nameResId) - - holder.emoji5View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation5.emoji - holder.emoji5View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation5.nameResId) - - holder.emoji6View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation6.emoji - holder.emoji6View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation6.nameResId) + private fun bindEmojiView(view: ViewGroup, rep: EmojiRepresentation) { + rep.drawableRes?.let { + view.findViewById(R.id.item_emoji_tv).isVisible = false + view.findViewById(R.id.item_emoji_image).isVisible = true + view.findViewById(R.id.item_emoji_image).setImageDrawable(ContextCompat.getDrawable(view.context, it)) + } ?: kotlin.run { + view.findViewById(R.id.item_emoji_tv).isVisible = true + view.findViewById(R.id.item_emoji_image).isVisible = false + view.findViewById(R.id.item_emoji_tv).text = rep.emoji + } + view.findViewById(R.id.item_emoji_name_tv).setText(rep.nameResId) } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt new file mode 100644 index 0000000000..2214774882 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingController.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.crypto.verification.qrconfirmation + +import com.airbnb.epoxy.EpoxyController +import im.vector.riotx.R +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem +import javax.inject.Inject + +class VerificationQRWaitingController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider +) : EpoxyController() { + + private var args: VerificationQRWaitingFragment.Args? = null + + fun update(args: VerificationQRWaitingFragment.Args) { + this.args = args + requestModelBuild() + } + + override fun buildModels() { + val params = args ?: return + + bottomSheetVerificationNoticeItem { + id("notice") + apply { + notice(stringProvider.getString(R.string.qr_code_scanned_verif_waiting_notice)) + } + } + + bottomSheetVerificationBigImageItem { + id("image") + imageRes(R.drawable.ic_shield_trusted) + } + + bottomSheetVerificationWaitingItem { + id("waiting") + title(stringProvider.getString(R.string.qr_code_scanned_verif_waiting, params.otherUserName)) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt new file mode 100644 index 0000000000..77de3997cb --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQRWaitingFragment.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.crypto.verification.qrconfirmation + +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import com.airbnb.mvrx.MvRx +import im.vector.riotx.R +import im.vector.riotx.core.extensions.cleanup +import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.platform.VectorBaseFragment +import kotlinx.android.parcel.Parcelize +import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.* +import javax.inject.Inject + +class VerificationQRWaitingFragment @Inject constructor( + val controller: VerificationQRWaitingController +) : VectorBaseFragment() { + + @Parcelize + data class Args( + val isMe: Boolean, + val otherUserName: String + ) : Parcelable + + override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupRecyclerView() + (arguments?.getParcelable(MvRx.KEY_ARG) as? Args)?.let { + controller.update(it) + } + } + + override fun onDestroyView() { + bottomSheetVerificationRecyclerView.cleanup() + super.onDestroyView() + } + + private fun setupRecyclerView() { + bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt index f775ac7941..dd1d3d0f90 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherController.kt @@ -21,7 +21,9 @@ import im.vector.riotx.R import im.vector.riotx.core.epoxy.dividerItem import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewState import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem import javax.inject.Inject @@ -32,33 +34,37 @@ class VerificationQrScannedByOtherController @Inject constructor( var listener: Listener? = null - init { + private var viewState: VerificationBottomSheetViewState? = null + + fun update(viewState: VerificationBottomSheetViewState) { + this.viewState = viewState requestModelBuild() } override fun buildModels() { + val state = viewState ?: return + bottomSheetVerificationNoticeItem { id("notice") - notice(stringProvider.getString(R.string.qr_code_scanned_by_other_notice)) + apply { + if (state.isMe) { + notice(stringProvider.getString(R.string.qr_code_scanned_self_verif_notice)) + } else { + val name = state.otherUserMxItem?.getBestName() ?: "" + notice(stringProvider.getString(R.string.qr_code_scanned_by_other_notice, name)) + } + } + } + + bottomSheetVerificationBigImageItem { + id("image") + imageRes(R.drawable.ic_shield_trusted) } dividerItem { id("sep0") } - bottomSheetVerificationActionItem { - id("confirm") - title(stringProvider.getString(R.string.qr_code_scanned_by_other_yes)) - titleColor(colorProvider.getColor(R.color.riotx_accent)) - iconRes(R.drawable.ic_check_on) - iconColor(colorProvider.getColor(R.color.riotx_accent)) - listener { listener?.onUserConfirmsQrCodeScanned() } - } - - dividerItem { - id("sep1") - } - bottomSheetVerificationActionItem { id("deny") title(stringProvider.getString(R.string.qr_code_scanned_by_other_no)) @@ -67,6 +73,19 @@ class VerificationQrScannedByOtherController @Inject constructor( iconColor(colorProvider.getColor(R.color.vector_error_color)) listener { listener?.onUserDeniesQrCodeScanned() } } + + dividerItem { + id("sep1") + } + + bottomSheetVerificationActionItem { + id("confirm") + title(stringProvider.getString(R.string.qr_code_scanned_by_other_yes)) + titleColor(colorProvider.getColor(R.color.riotx_accent)) + iconRes(R.drawable.ic_check_on) + iconColor(colorProvider.getColor(R.color.riotx_accent)) + listener { listener?.onUserConfirmsQrCodeScanned() } + } } interface Listener { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherFragment.kt index 14d294a27a..a8a16f8006 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/qrconfirmation/VerificationQrScannedByOtherFragment.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.crypto.verification.qrconfirmation import android.os.Bundle import android.view.View import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith @@ -37,10 +38,13 @@ class VerificationQrScannedByOtherFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupRecyclerView() } + override fun invalidate() = withState(sharedViewModel) { state -> + controller.update(state) + } + override fun onDestroyView() { bottomSheetVerificationRecyclerView.cleanup() controller.listener = null diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt index a8c9cf679b..48e92ca438 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt @@ -47,9 +47,9 @@ import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineEventVi import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotx.features.home.room.detail.timeline.helper.nextOrNull import im.vector.riotx.features.home.room.detail.timeline.item.BaseEventItem +import im.vector.riotx.features.home.room.detail.timeline.item.BasedMergedItem import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem_ -import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.riotx.features.home.room.detail.timeline.item.TimelineReadMarkerItem_ @@ -373,7 +373,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec val localId: Long, val eventId: String?, val eventModel: EpoxyModel<*>? = null, - val mergedHeaderModel: MergedHeaderItem? = null, + val mergedHeaderModel: BasedMergedItem<*>? = null, val formattedDayModel: DaySeparatorItem? = null ) { fun shouldTriggerBuild(): Boolean { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt new file mode 100644 index 0000000000..ff65b0e656 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home.room.detail.timeline.factory + +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent +import im.vector.riotx.R +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider +import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController +import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider +import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory +import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory +import im.vector.riotx.features.home.room.detail.timeline.item.StatusTileTimelineItem +import im.vector.riotx.features.home.room.detail.timeline.item.StatusTileTimelineItem_ +import javax.inject.Inject + +class EncryptionItemFactory @Inject constructor( + private val messageItemAttributesFactory: MessageItemAttributesFactory, + private val messageColorProvider: MessageColorProvider, + private val stringProvider: StringProvider, + private val informationDataFactory: MessageInformationDataFactory, + private val avatarSizeProvider: AvatarSizeProvider) { + + fun create(event: TimelineEvent, + highlight: Boolean, + callback: TimelineEventController.Callback?): StatusTileTimelineItem? { + val algorithm = event.root.getClearContent().toModel()?.algorithm + val informationData = informationDataFactory.create(event, null) + val attributes = messageItemAttributesFactory.create(null, informationData, callback) + + val isSafeAlgorithm = algorithm == MXCRYPTO_ALGORITHM_MEGOLM + val title: String + val description: String + val shield: StatusTileTimelineItem.ShieldUIState + if (isSafeAlgorithm) { + title = stringProvider.getString(R.string.encryption_enabled) + description = stringProvider.getString(R.string.encryption_enabled_tile_description) + shield = StatusTileTimelineItem.ShieldUIState.BLACK + } else { + title = stringProvider.getString(R.string.encryption_not_enabled) + description = stringProvider.getString(R.string.encryption_unknown_algorithm_tile_description) + shield = StatusTileTimelineItem.ShieldUIState.RED + } + return StatusTileTimelineItem_() + .attributes( + StatusTileTimelineItem.Attributes( + title = title, + description = description, + shieldUIState = shield, + informationData = informationData, + avatarRenderer = attributes.avatarRenderer, + messageColorProvider = messageColorProvider, + emojiTypeFace = attributes.emojiTypeFace, + itemClickListener = attributes.itemClickListener, + itemLongClickListener = attributes.itemLongClickListener, + reactionPillCallback = attributes.reactionPillCallback, + readReceiptsCallback = attributes.readReceiptsCallback + ) + ) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 42dc4e07eb..377fc5ab4a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -16,16 +16,24 @@ package im.vector.riotx.features.home.room.detail.timeline.factory +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.riotx.features.home.room.detail.timeline.helper.MergedTimelineEventVisibilityStateChangedListener import im.vector.riotx.features.home.room.detail.timeline.helper.canBeMerged +import im.vector.riotx.features.home.room.detail.timeline.helper.isRoomConfiguration import im.vector.riotx.features.home.room.detail.timeline.helper.prevSameTypeEvents -import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem -import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem_ +import im.vector.riotx.features.home.room.detail.timeline.item.BasedMergedItem +import im.vector.riotx.features.home.room.detail.timeline.item.MergedMembershipEventsItem +import im.vector.riotx.features.home.room.detail.timeline.item.MergedMembershipEventsItem_ +import im.vector.riotx.features.home.room.detail.timeline.item.MergedRoomCreationItem +import im.vector.riotx.features.home.room.detail.timeline.item.MergedRoomCreationItem_ import javax.inject.Inject class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: ActiveSessionHolder, @@ -43,8 +51,12 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act eventIdToHighlight: String?, callback: TimelineEventController.Callback?, requestModelBuild: () -> Unit) - : MergedHeaderItem? { - return if (!event.canBeMerged() || (nextEvent?.root?.getClearType() == event.root.getClearType() && !addDaySeparator)) { + : BasedMergedItem<*>? { + return if (nextEvent?.root?.getClearType() == EventType.STATE_ROOM_CREATE && event.isRoomConfiguration()) { + // It's the first item before room.create + // Collapse all room configuration events + buildRoomCreationMergedSummary(currentPosition, items, event, eventIdToHighlight, requestModelBuild, callback) + } else if (!event.canBeMerged() || (nextEvent?.root?.getClearType() == event.root.getClearType() && !addDaySeparator)) { null } else { val prevSameTypeEvents = items.prevSameTypeEvents(currentPosition, 2) @@ -53,14 +65,14 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act } else { var highlighted = false val mergedEvents = (prevSameTypeEvents + listOf(event)).asReversed() - val mergedData = ArrayList(mergedEvents.size) + val mergedData = ArrayList(mergedEvents.size) mergedEvents.forEach { mergedEvent -> if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) { highlighted = true } val senderAvatar = mergedEvent.senderAvatar val senderName = mergedEvent.getDisambiguatedDisplayName() - val data = MergedHeaderItem.Data( + val data = BasedMergedItem.Data( userId = mergedEvent.root.senderId ?: "", avatarUrl = senderAvatar, memberName = senderName, @@ -82,7 +94,7 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act collapsedEventIds.removeAll(mergedEventIds) } val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() } - val attributes = MergedHeaderItem.Attributes( + val attributes = MergedMembershipEventsItem.Attributes( isCollapsed = isCollapsed, mergeData = mergedData, avatarRenderer = avatarRenderer, @@ -92,7 +104,7 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act }, readReceiptsCallback = callback ) - MergedHeaderItem_() + MergedMembershipEventsItem_() .id(mergeId) .leftGuideline(avatarSizeProvider.leftGuideline) .highlighted(isCollapsed && highlighted) @@ -104,6 +116,81 @@ class MergedHeaderItemFactory @Inject constructor(private val sessionHolder: Act } } + private fun buildRoomCreationMergedSummary(currentPosition: Int, + items: List, + event: TimelineEvent, + eventIdToHighlight: String?, + requestModelBuild: () -> Unit, + callback: TimelineEventController.Callback?): MergedRoomCreationItem_? { + var prevEvent = if (currentPosition > 0) items[currentPosition - 1] else null + var tmpPos = currentPosition - 1 + val mergedEvents = ArrayList().also { it.add(event) } + var hasEncryption = false + var encryptionAlgorithm: String? = null + while (prevEvent != null && prevEvent.isRoomConfiguration()) { + if (prevEvent.root.getClearType() == EventType.STATE_ROOM_ENCRYPTION) { + hasEncryption = true + encryptionAlgorithm = prevEvent.root.getClearContent()?.toModel()?.algorithm + } + mergedEvents.add(prevEvent) + tmpPos-- + prevEvent = if (tmpPos >= 0) items[tmpPos] else null + } + return if (mergedEvents.size > 2) { + var highlighted = false + val mergedData = ArrayList(mergedEvents.size) + mergedEvents.reversed() + .forEach { mergedEvent -> + if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) { + highlighted = true + } + val senderAvatar = mergedEvent.senderAvatar + val senderName = mergedEvent.getDisambiguatedDisplayName() + val data = BasedMergedItem.Data( + userId = mergedEvent.root.senderId ?: "", + avatarUrl = senderAvatar, + memberName = senderName, + localId = mergedEvent.localId, + eventId = mergedEvent.root.eventId ?: "" + ) + mergedData.add(data) + } + val mergedEventIds = mergedEvents.map { it.localId } + // We try to find if one of the item id were used as mergeItemCollapseStates key + // => handle case where paginating from mergeable events and we get more + val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull() + val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey) + ?: true + val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState } + if (isCollapsed) { + collapsedEventIds.addAll(mergedEventIds) + } else { + collapsedEventIds.removeAll(mergedEventIds) + } + val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() } + val attributes = MergedRoomCreationItem.Attributes( + isCollapsed = isCollapsed, + mergeData = mergedData, + avatarRenderer = avatarRenderer, + onCollapsedStateChanged = { + mergeItemCollapseStates[event.localId] = it + requestModelBuild() + }, + hasEncryptionEvent = hasEncryption, + isEncryptionAlgorithmSecure = encryptionAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM, + readReceiptsCallback = callback + ) + MergedRoomCreationItem_() + .id(mergeId) + .leftGuideline(avatarSizeProvider.leftGuideline) + .highlighted(isCollapsed && highlighted) + .attributes(attributes) + .also { + it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents)) + } + } else null + } + fun isCollapsed(localId: Long): Boolean { return collapsedEventIds.contains(localId) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 1462f5fe0d..7e6c387934 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -29,6 +29,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me private val encryptedItemFactory: EncryptedItemFactory, private val noticeItemFactory: NoticeItemFactory, private val defaultItemFactory: DefaultItemFactory, + private val encryptionItemFactory: EncryptionItemFactory, private val roomCreateItemFactory: RoomCreateItemFactory, private val verificationConclusionItemFactory: VerificationItemFactory, private val userPreferencesProvider: UserPreferencesProvider) { @@ -57,8 +58,10 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.CALL_HANGUP, EventType.CALL_ANSWER, EventType.REACTION, - EventType.REDACTION, - EventType.STATE_ROOM_ENCRYPTION -> noticeItemFactory.create(event, highlight, callback) + EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback) + EventType.STATE_ROOM_ENCRYPTION -> { + encryptionItemFactory.create(event, highlight, callback) + } // State room create EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) // Crypto diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/VerificationItemFactory.kt index ee529282f9..837d0ad571 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/VerificationItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/VerificationItemFactory.kt @@ -25,15 +25,17 @@ import im.vector.matrix.android.api.session.room.model.message.MessageRelationCo import im.vector.matrix.android.api.session.room.model.message.MessageVerificationCancelContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.session.room.VerificationState +import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyModel +import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.UserPreferencesProvider import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory -import im.vector.riotx.features.home.room.detail.timeline.item.VerificationRequestConclusionItem -import im.vector.riotx.features.home.room.detail.timeline.item.VerificationRequestConclusionItem_ +import im.vector.riotx.features.home.room.detail.timeline.item.StatusTileTimelineItem +import im.vector.riotx.features.home.room.detail.timeline.item.StatusTileTimelineItem_ import javax.inject.Inject /** @@ -48,6 +50,7 @@ class VerificationItemFactory @Inject constructor( private val avatarSizeProvider: AvatarSizeProvider, private val noticeItemFactory: NoticeItemFactory, private val userPreferencesProvider: UserPreferencesProvider, + private val stringProvider: StringProvider, private val session: Session ) { @@ -88,12 +91,12 @@ class VerificationItemFactory @Inject constructor( CancelCode.MismatchedKeys, CancelCode.MismatchedSas -> { // We should display these bad conclusions - return VerificationRequestConclusionItem_() + return StatusTileTimelineItem_() .attributes( - VerificationRequestConclusionItem.Attributes( - toUserId = informationData.senderId, - toUserName = informationData.memberName.toString(), - isPositive = false, + StatusTileTimelineItem.Attributes( + title = stringProvider.getString(R.string.verification_conclusion_warning), + description = "${informationData.memberName} (${informationData.senderId})", + shieldUIState = StatusTileTimelineItem.ShieldUIState.RED, informationData = informationData, avatarRenderer = attributes.avatarRenderer, messageColorProvider = messageColorProvider, @@ -121,12 +124,12 @@ class VerificationItemFactory @Inject constructor( // We only display the done sent by the other user, the done send by me is ignored return ignoredConclusion(event, highlight, callback) } - return VerificationRequestConclusionItem_() + return StatusTileTimelineItem_() .attributes( - VerificationRequestConclusionItem.Attributes( - toUserId = informationData.senderId, - toUserName = informationData.memberName.toString(), - isPositive = true, + StatusTileTimelineItem.Attributes( + title = stringProvider.getString(R.string.sas_verified), + description = "${informationData.memberName} (${informationData.senderId})", + shieldUIState = StatusTileTimelineItem.ShieldUIState.GREEN, informationData = informationData, avatarRenderer = attributes.avatarRenderer, messageColorProvider = messageColorProvider, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 5c763cb114..1ea3cd64ac 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -50,6 +50,18 @@ fun TimelineEvent.canBeMerged(): Boolean { return root.getClearType() == EventType.STATE_ROOM_MEMBER } +fun TimelineEvent.isRoomConfiguration(): Boolean { + return when (root.getClearType()) { + EventType.STATE_ROOM_GUEST_ACCESS, + EventType.STATE_ROOM_HISTORY_VISIBILITY, + EventType.STATE_ROOM_JOIN_RULES, + EventType.STATE_ROOM_MEMBER, + EventType.STATE_ROOM_NAME, + EventType.STATE_ROOM_ENCRYPTION -> true + else -> false + } +} + fun List.nextSameTypeEvents(index: Int, minSize: Int): List { if (index >= size - 1) { return emptyList() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BasedMergedItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BasedMergedItem.kt new file mode 100644 index 0000000000..adc9b1442f --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BasedMergedItem.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home.room.detail.timeline.item + +import android.view.View +import android.widget.TextView +import androidx.annotation.IdRes +import androidx.core.view.isVisible +import im.vector.matrix.android.api.util.MatrixItem +import im.vector.riotx.R +import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController + +abstract class BasedMergedItem : BaseEventItem() { + + abstract val attributes: Attributes + + override fun bind(holder: H) { + super.bind(holder) + holder.expandView.setOnClickListener { + attributes.onCollapsedStateChanged(!attributes.isCollapsed) + } + if (attributes.isCollapsed) { + holder.separatorView.visibility = View.GONE + holder.expandView.setText(R.string.merged_events_expand) + } else { + holder.separatorView.visibility = View.VISIBLE + holder.expandView.setText(R.string.merged_events_collapse) + } + // No read receipt for this item + holder.readReceiptsView.isVisible = false + } + + protected val distinctMergeData by lazy { + attributes.mergeData.distinctBy { it.userId } + } + + override fun getEventIds(): List { + return if (attributes.isCollapsed) { + attributes.mergeData.map { it.eventId } + } else { + emptyList() + } + } + + data class Data( + val localId: Long, + val eventId: String, + val userId: String, + val memberName: String, + val avatarUrl: String? + ) + + fun Data.toMatrixItem() = MatrixItem.UserItem(userId, memberName, avatarUrl) + + interface Attributes { + val isCollapsed: Boolean + val mergeData: List + val avatarRenderer: AvatarRenderer + val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? + val onCollapsedStateChanged: (Boolean) -> Unit + } + + abstract class Holder(@IdRes stubId: Int) : BaseEventItem.BaseHolder(stubId) { + val expandView by bind(R.id.itemMergedExpandTextView) + val separatorView by bind(R.id.itemMergedSeparatorView) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedHeaderItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt similarity index 61% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedHeaderItem.kt rename to vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt index 93f7dc271d..8e3ba0bcff 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedHeaderItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt @@ -24,28 +24,20 @@ import androidx.core.view.children import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass -import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo) -abstract class MergedHeaderItem : BaseEventItem() { - - @EpoxyAttribute - lateinit var attributes: Attributes - - private val distinctMergeData by lazy { - attributes.mergeData.distinctBy { it.userId } - } +abstract class MergedMembershipEventsItem : BasedMergedItem() { override fun getViewType() = STUB_ID + @EpoxyAttribute + override lateinit var attributes: Attributes + override fun bind(holder: Holder) { super.bind(holder) - holder.expandView.setOnClickListener { - attributes.onCollapsedStateChanged(!attributes.isCollapsed) - } if (attributes.isCollapsed) { val summary = holder.expandView.resources.getQuantityString(R.plurals.membership_changes, attributes.mergeData.size, attributes.mergeData.size) holder.summaryView.text = summary @@ -60,52 +52,28 @@ abstract class MergedHeaderItem : BaseEventItem() { view.visibility = View.GONE } } - holder.separatorView.visibility = View.GONE - holder.expandView.setText(R.string.merged_events_expand) } else { holder.avatarListView.visibility = View.INVISIBLE holder.summaryView.visibility = View.GONE - holder.separatorView.visibility = View.VISIBLE - holder.expandView.setText(R.string.merged_events_collapse) } // No read receipt for this item holder.readReceiptsView.isVisible = false } - override fun getEventIds(): List { - return if (attributes.isCollapsed) { - attributes.mergeData.map { it.eventId } - } else { - emptyList() - } - } - - data class Data( - val localId: Long, - val eventId: String, - val userId: String, - val memberName: String, - val avatarUrl: String? - ) - - fun Data.toMatrixItem() = MatrixItem.UserItem(userId, memberName, avatarUrl) - - data class Attributes( - val isCollapsed: Boolean, - val mergeData: List, - val avatarRenderer: AvatarRenderer, - val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, - val onCollapsedStateChanged: (Boolean) -> Unit - ) - - class Holder : BaseHolder(STUB_ID) { - val expandView by bind(R.id.itemMergedExpandTextView) + class Holder : BasedMergedItem.Holder(STUB_ID) { val summaryView by bind(R.id.itemMergedSummaryTextView) - val separatorView by bind(R.id.itemMergedSeparatorView) val avatarListView by bind(R.id.itemMergedAvatarListView) } companion object { private const val STUB_ID = R.id.messageContentMergedHeaderStub } + + data class Attributes( + override val isCollapsed: Boolean, + override val mergeData: List, + override val avatarRenderer: AvatarRenderer, + override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, + override val onCollapsedStateChanged: (Boolean) -> Unit + ) : BasedMergedItem.Attributes } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt new file mode 100644 index 0000000000..81050194a8 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home.room.detail.timeline.item + +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController + +@EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo) +abstract class MergedRoomCreationItem : BasedMergedItem() { + + @EpoxyAttribute + override lateinit var attributes: Attributes + + override fun getViewType() = STUB_ID + + override fun bind(holder: Holder) { + super.bind(holder) + + if (attributes.isCollapsed) { + val data = distinctMergeData.firstOrNull() + + val summary = holder.expandView.resources.getString(R.string.room_created_summary_item, + data?.memberName ?: data?.userId ?: "") + holder.summaryView.text = summary + holder.summaryView.visibility = View.VISIBLE + holder.avatarView.visibility = View.VISIBLE + if (data != null) { + holder.avatarView.visibility = View.VISIBLE + attributes.avatarRenderer.render(data.toMatrixItem(), holder.avatarView) + } else { + holder.avatarView.visibility = View.GONE + } + + if (attributes.hasEncryptionEvent) { + holder.encryptionTile.isVisible = true + holder.encryptionTile.updateLayoutParams { + this.marginEnd = leftGuideline + } + if (attributes.isEncryptionAlgorithmSecure) { + holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_enabled) + holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_enabled_tile_description) + holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER + holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( + ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_black), + null, null, null + ) + } else { + holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_not_enabled) + holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_unknown_algorithm_tile_description) + holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( + ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning), + null, null, null + ) + } + } else { + holder.encryptionTile.isVisible = false + } + } else { + holder.avatarView.visibility = View.INVISIBLE + holder.summaryView.visibility = View.GONE + holder.encryptionTile.isGone = true + } + // No read receipt for this item + holder.readReceiptsView.isVisible = false + } + + class Holder : BasedMergedItem.Holder(STUB_ID) { + val summaryView by bind(R.id.itemNoticeTextView) + val avatarView by bind(R.id.itemNoticeAvatarView) + val encryptionTile by bind(R.id.creationEncryptionTile) + + val e2eTitleTextView by bind(R.id.itemVerificationDoneTitleTextView) + val e2eTitleDescriptionView by bind(R.id.itemVerificationDoneDetailTextView) + } + + companion object { + private const val STUB_ID = R.id.messageContentMergedCreationStub + } + + data class Attributes( + override val isCollapsed: Boolean, + override val mergeData: List, + override val avatarRenderer: AvatarRenderer, + override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, + override val onCollapsedStateChanged: (Boolean) -> Unit, + val hasEncryptionEvent : Boolean, + val isEncryptionAlgorithmSecure: Boolean + ) : BasedMergedItem.Attributes +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestConclusionItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt similarity index 81% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestConclusionItem.kt rename to vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt index 2b28e15cab..f9ea2a71df 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestConclusionItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt @@ -31,7 +31,7 @@ import im.vector.riotx.features.home.room.detail.timeline.MessageColorProvider import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController @EpoxyModelClass(layout = R.layout.item_timeline_event_base_state) -abstract class VerificationRequestConclusionItem : AbsBaseMessageItem() { +abstract class StatusTileTimelineItem : AbsBaseMessageItem() { override val baseAttributes: AbsBaseMessageItem.Attributes get() = attributes @@ -47,11 +47,17 @@ abstract class VerificationRequestConclusionItem : AbsBaseMessageItem { this.marginEnd = leftGuideline } - val title = if (attributes.isPositive) R.string.sas_verified else R.string.verification_conclusion_warning - holder.titleView.text = holder.view.context.getString(title) - holder.descriptionView.text = "${attributes.informationData.memberName} (${attributes.informationData.senderId})" - val startDrawable = if (attributes.isPositive) R.drawable.ic_shield_trusted else R.drawable.ic_shield_warning + holder.titleView.text = attributes.title + holder.descriptionView.text = attributes.description + holder.descriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER + + val startDrawable = when (attributes.shieldUIState) { + ShieldUIState.GREEN -> R.drawable.ic_shield_trusted + ShieldUIState.BLACK -> R.drawable.ic_shield_black + ShieldUIState.RED -> R.drawable.ic_shield_warning + } + holder.titleView.setCompoundDrawablesWithIntrinsicBounds( ContextCompat.getDrawable(holder.view.context, startDrawable), null, null, null @@ -75,9 +81,9 @@ abstract class VerificationRequestConclusionItem : AbsBaseMessageItem + + diff --git a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml index ebc32baa57..3e959bcda8 100644 --- a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml +++ b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml @@ -54,6 +54,13 @@ tools:layout_marginTop="160dp" tools:visibility="visible" /> + + diff --git a/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml b/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml new file mode 100644 index 0000000000..3ed840954f --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_verification_done_stub.xml b/vector/src/main/res/layout/item_timeline_event_status_tile_stub.xml similarity index 100% rename from vector/src/main/res/layout/item_timeline_event_verification_done_stub.xml rename to vector/src/main/res/layout/item_timeline_event_status_tile_stub.xml diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index 86fb1e0e12..dd8ead5f27 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -2166,7 +2166,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. QR kodea - Beste erabiltzaileak QR kodea ongi eskaneatu du\? Bai Ez diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 341a247d52..76e2a0d0a7 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2174,7 +2174,6 @@ Si vous nโ€™avez pas configurรฉ de nouvelle mรฉthode de rรฉcupรฉration, un attaq Code QR - Lโ€™autre utilisateur a-t-il bien scannรฉ le code QRย \? Oui Non diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index 64eee79075..c4dde2e079 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -2169,7 +2169,6 @@ Ha nem te รกllรญtottad be a visszaรกllรญtรกsi metรณdust, akkor egy tรกmadรณ prรณ QR kรณd - A mรกsik felhasznรกlรณ sikeresen beolvasta a QR kรณdot\? Igen Nem diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 4d1ac8edaf..92b235c5dd 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -2219,7 +2219,6 @@ Codice QR - L\'altro utente ha scansionato correttamente il codice QR\? Sรฌ No diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 7ff5253530..96e0e1201a 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -2129,7 +2129,6 @@ Qรซ tรซ garantoni se sโ€™ju shpรซton gjรซ, thjesht mbajeni tรซ aktivizuar mekani Gatit CrossSigning Zeroji Kyรงet - A e skanoi me sukses pรซrdoruesi tjetรซr kodin QR\? Jo Humbi lidhja me shรซrbyesin diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 8078ca8efa..111ee26b36 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -2119,7 +2119,6 @@ Matrix ไธญ็š„ๆถˆๆฏๅฏ่ฆ‹ๅบฆ้กžไผผไบŽ้›ปๅญ้ƒตไปถใ€‚ๆˆ‘ๅ€‘ๅฟ˜่จ˜ๆ‚จ็š„้ƒตไปถๆ„ QR code - ๅ…ถไป–ไฝฟ็”จ่€…ๆ˜ฏๅฆๆŽƒ่‹— QR code ๆˆๅŠŸ๏ผŸ ๆ˜ฏ ๅฆ diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 928aafbd55..ae2cb7bbbb 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2148,7 +2148,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming QR code - Did the other user successfully scan the QR code? + Almost there! Is %s showing the same shield? Yes No diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 29948d1838..9ef21170a4 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -87,6 +87,20 @@ Setting a Message Password lets you secure & unlock encrypted messages and trust.\n\nIf you donโ€™t want to set a Message Password, generate a Message Key instead. Setting a Message Password lets you secure & unlock encrypted messages and trust. + + Encryption enabled + Messages in this room are end-to-end encrypted. Learn more & verify users in their profile. + Encryption not enabled + The encryption used by this room is not supported + + %s created and configured the room. + + Almost there! Is the other device showing the same shield? + Almost there! Waiting for confirmationโ€ฆ + Waiting for %sโ€ฆ + + Failed to import keys + @@ -107,7 +121,7 @@ - An unexpected error occurred +