Merge pull request #811 from vector-im/verification_toasters

[Cross Signing DM Verif]  Basic Incoming request toast + cleaning
This commit is contained in:
Valere 2020-01-21 10:21:24 +01:00 committed by GitHub
commit ea9166e0c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
100 changed files with 2071 additions and 2609 deletions

View file

@ -1,5 +1,6 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<state> <state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" /> <option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state> </state>
</component> </component>

View file

@ -44,22 +44,18 @@ interface SasVerificationService {
fun getExistingVerificationRequest(otherUser: String, tid: String?): PendingVerificationRequest? fun getExistingVerificationRequest(otherUser: String, tid: String?): PendingVerificationRequest?
/** fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest?
* Shortcut for KeyVerificationStart.VERIF_METHOD_SAS
* @see beginKeyVerification fun beginKeyVerification(method: VerificationMethod, userId: String, deviceID: String): String?
*/
fun beginKeyVerificationSAS(userId: String, deviceID: String): String?
/** /**
* Request a key verification from another user using toDevice events. * Request a key verification from another user using toDevice events.
*/ */
fun beginKeyVerification(method: String, userId: String, deviceID: String): String? fun requestKeyVerificationInDMs(methods: List<VerificationMethod>, userId: String, roomId: String): PendingVerificationRequest
fun requestKeyVerificationInDMs(userId: String, roomId: String): PendingVerificationRequest
fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String)
fun beginKeyVerificationInDMs(method: String, fun beginKeyVerificationInDMs(method: VerificationMethod,
transactionId: String, transactionId: String,
roomId: String, roomId: String,
otherUserId: String, otherUserId: String,
@ -67,7 +63,7 @@ interface SasVerificationService {
callback: MatrixCallback<String>?): String? callback: MatrixCallback<String>?): String?
/** /**
* Returns false if the request is unknwown * Returns false if the request is unknown
*/ */
fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String): Boolean fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String): Boolean
@ -84,14 +80,14 @@ interface SasVerificationService {
companion object { companion object {
private const val TEN_MINTUTES_IN_MILLIS = 10 * 60 * 1000 private const val TEN_MINUTES_IN_MILLIS = 10 * 60 * 1000
private const val FIVE_MINTUTES_IN_MILLIS = 5 * 60 * 1000 private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000
fun isValidRequest(age: Long?): Boolean { fun isValidRequest(age: Long?): Boolean {
if (age == null) return false if (age == null) return false
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val tooInThePast = now - TEN_MINTUTES_IN_MILLIS val tooInThePast = now - TEN_MINUTES_IN_MILLIS
val tooInTheFuture = now + FIVE_MINTUTES_IN_MILLIS val tooInTheFuture = now + FIVE_MINUTES_IN_MILLIS
return age in tooInThePast..tooInTheFuture return age in tooInThePast..tooInTheFuture
} }
} }

View file

@ -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
}

View file

