Merge pull request #1218 from vector-im/feature/scan_confirm_update

Feature/scan confirm update
This commit is contained in:
Valere 2020-04-10 16:05:02 +02:00 committed by GitHub
commit ac46fe9e16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 256 additions and 66 deletions

View File

@ -15,6 +15,7 @@ Improvements 🙌:
- Cross-Signing | Gossip key backup recovery key (#1200) - Cross-Signing | Gossip key backup recovery key (#1200)
- Show room encryption status as a bubble tile (#1078) - Show room encryption status as a bubble tile (#1078)
- Cross-Signing | Restore history after recover from passphrase (#1214) - Cross-Signing | Restore history after recover from passphrase (#1214)
- Cross-Sign | QR code scan confirmation screens design update (#1187)
Bugfix 🐛: Bugfix 🐛:
- Missing avatar/displayname after verification request message (#841) - Missing avatar/displayname after verification request message (#841)

View File

@ -43,6 +43,7 @@ sealed class VerificationTxState {
// Will be used to ask the user if the other user has correctly scanned // Will be used to ask the user if the other user has correctly scanned
object QrScannedByOther : VerificationQrTxState() object QrScannedByOther : VerificationQrTxState()
object WaitingOtherReciprocateConfirm : VerificationQrTxState()
// Terminal states // Terminal states
abstract class TerminalTxState : VerificationTxState() abstract class TerminalTxState : VerificationTxState()

View File

@ -646,9 +646,7 @@ internal class DefaultVerificationService @Inject constructor(
)) ))
} }
if (existingTransaction is SASDefaultVerificationTransaction) { existingTransaction?.state = VerificationTxState.Cancelled(safeValueOf(cancelReq.code), false)
existingTransaction.state = VerificationTxState.Cancelled(safeValueOf(cancelReq.code), false)
}
} }
private fun onRoomAcceptReceived(event: Event) { private fun onRoomAcceptReceived(event: Event) {
@ -792,26 +790,53 @@ internal class DefaultVerificationService @Inject constructor(
private fun onDoneReceived(event: Event) { private fun onDoneReceived(event: Event) {
Timber.v("## onDoneReceived") Timber.v("## onDoneReceived")
val doneReq = event.getClearContent().toModel<KeyVerificationDone>()?.asValidObject() val doneReq = event.getClearContent().toModel<KeyVerificationDone>()?.asValidObject()
if (doneReq == null || event.senderId != userId) { if (doneReq == null || event.senderId == null) {
// ignore // ignore
Timber.e("## SAS Received invalid done request") Timber.e("## SAS Received invalid done request")
return return
} }
// We only send gossiping request when the other sent us a done handleDoneReceived(event.senderId, doneReq)
// We can ask without checking too much thinks (like trust), because we will check validity of secret on reception
getExistingTransaction(userId, doneReq.transactionId) if (event.senderId == userId) {
?: getOldTransaction(userId, doneReq.transactionId) // We only send gossiping request when the other sent us a done
?.let { vt -> // We can ask without checking too much thinks (like trust), because we will check validity of secret on reception
val otherDeviceId = vt.otherDeviceId getExistingTransaction(userId, doneReq.transactionId)
if (!crossSigningService.canCrossSign()) { ?: getOldTransaction(userId, doneReq.transactionId)
outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?.let { vt ->
?: "*"))) val otherDeviceId = vt.otherDeviceId
outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId if (!crossSigningService.canCrossSign()) {
outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
?: "*")))
outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
?: "*")))
}
outgoingGossipingRequestManager.sendSecretShareRequest(KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
?: "*"))) ?: "*")))
} }
outgoingGossipingRequestManager.sendSecretShareRequest(KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) }
} }
private fun handleDoneReceived(senderId: String, doneReq: ValidVerificationDone) {
Timber.v("## SAS Done receieved $doneReq")
val existing = getExistingTransaction(senderId, doneReq.transactionId)
if (existing == null) {
Timber.e("## SAS Received invalid Done request")
return
}
if (existing is DefaultQrCodeVerificationTransaction) {
existing.onDoneReceived()
} else {
// SAS do not care for now?
}
// Now transactions are udated, let's also update Requests
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneReq.transactionId }
if (existingRequest == null) {
Timber.e("## SAS Received Done for unknown request txId:${doneReq.transactionId}")
return
}
updatePendingRequest(existingRequest.copy(isSuccessful = true))
} }
private fun onRoomDoneReceived(event: Event) { private fun onRoomDoneReceived(event: Event) {
@ -993,14 +1018,14 @@ internal class DefaultVerificationService @Inject constructor(
) )
} }
private fun handleDoneReceived(senderId: String, doneInfo: ValidVerificationDone) { // private fun handleDoneReceived(senderId: String, doneInfo: ValidVerificationDone) {
val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionId } // val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneInfo.transactionId }
if (existingRequest == null) { // if (existingRequest == null) {
Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionId}") // Timber.e("## SAS Received Done for unknown request txId:${doneInfo.transactionId}")
return // return
} // }
updatePendingRequest(existingRequest.copy(isSuccessful = true)) // updatePendingRequest(existingRequest.copy(isSuccessful = true))
} // }
// TODO All this methods should be delegated to a TransactionStore // TODO All this methods should be delegated to a TransactionStore
override fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? { override fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? {

View File

@ -57,7 +57,7 @@ internal abstract class DefaultVerificationTransaction(
protected fun trust(canTrustOtherUserMasterKey: Boolean, protected fun trust(canTrustOtherUserMasterKey: Boolean,
toVerifyDeviceIds: List<String>, toVerifyDeviceIds: List<String>,
eventuallyMarkMyMasterKeyAsTrusted: Boolean) { eventuallyMarkMyMasterKeyAsTrusted: Boolean, autoDone : Boolean = true) {
Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds") Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds")
Timber.d("## Verification: trust Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted") Timber.d("## Verification: trust Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted")
@ -97,14 +97,9 @@ internal abstract class DefaultVerificationTransaction(
}) })
} }
state = VerificationTxState.Verified if (autoDone) {
state = VerificationTxState.Verified
transport.done(transactionId) { transport.done(transactionId) {}
// if (otherUserId == userId && !crossSigningService.canCrossSign()) {
// outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*")))
// outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*")))
// outgoingGossipingRequestManager.sendSecretShareRequest(KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*")))
// }
} }
} }

