diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index 79ee123c2b..6e6eec1148 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -1,5 +1,6 @@ \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt index c2a847dd0b..a626dd5573 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt @@ -44,22 +44,18 @@ interface SasVerificationService { fun getExistingVerificationRequest(otherUser: String, tid: String?): PendingVerificationRequest? - /** - * Shortcut for KeyVerificationStart.VERIF_METHOD_SAS - * @see beginKeyVerification - */ - fun beginKeyVerificationSAS(userId: String, deviceID: String): String? + fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest? + + fun beginKeyVerification(method: VerificationMethod, userId: String, deviceID: String): String? /** * Request a key verification from another user using toDevice events. */ - fun beginKeyVerification(method: String, userId: String, deviceID: String): String? - - fun requestKeyVerificationInDMs(userId: String, roomId: String): PendingVerificationRequest + fun requestKeyVerificationInDMs(methods: List, userId: String, roomId: String): PendingVerificationRequest fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) - fun beginKeyVerificationInDMs(method: String, + fun beginKeyVerificationInDMs(method: VerificationMethod, transactionId: String, roomId: String, otherUserId: String, @@ -67,7 +63,7 @@ interface SasVerificationService { callback: MatrixCallback?): String? /** - * Returns false if the request is unknwown + * Returns false if the request is unknown */ fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String): Boolean @@ -84,14 +80,14 @@ interface SasVerificationService { companion object { - private const val TEN_MINTUTES_IN_MILLIS = 10 * 60 * 1000 - private const val FIVE_MINTUTES_IN_MILLIS = 5 * 60 * 1000 + private const val TEN_MINUTES_IN_MILLIS = 10 * 60 * 1000 + private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000 fun isValidRequest(age: Long?): Boolean { if (age == null) return false val now = System.currentTimeMillis() - val tooInThePast = now - TEN_MINTUTES_IN_MILLIS - val tooInTheFuture = now + FIVE_MINTUTES_IN_MILLIS + val tooInThePast = now - TEN_MINUTES_IN_MILLIS + val tooInTheFuture = now + FIVE_MINUTES_IN_MILLIS return age in tooInThePast..tooInTheFuture } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationMethod.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationMethod.kt new file mode 100644 index 0000000000..a2dd90bc84 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/VerificationMethod.kt @@ -0,0 +1,26 @@ +/* + * Copyright 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.matrix.android.api.session.crypto.sas + +/** + * Verification methods supported (or to be supported) by the matrix SDK + */ +enum class VerificationMethod { + SAS, + // Not supported yet + SCAN +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt index a33decc821..dffa4ba3cb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.crypto.sas.SasMode import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.matrix.android.internal.crypto.model.rest.supportedVerificationMethods import im.vector.matrix.android.internal.crypto.verification.SASVerificationTransaction import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart import im.vector.matrix.android.internal.util.JsonCanonicalizer @@ -45,12 +45,11 @@ internal data class MessageVerificationStartContent( get() = relatesTo?.eventId override fun isValid(): Boolean { - if ( - (transactionID.isNullOrBlank() - || fromDevice.isNullOrBlank() - || method != KeyVerificationStart.VERIF_METHOD_SAS - || keyAgreementProtocols.isNullOrEmpty() - || hashes.isNullOrEmpty()) + if (transactionID.isNullOrBlank() + || fromDevice.isNullOrBlank() + || method !in supportedVerificationMethods + || keyAgreementProtocols.isNullOrEmpty() + || hashes.isNullOrEmpty() || !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty() || (!messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256) && !messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationReady.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationReady.kt index ca3b1e0075..84bc73fda0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationReady.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationReady.kt @@ -25,8 +25,7 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRea @JsonClass(generateAdapter = true) internal data class KeyVerificationReady( @Json(name = "from_device") override val fromDevice: String?, - // TODO add qr? - @Json(name = "methods") override val methods: List? = listOf(KeyVerificationStart.VERIF_METHOD_SAS), + @Json(name = "methods") override val methods: List?, @Json(name = "transaction_id") override var transactionID: String? = null ) : SendToDeviceObject, VerificationInfoReady { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt index 4511db324a..0af7b45b70 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt @@ -28,7 +28,7 @@ internal data class KeyVerificationRequest( @Json(name = "from_device") val fromDevice: String, /** The verification methods supported by the sender. */ - val methods: List = listOf(KeyVerificationStart.VERIF_METHOD_SAS), + val methods: List, /** * The POSIX timestamp in milliseconds for when the request was made. * If the request is in the future by more than 5 minutes or more than 10 minutes in the past, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt index e25ed10a6a..fa4ec3acfa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt @@ -27,7 +27,7 @@ import timber.log.Timber * Sent by Alice to initiate an interactive key verification. */ @JsonClass(generateAdapter = true) -data class KeyVerificationStart( +internal data class KeyVerificationStart( @Json(name = "from_device") override val fromDevice: String? = null, override val method: String? = null, @Json(name = "transaction_id") override val transactionID: String? = null, @@ -41,22 +41,18 @@ data class KeyVerificationStart( return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this) } - companion object { - const val VERIF_METHOD_SAS = "m.sas.v1" - const val VERIF_METHOD_SCAN = "m.qr_code.scan.v1" - } - override fun isValid(): Boolean { - if ((transactionID.isNullOrBlank() - || fromDevice.isNullOrBlank() - || method != VERIF_METHOD_SAS - || keyAgreementProtocols.isNullOrEmpty() - || hashes.isNullOrEmpty()) + if (transactionID.isNullOrBlank() + || fromDevice.isNullOrBlank() + || method !in supportedVerificationMethods + || keyAgreementProtocols.isNullOrEmpty() + || hashes.isNullOrEmpty() || !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty() || (!messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256) && !messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) - || shortAuthenticationStrings.isNullOrEmpty() || !shortAuthenticationStrings.contains(SasMode.DECIMAL)) { + || shortAuthenticationStrings.isNullOrEmpty() + || !shortAuthenticationStrings.contains(SasMode.DECIMAL)) { Timber.e("## received invalid verification request") return false } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/VerificationMethodValues.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/VerificationMethodValues.kt new file mode 100644 index 0000000000..168a8c8f48 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/VerificationMethodValues.kt @@ -0,0 +1,32 @@ +/* + * Copyright 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.matrix.android.internal.crypto.model.rest + +import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod + +internal const val VERIFICATION_METHOD_SAS = "m.sas.v1" +internal const val VERIFICATION_METHOD_SCAN = "m.qr_code.scan.v1" + +internal fun VerificationMethod.toValue(): String { + return when (this) { + VerificationMethod.SAS -> VERIFICATION_METHOD_SAS + VerificationMethod.SCAN -> VERIFICATION_METHOD_SCAN + } +} + +// TODO Add SCAN +internal val supportedVerificationMethods = listOf(VERIFICATION_METHOD_SAS) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt index 2167940aaa..c285f16ee3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt @@ -104,6 +104,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( // The verification is started from another device Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } + params.sasVerificationService.onRoomRequestHandledByOtherDevice(event) } } } else if (EventType.KEY_VERIFICATION_READY == event.type) { @@ -112,11 +113,13 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( // The verification is started from another device Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } + params.sasVerificationService.onRoomRequestHandledByOtherDevice(event) } } } else if (EventType.KEY_VERIFICATION_CANCEL == event.type || EventType.KEY_VERIFICATION_DONE == event.type) { event.getClearContent().toModel()?.relatesTo?.eventId?.let { transactionsHandledByOtherDevice.remove(it) + params.sasVerificationService.onRoomRequestHandledByOtherDevice(event) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt index 5eead70c17..6e6f55b9fb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt @@ -19,9 +19,10 @@ import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState +import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.matrix.android.internal.crypto.model.rest.toValue import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import timber.log.Timber @@ -71,7 +72,7 @@ internal class DefaultOutgoingSASVerificationRequest( cancel(CancelCode.UnexpectedMessage) } - fun start() { + fun start(method: VerificationMethod) { if (state != SasVerificationTxState.None) { Timber.e("## SAS O: start verification from invalid state") // should I cancel?? @@ -80,7 +81,7 @@ internal class DefaultOutgoingSASVerificationRequest( val startMessage = transport.createStart( credentials.deviceId ?: "", - KeyVerificationStart.VERIF_METHOD_SAS, + method.toValue(), transactionId, KNOWN_AGREEMENT_PROTOCOLS, KNOWN_HASHES, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index f5075cbb23..8fa91e2f4a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -22,10 +22,7 @@ import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.CryptoService -import im.vector.matrix.android.api.session.crypto.sas.CancelCode -import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService -import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState -import im.vector.matrix.android.api.session.crypto.sas.safeValueOf +import im.vector.matrix.android.api.session.crypto.sas.* import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.LocalEcho @@ -216,7 +213,20 @@ internal class DefaultSasVerificationService @Inject constructor( } } - fun onRoomRequestReceived(event: Event) { + fun onRoomRequestHandledByOtherDevice(event: Event) { + val requestInfo = event.getClearContent().toModel() + ?: return + val requestId = requestInfo.relatesTo?.eventId ?: return + getExistingVerificationRequestInRoom(event.roomId ?: "", requestId)?.let { + updatePendingRequest( + it.copy( + handledByOtherSession = true + ) + ) + } + } + + suspend fun onRoomRequestReceived(event: Event) { Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}") val requestInfo = event.getClearContent().toModel() ?: return @@ -245,6 +255,7 @@ internal class DefaultSasVerificationService @Inject constructor( ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(), isIncoming = true, otherUserId = senderId, // requestInfo.toUserId, + roomId = event.roomId, transactionId = event.eventId, requestInfo = requestInfo ) @@ -274,31 +285,27 @@ internal class DefaultSasVerificationService @Inject constructor( if (startReq?.isValid()?.not() == true) { Timber.e("## received invalid verification request") if (startReq.transactionID != null) { - sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId - ?: "", event.roomId - ?: "", null).cancelTransaction( - startReq.transactionID ?: "", - otherUserId!!, - startReq.fromDevice ?: event.getSenderKey()!!, - CancelCode.UnknownMethod - ) + sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", null) + .cancelTransaction( + startReq.transactionID ?: "", + otherUserId!!, + startReq.fromDevice ?: event.getSenderKey()!!, + CancelCode.UnknownMethod + ) } return } handleStart(otherUserId, startReq as VerificationInfoStart) { - it.transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId - ?: "", event.roomId - ?: "", it) + it.transport = sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", it) }?.let { - sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId - ?: "", event.roomId - ?: "", null).cancelTransaction( - startReq.transactionID ?: "", - otherUserId!!, - startReq.fromDevice ?: event.getSenderKey()!!, - it - ) + sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", null) + .cancelTransaction( + startReq.transactionID ?: "", + otherUserId!!, + startReq.fromDevice ?: event.getSenderKey()!!, + it + ) } } @@ -356,7 +363,7 @@ internal class DefaultSasVerificationService @Inject constructor( // cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) } else { // Ok we can create - if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) { + if (startReq.method == VERIFICATION_METHOD_SAS) { Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}") // If there is a corresponding request, we can auto accept // as we are the one requesting in first place (or we accepted the request) @@ -647,6 +654,16 @@ internal class DefaultSasVerificationService @Inject constructor( } } + override fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest? { + synchronized(lock = pendingRequests) { + return tid?.let { tid -> + pendingRequests.flatMap { entry -> + entry.value.filter { it.roomId == roomId && it.transactionId == tid } + }.firstOrNull() + } + } + } + private fun getExistingTransactionsForUser(otherUser: String): Collection? { synchronized(txMap) { return txMap[otherUser]?.values @@ -670,14 +687,10 @@ internal class DefaultSasVerificationService @Inject constructor( } } - override fun beginKeyVerificationSAS(userId: String, deviceID: String): String? { - return beginKeyVerification(KeyVerificationStart.VERIF_METHOD_SAS, userId, deviceID) - } - - override fun beginKeyVerification(method: String, userId: String, deviceID: String): String? { + override fun beginKeyVerification(method: VerificationMethod, userId: String, deviceID: String): String? { val txID = createUniqueIDForTransaction(userId, deviceID) // should check if already one (and cancel it) - if (KeyVerificationStart.VERIF_METHOD_SAS == method) { + if (method == VerificationMethod.SAS) { val tx = DefaultOutgoingSASVerificationRequest( setDeviceVerificationAction, credentials, @@ -689,14 +702,14 @@ internal class DefaultSasVerificationService @Inject constructor( tx.transport = sasTransportToDeviceFactory.createTransport(tx) addTransaction(tx) - tx.start() + tx.start(method) return txID } else { throw IllegalArgumentException("Unknown verification method") } } - override fun requestKeyVerificationInDMs(userId: String, roomId: String) + override fun requestKeyVerificationInDMs(methods: List, userId: String, roomId: String) : PendingVerificationRequest { Timber.i("## SAS Requesting verification to user: $userId in room $roomId") @@ -705,8 +718,7 @@ internal class DefaultSasVerificationService @Inject constructor( pendingRequests[userId] = it } - val transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId - ?: "", roomId, null) + val transport = sasTransportRoomMessageFactory.createTransport(roomId, null) // Cancel existing pending requests? requestsForUser.forEach { existingRequest -> @@ -723,11 +735,12 @@ internal class DefaultSasVerificationService @Inject constructor( val verificationRequest = PendingVerificationRequest( ageLocalTs = System.currentTimeMillis(), isIncoming = false, + roomId = roomId, localID = localID, otherUserId = userId ) - transport.sendVerificationRequest(localID, userId, roomId) { syncedId, info -> + transport.sendVerificationRequest(methods.map { it.toValue() }, localID, userId, roomId) { syncedId, info -> // We need to update with the syncedID updatePendingRequest(verificationRequest.copy( transactionId = syncedId, @@ -742,8 +755,8 @@ internal class DefaultSasVerificationService @Inject constructor( } override fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) { - sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId - ?: "", roomId, null).cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User) + sasTransportRoomMessageFactory.createTransport(roomId, null) + .cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User) getExistingVerificationRequest(otherUserId, transactionId)?.let { updatePendingRequest(it.copy( @@ -768,10 +781,13 @@ internal class DefaultSasVerificationService @Inject constructor( dispatchRequestUpdated(updated) } - override fun beginKeyVerificationInDMs(method: String, transactionId: String, roomId: String, - otherUserId: String, otherDeviceId: String, + override fun beginKeyVerificationInDMs(method: VerificationMethod, + transactionId: String, + roomId: String, + otherUserId: String, + otherDeviceId: String, callback: MatrixCallback?): String? { - if (KeyVerificationStart.VERIF_METHOD_SAS == method) { + if (method == VerificationMethod.SAS) { val tx = DefaultOutgoingSASVerificationRequest( setDeviceVerificationAction, credentials, @@ -780,11 +796,10 @@ internal class DefaultSasVerificationService @Inject constructor( transactionId, otherUserId, otherDeviceId) - tx.transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId - ?: "", roomId, tx) + tx.transport = sasTransportRoomMessageFactory.createTransport(roomId, tx) addTransaction(tx) - tx.start() + tx.start(method) return transactionId } else { throw IllegalArgumentException("Unknown verification method") @@ -797,9 +812,8 @@ internal class DefaultSasVerificationService @Inject constructor( val existingRequest = getExistingVerificationRequest(otherUserId, transactionId) if (existingRequest != null) { // we need to send a ready event, with matching methods - val transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId - ?: "", roomId, null) - val methods = existingRequest.requestInfo?.methods?.intersect(listOf(KeyVerificationStart.VERIF_METHOD_SAS))?.toList() + val transport = sasTransportRoomMessageFactory.createTransport(roomId, null) + val methods = existingRequest.requestInfo?.methods?.intersect(supportedVerificationMethods)?.toList() if (methods.isNullOrEmpty()) { Timber.i("Cannot ready this request, no common methods found txId:$transactionId") // TODO buttons should not be shown in this case? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt index c416bd3be3..0ac3847a53 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/PendingVerificationRequest.kt @@ -16,27 +16,38 @@ package im.vector.matrix.android.internal.crypto.verification import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent +import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS +import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SCAN import java.util.* /** * Stores current pending verification requests */ data class PendingVerificationRequest( - val ageLocalTs : Long, + val ageLocalTs: Long, val isIncoming: Boolean = false, - val localID: String = UUID.randomUUID().toString(), + val localID: String = UUID.randomUUID().toString(), val otherUserId: String, + val roomId: String?, val transactionId: String? = null, val requestInfo: MessageVerificationRequestContent? = null, val readyInfo: VerificationInfoReady? = null, val cancelConclusion: CancelCode? = null, - val isSuccessful : Boolean = false + val isSuccessful: Boolean = false, + val handledByOtherSession: Boolean = false ) { - val isReady: Boolean = readyInfo != null val isSent: Boolean = transactionId != null val isFinished: Boolean = isSuccessful || cancelConclusion != null + + fun hasMethod(method: VerificationMethod): Boolean? { + return when (method) { + VerificationMethod.SAS -> readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS) + VerificationMethod.SCAN -> readyInfo?.methods?.contains(VERIFICATION_METHOD_SCAN) + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt index 60e0204800..12fe5c338f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt @@ -34,9 +34,16 @@ internal interface SasTransport { onErrorReason: CancelCode, onDone: (() -> Unit)?) - fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, callback: (String?, MessageVerificationRequestContent?) -> Unit) + fun sendVerificationRequest(supportedMethods: List, + localID: String, + otherUserId: String, + roomId: String, callback: + (String?, MessageVerificationRequestContent?) -> Unit) - fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDevice: String, code: CancelCode) + fun cancelTransaction(transactionId: String, + otherUserId: String, + otherUserDeviceId: String, + code: CancelCode) fun done(transactionId: String) /** @@ -58,7 +65,7 @@ internal interface SasTransport { keyAgreementProtocols: List, hashes: List, messageAuthenticationCodes: List, - shortAuthenticationStrings: List) : VerificationInfoStart + shortAuthenticationStrings: List): VerificationInfoStart fun createMac(tid: String, mac: Map, keys: String): VerificationInfoMac diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt index 20c59def18..c9799cc58d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -15,7 +15,6 @@ */ package im.vector.matrix.android.internal.crypto.verification -import android.content.Context import androidx.lifecycle.Observer import androidx.work.* import com.zhuinden.monarchy.Monarchy @@ -25,9 +24,12 @@ import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.events.model.* import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.matrix.android.internal.di.DeviceId +import im.vector.matrix.android.internal.di.SessionId +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory -import im.vector.matrix.android.internal.worker.WorkManagerUtil +import im.vector.matrix.android.internal.util.StringProvider import im.vector.matrix.android.internal.worker.WorkerParamsFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -38,9 +40,11 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject internal class SasTransportRoomMessage( - private val context: Context, + private val workManagerProvider: WorkManagerProvider, + private val stringProvider: StringProvider, + private val sessionId: String, private val userId: String, - private val userDevice: String, + private val userDeviceId: String?, private val roomId: String, private val monarchy: Monarchy, private val localEchoEventFactory: LocalEchoEventFactory, @@ -61,7 +65,7 @@ internal class SasTransportRoomMessage( ) val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( - userId = userId, + sessionId = sessionId, event = event )) val enqueueInfo = enqueueSendWork(workerParams) @@ -84,7 +88,8 @@ internal class SasTransportRoomMessage( // } // }, listenerExecutor) - val workLiveData = WorkManager.getInstance(context).getWorkInfosForUniqueWorkLiveData("${roomId}_VerificationWork") + val workLiveData = workManagerProvider.workManager + .getWorkInfosForUniqueWorkLiveData("${roomId}_VerificationWork") val observer = object : Observer> { override fun onChanged(workInfoList: List?) { @@ -113,13 +118,16 @@ internal class SasTransportRoomMessage( } } - override fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, + override fun sendVerificationRequest(supportedMethods: List, + localID: String, + otherUserId: String, + roomId: String, callback: (String?, MessageVerificationRequestContent?) -> Unit) { val info = MessageVerificationRequestContent( - body = context.getString(R.string.key_verification_request_fallback_message, userId), - fromDevice = userDevice, + body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId), + fromDevice = userDeviceId ?: "", toUserId = otherUserId, - methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS) + methods = supportedMethods ) val content = info.toContent() @@ -131,24 +139,25 @@ internal class SasTransportRoomMessage( ) val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( - userId = userId, + sessionId = sessionId, event = event )) - val workRequest = WorkManagerUtil.matrixOneTimeWorkRequestBuilder() - .setConstraints(WorkManagerUtil.workConstraints) + val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setConstraints(WorkManagerProvider.workConstraints) .setInputData(workerParams) .setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS) .build() - WorkManager.getInstance(context) + workManagerProvider.workManager .beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND, workRequest) .enqueue() // I cannot just listen to the given work request, because when used in a uniqueWork, // The callback is called while it is still Running ... - val workLiveData = WorkManager.getInstance(context).getWorkInfosForUniqueWorkLiveData("${roomId}_VerificationWork") + val workLiveData = workManagerProvider.workManager + .getWorkInfosForUniqueWorkLiveData("${roomId}_VerificationWork") val observer = object : Observer> { override fun onChanged(workInfoList: List?) { @@ -174,7 +183,7 @@ internal class SasTransportRoomMessage( } } - override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDevice: String, code: CancelCode) { + override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String, code: CancelCode) { Timber.d("## SAS canceling transaction $transactionId for reason $code") val event = createEventAndLocalEcho( type = EventType.KEY_VERIFICATION_CANCEL, @@ -182,7 +191,7 @@ internal class SasTransportRoomMessage( content = MessageVerificationCancelContent.create(transactionId, code).toContent() ) val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( - userId = this.userId, + sessionId = sessionId, event = event )) enqueueSendWork(workerParams) @@ -200,19 +209,19 @@ internal class SasTransportRoomMessage( ).toContent() ) val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( - userId = userId, + sessionId = sessionId, event = event )) enqueueSendWork(workerParams) } private fun enqueueSendWork(workerParams: Data): Pair { - val workRequest = WorkManagerUtil.matrixOneTimeWorkRequestBuilder() - .setConstraints(WorkManagerUtil.workConstraints) + val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setConstraints(WorkManagerProvider.workConstraints) .setInputData(workerParams) .setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS) .build() - return WorkManager.getInstance(context) + return workManagerProvider.workManager .beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND, workRequest) .enqueue() to workRequest.id } @@ -284,12 +293,18 @@ internal class SasTransportRoomMessage( } internal class SasTransportRoomMessageFactory @Inject constructor( - private val context: Context, + private val workManagerProvider: WorkManagerProvider, + private val stringProvider: StringProvider, private val monarchy: Monarchy, + @SessionId + private val sessionId: String, + @UserId + private val userId: String, + @DeviceId + private val deviceId: String?, private val localEchoEventFactory: LocalEchoEventFactory) { - fun createTransport(userId: String, userDevice: String, roomId: String, tx: SASVerificationTransaction? - ): SasTransportRoomMessage { - return SasTransportRoomMessage(context, userId, userDevice, roomId, monarchy, localEchoEventFactory, tx) + fun createTransport(roomId: String, tx: SASVerificationTransaction?): SasTransportRoomMessage { + return SasTransportRoomMessage(workManagerProvider, stringProvider, sessionId, userId, deviceId, roomId, monarchy, localEchoEventFactory, tx) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt index 8d73695f4a..8d280f50d8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt @@ -34,7 +34,10 @@ internal class SasTransportToDevice( private var taskExecutor: TaskExecutor ) : SasTransport { - override fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, + override fun sendVerificationRequest(supportedMethods: List, + localID: String, + otherUserId: String, + roomId: String, callback: (String?, MessageVerificationRequestContent?) -> Unit) { // TODO "not implemented" } @@ -78,11 +81,11 @@ internal class SasTransportToDevice( // To device do not do anything here } - override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDevice: String, code: CancelCode) { + override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String, code: CancelCode) { Timber.d("## SAS canceling transaction $transactionId for reason $code") val cancelMessage = KeyVerificationCancel.create(transactionId, code) val contentMap = MXUsersDevicesMap() - contentMap.setObject(otherUserId, otherUserDevice, cancelMessage) + contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage) sendToDeviceTask .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { this.callback = object : MatrixCallback { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt index 55f57d80d3..47eceb179b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SendVerificationMessageWorker.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.failure.shouldBeRetried import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask +import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent import timber.log.Timber @@ -34,9 +35,10 @@ internal class SendVerificationMessageWorker constructor(context: Context, param @JsonClass(generateAdapter = true) internal data class Params( - val userId: String, - val event: Event - ) + override val sessionId: String, + val event: Event, + override val lastFailureMessage: String? = null + ) : SessionWorkerParams @Inject lateinit var sendVerificationMessageTask: SendVerificationMessageTask @@ -49,10 +51,10 @@ internal class SendVerificationMessageWorker constructor(context: Context, param val params = WorkerParamsFactory.fromData(inputData) ?: return Result.success(errorOutputData) - val sessionComponent = getSessionComponent(params.userId) + val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success(errorOutputData).also { // TODO, can this happen? should I update local echo? - Timber.e("Unknown Session, cannot send message, userId:${params.userId}") + Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}") } sessionComponent.inject(this) val localId = params.event.eventId ?: "" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/StringQualifiers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/StringQualifiers.kt index b084ba012d..a0a36d9982 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/StringQualifiers.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/StringQualifiers.kt @@ -26,7 +26,7 @@ import javax.inject.Qualifier internal annotation class UserId /** - * Used to inject the userId + * Used to inject the deviceId */ @Qualifier @Retention(AnnotationRetention.RUNTIME) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/WorkManagerProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/WorkManagerProvider.kt new file mode 100644 index 0000000000..82091be697 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/WorkManagerProvider.kt @@ -0,0 +1,58 @@ +/* + * Copyright 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.matrix.android.internal.di + +import android.content.Context +import androidx.work.* +import javax.inject.Inject + +internal class WorkManagerProvider @Inject constructor( + context: Context, + @SessionId private val sessionId: String +) { + private val tag = MATRIX_SDK_TAG_PREFIX + sessionId + + val workManager = WorkManager.getInstance(context) + + /** + * Create a OneTimeWorkRequestBuilder, with the Matrix SDK tag + */ + inline fun matrixOneTimeWorkRequestBuilder() = + OneTimeWorkRequestBuilder() + .addTag(tag) + + /** + * Cancel all works instantiated by the Matrix SDK for the current session, and not those from the SDK client, or for other sessions + */ + fun cancelAllWorks() { + workManager.let { + it.cancelAllWorkByTag(tag) + it.pruneWork() + } + } + + companion object { + private const val MATRIX_SDK_TAG_PREFIX = "MatrixSDK-" + + /** + * Default constraints: connected network + */ + val workConstraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index e22e47bc1c..537bc63355 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.session -import android.content.Context import androidx.annotation.MainThread import androidx.lifecycle.LiveData import dagger.Lazy @@ -46,6 +45,7 @@ import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.crypto.DefaultCryptoService import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.di.SessionId +import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.session.sync.SyncTaskSequencer import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.job.SyncThread @@ -63,7 +63,7 @@ import javax.inject.Provider @SessionScope internal class DefaultSession @Inject constructor( override val sessionParams: SessionParams, - private val context: Context, + private val workManagerProvider: WorkManagerProvider, private val eventBus: EventBus, @SessionId override val sessionId: String, @@ -122,15 +122,15 @@ internal class DefaultSession @Inject constructor( } override fun requireBackgroundSync() { - SyncWorker.requireBackgroundSync(context, sessionId) + SyncWorker.requireBackgroundSync(workManagerProvider, sessionId) } override fun startAutomaticBackgroundSync(repeatDelay: Long) { - SyncWorker.automaticallyBackgroundSync(context, sessionId, 0, repeatDelay) + SyncWorker.automaticallyBackgroundSync(workManagerProvider, sessionId, 0, repeatDelay) } override fun stopAnyBackgroundSync() { - SyncWorker.stopAnyBackgroundSync(context) + SyncWorker.stopAnyBackgroundSync(workManagerProvider) } override fun startSync(fromForeground: Boolean) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt index a60bc78b6c..3ca5a03822 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt @@ -16,9 +16,7 @@ package im.vector.matrix.android.internal.session.group -import android.content.Context import androidx.work.ExistingWorkPolicy -import androidx.work.WorkManager import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.internal.database.RealmLiveEntityObserver @@ -27,8 +25,7 @@ import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupSummaryEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.SessionId -import im.vector.matrix.android.internal.worker.WorkManagerUtil -import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder +import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.worker.WorkerParamsFactory import io.realm.OrderedCollectionChangeSet import io.realm.RealmResults @@ -38,7 +35,7 @@ import javax.inject.Inject private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" internal class GroupSummaryUpdater @Inject constructor( - private val context: Context, + private val workManagerProvider: WorkManagerProvider, @SessionId private val sessionId: String, private val monarchy: Monarchy) : RealmLiveEntityObserver(monarchy.realmConfiguration) { @@ -72,12 +69,12 @@ internal class GroupSummaryUpdater @Inject constructor( val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams) - val sendWork = matrixOneTimeWorkRequestBuilder() + val sendWork = workManagerProvider.matrixOneTimeWorkRequestBuilder() .setInputData(workData) - .setConstraints(WorkManagerUtil.workConstraints) + .setConstraints(WorkManagerProvider.workConstraints) .build() - WorkManager.getInstance(context) + workManagerProvider.workManager .beginUniqueWork(GET_GROUP_DATA_WORKER, ExistingWorkPolicy.APPEND, sendWork) .enqueue() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt index cdbf6aeee4..9d80223149 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/pushers/DefaultPusherService.kt @@ -15,10 +15,8 @@ */ package im.vector.matrix.android.internal.session.pushers -import android.content.Context import androidx.lifecycle.LiveData import androidx.work.BackoffPolicy -import androidx.work.WorkManager import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.pushers.Pusher @@ -27,17 +25,16 @@ import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.SessionId +import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith -import im.vector.matrix.android.internal.worker.WorkManagerUtil -import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkerParamsFactory import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject internal class DefaultPusherService @Inject constructor( - private val context: Context, + private val workManagerProvider: WorkManagerProvider, private val monarchy: Monarchy, @SessionId private val sessionId: String, private val getPusherTask: GetPushersTask, @@ -68,12 +65,12 @@ internal class DefaultPusherService @Inject constructor( val params = AddHttpPusherWorker.Params(sessionId, pusher) - val request = matrixOneTimeWorkRequestBuilder() - .setConstraints(WorkManagerUtil.workConstraints) + val request = workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setConstraints(WorkManagerProvider.workConstraints) .setInputData(WorkerParamsFactory.toData(params)) .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS) .build() - WorkManager.getInstance(context).enqueue(request) + workManagerProvider.workManager.enqueue(request) return request.id } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt index 1b2b27a3eb..8039a4cd5d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt @@ -15,7 +15,6 @@ */ package im.vector.matrix.android.internal.session.room.relation -import android.content.Context import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import androidx.work.OneTimeWorkRequest @@ -46,15 +45,14 @@ import im.vector.matrix.android.internal.session.room.send.SendEventWorker import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith -import im.vector.matrix.android.internal.util.CancelableWork import im.vector.matrix.android.internal.util.fetchCopyMap import im.vector.matrix.android.internal.worker.WorkerParamsFactory import timber.log.Timber internal class DefaultRelationService @AssistedInject constructor( @Assisted private val roomId: String, - private val context: Context, @SessionId private val sessionId: String, + private val timeLineSendEventWorkCommon: TimelineSendEventWorkCommon, private val eventFactory: LocalEchoEventFactory, private val cryptoService: CryptoService, private val findReactionEventForUndoTask: FindReactionEventForUndoTask, @@ -85,8 +83,7 @@ internal class DefaultRelationService @AssistedInject constructor( val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction) .also { saveLocalEcho(it) } val sendRelationWork = createSendEventWork(event, true) - TimelineSendEventWorkCommon.postWork(context, roomId, sendRelationWork) - CancelableWork(context, sendRelationWork.id) + timeLineSendEventWorkCommon.postWork(roomId, sendRelationWork) } else { Timber.w("Reaction already added") NoOpCancellable @@ -111,7 +108,7 @@ internal class DefaultRelationService @AssistedInject constructor( .also { saveLocalEcho(it) } val redactWork = createRedactEventWork(redactEvent, toRedact, null) - TimelineSendEventWorkCommon.postWork(context, roomId, redactWork) + timeLineSendEventWorkCommon.postWork(roomId, redactWork) } } } @@ -132,7 +129,7 @@ internal class DefaultRelationService @AssistedInject constructor( eventId, reason) val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) - return TimelineSendEventWorkCommon.createWork(redactWorkData, true) + return timeLineSendEventWorkCommon.createWork(redactWorkData, true) } override fun editTextMessage(targetEventId: String, @@ -146,12 +143,10 @@ internal class DefaultRelationService @AssistedInject constructor( return if (cryptoService.isRoomEncrypted(roomId)) { val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) val workRequest = createSendEventWork(event, false) - TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) - CancelableWork(context, encryptWork.id) + timeLineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, workRequest) } else { val workRequest = createSendEventWork(event, true) - TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) - CancelableWork(context, workRequest.id) + timeLineSendEventWorkCommon.postWork(roomId, workRequest) } } @@ -168,12 +163,10 @@ internal class DefaultRelationService @AssistedInject constructor( return if (cryptoService.isRoomEncrypted(roomId)) { val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) val workRequest = createSendEventWork(event, false) - TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) - CancelableWork(context, encryptWork.id) + timeLineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, workRequest) } else { val workRequest = createSendEventWork(event, true) - TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) - CancelableWork(context, workRequest.id) + timeLineSendEventWorkCommon.postWork(roomId, workRequest) } } @@ -194,12 +187,10 @@ internal class DefaultRelationService @AssistedInject constructor( return if (cryptoService.isRoomEncrypted(roomId)) { val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) val workRequest = createSendEventWork(event, false) - TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) - CancelableWork(context, encryptWork.id) + timeLineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, workRequest) } else { val workRequest = createSendEventWork(event, true) - TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) - CancelableWork(context, workRequest.id) + timeLineSendEventWorkCommon.postWork(roomId, workRequest) } } @@ -207,13 +198,13 @@ internal class DefaultRelationService @AssistedInject constructor( // Same parameter val params = EncryptEventWorker.Params(sessionId, roomId, event, keepKeys) val sendWorkData = WorkerParamsFactory.toData(params) - return TimelineSendEventWorkCommon.createWork(sendWorkData, true) + return timeLineSendEventWorkCommon.createWork(sendWorkData, true) } private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) - return TimelineSendEventWorkCommon.createWork(sendWorkData, startChain) + return timeLineSendEventWorkCommon.createWork(sendWorkData, startChain) } override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index 9942e36593..121436a9bf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -16,8 +16,10 @@ package im.vector.matrix.android.internal.session.room.send -import android.content.Context -import androidx.work.* +import androidx.work.BackoffPolicy +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequest +import androidx.work.Operation import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import com.zhuinden.monarchy.Monarchy @@ -38,12 +40,11 @@ import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.SessionId +import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.session.content.UploadContentWorker import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon import im.vector.matrix.android.internal.util.CancelableWork import im.vector.matrix.android.internal.worker.AlwaysSuccessfulWorker -import im.vector.matrix.android.internal.worker.WorkManagerUtil -import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.startChain import timber.log.Timber @@ -55,7 +56,8 @@ private const val BACKOFF_DELAY = 10_000L internal class DefaultSendService @AssistedInject constructor( @Assisted private val roomId: String, - private val context: Context, + private val workManagerProvider: WorkManagerProvider, + private val timelineSendEventWorkCommon: TimelineSendEventWorkCommon, @SessionId private val sessionId: String, private val localEchoEventFactory: LocalEchoEventFactory, private val cryptoService: CryptoService, @@ -91,12 +93,10 @@ internal class DefaultSendService @AssistedInject constructor( Timber.v("Send event in encrypted room") val encryptWork = createEncryptEventWork(event, true) val sendWork = createSendEventWork(event, false) - TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, sendWork) - CancelableWork(context, encryptWork.id) + timelineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, sendWork) } else { val sendWork = createSendEventWork(event, true) - TimelineSendEventWorkCommon.postWork(context, roomId, sendWork) - CancelableWork(context, sendWork.id) + timelineSendEventWorkCommon.postWork(roomId, sendWork) } } @@ -109,8 +109,7 @@ internal class DefaultSendService @AssistedInject constructor( override fun redactEvent(event: Event, reason: String?): Cancelable { // TODO manage media/attachements? val redactWork = createRedactEventWork(event, reason) - TimelineSendEventWorkCommon.postWork(context, roomId, redactWork) - return CancelableWork(context, redactWork.id) + return timelineSendEventWorkCommon.postWork(roomId, redactWork) } override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? { @@ -168,16 +167,16 @@ internal class DefaultSendService @AssistedInject constructor( } override fun clearSendingQueue() { - TimelineSendEventWorkCommon.cancelAllWorks(context, roomId) - WorkManager.getInstance(context).cancelUniqueWork(buildWorkName(UPLOAD_WORK)) + timelineSendEventWorkCommon.cancelAllWorks(roomId) + workManagerProvider.workManager.cancelUniqueWork(buildWorkName(UPLOAD_WORK)) // Replace the worker chains with a AlwaysSuccessfulWorker, to ensure the queues are well emptied - matrixOneTimeWorkRequestBuilder() + workManagerProvider.matrixOneTimeWorkRequestBuilder() .build().let { - TimelineSendEventWorkCommon.postWork(context, roomId, it, ExistingWorkPolicy.REPLACE) + timelineSendEventWorkCommon.postWork(roomId, it, ExistingWorkPolicy.REPLACE) // need to clear also image sending queue - WorkManager.getInstance(context) + workManagerProvider.workManager .beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.REPLACE, it) .enqueue() } @@ -245,7 +244,7 @@ internal class DefaultSendService @AssistedInject constructor( return internalSendMedia(event, attachment) } - private fun internalSendMedia(localEcho: Event, attachment: ContentAttachmentData): CancelableWork { + private fun internalSendMedia(localEcho: Event, attachment: ContentAttachmentData): Cancelable { val isRoomEncrypted = cryptoService.isRoomEncrypted(roomId) val uploadWork = createUploadMediaWork(localEcho, attachment, isRoomEncrypted, startChain = true) @@ -254,7 +253,7 @@ internal class DefaultSendService @AssistedInject constructor( if (isRoomEncrypted) { val encryptWork = createEncryptEventWork(localEcho, false /*not start of chain, take input error*/) - val op: Operation = WorkManager.getInstance(context) + val op: Operation = workManagerProvider.workManager .beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork) .then(encryptWork) .then(sendWork) @@ -267,13 +266,13 @@ internal class DefaultSendService @AssistedInject constructor( } }, workerFutureListenerExecutor) } else { - WorkManager.getInstance(context) + workManagerProvider.workManager .beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork) .then(sendWork) .enqueue() } - return CancelableWork(context, sendWork.id) + return CancelableWork(workManagerProvider.workManager, sendWork.id) } private fun saveLocalEcho(event: Event) { @@ -289,8 +288,8 @@ internal class DefaultSendService @AssistedInject constructor( val params = EncryptEventWorker.Params(sessionId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(params) - return matrixOneTimeWorkRequestBuilder() - .setConstraints(WorkManagerUtil.workConstraints) + return workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setConstraints(WorkManagerProvider.workConstraints) .setInputData(sendWorkData) .startChain(startChain) .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS) @@ -301,7 +300,7 @@ internal class DefaultSendService @AssistedInject constructor( val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) - return TimelineSendEventWorkCommon.createWork(sendWorkData, startChain) + return timelineSendEventWorkCommon.createWork(sendWorkData, startChain) } private fun createRedactEventWork(event: Event, reason: String?): OneTimeWorkRequest { @@ -310,7 +309,7 @@ internal class DefaultSendService @AssistedInject constructor( } val sendContentWorkerParams = RedactEventWorker.Params(sessionId, redactEvent.eventId!!, roomId, event.eventId, reason) val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) - return TimelineSendEventWorkCommon.createWork(redactWorkData, true) + return timelineSendEventWorkCommon.createWork(redactWorkData, true) } private fun createUploadMediaWork(event: Event, @@ -320,8 +319,8 @@ internal class DefaultSendService @AssistedInject constructor( val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, roomId, event, attachment, isRoomEncrypted) val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams) - return matrixOneTimeWorkRequestBuilder() - .setConstraints(WorkManagerUtil.workConstraints) + return workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setConstraints(WorkManagerProvider.workConstraints) .startChain(startChain) .setInputData(uploadWorkData) .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineSendEventWorkCommon.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineSendEventWorkCommon.kt index f6f894d860..ff3cedf044 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineSendEventWorkCommon.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineSendEventWorkCommon.kt @@ -15,51 +15,54 @@ */ package im.vector.matrix.android.internal.session.room.timeline -import android.content.Context import androidx.work.* -import im.vector.matrix.android.internal.worker.WorkManagerUtil -import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder +import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.api.util.NoOpCancellable +import im.vector.matrix.android.internal.di.WorkManagerProvider +import im.vector.matrix.android.internal.util.CancelableWork import im.vector.matrix.android.internal.worker.startChain import java.util.concurrent.TimeUnit - -private const val SEND_WORK = "SEND_WORK" -private const val BACKOFF_DELAY = 10_000L +import javax.inject.Inject /** * Helper class for sending event related works. * All send event from a room are using the same workchain, in order to ensure order. - * WorkRequest must always return success (even if server error, in this case marking the event as failed to send) - * , if not the chain will be doomed in failed state. - * + * WorkRequest must always return success (even if server error, in this case marking the event as failed to send), + * if not the chain will be doomed in failed state. */ -internal object TimelineSendEventWorkCommon { +internal class TimelineSendEventWorkCommon @Inject constructor( + private val workManagerProvider: WorkManagerProvider +) { - fun postSequentialWorks(context: Context, roomId: String, vararg workRequests: OneTimeWorkRequest) { - when { - workRequests.isEmpty() -> return - workRequests.size == 1 -> postWork(context, roomId, workRequests.first()) + fun postSequentialWorks(roomId: String, vararg workRequests: OneTimeWorkRequest): Cancelable { + return when { + workRequests.isEmpty() -> NoOpCancellable + workRequests.size == 1 -> postWork(roomId, workRequests.first()) else -> { val firstWork = workRequests.first() - var continuation = WorkManager.getInstance(context) + var continuation = workManagerProvider.workManager .beginUniqueWork(buildWorkName(roomId), ExistingWorkPolicy.APPEND, firstWork) for (i in 1 until workRequests.size) { val workRequest = workRequests[i] continuation = continuation.then(workRequest) } continuation.enqueue() + CancelableWork(workManagerProvider.workManager, firstWork.id) } } } - fun postWork(context: Context, roomId: String, workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND) { - WorkManager.getInstance(context) + fun postWork(roomId: String, workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable { + workManagerProvider.workManager .beginUniqueWork(buildWorkName(roomId), policy, workRequest) .enqueue() + + return CancelableWork(workManagerProvider.workManager, workRequest.id) } inline fun createWork(data: Data, startChain: Boolean): OneTimeWorkRequest { - return matrixOneTimeWorkRequestBuilder() - .setConstraints(WorkManagerUtil.workConstraints) + return workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setConstraints(WorkManagerProvider.workConstraints) .startChain(startChain) .setInputData(data) .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS) @@ -70,7 +73,13 @@ internal object TimelineSendEventWorkCommon { return "${roomId}_$SEND_WORK" } - fun cancelAllWorks(context: Context, roomId: String) { - WorkManager.getInstance(context).cancelUniqueWork(buildWorkName(roomId)) + fun cancelAllWorks(roomId: String) { + workManagerProvider.workManager + .cancelUniqueWork(buildWorkName(roomId)) + } + + companion object { + private const val SEND_WORK = "SEND_WORK" + private const val BACKOFF_DELAY = 10_000L } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt index e6913f8b54..05a2324047 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.session.signout -import android.content.Context import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError @@ -29,7 +28,6 @@ import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.SessionModule import im.vector.matrix.android.internal.session.cache.ClearCacheTask import im.vector.matrix.android.internal.task.Task -import im.vector.matrix.android.internal.worker.WorkManagerUtil import io.realm.Realm import io.realm.RealmConfiguration import org.greenrobot.eventbus.EventBus @@ -45,7 +43,7 @@ internal interface SignOutTask : Task { } internal class DefaultSignOutTask @Inject constructor( - private val context: Context, + private val workManagerProvider: WorkManagerProvider, @SessionId private val sessionId: String, private val signOutAPI: SignOutAPI, private val sessionManager: SessionManager, @@ -87,7 +85,7 @@ internal class DefaultSignOutTask @Inject constructor( sessionManager.releaseSession(sessionId) Timber.d("SignOut: cancel pending works...") - WorkManagerUtil.cancelAllWorks(context) + workManagerProvider.cancelAllWorks() Timber.d("SignOut: delete session params...") sessionParamsStore.delete(sessionId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt index 3637cc624f..c844db8d33 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt @@ -16,15 +16,17 @@ package im.vector.matrix.android.internal.session.sync.job import android.content.Context -import androidx.work.* +import androidx.work.BackoffPolicy +import androidx.work.CoroutineWorker +import androidx.work.ExistingWorkPolicy +import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.failure.isTokenError +import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.session.sync.SyncTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.worker.SessionWorkerParams -import im.vector.matrix.android.internal.worker.WorkManagerUtil -import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent import timber.log.Timber @@ -75,30 +77,33 @@ internal class SyncWorker(context: Context, companion object { - const val BG_SYNC_WORK_NAME = "BG_SYNCP" + private const val BG_SYNC_WORK_NAME = "BG_SYNCP" - fun requireBackgroundSync(context: Context, sessionId: String, serverTimeout: Long = 0) { + fun requireBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0) { val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, false)) - val workRequest = matrixOneTimeWorkRequestBuilder() - .setConstraints(WorkManagerUtil.workConstraints) + val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setConstraints(WorkManagerProvider.workConstraints) .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) .setInputData(data) .build() - WorkManager.getInstance(context).enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest) + workManagerProvider.workManager + .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest) } - fun automaticallyBackgroundSync(context: Context, sessionId: String, serverTimeout: Long = 0, delay: Long = 30_000) { + fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0, delay: Long = 30_000) { val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, true)) - val workRequest = matrixOneTimeWorkRequestBuilder() - .setConstraints(WorkManagerUtil.workConstraints) + val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setConstraints(WorkManagerProvider.workConstraints) .setInputData(data) .setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS) .build() - WorkManager.getInstance(context).enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest) + workManagerProvider.workManager + .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest) } - fun stopAnyBackgroundSync(context: Context) { - WorkManager.getInstance(context).cancelUniqueWork(BG_SYNC_WORK_NAME) + fun stopAnyBackgroundSync(workManagerProvider: WorkManagerProvider) { + workManagerProvider.workManager + .cancelUniqueWork(BG_SYNC_WORK_NAME) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CancelableWork.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CancelableWork.kt index bff20a80e7..cba104ebe8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CancelableWork.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/CancelableWork.kt @@ -16,15 +16,14 @@ package im.vector.matrix.android.internal.util -import android.content.Context import androidx.work.WorkManager import im.vector.matrix.android.api.util.Cancelable -import java.util.UUID +import java.util.* -internal class CancelableWork(private val context: Context, +internal class CancelableWork(private val workManager: WorkManager, private val workId: UUID) : Cancelable { override fun cancel() { - WorkManager.getInstance(context).cancelWorkById(workId) + workManager.cancelWorkById(workId) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/WorkManagerUtil.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/WorkManagerUtil.kt deleted file mode 100644 index 27ec28dcac..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/worker/WorkManagerUtil.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2019 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.matrix.android.internal.worker - -import android.content.Context -import androidx.work.* - -// TODO Multiaccount -internal object WorkManagerUtil { - private const val MATRIX_SDK_TAG = "MatrixSDK" - - /** - * Default constraints: connected network - */ - val workConstraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() - - /** - * Create a OneTimeWorkRequestBuilder, with the Matrix SDK tag - */ - inline fun matrixOneTimeWorkRequestBuilder() = - OneTimeWorkRequestBuilder() - .addTag(MATRIX_SDK_TAG) - - /** - * Cancel all works instantiated by the Matrix SDK and not those from the SDK client - */ - fun cancelAllWorks(context: Context) { - WorkManager.getInstance(context).also { - it.cancelAllWorkByTag(MATRIX_SDK_TAG) - it.pruneWork() - } - } -} diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index a0d4402767..c6e4b51c44 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -47,9 +47,6 @@ android:label="@string/title_activity_settings" android:windowSoftInputMode="adjustResize" /> - 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 6855631aa3..53bd7d169f 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 @@ -23,7 +23,10 @@ import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment -import im.vector.riotx.features.crypto.verification.* +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.request.VerificationRequestFragment import im.vector.riotx.features.home.HomeDetailFragment import im.vector.riotx.features.home.HomeDrawerFragment import im.vector.riotx.features.home.LoadingFragment @@ -239,26 +242,6 @@ interface FragmentModule { @FragmentKey(VectorSettingsDevicesFragment::class) fun bindVectorSettingsDevicesFragment(fragment: VectorSettingsDevicesFragment): Fragment - @Binds - @IntoMap - @FragmentKey(SASVerificationIncomingFragment::class) - fun bindSASVerificationIncomingFragment(fragment: SASVerificationIncomingFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SASVerificationShortCodeFragment::class) - fun bindSASVerificationShortCodeFragment(fragment: SASVerificationShortCodeFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SASVerificationVerifiedFragment::class) - fun bindSASVerificationVerifiedFragment(fragment: SASVerificationVerifiedFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(SASVerificationStartFragment::class) - fun bindSASVerificationStartFragment(fragment: SASVerificationStartFragment): Fragment - @Binds @IntoMap @FragmentKey(PublicRoomsFragment::class) @@ -302,15 +285,15 @@ interface FragmentModule { @Binds @IntoMap @FragmentKey(VerificationChooseMethodFragment::class) - fun bindVerificationMethodChooserFragment(fragment: VerificationChooseMethodFragment): Fragment + fun bindVerificationChooseMethodFragment(fragment: VerificationChooseMethodFragment): Fragment @Binds @IntoMap - @FragmentKey(SASVerificationCodeFragment::class) - fun bindVerificationSasCodeFragment(fragment: SASVerificationCodeFragment): Fragment + @FragmentKey(VerificationEmojiCodeFragment::class) + fun bindVerificationEmojiCodeFragment(fragment: VerificationEmojiCodeFragment): Fragment @Binds @IntoMap @FragmentKey(VerificationConclusionFragment::class) - fun bindVerificationSasConclusionFragment(fragment: VerificationConclusionFragment): Fragment + fun bindVerificationConclusionFragment(fragment: VerificationConclusionFragment): Fragment } diff --git a/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt index 5d4288f4b8..4bb0adb9f0 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt @@ -27,7 +27,6 @@ import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromK import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel -import im.vector.riotx.features.crypto.verification.SasVerificationViewModel import im.vector.riotx.features.home.HomeSharedActionViewModel import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel @@ -61,11 +60,6 @@ interface ViewModelModule { @ViewModelKey(EmojiChooserViewModel::class) fun bindEmojiChooserViewModel(viewModel: EmojiChooserViewModel): ViewModel - @Binds - @IntoMap - @ViewModelKey(SasVerificationViewModel::class) - fun bindSasVerificationViewModel(viewModel: SasVerificationViewModel): ViewModel - @Binds @IntoMap @ViewModelKey(KeysBackupRestoreFromKeyViewModel::class) diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/ErrorWithRetryItem.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/ErrorWithRetryItem.kt index e6336e5753..25bf2f514a 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/ErrorWithRetryItem.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/ErrorWithRetryItem.kt @@ -18,6 +18,7 @@ package im.vector.riotx.core.epoxy import android.widget.Button import android.widget.TextView +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R @@ -33,6 +34,7 @@ abstract class ErrorWithRetryItem : VectorEpoxyModel( override fun bind(holder: Holder) { holder.textView.text = text + holder.buttonView.isVisible = listener != null holder.buttonView.setOnClickListener { listener?.invoke() } } diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt index 3d247e149c..51f3ce611a 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt @@ -28,12 +28,17 @@ fun RecyclerView.configureWith(epoxyController: EpoxyController, itemAnimator: RecyclerView.ItemAnimator? = null, viewPool: RecyclerView.RecycledViewPool? = null, showDivider: Boolean = false, - hasFixedSize: Boolean = true) { + hasFixedSize: Boolean = true, + disableItemAnimation: Boolean = false) { layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false).apply { recycleChildrenOnDetach = viewPool != null } setRecycledViewPool(viewPool) - itemAnimator?.let { this.itemAnimator = it } + if (disableItemAnimation) { + this.itemAnimator = null + } else { + itemAnimator?.let { this.itemAnimator = it } + } if (showDivider) { addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) } diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt index 5c73fc97da..15f541f72d 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt @@ -102,9 +102,11 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { private lateinit var sessionListener: SessionListener protected lateinit var bugReporter: BugReporter lateinit var rageShake: RageShake + + lateinit var navigator: Navigator private set - protected lateinit var navigator: Navigator private lateinit var fragmentFactory: FragmentFactory + private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var vectorPreferences: VectorPreferences @@ -210,8 +212,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { handleInvalidToken(globalError) is GlobalError.ConsentNotGivenError -> consentNotGivenHelper.displayDialog(globalError.consentUri, - activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host - ?: "") + activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host + ?: "") } } diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt index b3a56f48ee..c066ffff43 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -19,10 +19,16 @@ import android.app.Dialog import android.content.Context import android.os.Bundle import android.os.Parcelable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import android.widget.FrameLayout import androidx.annotation.CallSuper +import androidx.annotation.LayoutRes import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders +import butterknife.ButterKnife +import butterknife.Unbinder import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRxView import com.airbnb.mvrx.MvRxViewId @@ -43,6 +49,15 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() final override val mvrxViewId: String by mvrxViewIdProperty private lateinit var screenComponent: ScreenComponent + /* ========================================================================================== + * View + * ========================================================================================== */ + + @LayoutRes + abstract fun getLayoutResId(): Int + + private var unBinder: Unbinder? = null + /* ========================================================================================== * View model * ========================================================================================== */ @@ -67,6 +82,18 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() open val showExpanded = false + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(getLayoutResId(), container, false) + unBinder = ButterKnife.bind(this, view) + return view + } + + override fun onDestroyView() { + super.onDestroyView() + unBinder?.unbind() + unBinder = null + } + override fun onAttach(context: Context) { screenComponent = DaggerScreenComponent.factory().create(vectorBaseActivity.getVectorComponent(), vectorBaseActivity) viewModelFactory = screenComponent.viewModelFactory() diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomKnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomKnownUsersFragment.kt index c3a66dce1e..1fcc8d443b 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomKnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomKnownUsersFragment.kt @@ -98,10 +98,9 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor( } private fun setupRecyclerView() { - // Don't activate animation as we might have way to much item animation when filtering - recyclerView.itemAnimator = null knownUsersController.callback = this - recyclerView.configureWith(knownUsersController) + // Don't activate animation as we might have way to much item animation when filtering + recyclerView.configureWith(knownUsersController, disableItemAnimation = true) } private fun setupFilterView() { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt index f5db16c8ee..1af7d2fbd1 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt @@ -33,13 +33,11 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.riotx.R -import im.vector.riotx.features.crypto.verification.SASVerificationActivity import im.vector.riotx.features.popup.PopupAlertManager import timber.log.Timber import java.text.DateFormat import java.text.SimpleDateFormat -import java.util.Locale -import java.util.Date +import java.util.* import javax.inject.Inject import javax.inject.Singleton import kotlin.collections.ArrayList @@ -195,18 +193,19 @@ class KeyRequestHandler @Inject constructor(private val context: Context) denyAllRequests(mappingKey) } - alert.addButton( - context.getString(R.string.start_verification_short_label), - Runnable { - alert.weakCurrentActivity?.get()?.let { - val intent = SASVerificationActivity.outgoingIntent(it, - session?.myUserId ?: "", - userId, deviceId) - it.startActivity(intent) - } - }, - false - ) + // TODO send to the new profile page +// alert.addButton( +// context.getString(R.string.start_verification_short_label), +// Runnable { +// alert.weakCurrentActivity?.get()?.let { +// val intent = SASVerificationActivity.outgoingIntent(it, +// session?.myUserId ?: "", +// userId, deviceId) +// it.startActivity(intent) +// } +// }, +// false +// ) alert.addButton(context.getString(R.string.share_without_verifying_short_label), Runnable { shareAllSessions(mappingKey) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/Config.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/Config.kt new file mode 100644 index 0000000000..0bc9a3e144 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/Config.kt @@ -0,0 +1,22 @@ +/* + * Copyright 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 + +import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod + +// TODO Add support for SCAN (QR code) +val supportedVerificationMethods = listOf(VerificationMethod.SAS) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt index 22ccec534a..b59e2d3f8c 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt @@ -20,8 +20,13 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState +import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.riotx.R +import im.vector.riotx.core.platform.VectorBaseActivity +import im.vector.riotx.features.home.room.detail.RoomDetailActivity +import im.vector.riotx.features.home.room.detail.RoomDetailArgs import im.vector.riotx.features.popup.PopupAlertManager +import im.vector.riotx.features.themes.ThemeUtils import javax.inject.Inject import javax.inject.Singleton @@ -48,46 +53,46 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context override fun transactionUpdated(tx: SasVerificationTransaction) { when (tx.state) { SasVerificationTxState.OnStarted -> { - // Add a notification for every incoming request - val name = session?.getUser(tx.otherUserId)?.displayName - ?: tx.otherUserId - - val alert = PopupAlertManager.VectorAlert( - "kvr_${tx.transactionId}", - context.getString(R.string.sas_incoming_request_notif_title), - context.getString(R.string.sas_incoming_request_notif_content, name), - R.drawable.shield) - .apply { - contentAction = Runnable { - val intent = SASVerificationActivity.incomingIntent(context, - session?.myUserId ?: "", - tx.otherUserId, - tx.transactionId) - weakCurrentActivity?.get()?.startActivity(intent) - } - dismissedAction = Runnable { - tx.cancel() - } - addButton( - context.getString(R.string.ignore), - Runnable { - tx.cancel() - } - ) - addButton( - context.getString(R.string.action_open), - Runnable { - val intent = SASVerificationActivity.incomingIntent(context, - session?.myUserId ?: "", - tx.otherUserId, - tx.transactionId) - weakCurrentActivity?.get()?.startActivity(intent) - } - ) - // 10mn expiration - expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L) - } - PopupAlertManager.postVectorAlert(alert) +// // Add a notification for every incoming request +// val name = session?.getUser(tx.otherUserId)?.displayName +// ?: tx.otherUserId +// +// val alert = PopupAlertManager.VectorAlert( +// "kvr_${tx.transactionId}", +// context.getString(R.string.sas_incoming_request_notif_title), +// context.getString(R.string.sas_incoming_request_notif_content, name), +// R.drawable.shield) +// .apply { +// contentAction = Runnable { +// val intent = SASVerificationActivity.incomingIntent(context, +// session?.myUserId ?: "", +// tx.otherUserId, +// tx.transactionId) +// weakCurrentActivity?.get()?.startActivity(intent) +// } +// dismissedAction = Runnable { +// tx.cancel() +// } +// addButton( +// context.getString(R.string.ignore), +// Runnable { +// tx.cancel() +// } +// ) +// addButton( +// context.getString(R.string.action_open), +// Runnable { +// val intent = SASVerificationActivity.incomingIntent(context, +// session?.myUserId ?: "", +// tx.otherUserId, +// tx.transactionId) +// weakCurrentActivity?.get()?.startActivity(intent) +// } +// ) +// // 10mn expiration +// expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L) +// } +// PopupAlertManager.postVectorAlert(alert) } SasVerificationTxState.Cancelled, SasVerificationTxState.OnCancelled, @@ -101,4 +106,54 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context override fun markedAsManuallyVerified(userId: String, deviceId: String) { } + + override fun verificationRequestCreated(pr: PendingVerificationRequest) { + // For incoming request we should prompt (if not in activity where this request apply) + if (pr.isIncoming) { + val name = session?.getUser(pr.otherUserId)?.displayName + ?: pr.otherUserId + + val alert = PopupAlertManager.VectorAlert( + uniqueIdForVerificationRequest(pr), + context.getString(R.string.sas_incoming_request_notif_title), + "$name(${pr.otherUserId})", + R.drawable.ic_shield_black, + shouldBeDisplayedIn = { activity -> + if (activity is RoomDetailActivity) { + activity.intent?.extras?.getParcelable(RoomDetailActivity.EXTRA_ROOM_DETAIL_ARGS)?.let { + it.roomId != pr.roomId + } ?: true + } else true + }) + .apply { + contentAction = Runnable { + (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { + it.navigator.openRoom(it, pr.roomId ?: "", pr.transactionId) + } + } + dismissedAction = Runnable { + session?.getSasVerificationService()?.declineVerificationRequestInDMs(pr.otherUserId, + pr.requestInfo?.fromDevice ?: "", + pr.transactionId ?: "", + pr.roomId ?: "" + ) + } + colorInt = ThemeUtils.getColor(context, R.attr.vctr_notice_secondary) + // 5mn expiration + expirationTimestamp = System.currentTimeMillis() + (5 * 60 * 1000L) + } + PopupAlertManager.postVectorAlert(alert) + } + } + + override fun verificationRequestUpdated(pr: PendingVerificationRequest) { + // If an incoming request is readied (by another device?) we should discard the alert + if (pr.isIncoming && (pr.isReady || pr.handledByOtherSession)) { + PopupAlertManager.cancelAlert(uniqueIdForVerificationRequest(pr)) + } + super.verificationRequestUpdated(pr) + } + + private fun uniqueIdForVerificationRequest(pr: PendingVerificationRequest) = + "verificationRequest_${pr.transactionId}" } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationActivity.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationActivity.kt deleted file mode 100644 index 222891eb3d..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationActivity.kt +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2019 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 - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.view.MenuItem -import androidx.appcompat.app.AlertDialog -import androidx.lifecycle.Observer -import im.vector.matrix.android.api.session.crypto.sas.CancelCode -import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction -import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest -import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState -import im.vector.riotx.R -import im.vector.riotx.core.extensions.commitTransaction -import im.vector.riotx.core.extensions.observeEvent -import im.vector.riotx.core.platform.SimpleFragmentActivity -import im.vector.riotx.core.platform.WaitingViewData - -// TODO Deprecated("replaced by bottomsheet UX") -class SASVerificationActivity : SimpleFragmentActivity() { - - companion object { - - private const val EXTRA_MATRIX_ID = "EXTRA_MATRIX_ID" - private const val EXTRA_TRANSACTION_ID = "EXTRA_TRANSACTION_ID" - private const val EXTRA_OTHER_USER_ID = "EXTRA_OTHER_USER_ID" - private const val EXTRA_OTHER_DEVICE_ID = "EXTRA_OTHER_DEVICE_ID" - private const val EXTRA_IS_INCOMING = "EXTRA_IS_INCOMING" - - /* ========================================================================================== - * INPUT - * ========================================================================================== */ - - fun incomingIntent(context: Context, matrixID: String, otherUserId: String, transactionID: String): Intent { - val intent = Intent(context, SASVerificationActivity::class.java) - intent.putExtra(EXTRA_MATRIX_ID, matrixID) - intent.putExtra(EXTRA_TRANSACTION_ID, transactionID) - intent.putExtra(EXTRA_OTHER_USER_ID, otherUserId) - intent.putExtra(EXTRA_IS_INCOMING, true) - return intent - } - - fun outgoingIntent(context: Context, matrixID: String, otherUserId: String, otherDeviceId: String): Intent { - val intent = Intent(context, SASVerificationActivity::class.java) - intent.putExtra(EXTRA_MATRIX_ID, matrixID) - intent.putExtra(EXTRA_OTHER_DEVICE_ID, otherDeviceId) - intent.putExtra(EXTRA_OTHER_USER_ID, otherUserId) - intent.putExtra(EXTRA_IS_INCOMING, false) - return intent - } - - /* ========================================================================================== - * OUTPUT - * ========================================================================================== */ - - fun getOtherUserId(intent: Intent?): String? { - return intent?.getStringExtra(EXTRA_OTHER_USER_ID) - } - - fun getOtherDeviceId(intent: Intent?): String? { - return intent?.getStringExtra(EXTRA_OTHER_DEVICE_ID) - } - } - - override fun getTitleRes() = R.string.title_activity_verify_device - - private lateinit var viewModel: SasVerificationViewModel - - override fun initUiAndData() { - super.initUiAndData() - viewModel = viewModelProvider.get(SasVerificationViewModel::class.java) - val transactionID: String? = intent.getStringExtra(EXTRA_TRANSACTION_ID) - - if (isFirstCreation()) { - val isIncoming = intent.getBooleanExtra(EXTRA_IS_INCOMING, false) - if (isIncoming) { - // incoming always have a transaction id - viewModel.initIncoming(session, intent.getStringExtra(EXTRA_OTHER_USER_ID), transactionID) - } else { - viewModel.initOutgoing(session, intent.getStringExtra(EXTRA_OTHER_USER_ID), intent.getStringExtra(EXTRA_OTHER_DEVICE_ID)) - } - - if (isIncoming) { - val incoming = viewModel.transaction as? IncomingSasVerificationTransaction - when (incoming?.uxState) { - null, - IncomingSasVerificationTransaction.UxState.UNKNOWN, - IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT, - IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT -> { - supportActionBar?.setTitle(R.string.sas_incoming_request_title) - supportFragmentManager.commitTransaction { - setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out) - replace(R.id.container, SASVerificationIncomingFragment::class.java, null) - } - } - IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION, - IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { - supportFragmentManager.commitTransaction { - setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out) - replace(R.id.container, SASVerificationShortCodeFragment::class.java, null) - } - } - IncomingSasVerificationTransaction.UxState.VERIFIED -> { - supportFragmentManager.commitTransaction { - setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out) - replace(R.id.container, SASVerificationVerifiedFragment::class.java, null) - } - } - IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME, - IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER -> { - viewModel.navigateCancel() - } - } - } else { - val outgoing = viewModel.transaction as? OutgoingSasVerificationRequest - // transaction can be null, as not yet created - when (outgoing?.uxState) { - null, - OutgoingSasVerificationRequest.UxState.UNKNOWN, - OutgoingSasVerificationRequest.UxState.WAIT_FOR_START, - OutgoingSasVerificationRequest.UxState.WAIT_FOR_KEY_AGREEMENT -> { - supportFragmentManager.commitTransaction { - setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out) - replace(R.id.container, SASVerificationStartFragment::class.java, null) - } - } - OutgoingSasVerificationRequest.UxState.SHOW_SAS, - OutgoingSasVerificationRequest.UxState.WAIT_FOR_VERIFICATION -> { - supportFragmentManager.commitTransaction { - setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out) - replace(R.id.container, SASVerificationShortCodeFragment::class.java, null) - } - } - OutgoingSasVerificationRequest.UxState.VERIFIED -> { - supportFragmentManager.commitTransaction { - setCustomAnimations(R.anim.no_anim, R.anim.exit_fade_out) - replace(R.id.container, SASVerificationVerifiedFragment::class.java, null) - } - } - OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME, - OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER -> { - viewModel.navigateCancel() - } - } - } - } - - viewModel.navigateEvent.observeEvent(this) { uxStateEvent -> - when (uxStateEvent) { - SasVerificationViewModel.NAVIGATE_FINISH -> { - finish() - } - SasVerificationViewModel.NAVIGATE_FINISH_SUCCESS -> { - val dataResult = Intent() - dataResult.putExtra(EXTRA_OTHER_DEVICE_ID, viewModel.otherDeviceId) - dataResult.putExtra(EXTRA_OTHER_USER_ID, viewModel.otherUserId) - setResult(Activity.RESULT_OK, dataResult) - finish() - } - SasVerificationViewModel.NAVIGATE_SAS_DISPLAY -> { - supportFragmentManager.commitTransaction { - setCustomAnimations(R.anim.enter_from_right, R.anim.exit_fade_out) - replace(R.id.container, SASVerificationShortCodeFragment::class.java, null) - } - } - SasVerificationViewModel.NAVIGATE_SUCCESS -> { - supportFragmentManager.commitTransaction { - setCustomAnimations(R.anim.enter_from_right, R.anim.exit_fade_out) - replace(R.id.container, SASVerificationVerifiedFragment::class.java, null) - } - } - SasVerificationViewModel.NAVIGATE_CANCELLED -> { - val isCancelledByMe = viewModel.transaction?.state == SasVerificationTxState.Cancelled - val humanReadableReason = when (viewModel.transaction?.cancelledReason) { - CancelCode.User -> getString(R.string.sas_error_m_user) - CancelCode.Timeout -> getString(R.string.sas_error_m_timeout) - CancelCode.UnknownTransaction -> getString(R.string.sas_error_m_unknown_transaction) - CancelCode.UnknownMethod -> getString(R.string.sas_error_m_unknown_method) - CancelCode.MismatchedCommitment -> getString(R.string.sas_error_m_mismatched_commitment) - CancelCode.MismatchedSas -> getString(R.string.sas_error_m_mismatched_sas) - CancelCode.UnexpectedMessage -> getString(R.string.sas_error_m_unexpected_message) - CancelCode.InvalidMessage -> getString(R.string.sas_error_m_invalid_message) - CancelCode.MismatchedKeys -> getString(R.string.sas_error_m_key_mismatch) - // Use user error - CancelCode.UserMismatchError -> getString(R.string.sas_error_m_user_error) - null -> getString(R.string.sas_error_unknown) - } - val message = - if (isCancelledByMe) getString(R.string.sas_cancelled_by_me, humanReadableReason) - else getString(R.string.sas_cancelled_by_other, humanReadableReason) - // Show a dialog - if (!this.isFinishing) { - AlertDialog.Builder(this) - .setTitle(R.string.sas_cancelled_dialog_title) - .setMessage(message) - .setCancelable(false) - .setPositiveButton(R.string.ok) { _, _ -> - // nop - finish() - } - .show() - } - } - } - } - - viewModel.loadingLiveEvent.observe(this, Observer { - if (it == null) { - hideWaitingView() - } else { - val status = if (it == -1) "" else getString(it) - updateWaitingView(WaitingViewData(status, isIndeterminate = true)) - } - }) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - // we want to cancel the transaction - viewModel.cancelTransaction() - } - - return super.onOptionsItemSelected(item) - } - - override fun onBackPressed() { - // we want to cancel the transaction - viewModel.cancelTransaction() - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt deleted file mode 100644 index 122af8b625..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeFragment.kt +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2019 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 - -import android.view.ViewGroup -import android.widget.TextView -import androidx.core.view.isInvisible -import androidx.core.view.isVisible -import butterknife.BindView -import butterknife.OnClick -import com.airbnb.mvrx.* -import im.vector.riotx.R -import im.vector.riotx.core.platform.VectorBaseFragment -import kotlinx.android.synthetic.main.fragment_bottom_sas_verification_code.* -import javax.inject.Inject - -class SASVerificationCodeFragment @Inject constructor( - val viewModelFactory: SASVerificationCodeViewModel.Factory -) : VectorBaseFragment() { - - override fun getLayoutResId() = R.layout.fragment_bottom_sas_verification_code - - @BindView(R.id.sas_emoji_grid) - lateinit var emojiGrid: ViewGroup - - @BindView(R.id.sas_decimal_code) - lateinit var decimalTextView: TextView - - @BindView(R.id.emoji0) - lateinit var emoji0View: ViewGroup - @BindView(R.id.emoji1) - lateinit var emoji1View: ViewGroup - @BindView(R.id.emoji2) - lateinit var emoji2View: ViewGroup - @BindView(R.id.emoji3) - lateinit var emoji3View: ViewGroup - @BindView(R.id.emoji4) - lateinit var emoji4View: ViewGroup - @BindView(R.id.emoji5) - lateinit var emoji5View: ViewGroup - @BindView(R.id.emoji6) - lateinit var emoji6View: ViewGroup - - private val viewModel by fragmentViewModel(SASVerificationCodeViewModel::class) - - private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) - - override fun invalidate() = withState(viewModel) { state -> - - if (state.supportsEmoji) { - decimalTextView.isVisible = false - when (val emojiDescription = state.emojiDescription) { - is Success -> { - sasLoadingProgress.isVisible = false - emojiGrid.isVisible = true - ButtonsVisibilityGroup.isVisible = true - emojiDescription.invoke().forEachIndexed { index, emojiRepresentation -> - when (index) { - 0 -> { - emoji0View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji0View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) - } - 1 -> { - emoji1View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji1View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) - } - 2 -> { - emoji2View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji2View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) - } - 3 -> { - emoji3View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji3View.findViewById(R.id.item_emoji_name_tv)?.setText(emojiRepresentation.nameResId) - } - 4 -> { - emoji4View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji4View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) - } - 5 -> { - emoji5View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji5View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) - } - 6 -> { - emoji6View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji6View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) - } - } - } - - if (state.isWaitingFromOther) { - // hide buttons - ButtonsVisibilityGroup.isInvisible = true - sasCodeWaitingPartnerText.isVisible = true - } else { - ButtonsVisibilityGroup.isVisible = true - sasCodeWaitingPartnerText.isVisible = false - } - } - is Fail -> { - sasLoadingProgress.isVisible = false - emojiGrid.isInvisible = true - ButtonsVisibilityGroup.isInvisible = true - // TODO? - } - else -> { - sasLoadingProgress.isVisible = true - emojiGrid.isInvisible = true - ButtonsVisibilityGroup.isInvisible = true - } - } - } else { - // Decimal - emojiGrid.isInvisible = true - decimalTextView.isVisible = true - val decimalCode = state.decimalDescription.invoke() - decimalTextView.text = decimalCode - - // TODO - if (state.isWaitingFromOther) { - // hide buttons - ButtonsVisibilityGroup.isInvisible = true - sasCodeWaitingPartnerText.isVisible = true - } else { - ButtonsVisibilityGroup.isVisible = decimalCode != null - sasCodeWaitingPartnerText.isVisible = false - } - } - } - - @OnClick(R.id.sas_request_continue_button) - fun onMatchButtonTapped() = withState(viewModel) { state -> - val otherUserId = state.otherUser?.id ?: return@withState - val txId = state.transactionId ?: return@withState - // UX echo - ButtonsVisibilityGroup.isInvisible = true - sasCodeWaitingPartnerText.isVisible = true - sharedViewModel.handle(VerificationAction.SASMatchAction(otherUserId, txId)) - } - - @OnClick(R.id.sas_request_cancel_button) - fun onDoNotMatchButtonTapped() = withState(viewModel) { state -> - val otherUserId = state.otherUser?.id ?: return@withState - val txId = state.transactionId ?: return@withState - // UX echo - ButtonsVisibilityGroup.isInvisible = true - sasCodeWaitingPartnerText.isVisible = true - sharedViewModel.handle(VerificationAction.SASDoNotMatchAction(otherUserId, txId)) - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationIncomingFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationIncomingFragment.kt deleted file mode 100644 index cb96900ba7..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationIncomingFragment.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2019 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 - -import android.os.Bundle -import android.widget.ImageView -import android.widget.TextView -import androidx.lifecycle.Observer -import butterknife.BindView -import butterknife.OnClick -import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction -import im.vector.matrix.android.api.util.MatrixItem -import im.vector.matrix.android.api.util.toMatrixItem -import im.vector.riotx.R -import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.features.home.AvatarRenderer -import javax.inject.Inject - -// TODO Deprecated("replaced by bottomsheet UX") -class SASVerificationIncomingFragment @Inject constructor( - private var avatarRenderer: AvatarRenderer -) : VectorBaseFragment() { - - @BindView(R.id.sas_incoming_request_user_display_name) - lateinit var otherUserDisplayNameTextView: TextView - - @BindView(R.id.sas_incoming_request_user_id) - lateinit var otherUserIdTextView: TextView - - @BindView(R.id.sas_incoming_request_user_device) - lateinit var otherDeviceTextView: TextView - - @BindView(R.id.sas_incoming_request_user_avatar) - lateinit var avatarImageView: ImageView - - override fun getLayoutResId() = R.layout.fragment_sas_verification_incoming_request - - private lateinit var viewModel: SasVerificationViewModel - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - - viewModel = activityViewModelProvider.get(SasVerificationViewModel::class.java) - - otherUserDisplayNameTextView.text = viewModel.otherUser?.displayName ?: viewModel.otherUserId - otherUserIdTextView.text = viewModel.otherUserId - otherDeviceTextView.text = viewModel.otherDeviceId - - viewModel.otherUser?.let { - avatarRenderer.render(it.toMatrixItem(), avatarImageView) - } ?: run { - // Fallback to what we know - avatarRenderer.render(MatrixItem.UserItem(viewModel.otherUserId ?: "", viewModel.otherUserId), avatarImageView) - } - - viewModel.transactionState.observe(viewLifecycleOwner, Observer { - val uxState = (viewModel.transaction as? IncomingSasVerificationTransaction)?.uxState - when (uxState) { - IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { - viewModel.loadingLiveEvent.value = null - } - IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT -> { - viewModel.loadingLiveEvent.value = R.string.sas_waiting_for_partner - } - IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { - viewModel.shortCodeReady() - } - IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME, - IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER -> { - viewModel.loadingLiveEvent.value = null - viewModel.navigateCancel() - } - else -> Unit - } - }) - } - - @OnClick(R.id.sas_request_continue_button) - fun didAccept() { - viewModel.acceptTransaction() - } - - @OnClick(R.id.sas_request_cancel_button) - fun didCancel() { - viewModel.cancelTransaction() - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationShortCodeFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationShortCodeFragment.kt deleted file mode 100644 index 64c1c4b1f0..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationShortCodeFragment.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2019 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 - -import android.os.Bundle -import android.view.ViewGroup -import android.widget.TextView -import androidx.core.view.isInvisible -import androidx.core.view.isVisible -import androidx.lifecycle.Observer -import butterknife.BindView -import butterknife.OnClick -import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction -import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest -import im.vector.riotx.R -import im.vector.riotx.core.platform.VectorBaseFragment -import javax.inject.Inject - -// TODO Deprecated("replaced by bottomsheet UX") -class SASVerificationShortCodeFragment @Inject constructor(): VectorBaseFragment() { - - private lateinit var viewModel: SasVerificationViewModel - - @BindView(R.id.sas_decimal_code) - lateinit var decimalTextView: TextView - - @BindView(R.id.sas_emoji_description) - lateinit var descriptionTextView: TextView - - @BindView(R.id.sas_emoji_grid) - lateinit var emojiGrid: ViewGroup - - @BindView(R.id.emoji0) - lateinit var emoji0View: ViewGroup - @BindView(R.id.emoji1) - lateinit var emoji1View: ViewGroup - @BindView(R.id.emoji2) - lateinit var emoji2View: ViewGroup - @BindView(R.id.emoji3) - lateinit var emoji3View: ViewGroup - @BindView(R.id.emoji4) - lateinit var emoji4View: ViewGroup - @BindView(R.id.emoji5) - lateinit var emoji5View: ViewGroup - @BindView(R.id.emoji6) - lateinit var emoji6View: ViewGroup - - override fun getLayoutResId() = R.layout.fragment_sas_verification_display_code - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - viewModel = activityViewModelProvider.get(SasVerificationViewModel::class.java) - - viewModel.transaction?.let { - if (it.supportsEmoji()) { - val emojicodes = it.getEmojiCodeRepresentation() - emojicodes.forEachIndexed { index, emojiRepresentation -> - when (index) { - 0 -> { - emoji0View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji0View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) - } - 1 -> { - emoji1View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji1View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) - } - 2 -> { - emoji2View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji2View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) - } - 3 -> { - emoji3View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji3View.findViewById(R.id.item_emoji_name_tv)?.setText(emojiRepresentation.nameResId) - } - 4 -> { - emoji4View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji4View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) - } - 5 -> { - emoji5View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji5View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) - } - 6 -> { - emoji6View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation.emoji - emoji6View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId) - } - } - } - } - - // decimal is at least supported - decimalTextView.text = it.getDecimalCodeRepresentation() - - if (it.supportsEmoji()) { - descriptionTextView.text = getString(R.string.sas_emoji_description) - decimalTextView.isVisible = false - emojiGrid.isVisible = true - } else { - descriptionTextView.text = getString(R.string.sas_decimal_description) - decimalTextView.isVisible = true - emojiGrid.isInvisible = true - } - } - - viewModel.transactionState.observe(viewLifecycleOwner, Observer { - if (viewModel.transaction is IncomingSasVerificationTransaction) { - val uxState = (viewModel.transaction as IncomingSasVerificationTransaction).uxState - when (uxState) { - IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { - viewModel.loadingLiveEvent.value = null - } - IncomingSasVerificationTransaction.UxState.VERIFIED -> { - viewModel.loadingLiveEvent.value = null - viewModel.deviceIsVerified() - } - IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME, - IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER -> { - viewModel.loadingLiveEvent.value = null - viewModel.navigateCancel() - } - else -> { - viewModel.loadingLiveEvent.value = R.string.sas_waiting_for_partner - } - } - } else if (viewModel.transaction is OutgoingSasVerificationRequest) { - val uxState = (viewModel.transaction as OutgoingSasVerificationRequest).uxState - when (uxState) { - OutgoingSasVerificationRequest.UxState.SHOW_SAS -> { - viewModel.loadingLiveEvent.value = null - } - OutgoingSasVerificationRequest.UxState.VERIFIED -> { - viewModel.loadingLiveEvent.value = null - viewModel.deviceIsVerified() - } - OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME, - OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER -> { - viewModel.loadingLiveEvent.value = null - viewModel.navigateCancel() - } - else -> { - viewModel.loadingLiveEvent.value = R.string.sas_waiting_for_partner - } - } - } - }) - } - - @OnClick(R.id.sas_request_continue_button) - fun didAccept() { - viewModel.confirmEmojiSame() - } - - @OnClick(R.id.sas_request_cancel_button) - fun didCancel() { - viewModel.cancelTransaction() - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationStartFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationStartFragment.kt deleted file mode 100644 index 4e9699e853..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationStartFragment.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2019 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 - -import android.os.Bundle -import android.view.ViewGroup -import android.widget.Button -import android.widget.ProgressBar -import android.widget.TextView -import androidx.core.view.isInvisible -import androidx.core.view.isVisible -import androidx.lifecycle.Observer -import androidx.transition.TransitionManager -import butterknife.BindView -import butterknife.OnClick -import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest -import im.vector.riotx.R -import im.vector.riotx.core.platform.VectorBaseActivity -import im.vector.riotx.core.platform.VectorBaseFragment -import javax.inject.Inject - -// TODO Deprecated("replaced by bottomsheet UX") -class SASVerificationStartFragment @Inject constructor(): VectorBaseFragment() { - - override fun getLayoutResId() = R.layout.fragment_sas_verification_start - - private lateinit var viewModel: SasVerificationViewModel - - @BindView(R.id.rootLayout) - lateinit var rootLayout: ViewGroup - - @BindView(R.id.sas_start_button) - lateinit var startButton: Button - - @BindView(R.id.sas_start_button_loading) - lateinit var startButtonLoading: ProgressBar - - @BindView(R.id.sas_verifying_keys) - lateinit var loadingText: TextView - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - viewModel = activityViewModelProvider.get(SasVerificationViewModel::class.java) - viewModel.transactionState.observe(viewLifecycleOwner, Observer { - val uxState = (viewModel.transaction as? OutgoingSasVerificationRequest)?.uxState - when (uxState) { - OutgoingSasVerificationRequest.UxState.WAIT_FOR_KEY_AGREEMENT -> { - // display loading - TransitionManager.beginDelayedTransition(this.rootLayout) - this.loadingText.isVisible = true - this.startButton.isInvisible = true - this.startButtonLoading.isVisible = true - this.startButtonLoading.animate() - } - OutgoingSasVerificationRequest.UxState.SHOW_SAS -> { - viewModel.shortCodeReady() - } - OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME, - OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER -> { - viewModel.navigateCancel() - } - else -> { - TransitionManager.beginDelayedTransition(this.rootLayout) - this.loadingText.isVisible = false - this.startButton.isVisible = true - this.startButtonLoading.isVisible = false - } - } - }) - } - - @OnClick(R.id.sas_start_button) - fun doStart() { - viewModel.beginSasKeyVerification() - } - - @OnClick(R.id.sas_legacy_verification) - fun doLegacy() { - (requireActivity() as VectorBaseActivity).notImplemented() - - /* - viewModel.session.crypto?.getDeviceInfo(viewModel.otherUserMxItem ?: "", viewModel.otherDeviceId - ?: "", object : SimpleApiCallback() { - override fun onSuccess(info: MXDeviceInfo?) { - info?.let { - - CommonActivityUtils.displayDeviceVerificationDialogLegacy(it, it.userId, viewModel.session, activity, object : YesNoListener { - override fun yes() { - viewModel.manuallyVerified() - } - - override fun no() { - - } - }) - } - } - }) - */ - } - - @OnClick(R.id.sas_cancel_button) - fun doCancel() { - // Transaction may be started, or not - viewModel.cancelTransaction() - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationVerifiedFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationVerifiedFragment.kt deleted file mode 100644 index fe7f9861b0..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationVerifiedFragment.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2019 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 - -import android.os.Bundle -import butterknife.OnClick -import im.vector.riotx.R -import im.vector.riotx.core.platform.VectorBaseFragment -import javax.inject.Inject - -// TODO Deprecated("replaced by bottomsheet UX") -class SASVerificationVerifiedFragment @Inject constructor() : VectorBaseFragment() { - - override fun getLayoutResId() = R.layout.fragment_sas_verification_verified - - private lateinit var viewModel: SasVerificationViewModel - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - - viewModel = activityViewModelProvider.get(SasVerificationViewModel::class.java) - } - - @OnClick(R.id.sas_verification_verified_done_button) - fun onDone() { - viewModel.finishSuccess() - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt deleted file mode 100644 index ca283e449d..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SasVerificationViewModel.kt +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2019 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 - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction -import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService -import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction -import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState -import im.vector.matrix.android.api.session.user.model.User -import im.vector.riotx.core.utils.LiveEvent -import javax.inject.Inject - -// TODO Deprecated("replaced by bottomsheet UX") -class SasVerificationViewModel @Inject constructor() : ViewModel(), - SasVerificationService.SasVerificationListener { - - companion object { - const val NAVIGATE_FINISH = "NAVIGATE_FINISH" - const val NAVIGATE_FINISH_SUCCESS = "NAVIGATE_FINISH_SUCCESS" - const val NAVIGATE_SAS_DISPLAY = "NAVIGATE_SAS_DISPLAY" - const val NAVIGATE_SUCCESS = "NAVIGATE_SUCCESS" - const val NAVIGATE_CANCELLED = "NAVIGATE_CANCELLED" - } - - private lateinit var sasVerificationService: SasVerificationService - - var otherUserId: String? = null - var otherDeviceId: String? = null - var otherUser: User? = null - var transaction: SasVerificationTransaction? = null - - var transactionState: MutableLiveData = MutableLiveData() - - init { - // Force a first observe - transactionState.value = null - } - - private var _navigateEvent: MutableLiveData> = MutableLiveData() - val navigateEvent: LiveData> - get() = _navigateEvent - - var loadingLiveEvent: MutableLiveData = MutableLiveData() - - var transactionID: String? = null - set(value) { - if (value != null) { - transaction = sasVerificationService.getExistingTransaction(otherUserId!!, value) - transactionState.value = transaction?.state - otherDeviceId = transaction?.otherDeviceId - } - field = value - } - - fun initIncoming(session: Session, otherUserId: String, transactionID: String?) { - this.sasVerificationService = session.getSasVerificationService() - this.otherUserId = otherUserId - this.transactionID = transactionID - this.sasVerificationService.addListener(this) - this.otherUser = session.getUser(otherUserId) - if (transactionID == null || transaction == null) { - // sanity, this transaction is not known anymore - _navigateEvent.value = LiveEvent(NAVIGATE_FINISH) - } - } - - fun initOutgoing(session: Session, otherUserId: String, otherDeviceId: String) { - this.sasVerificationService = session.getSasVerificationService() - this.otherUserId = otherUserId - this.otherDeviceId = otherDeviceId - this.sasVerificationService.addListener(this) - this.otherUser = session.getUser(otherUserId) - } - - fun beginSasKeyVerification() { - val verificationSAS = sasVerificationService.beginKeyVerificationSAS(otherUserId!!, otherDeviceId!!) - this.transactionID = verificationSAS - } - - override fun transactionCreated(tx: SasVerificationTransaction) { - } - - override fun transactionUpdated(tx: SasVerificationTransaction) { - if (transactionID == tx.transactionId) { - transactionState.value = tx.state - } - } - - override fun markedAsManuallyVerified(userId: String, deviceId: String) { - } - - fun cancelTransaction() { - transaction?.cancel() - _navigateEvent.value = LiveEvent(NAVIGATE_FINISH) - } - - fun finishSuccess() { - _navigateEvent.value = LiveEvent(NAVIGATE_FINISH_SUCCESS) - } - - fun manuallyVerified() { - if (otherUserId != null && otherDeviceId != null) { - sasVerificationService.markedLocallyAsManuallyVerified(otherUserId!!, otherDeviceId!!) - } - _navigateEvent.value = LiveEvent(NAVIGATE_FINISH_SUCCESS) - } - - fun acceptTransaction() { - (transaction as? IncomingSasVerificationTransaction)?.performAccept() - } - - fun confirmEmojiSame() { - transaction?.userHasVerifiedShortCode() - } - - fun shortCodeReady() { - loadingLiveEvent.value = null - _navigateEvent.value = LiveEvent(NAVIGATE_SAS_DISPLAY) - } - - fun deviceIsVerified() { - loadingLiveEvent.value = null - _navigateEvent.value = LiveEvent(NAVIGATE_SUCCESS) - } - - fun navigateCancel() { - _navigateEvent.value = LiveEvent(NAVIGATE_CANCELLED) - } - - override fun onCleared() { - super.onCleared() - if (::sasVerificationService.isInitialized) { - sasVerificationService.removeListener(this) - } - } -} 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 61598bd5fc..22369f37b5 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 @@ -17,20 +17,16 @@ package im.vector.riotx.features.crypto.verification import android.os.Bundle import android.os.Parcelable -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.core.text.toSpannable +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.transition.AutoTransition import androidx.transition.TransitionManager import butterknife.BindView -import butterknife.ButterKnife -import butterknife.Unbinder import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.Success import com.airbnb.mvrx.fragmentViewModel @@ -40,9 +36,12 @@ import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.extensions.commitTransactionNow import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment -import im.vector.riotx.core.utils.colorizeMatchingText +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.request.VerificationRequestFragment +import im.vector.riotx.features.crypto.verification.request.VerificationRequestViewModel import im.vector.riotx.features.home.AvatarRenderer -import im.vector.riotx.features.themes.ThemeUtils import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.bottom_sheet_verification.* import timber.log.Timber @@ -74,22 +73,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { @BindView(R.id.verificationRequestName) lateinit var otherUserNameText: TextView + @BindView(R.id.verificationRequestShield) + lateinit var otherUserShield: View + @BindView(R.id.verificationRequestAvatar) lateinit var otherUserAvatarImageView: ImageView - private var unBinder: Unbinder? = null - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.bottom_sheet_verification, container, false) - unBinder = ButterKnife.bind(this, view) - return view - } - - override fun onDestroyView() { - super.onDestroyView() - unBinder?.unbind() - unBinder = null - } + override fun getLayoutResId() = R.layout.bottom_sheet_verification override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -109,12 +99,15 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { override fun invalidate() = withState(viewModel) { it.otherUserMxItem?.let { matrixItem -> - val displayName = matrixItem.displayName ?: "" - otherUserNameText.text = getString(R.string.verification_request_alert_title, displayName) - .toSpannable() - .colorizeMatchingText(displayName, ThemeUtils.getColor(requireContext(), R.attr.vctr_notice_text_color)) - avatarRenderer.render(matrixItem, otherUserAvatarImageView) + + if(it.sasTransactionState == SasVerificationTxState.Verified) { + otherUserNameText.text = getString(R.string.verification_verified_user, matrixItem.getBestName()) + otherUserShield.isVisible = true + } else { + otherUserNameText.text = getString(R.string.verification_verify_user, matrixItem.getBestName()) + otherUserShield.isVisible = false + } } // Did the request result in a SAS transaction? @@ -135,7 +128,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { SasVerificationTxState.SendingMac, SasVerificationTxState.MacSent, SasVerificationTxState.Verifying -> { - showFragment(SASVerificationCodeFragment::class, Bundle().apply { + showFragment(VerificationEmojiCodeFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationArgs( it.otherUserMxItem?.id ?: "", it.pendingRequest?.transactionId)) 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 e25a22e668..20383b306d 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 @@ -21,13 +21,9 @@ import com.airbnb.mvrx.* import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.crypto.sas.CancelCode -import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService -import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction -import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState +import im.vector.matrix.android.api.session.crypto.sas.* import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.platform.VectorViewModel @@ -108,7 +104,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini is VerificationAction.RequestVerificationByDM -> { // session setState { - copy(pendingRequest = session.getSasVerificationService().requestKeyVerificationInDMs(otherUserId, roomId)) + copy(pendingRequest = session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, roomId)) } } is VerificationAction.StartSASVerification -> { @@ -117,7 +113,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini val otherDevice = if (request.isIncoming) request.requestInfo?.fromDevice else request.readyInfo?.fromDevice session.getSasVerificationService().beginKeyVerificationInDMs( - KeyVerificationStart.VERIF_METHOD_SAS, + VerificationMethod.SAS, transactionId = action.pendingRequestTransactionId, roomId = roomId, otherUserId = request.otherUserId, diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt deleted file mode 100644 index 69abc77b6f..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodFragment.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2019 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 - -import android.text.style.ClickableSpan -import android.view.View -import androidx.core.text.toSpannable -import androidx.core.view.isVisible -import butterknife.OnClick -import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.parentFragmentViewModel -import com.airbnb.mvrx.withState -import im.vector.riotx.R -import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.core.utils.tappableMatchingText -import kotlinx.android.synthetic.main.fragment_verification_choose_method.* -import javax.inject.Inject - -class VerificationChooseMethodFragment @Inject constructor( - val verificationChooseMethodViewModelFactory: VerificationChooseMethodViewModel.Factory -) : VectorBaseFragment() { - - override fun getLayoutResId() = R.layout.fragment_verification_choose_method - - private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) - - private val viewModel by fragmentViewModel(VerificationChooseMethodViewModel::class) - - override fun invalidate() = withState(viewModel) { state -> - if (state.QRModeAvailable) { - val cSpan = object : ClickableSpan() { - override fun onClick(widget: View) { - } - } - val openLink = getString(R.string.verify_open_camera_link) - val descCharSequence = - getString(R.string.verify_by_scanning_description, openLink) - .toSpannable() - .tappableMatchingText(openLink, cSpan) - verifyQRDescription.text = descCharSequence - verifyQRGroup.isVisible = true - } else { - verifyQRGroup.isVisible = false - } - - verifyEmojiGroup.isVisible = state.SASMOdeAvailable - } - - @OnClick(R.id.verificationByEmojiButton) - fun doVerifyBySas() = withState(sharedViewModel) { - sharedViewModel.handle(VerificationAction.StartSASVerification(it.otherUserMxItem?.id ?: "", it.pendingRequest?.transactionId - ?: "")) - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt deleted file mode 100644 index fdf4b0cbd4..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionFragment.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2019 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 - -import android.os.Parcelable -import androidx.core.content.ContextCompat -import androidx.core.view.isVisible -import butterknife.OnClick -import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.parentFragmentViewModel -import com.airbnb.mvrx.withState -import im.vector.riotx.R -import im.vector.riotx.core.extensions.setTextOrHide -import im.vector.riotx.core.platform.VectorBaseFragment -import io.noties.markwon.Markwon -import kotlinx.android.parcel.Parcelize -import kotlinx.android.synthetic.main.fragment_verification_conclusion.* -import javax.inject.Inject - -class VerificationConclusionFragment @Inject constructor() : VectorBaseFragment() { - - @Parcelize - data class Args( - val isSuccessFull: Boolean, - val cancelReason: String? - ) : Parcelable - - override fun getLayoutResId() = R.layout.fragment_verification_conclusion - - private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) - - private val viewModel by fragmentViewModel(VerificationConclusionViewModel::class) - - override fun invalidate() = withState(viewModel) { - when (it.conclusionState) { - ConclusionState.SUCCESS -> { - verificationConclusionTitle.text = getString(R.string.sas_verified) - verifyConclusionDescription.setTextOrHide(getString(R.string.sas_verified_successful_description)) - verifyConclusionBottomDescription.text = getString(R.string.verification_green_shield) - verifyConclusionImageView.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_shield_trusted)) - } - ConclusionState.WARNING -> { - verificationConclusionTitle.text = getString(R.string.verification_conclusion_not_secure) - verifyConclusionDescription.isVisible = false - verifyConclusionImageView.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_shield_warning)) - - verifyConclusionBottomDescription.text = Markwon.builder(requireContext()) - .build() - .toMarkdown(getString(R.string.verification_conclusion_compromised)) - } - ConclusionState.CANCELLED -> { - // Just dismiss in this case - sharedViewModel.handle(VerificationAction.GotItConclusion) - } - } - } - - @OnClick(R.id.verificationConclusionButton) - fun onButtonTapped() { - sharedViewModel.handle(VerificationAction.GotItConclusion) - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt deleted file mode 100644 index d6e4105fdd..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestFragment.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2019 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 - -import android.graphics.Typeface -import androidx.core.text.toSpannable -import androidx.core.view.isInvisible -import androidx.core.view.isVisible -import butterknife.OnClick -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.parentFragmentViewModel -import com.airbnb.mvrx.withState -import im.vector.riotx.R -import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.core.utils.colorizeMatchingText -import im.vector.riotx.core.utils.styleMatchingText -import im.vector.riotx.features.home.AvatarRenderer -import im.vector.riotx.features.themes.ThemeUtils -import kotlinx.android.synthetic.main.fragment_verification_request.* -import javax.inject.Inject - -class VerificationRequestFragment @Inject constructor( - val verificationRequestViewModelFactory: VerificationRequestViewModel.Factory, - val avatarRenderer: AvatarRenderer -) : VectorBaseFragment() { - - private val viewModel by fragmentViewModel(VerificationRequestViewModel::class) - - private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) - - override fun getLayoutResId() = R.layout.fragment_verification_request - - override fun invalidate() = withState(viewModel) { state -> - state.matrixItem.let { - val styledText = getString(R.string.verification_request_alert_description, it.id) - .toSpannable() - .styleMatchingText(it.id, Typeface.BOLD) - .colorizeMatchingText(it.id, ThemeUtils.getColor(requireContext(), R.attr.vctr_notice_text_color)) - verificationRequestText.text = styledText - } - - when (state.started) { - is Loading -> { - // Hide the start button, show waiting - verificationStartButton.isInvisible = true - verificationWaitingText.isVisible = true - val otherUser = state.matrixItem.displayName ?: state.matrixItem.id - verificationWaitingText.text = getString(R.string.verification_request_waiting_for, otherUser) - .toSpannable() - .styleMatchingText(otherUser, Typeface.BOLD) - .colorizeMatchingText(otherUser, ThemeUtils.getColor(requireContext(), R.attr.vctr_notice_text_color)) - } - else -> { - verificationStartButton.isEnabled = true - verificationStartButton.isVisible = true - verificationWaitingText.isInvisible = true - } - } - - Unit - } - - @OnClick(R.id.verificationStartButton) - fun onClickOnVerificationStart() = withState(viewModel) { state -> - verificationStartButton.isEnabled = false - sharedViewModel.handle(VerificationAction.RequestVerificationByDM(state.matrixItem.id, state.roomId)) - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt new file mode 100644 index 0000000000..8760a8603e --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt @@ -0,0 +1,100 @@ +/* + * Copyright 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.choose + +import com.airbnb.epoxy.EpoxyController +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.epoxy.bottomSheetVerificationActionItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem +import javax.inject.Inject + +class VerificationChooseMethodController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider +) : EpoxyController() { + + var listener: Listener? = null + + private var viewState: VerificationChooseMethodViewState? = null + + fun update(viewState: VerificationChooseMethodViewState) { + this.viewState = viewState + requestModelBuild() + } + + override fun buildModels() { + val state = viewState ?: return + + if (state.QRModeAvailable) { + bottomSheetVerificationNoticeItem { + id("notice") + notice(stringProvider.getString(R.string.verification_scan_notice)) + } + + // TODO Generate the QR code + bottomSheetVerificationBigImageItem { + id("qr") + imageRes(R.drawable.riotx_logo) + } + + dividerItem { + id("sep0") + } + + bottomSheetVerificationActionItem { + id("openCamera") + title(stringProvider.getString(R.string.verification_scan_their_code)) + titleColor(colorProvider.getColor(R.color.riotx_accent)) + iconRes(R.drawable.ic_camera) + iconColor(colorProvider.getColor(R.color.riotx_accent)) + listener { listener?.openCamera() } + } + + dividerItem { + id("sep1") + } + + bottomSheetVerificationActionItem { + id("openEmoji") + title(stringProvider.getString(R.string.verification_scan_emoji_title)) + titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + subTitle(stringProvider.getString(R.string.verification_scan_emoji_subtitle)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + listener { listener?.doVerifyBySas() } + } + } else if (state.SASModeAvailable) { + bottomSheetVerificationActionItem { + id("openEmoji") + title(stringProvider.getString(R.string.verification_no_scan_emoji_title)) + titleColor(colorProvider.getColor(R.color.riotx_accent)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + listener { listener?.doVerifyBySas() } + } + } + } + + interface Listener { + fun openCamera() + fun doVerifyBySas() + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt new file mode 100644 index 0000000000..b782afca39 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2019 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.choose + +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.fragmentViewModel +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 +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.features.crypto.verification.VerificationAction +import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel +import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.* +import javax.inject.Inject + +class VerificationChooseMethodFragment @Inject constructor( + val verificationChooseMethodViewModelFactory: VerificationChooseMethodViewModel.Factory, + val controller: VerificationChooseMethodController +) : VectorBaseFragment(), VerificationChooseMethodController.Listener { + + private val viewModel by fragmentViewModel(VerificationChooseMethodViewModel::class) + + private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) + + override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupRecyclerView() + } + + override fun onDestroyView() { + bottomSheetVerificationRecyclerView.cleanup() + controller.listener = null + super.onDestroyView() + } + + private fun setupRecyclerView() { + bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) + controller.listener = this + } + + override fun invalidate() = withState(viewModel) { state -> + controller.update(state) + } + + override fun doVerifyBySas() = withState(sharedViewModel) { + sharedViewModel.handle(VerificationAction.StartSASVerification( + it.otherUserMxItem?.id ?: "", + it.pendingRequest?.transactionId ?: "")) + } + + override fun openCamera() { + // TODO + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt similarity index 82% rename from vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt rename to vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt index 2fcf94a036..a1d566d77b 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationChooseMethodViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotx.features.crypto.verification +package im.vector.riotx.features.crypto.verification.choose import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxState @@ -24,17 +24,18 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.features.crypto.verification.VerificationBottomSheet data class VerificationChooseMethodViewState( val otherUserId: String = "", val transactionId: String = "", val QRModeAvailable: Boolean = false, - val SASMOdeAvailable: Boolean = false + val SASModeAvailable: Boolean = false ) : MvRxState class VerificationChooseMethodViewModel @AssistedInject constructor( @@ -48,15 +49,13 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state -> val pvr = session.getSasVerificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId) - val qrAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SCAN) - ?: false - val emojiAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SAS) - ?: false + val qrAvailable = pvr?.hasMethod(VerificationMethod.SCAN) ?: false + val emojiAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false setState { copy( QRModeAvailable = qrAvailable, - SASMOdeAvailable = emojiAvailable + SASModeAvailable = emojiAvailable ) } } @@ -85,15 +84,13 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( val args: VerificationBottomSheet.VerificationArgs = viewModelContext.args() val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() val pvr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId) - val qrAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SCAN) - ?: false - val emojiAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SAS) - ?: false + val qrAvailable = pvr?.hasMethod(VerificationMethod.SCAN) ?: false + val emojiAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false return VerificationChooseMethodViewState(otherUserId = args.otherUserId, transactionId = args.verificationId ?: "", QRModeAvailable = qrAvailable, - SASMOdeAvailable = emojiAvailable + SASModeAvailable = emojiAvailable ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionController.kt new file mode 100644 index 0000000000..5c7e35dc28 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionController.kt @@ -0,0 +1,96 @@ +/* + * Copyright 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.conclusion + +import com.airbnb.epoxy.EpoxyController +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.epoxy.bottomSheetVerificationActionItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem +import im.vector.riotx.features.html.EventHtmlRenderer +import javax.inject.Inject + +class VerificationConclusionController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider, + private val eventHtmlRenderer: EventHtmlRenderer +) : EpoxyController() { + + var listener: Listener? = null + + private var viewState: VerificationConclusionViewState? = null + + fun update(viewState: VerificationConclusionViewState) { + this.viewState = viewState + requestModelBuild() + } + + override fun buildModels() { + val state = viewState ?: return + + when (state.conclusionState) { + ConclusionState.SUCCESS -> { + bottomSheetVerificationNoticeItem { + id("notice") + notice(stringProvider.getString(R.string.verification_conclusion_ok_notice)) + } + + bottomSheetVerificationBigImageItem { + id("image") + imageRes(R.drawable.ic_shield_trusted) + } + } + ConclusionState.WARNING -> { + bottomSheetVerificationNoticeItem { + id("notice") + notice(stringProvider.getString(R.string.verification_conclusion_not_secure)) + } + + bottomSheetVerificationBigImageItem { + id("image") + imageRes(R.drawable.ic_shield_warning) + } + + bottomSheetVerificationNoticeItem { + id("warning_notice") + notice(eventHtmlRenderer.render(stringProvider.getString(R.string.verification_conclusion_compromised))) + } + } + else -> Unit + } + + dividerItem { + id("sep0") + } + + bottomSheetVerificationActionItem { + id("done") + title(stringProvider.getString(R.string.done)) + titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + listener { listener?.onButtonTapped() } + } + } + + interface Listener { + fun onButtonTapped() + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionFragment.kt new file mode 100644 index 0000000000..a46bb579de --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionFragment.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2019 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.conclusion + +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import com.airbnb.mvrx.fragmentViewModel +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 +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.features.crypto.verification.VerificationAction +import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel +import kotlinx.android.parcel.Parcelize +import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.* +import javax.inject.Inject + +class VerificationConclusionFragment @Inject constructor( + val controller: VerificationConclusionController +) : VectorBaseFragment(), VerificationConclusionController.Listener { + + @Parcelize + data class Args( + val isSuccessFull: Boolean, + val cancelReason: String? + ) : Parcelable + + private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) + + private val viewModel by fragmentViewModel(VerificationConclusionViewModel::class) + + override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupRecyclerView() + } + + override fun onDestroyView() { + bottomSheetVerificationRecyclerView.cleanup() + controller.listener = null + super.onDestroyView() + } + + private fun setupRecyclerView() { + bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) + controller.listener = this + } + + override fun invalidate() = withState(viewModel) { state -> + if (state.conclusionState == ConclusionState.CANCELLED) { + // Just dismiss in this case + sharedViewModel.handle(VerificationAction.GotItConclusion) + } else { + controller.update(state) + } + } + + override fun onButtonTapped() { + sharedViewModel.handle(VerificationAction.GotItConclusion) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt similarity index 78% rename from vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionViewModel.kt rename to vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt index ca069bf853..fc46ba8516 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationConclusionViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotx.features.crypto.verification +package im.vector.riotx.features.crypto.verification.conclusion import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory @@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.crypto.sas.safeValueOf import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.VectorViewModel -data class SASVerificationConclusionViewState( +data class VerificationConclusionViewState( val conclusionState: ConclusionState = ConclusionState.CANCELLED ) : MvRxState @@ -33,22 +33,22 @@ enum class ConclusionState { CANCELLED } -class VerificationConclusionViewModel(initialState: SASVerificationConclusionViewState) - : VectorViewModel(initialState) { +class VerificationConclusionViewModel(initialState: VerificationConclusionViewState) + : VectorViewModel(initialState) { - companion object : MvRxViewModelFactory { + companion object : MvRxViewModelFactory { - override fun initialState(viewModelContext: ViewModelContext): SASVerificationConclusionViewState? { + override fun initialState(viewModelContext: ViewModelContext): VerificationConclusionViewState? { val args = viewModelContext.args() return when (safeValueOf(args.cancelReason)) { CancelCode.MismatchedSas, CancelCode.MismatchedCommitment, CancelCode.MismatchedKeys -> { - SASVerificationConclusionViewState(ConclusionState.WARNING) + VerificationConclusionViewState(ConclusionState.WARNING) } else -> { - SASVerificationConclusionViewState( + VerificationConclusionViewState( if (args.isSuccessFull) ConclusionState.SUCCESS else ConclusionState.CANCELLED ) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeController.kt new file mode 100644 index 0000000000..12fffd3e11 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeController.kt @@ -0,0 +1,163 @@ +/* + * Copyright 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.emoji + +import com.airbnb.epoxy.EpoxyController +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Success +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.dividerItem +import im.vector.riotx.core.epoxy.errorWithRetryItem +import im.vector.riotx.core.error.ErrorFormatter +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationDecimalCodeItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationEmojisItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem +import javax.inject.Inject + +class VerificationEmojiCodeController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider, + private val errorFormatter: ErrorFormatter +) : EpoxyController() { + + var listener: Listener? = null + + private var viewState: VerificationEmojiCodeViewState? = null + + fun update(viewState: VerificationEmojiCodeViewState) { + this.viewState = viewState + requestModelBuild() + } + + override fun buildModels() { + val state = viewState ?: return + + if (state.supportsEmoji) { + buildEmojiItem(state) + } else { + buildDecimal(state) + } + } + + private fun buildEmojiItem(state: VerificationEmojiCodeViewState) { + when (val emojiDescription = state.emojiDescription) { + is Success -> { + bottomSheetVerificationNoticeItem { + id("notice") + notice(stringProvider.getString(R.string.verification_emoji_notice)) + } + + bottomSheetVerificationEmojisItem { + id("emojis") + emojiRepresentation0(emojiDescription()[0]) + emojiRepresentation1(emojiDescription()[1]) + emojiRepresentation2(emojiDescription()[2]) + emojiRepresentation3(emojiDescription()[3]) + emojiRepresentation4(emojiDescription()[4]) + emojiRepresentation5(emojiDescription()[5]) + emojiRepresentation6(emojiDescription()[6]) + } + + buildActions(state) + } + is Fail -> { + errorWithRetryItem { + id("error") + text(errorFormatter.toHumanReadable(emojiDescription.error)) + } + } + else -> { + bottomSheetVerificationWaitingItem { + id("waiting") + title(stringProvider.getString(R.string.please_wait)) + } + } + } + } + + private fun buildDecimal(state: VerificationEmojiCodeViewState) { + when (val decimalDescription = state.decimalDescription) { + is Success -> { + bottomSheetVerificationNoticeItem { + id("notice") + notice(stringProvider.getString(R.string.verification_code_notice)) + } + + bottomSheetVerificationDecimalCodeItem { + id("decimal") + code(state.decimalDescription.invoke() ?: "") + } + + buildActions(state) + } + is Fail -> { + errorWithRetryItem { + id("error") + text(errorFormatter.toHumanReadable(decimalDescription.error)) + } + } + else -> { + bottomSheetVerificationWaitingItem { + id("waiting") + title(stringProvider.getString(R.string.please_wait)) + } + } + } + } + + private fun buildActions(state: VerificationEmojiCodeViewState) { + dividerItem { + id("sep0") + } + + if (state.isWaitingFromOther) { + bottomSheetVerificationWaitingItem { + id("waiting") + title(stringProvider.getString(R.string.verification_request_waiting_for, state.otherUser?.getBestName() ?: "")) + } + } else { + bottomSheetVerificationActionItem { + id("ko") + title(stringProvider.getString(R.string.verification_sas_do_not_match)) + titleColor(colorProvider.getColor(R.color.vector_error_color)) + iconRes(R.drawable.ic_check_off) + iconColor(colorProvider.getColor(R.color.vector_error_color)) + listener { listener?.onDoNotMatchButtonTapped() } + } + dividerItem { + id("sep1") + } + bottomSheetVerificationActionItem { + id("ok") + title(stringProvider.getString(R.string.verification_sas_match)) + titleColor(colorProvider.getColor(R.color.riotx_accent)) + iconRes(R.drawable.ic_check_on) + iconColor(colorProvider.getColor(R.color.riotx_accent)) + listener { listener?.onMatchButtonTapped() } + } + } + } + + interface Listener { + fun onDoNotMatchButtonTapped() + fun onMatchButtonTapped() + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeFragment.kt new file mode 100644 index 0000000000..2cd20ca4fb --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeFragment.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2019 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.emoji + +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.fragmentViewModel +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 +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.features.crypto.verification.VerificationAction +import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel +import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.* +import javax.inject.Inject + +class VerificationEmojiCodeFragment @Inject constructor( + val viewModelFactory: VerificationEmojiCodeViewModel.Factory, + val controller: VerificationEmojiCodeController +) : VectorBaseFragment(), VerificationEmojiCodeController.Listener { + + private val viewModel by fragmentViewModel(VerificationEmojiCodeViewModel::class) + + private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) + + override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupRecyclerView() + } + + override fun onDestroyView() { + bottomSheetVerificationRecyclerView.cleanup() + controller.listener = null + super.onDestroyView() + } + + private fun setupRecyclerView() { + bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) + controller.listener = this + } + + override fun invalidate() = withState(viewModel) { state -> + controller.update(state) + } + + override fun onMatchButtonTapped() = withState(viewModel) { state -> + val otherUserId = state.otherUser?.id ?: return@withState + val txId = state.transactionId ?: return@withState + sharedViewModel.handle(VerificationAction.SASMatchAction(otherUserId, txId)) + } + + override fun onDoNotMatchButtonTapped() = withState(viewModel) { state -> + val otherUserId = state.otherUser?.id ?: return@withState + val txId = state.transactionId ?: return@withState + sharedViewModel.handle(VerificationAction.SASDoNotMatchAction(otherUserId, txId)) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt similarity index 88% rename from vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt rename to vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt index cf3a542430..5659dd8291 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/SASVerificationCodeViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotx.features.crypto.verification +package im.vector.riotx.features.crypto.verification.emoji import com.airbnb.mvrx.* import com.squareup.inject.assisted.Assisted @@ -28,8 +28,9 @@ import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.features.crypto.verification.VerificationBottomSheet -data class SASVerificationCodeViewState( +data class VerificationEmojiCodeViewState( val transactionId: String?, val otherUser: MatrixItem? = null, val supportsEmoji: Boolean = true, @@ -38,10 +39,10 @@ data class SASVerificationCodeViewState( val isWaitingFromOther: Boolean = false ) : MvRxState -class SASVerificationCodeViewModel @AssistedInject constructor( - @Assisted initialState: SASVerificationCodeViewState, +class VerificationEmojiCodeViewModel @AssistedInject constructor( + @Assisted initialState: VerificationEmojiCodeViewState, private val session: Session -) : VectorViewModel(initialState), SasVerificationService.SasVerificationListener { +) : VectorViewModel(initialState), SasVerificationService.SasVerificationListener { init { withState { state -> @@ -141,22 +142,22 @@ class SASVerificationCodeViewModel @AssistedInject constructor( @AssistedInject.Factory interface Factory { - fun create(initialState: SASVerificationCodeViewState): SASVerificationCodeViewModel + fun create(initialState: VerificationEmojiCodeViewState): VerificationEmojiCodeViewModel } - companion object : MvRxViewModelFactory { + companion object : MvRxViewModelFactory { - override fun create(viewModelContext: ViewModelContext, state: SASVerificationCodeViewState): SASVerificationCodeViewModel? { - val factory = (viewModelContext as FragmentViewModelContext).fragment().viewModelFactory + override fun create(viewModelContext: ViewModelContext, state: VerificationEmojiCodeViewState): VerificationEmojiCodeViewModel? { + val factory = (viewModelContext as FragmentViewModelContext).fragment().viewModelFactory return factory.create(state) } - override fun initialState(viewModelContext: ViewModelContext): SASVerificationCodeViewState? { + override fun initialState(viewModelContext: ViewModelContext): VerificationEmojiCodeViewState? { val args = viewModelContext.args() val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() val matrixItem = session.getUser(args.otherUserId)?.toMatrixItem() - return SASVerificationCodeViewState( + return VerificationEmojiCodeViewState( transactionId = args.verificationId, otherUser = matrixItem ) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationActionItem.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationActionItem.kt new file mode 100644 index 0000000000..a6b3459701 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationActionItem.kt @@ -0,0 +1,79 @@ +/* + * Copyright 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.epoxy + +import android.content.res.ColorStateList +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.core.view.isVisible +import androidx.core.widget.ImageViewCompat +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel +import im.vector.riotx.core.extensions.setTextOrHide + +/** + * A action for bottom sheet. + */ +@EpoxyModelClass(layout = R.layout.item_verification_action) +abstract class BottomSheetVerificationActionItem : VectorEpoxyModel() { + + @EpoxyAttribute + @DrawableRes + var iconRes: Int = -1 + @EpoxyAttribute + var title: CharSequence = "" + @EpoxyAttribute + var subTitle: CharSequence? = null + @EpoxyAttribute + var titleColor: Int = 0 + @EpoxyAttribute + var iconColor: Int = -1 + + @EpoxyAttribute + lateinit var listener: () -> Unit + + override fun bind(holder: Holder) { + holder.view.setOnClickListener { + listener.invoke() + } + + holder.title.text = title + holder.title.setTextColor(titleColor) + + holder.subTitle.setTextOrHide(subTitle) + + if (iconRes != -1) { + holder.icon.isVisible = true + holder.icon.setImageResource(iconRes) + if (iconColor != -1) { + ImageViewCompat.setImageTintList(holder.icon, ColorStateList.valueOf(iconColor)) + } + } else { + holder.icon.isVisible = false + } + } + + class Holder : VectorEpoxyHolder() { + val title by bind(R.id.itemVerificationActionTitle) + val subTitle by bind(R.id.itemVerificationActionSubTitle) + val icon by bind(R.id.itemVerificationActionIcon) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationBigImageItem.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationBigImageItem.kt new file mode 100644 index 0000000000..5163f5e8a8 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationBigImageItem.kt @@ -0,0 +1,53 @@ +/* + * Copyright 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.epoxy + +import android.widget.ImageView +import androidx.core.view.ViewCompat +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel + +/** + * A action for bottom sheet. + */ +@EpoxyModelClass(layout = R.layout.item_verification_big_image) +abstract class BottomSheetVerificationBigImageItem : VectorEpoxyModel() { + + @EpoxyAttribute + var imageRes: Int = 0 + + @EpoxyAttribute + var contentDescription: String? = null + + override fun bind(holder: Holder) { + holder.image.setImageResource(imageRes) + + if (contentDescription == null) { + ViewCompat.setImportantForAccessibility(holder.image, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO) + } else { + ViewCompat.setImportantForAccessibility(holder.image, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES) + holder.image.contentDescription = contentDescription + } + } + + class Holder : VectorEpoxyHolder() { + val image by bind(R.id.itemVerificationBigImage) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationDecimalCodeItem.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationDecimalCodeItem.kt new file mode 100644 index 0000000000..8d08ef0ba7 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationDecimalCodeItem.kt @@ -0,0 +1,42 @@ +/* + * Copyright 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.epoxy + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel + +/** + * A action for bottom sheet. + */ +@EpoxyModelClass(layout = R.layout.item_verification_decimal_code) +abstract class BottomSheetVerificationDecimalCodeItem : VectorEpoxyModel() { + + @EpoxyAttribute + var code: CharSequence = "" + + override fun bind(holder: Holder) { + holder.code.text = code + } + + class Holder : VectorEpoxyHolder() { + val code by bind(R.id.itemVerificationDecimalCode) + } +} 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 new file mode 100644 index 0000000000..6f75d91d8f --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationEmojisItem.kt @@ -0,0 +1,74 @@ +/* + * Copyright 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.epoxy + +import android.view.ViewGroup +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel + +/** + * A emoji list for bottom sheet. + */ +@EpoxyModelClass(layout = R.layout.item_verification_emojis) +abstract class BottomSheetVerificationEmojisItem : VectorEpoxyModel() { + + @EpoxyAttribute lateinit var emojiRepresentation0: EmojiRepresentation + @EpoxyAttribute lateinit var emojiRepresentation1: EmojiRepresentation + @EpoxyAttribute lateinit var emojiRepresentation2: EmojiRepresentation + @EpoxyAttribute lateinit var emojiRepresentation3: EmojiRepresentation + @EpoxyAttribute lateinit var emojiRepresentation4: EmojiRepresentation + @EpoxyAttribute lateinit var emojiRepresentation5: EmojiRepresentation + @EpoxyAttribute lateinit var emojiRepresentation6: EmojiRepresentation + + override fun bind(holder: Holder) { + holder.emoji0View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation0.emoji + holder.emoji0View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation0.nameResId) + + holder.emoji1View.findViewById(R.id.item_emoji_tv).text = emojiRepresentation1.emoji + holder.emoji1View.findViewById(R.id.item_emoji_name_tv).setText(emojiRepresentation1.nameResId) + + 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) + } + + class Holder : VectorEpoxyHolder() { + val emoji0View by bind(R.id.emoji0) + val emoji1View by bind(R.id.emoji1) + val emoji2View by bind(R.id.emoji2) + val emoji3View by bind(R.id.emoji3) + val emoji4View by bind(R.id.emoji4) + val emoji5View by bind(R.id.emoji5) + val emoji6View by bind(R.id.emoji6) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationNoticeItem.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationNoticeItem.kt new file mode 100644 index 0000000000..b5314d4869 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationNoticeItem.kt @@ -0,0 +1,42 @@ +/* + * Copyright 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.epoxy + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel + +/** + * A action for bottom sheet. + */ +@EpoxyModelClass(layout = R.layout.item_verification_notice) +abstract class BottomSheetVerificationNoticeItem : VectorEpoxyModel() { + + @EpoxyAttribute + var notice: CharSequence = "" + + override fun bind(holder: Holder) { + holder.notice.text = notice + } + + class Holder : VectorEpoxyHolder() { + val notice by bind(R.id.itemVerificationNoticeText) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationWaitingItem.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationWaitingItem.kt new file mode 100644 index 0000000000..2af5ef418f --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/epoxy/BottomSheetVerificationWaitingItem.kt @@ -0,0 +1,42 @@ +/* + * Copyright 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.epoxy + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel + +/** + * A action for bottom sheet. + */ +@EpoxyModelClass(layout = R.layout.item_verification_waiting) +abstract class BottomSheetVerificationWaitingItem : VectorEpoxyModel() { + + @EpoxyAttribute + var title: CharSequence = "" + + override fun bind(holder: Holder) { + holder.title.text = title + } + + class Holder : VectorEpoxyHolder() { + val title by bind(R.id.itemVerificationWaitingTitle) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt new file mode 100644 index 0000000000..e14b6573f4 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2019 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.request + +import androidx.core.text.toSpannable +import com.airbnb.epoxy.EpoxyController +import com.airbnb.mvrx.Loading +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.core.utils.colorizeMatchingText +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem +import javax.inject.Inject + +class VerificationRequestController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider +) : EpoxyController() { + + var listener: Listener? = null + + private var viewState: VerificationRequestViewState? = null + + fun update(viewState: VerificationRequestViewState) { + this.viewState = viewState + requestModelBuild() + } + + override fun buildModels() { + val state = viewState ?: return + + val styledText = state.matrixItem.let { + stringProvider.getString(R.string.verification_request_notice, it.id) + .toSpannable() + .colorizeMatchingText(it.id, colorProvider.getColorFromAttribute(R.attr.vctr_notice_text_color)) + } + + bottomSheetVerificationNoticeItem { + id("notice") + notice(styledText) + } + + dividerItem { + id("sep") + } + + when (state.started) { + is Loading -> { + bottomSheetVerificationWaitingItem { + id("waiting") + title(stringProvider.getString(R.string.verification_request_waiting_for, state.matrixItem.getBestName())) + } + } + else -> { + bottomSheetVerificationActionItem { + id("start") + title(stringProvider.getString(R.string.start_verification)) + titleColor(colorProvider.getColor(R.color.riotx_accent)) + subTitle(stringProvider.getString(R.string.verification_request_start_notice)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + listener { listener?.onClickOnVerificationStart() } + } + } + } + } + + interface Listener { + fun onClickOnVerificationStart() + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt new file mode 100644 index 0000000000..8250bd74b8 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2019 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.request + +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.fragmentViewModel +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 +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.features.crypto.verification.VerificationAction +import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel +import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.* +import javax.inject.Inject + +class VerificationRequestFragment @Inject constructor( + val verificationRequestViewModelFactory: VerificationRequestViewModel.Factory, + val controller: VerificationRequestController +) : VectorBaseFragment(), VerificationRequestController.Listener { + + private val viewModel by fragmentViewModel(VerificationRequestViewModel::class) + + private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) + + override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupRecyclerView() + } + + override fun onDestroyView() { + bottomSheetVerificationRecyclerView.cleanup() + controller.listener = null + super.onDestroyView() + } + + private fun setupRecyclerView() { + bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) + controller.listener = this + } + + override fun invalidate() = withState(viewModel) { state -> + controller.update(state) + } + + override fun onClickOnVerificationStart() = withState(viewModel) { state -> + sharedViewModel.handle(VerificationAction.RequestVerificationByDM(state.matrixItem.id, state.roomId)) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestViewModel.kt similarity index 96% rename from vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt rename to vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestViewModel.kt index b21ce4e3a5..6f2855a5bb 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationRequestViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestViewModel.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotx.features.crypto.verification +package im.vector.riotx.features.crypto.verification.request import com.airbnb.mvrx.* import com.squareup.inject.assisted.Assisted @@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.crypto.verification.PendingVerification import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.features.crypto.verification.VerificationBottomSheet data class VerificationRequestViewState( val roomId: String? = null, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt index 358a5f3f57..d0716bd047 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt @@ -67,6 +67,6 @@ sealed class RoomDetailAction : VectorViewModelAction { data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction() data class DeclineVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction() - data class RequestVerification(val userId: String) : RoomDetailAction() + data class ResumeVerification(val transactionId: String, val otherUserId: String? = null, val otherdDeviceId: String? = null) : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt index 14e9061c36..fe4d0ae1f7 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt @@ -109,7 +109,7 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable { companion object { - private const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS" + const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS" fun newIntent(context: Context, roomDetailArgs: RoomDetailArgs): Intent { return Intent(context, RoomDetailActivity::class.java).apply { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 7e6d73eea7..236dd19030 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -54,6 +54,7 @@ import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.Success import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel @@ -74,6 +75,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageFileConten import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.Timeline @@ -849,6 +851,15 @@ class RoomDetailFragment @Inject constructor( data.transactionId ).show(parentFragmentManager, "REQ") } + is RoomDetailAction.ResumeVerification -> { + val otherUserId = data.otherUserId ?: return + VerificationBottomSheet().apply { + arguments = Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs( + otherUserId, data.transactionId, roomId = roomDetailArgs.roomId)) + } + }.show(parentFragmentManager, "REQ") + } } } } @@ -998,6 +1009,9 @@ class RoomDetailFragment @Inject constructor( } override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) { + if (messageContent is MessageVerificationRequestContent) { + roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId)) + } } override fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 9fb08c2efe..9ca1feae21 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -61,6 +61,7 @@ import im.vector.riotx.core.utils.PublishDataSource import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.ParsedCommand +import im.vector.riotx.features.crypto.verification.supportedVerificationMethods import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import im.vector.riotx.features.home.room.typing.TypingHelper import im.vector.riotx.features.settings.VectorPreferences @@ -187,6 +188,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) is RoomDetailAction.RequestVerification -> handleRequestVerification(action) + is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) } } @@ -408,7 +410,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro popDraft() } is ParsedCommand.VerifyUser -> { - session.getSasVerificationService().requestKeyVerificationInDMs(slashCommandResult.userId, room.roomId) + session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, slashCommandResult.userId, room.roomId) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) popDraft() } @@ -824,6 +826,18 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro _requestLiveData.postValue(LiveEvent(Success(action))) } + private fun handleResumeRequestVerification(action: RoomDetailAction.ResumeVerification) { + // Check if this request is still active and handled by me + session.getSasVerificationService().getExistingVerificationRequestInRoom(room.roomId, action.transactionId)?.let { + if (it.handledByOtherSession) return + if (!it.isFinished) { + _requestLiveData.postValue(LiveEvent(Success(action.copy( + otherUserId = it.otherUserId + )))) + } + } + } + private fun observeSyncState() { session.rx() .liveSyncState() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt index 4827c825cb..fe05d032ea 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt @@ -18,12 +18,8 @@ package im.vector.riotx.features.home.room.detail.readreceipts import android.os.Bundle import android.os.Parcelable -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import butterknife.BindView -import butterknife.ButterKnife import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.args import im.vector.riotx.R @@ -57,11 +53,7 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() { injector.inject(this) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.bottom_sheet_generic_list_with_title, container, false) - ButterKnife.bind(this, view) - return view - } + override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index ba772344e0..1336c61b68 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -16,12 +16,8 @@ package im.vector.riotx.features.home.room.detail.timeline.action import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import butterknife.BindView -import butterknife.ButterKnife import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R @@ -53,18 +49,12 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message injector.inject(this) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.bottom_sheet_generic_list, container, false) - ButterKnife.bind(this, view) - return view - } + override fun getLayoutResId() = R.layout.bottom_sheet_generic_list override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java) - recyclerView.configureWith(messageActionsEpoxyController, hasFixedSize = false) - // Disable item animation - recyclerView.itemAnimator = null + recyclerView.configureWith(messageActionsEpoxyController, hasFixedSize = false, disableItemAnimation = true) messageActionsEpoxyController.listener = this } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt index 8aa7c8561c..bf3816a030 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt @@ -16,12 +16,8 @@ package im.vector.riotx.features.home.room.detail.timeline.edithistory import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import butterknife.BindView -import butterknife.ButterKnife import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -57,11 +53,7 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() { injector.inject(this) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.bottom_sheet_generic_list_with_title, container, false) - ButterKnife.bind(this, view) - return view - } + override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt index 8fddc4c06a..47764edf93 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt @@ -17,12 +17,8 @@ package im.vector.riotx.features.home.room.detail.timeline.reactions import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import butterknife.BindView -import butterknife.ButterKnife import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -54,11 +50,7 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() { injector.inject(this) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.bottom_sheet_generic_list_with_title, container, false) - ButterKnife.bind(this, view) - return view - } + override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt index 1e775a934a..6148b3c725 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt @@ -18,12 +18,8 @@ package im.vector.riotx.features.home.room.list.actions import android.os.Bundle import android.os.Parcelable -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import butterknife.BindView -import butterknife.ButterKnife import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R @@ -69,18 +65,12 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R injector.inject(this) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.bottom_sheet_generic_list, container, false) - ButterKnife.bind(this, view) - return view - } + override fun getLayoutResId() = R.layout.bottom_sheet_generic_list override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) - recyclerView.configureWith(roomListActionsEpoxyController, viewPool = sharedViewPool, hasFixedSize = false) - // Disable item animation - recyclerView.itemAnimator = null + recyclerView.configureWith(roomListActionsEpoxyController, viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true) roomListActionsEpoxyController.listener = this } diff --git a/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt index 664ea79fd2..6d92d36d38 100644 --- a/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt @@ -20,12 +20,12 @@ import android.os.Build import android.os.Handler import android.os.Looper import android.view.View +import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import com.tapadoo.alerter.Alerter import com.tapadoo.alerter.OnHideAlertListener import im.vector.riotx.R -import im.vector.riotx.features.crypto.verification.SASVerificationActivity import timber.log.Timber import java.lang.ref.WeakReference @@ -78,8 +78,7 @@ object PopupAlertManager { setLightStatusBar() } } - - if (shouldIgnoreActivity(activity)) { + if (currentAlerter?.shouldBeDisplayedIn?.invoke(activity) == false) { return } @@ -108,8 +107,6 @@ object PopupAlertManager { } } - private fun shouldIgnoreActivity(activity: Activity) = activity is SASVerificationActivity - private fun displayNextIfPossible() { val currentActivity = weakCurrentActivity?.get() if (Alerter.isShowing || currentActivity == null) { @@ -209,7 +206,13 @@ object PopupAlertManager { }) .enableSwipeToDismiss() .enableInfiniteDuration(true) - .setBackgroundColorRes(alert.colorRes ?: R.color.notification_accent_color) + .apply { + if (alert.colorInt != null) { + setBackgroundColorInt(alert.colorInt!!) + } else { + setBackgroundColorRes(alert.colorRes ?: R.color.notification_accent_color) + } + } .show() } @@ -229,7 +232,8 @@ object PopupAlertManager { data class VectorAlert(val uid: String, val title: String, val description: String, - @DrawableRes val iconId: Int?) { + @DrawableRes val iconId: Int?, + val shouldBeDisplayedIn: ((Activity) -> Boolean)? = null) { data class Button(val title: String, val action: Runnable, val autoClose: Boolean) @@ -250,5 +254,8 @@ object PopupAlertManager { @ColorRes var colorRes: Int? = null + + @ColorInt + var colorInt: Int? = null } } diff --git a/vector/src/main/java/im/vector/riotx/features/workers/signout/SignOutBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/riotx/features/workers/signout/SignOutBottomSheetDialogFragment.kt index 94c718466f..e1ef7bc07b 100644 --- a/vector/src/main/java/im/vector/riotx/features/workers/signout/SignOutBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/workers/signout/SignOutBottomSheetDialogFragment.kt @@ -20,7 +20,6 @@ import android.app.Activity import android.app.Dialog import android.content.Intent import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout @@ -32,7 +31,6 @@ import androidx.core.view.isVisible import androidx.lifecycle.Observer import androidx.transition.TransitionManager import butterknife.BindView -import butterknife.ButterKnife import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState @@ -210,11 +208,7 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment() { }) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.bottom_sheet_logout_and_backup, container, false) - ButterKnife.bind(this, view) - return view - } + override fun getLayoutResId() = R.layout.bottom_sheet_logout_and_backup override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val dialog = super.onCreateDialog(savedInstanceState) diff --git a/vector/src/main/res/drawable/ic_camera.xml b/vector/src/main/res/drawable/ic_camera.xml new file mode 100644 index 0000000000..af44a317b3 --- /dev/null +++ b/vector/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,22 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_check_off.xml b/vector/src/main/res/drawable/ic_check_off.xml new file mode 100644 index 0000000000..6faf291278 --- /dev/null +++ b/vector/src/main/res/drawable/ic_check_off.xml @@ -0,0 +1,20 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_check_on.xml b/vector/src/main/res/drawable/ic_check_on.xml new file mode 100644 index 0000000000..05439c4df4 --- /dev/null +++ b/vector/src/main/res/drawable/ic_check_on.xml @@ -0,0 +1,13 @@ + + + diff --git a/vector/src/main/res/layout/bottom_sheet_verification.xml b/vector/src/main/res/layout/bottom_sheet_verification.xml index 7293434f0d..4585865973 100644 --- a/vector/src/main/res/layout/bottom_sheet_verification.xml +++ b/vector/src/main/res/layout/bottom_sheet_verification.xml @@ -1,6 +1,7 @@ - + android:layout_height="wrap_content"> - + + + + - - - - - - + android:layout_marginStart="8dp" + android:layout_marginEnd="16dp" + android:layout_weight="1" + android:ellipsize="end" + android:maxLines="2" + android:textColor="?riotx_text_primary" + android:textSize="20sp" + android:textStyle="bold" + app:layout_constraintBottom_toBottomOf="@+id/verificationRequestAvatar" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/verificationRequestAvatar" + app:layout_constraintTop_toTopOf="@+id/verificationRequestAvatar" + tools:text="@string/verification_verify_user" /> + android:layout_marginTop="16dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/verificationRequestAvatar" /> + + - diff --git a/vector/src/main/res/layout/bottom_sheet_verification_child_fragment.xml b/vector/src/main/res/layout/bottom_sheet_verification_child_fragment.xml new file mode 100644 index 0000000000..7375e505c7 --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_verification_child_fragment.xml @@ -0,0 +1,10 @@ + + diff --git a/vector/src/main/res/layout/fragment_bottom_sas_verification_code.xml b/vector/src/main/res/layout/fragment_bottom_sas_verification_code.xml deleted file mode 100644 index 871e28b536..0000000000 --- a/vector/src/main/res/layout/fragment_bottom_sas_verification_code.xml +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_sas_verification_display_code.xml b/vector/src/main/res/layout/fragment_sas_verification_display_code.xml deleted file mode 100644 index 2d3a3135cb..0000000000 --- a/vector/src/main/res/layout/fragment_sas_verification_display_code.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_sas_verification_incoming_request.xml b/vector/src/main/res/layout/fragment_sas_verification_incoming_request.xml deleted file mode 100644 index 67815d3e65..0000000000 --- a/vector/src/main/res/layout/fragment_sas_verification_incoming_request.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_sas_verification_start.xml b/vector/src/main/res/layout/fragment_sas_verification_start.xml deleted file mode 100644 index edc7e8f7e3..0000000000 --- a/vector/src/main/res/layout/fragment_sas_verification_start.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_sas_verification_verified.xml b/vector/src/main/res/layout/fragment_sas_verification_verified.xml deleted file mode 100644 index 170f977620..0000000000 --- a/vector/src/main/res/layout/fragment_sas_verification_verified.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_verification_choose_method.xml b/vector/src/main/res/layout/fragment_verification_choose_method.xml deleted file mode 100644 index 37b3c6e53a..0000000000 --- a/vector/src/main/res/layout/fragment_verification_choose_method.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_verification_conclusion.xml b/vector/src/main/res/layout/fragment_verification_conclusion.xml deleted file mode 100644 index 0c5ab7e6c2..0000000000 --- a/vector/src/main/res/layout/fragment_verification_conclusion.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_verification_request.xml b/vector/src/main/res/layout/fragment_verification_request.xml deleted file mode 100644 index 79c81eb399..0000000000 --- a/vector/src/main/res/layout/fragment_verification_request.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - diff --git a/vector/src/main/res/layout/item_verification_action.xml b/vector/src/main/res/layout/item_verification_action.xml new file mode 100644 index 0000000000..a92379a2a6 --- /dev/null +++ b/vector/src/main/res/layout/item_verification_action.xml @@ -0,0 +1,57 @@ + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_verification_big_image.xml b/vector/src/main/res/layout/item_verification_big_image.xml new file mode 100644 index 0000000000..9f33b6c03c --- /dev/null +++ b/vector/src/main/res/layout/item_verification_big_image.xml @@ -0,0 +1,7 @@ + + diff --git a/vector/src/main/res/layout/item_verification_decimal_code.xml b/vector/src/main/res/layout/item_verification_decimal_code.xml new file mode 100644 index 0000000000..e7e2c503cb --- /dev/null +++ b/vector/src/main/res/layout/item_verification_decimal_code.xml @@ -0,0 +1,18 @@ + + diff --git a/vector/src/main/res/layout/item_verification_emojis.xml b/vector/src/main/res/layout/item_verification_emojis.xml new file mode 100644 index 0000000000..4e05e6207d --- /dev/null +++ b/vector/src/main/res/layout/item_verification_emojis.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_verification_notice.xml b/vector/src/main/res/layout/item_verification_notice.xml new file mode 100644 index 0000000000..bb495f88b1 --- /dev/null +++ b/vector/src/main/res/layout/item_verification_notice.xml @@ -0,0 +1,13 @@ + + diff --git a/vector/src/main/res/layout/item_verification_waiting.xml b/vector/src/main/res/layout/item_verification_waiting.xml new file mode 100644 index 0000000000..bcf99e6894 --- /dev/null +++ b/vector/src/main/res/layout/item_verification_waiting.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 4eb286ceaa..444d5ceb92 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -35,18 +35,20 @@ You - Verify by scanning - - Ask the other user to scan this code, or %s to scan theirs - - open your camera + Scan the code with the other user\'s device to securely verify each other + Scan their code + Can\'t scan + If you\'re not in person, compare emoji instead + + Continue Verify by Emoji If you can’t scan the code above, verify by comparing a short, unique selection of emoji. - QR code image + QR code image - Verify %s + Verify %s + Verified %s Waiting for %s… For extra security, verify %s by checking a one-time code on both your devices.\n\nFor maximum security, do this in person. Messages in this room are not end-to-end encrypted. @@ -85,4 +87,12 @@ Message editor + + For extra security, verify %s by checking a one-time code. + For maximum security, do this in person. + + Compare the unique emoji, ensuring they appear in the same order. + Compare the code with the one displayed on the other user\'s screen. + Messages with this user are end-to-end encrypted and can\'t be read by third parties. +