@ -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.crypto.sas.SasMode
import im.vector.matrix.android.api.session.events.model.toContent 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.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.SASVerificationTransaction
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
import im.vector.matrix.android.internal.util.JsonCanonicalizer import im.vector.matrix.android.internal.util.JsonCanonicalizer
@ -45,12 +45,11 @@ internal data class MessageVerificationStartContent(
get() = relatesTo?.eventId get() = relatesTo?.eventId
override fun isValid(): Boolean { override fun isValid(): Boolean {
if ( if (transactionID.isNullOrBlank()
(transactionID.isNullOrBlank() || fromDevice.isNullOrBlank()
|| fromDevice.isNullOrBlank() || method !in supportedVerificationMethods
|| method != KeyVerificationStart.VERIF_METHOD_SAS || keyAgreementProtocols.isNullOrEmpty()
|| keyAgreementProtocols.isNullOrEmpty() || hashes.isNullOrEmpty()
|| hashes.isNullOrEmpty())
|| !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty() || !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty()
|| (!messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256) || (!messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256)
&& !messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) && !messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF))

View file

@ -25,8 +25,7 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRea
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class KeyVerificationReady( internal data class KeyVerificationReady(
@Json(name = "from_device") override val fromDevice: String?, @Json(name = "from_device") override val fromDevice: String?,
// TODO add qr? @Json(name = "methods") override val methods: List<String>?,
@Json(name = "methods") override val methods: List<String>? = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
@Json(name = "transaction_id") override var transactionID: String? = null @Json(name = "transaction_id") override var transactionID: String? = null
) : SendToDeviceObject, VerificationInfoReady { ) : SendToDeviceObject, VerificationInfoReady {

View file

@ -28,7 +28,7 @@ internal data class KeyVerificationRequest(
@Json(name = "from_device") @Json(name = "from_device")
val fromDevice: String, val fromDevice: String,
/** The verification methods supported by the sender. */ /** The verification methods supported by the sender. */
val methods: List<String> = listOf(KeyVerificationStart.VERIF_METHOD_SAS), val methods: List<String>,
/** /**
* The POSIX timestamp in milliseconds for when the request was made. * 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, * If the request is in the future by more than 5 minutes or more than 10 minutes in the past,

View file

@ -27,7 +27,7 @@ import timber.log.Timber
* Sent by Alice to initiate an interactive key verification. * Sent by Alice to initiate an interactive key verification.
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class KeyVerificationStart( internal data class KeyVerificationStart(
@Json(name = "from_device") override val fromDevice: String? = null, @Json(name = "from_device") override val fromDevice: String? = null,
override val method: String? = null, override val method: String? = null,
@Json(name = "transaction_id") override val transactionID: 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) 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 { override fun isValid(): Boolean {
if ((transactionID.isNullOrBlank() if (transactionID.isNullOrBlank()
|| fromDevice.isNullOrBlank() || fromDevice.isNullOrBlank()
|| method != VERIF_METHOD_SAS || method !in supportedVerificationMethods
|| keyAgreementProtocols.isNullOrEmpty() || keyAgreementProtocols.isNullOrEmpty()
|| hashes.isNullOrEmpty()) || hashes.isNullOrEmpty()
|| !hashes.contains("sha256") || !hashes.contains("sha256")
|| messageAuthenticationCodes.isNullOrEmpty() || messageAuthenticationCodes.isNullOrEmpty()
|| (!messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256) || (!messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256)
&& !messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) && !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") Timber.e("## received invalid verification request")
return false return false
} }

View file

@ -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)

View file

@ -104,6 +104,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
// The verification is started from another device // The verification is started from another device
Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ")
it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
params.sasVerificationService.onRoomRequestHandledByOtherDevice(event)
} }
} }
} else if (EventType.KEY_VERIFICATION_READY == event.type) { } else if (EventType.KEY_VERIFICATION_READY == event.type) {
@ -112,11 +113,13 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
// The verification is started from another device // The verification is started from another device
Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ") Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ")
it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) } 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) { } else if (EventType.KEY_VERIFICATION_CANCEL == event.type || EventType.KEY_VERIFICATION_DONE == event.type) {
event.getClearContent().toModel<MessageRelationContent>()?.relatesTo?.eventId?.let { event.getClearContent().toModel<MessageRelationContent>()?.relatesTo?.eventId?.let {
transactionsHandledByOtherDevice.remove(it) transactionsHandledByOtherDevice.remove(it)
params.sasVerificationService.onRoomRequestHandledByOtherDevice(event)
} }
} }

View file

@ -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.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest 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.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.api.session.events.model.EventType
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction 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 im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import timber.log.Timber import timber.log.Timber
@ -71,7 +72,7 @@ internal class DefaultOutgoingSASVerificationRequest(
cancel(CancelCode.UnexpectedMessage) cancel(CancelCode.UnexpectedMessage)
} }
fun start() { fun start(method: VerificationMethod) {
if (state != SasVerificationTxState.None) { if (state != SasVerificationTxState.None) {
Timber.e("## SAS O: start verification from invalid state") Timber.e("## SAS O: start verification from invalid state")
// should I cancel?? // should I cancel??
@ -80,7 +81,7 @@ internal class DefaultOutgoingSASVerificationRequest(
val startMessage = transport.createStart( val startMessage = transport.createStart(
credentials.deviceId ?: "", credentials.deviceId ?: "",
KeyVerificationStart.VERIF_METHOD_SAS, method.toValue(),
transactionId, transactionId,
KNOWN_AGREEMENT_PROTOCOLS, KNOWN_AGREEMENT_PROTOCOLS,
KNOWN_HASHES, KNOWN_HASHES,

View file

@ -22,10 +22,7 @@ import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials 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.CryptoService
import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.*
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.events.model.Event 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.EventType
import im.vector.matrix.android.api.session.events.model.LocalEcho 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<MessageRelationContent>()
?: 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}") Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}")
val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>() val requestInfo = event.getClearContent().toModel<MessageVerificationRequestContent>()
?: return ?: return
@ -245,6 +255,7 @@ internal class DefaultSasVerificationService @Inject constructor(
ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(), ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
isIncoming = true, isIncoming = true,
otherUserId = senderId, // requestInfo.toUserId, otherUserId = senderId, // requestInfo.toUserId,
roomId = event.roomId,
transactionId = event.eventId, transactionId = event.eventId,
requestInfo = requestInfo requestInfo = requestInfo
) )
@ -274,31 +285,27 @@ internal class DefaultSasVerificationService @Inject constructor(
if (startReq?.isValid()?.not() == true) { if (startReq?.isValid()?.not() == true) {
Timber.e("## received invalid verification request") Timber.e("## received invalid verification request")
if (startReq.transactionID != null) { if (startReq.transactionID != null) {
sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
?: "", event.roomId .cancelTransaction(
?: "", null).cancelTransaction( startReq.transactionID ?: "",
startReq.transactionID ?: "", otherUserId!!,
otherUserId!!, startReq.fromDevice ?: event.getSenderKey()!!,
startReq.fromDevice ?: event.getSenderKey()!!, CancelCode.UnknownMethod
CancelCode.UnknownMethod )
)
} }
return return
} }
handleStart(otherUserId, startReq as VerificationInfoStart) { handleStart(otherUserId, startReq as VerificationInfoStart) {
it.transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId it.transport = sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", it)
?: "", event.roomId
?: "", it)
}?.let { }?.let {
sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", null)
?: "", event.roomId .cancelTransaction(
?: "", null).cancelTransaction( startReq.transactionID ?: "",
startReq.transactionID ?: "", otherUserId!!,
otherUserId!!, startReq.fromDevice ?: event.getSenderKey()!!,
startReq.fromDevice ?: event.getSenderKey()!!, it
it )
)
} }
} }
@ -356,7 +363,7 @@ internal class DefaultSasVerificationService @Inject constructor(
// cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) // cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
} else { } else {
// Ok we can create // 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!!}") Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
// If there is a corresponding request, we can auto accept // If there is a corresponding request, we can auto accept
// as we are the one requesting in first place (or we accepted the request) // 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<VerificationTransaction>? { private fun getExistingTransactionsForUser(otherUser: String): Collection<VerificationTransaction>? {
synchronized(txMap) { synchronized(txMap) {
return txMap[otherUser]?.values return txMap[otherUser]?.values
@ -670,14 +687,10 @@ internal class DefaultSasVerificationService @Inject constructor(
} }
} }
override fun beginKeyVerificationSAS(userId: String, deviceID: String): String? { override fun beginKeyVerification(method: VerificationMethod, userId: String, deviceID: String): String? {
return beginKeyVerification(KeyVerificationStart.VERIF_METHOD_SAS, userId, deviceID)
}
override fun beginKeyVerification(method: String, userId: String, deviceID: String): String? {
val txID = createUniqueIDForTransaction(userId, deviceID) val txID = createUniqueIDForTransaction(userId, deviceID)
// should check if already one (and cancel it) // should check if already one (and cancel it)
if (KeyVerificationStart.VERIF_METHOD_SAS == method) { if (method == VerificationMethod.SAS) {
val tx = DefaultOutgoingSASVerificationRequest( val tx = DefaultOutgoingSASVerificationRequest(
setDeviceVerificationAction, setDeviceVerificationAction,
credentials, credentials,
@ -689,14 +702,14 @@ internal class DefaultSasVerificationService @Inject constructor(
tx.transport = sasTransportToDeviceFactory.createTransport(tx) tx.transport = sasTransportToDeviceFactory.createTransport(tx)
addTransaction(tx) addTransaction(tx)
tx.start() tx.start(method)
return txID return txID
} else { } else {
throw IllegalArgumentException("Unknown verification method") throw IllegalArgumentException("Unknown verification method")
} }
} }
override fun requestKeyVerificationInDMs(userId: String, roomId: String) override fun requestKeyVerificationInDMs(methods: List<VerificationMethod>, userId: String, roomId: String)
: PendingVerificationRequest { : PendingVerificationRequest {
Timber.i("## SAS Requesting verification to user: $userId in room $roomId") Timber.i("## SAS Requesting verification to user: $userId in room $roomId")
@ -705,8 +718,7 @@ internal class DefaultSasVerificationService @Inject constructor(
pendingRequests[userId] = it pendingRequests[userId] = it
} }
val transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId val transport = sasTransportRoomMessageFactory.createTransport(roomId, null)
?: "", roomId, null)
// Cancel existing pending requests? // Cancel existing pending requests?
requestsForUser.forEach { existingRequest -> requestsForUser.forEach { existingRequest ->
@ -723,11 +735,12 @@ internal class DefaultSasVerificationService @Inject constructor(
val verificationRequest = PendingVerificationRequest( val verificationRequest = PendingVerificationRequest(
ageLocalTs = System.currentTimeMillis(), ageLocalTs = System.currentTimeMillis(),
isIncoming = false, isIncoming = false,
roomId = roomId,
localID = localID, localID = localID,
otherUserId = userId 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 // We need to update with the syncedID
updatePendingRequest(verificationRequest.copy( updatePendingRequest(verificationRequest.copy(
transactionId = syncedId, transactionId = syncedId,
@ -742,8 +755,8 @@ internal class DefaultSasVerificationService @Inject constructor(
} }
override fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) { override fun declineVerificationRequestInDMs(otherUserId: String, otherDeviceId: String, transactionId: String, roomId: String) {
sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId sasTransportRoomMessageFactory.createTransport(roomId, null)
?: "", roomId, null).cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User) .cancelTransaction(transactionId, otherUserId, otherDeviceId, CancelCode.User)
getExistingVerificationRequest(otherUserId, transactionId)?.let { getExistingVerificationRequest(otherUserId, transactionId)?.let {
updatePendingRequest(it.copy( updatePendingRequest(it.copy(
@ -768,10 +781,13 @@ internal class DefaultSasVerificationService @Inject constructor(
dispatchRequestUpdated(updated) dispatchRequestUpdated(updated)
} }
override fun beginKeyVerificationInDMs(method: String, transactionId: String, roomId: String, override fun beginKeyVerificationInDMs(method: VerificationMethod,
otherUserId: String, otherDeviceId: String, transactionId: String,
roomId: String,
otherUserId: String,
otherDeviceId: String,
callback: MatrixCallback<String>?): String? { callback: MatrixCallback<String>?): String? {
if (KeyVerificationStart.VERIF_METHOD_SAS == method) { if (method == VerificationMethod.SAS) {
val tx = DefaultOutgoingSASVerificationRequest( val tx = DefaultOutgoingSASVerificationRequest(
setDeviceVerificationAction, setDeviceVerificationAction,
credentials, credentials,
@ -780,11 +796,10 @@ internal class DefaultSasVerificationService @Inject constructor(
transactionId, transactionId,
otherUserId, otherUserId,
otherDeviceId) otherDeviceId)
tx.transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId tx.transport = sasTransportRoomMessageFactory.createTransport(roomId, tx)
?: "", roomId, tx)
addTransaction(tx) addTransaction(tx)
tx.start() tx.start(method)
return transactionId return transactionId
} else { } else {
throw IllegalArgumentException("Unknown verification method") throw IllegalArgumentException("Unknown verification method")
@ -797,9 +812,8 @@ internal class DefaultSasVerificationService @Inject constructor(
val existingRequest = getExistingVerificationRequest(otherUserId, transactionId) val existingRequest = getExistingVerificationRequest(otherUserId, transactionId)
if (existingRequest != null) { if (existingRequest != null) {
// we need to send a ready event, with matching methods // we need to send a ready event, with matching methods
val transport = sasTransportRoomMessageFactory.createTransport(credentials.userId, credentials.deviceId val transport = sasTransportRoomMessageFactory.createTransport(roomId, null)
?: "", roomId, null) val methods = existingRequest.requestInfo?.methods?.intersect(supportedVerificationMethods)?.toList()
val methods = existingRequest.requestInfo?.methods?.intersect(listOf(KeyVerificationStart.VERIF_METHOD_SAS))?.toList()
if (methods.isNullOrEmpty()) { if (methods.isNullOrEmpty()) {
Timber.i("Cannot ready this request, no common methods found txId:$transactionId") Timber.i("Cannot ready this request, no common methods found txId:$transactionId")
// TODO buttons should not be shown in this case? // TODO buttons should not be shown in this case?

View file

@ -16,27 +16,38 @@
package im.vector.matrix.android.internal.crypto.verification 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.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.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.* import java.util.*
/** /**
* Stores current pending verification requests * Stores current pending verification requests
*/ */
data class PendingVerificationRequest( data class PendingVerificationRequest(
val ageLocalTs : Long, val ageLocalTs: Long,
val isIncoming: Boolean = false, val isIncoming: Boolean = false,
val localID: String = UUID.randomUUID().toString(), val localID: String = UUID.randomUUID().toString(),
val otherUserId: String, val otherUserId: String,
val roomId: String?,
val transactionId: String? = null, val transactionId: String? = null,
val requestInfo: MessageVerificationRequestContent? = null, val requestInfo: MessageVerificationRequestContent? = null,
val readyInfo: VerificationInfoReady? = null, val readyInfo: VerificationInfoReady? = null,
val cancelConclusion: CancelCode? = null, val cancelConclusion: CancelCode? = null,
val isSuccessful : Boolean = false val isSuccessful: Boolean = false,
val handledByOtherSession: Boolean = false
) { ) {
val isReady: Boolean = readyInfo != null val isReady: Boolean = readyInfo != null
val isSent: Boolean = transactionId != null val isSent: Boolean = transactionId != null
val isFinished: Boolean = isSuccessful || cancelConclusion != 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)
}
}
} }

View file

@ -34,9 +34,16 @@ internal interface SasTransport {
onErrorReason: CancelCode, onErrorReason: CancelCode,
onDone: (() -> Unit)?) onDone: (() -> Unit)?)
fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, callback: (String?, MessageVerificationRequestContent?) -> Unit) fun sendVerificationRequest(supportedMethods: List<String>,
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) fun done(transactionId: String)
/** /**
@ -58,7 +65,7 @@ internal interface SasTransport {
keyAgreementProtocols: List<String>, keyAgreementProtocols: List<String>,
hashes: List<String>, hashes: List<String>,
messageAuthenticationCodes: List<String>, messageAuthenticationCodes: List<String>,
shortAuthenticationStrings: List<String>) : VerificationInfoStart shortAuthenticationStrings: List<String>): VerificationInfoStart
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac

View file

@ -15,7 +15,6 @@
*/ */
package im.vector.matrix.android.internal.crypto.verification package im.vector.matrix.android.internal.crypto.verification
import android.content.Context
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.work.* import androidx.work.*
import com.zhuinden.monarchy.Monarchy 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.events.model.*
import im.vector.matrix.android.api.session.room.model.message.* 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.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.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 im.vector.matrix.android.internal.worker.WorkerParamsFactory
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -38,9 +40,11 @@ import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
internal class SasTransportRoomMessage( 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 userId: String,
private val userDevice: String, private val userDeviceId: String?,
private val roomId: String, private val roomId: String,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val localEchoEventFactory: LocalEchoEventFactory, private val localEchoEventFactory: LocalEchoEventFactory,
@ -61,7 +65,7 @@ internal class SasTransportRoomMessage(
) )
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
userId = userId, sessionId = sessionId,
event = event event = event
)) ))
val enqueueInfo = enqueueSendWork(workerParams) val enqueueInfo = enqueueSendWork(workerParams)
@ -84,7 +88,8 @@ internal class SasTransportRoomMessage(
// } // }
// }, listenerExecutor) // }, listenerExecutor)
val workLiveData = WorkManager.getInstance(context).getWorkInfosForUniqueWorkLiveData("${roomId}_VerificationWork") val workLiveData = workManagerProvider.workManager
.getWorkInfosForUniqueWorkLiveData("${roomId}_VerificationWork")
val observer = object : Observer<List<WorkInfo>> { val observer = object : Observer<List<WorkInfo>> {
override fun onChanged(workInfoList: List<WorkInfo>?) { override fun onChanged(workInfoList: List<WorkInfo>?) {
@ -113,13 +118,16 @@ internal class SasTransportRoomMessage(
} }
} }
override fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, override fun sendVerificationRequest(supportedMethods: List<String>,
localID: String,
otherUserId: String,
roomId: String,
callback: (String?, MessageVerificationRequestContent?) -> Unit) { callback: (String?, MessageVerificationRequestContent?) -> Unit) {
val info = MessageVerificationRequestContent( val info = MessageVerificationRequestContent(
body = context.getString(R.string.key_verification_request_fallback_message, userId), body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId),
fromDevice = userDevice, fromDevice = userDeviceId ?: "",
toUserId = otherUserId, toUserId = otherUserId,
methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS) methods = supportedMethods
) )
val content = info.toContent() val content = info.toContent()
@ -131,24 +139,25 @@ internal class SasTransportRoomMessage(
) )
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
userId = userId, sessionId = sessionId,
event = event event = event
)) ))
val workRequest = WorkManagerUtil.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>() val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerProvider.workConstraints)
.setInputData(workerParams) .setInputData(workerParams)
.setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS) .setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS)
.build() .build()
WorkManager.getInstance(context) workManagerProvider.workManager
.beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND, workRequest) .beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND, workRequest)
.enqueue() .enqueue()
// I cannot just listen to the given work request, because when used in a uniqueWork, // I cannot just listen to the given work request, because when used in a uniqueWork,
// The callback is called while it is still Running ... // 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<List<WorkInfo>> { val observer = object : Observer<List<WorkInfo>> {
override fun onChanged(workInfoList: List<WorkInfo>?) { override fun onChanged(workInfoList: List<WorkInfo>?) {
@ -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") Timber.d("## SAS canceling transaction $transactionId for reason $code")
val event = createEventAndLocalEcho( val event = createEventAndLocalEcho(
type = EventType.KEY_VERIFICATION_CANCEL, type = EventType.KEY_VERIFICATION_CANCEL,
@ -182,7 +191,7 @@ internal class SasTransportRoomMessage(
content = MessageVerificationCancelContent.create(transactionId, code).toContent() content = MessageVerificationCancelContent.create(transactionId, code).toContent()
) )
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
userId = this.userId, sessionId = sessionId,
event = event event = event
)) ))
enqueueSendWork(workerParams) enqueueSendWork(workerParams)
@ -200,19 +209,19 @@ internal class SasTransportRoomMessage(
).toContent() ).toContent()
) )
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params( val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
userId = userId, sessionId = sessionId,
event = event event = event
)) ))
enqueueSendWork(workerParams) enqueueSendWork(workerParams)
} }
private fun enqueueSendWork(workerParams: Data): Pair<Operation, UUID> { private fun enqueueSendWork(workerParams: Data): Pair<Operation, UUID> {
val workRequest = WorkManagerUtil.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>() val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerProvider.workConstraints)
.setInputData(workerParams) .setInputData(workerParams)
.setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS) .setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS)
.build() .build()
return WorkManager.getInstance(context) return workManagerProvider.workManager
.beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND, workRequest) .beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND, workRequest)
.enqueue() to workRequest.id .enqueue() to workRequest.id
} }
@ -284,12 +293,18 @@ internal class SasTransportRoomMessage(
} }
internal class SasTransportRoomMessageFactory @Inject constructor( internal class SasTransportRoomMessageFactory @Inject constructor(
private val context: Context, private val workManagerProvider: WorkManagerProvider,
private val stringProvider: StringProvider,
private val monarchy: Monarchy, private val monarchy: Monarchy,
@SessionId
private val sessionId: String,
@UserId
private val userId: String,
@DeviceId
private val deviceId: String?,
private val localEchoEventFactory: LocalEchoEventFactory) { private val localEchoEventFactory: LocalEchoEventFactory) {
fun createTransport(userId: String, userDevice: String, roomId: String, tx: SASVerificationTransaction? fun createTransport(roomId: String, tx: SASVerificationTransaction?): SasTransportRoomMessage {
): SasTransportRoomMessage { return SasTransportRoomMessage(workManagerProvider, stringProvider, sessionId, userId, deviceId, roomId, monarchy, localEchoEventFactory, tx)
return SasTransportRoomMessage(context, userId, userDevice, roomId, monarchy, localEchoEventFactory, tx)
} }
} }

View file

