mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
merge madness ??
This commit is contained in:
parent
3eed9b5083
commit
3c4506cb58
@ -17,6 +17,7 @@
|
|||||||
package im.vector.matrix.android.api.session.crypto.sas
|
package im.vector.matrix.android.api.session.crypto.sas
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework
|
* https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework
|
||||||
@ -54,7 +55,7 @@ interface SasVerificationService {
|
|||||||
*/
|
*/
|
||||||
fun beginKeyVerification(method: String, userId: String, deviceID: String): String?
|
fun beginKeyVerification(method: String, userId: String, deviceID: String): String?
|
||||||
|
|
||||||
fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback<String>?)
|
fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback<String>?) : PendingVerificationRequest
|
||||||
|
|
||||||
fun beginKeyVerificationInDMs(method: String,
|
fun beginKeyVerificationInDMs(method: String,
|
||||||
transactionId: String,
|
transactionId: String,
|
||||||
@ -63,11 +64,16 @@ interface SasVerificationService {
|
|||||||
otherDeviceId: String,
|
otherDeviceId: String,
|
||||||
callback: MatrixCallback<String>?): String?
|
callback: MatrixCallback<String>?): String?
|
||||||
|
|
||||||
|
fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String)
|
||||||
|
|
||||||
// fun transactionUpdated(tx: SasVerificationTransaction)
|
// fun transactionUpdated(tx: SasVerificationTransaction)
|
||||||
|
|
||||||
interface SasVerificationListener {
|
interface SasVerificationListener {
|
||||||
fun transactionCreated(tx: SasVerificationTransaction)
|
fun transactionCreated(tx: SasVerificationTransaction)
|
||||||
fun transactionUpdated(tx: SasVerificationTransaction)
|
fun transactionUpdated(tx: SasVerificationTransaction)
|
||||||
fun markedAsManuallyVerified(userId: String, deviceId: String)
|
fun markedAsManuallyVerified(userId: String, deviceId: String) {}
|
||||||
|
|
||||||
|
fun verificationRequestCreated(pr: PendingVerificationRequest) {}
|
||||||
|
fun verificationRequestUpdated(pr: PendingVerificationRequest) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,4 +47,7 @@ interface SasVerificationTransaction {
|
|||||||
* both short codes do match
|
* both short codes do match
|
||||||
*/
|
*/
|
||||||
fun userHasVerifiedShortCode()
|
fun userHasVerifiedShortCode()
|
||||||
|
|
||||||
|
|
||||||
|
fun shortCodeDoNotMatch()
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,7 @@ object EventType {
|
|||||||
const val KEY_VERIFICATION_MAC = "m.key.verification.mac"
|
const val KEY_VERIFICATION_MAC = "m.key.verification.mac"
|
||||||
const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel"
|
const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel"
|
||||||
const val KEY_VERIFICATION_DONE = "m.key.verification.done"
|
const val KEY_VERIFICATION_DONE = "m.key.verification.done"
|
||||||
|
const val KEY_VERIFICATION_READY = "m.key.verification.ready"
|
||||||
|
|
||||||
// Relation Events
|
// Relation Events
|
||||||
const val REACTION = "m.reaction"
|
const val REACTION = "m.reaction"
|
||||||
|
@ -89,4 +89,6 @@ interface RoomService {
|
|||||||
fun getRoomIdByAlias(roomAlias: String,
|
fun getRoomIdByAlias(roomAlias: String,
|
||||||
searchOnServer: Boolean,
|
searchOnServer: Boolean,
|
||||||
callback: MatrixCallback<Optional<String>>): Cancelable
|
callback: MatrixCallback<Optional<String>>): Cancelable
|
||||||
|
|
||||||
|
fun getExistingDirectRoomWithUser(otherUserId: String) : Room?
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* 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.api.session.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||||
|
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.verification.MessageVerificationReadyFactory
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoReady
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class MessageVerificationReadyContent(
|
||||||
|
@Json(name = "from_device") override val fromDevice: String? = null,
|
||||||
|
@Json(name = "methods") override val methods: List<String>? = null,
|
||||||
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
|
||||||
|
) : VerificationInfoReady {
|
||||||
|
|
||||||
|
override val transactionID: String?
|
||||||
|
get() = relatesTo?.eventId
|
||||||
|
|
||||||
|
override fun toEventContent() = this.toContent()
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MessageVerificationReadyFactory {
|
||||||
|
override fun create(tid: String, methods: List<String>, fromDevice: String): VerificationInfoReady {
|
||||||
|
return MessageVerificationReadyContent(
|
||||||
|
fromDevice = fromDevice,
|
||||||
|
methods = methods,
|
||||||
|
relatesTo = RelationDefaultContent(
|
||||||
|
RelationType.REFERENCE,
|
||||||
|
tid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.crypto.model.rest
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoReady
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests a key verification with another user's devices.
|
||||||
|
*/
|
||||||
|
@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 = "transaction_id") override var transactionID: String? = null
|
||||||
|
) : SendToDeviceObject, VerificationInfoReady {
|
||||||
|
|
||||||
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
return !transactionID.isNullOrBlank() && !fromDevice.isNullOrBlank() && !methods.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
}
|
@ -43,6 +43,7 @@ data class KeyVerificationStart(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERIF_METHOD_SAS = "m.sas.v1"
|
const val VERIF_METHOD_SAS = "m.sas.v1"
|
||||||
|
const val VERIF_METHOD_SCAN = "m.qr_code.scan.v1"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
override fun isValid(): Boolean {
|
||||||
|
@ -33,7 +33,8 @@ internal class DefaultIncomingSASVerificationTransaction(
|
|||||||
private val cryptoStore: IMXCryptoStore,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
deviceFingerprint: String,
|
deviceFingerprint: String,
|
||||||
transactionId: String,
|
transactionId: String,
|
||||||
otherUserID: String
|
otherUserID: String,
|
||||||
|
val autoAccept: Boolean = false
|
||||||
) : SASVerificationTransaction(
|
) : SASVerificationTransaction(
|
||||||
setDeviceVerificationAction,
|
setDeviceVerificationAction,
|
||||||
credentials,
|
credentials,
|
||||||
@ -76,6 +77,10 @@ internal class DefaultIncomingSASVerificationTransaction(
|
|||||||
this.startReq = startReq
|
this.startReq = startReq
|
||||||
state = SasVerificationTxState.OnStarted
|
state = SasVerificationTxState.OnStarted
|
||||||
this.otherDeviceId = startReq.fromDevice
|
this.otherDeviceId = startReq.fromDevice
|
||||||
|
|
||||||
|
if (autoAccept) {
|
||||||
|
performAccept()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun performAccept() {
|
override fun performAccept() {
|
||||||
|
@ -75,6 +75,12 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||||||
// map [sender : [transaction]]
|
// map [sender : [transaction]]
|
||||||
private val txMap = HashMap<String, HashMap<String, VerificationTransaction>>()
|
private val txMap = HashMap<String, HashMap<String, VerificationTransaction>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map [sender: [PendingVerificationRequest]]
|
||||||
|
*/
|
||||||
|
private val pendingRequests = HashMap<String, ArrayList<PendingVerificationRequest>>()
|
||||||
|
|
||||||
|
|
||||||
// Event received from the sync
|
// Event received from the sync
|
||||||
fun onToDeviceEvent(event: Event) {
|
fun onToDeviceEvent(event: Event) {
|
||||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||||
@ -120,6 +126,9 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||||||
EventType.KEY_VERIFICATION_MAC -> {
|
EventType.KEY_VERIFICATION_MAC -> {
|
||||||
onRoomMacReceived(event)
|
onRoomMacReceived(event)
|
||||||
}
|
}
|
||||||
|
EventType.KEY_VERIFICATION_READY -> {
|
||||||
|
onRoomReadyReceived(event)
|
||||||
|
}
|
||||||
EventType.KEY_VERIFICATION_DONE -> {
|
EventType.KEY_VERIFICATION_DONE -> {
|
||||||
// TODO?
|
// TODO?
|
||||||
}
|
}
|
||||||
@ -175,6 +184,31 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun dispatchRequestAdded(tx: PendingVerificationRequest) {
|
||||||
|
uiHandler.post {
|
||||||
|
listeners.forEach {
|
||||||
|
try {
|
||||||
|
it.verificationRequestCreated(tx)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Timber.e(e, "## Error while notifying listeners")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchRequestUpdated(tx: PendingVerificationRequest) {
|
||||||
|
uiHandler.post {
|
||||||
|
listeners.forEach {
|
||||||
|
try {
|
||||||
|
it.verificationRequestUpdated(tx)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Timber.e(e, "## Error while notifying listeners")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
|
override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
|
||||||
setDeviceVerificationAction.handle(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED,
|
setDeviceVerificationAction.handle(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED,
|
||||||
deviceID,
|
deviceID,
|
||||||
@ -326,6 +360,10 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||||||
// Ok we can create
|
// Ok we can create
|
||||||
if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
|
if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
|
||||||
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
|
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
|
||||||
|
// If there is a corresponding request, we can auto accept
|
||||||
|
// as we are the one requesting in first place (or we accepted the request)
|
||||||
|
val autoAccept = getExistingVerificationRequest(otherUserId)?.any { it.transactionId == startReq.transactionID }
|
||||||
|
?: false
|
||||||
val tx = DefaultIncomingSASVerificationTransaction(
|
val tx = DefaultIncomingSASVerificationTransaction(
|
||||||
// this,
|
// this,
|
||||||
setDeviceVerificationAction,
|
setDeviceVerificationAction,
|
||||||
@ -333,7 +371,8 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||||||
cryptoStore,
|
cryptoStore,
|
||||||
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
|
||||||
startReq.transactionID!!,
|
startReq.transactionID!!,
|
||||||
otherUserId).also { txConfigure(it) }
|
otherUserId,
|
||||||
|
autoAccept).also { txConfigure(it) }
|
||||||
addTransaction(tx)
|
addTransaction(tx)
|
||||||
tx.acceptVerificationEvent(otherUserId, startReq)
|
tx.acceptVerificationEvent(otherUserId, startReq)
|
||||||
} else {
|
} else {
|
||||||
@ -546,6 +585,15 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleReadyReceived(senderId: String, readyReq: VerificationInfoReady) {
|
||||||
|
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionID }
|
||||||
|
if (existingRequest == null) {
|
||||||
|
Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionID} fromDevice ${readyReq.fromDevice}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateOutgoingPendingRequest(existingRequest.copy(readyInfo = readyReq))
|
||||||
|
}
|
||||||
|
|
||||||
override fun getExistingTransaction(otherUser: String, tid: String): VerificationTransaction? {
|
override fun getExistingTransaction(otherUser: String, tid: String): VerificationTransaction? {
|
||||||
synchronized(lock = txMap) {
|
synchronized(lock = txMap) {
|
||||||
return txMap[otherUser]?.get(tid)
|
return txMap[otherUser]?.get(tid)
|
||||||
@ -647,6 +695,12 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
this.callback = object : MatrixCallback<SendResponse> {
|
this.callback = object : MatrixCallback<SendResponse> {
|
||||||
override fun onSuccess(data: SendResponse) {
|
override fun onSuccess(data: SendResponse) {
|
||||||
|
params.event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
|
||||||
|
updateOutgoingPendingRequest(verificationRequest.copy(
|
||||||
|
transactionId = data.eventId,
|
||||||
|
requestInfo = it
|
||||||
|
))
|
||||||
|
}
|
||||||
callback?.onSuccess(data.eventId)
|
callback?.onSuccess(data.eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -657,6 +711,24 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||||||
constraints = TaskConstraints(true)
|
constraints = TaskConstraints(true)
|
||||||
retryCount = 3
|
retryCount = 3
|
||||||
}.executeBy(taskExecutor)
|
}.executeBy(taskExecutor)
|
||||||
|
|
||||||
|
return verificationRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateOutgoingPendingRequest(updated: PendingVerificationRequest) {
|
||||||
|
val requestsForUser = pendingRequests[updated.otherUserId]
|
||||||
|
?: ArrayList<PendingVerificationRequest>().also {
|
||||||
|
pendingRequests[updated.otherUserId] = it
|
||||||
|
}
|
||||||
|
val index = requestsForUser.indexOfFirst {
|
||||||
|
it.transactionId == updated.transactionId
|
||||||
|
|| it.transactionId == null && it.localID == updated.localID
|
||||||
|
}
|
||||||
|
if (index != -1) {
|
||||||
|
requestsForUser.removeAt(index)
|
||||||
|
}
|
||||||
|
requestsForUser.add(updated)
|
||||||
|
dispatchRequestUpdated(updated)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun beginKeyVerificationInDMs(method: String, transactionId: String, roomId: String,
|
override fun beginKeyVerificationInDMs(method: String, transactionId: String, roomId: String,
|
||||||
@ -681,6 +753,27 @@ internal class DefaultSasVerificationService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun readyPendingVerificationInDMs(otherUserId: String, roomId: String, transactionId: String) {
|
||||||
|
// Let's find the related request
|
||||||
|
getExistingVerificationRequest(otherUserId)?.find { it.transactionId == transactionId }?.let {
|
||||||
|
//we need to send a ready event, with matching methods
|
||||||
|
val transport = sasTransportRoomMessageFactory.createTransport(roomId, cryptoService, null)
|
||||||
|
val methods = it.requestInfo?.methods?.intersect(listOf(KeyVerificationStart.VERIF_METHOD_SAS))?.toList()
|
||||||
|
if (methods.isNullOrEmpty()) {
|
||||||
|
Timber.i("Cannot ready this request, no common methods found txId:$transactionId")
|
||||||
|
return@let
|
||||||
|
}
|
||||||
|
//TODO this is not yet related to a transaction, maybe we should use another method like for cancel?
|
||||||
|
val readyMsg = transport.createReady(transactionId, credentials.deviceId ?: "", methods)
|
||||||
|
transport.sendToOther(EventType.KEY_VERIFICATION_READY, readyMsg,
|
||||||
|
SasVerificationTxState.None,
|
||||||
|
CancelCode.User,
|
||||||
|
null // TODO handle error?
|
||||||
|
)
|
||||||
|
updateOutgoingPendingRequest(it.copy(readyInfo = readyMsg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid
|
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid
|
||||||
*/
|
*/
|
||||||
|
@ -169,6 +169,11 @@ internal abstract class SASVerificationTransaction(
|
|||||||
} // if not wait for it
|
} // if not wait for it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun shortCodeDoNotMatch() {
|
||||||
|
Timber.v("## SAS short code do not match for id:$transactionId")
|
||||||
|
cancel(CancelCode.MismatchedSas)
|
||||||
|
}
|
||||||
|
|
||||||
override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) {
|
override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) {
|
||||||
when (info) {
|
when (info) {
|
||||||
is VerificationInfoStart -> onVerificationStart(info)
|
is VerificationInfoStart -> onVerificationStart(info)
|
||||||
|
@ -58,4 +58,7 @@ internal interface SasTransport {
|
|||||||
shortAuthenticationStrings: List<String>) : VerificationInfoStart
|
shortAuthenticationStrings: List<String>) : VerificationInfoStart
|
||||||
|
|
||||||
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
|
||||||
|
|
||||||
|
|
||||||
|
fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,17 @@ internal class SasTransportRoomMessage(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
|
||||||
|
return MessageVerificationReadyContent(
|
||||||
|
fromDevice = fromDevice,
|
||||||
|
relatesTo = RelationDefaultContent(
|
||||||
|
type = RelationType.REFERENCE,
|
||||||
|
eventId = tid
|
||||||
|
),
|
||||||
|
methods = methods
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class SasTransportRoomMessageFactory @Inject constructor(
|
internal class SasTransportRoomMessageFactory @Inject constructor(
|
||||||
|
@ -126,6 +126,14 @@ internal class SasTransportToDevice(
|
|||||||
messageAuthenticationCodes,
|
messageAuthenticationCodes,
|
||||||
shortAuthenticationStrings)
|
shortAuthenticationStrings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
|
||||||
|
return KeyVerificationReady(
|
||||||
|
transactionID = tid,
|
||||||
|
fromDevice = fromDevice,
|
||||||
|
methods = methods
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class SasTransportToDeviceFactory @Inject constructor(
|
internal class SasTransportToDeviceFactory @Inject constructor(
|
||||||
|
@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.crypto.verification
|
|||||||
import im.vector.matrix.android.api.session.events.model.Content
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceObject
|
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceObject
|
||||||
|
|
||||||
internal interface VerificationInfo {
|
interface VerificationInfo {
|
||||||
fun toEventContent(): Content? = null
|
fun toEventContent(): Content? = null
|
||||||
fun toSendToDeviceObject(): SendToDeviceObject? = null
|
fun toSendToDeviceObject(): SendToDeviceObject? = null
|
||||||
fun isValid() : Boolean
|
fun isValid() : Boolean
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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.crypto.verification
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A new event type is added to the key verification framework: m.key.verification.ready,
|
||||||
|
* which may be sent by the target of the m.key.verification.request message, upon receipt of the m.key.verification.request event.
|
||||||
|
*
|
||||||
|
* The m.key.verification.ready event is optional; the recipient of the m.key.verification.request event may respond directly
|
||||||
|
* with a m.key.verification.start event instead.
|
||||||
|
*/
|
||||||
|
interface VerificationInfoReady : VerificationInfo {
|
||||||
|
|
||||||
|
val transactionID: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the device that sent the m.key.verification.ready message
|
||||||
|
*/
|
||||||
|
val fromDevice: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of verification methods that the device supports
|
||||||
|
*/
|
||||||
|
val methods: List<String>?
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface MessageVerificationReadyFactory {
|
||||||
|
fun create(tid: String, methods: List<String>, fromDevice: String): VerificationInfoReady
|
||||||
|
}
|
@ -49,6 +49,7 @@ internal class VerificationMessageLiveObserver @Inject constructor(
|
|||||||
EventType.KEY_VERIFICATION_MAC,
|
EventType.KEY_VERIFICATION_MAC,
|
||||||
EventType.KEY_VERIFICATION_CANCEL,
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
EventType.KEY_VERIFICATION_DONE,
|
EventType.KEY_VERIFICATION_DONE,
|
||||||
|
EventType.KEY_VERIFICATION_READY,
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
EventType.ENCRYPTED)
|
EventType.ENCRYPTED)
|
||||||
)
|
)
|
||||||
|
@ -71,6 +71,20 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getExistingDirectRoomWithUser(otherUserId: String): Room? {
|
||||||
|
Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||||
|
val roomId = RoomSummaryEntity.where(realm)
|
||||||
|
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||||
|
.findAll()?.let { dms ->
|
||||||
|
dms.firstOrNull {
|
||||||
|
it.otherMemberIds.contains(otherUserId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?.roomId ?: return null
|
||||||
|
return RoomEntity.where(realm, roomId).findFirst()?.let { roomFactory.create(roomId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
|
override fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
|
||||||
return monarchy
|
return monarchy
|
||||||
.fetchCopyMap({
|
.fetchCopyMap({
|
||||||
|
@ -21,4 +21,21 @@
|
|||||||
|
|
||||||
<string name="key_verification_request_fallback_message">%s is requesting to verify your key, but your client does not support in-chat key verification. You will need to use legacy key verification to verify keys.</string>
|
<string name="key_verification_request_fallback_message">%s is requesting to verify your key, but your client does not support in-chat key verification. You will need to use legacy key verification to verify keys.</string>
|
||||||
|
|
||||||
|
<!-- 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="verify_by_emoji_title">Verify by Emoji</string>
|
||||||
|
<string name="verify_by_emoji_description">If you can’t 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="verification_request_alert_title">Verify %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>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -23,10 +23,7 @@ import dagger.Binds
|
|||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.multibindings.IntoMap
|
import dagger.multibindings.IntoMap
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
||||||
import im.vector.riotx.features.crypto.verification.SASVerificationIncomingFragment
|
import im.vector.riotx.features.crypto.verification.*
|
||||||
import im.vector.riotx.features.crypto.verification.SASVerificationShortCodeFragment
|
|
||||||
import im.vector.riotx.features.crypto.verification.SASVerificationStartFragment
|
|
||||||
import im.vector.riotx.features.crypto.verification.SASVerificationVerifiedFragment
|
|
||||||
import im.vector.riotx.features.home.HomeDetailFragment
|
import im.vector.riotx.features.home.HomeDetailFragment
|
||||||
import im.vector.riotx.features.home.HomeDrawerFragment
|
import im.vector.riotx.features.home.HomeDrawerFragment
|
||||||
import im.vector.riotx.features.home.LoadingFragment
|
import im.vector.riotx.features.home.LoadingFragment
|
||||||
|
@ -25,6 +25,7 @@ import im.vector.riotx.core.error.ErrorFormatter
|
|||||||
import im.vector.riotx.core.preference.UserAvatarPreference
|
import im.vector.riotx.core.preference.UserAvatarPreference
|
||||||
import im.vector.riotx.features.MainActivity
|
import im.vector.riotx.features.MainActivity
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||||
|
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
||||||
import im.vector.riotx.features.home.HomeActivity
|
import im.vector.riotx.features.home.HomeActivity
|
||||||
import im.vector.riotx.features.home.HomeModule
|
import im.vector.riotx.features.home.HomeModule
|
||||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomActivity
|
||||||
@ -133,6 +134,8 @@ interface ScreenComponent {
|
|||||||
|
|
||||||
fun inject(activity: SoftLogoutActivity)
|
fun inject(activity: SoftLogoutActivity)
|
||||||
|
|
||||||
|
fun inject(verificationBottomSheet: VerificationBottomSheet)
|
||||||
|
|
||||||
fun inject(permalinkHandlerActivity: PermalinkHandlerActivity)
|
fun inject(permalinkHandlerActivity: PermalinkHandlerActivity)
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
|
@ -24,7 +24,6 @@ import butterknife.OnClick
|
|||||||
import com.airbnb.mvrx.*
|
import com.airbnb.mvrx.*
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.core.platform.parentFragmentViewModel
|
|
||||||
import kotlinx.android.synthetic.main.fragment_bottom_sas_verification_code.*
|
import kotlinx.android.synthetic.main.fragment_bottom_sas_verification_code.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -21,10 +21,10 @@ import androidx.core.text.toSpannable
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import butterknife.OnClick
|
import butterknife.OnClick
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.parentFragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.core.platform.parentFragmentViewModel
|
|
||||||
import im.vector.riotx.core.utils.tappableMatchingText
|
import im.vector.riotx.core.utils.tappableMatchingText
|
||||||
import kotlinx.android.synthetic.main.fragment_verification_choose_method.*
|
import kotlinx.android.synthetic.main.fragment_verification_choose_method.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -19,11 +19,11 @@ import android.os.Parcelable
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import butterknife.OnClick
|
import butterknife.OnClick
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.parentFragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.setTextOrHide
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.core.platform.parentFragmentViewModel
|
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.fragment_verification_conclusion.*
|
import kotlinx.android.synthetic.main.fragment_verification_conclusion.*
|
||||||
@ -56,7 +56,9 @@ class VerificationConclusionFragment @Inject constructor() : VectorBaseFragment(
|
|||||||
verifyConclusionDescription.setTextOrHide(null)
|
verifyConclusionDescription.setTextOrHide(null)
|
||||||
verifyConclusionImageView.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_shield_warning))
|
verifyConclusionImageView.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_shield_warning))
|
||||||
|
|
||||||
verifyConclusionBottomDescription.text = Markwon.builder(requireContext()).build().toMarkdown(getString(R.string.verification_conclusion_compromised))
|
verifyConclusionBottomDescription.text = Markwon.builder(requireContext())
|
||||||
|
.build()
|
||||||
|
.toMarkdown(getString(R.string.verification_conclusion_compromised))
|
||||||
}
|
}
|
||||||
ConclusionState.CANCELLED -> {
|
ConclusionState.CANCELLED -> {
|
||||||
// Just dismiss in this case
|
// Just dismiss in this case
|
||||||
|
@ -22,10 +22,10 @@ import androidx.core.view.isVisible
|
|||||||
import butterknife.OnClick
|
import butterknife.OnClick
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.parentFragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.core.platform.parentFragmentViewModel
|
|
||||||
import im.vector.riotx.core.utils.colorizeMatchingText
|
import im.vector.riotx.core.utils.colorizeMatchingText
|
||||||
import im.vector.riotx.core.utils.styleMatchingText
|
import im.vector.riotx.core.utils.styleMatchingText
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
@ -66,4 +66,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||||||
|
|
||||||
data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction()
|
data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction()
|
||||||
data class DeclineVerificationRequest(val transactionId: String) : RoomDetailAction()
|
data class DeclineVerificationRequest(val transactionId: String) : RoomDetailAction()
|
||||||
|
|
||||||
|
data class RequestVerification(val userId: String) : RoomDetailAction()
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,7 @@ import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter
|
|||||||
import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter
|
import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter
|
||||||
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
||||||
import im.vector.riotx.features.command.Command
|
import im.vector.riotx.features.command.Command
|
||||||
|
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.getColorFromUserId
|
import im.vector.riotx.features.home.getColorFromUserId
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerAction
|
import im.vector.riotx.features.home.room.detail.composer.TextComposerAction
|
||||||
|
55
vector/src/main/res/layout/bottom_sheet_verification.xml
Normal file
55
vector/src/main/res/layout/bottom_sheet_verification.xml
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/bottomSheetScrollView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
|
android:fadeScrollbars="false"
|
||||||
|
android:scrollbars="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
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>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/bottomSheetFragmentContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
73
vector/src/main/res/layout/fragment_verification_request.xml
Normal file
73
vector/src/main/res/layout/fragment_verification_request.xml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?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/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"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<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"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/verificationStartButton"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/verificationStartButton"
|
||||||
|
tools:text="@string/verification_request_waiting_for" />
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in New Issue
Block a user