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">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View file

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

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

View file

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

View file

@ -28,7 +28,7 @@ internal data class KeyVerificationRequest(
@Json(name = "from_device")
val fromDevice: String,
/** The verification methods supported by the sender. */
val methods: List<String> = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
val methods: List<String>,
/**
* 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,

View file

@ -27,7 +27,7 @@ import timber.log.Timber
* Sent by Alice to initiate an interactive key verification.
*/
@JsonClass(generateAdapter = true)
data class KeyVerificationStart(
internal data class KeyVerificationStart(
@Json(name = "from_device") override val fromDevice: String? = null,
override val method: String? = null,
@Json(name = "transaction_id") override val transactionID: String? = null,
@ -41,22 +41,18 @@ data class KeyVerificationStart(
return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this)
}
companion object {
const val VERIF_METHOD_SAS = "m.sas.v1"
const val VERIF_METHOD_SCAN = "m.qr_code.scan.v1"
}
override fun isValid(): Boolean {
if ((transactionID.isNullOrBlank()
|| fromDevice.isNullOrBlank()
|| method != VERIF_METHOD_SAS
|| keyAgreementProtocols.isNullOrEmpty()
|| hashes.isNullOrEmpty())
if (transactionID.isNullOrBlank()
|| fromDevice.isNullOrBlank()
|| method !in supportedVerificationMethods
|| keyAgreementProtocols.isNullOrEmpty()
|| hashes.isNullOrEmpty()
|| !hashes.contains("sha256")
|| messageAuthenticationCodes.isNullOrEmpty()
|| (!messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256)
&& !messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|| shortAuthenticationStrings.isNullOrEmpty() || !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
|| shortAuthenticationStrings.isNullOrEmpty()
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
Timber.e("## received invalid verification request")
return false
}

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
Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ")
it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
params.sasVerificationService.onRoomRequestHandledByOtherDevice(event)
}
}
} else if (EventType.KEY_VERIFICATION_READY == event.type) {
@ -112,11 +113,13 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
// The verification is started from another device
Timber.v("## SAS Verification live observer: Transaction started by other device tid:${it.transactionID} ")
it.transactionID?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
params.sasVerificationService.onRoomRequestHandledByOtherDevice(event)
}
}
} else if (EventType.KEY_VERIFICATION_CANCEL == event.type || EventType.KEY_VERIFICATION_DONE == event.type) {
event.getClearContent().toModel<MessageRelationContent>()?.relatesTo?.eventId?.let {
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.OutgoingSasVerificationRequest
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
import im.vector.matrix.android.internal.crypto.model.rest.toValue
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import timber.log.Timber
@ -71,7 +72,7 @@ internal class DefaultOutgoingSASVerificationRequest(
cancel(CancelCode.UnexpectedMessage)
}
fun start() {
fun start(method: VerificationMethod) {
if (state != SasVerificationTxState.None) {
Timber.e("## SAS O: start verification from invalid state")
// should I cancel??
@ -80,7 +81,7 @@ internal class DefaultOutgoingSASVerificationRequest(
val startMessage = transport.createStart(
credentials.deviceId ?: "",
KeyVerificationStart.VERIF_METHOD_SAS,
method.toValue(),
transactionId,
KNOWN_AGREEMENT_PROTOCOLS,
KNOWN_HASHES,

View file

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

View file

@ -16,27 +16,38 @@
package im.vector.matrix.android.internal.crypto.verification
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SCAN
import java.util.*
/**
* Stores current pending verification requests
*/
data class PendingVerificationRequest(
val ageLocalTs : Long,
val ageLocalTs: Long,
val isIncoming: Boolean = false,
val localID: String = UUID.randomUUID().toString(),
val localID: String = UUID.randomUUID().toString(),
val otherUserId: String,
val roomId: String?,
val transactionId: String? = null,
val requestInfo: MessageVerificationRequestContent? = null,
val readyInfo: VerificationInfoReady? = null,
val cancelConclusion: CancelCode? = null,
val isSuccessful : Boolean = false
val isSuccessful: Boolean = false,
val handledByOtherSession: Boolean = false
) {
val isReady: Boolean = readyInfo != null
val isSent: Boolean = transactionId != null
val isFinished: Boolean = isSuccessful || cancelConclusion != null
fun hasMethod(method: VerificationMethod): Boolean? {
return when (method) {
VerificationMethod.SAS -> readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS)
VerificationMethod.SCAN -> readyInfo?.methods?.contains(VERIFICATION_METHOD_SCAN)
}
}
}

View file

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

View file

@ -34,7 +34,10 @@ internal class SasTransportToDevice(
private var taskExecutor: TaskExecutor
) : SasTransport {
override fun sendVerificationRequest(localID: String, otherUserId: String, roomId: String,
override fun sendVerificationRequest(supportedMethods: List<String>,
localID: String,
otherUserId: String,
roomId: String,
callback: (String?, MessageVerificationRequestContent?) -> Unit) {
// TODO "not implemented"
}
@ -78,11 +81,11 @@ internal class SasTransportToDevice(
// To device do not do anything here
}
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDevice: String, code: CancelCode) {
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String, code: CancelCode) {
Timber.d("## SAS canceling transaction $transactionId for reason $code")
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(otherUserId, otherUserDevice, cancelMessage)
contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage)
sendToDeviceTask
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) {
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.events.model.Event
import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask
import im.vector.matrix.android.internal.worker.SessionWorkerParams
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent
import timber.log.Timber
@ -34,9 +35,10 @@ internal class SendVerificationMessageWorker constructor(context: Context, param
@JsonClass(generateAdapter = true)
internal data class Params(
val userId: String,
val event: Event
)
override val sessionId: String,
val event: Event,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
@Inject
lateinit var sendVerificationMessageTask: SendVerificationMessageTask
@ -49,10 +51,10 @@ internal class SendVerificationMessageWorker constructor(context: Context, param
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success(errorOutputData)
val sessionComponent = getSessionComponent(params.userId)
val sessionComponent = getSessionComponent(params.sessionId)
?: return Result.success(errorOutputData).also {
// TODO, can this happen? should I update local echo?
Timber.e("Unknown Session, cannot send message, userId:${params.userId}")
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
}
sessionComponent.inject(this)
val localId = params.event.eventId ?: ""

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,15 +16,17 @@
package im.vector.matrix.android.internal.session.sync.job
import android.content.Context
import androidx.work.*
import androidx.work.BackoffPolicy
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.failure.isTokenError
import im.vector.matrix.android.internal.di.WorkManagerProvider
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.session.sync.SyncTask
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.worker.SessionWorkerParams
import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent
import timber.log.Timber
@ -75,30 +77,33 @@ internal class SyncWorker(context: Context,
companion object {
const val BG_SYNC_WORK_NAME = "BG_SYNCP"
private const val BG_SYNC_WORK_NAME = "BG_SYNCP"
fun requireBackgroundSync(context: Context, sessionId: String, serverTimeout: Long = 0) {
fun requireBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0) {
val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, false))
val workRequest = matrixOneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(WorkManagerUtil.workConstraints)
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(WorkManagerProvider.workConstraints)
.setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS)
.setInputData(data)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest)
workManagerProvider.workManager
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest)
}
fun automaticallyBackgroundSync(context: Context, sessionId: String, serverTimeout: Long = 0, delay: Long = 30_000) {
fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0, delay: Long = 30_000) {
val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, true))
val workRequest = matrixOneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(WorkManagerUtil.workConstraints)
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(WorkManagerProvider.workConstraints)
.setInputData(data)
.setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest)
workManagerProvider.workManager
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest)
}
fun stopAnyBackgroundSync(context: Context) {
WorkManager.getInstance(context).cancelUniqueWork(BG_SYNC_WORK_NAME)
fun stopAnyBackgroundSync(workManagerProvider: WorkManagerProvider) {
workManagerProvider.workManager
.cancelUniqueWork(BG_SYNC_WORK_NAME)
}
}
}

View file

@ -16,15 +16,14 @@
package im.vector.matrix.android.internal.util
import android.content.Context
import androidx.work.WorkManager
import im.vector.matrix.android.api.util.Cancelable
import java.util.UUID
import java.util.*
internal class CancelableWork(private val context: Context,
internal class CancelableWork(private val workManager: WorkManager,
private val workId: UUID) : Cancelable {
override fun cancel() {
WorkManager.getInstance(context).cancelWorkById(workId)
workManager.cancelWorkById(workId)
}
}

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:windowSoftInputMode="adjustResize" />
<activity android:name=".features.media.VideoMediaViewerActivity" />
<activity
android:name=".features.crypto.verification.SASVerificationActivity"
android:label="@string/title_activity_verify_device" />
<activity
android:name=".features.crypto.keysbackup.restore.KeysBackupRestoreActivity"
android:label="@string/title_activity_keys_backup_setup" />

View file

@ -23,7 +23,10 @@ import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
import im.vector.riotx.features.crypto.verification.*
import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment
import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment
import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
import im.vector.riotx.features.home.HomeDetailFragment
import im.vector.riotx.features.home.HomeDrawerFragment
import im.vector.riotx.features.home.LoadingFragment
@ -239,26 +242,6 @@ interface FragmentModule {
@FragmentKey(VectorSettingsDevicesFragment::class)
fun bindVectorSettingsDevicesFragment(fragment: VectorSettingsDevicesFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SASVerificationIncomingFragment::class)
fun bindSASVerificationIncomingFragment(fragment: SASVerificationIncomingFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SASVerificationShortCodeFragment::class)
fun bindSASVerificationShortCodeFragment(fragment: SASVerificationShortCodeFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SASVerificationVerifiedFragment::class)
fun bindSASVerificationVerifiedFragment(fragment: SASVerificationVerifiedFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SASVerificationStartFragment::class)
fun bindSASVerificationStartFragment(fragment: SASVerificationStartFragment): Fragment
@Binds
@IntoMap
@FragmentKey(PublicRoomsFragment::class)
@ -302,15 +285,15 @@ interface FragmentModule {
@Binds
@IntoMap
@FragmentKey(VerificationChooseMethodFragment::class)
fun bindVerificationMethodChooserFragment(fragment: VerificationChooseMethodFragment): Fragment
fun bindVerificationChooseMethodFragment(fragment: VerificationChooseMethodFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SASVerificationCodeFragment::class)
fun bindVerificationSasCodeFragment(fragment: SASVerificationCodeFragment): Fragment
@FragmentKey(VerificationEmojiCodeFragment::class)
fun bindVerificationEmojiCodeFragment(fragment: VerificationEmojiCodeFragment): Fragment
@Binds
@IntoMap
@FragmentKey(VerificationConclusionFragment::class)
fun bindVerificationSasConclusionFragment(fragment: VerificationConclusionFragment): Fragment
fun bindVerificationConclusionFragment(fragment: VerificationConclusionFragment): Fragment
}

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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.SasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
import im.vector.riotx.features.home.room.detail.RoomDetailArgs
import im.vector.riotx.features.popup.PopupAlertManager
import im.vector.riotx.features.themes.ThemeUtils
import javax.inject.Inject
import javax.inject.Singleton
@ -48,46 +53,46 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
override fun transactionUpdated(tx: SasVerificationTransaction) {
when (tx.state) {
SasVerificationTxState.OnStarted -> {
// Add a notification for every incoming request
val name = session?.getUser(tx.otherUserId)?.displayName
?: tx.otherUserId
val alert = PopupAlertManager.VectorAlert(
"kvr_${tx.transactionId}",
context.getString(R.string.sas_incoming_request_notif_title),
context.getString(R.string.sas_incoming_request_notif_content, name),
R.drawable.shield)
.apply {
contentAction = Runnable {
val intent = SASVerificationActivity.incomingIntent(context,
session?.myUserId ?: "",
tx.otherUserId,
tx.transactionId)
weakCurrentActivity?.get()?.startActivity(intent)
}
dismissedAction = Runnable {
tx.cancel()
}
addButton(
context.getString(R.string.ignore),
Runnable {
tx.cancel()
}
)
addButton(
context.getString(R.string.action_open),
Runnable {
val intent = SASVerificationActivity.incomingIntent(context,
session?.myUserId ?: "",
tx.otherUserId,
tx.transactionId)
weakCurrentActivity?.get()?.startActivity(intent)
}
)
// 10mn expiration
expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L)
}
PopupAlertManager.postVectorAlert(alert)
// // Add a notification for every incoming request
// val name = session?.getUser(tx.otherUserId)?.displayName
// ?: tx.otherUserId
//
// val alert = PopupAlertManager.VectorAlert(
// "kvr_${tx.transactionId}",
// context.getString(R.string.sas_incoming_request_notif_title),
// context.getString(R.string.sas_incoming_request_notif_content, name),
// R.drawable.shield)
// .apply {
// contentAction = Runnable {
// val intent = SASVerificationActivity.incomingIntent(context,
// session?.myUserId ?: "",
// tx.otherUserId,
// tx.transactionId)
// weakCurrentActivity?.get()?.startActivity(intent)
// }
// dismissedAction = Runnable {
// tx.cancel()
// }
// addButton(
// context.getString(R.string.ignore),
// Runnable {
// tx.cancel()
// }
// )
// addButton(
// context.getString(R.string.action_open),
// Runnable {
// val intent = SASVerificationActivity.incomingIntent(context,
// session?.myUserId ?: "",
// tx.otherUserId,
// tx.transactionId)
// weakCurrentActivity?.get()?.startActivity(intent)
// }
// )
// // 10mn expiration
// expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L)
// }
// PopupAlertManager.postVectorAlert(alert)
}
SasVerificationTxState.Cancelled,
SasVerificationTxState.OnCancelled,
@ -101,4 +106,54 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
override fun markedAsManuallyVerified(userId: String, deviceId: String) {
}
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// For incoming request we should prompt (if not in activity where this request apply)
if (pr.isIncoming) {
val name = session?.getUser(pr.otherUserId)?.displayName
?: pr.otherUserId
val alert = PopupAlertManager.VectorAlert(
uniqueIdForVerificationRequest(pr),
context.getString(R.string.sas_incoming_request_notif_title),
"$name(${pr.otherUserId})",
R.drawable.ic_shield_black,
shouldBeDisplayedIn = { activity ->
if (activity is RoomDetailActivity) {
activity.intent?.extras?.getParcelable<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.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.text.toSpannable
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.Unbinder
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel
@ -40,9 +36,12 @@ import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.commitTransactionNow
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.riotx.core.utils.colorizeMatchingText
import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment
import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment
import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
import im.vector.riotx.features.crypto.verification.request.VerificationRequestViewModel
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.themes.ThemeUtils
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.bottom_sheet_verification.*
import timber.log.Timber
@ -74,22 +73,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
@BindView(R.id.verificationRequestName)
lateinit var otherUserNameText: TextView
@BindView(R.id.verificationRequestShield)
lateinit var otherUserShield: View
@BindView(R.id.verificationRequestAvatar)
lateinit var otherUserAvatarImageView: ImageView
private var unBinder: Unbinder? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.bottom_sheet_verification, container, false)
unBinder = ButterKnife.bind(this, view)
return view
}
override fun onDestroyView() {
super.onDestroyView()
unBinder?.unbind()
unBinder = null
}
override fun getLayoutResId() = R.layout.bottom_sheet_verification
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -109,12 +99,15 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
override fun invalidate() = withState(viewModel) {
it.otherUserMxItem?.let { matrixItem ->
val displayName = matrixItem.displayName ?: ""
otherUserNameText.text = getString(R.string.verification_request_alert_title, displayName)
.toSpannable()
.colorizeMatchingText(displayName, ThemeUtils.getColor(requireContext(), R.attr.vctr_notice_text_color))
avatarRenderer.render(matrixItem, otherUserAvatarImageView)
if(it.sasTransactionState == SasVerificationTxState.Verified) {
otherUserNameText.text = getString(R.string.verification_verified_user, matrixItem.getBestName())
otherUserShield.isVisible = true
} else {
otherUserNameText.text = getString(R.string.verification_verify_user, matrixItem.getBestName())
otherUserShield.isVisible = false
}
}
// Did the request result in a SAS transaction?
@ -135,7 +128,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
SasVerificationTxState.SendingMac,
SasVerificationTxState.MacSent,
SasVerificationTxState.Verifying -> {
showFragment(SASVerificationCodeFragment::class, Bundle().apply {
showFragment(VerificationEmojiCodeFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationArgs(
it.otherUserMxItem?.id ?: "",
it.pendingRequest?.transactionId))

View file

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

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

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
* limitations under the License.
*/
package im.vector.riotx.features.crypto.verification
package im.vector.riotx.features.crypto.verification.conclusion
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.crypto.sas.safeValueOf
import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.VectorViewModel
data class SASVerificationConclusionViewState(
data class VerificationConclusionViewState(
val conclusionState: ConclusionState = ConclusionState.CANCELLED
) : MvRxState
@ -33,22 +33,22 @@ enum class ConclusionState {
CANCELLED
}
class VerificationConclusionViewModel(initialState: SASVerificationConclusionViewState)
: VectorViewModel<SASVerificationConclusionViewState, EmptyAction>(initialState) {
class VerificationConclusionViewModel(initialState: VerificationConclusionViewState)
: 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>()
return when (safeValueOf(args.cancelReason)) {
CancelCode.MismatchedSas,
CancelCode.MismatchedCommitment,
CancelCode.MismatchedKeys -> {
SASVerificationConclusionViewState(ConclusionState.WARNING)
VerificationConclusionViewState(ConclusionState.WARNING)
}
else -> {
SASVerificationConclusionViewState(
VerificationConclusionViewState(
if (args.isSuccessFull) ConclusionState.SUCCESS
else ConclusionState.CANCELLED
)

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

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
* limitations under the License.
*/
package im.vector.riotx.features.crypto.verification
package im.vector.riotx.features.crypto.verification.request
import com.airbnb.mvrx.*
import com.squareup.inject.assisted.Assisted
@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.crypto.verification.PendingVerification
import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
data class VerificationRequestViewState(
val roomId: String? = null,

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 DeclineVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction()
data class RequestVerification(val userId: String) : RoomDetailAction()
data class ResumeVerification(val transactionId: String, val otherUserId: String? = null, val otherdDeviceId: String? = null) : RoomDetailAction()
}

View file

@ -109,7 +109,7 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
companion object {
private const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS"
const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS"
fun newIntent(context: Context, roomDetailArgs: RoomDetailArgs): Intent {
return Intent(context, RoomDetailActivity::class.java).apply {

View file

@ -54,6 +54,7 @@ import com.airbnb.epoxy.OnModelBuildFinishedListener
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
@ -74,6 +75,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageFileConten
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.Timeline
@ -849,6 +851,15 @@ class RoomDetailFragment @Inject constructor(
data.transactionId
).show(parentFragmentManager, "REQ")
}
is RoomDetailAction.ResumeVerification -> {
val otherUserId = data.otherUserId ?: return
VerificationBottomSheet().apply {
arguments = Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs(
otherUserId, data.transactionId, roomId = roomDetailArgs.roomId))
}
}.show(parentFragmentManager, "REQ")
}
}
}
}
@ -998,6 +1009,9 @@ class RoomDetailFragment @Inject constructor(
}
override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) {
if (messageContent is MessageVerificationRequestContent) {
roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId))
}
}
override fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean {

View file

@ -61,6 +61,7 @@ import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.core.utils.subscribeLogError
import im.vector.riotx.features.command.CommandParser
import im.vector.riotx.features.command.ParsedCommand
import im.vector.riotx.features.crypto.verification.supportedVerificationMethods
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
import im.vector.riotx.features.home.room.typing.TypingHelper
import im.vector.riotx.features.settings.VectorPreferences
@ -187,6 +188,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
}
}
@ -408,7 +410,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
popDraft()
}
is ParsedCommand.VerifyUser -> {
session.getSasVerificationService().requestKeyVerificationInDMs(slashCommandResult.userId, room.roomId)
session.getSasVerificationService().requestKeyVerificationInDMs(supportedVerificationMethods, slashCommandResult.userId, room.roomId)
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
popDraft()
}
@ -824,6 +826,18 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
_requestLiveData.postValue(LiveEvent(Success(action)))
}
private fun handleResumeRequestVerification(action: RoomDetailAction.ResumeVerification) {
// Check if this request is still active and handled by me
session.getSasVerificationService().getExistingVerificationRequestInRoom(room.roomId, action.transactionId)?.let {
if (it.handledByOtherSession) return
if (!it.isFinished) {
_requestLiveData.postValue(LiveEvent(Success(action.copy(
otherUserId = it.otherUserId
))))
}
}
}
private fun observeSyncState() {
session.rx()
.liveSyncState()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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"?>
<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"
android:id="@+id/bottomSheetScrollView"
android:layout_width="match_parent"
@ -9,47 +10,62 @@
android:fadeScrollbars="false"
android:scrollbars="vertical">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
<ImageView
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:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="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"
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>
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?riotx_text_primary"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/verificationRequestAvatar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/verificationRequestAvatar"
app:layout_constraintTop_toTopOf="@+id/verificationRequestAvatar"
tools:text="@string/verification_verify_user" />
<FrameLayout
android:id="@+id/bottomSheetFragmentContainer"
android:layout_width="match_parent"
android:layout_width="0dp"
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>

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!-->
<string name="you">You</string>
<string name="verify_by_scanning_title">Verify by scanning</string>
<!-- the %s will be replaced by verify_open_camera_link that will be clickable -->
<string name="verify_by_scanning_description">Ask the other user to scan this code, or %s to scan theirs</string>
<!-- This part is inserted in verify_by_scanning_description-->
<string name="verify_open_camera_link">open your camera</string>
<string name="verification_scan_notice">Scan the code with the other user\'s device to securely verify each other</string>
<string name="verification_scan_their_code">Scan their code</string>
<string name="verification_scan_emoji_title">Can\'t scan</string>
<string name="verification_scan_emoji_subtitle">If you\'re not in person, compare emoji instead</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_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_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>
@ -85,4 +87,12 @@
<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>