@ -34,7 +34,10 @@ internal class SasTransportToDevice(
private var taskExecutor: TaskExecutor private var taskExecutor: TaskExecutor
) : SasTransport { ) : SasTransport {
override fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String, override fun sendVerificationRequest(supportedMethods: List<String>,
localID: String,
otherUserId: String,
roomId: String,
callback: (String?, MessageVerificationRequestContent?) -> Unit) { callback: (String?, MessageVerificationRequestContent?) -> Unit) {
// TODO "not implemented" // TODO "not implemented"
} }
@ -78,11 +81,11 @@ internal class SasTransportToDevice(
// To device do not do anything here // 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") Timber.d("## SAS canceling transaction $transactionId for reason $code")
val cancelMessage = KeyVerificationCancel.create(transactionId, code) val cancelMessage = KeyVerificationCancel.create(transactionId, code)
val contentMap = MXUsersDevicesMap<Any>() val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(otherUserId, otherUserDevice, cancelMessage) contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage)
sendToDeviceTask sendToDeviceTask
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) {
this.callback = object : MatrixCallback<Unit> { this.callback = object : MatrixCallback<Unit> {

View file

@ -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.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.Event 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.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.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent import im.vector.matrix.android.internal.worker.getSessionComponent
import timber.log.Timber import timber.log.Timber
@ -34,9 +35,10 @@ internal class SendVerificationMessageWorker constructor(context: Context, param
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal data class Params(
val userId: String, override val sessionId: String,
val event: Event val event: Event,
) override val lastFailureMessage: String? = null
) : SessionWorkerParams
@Inject @Inject
lateinit var sendVerificationMessageTask: SendVerificationMessageTask lateinit var sendVerificationMessageTask: SendVerificationMessageTask
@ -49,10 +51,10 @@ internal class SendVerificationMessageWorker constructor(context: Context, param
val params = WorkerParamsFactory.fromData<Params>(inputData) val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success(errorOutputData) ?: return Result.success(errorOutputData)
val sessionComponent = getSessionComponent(params.userId) val sessionComponent = getSessionComponent(params.sessionId)
?: return Result.success(errorOutputData).also { ?: return Result.success(errorOutputData).also {
// TODO, can this happen? should I update local echo? // 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) sessionComponent.inject(this)
val localId = params.event.eventId ?: "" val localId = params.event.eventId ?: ""

View file

@ -26,7 +26,7 @@ import javax.inject.Qualifier
internal annotation class UserId internal annotation class UserId
/** /**
* Used to inject the userId * Used to inject the deviceId
*/ */
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)

View file

@ -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 <reified W : ListenableWorker> matrixOneTimeWorkRequestBuilder() =
OneTimeWorkRequestBuilder<W>()
.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()
}
}

View file

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.session package im.vector.matrix.android.internal.session
import android.content.Context
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import dagger.Lazy 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.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.di.SessionId 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.SyncTaskSequencer
import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.job.SyncThread import im.vector.matrix.android.internal.session.sync.job.SyncThread
@ -63,7 +63,7 @@ import javax.inject.Provider
@SessionScope @SessionScope
internal class DefaultSession @Inject constructor( internal class DefaultSession @Inject constructor(
override val sessionParams: SessionParams, override val sessionParams: SessionParams,
private val context: Context, private val workManagerProvider: WorkManagerProvider,
private val eventBus: EventBus, private val eventBus: EventBus,
@SessionId @SessionId
override val sessionId: String, override val sessionId: String,
@ -122,15 +122,15 @@ internal class DefaultSession @Inject constructor(
} }
override fun requireBackgroundSync() { override fun requireBackgroundSync() {
SyncWorker.requireBackgroundSync(context, sessionId) SyncWorker.requireBackgroundSync(workManagerProvider, sessionId)
} }
override fun startAutomaticBackgroundSync(repeatDelay: Long) { override fun startAutomaticBackgroundSync(repeatDelay: Long) {
SyncWorker.automaticallyBackgroundSync(context, sessionId, 0, repeatDelay) SyncWorker.automaticallyBackgroundSync(workManagerProvider, sessionId, 0, repeatDelay)
} }
override fun stopAnyBackgroundSync() { override fun stopAnyBackgroundSync() {
SyncWorker.stopAnyBackgroundSync(context) SyncWorker.stopAnyBackgroundSync(workManagerProvider)
} }
override fun startSync(fromForeground: Boolean) { override fun startSync(fromForeground: Boolean) {

View file

@ -16,9 +16,7 @@
package im.vector.matrix.android.internal.session.group package im.vector.matrix.android.internal.session.group
import android.content.Context
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver 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.model.GroupSummaryEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.di.WorkManagerProvider
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import io.realm.OrderedCollectionChangeSet import io.realm.OrderedCollectionChangeSet
import io.realm.RealmResults import io.realm.RealmResults
@ -38,7 +35,7 @@ import javax.inject.Inject
private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
internal class GroupSummaryUpdater @Inject constructor( internal class GroupSummaryUpdater @Inject constructor(
private val context: Context, private val workManagerProvider: WorkManagerProvider,
@SessionId private val sessionId: String, @SessionId private val sessionId: String,
private val monarchy: Monarchy) private val monarchy: Monarchy)
: RealmLiveEntityObserver<GroupEntity>(monarchy.realmConfiguration) { : RealmLiveEntityObserver<GroupEntity>(monarchy.realmConfiguration) {
@ -72,12 +69,12 @@ internal class GroupSummaryUpdater @Inject constructor(
val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams) val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams)
val sendWork = matrixOneTimeWorkRequestBuilder<GetGroupDataWorker>() val sendWork = workManagerProvider.matrixOneTimeWorkRequestBuilder<GetGroupDataWorker>()
.setInputData(workData) .setInputData(workData)
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerProvider.workConstraints)
.build() .build()
WorkManager.getInstance(context) workManagerProvider.workManager
.beginUniqueWork(GET_GROUP_DATA_WORKER, ExistingWorkPolicy.APPEND, sendWork) .beginUniqueWork(GET_GROUP_DATA_WORKER, ExistingWorkPolicy.APPEND, sendWork)
.enqueue() .enqueue()
} }

View file

@ -15,10 +15,8 @@
*/ */
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers
import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.work.BackoffPolicy import androidx.work.BackoffPolicy
import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.pushers.Pusher 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.model.PusherEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionId 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.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith 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 im.vector.matrix.android.internal.worker.WorkerParamsFactory
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
internal class DefaultPusherService @Inject constructor( internal class DefaultPusherService @Inject constructor(
private val context: Context, private val workManagerProvider: WorkManagerProvider,
private val monarchy: Monarchy, private val monarchy: Monarchy,
@SessionId private val sessionId: String, @SessionId private val sessionId: String,
private val getPusherTask: GetPushersTask, private val getPusherTask: GetPushersTask,
@ -68,12 +65,12 @@ internal class DefaultPusherService @Inject constructor(
val params = AddHttpPusherWorker.Params(sessionId, pusher) val params = AddHttpPusherWorker.Params(sessionId, pusher)
val request = matrixOneTimeWorkRequestBuilder<AddHttpPusherWorker>() val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddHttpPusherWorker>()
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerProvider.workConstraints)
.setInputData(WorkerParamsFactory.toData(params)) .setInputData(WorkerParamsFactory.toData(params))
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS) .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS)
.build() .build()
WorkManager.getInstance(context).enqueue(request) workManagerProvider.workManager.enqueue(request)
return request.id return request.id
} }

View file

@ -15,7 +15,6 @@
*/ */
package im.vector.matrix.android.internal.session.room.relation package im.vector.matrix.android.internal.session.room.relation
import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import androidx.work.OneTimeWorkRequest 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.session.room.timeline.TimelineSendEventWorkCommon
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith 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.util.fetchCopyMap
import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import timber.log.Timber import timber.log.Timber
internal class DefaultRelationService @AssistedInject constructor( internal class DefaultRelationService @AssistedInject constructor(
@Assisted private val roomId: String, @Assisted private val roomId: String,
private val context: Context,
@SessionId private val sessionId: String, @SessionId private val sessionId: String,
private val timeLineSendEventWorkCommon: TimelineSendEventWorkCommon,
private val eventFactory: LocalEchoEventFactory, private val eventFactory: LocalEchoEventFactory,
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
private val findReactionEventForUndoTask: FindReactionEventForUndoTask, private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
@ -85,8 +83,7 @@ internal class DefaultRelationService @AssistedInject constructor(
val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction) val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction)
.also { saveLocalEcho(it) } .also { saveLocalEcho(it) }
val sendRelationWork = createSendEventWork(event, true) val sendRelationWork = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, sendRelationWork) timeLineSendEventWorkCommon.postWork(roomId, sendRelationWork)
CancelableWork(context, sendRelationWork.id)
} else { } else {
Timber.w("Reaction already added") Timber.w("Reaction already added")
NoOpCancellable NoOpCancellable
@ -111,7 +108,7 @@ internal class DefaultRelationService @AssistedInject constructor(
.also { saveLocalEcho(it) } .also { saveLocalEcho(it) }
val redactWork = createRedactEventWork(redactEvent, toRedact, null) 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, eventId,
reason) reason)
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true) return timeLineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
} }
override fun editTextMessage(targetEventId: String, override fun editTextMessage(targetEventId: String,
@ -146,12 +143,10 @@ internal class DefaultRelationService @AssistedInject constructor(
return if (cryptoService.isRoomEncrypted(roomId)) { return if (cryptoService.isRoomEncrypted(roomId)) {
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
val workRequest = createSendEventWork(event, false) val workRequest = createSendEventWork(event, false)
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) timeLineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, workRequest)
CancelableWork(context, encryptWork.id)
} else { } else {
val workRequest = createSendEventWork(event, true) val workRequest = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) timeLineSendEventWorkCommon.postWork(roomId, workRequest)
CancelableWork(context, workRequest.id)
} }
} }
@ -168,12 +163,10 @@ internal class DefaultRelationService @AssistedInject constructor(
return if (cryptoService.isRoomEncrypted(roomId)) { return if (cryptoService.isRoomEncrypted(roomId)) {
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
val workRequest = createSendEventWork(event, false) val workRequest = createSendEventWork(event, false)
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) timeLineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, workRequest)
CancelableWork(context, encryptWork.id)
} else { } else {
val workRequest = createSendEventWork(event, true) val workRequest = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) timeLineSendEventWorkCommon.postWork(roomId, workRequest)
CancelableWork(context, workRequest.id)
} }
} }
@ -194,12 +187,10 @@ internal class DefaultRelationService @AssistedInject constructor(
return if (cryptoService.isRoomEncrypted(roomId)) { return if (cryptoService.isRoomEncrypted(roomId)) {
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to")) val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
val workRequest = createSendEventWork(event, false) val workRequest = createSendEventWork(event, false)
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest) timeLineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, workRequest)
CancelableWork(context, encryptWork.id)
} else { } else {
val workRequest = createSendEventWork(event, true) val workRequest = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) timeLineSendEventWorkCommon.postWork(roomId, workRequest)
CancelableWork(context, workRequest.id)
} }
} }
@ -207,13 +198,13 @@ internal class DefaultRelationService @AssistedInject constructor(
// Same parameter // Same parameter
val params = EncryptEventWorker.Params(sessionId, roomId, event, keepKeys) val params = EncryptEventWorker.Params(sessionId, roomId, event, keepKeys)
val sendWorkData = WorkerParamsFactory.toData(params) val sendWorkData = WorkerParamsFactory.toData(params)
return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true) return timeLineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
} }
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest { private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event) val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain) return timeLineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
} }
override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? { override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? {

View file

@ -16,8 +16,10 @@
package im.vector.matrix.android.internal.session.room.send package im.vector.matrix.android.internal.session.room.send
import android.content.Context import androidx.work.BackoffPolicy
import androidx.work.* import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.Operation
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy 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.findAllInRoomWithSendStates
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionId 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.content.UploadContentWorker
import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon
import im.vector.matrix.android.internal.util.CancelableWork import im.vector.matrix.android.internal.util.CancelableWork
import im.vector.matrix.android.internal.worker.AlwaysSuccessfulWorker 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.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.startChain import im.vector.matrix.android.internal.worker.startChain
import timber.log.Timber import timber.log.Timber
@ -55,7 +56,8 @@ private const val BACKOFF_DELAY = 10_000L
internal class DefaultSendService @AssistedInject constructor( internal class DefaultSendService @AssistedInject constructor(
@Assisted private val roomId: String, @Assisted private val roomId: String,
private val context: Context, private val workManagerProvider: WorkManagerProvider,
private val timelineSendEventWorkCommon: TimelineSendEventWorkCommon,
@SessionId private val sessionId: String, @SessionId private val sessionId: String,
private val localEchoEventFactory: LocalEchoEventFactory, private val localEchoEventFactory: LocalEchoEventFactory,
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
@ -91,12 +93,10 @@ internal class DefaultSendService @AssistedInject constructor(
Timber.v("Send event in encrypted room") Timber.v("Send event in encrypted room")
val encryptWork = createEncryptEventWork(event, true) val encryptWork = createEncryptEventWork(event, true)
val sendWork = createSendEventWork(event, false) val sendWork = createSendEventWork(event, false)
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, sendWork) timelineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, sendWork)
CancelableWork(context, encryptWork.id)
} else { } else {
val sendWork = createSendEventWork(event, true) val sendWork = createSendEventWork(event, true)
TimelineSendEventWorkCommon.postWork(context, roomId, sendWork) timelineSendEventWorkCommon.postWork(roomId, sendWork)
CancelableWork(context, sendWork.id)
} }
} }
@ -109,8 +109,7 @@ internal class DefaultSendService @AssistedInject constructor(
override fun redactEvent(event: Event, reason: String?): Cancelable { override fun redactEvent(event: Event, reason: String?): Cancelable {
// TODO manage media/attachements? // TODO manage media/attachements?
val redactWork = createRedactEventWork(event, reason) val redactWork = createRedactEventWork(event, reason)
TimelineSendEventWorkCommon.postWork(context, roomId, redactWork) return timelineSendEventWorkCommon.postWork(roomId, redactWork)
return CancelableWork(context, redactWork.id)
} }
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? { override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? {
@ -168,16 +167,16 @@ internal class DefaultSendService @AssistedInject constructor(
} }
override fun clearSendingQueue() { override fun clearSendingQueue() {
TimelineSendEventWorkCommon.cancelAllWorks(context, roomId) timelineSendEventWorkCommon.cancelAllWorks(roomId)
WorkManager.getInstance(context).cancelUniqueWork(buildWorkName(UPLOAD_WORK)) workManagerProvider.workManager.cancelUniqueWork(buildWorkName(UPLOAD_WORK))
// Replace the worker chains with a AlwaysSuccessfulWorker, to ensure the queues are well emptied // Replace the worker chains with a AlwaysSuccessfulWorker, to ensure the queues are well emptied
matrixOneTimeWorkRequestBuilder<AlwaysSuccessfulWorker>() workManagerProvider.matrixOneTimeWorkRequestBuilder<AlwaysSuccessfulWorker>()
.build().let { .build().let {
TimelineSendEventWorkCommon.postWork(context, roomId, it, ExistingWorkPolicy.REPLACE) timelineSendEventWorkCommon.postWork(roomId, it, ExistingWorkPolicy.REPLACE)
// need to clear also image sending queue // need to clear also image sending queue
WorkManager.getInstance(context) workManagerProvider.workManager
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.REPLACE, it) .beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.REPLACE, it)
.enqueue() .enqueue()
} }
@ -245,7 +244,7 @@ internal class DefaultSendService @AssistedInject constructor(
return internalSendMedia(event, attachment) 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 isRoomEncrypted = cryptoService.isRoomEncrypted(roomId)
val uploadWork = createUploadMediaWork(localEcho, attachment, isRoomEncrypted, startChain = true) val uploadWork = createUploadMediaWork(localEcho, attachment, isRoomEncrypted, startChain = true)
@ -254,7 +253,7 @@ internal class DefaultSendService @AssistedInject constructor(
if (isRoomEncrypted) { if (isRoomEncrypted) {
val encryptWork = createEncryptEventWork(localEcho, false /*not start of chain, take input error*/) 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) .beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
.then(encryptWork) .then(encryptWork)
.then(sendWork) .then(sendWork)
@ -267,13 +266,13 @@ internal class DefaultSendService @AssistedInject constructor(
} }
}, workerFutureListenerExecutor) }, workerFutureListenerExecutor)
} else { } else {
WorkManager.getInstance(context) workManagerProvider.workManager
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork) .beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
.then(sendWork) .then(sendWork)
.enqueue() .enqueue()
} }
return CancelableWork(context, sendWork.id) return CancelableWork(workManagerProvider.workManager, sendWork.id)
} }
private fun saveLocalEcho(event: Event) { private fun saveLocalEcho(event: Event) {
@ -289,8 +288,8 @@ internal class DefaultSendService @AssistedInject constructor(
val params = EncryptEventWorker.Params(sessionId, roomId, event) val params = EncryptEventWorker.Params(sessionId, roomId, event)
val sendWorkData = WorkerParamsFactory.toData(params) val sendWorkData = WorkerParamsFactory.toData(params)
return matrixOneTimeWorkRequestBuilder<EncryptEventWorker>() return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerProvider.workConstraints)
.setInputData(sendWorkData) .setInputData(sendWorkData)
.startChain(startChain) .startChain(startChain)
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS) .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
@ -301,7 +300,7 @@ internal class DefaultSendService @AssistedInject constructor(
val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event) val sendContentWorkerParams = SendEventWorker.Params(sessionId, roomId, event)
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain) return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
} }
private fun createRedactEventWork(event: Event, reason: String?): OneTimeWorkRequest { 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 sendContentWorkerParams = RedactEventWorker.Params(sessionId, redactEvent.eventId!!, roomId, event.eventId, reason)
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
return TimelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true) return timelineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
} }
private fun createUploadMediaWork(event: Event, private fun createUploadMediaWork(event: Event,
@ -320,8 +319,8 @@ internal class DefaultSendService @AssistedInject constructor(
val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, roomId, event, attachment, isRoomEncrypted) val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, roomId, event, attachment, isRoomEncrypted)
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams) val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
return matrixOneTimeWorkRequestBuilder<UploadContentWorker>() return workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerProvider.workConstraints)
.startChain(startChain) .startChain(startChain)
.setInputData(uploadWorkData) .setInputData(uploadWorkData)
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS) .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)