View File

@ -15,12 +15,12 @@
*/ */
package im.vector.matrix.android.internal.crypto.verification package im.vector.matrix.android.internal.crypto.verification
internal interface VerificationInfoDone : VerificationInfo<ValidVerificationInfoDone> { import im.vector.matrix.android.api.session.room.model.message.ValidVerificationDone
override fun asValidObject(): ValidVerificationInfoDone? { internal interface VerificationInfoDone : VerificationInfo<ValidVerificationDone> {
override fun asValidObject(): ValidVerificationDone? {
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
return ValidVerificationInfoDone(validTransactionId) return ValidVerificationDone(validTransactionId)
} }
} }
internal data class ValidVerificationInfoDone(val transactionId: String)

View File

@ -187,9 +187,12 @@ internal class DefaultQrCodeVerificationTransaction(
// qrCodeData.sharedSecret will be used to send the start request // qrCodeData.sharedSecret will be used to send the start request
start(otherQrCodeData.sharedSecret) start(otherQrCodeData.sharedSecret)
trust(canTrustOtherUserMasterKey, trust(
toVerifyDeviceIds.distinct(), canTrustOtherUserMasterKey = canTrustOtherUserMasterKey,
eventuallyMarkMyMasterKeyAsTrusted = true) toVerifyDeviceIds = toVerifyDeviceIds.distinct(),
eventuallyMarkMyMasterKeyAsTrusted = true,
autoDone = false
)
} }
private fun start(remoteSecret: String, onDone: (() -> Unit)? = null) { private fun start(remoteSecret: String, onDone: (() -> Unit)? = null) {
@ -199,6 +202,7 @@ internal class DefaultQrCodeVerificationTransaction(
throw IllegalStateException("Interactive Key verification already started") throw IllegalStateException("Interactive Key verification already started")
} }
state = VerificationTxState.Started
val startMessage = transport.createStartForQrCode( val startMessage = transport.createStartForQrCode(
deviceId, deviceId,
transactionId, transactionId,
@ -208,7 +212,7 @@ internal class DefaultQrCodeVerificationTransaction(
transport.sendToOther( transport.sendToOther(
EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_START,
startMessage, startMessage,
VerificationTxState.Started, VerificationTxState.WaitingOtherReciprocateConfirm,
CancelCode.User, CancelCode.User,
onDone onDone
) )
@ -244,6 +248,15 @@ internal class DefaultQrCodeVerificationTransaction(
} }
} }
fun onDoneReceived() {
if (state != VerificationTxState.WaitingOtherReciprocateConfirm) {
cancel(CancelCode.UnexpectedMessage)
return
}
state = VerificationTxState.Verified
transport.done(transactionId) {}
}
override fun otherUserScannedMyQrCode() { override fun otherUserScannedMyQrCode() {
when (qrCodeData) { when (qrCodeData) {
is QrCodeData.VerifyingAnotherUser -> { is QrCodeData.VerifyingAnotherUser -> {
@ -265,6 +278,6 @@ internal class DefaultQrCodeVerificationTransaction(
override fun otherUserDidNotScannedMyQrCode() { override fun otherUserDidNotScannedMyQrCode() {
// What can I do then? // What can I do then?
// At least remove the transaction... // At least remove the transaction...
state = VerificationTxState.Cancelled(CancelCode.MismatchedKeys, true) cancel(CancelCode.MismatchedKeys)
} }
} }

View File

@ -37,6 +37,7 @@ import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFrag
import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment 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.emoji.VerificationEmojiCodeFragment
import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQRWaitingFragment
import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment
import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
import im.vector.riotx.features.grouplist.GroupListFragment import im.vector.riotx.features.grouplist.GroupListFragment
@ -339,6 +340,11 @@ interface FragmentModule {
@FragmentKey(VerificationQrScannedByOtherFragment::class) @FragmentKey(VerificationQrScannedByOtherFragment::class)
fun bindVerificationQrScannedByOtherFragment(fragment: VerificationQrScannedByOtherFragment): Fragment fun bindVerificationQrScannedByOtherFragment(fragment: VerificationQrScannedByOtherFragment): Fragment
@Binds
@IntoMap
@FragmentKey(VerificationQRWaitingFragment::class)
fun bindVerificationQRWaitingFragment(fragment: VerificationQRWaitingFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(VerificationConclusionFragment::class) @FragmentKey(VerificationConclusionFragment::class)

View File

@ -50,6 +50,7 @@ import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFrag
import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment
import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment 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.emoji.VerificationEmojiCodeFragment
import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQRWaitingFragment
import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment
import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
@ -244,6 +245,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
showFragment(VerificationQrScannedByOtherFragment::class, Bundle()) showFragment(VerificationQrScannedByOtherFragment::class, Bundle())
return@withState return@withState
} }
is VerificationTxState.Started,
is VerificationTxState.WaitingOtherReciprocateConfirm -> {
showFragment(VerificationQRWaitingFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationQRWaitingFragment.Args(state.isMe, state.otherUserMxItem?.getBestName() ?: ""))
})
return@withState
}
is VerificationTxState.Verified -> { is VerificationTxState.Verified -> {
showFragment(VerificationConclusionFragment::class, Bundle().apply { showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe))

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.crypto.verification.qrconfirmation
import com.airbnb.epoxy.EpoxyController
import im.vector.riotx.R
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem
import javax.inject.Inject
class VerificationQRWaitingController @Inject constructor(
private val stringProvider: StringProvider,
private val colorProvider: ColorProvider
) : EpoxyController() {
private var args: VerificationQRWaitingFragment.Args? = null
fun update(args: VerificationQRWaitingFragment.Args) {
this.args = args
requestModelBuild()
}
override fun buildModels() {
val params = args ?: return
bottomSheetVerificationNoticeItem {
id("notice")
apply {
notice(stringProvider.getString(R.string.qr_code_scanned_verif_waiting_notice))
}
}
bottomSheetVerificationBigImageItem {
id("image")
imageRes(R.drawable.ic_shield_trusted)
}
bottomSheetVerificationWaitingItem {
id("waiting")
title(stringProvider.getString(R.string.qr_code_scanned_verif_waiting, params.otherUserName))
}
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.crypto.verification.qrconfirmation
import android.os.Bundle
import android.os.Parcelable
import android.view.View
import com.airbnb.mvrx.MvRx
import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.platform.VectorBaseFragment
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import javax.inject.Inject
class VerificationQRWaitingFragment @Inject constructor(
val controller: VerificationQRWaitingController
) : VectorBaseFragment() {
@Parcelize
data class Args(
val isMe: Boolean,
val otherUserName: String
) : Parcelable
override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
(arguments?.getParcelable(MvRx.KEY_ARG) as? Args)?.let {
controller.update(it)
}
}
override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup()
super.onDestroyView()
}
private fun setupRecyclerView() {
bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
}
}

View File

@ -21,7 +21,9 @@ import im.vector.riotx.R
import im.vector.riotx.core.epoxy.dividerItem import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewState
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
import javax.inject.Inject import javax.inject.Inject
@ -32,33 +34,37 @@ class VerificationQrScannedByOtherController @Inject constructor(
var listener: Listener? = null var listener: Listener? = null
init { private var viewState: VerificationBottomSheetViewState? = null
fun update(viewState: VerificationBottomSheetViewState) {
this.viewState = viewState
requestModelBuild() requestModelBuild()
} }
override fun buildModels() { override fun buildModels() {
val state = viewState ?: return
bottomSheetVerificationNoticeItem { bottomSheetVerificationNoticeItem {
id("notice") id("notice")
notice(stringProvider.getString(R.string.qr_code_scanned_by_other_notice)) apply {
if (state.isMe) {
notice(stringProvider.getString(R.string.qr_code_scanned_self_verif_notice))
} else {
val name = state.otherUserMxItem?.getBestName() ?: ""
notice(stringProvider.getString(R.string.qr_code_scanned_by_other_notice, name))
}
}
}
bottomSheetVerificationBigImageItem {
id("image")
imageRes(R.drawable.ic_shield_trusted)
} }
dividerItem { dividerItem {
id("sep0") id("sep0")
} }
bottomSheetVerificationActionItem {
id("confirm")
title(stringProvider.getString(R.string.qr_code_scanned_by_other_yes))
titleColor(colorProvider.getColor(R.color.riotx_accent))
iconRes(R.drawable.ic_check_on)
iconColor(colorProvider.getColor(R.color.riotx_accent))
listener { listener?.onUserConfirmsQrCodeScanned() }
}
dividerItem {
id("sep1")
}
bottomSheetVerificationActionItem { bottomSheetVerificationActionItem {
id("deny") id("deny")
title(stringProvider.getString(R.string.qr_code_scanned_by_other_no)) title(stringProvider.getString(R.string.qr_code_scanned_by_other_no))
@ -67,6 +73,19 @@ class VerificationQrScannedByOtherController @Inject constructor(
iconColor(colorProvider.getColor(R.color.vector_error_color)) iconColor(colorProvider.getColor(R.color.vector_error_color))
listener { listener?.onUserDeniesQrCodeScanned() } listener { listener?.onUserDeniesQrCodeScanned() }
} }
dividerItem {
id("sep1")
}
bottomSheetVerificationActionItem {
id("confirm")
title(stringProvider.getString(R.string.qr_code_scanned_by_other_yes))
titleColor(colorProvider.getColor(R.color.riotx_accent))
iconRes(R.drawable.ic_check_on)
iconColor(colorProvider.getColor(R.color.riotx_accent))
listener { listener?.onUserConfirmsQrCodeScanned() }
}
} }
interface Listener { interface Listener {

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.crypto.verification.qrconfirmation
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.configureWith
@ -37,10 +38,13 @@ class VerificationQrScannedByOtherFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupRecyclerView() setupRecyclerView()
} }
override fun invalidate() = withState(sharedViewModel) { state ->
controller.update(state)
}
override fun onDestroyView() { override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup() bottomSheetVerificationRecyclerView.cleanup()
controller.listener = null controller.listener = null

View File

@ -2166,7 +2166,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada.</string>
<string name="a11y_qr_code_for_verification">QR kodea</string> <string name="a11y_qr_code_for_verification">QR kodea</string>
<string name="qr_code_scanned_by_other_notice">Beste erabiltzaileak QR kodea ongi eskaneatu du\?</string>
<string name="qr_code_scanned_by_other_yes">Bai</string> <string name="qr_code_scanned_by_other_yes">Bai</string>
<string name="qr_code_scanned_by_other_no">Ez</string> <string name="qr_code_scanned_by_other_no">Ez</string>

View File

@ -2174,7 +2174,6 @@ Si vous navez pas configuré de nouvelle méthode de récupération, un attaq
<string name="a11y_qr_code_for_verification">Code QR</string> <string name="a11y_qr_code_for_verification">Code QR</string>
<string name="qr_code_scanned_by_other_notice">Lautre utilisateur a-t-il bien scanné le code QR \?</string>
<string name="qr_code_scanned_by_other_yes">Oui</string> <string name="qr_code_scanned_by_other_yes">Oui</string>
<string name="qr_code_scanned_by_other_no">Non</string> <string name="qr_code_scanned_by_other_no">Non</string>

View File

@ -2169,7 +2169,6 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró
<string name="a11y_qr_code_for_verification">QR kód</string> <string name="a11y_qr_code_for_verification">QR kód</string>
<string name="qr_code_scanned_by_other_notice">A másik felhasználó sikeresen beolvasta a QR kódot\?</string>
<string name="qr_code_scanned_by_other_yes">Igen</string> <string name="qr_code_scanned_by_other_yes">Igen</string>
<string name="qr_code_scanned_by_other_no">Nem</string> <string name="qr_code_scanned_by_other_no">Nem</string>

View File

@ -2219,7 +2219,6 @@
<string name="a11y_qr_code_for_verification">Codice QR</string> <string name="a11y_qr_code_for_verification">Codice QR</string>
<string name="qr_code_scanned_by_other_notice">L\'altro utente ha scansionato correttamente il codice QR\?</string>
<string name="qr_code_scanned_by_other_yes"></string> <string name="qr_code_scanned_by_other_yes"></string>
<string name="qr_code_scanned_by_other_no">No</string> <string name="qr_code_scanned_by_other_no">No</string>

View File

@ -2129,7 +2129,6 @@ Që të garantoni se sju shpëton gjë, thjesht mbajeni të aktivizuar mekani
<string name="initialize_cross_signing">Gatit <em>CrossSigning</em></string> <string name="initialize_cross_signing">Gatit <em>CrossSigning</em></string>
<string name="reset_cross_signing">Zeroji Kyçet</string> <string name="reset_cross_signing">Zeroji Kyçet</string>
<string name="qr_code_scanned_by_other_notice">A e skanoi me sukses përdoruesi tjetër kodin QR\?</string>
<string name="qr_code_scanned_by_other_no">Jo</string> <string name="qr_code_scanned_by_other_no">Jo</string>
<string name="no_connectivity_to_the_server_indicator">Humbi lidhja me shërbyesin</string> <string name="no_connectivity_to_the_server_indicator">Humbi lidhja me shërbyesin</string>

View File

@ -2119,7 +2119,6 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意
<string name="a11y_qr_code_for_verification">QR code</string> <string name="a11y_qr_code_for_verification">QR code</string>
<string name="qr_code_scanned_by_other_notice">其他使用者是否掃苗 QR code 成功?</string>
<string name="qr_code_scanned_by_other_yes"></string> <string name="qr_code_scanned_by_other_yes"></string>
<string name="qr_code_scanned_by_other_no"></string> <string name="qr_code_scanned_by_other_no"></string>

View File

@ -2148,7 +2148,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
<string name="a11y_qr_code_for_verification">QR code</string> <string name="a11y_qr_code_for_verification">QR code</string>
<string name="qr_code_scanned_by_other_notice">Did the other user successfully scan the QR code?</string> <string name="qr_code_scanned_by_other_notice">Almost there! Is %s showing the same shield?</string>
<string name="qr_code_scanned_by_other_yes">Yes</string> <string name="qr_code_scanned_by_other_yes">Yes</string>
<string name="qr_code_scanned_by_other_no">No</string> <string name="qr_code_scanned_by_other_no">No</string>

View File

@ -94,6 +94,11 @@
<string name="encryption_unknown_algorithm_tile_description">The encryption used by this room is not supported</string> <string name="encryption_unknown_algorithm_tile_description">The encryption used by this room is not supported</string>
<string name="room_created_summary_item">%s created and configured the room.</string> <string name="room_created_summary_item">%s created and configured the room.</string>
<string name="qr_code_scanned_self_verif_notice">Almost there! Is the other device showing the same shield?</string>
<string name="qr_code_scanned_verif_waiting_notice">Almost there! Waiting for confirmation…</string>
<string name="qr_code_scanned_verif_waiting">Waiting for %s…</string>
<string name="error_failed_to_import_keys">Failed to import keys</string> <string name="error_failed_to_import_keys">Failed to import keys</string>
<!-- END Strings added by Valere --> <!-- END Strings added by Valere -->