View file

@ -15,51 +15,54 @@
*/ */
package im.vector.matrix.android.internal.session.room.timeline package im.vector.matrix.android.internal.session.room.timeline
import android.content.Context
import androidx.work.* import androidx.work.*
import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder 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 im.vector.matrix.android.internal.worker.startChain
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject
private const val SEND_WORK = "SEND_WORK"
private const val BACKOFF_DELAY = 10_000L
/** /**
* Helper class for sending event related works. * Helper class for sending event related works.
* All send event from a room are using the same workchain, in order to ensure order. * 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) * 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. * 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) { fun postSequentialWorks(roomId: String, vararg workRequests: OneTimeWorkRequest): Cancelable {
when { return when {
workRequests.isEmpty() -> return workRequests.isEmpty() -> NoOpCancellable
workRequests.size == 1 -> postWork(context, roomId, workRequests.first()) workRequests.size == 1 -> postWork(roomId, workRequests.first())
else -> { else -> {
val firstWork = workRequests.first() val firstWork = workRequests.first()
var continuation = WorkManager.getInstance(context) var continuation = workManagerProvider.workManager
.beginUniqueWork(buildWorkName(roomId), ExistingWorkPolicy.APPEND, firstWork) .beginUniqueWork(buildWorkName(roomId), ExistingWorkPolicy.APPEND, firstWork)
for (i in 1 until workRequests.size) { for (i in 1 until workRequests.size) {
val workRequest = workRequests[i] val workRequest = workRequests[i]
continuation = continuation.then(workRequest) continuation = continuation.then(workRequest)
} }
continuation.enqueue() continuation.enqueue()
CancelableWork(workManagerProvider.workManager, firstWork.id)
} }
} }
} }
fun postWork(context: Context, roomId: String, workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND) { fun postWork(roomId: String, workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable {
WorkManager.getInstance(context) workManagerProvider.workManager
.beginUniqueWork(buildWorkName(roomId), policy, workRequest) .beginUniqueWork(buildWorkName(roomId), policy, workRequest)
.enqueue() .enqueue()
return CancelableWork(workManagerProvider.workManager, workRequest.id)
} }
inline fun <reified W : ListenableWorker> createWork(data: Data, startChain: Boolean): OneTimeWorkRequest { inline fun <reified W : ListenableWorker> createWork(data: Data, startChain: Boolean): OneTimeWorkRequest {
return matrixOneTimeWorkRequestBuilder<W>() return workManagerProvider.matrixOneTimeWorkRequestBuilder<W>()
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerProvider.workConstraints)
.startChain(startChain) .startChain(startChain)
.setInputData(data) .setInputData(data)
.setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS) .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
@ -70,7 +73,13 @@ internal object TimelineSendEventWorkCommon {
return "${roomId}_$SEND_WORK" return "${roomId}_$SEND_WORK"
} }
fun cancelAllWorks(context: Context, roomId: String) { fun cancelAllWorks(roomId: String) {
WorkManager.getInstance(context).cancelUniqueWork(buildWorkName(roomId)) workManagerProvider.workManager
.cancelUniqueWork(buildWorkName(roomId))
}
companion object {
private const val SEND_WORK = "SEND_WORK"
private const val BACKOFF_DELAY = 10_000L
} }
} }

View file

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.session.signout package im.vector.matrix.android.internal.session.signout
import android.content.Context
import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError 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.SessionModule
import im.vector.matrix.android.internal.session.cache.ClearCacheTask import im.vector.matrix.android.internal.session.cache.ClearCacheTask
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.worker.WorkManagerUtil
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
@ -45,7 +43,7 @@ internal interface SignOutTask : Task<SignOutTask.Params, Unit> {
} }
internal class DefaultSignOutTask @Inject constructor( internal class DefaultSignOutTask @Inject constructor(
private val context: Context, private val workManagerProvider: WorkManagerProvider,
@SessionId private val sessionId: String, @SessionId private val sessionId: String,
private val signOutAPI: SignOutAPI, private val signOutAPI: SignOutAPI,
private val sessionManager: SessionManager, private val sessionManager: SessionManager,
@ -87,7 +85,7 @@ internal class DefaultSignOutTask @Inject constructor(
sessionManager.releaseSession(sessionId) sessionManager.releaseSession(sessionId)
Timber.d("SignOut: cancel pending works...") Timber.d("SignOut: cancel pending works...")
WorkManagerUtil.cancelAllWorks(context) workManagerProvider.cancelAllWorks()
Timber.d("SignOut: delete session params...") Timber.d("SignOut: delete session params...")
sessionParamsStore.delete(sessionId) sessionParamsStore.delete(sessionId)

View file

@ -16,15 +16,17 @@
package im.vector.matrix.android.internal.session.sync.job package im.vector.matrix.android.internal.session.sync.job
import android.content.Context 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 com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.failure.isTokenError 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.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.session.sync.SyncTask import im.vector.matrix.android.internal.session.sync.SyncTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.worker.SessionWorkerParams 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.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent import im.vector.matrix.android.internal.worker.getSessionComponent
import timber.log.Timber import timber.log.Timber
@ -75,30 +77,33 @@ internal class SyncWorker(context: Context,
companion object { 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 data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, false))
val workRequest = matrixOneTimeWorkRequestBuilder<SyncWorker>() val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerProvider.workConstraints)
.setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS)
.setInputData(data) .setInputData(data)
.build() .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 data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, true))
val workRequest = matrixOneTimeWorkRequestBuilder<SyncWorker>() val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerProvider.workConstraints)
.setInputData(data) .setInputData(data)
.setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS) .setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS)
.build() .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) { fun stopAnyBackgroundSync(workManagerProvider: WorkManagerProvider) {
WorkManager.getInstance(context).cancelUniqueWork(BG_SYNC_WORK_NAME) workManagerProvider.workManager
.cancelUniqueWork(BG_SYNC_WORK_NAME)
} }
} }
} }

View file

@ -16,15 +16,14 @@
package im.vector.matrix.android.internal.util package im.vector.matrix.android.internal.util
import android.content.Context
import androidx.work.WorkManager import androidx.work.WorkManager
import im.vector.matrix.android.api.util.Cancelable 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 { private val workId: UUID) : Cancelable {
override fun cancel() { override fun cancel() {
WorkManager.getInstance(context).cancelWorkById(workId) workManager.cancelWorkById(workId)
} }
} }

View file

@ -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 <reified W : ListenableWorker> matrixOneTimeWorkRequestBuilder() =
OneTimeWorkRequestBuilder<W>()
.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()
}
}
}

View file

@ -47,9 +47,6 @@
android:label="@string/title_activity_settings" android:label="@string/title_activity_settings"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity android:name=".features.media.VideoMediaViewerActivity" /> <activity android:name=".features.media.VideoMediaViewerActivity" />
<activity
android:name=".features.crypto.verification.SASVerificationActivity"
android:label="@string/title_activity_verify_device" />
<activity <activity
android:name=".features.crypto.keysbackup.restore.KeysBackupRestoreActivity" android:name=".features.crypto.keysbackup.restore.KeysBackupRestoreActivity"
android:label="@string/title_activity_keys_backup_setup" /> android:label="@string/title_activity_keys_backup_setup" />

View file

@ -23,7 +23,10 @@ import dagger.Binds
import dagger.Module import dagger.Module
import dagger.multibindings.IntoMap import dagger.multibindings.IntoMap
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment 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.HomeDetailFragment
import im.vector.riotx.features.home.HomeDrawerFragment import im.vector.riotx.features.home.HomeDrawerFragment
import im.vector.riotx.features.home.LoadingFragment import im.vector.riotx.features.home.LoadingFragment
@ -239,26 +242,6 @@ interface FragmentModule {
@FragmentKey(VectorSettingsDevicesFragment::class) @FragmentKey(VectorSettingsDevicesFragment::class)
fun bindVectorSettingsDevicesFragment(fragment: VectorSettingsDevicesFragment): Fragment 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 @Binds
@IntoMap @IntoMap
@FragmentKey(PublicRoomsFragment::class) @FragmentKey(PublicRoomsFragment::class)
@ -302,15 +285,15 @@ interface FragmentModule {
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(VerificationChooseMethodFragment::class) @FragmentKey(VerificationChooseMethodFragment::class)
fun bindVerificationMethodChooserFragment(fragment: VerificationChooseMethodFragment): Fragment fun bindVerificationChooseMethodFragment(fragment: VerificationChooseMethodFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(SASVerificationCodeFragment::class) @FragmentKey(VerificationEmojiCodeFragment::class)
fun bindVerificationSasCodeFragment(fragment: SASVerificationCodeFragment): Fragment fun bindVerificationEmojiCodeFragment(fragment: VerificationEmojiCodeFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(VerificationConclusionFragment::class) @FragmentKey(VerificationConclusionFragment::class)
fun bindVerificationSasConclusionFragment(fragment: VerificationConclusionFragment): Fragment fun bindVerificationConclusionFragment(fragment: VerificationConclusionFragment): Fragment
} }

View file

@ -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.KeysBackupRestoreFromPassphraseViewModel
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel 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.HomeSharedActionViewModel
import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel
import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel
@ -61,11 +60,6 @@ interface ViewModelModule {
@ViewModelKey(EmojiChooserViewModel::class) @ViewModelKey(EmojiChooserViewModel::class)
fun bindEmojiChooserViewModel(viewModel: EmojiChooserViewModel): ViewModel fun bindEmojiChooserViewModel(viewModel: EmojiChooserViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(SasVerificationViewModel::class)
fun bindSasVerificationViewModel(viewModel: SasVerificationViewModel): ViewModel
@Binds @Binds
@IntoMap @IntoMap
@ViewModelKey(KeysBackupRestoreFromKeyViewModel::class) @ViewModelKey(KeysBackupRestoreFromKeyViewModel::class)

View file

@ -18,6 +18,7 @@ package im.vector.riotx.core.epoxy
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R import im.vector.riotx.R
@ -33,6 +34,7 @@ abstract class ErrorWithRetryItem : VectorEpoxyModel<ErrorWithRetryItem.Holder>(
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
holder.textView.text = text holder.textView.text = text
holder.buttonView.isVisible = listener != null
holder.buttonView.setOnClickListener { listener?.invoke() } holder.buttonView.setOnClickListener { listener?.invoke() }
} }

View file

@ -28,12 +28,17 @@ fun RecyclerView.configureWith(epoxyController: EpoxyController,
itemAnimator: RecyclerView.ItemAnimator? = null, itemAnimator: RecyclerView.ItemAnimator? = null,
viewPool: RecyclerView.RecycledViewPool? = null, viewPool: RecyclerView.RecycledViewPool? = null,
showDivider: Boolean = false, showDivider: Boolean = false,
hasFixedSize: Boolean = true) { hasFixedSize: Boolean = true,
disableItemAnimation: Boolean = false) {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false).apply { layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false).apply {
recycleChildrenOnDetach = viewPool != null recycleChildrenOnDetach = viewPool != null
} }
setRecycledViewPool(viewPool) setRecycledViewPool(viewPool)
itemAnimator?.let { this.itemAnimator = it } if (disableItemAnimation) {
this.itemAnimator = null
} else {
itemAnimator?.let { this.itemAnimator = it }
}
if (showDivider) { if (showDivider) {
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
} }

View file

@ -102,9 +102,11 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
private lateinit var sessionListener: SessionListener private lateinit var sessionListener: SessionListener
protected lateinit var bugReporter: BugReporter protected lateinit var bugReporter: BugReporter
lateinit var rageShake: RageShake lateinit var rageShake: RageShake
lateinit var navigator: Navigator
private set private set
protected lateinit var navigator: Navigator
private lateinit var fragmentFactory: FragmentFactory private lateinit var fragmentFactory: FragmentFactory
private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var activeSessionHolder: ActiveSessionHolder
private lateinit var vectorPreferences: VectorPreferences private lateinit var vectorPreferences: VectorPreferences
@ -210,8 +212,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
handleInvalidToken(globalError) handleInvalidToken(globalError)
is GlobalError.ConsentNotGivenError -> is GlobalError.ConsentNotGivenError ->
consentNotGivenHelper.displayDialog(globalError.consentUri, consentNotGivenHelper.displayDialog(globalError.consentUri,
activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host
?: "") ?: "")
} }
} }

View file

@ -19,10 +19,16 @@ import android.app.Dialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import butterknife.ButterKnife
import butterknife.Unbinder
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.MvRxView import com.airbnb.mvrx.MvRxView
import com.airbnb.mvrx.MvRxViewId import com.airbnb.mvrx.MvRxViewId
@ -43,6 +49,15 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
final override val mvrxViewId: String by mvrxViewIdProperty final override val mvrxViewId: String by mvrxViewIdProperty
private lateinit var screenComponent: ScreenComponent private lateinit var screenComponent: ScreenComponent
/* ==========================================================================================
* View
* ========================================================================================== */
@LayoutRes
abstract fun getLayoutResId(): Int
private var unBinder: Unbinder? = null
/* ========================================================================================== /* ==========================================================================================
* View model * View model
* ========================================================================================== */ * ========================================================================================== */
@ -67,6 +82,18 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
open val showExpanded = false 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) { override fun onAttach(context: Context) {
screenComponent = DaggerScreenComponent.factory().create(vectorBaseActivity.getVectorComponent(), vectorBaseActivity) screenComponent = DaggerScreenComponent.factory().create(vectorBaseActivity.getVectorComponent(), vectorBaseActivity)
viewModelFactory = screenComponent.viewModelFactory() viewModelFactory = screenComponent.viewModelFactory()

View file

@ -98,10 +98,9 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
// Don't activate animation as we might have way to much item animation when filtering
recyclerView.itemAnimator = null
knownUsersController.callback = this 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() { private fun setupFilterView() {

View file

@ -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.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.features.crypto.verification.SASVerificationActivity
import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.popup.PopupAlertManager
import timber.log.Timber import timber.log.Timber
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.*
import java.util.Date
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -195,18 +193,19 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
denyAllRequests(mappingKey) denyAllRequests(mappingKey)
} }
alert.addButton( // TODO send to the new profile page
context.getString(R.string.start_verification_short_label), // alert.addButton(
Runnable { // context.getString(R.string.start_verification_short_label),
alert.weakCurrentActivity?.get()?.let { // Runnable {
val intent = SASVerificationActivity.outgoingIntent(it, // alert.weakCurrentActivity?.get()?.let {
session?.myUserId ?: "", // val intent = SASVerificationActivity.outgoingIntent(it,
userId, deviceId) // session?.myUserId ?: "",
it.startActivity(intent) // userId, deviceId)
} // it.startActivity(intent)
}, // }
false // },
) // false
// )
alert.addButton(context.getString(R.string.share_without_verifying_short_label), Runnable { alert.addButton(context.getString(R.string.share_without_verifying_short_label), Runnable {
shareAllSessions(mappingKey) shareAllSessions(mappingKey)

View file

@ -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)

View file

@ -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.SasVerificationService
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction 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.SasVerificationTxState
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
import im.vector.riotx.R 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.popup.PopupAlertManager
import im.vector.riotx.features.themes.ThemeUtils
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -48,46 +53,46 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
override fun transactionUpdated(tx: SasVerificationTransaction) { override fun transactionUpdated(tx: SasVerificationTransaction) {
when (tx.state) { when (tx.state) {
SasVerificationTxState.OnStarted -> { SasVerificationTxState.OnStarted -> {
// Add a notification for every incoming request // // Add a notification for every incoming request
val name = session?.getUser(tx.otherUserId)?.displayName // val name = session?.getUser(tx.otherUserId)?.displayName
?: tx.otherUserId // ?: tx.otherUserId
//
val alert = PopupAlertManager.VectorAlert( // val alert = PopupAlertManager.VectorAlert(
"kvr_${tx.transactionId}", // "kvr_${tx.transactionId}",
context.getString(R.string.sas_incoming_request_notif_title), // context.getString(R.string.sas_incoming_request_notif_title),
context.getString(R.string.sas_incoming_request_notif_content, name), // context.getString(R.string.sas_incoming_request_notif_content, name),
R.drawable.shield) // R.drawable.shield)
.apply { // .apply {
contentAction = Runnable { // contentAction = Runnable {
val intent = SASVerificationActivity.incomingIntent(context, // val intent = SASVerificationActivity.incomingIntent(context,
session?.myUserId ?: "", // session?.myUserId ?: "",
tx.otherUserId, // tx.otherUserId,
tx.transactionId) // tx.transactionId)
weakCurrentActivity?.get()?.startActivity(intent) // weakCurrentActivity?.get()?.startActivity(intent)
} // }
dismissedAction = Runnable { // dismissedAction = Runnable {
tx.cancel() // tx.cancel()
} // }
addButton( // addButton(
context.getString(R.string.ignore), // context.getString(R.string.ignore),
Runnable { // Runnable {
tx.cancel() // tx.cancel()
} // }
) // )
addButton( // addButton(
context.getString(R.string.action_open), // context.getString(R.string.action_open),
Runnable { // Runnable {
val intent = SASVerificationActivity.incomingIntent(context, // val intent = SASVerificationActivity.incomingIntent(context,
session?.myUserId ?: "", // session?.myUserId ?: "",
tx.otherUserId, // tx.otherUserId,
tx.transactionId) // tx.transactionId)
weakCurrentActivity?.get()?.startActivity(intent) // weakCurrentActivity?.get()?.startActivity(intent)
} // }
) // )
// 10mn expiration // // 10mn expiration
expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L) // expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L)
} // }
PopupAlertManager.postVectorAlert(alert) // PopupAlertManager.postVectorAlert(alert)
} }
SasVerificationTxState.Cancelled, SasVerificationTxState.Cancelled,
SasVerificationTxState.OnCancelled, SasVerificationTxState.OnCancelled,
@ -101,4 +106,54 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
override fun markedAsManuallyVerified(userId: String, deviceId: String) { 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<RoomDetailArgs>(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}"
} }

View file

@ -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()
}
}

View file

@ -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<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji0View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
1 -> {
emoji1View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji1View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
2 -> {
emoji2View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji2View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
3 -> {
emoji3View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji3View.findViewById<TextView>(R.id.item_emoji_name_tv)?.setText(emojiRepresentation.nameResId)
}
4 -> {
emoji4View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji4View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
5 -> {
emoji5View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji5View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
6 -> {
emoji6View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji6View.findViewById<TextView>(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))
}
}

View file

@ -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()
}
}

View file

@ -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<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji0View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
1 -> {
emoji1View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji1View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
2 -> {
emoji2View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji2View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
3 -> {
emoji3View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji3View.findViewById<TextView>(R.id.item_emoji_name_tv)?.setText(emojiRepresentation.nameResId)
}
4 -> {
emoji4View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji4View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
5 -> {
emoji5View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji5View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation.nameResId)
}
6 -> {
emoji6View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation.emoji
emoji6View.findViewById<TextView>(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()
}
}

View file

@ -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<MXDeviceInfo>() {
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()
}
}

View file

@ -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()
}
}

View file

@ -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<SasVerificationTxState> = MutableLiveData()
init {
// Force a first observe
transactionState.value = null
}
private var _navigateEvent: MutableLiveData<LiveEvent<String>> = MutableLiveData()
val navigateEvent: LiveData<LiveEvent<String>>
get() = _navigateEvent
var loadingLiveEvent: MutableLiveData<Int> = 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)
}
}
}

View file

@ -17,20 +17,16 @@ package im.vector.riotx.features.crypto.verification
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.text.toSpannable import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.transition.AutoTransition import androidx.transition.AutoTransition
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife
import butterknife.Unbinder
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel 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.di.ScreenComponent
import im.vector.riotx.core.extensions.commitTransactionNow import im.vector.riotx.core.extensions.commitTransactionNow
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment 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.home.AvatarRenderer
import im.vector.riotx.features.themes.ThemeUtils
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.bottom_sheet_verification.* import kotlinx.android.synthetic.main.bottom_sheet_verification.*
import timber.log.Timber import timber.log.Timber
@ -74,22 +73,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
@BindView(R.id.verificationRequestName) @BindView(R.id.verificationRequestName)
lateinit var otherUserNameText: TextView lateinit var otherUserNameText: TextView
@BindView(R.id.verificationRequestShield)
lateinit var otherUserShield: View
@BindView(R.id.verificationRequestAvatar) @BindView(R.id.verificationRequestAvatar)
lateinit var otherUserAvatarImageView: ImageView lateinit var otherUserAvatarImageView: ImageView
private var unBinder: Unbinder? = null override fun getLayoutResId() = R.layout.bottom_sheet_verification
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 onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -109,12 +99,15 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
override fun invalidate() = withState(viewModel) { override fun invalidate() = withState(viewModel) {
it.otherUserMxItem?.let { matrixItem -> 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) 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? // Did the request result in a SAS transaction?
@ -135,7 +128,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
SasVerificationTxState.SendingMac, SasVerificationTxState.SendingMac,
SasVerificationTxState.MacSent, SasVerificationTxState.MacSent,
SasVerificationTxState.Verifying -> { SasVerificationTxState.Verifying -> {
showFragment(SASVerificationCodeFragment::class, Bundle().apply { showFragment(VerificationEmojiCodeFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationArgs( putParcelable(MvRx.KEY_ARG, VerificationArgs(
it.otherUserMxItem?.id ?: "", it.otherUserMxItem?.id ?: "",
it.pendingRequest?.transactionId)) it.pendingRequest?.transactionId))

View file

@ -21,13 +21,9 @@ import com.airbnb.mvrx.*
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session 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.*
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.util.MatrixItem import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem 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.matrix.android.internal.crypto.verification.PendingVerificationRequest
import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
@ -108,7 +104,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
is VerificationAction.RequestVerificationByDM -> { is VerificationAction.RequestVerificationByDM -> {
// session // session
setState { setState {
copy(pendingRequest = session.getSasVerificationService().requestKeyVerificationInDMs(otherUserId, roomId)) copy(pendingRequest = session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, otherUserId, roomId))
} }
} }
is VerificationAction.StartSASVerification -> { 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 val otherDevice = if (request.isIncoming) request.requestInfo?.fromDevice else request.readyInfo?.fromDevice
session.getSasVerificationService().beginKeyVerificationInDMs( session.getSasVerificationService().beginKeyVerificationInDMs(
KeyVerificationStart.VERIF_METHOD_SAS, VerificationMethod.SAS,
transactionId = action.pendingRequestTransactionId, transactionId = action.pendingRequestTransactionId,
roomId = roomId, roomId = roomId,
otherUserId = request.otherUserId, otherUserId = request.otherUserId,

View file

@ -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
?: ""))
}
}

View file

@ -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)
}
}

View file

@ -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))
}
}

View file

@ -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()
}
}

View file

@ -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
}
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState 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.Session
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService 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.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.matrix.android.internal.crypto.verification.PendingVerificationRequest
import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
data class VerificationChooseMethodViewState( data class VerificationChooseMethodViewState(
val otherUserId: String = "", val otherUserId: String = "",
val transactionId: String = "", val transactionId: String = "",
val QRModeAvailable: Boolean = false, val QRModeAvailable: Boolean = false,
val SASMOdeAvailable: Boolean = false val SASModeAvailable: Boolean = false
) : MvRxState ) : MvRxState
class VerificationChooseMethodViewModel @AssistedInject constructor( class VerificationChooseMethodViewModel @AssistedInject constructor(
@ -48,15 +49,13 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state -> override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state ->
val pvr = session.getSasVerificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId) val pvr = session.getSasVerificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId)
val qrAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SCAN) val qrAvailable = pvr?.hasMethod(VerificationMethod.SCAN) ?: false
?: false val emojiAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false
val emojiAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SAS)
?: false
setState { setState {
copy( copy(
QRModeAvailable = qrAvailable, QRModeAvailable = qrAvailable,
SASMOdeAvailable = emojiAvailable SASModeAvailable = emojiAvailable
) )
} }
} }
@ -85,15 +84,13 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
val args: VerificationBottomSheet.VerificationArgs = viewModelContext.args() val args: VerificationBottomSheet.VerificationArgs = viewModelContext.args()
val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
val pvr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId) val pvr = session.getSasVerificationService().getExistingVerificationRequest(args.otherUserId, args.verificationId)
val qrAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SCAN) val qrAvailable = pvr?.hasMethod(VerificationMethod.SCAN) ?: false
?: false val emojiAvailable = pvr?.hasMethod(VerificationMethod.SAS) ?: false
val emojiAvailable = pvr?.readyInfo?.methods?.contains(KeyVerificationStart.VERIF_METHOD_SAS)
?: false
return VerificationChooseMethodViewState(otherUserId = args.otherUserId, return VerificationChooseMethodViewState(otherUserId = args.otherUserId,
transactionId = args.verificationId ?: "", transactionId = args.verificationId ?: "",
QRModeAvailable = qrAvailable, QRModeAvailable = qrAvailable,
SASMOdeAvailable = emojiAvailable SASModeAvailable = emojiAvailable
) )
} }
} }

View file

@ -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()
}
}

View file

@ -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)
}
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory 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.EmptyAction
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
data class SASVerificationConclusionViewState( data class VerificationConclusionViewState(
val conclusionState: ConclusionState = ConclusionState.CANCELLED val conclusionState: ConclusionState = ConclusionState.CANCELLED
) : MvRxState ) : MvRxState
@ -33,22 +33,22 @@ enum class ConclusionState {
CANCELLED CANCELLED
} }
class VerificationConclusionViewModel(initialState: SASVerificationConclusionViewState) class VerificationConclusionViewModel(initialState: VerificationConclusionViewState)
: VectorViewModel<SASVerificationConclusionViewState, EmptyAction>(initialState) { : VectorViewModel<VerificationConclusionViewState, EmptyAction>(initialState) {
companion object : MvRxViewModelFactory<VerificationConclusionViewModel, SASVerificationConclusionViewState> { companion object : MvRxViewModelFactory<VerificationConclusionViewModel, VerificationConclusionViewState> {
override fun initialState(viewModelContext: ViewModelContext): SASVerificationConclusionViewState? { override fun initialState(viewModelContext: ViewModelContext): VerificationConclusionViewState? {
val args = viewModelContext.args<VerificationConclusionFragment.Args>() val args = viewModelContext.args<VerificationConclusionFragment.Args>()
return when (safeValueOf(args.cancelReason)) { return when (safeValueOf(args.cancelReason)) {
CancelCode.MismatchedSas, CancelCode.MismatchedSas,
CancelCode.MismatchedCommitment, CancelCode.MismatchedCommitment,
CancelCode.MismatchedKeys -> { CancelCode.MismatchedKeys -> {
SASVerificationConclusionViewState(ConclusionState.WARNING) VerificationConclusionViewState(ConclusionState.WARNING)
} }
else -> { else -> {
SASVerificationConclusionViewState( VerificationConclusionViewState(
if (args.isSuccessFull) ConclusionState.SUCCESS if (args.isSuccessFull) ConclusionState.SUCCESS
else ConclusionState.CANCELLED else ConclusionState.CANCELLED
) )

View file

@ -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()
}
}

View file

@ -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))
}
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.airbnb.mvrx.*
import com.squareup.inject.assisted.Assisted 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.di.HasScreenInjector
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.VectorViewModel 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 transactionId: String?,
val otherUser: MatrixItem? = null, val otherUser: MatrixItem? = null,
val supportsEmoji: Boolean = true, val supportsEmoji: Boolean = true,
@ -38,10 +39,10 @@ data class SASVerificationCodeViewState(
val isWaitingFromOther: Boolean = false val isWaitingFromOther: Boolean = false
) : MvRxState ) : MvRxState
class SASVerificationCodeViewModel @AssistedInject constructor( class VerificationEmojiCodeViewModel @AssistedInject constructor(
@Assisted initialState: SASVerificationCodeViewState, @Assisted initialState: VerificationEmojiCodeViewState,
private val session: Session private val session: Session
) : VectorViewModel<SASVerificationCodeViewState, EmptyAction>(initialState), SasVerificationService.SasVerificationListener { ) : VectorViewModel<VerificationEmojiCodeViewState, EmptyAction>(initialState), SasVerificationService.SasVerificationListener {
init { init {
withState { state -> withState { state ->
@ -141,22 +142,22 @@ class SASVerificationCodeViewModel @AssistedInject constructor(
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {
fun create(initialState: SASVerificationCodeViewState): SASVerificationCodeViewModel fun create(initialState: VerificationEmojiCodeViewState): VerificationEmojiCodeViewModel
} }
companion object : MvRxViewModelFactory<SASVerificationCodeViewModel, SASVerificationCodeViewState> { companion object : MvRxViewModelFactory<VerificationEmojiCodeViewModel, VerificationEmojiCodeViewState> {
override fun create(viewModelContext: ViewModelContext, state: SASVerificationCodeViewState): SASVerificationCodeViewModel? { override fun create(viewModelContext: ViewModelContext, state: VerificationEmojiCodeViewState): VerificationEmojiCodeViewModel? {
val factory = (viewModelContext as FragmentViewModelContext).fragment<SASVerificationCodeFragment>().viewModelFactory val factory = (viewModelContext as FragmentViewModelContext).fragment<VerificationEmojiCodeFragment>().viewModelFactory
return factory.create(state) return factory.create(state)
} }
override fun initialState(viewModelContext: ViewModelContext): SASVerificationCodeViewState? { override fun initialState(viewModelContext: ViewModelContext): VerificationEmojiCodeViewState? {
val args = viewModelContext.args<VerificationBottomSheet.VerificationArgs>() val args = viewModelContext.args<VerificationBottomSheet.VerificationArgs>()
val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
val matrixItem = session.getUser(args.otherUserId)?.toMatrixItem() val matrixItem = session.getUser(args.otherUserId)?.toMatrixItem()
return SASVerificationCodeViewState( return VerificationEmojiCodeViewState(
transactionId = args.verificationId, transactionId = args.verificationId,
otherUser = matrixItem otherUser = matrixItem
) )

View file

@ -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<BottomSheetVerificationActionItem.Holder>() {
@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<TextView>(R.id.itemVerificationActionTitle)
val subTitle by bind<TextView>(R.id.itemVerificationActionSubTitle)
val icon by bind<ImageView>(R.id.itemVerificationActionIcon)
}
}

View file

@ -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<BottomSheetVerificationBigImageItem.Holder>() {
@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<ImageView>(R.id.itemVerificationBigImage)
}
}

View file

@ -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<BottomSheetVerificationDecimalCodeItem.Holder>() {
@EpoxyAttribute
var code: CharSequence = ""
override fun bind(holder: Holder) {
holder.code.text = code
}
class Holder : VectorEpoxyHolder() {
val code by bind<TextView>(R.id.itemVerificationDecimalCode)
}
}

View file

@ -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<BottomSheetVerificationEmojisItem.Holder>() {
@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<TextView>(R.id.item_emoji_tv).text = emojiRepresentation0.emoji
holder.emoji0View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation0.nameResId)
holder.emoji1View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation1.emoji
holder.emoji1View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation1.nameResId)
holder.emoji2View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation2.emoji
holder.emoji2View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation2.nameResId)
holder.emoji3View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation3.emoji
holder.emoji3View.findViewById<TextView>(R.id.item_emoji_name_tv)?.setText(emojiRepresentation3.nameResId)
holder.emoji4View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation4.emoji
holder.emoji4View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation4.nameResId)
holder.emoji5View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation5.emoji
holder.emoji5View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation5.nameResId)
holder.emoji6View.findViewById<TextView>(R.id.item_emoji_tv).text = emojiRepresentation6.emoji
holder.emoji6View.findViewById<TextView>(R.id.item_emoji_name_tv).setText(emojiRepresentation6.nameResId)
}
class Holder : VectorEpoxyHolder() {
val emoji0View by bind<ViewGroup>(R.id.emoji0)
val emoji1View by bind<ViewGroup>(R.id.emoji1)
val emoji2View by bind<ViewGroup>(R.id.emoji2)
val emoji3View by bind<ViewGroup>(R.id.emoji3)
val emoji4View by bind<ViewGroup>(R.id.emoji4)
val emoji5View by bind<ViewGroup>(R.id.emoji5)
val emoji6View by bind<ViewGroup>(R.id.emoji6)
}
}

View file

@ -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<BottomSheetVerificationNoticeItem.Holder>() {
@EpoxyAttribute
var notice: CharSequence = ""
override fun bind(holder: Holder) {
holder.notice.text = notice
}
class Holder : VectorEpoxyHolder() {
val notice by bind<TextView>(R.id.itemVerificationNoticeText)
}
}

View file

@ -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<BottomSheetVerificationWaitingItem.Holder>() {
@EpoxyAttribute
var title: CharSequence = ""
override fun bind(holder: Holder) {
holder.title.text = title
}
class Holder : VectorEpoxyHolder() {
val title by bind<TextView>(R.id.itemVerificationWaitingTitle)
}
}

View file

@ -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()
}
}

View file

@ -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))
}
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.airbnb.mvrx.*
import com.squareup.inject.assisted.Assisted 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.di.HasScreenInjector
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
data class VerificationRequestViewState( data class VerificationRequestViewState(
val roomId: String? = null, val roomId: String? = null,

View file

@ -67,6 +67,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction() 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 DeclineVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction()
data class RequestVerification(val userId: String) : RoomDetailAction() data class RequestVerification(val userId: String) : RoomDetailAction()
data class ResumeVerification(val transactionId: String, val otherUserId: String? = null, val otherdDeviceId: String? = null) : RoomDetailAction()
} }

View file

@ -109,7 +109,7 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
companion object { 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 { fun newIntent(context: Context, roomDetailArgs: RoomDetailArgs): Intent {
return Intent(context, RoomDetailActivity::class.java).apply { return Intent(context, RoomDetailActivity::class.java).apply {

View file

@ -54,6 +54,7 @@ import com.airbnb.epoxy.OnModelBuildFinishedListener
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel 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.MessageImageInfoContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent 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.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.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
@ -849,6 +851,15 @@ class RoomDetailFragment @Inject constructor(
data.transactionId data.transactionId
).show(parentFragmentManager, "REQ") ).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) { 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 { override fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean {

View file

@ -61,6 +61,7 @@ import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.core.utils.subscribeLogError
import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.CommandParser
import im.vector.riotx.features.command.ParsedCommand 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.detail.timeline.helper.TimelineDisplayableEvents
import im.vector.riotx.features.home.room.typing.TypingHelper import im.vector.riotx.features.home.room.typing.TypingHelper
import im.vector.riotx.features.settings.VectorPreferences 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.AcceptVerificationRequest -> handleAcceptVerification(action)
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
is RoomDetailAction.RequestVerification -> handleRequestVerification(action) is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
} }
} }
@ -408,7 +410,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
popDraft() popDraft()
} }
is ParsedCommand.VerifyUser -> { is ParsedCommand.VerifyUser -> {
session.getSasVerificationService().requestKeyVerificationInDMs(slashCommandResult.userId, room.roomId) session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, slashCommandResult.userId, room.roomId)
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
popDraft() popDraft()
} }
@ -824,6 +826,18 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
_requestLiveData.postValue(LiveEvent(Success(action))) _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() { private fun observeSyncState() {
session.rx() session.rx()
.liveSyncState() .liveSyncState()

View file

@ -18,12 +18,8 @@ package im.vector.riotx.features.home.room.detail.readreceipts
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import im.vector.riotx.R import im.vector.riotx.R
@ -57,11 +53,7 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
injector.inject(this) injector.inject(this)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
val view = inflater.inflate(R.layout.bottom_sheet_generic_list_with_title, container, false)
ButterKnife.bind(this, view)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)

View file

@ -16,12 +16,8 @@
package im.vector.riotx.features.home.room.detail.timeline.action package im.vector.riotx.features.home.room.detail.timeline.action
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.riotx.R import im.vector.riotx.R
@ -53,18 +49,12 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
injector.inject(this) injector.inject(this)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun getLayoutResId() = R.layout.bottom_sheet_generic_list
val view = inflater.inflate(R.layout.bottom_sheet_generic_list, container, false)
ButterKnife.bind(this, view)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java) sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
recyclerView.configureWith(messageActionsEpoxyController, hasFixedSize = false) recyclerView.configureWith(messageActionsEpoxyController, hasFixedSize = false, disableItemAnimation = true)
// Disable item animation
recyclerView.itemAnimator = null
messageActionsEpoxyController.listener = this messageActionsEpoxyController.listener = this
} }

View file

@ -16,12 +16,8 @@
package im.vector.riotx.features.home.room.detail.timeline.edithistory package im.vector.riotx.features.home.room.detail.timeline.edithistory
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
@ -57,11 +53,7 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() {
injector.inject(this) injector.inject(this)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
val view = inflater.inflate(R.layout.bottom_sheet_generic_list_with_title, container, false)
ButterKnife.bind(this, view)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)

View file

@ -17,12 +17,8 @@
package im.vector.riotx.features.home.room.detail.timeline.reactions package im.vector.riotx.features.home.room.detail.timeline.reactions
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
@ -54,11 +50,7 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
injector.inject(this) injector.inject(this)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
val view = inflater.inflate(R.layout.bottom_sheet_generic_list_with_title, container, false)
ButterKnife.bind(this, view)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)

View file

@ -18,12 +18,8 @@ package im.vector.riotx.features.home.room.list.actions
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.riotx.R import im.vector.riotx.R
@ -69,18 +65,12 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
injector.inject(this) injector.inject(this)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun getLayoutResId() = R.layout.bottom_sheet_generic_list
val view = inflater.inflate(R.layout.bottom_sheet_generic_list, container, false)
ButterKnife.bind(this, view)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
recyclerView.configureWith(roomListActionsEpoxyController, viewPool = sharedViewPool, hasFixedSize = false) recyclerView.configureWith(roomListActionsEpoxyController, viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true)
// Disable item animation
recyclerView.itemAnimator = null
roomListActionsEpoxyController.listener = this roomListActionsEpoxyController.listener = this
} }

View file

@ -20,12 +20,12 @@ import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.View import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import com.tapadoo.alerter.Alerter import com.tapadoo.alerter.Alerter
import com.tapadoo.alerter.OnHideAlertListener import com.tapadoo.alerter.OnHideAlertListener
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.features.crypto.verification.SASVerificationActivity
import timber.log.Timber import timber.log.Timber
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@ -78,8 +78,7 @@ object PopupAlertManager {
setLightStatusBar() setLightStatusBar()
} }
} }
if (currentAlerter?.shouldBeDisplayedIn?.invoke(activity) == false) {
if (shouldIgnoreActivity(activity)) {
return return
} }
@ -108,8 +107,6 @@ object PopupAlertManager {
} }
} }
private fun shouldIgnoreActivity(activity: Activity) = activity is SASVerificationActivity
private fun displayNextIfPossible() { private fun displayNextIfPossible() {
val currentActivity = weakCurrentActivity?.get() val currentActivity = weakCurrentActivity?.get()
if (Alerter.isShowing || currentActivity == null) { if (Alerter.isShowing || currentActivity == null) {
@ -209,7 +206,13 @@ object PopupAlertManager {
}) })
.enableSwipeToDismiss() .enableSwipeToDismiss()
.enableInfiniteDuration(true) .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() .show()
} }
@ -229,7 +232,8 @@ object PopupAlertManager {
data class VectorAlert(val uid: String, data class VectorAlert(val uid: String,
val title: String, val title: String,
val description: 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) data class Button(val title: String, val action: Runnable, val autoClose: Boolean)
@ -250,5 +254,8 @@ object PopupAlertManager {
@ColorRes @ColorRes
var colorRes: Int? = null var colorRes: Int? = null
@ColorInt
var colorInt: Int? = null
} }
} }

View file

@ -20,7 +20,6 @@ import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
@ -32,7 +31,6 @@ import androidx.core.view.isVisible
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState 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? { override fun getLayoutResId() = R.layout.bottom_sheet_logout_and_backup
val view = inflater.inflate(R.layout.bottom_sheet_logout_and_backup, container, false)
ButterKnife.bind(this, view)
return view
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState) val dialog = super.onCreateDialog(savedInstanceState)

View file

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M23,19C23,20.1046 22.1046,21 21,21H3C1.8954,21 1,20.1046 1,19V8C1,6.8954 1.8954,6 3,6H7L9,3H15L17,6H21C22.1046,6 23,6.8954 23,8V19Z"
android:strokeWidth="2"
android:strokeColor="#03B381"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M12,17C14.2091,17 16,15.2091 16,13C16,10.7909 14.2091,9 12,9C9.7909,9 8,10.7909 8,13C8,15.2091 9.7909,17 12,17Z"
android:strokeWidth="2"
android:strokeColor="#03B381"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View file

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:pathData="M18,6L6,18"
android:strokeWidth="2"
android:strokeColor="#ff4b55"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:pathData="M6,6L18,18"
android:strokeWidth="2"
android:strokeColor="#ff4b55"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:pathData="M20,7L9,18L4,13"
android:strokeWidth="2"
android:strokeColor="#03B381"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/bottomSheetScrollView" android:id="@+id/bottomSheetScrollView"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -9,47 +10,62 @@
android:fadeScrollbars="false" android:fadeScrollbars="false"
android:scrollbars="vertical"> android:scrollbars="vertical">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:orientation="vertical">
<LinearLayout <ImageView
android:layout_width="match_parent" android:id="@+id/verificationRequestAvatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:adjustViewBounds="true"
android:background="@drawable/circle"
android:contentDescription="@string/avatar"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<ImageView
android:id="@+id/verificationRequestShield"
android:layout_width="16dp"
android:layout_height="16dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_shield_trusted"
app:layout_constraintCircle="@+id/verificationRequestAvatar"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="16dp"
tools:ignore="MissingConstraints" />
<TextView
android:id="@+id/verificationRequestName"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:layout_marginStart="8dp"
android:paddingStart="16dp" android:layout_marginEnd="16dp"
android:paddingEnd="16dp" android:layout_weight="1"
android:paddingTop="16dp"> android:ellipsize="end"
android:maxLines="2"
<ImageView android:textColor="?riotx_text_primary"
android:id="@+id/verificationRequestAvatar" android:textSize="20sp"
android:layout_width="32dp" android:textStyle="bold"
android:layout_height="32dp" app:layout_constraintBottom_toBottomOf="@+id/verificationRequestAvatar"
android:adjustViewBounds="true" app:layout_constraintEnd_toEndOf="parent"
android:background="@drawable/circle" app:layout_constraintStart_toEndOf="@+id/verificationRequestAvatar"
android:contentDescription="@string/avatar" app:layout_constraintTop_toTopOf="@+id/verificationRequestAvatar"
android:scaleType="centerCrop" tools:text="@string/verification_verify_user" />
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/verificationRequestName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="@string/verification_request_alert_title"
android:textColor="?riotx_text_primary"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<FrameLayout <FrameLayout
android:id="@+id/bottomSheetFragmentContainer" android:id="@+id/bottomSheetFragmentContainer"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" /> android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/verificationRequestAvatar" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/bottomSheetVerificationRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fadeScrollbars="false"
android:scrollbars="vertical"
tools:itemCount="5"
tools:listitem="@layout/item_verification_action" />

View file

@ -1,188 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:colorBackground">
<TextView
android:id="@+id/sas_emoji_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_horizontal_margin"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:text="@string/verify_by_emoji_title"
android:textColor="?riotx_text_primary"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/sas_emoji_description_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:text="@string/verify_user_sas_emoji_help_text"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_emoji_description" />
<TextView
android:id="@+id/sas_decimal_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="?riotx_text_primary"
android:textSize="28sp"
android:textStyle="bold"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/sas_emoji_grid"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/sas_emoji_grid"
tools:text="1234-4320-3905"
tools:visibility="visible" />
<ProgressBar
android:id="@+id/sasLoadingProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:layout_constraintBottom_toBottomOf="@+id/sas_emoji_grid"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/sas_emoji_grid" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/sas_emoji_grid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:visibility="invisible"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sas_emoji_description_2"
tools:visibility="visible">
<include
android:id="@+id/emoji0"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji1"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji2"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji3"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji4"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji5"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji6"
layout="@layout/item_emoji_verif" />
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/sas_emoji_grid_flow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:constraint_referenced_ids="emoji0,emoji1,emoji2,emoji3,emoji4,emoji5,emoji6"
app:flow_horizontalBias="0.5"
app:flow_horizontalGap="16dp"
app:flow_horizontalStyle="packed"
app:flow_verticalBias="0"
app:flow_verticalGap="8dp"
app:flow_wrapMode="chain"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_request_continue_button"
style="@style/VectorButtonStylePositive"
android:layout_width="0dp"
android:layout_margin="@dimen/layout_vertical_margin"
android:minWidth="160dp"
android:text="@string/verification_sas_match"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/centerGuideLine"
app:layout_constraintTop_toBottomOf="@+id/sas_emoji_grid"
tools:text="A very long translation thats too big" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/centerGuideLine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_request_cancel_button"
style="@style/VectorButtonStyleDestructive"
android:layout_width="0dp"
android:layout_margin="@dimen/layout_vertical_margin"
android:text="@string/verification_sas_do_not_match"
app:layout_constraintEnd_toStartOf="@+id/centerGuideLine"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sas_emoji_grid" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/bottomBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:barrierMargin="8dp"
app:constraint_referenced_ids="sas_request_cancel_button,sas_request_continue_button" />
<TextView
android:id="@+id/sasCodeWaitingPartnerText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/sas_waiting_for_partner"
android:textColor="?attr/vctr_notice_secondary"
android:textSize="15sp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/bottomBarrier"
app:layout_constraintTop_toBottomOf="@id/sas_emoji_grid"
tools:visibility="visible" />
<TextView
android:id="@+id/sasEmojiSecurityTip"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:gravity="center"
android:text="@string/verify_user_sas_emoji_security_tip"
android:textColor="?riotx_text_secondary"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bottomBarrier" />
<androidx.constraintlayout.widget.Group
android:id="@+id/ButtonsVisibilityGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="sas_request_continue_button,sas_request_cancel_button"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,132 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:colorBackground">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/sas_emoji_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_horizontal_margin"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:gravity="center"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/sas_emoji_description" />
<TextView
android:id="@+id/sas_emoji_description_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:gravity="center"
android:text="@string/sas_security_advise"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_emoji_description" />
<TextView
android:id="@+id/sas_decimal_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="?riotx_text_primary"
android:textSize="28sp"
android:textStyle="bold"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/sas_emoji_grid"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/sas_emoji_grid"
tools:text="1234-4320-3905"
tools:visibility="visible" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/sas_emoji_grid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:visibility="invisible"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sas_emoji_description_2"
tools:visibility="visible">
<include
android:id="@+id/emoji0"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji1"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji2"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji3"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji4"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji5"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji6"
layout="@layout/item_emoji_verif" />
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/sas_emoji_grid_flow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:constraint_referenced_ids="emoji0,emoji1,emoji2,emoji3,emoji4,emoji5,emoji6"
app:flow_horizontalBias="0.5"
app:flow_horizontalGap="16dp"
app:flow_horizontalStyle="packed"
app:flow_verticalBias="0"
app:flow_verticalGap="8dp"
app:flow_wrapMode="chain"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_request_continue_button"
style="@style/VectorButtonStyle"
android:layout_margin="@dimen/layout_vertical_margin"
android:minWidth="160dp"
android:text="@string/_continue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sas_emoji_grid" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_request_cancel_button"
style="@style/VectorButtonStyleText"
android:layout_margin="@dimen/layout_vertical_margin"
android:text="@string/cancel"
app:layout_constraintEnd_toStartOf="@+id/sas_request_continue_button"
app:layout_constraintTop_toBottomOf="@+id/sas_emoji_grid" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -1,132 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/sas_incoming_request_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="@string/sas_incoming_request_title"
android:textAlignment="center"
android:textColor="?riotx_text_primary"
android:textSize="17sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/sas_incoming_request_user_avatar"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginTop="@dimen/layout_vertical_margin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sas_incoming_request_title"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/sas_incoming_request_user_display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAlignment="center"
android:textColor="?riotx_text_primary"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_user_avatar"
tools:text="User name" />
<TextView
android:id="@+id/sas_incoming_request_user_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_user_display_name"
tools:text="\@foo:matrix.org" />
<TextView
android:id="@+id/sas_incoming_request_user_device"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_user_id"
tools:text="Device: Mobile" />
<TextView
android:id="@+id/sas_incoming_request_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:text="@string/sas_incoming_request_description"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_user_device" />
<TextView
android:id="@+id/sas_incoming_request_description_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:text="@string/sas_incoming_request_description_2"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_incoming_request_description" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_request_continue_button"
style="@style/VectorButtonStyle"
android:layout_marginTop="@dimen/layout_vertical_margin_big"
android:layout_marginEnd="@dimen/layout_vertical_margin"
android:layout_marginRight="@dimen/layout_vertical_margin"
android:layout_marginBottom="@dimen/layout_vertical_margin"
android:minWidth="160dp"
android:text="@string/_continue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sas_incoming_request_description_2" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_request_cancel_button"
style="@style/VectorButtonStyleText"
android:layout_gravity="end"
android:layout_marginEnd="@dimen/layout_vertical_margin"
android:layout_marginRight="@dimen/layout_vertical_margin"
android:text="@string/cancel"
app:layout_constraintEnd_toStartOf="@+id/sas_request_continue_button"
app:layout_constraintTop_toTopOf="@+id/sas_request_continue_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -1,89 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/sas_verification_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/sas_verify_title"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/sas_verification_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/sas_security_advise"
android:textColor="?riotx_text_secondary" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_cancel_button"
style="@style/VectorButtonStyleText"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="@string/cancel" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginRight="@dimen/layout_horizontal_margin">
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_start_button"
style="@style/VectorButtonStyle"
android:minWidth="160dp"
android:text="@string/sas_verify_start_button_title" />
<ProgressBar
android:id="@+id/sas_start_button_loading"
android:layout_width="19dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_legacy_verification"
style="@style/VectorButtonStyleText"
android:layout_gravity="end"
android:layout_margin="@dimen/layout_horizontal_margin"
android:text="@string/sas_legacy_verification_button_title"
android:visibility="visible" />
<TextView
android:id="@+id/sas_verifying_keys"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/sas_verifying_keys"
android:textColor="?riotx_text_secondary"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
</ScrollView>

View file

@ -1,69 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:colorBackground">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/sas_verification_verified_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="16dp"
android:text="@string/sas_verified"
android:textColor="?riotx_text_primary"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/sas_verification_verified_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:gravity="center"
android:text="@string/sas_verified_successful"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_verification_verified_title" />
<TextView
android:id="@+id/sas_verification_verified_description_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin"
android:gravity="center"
android:text="@string/sas_verified_successful_description"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sas_verification_verified_description" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sas_verification_verified_done_button"
style="@style/VectorButtonStyle"
android:layout_marginTop="@dimen/layout_vertical_margin_big"
android:layout_marginEnd="@dimen/layout_vertical_margin"
android:layout_marginRight="@dimen/layout_vertical_margin"
android:layout_marginBottom="@dimen/layout_vertical_margin"
android:minWidth="160dp"
android:text="@string/sas_got_it"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sas_verification_verified_description_2" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -1,117 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- <ImageView-->
<!-- android:id="@+id/verificationRequestAvatar"-->
<!-- android:layout_width="32dp"-->
<!-- android:layout_height="32dp"-->
<!-- android:adjustViewBounds="true"-->
<!-- android:background="@drawable/circle"-->
<!-- android:contentDescription="@string/avatar"-->
<!-- android:scaleType="centerCrop"-->
<!-- android:transitionName="bottomSheetAvatar"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:layout_constraintVertical_bias="0"-->
<!-- tools:src="@tools:sample/avatars" />-->
<!-- <TextView-->
<!-- android:id="@+id/verificationRequestName"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginStart="16dp"-->
<!-- android:text="@string/verification_request_alert_title"-->
<!-- android:textColor="?riotx_text_primary"-->
<!-- android:textSize="20sp"-->
<!-- android:textStyle="bold"-->
<!-- android:transitionName="bottomSheetDisplayName"-->
<!-- app:layout_constraintBottom_toBottomOf="@id/verificationRequestAvatar"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toEndOf="@id/verificationRequestAvatar"-->
<!-- app:layout_constraintTop_toTopOf="@id/verificationRequestAvatar" />-->
<TextView
android:id="@+id/verificationQRTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/verify_by_scanning_title"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/verifyQRDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/verify_by_scanning_description"
android:textColor="?riotx_text_secondary"
app:layout_constraintTop_toBottomOf="@id/verificationQRTitle"
tools:text="@string/verify_by_scanning_description" />
<ImageView
android:id="@+id/verifyQRImageView"
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:background="?riotx_header_panel_background"
android:contentDescription="@string/aria_qr_code_description"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/verifyQRDescription" />
<TextView
android:id="@+id/verificationEmojiTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="@string/verify_by_emoji_title"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/verifyQRImageView"
app:layout_goneMarginTop="0dp" />
<TextView
android:id="@+id/verifyEmojiDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/verify_by_emoji_description"
android:textColor="?riotx_text_secondary"
app:layout_constraintTop_toBottomOf="@id/verificationEmojiTitle" />
<com.google.android.material.button.MaterialButton
android:id="@+id/verificationByEmojiButton"
style="@style/VectorButtonStylePositive"
android:layout_width="match_parent"
android:layout_marginTop="16dp"
android:text="@string/verify_by_emoji_title"
app:layout_constraintTop_toBottomOf="@id/verifyEmojiDescription" />
<androidx.constraintlayout.widget.Group
android:id="@+id/verifyQRGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:constraint_referenced_ids="verifyQRDescription,verificationQRTitle,verifyQRImageView" />
<androidx.constraintlayout.widget.Group
android:id="@+id/verifyEmojiGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:constraint_referenced_ids="verifyEmojiDescription,verificationEmojiTitle,verificationByEmojiButton" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,63 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/verificationConclusionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/sas_verified"
android:textColor="?riotx_text_primary"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/sas_verified" />
<TextView
android:id="@+id/verifyConclusionDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/sas_verified_successful_description"
android:textColor="?riotx_text_secondary"
app:layout_constraintTop_toBottomOf="@id/verificationConclusionTitle"
tools:text="@string/sas_verified_successful_description" />
<ImageView
android:id="@+id/verifyConclusionImageView"
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/verifyConclusionDescription"
tools:background="@drawable/ic_shield_trusted" />
<TextView
android:id="@+id/verifyConclusionBottomDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="?riotx_text_secondary"
app:layout_constraintTop_toBottomOf="@id/verifyConclusionImageView"
tools:text="@string/verification_green_shield" />
<com.google.android.material.button.MaterialButton
android:id="@+id/verificationConclusionButton"
style="@style/VectorButtonStylePositive"
android:layout_width="match_parent"
android:layout_marginTop="16dp"
android:text="@string/sas_got_it"
app:layout_constraintTop_toBottomOf="@id/verifyConclusionBottomDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/verificationRequestText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:textColor="?riotx_text_secondary"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/verification_request_alert_description" />
<!-- app:layout_constraintTop_toBottomOf="@id/verificationRequestAvatar"-->
<com.google.android.material.button.MaterialButton
android:id="@+id/verificationStartButton"
style="@style/VectorButtonStylePositive"
android:layout_width="match_parent"
android:layout_marginTop="16dp"
android:text="@string/start_verification"
app:layout_constraintTop_toBottomOf="@id/verificationRequestText" />
<TextView
android:id="@+id/verificationWaitingText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textColor="?vctr_notice_secondary"
android:textSize="17sp"
android:textStyle="bold"
android:visibility="invisible"
tools:visibility="visible"
app:layout_constraintBottom_toBottomOf="@id/verificationStartButton"
app:layout_constraintTop_toTopOf="@id/verificationStartButton"
tools:text="@string/verification_request_waiting_for" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="72dp"
android:paddingLeft="@dimen/layout_horizontal_margin"
android:paddingTop="8dp"
android:paddingRight="@dimen/layout_horizontal_margin"
android:paddingBottom="8dp">
<TextView
android:id="@+id/itemVerificationActionTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="@color/riotx_accent"
android:textSize="16sp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/itemVerificationActionSubTitle"
app:layout_constraintEnd_toStartOf="@+id/itemVerificationActionIcon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@string/start_verification" />
<TextView
android:id="@+id/itemVerificationActionSubTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="?riotx_text_secondary"
android:textSize="12sp"
android:visibility="gone"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/itemVerificationActionIcon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/itemVerificationActionTitle"
tools:text="For maximum security, do this in person"
tools:visibility="visible" />
<ImageView
android:id="@+id/itemVerificationActionIcon"
android:layout_width="48dp"
android:layout_height="48dp"
android:scaleType="center"
android:tint="?riotx_text_primary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_arrow_right" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/itemVerificationBigImage"
android:layout_width="match_parent"
android:layout_height="180dp"
android:layout_margin="8dp"
android:src="@drawable/ic_shield_trusted" />

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemVerificationDecimalCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:gravity="center_horizontal"
android:textColor="?riotx_text_primary"
android:textSize="28sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/sas_emoji_grid"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/sas_emoji_grid"
tools:text="1234-4320-3905" />

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_vertical_margin">
<include
android:id="@+id/emoji0"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji1"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji2"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji3"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji4"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji5"
layout="@layout/item_emoji_verif" />
<include
android:id="@+id/emoji6"
layout="@layout/item_emoji_verif" />
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/sas_emoji_grid_flow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:constraint_referenced_ids="emoji0,emoji1,emoji2,emoji3,emoji4,emoji5,emoji6"
app:flow_horizontalBias="0.5"
app:flow_horizontalGap="16dp"
app:flow_horizontalStyle="packed"
app:flow_verticalBias="0"
app:flow_verticalGap="8dp"
app:flow_wrapMode="chain"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemVerificationNoticeText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/layout_horizontal_margin"
android:paddingTop="8dp"
android:paddingRight="@dimen/layout_horizontal_margin"
android:paddingBottom="8dp"
android:textColor="?riotx_text_primary"
android:textSize="14sp"
tools:text="todo" />

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="72dp"
android:paddingLeft="@dimen/layout_horizontal_margin"
android:paddingTop="8dp"
android:paddingRight="@dimen/layout_horizontal_margin"
android:paddingBottom="8dp">
<TextView
android:id="@+id/itemVerificationWaitingTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textColor="?riotx_text_secondary"
android:textSize="16sp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/itemVerificationWaitingProgress"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Waiting for..." />
<ProgressBar
android:id="@+id/itemVerificationWaitingProgress"
android:layout_width="24dp"
android:layout_height="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -35,18 +35,20 @@
<!-- Sender name of a message when it is send by you, e.g. You: Hello!--> <!-- Sender name of a message when it is send by you, e.g. You: Hello!-->
<string name="you">You</string> <string name="you">You</string>
<string name="verify_by_scanning_title">Verify by scanning</string> <string name="verification_scan_notice">Scan the code with the other user\'s device to securely verify each other</string>
<!-- the %s will be replaced by verify_open_camera_link that will be clickable --> <string name="verification_scan_their_code">Scan their code</string>
<string name="verify_by_scanning_description">Ask the other user to scan this code, or %s to scan theirs</string> <string name="verification_scan_emoji_title">Can\'t scan</string>
<!-- This part is inserted in verify_by_scanning_description--> <string name="verification_scan_emoji_subtitle">If you\'re not in person, compare emoji instead</string>
<string name="verify_open_camera_link">open your camera</string>
<string name="verification_no_scan_emoji_title">Continue</string>
<string name="verify_by_emoji_title">Verify by Emoji</string> <string name="verify_by_emoji_title">Verify by Emoji</string>
<string name="verify_by_emoji_description">If you cant scan the code above, verify by comparing a short, unique selection of emoji.</string> <string name="verify_by_emoji_description">If you cant scan the code above, verify by comparing a short, unique selection of emoji.</string>
<string name="aria_qr_code_description">QR code image</string> <string name="a13n_qr_code_description">QR code image</string>
<string name="verification_request_alert_title">Verify %s</string> <string name="verification_verify_user">Verify %s</string>
<string name="verification_verified_user">Verified %s</string>
<string name="verification_request_waiting_for">Waiting for %s…</string> <string name="verification_request_waiting_for">Waiting for %s…</string>
<string name="verification_request_alert_description">For extra security, verify %s by checking a one-time code on both your devices.\n\nFor maximum security, do this in person.</string> <string name="verification_request_alert_description">For extra security, verify %s by checking a one-time code on both your devices.\n\nFor maximum security, do this in person.</string>
<string name="room_profile_not_encrypted_subtitle">Messages in this room are not end-to-end encrypted.</string> <string name="room_profile_not_encrypted_subtitle">Messages in this room are not end-to-end encrypted.</string>
@ -85,4 +87,12 @@
<string name="settings_category_composer">Message editor</string> <string name="settings_category_composer">Message editor</string>
<string name="verification_request_notice">For extra security, verify %s by checking a one-time code.</string>
<string name="verification_request_start_notice">For maximum security, do this in person.</string>
<string name="verification_emoji_notice">Compare the unique emoji, ensuring they appear in the same order.</string>
<string name="verification_code_notice">Compare the code with the one displayed on the other user\'s screen.</string>
<string name="verification_conclusion_ok_notice">Messages with this user are end-to-end encrypted and can\'t be read by third parties.</string>
</resources> </resources>