mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Verification: migrate to Epoxy - Conclusion
This commit is contained in:
parent
7170471686
commit
cd1665a8e8
6 changed files with 209 additions and 76 deletions
|
@ -21,6 +21,7 @@ import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.transition.AutoTransition
|
import androidx.transition.AutoTransition
|
||||||
|
@ -72,6 +73,9 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
@BindView(R.id.verificationRequestName)
|
@BindView(R.id.verificationRequestName)
|
||||||
lateinit var otherUserNameText: TextView
|
lateinit var otherUserNameText: TextView
|
||||||
|
|
||||||
|
@BindView(R.id.verificationRequestShield)
|
||||||
|
lateinit var otherUserShield: View
|
||||||
|
|
||||||
@BindView(R.id.verificationRequestAvatar)
|
@BindView(R.id.verificationRequestAvatar)
|
||||||
lateinit var otherUserAvatarImageView: ImageView
|
lateinit var otherUserAvatarImageView: ImageView
|
||||||
|
|
||||||
|
@ -95,8 +99,15 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) {
|
override fun invalidate() = withState(viewModel) {
|
||||||
it.otherUserMxItem?.let { matrixItem ->
|
it.otherUserMxItem?.let { matrixItem ->
|
||||||
otherUserNameText.text = getString(R.string.verification_request_alert_title, matrixItem.getBestName())
|
|
||||||
avatarRenderer.render(matrixItem, otherUserAvatarImageView)
|
avatarRenderer.render(matrixItem, otherUserAvatarImageView)
|
||||||
|
|
||||||
|
if(it.sasTransactionState == SasVerificationTxState.Verified) {
|
||||||
|
otherUserNameText.text = getString(R.string.verification_verified_user, matrixItem.getBestName())
|
||||||
|
otherUserShield.isVisible = true
|
||||||
|
} else {
|
||||||
|
otherUserNameText.text = getString(R.string.verification_verify_user, matrixItem.getBestName())
|
||||||
|
otherUserShield.isVisible = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Did the request result in a SAS transaction?
|
// Did the request result in a SAS transaction?
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* 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.bottomsheet.bottomSheetSeparatorItem
|
||||||
|
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
|
||||||
|
|
||||||
|
init {
|
||||||
|
// We are requesting a model build directly as the first build of epoxy is on the main thread.
|
||||||
|
// It avoids to build the whole list on the main thread.
|
||||||
|
requestModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetSeparatorItem {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,24 +15,25 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.riotx.features.crypto.verification.conclusion
|
package im.vector.riotx.features.crypto.verification.conclusion
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.core.content.ContextCompat
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import butterknife.OnClick
|
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.parentFragmentViewModel
|
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.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.features.crypto.verification.VerificationAction
|
import im.vector.riotx.features.crypto.verification.VerificationAction
|
||||||
import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel
|
import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel
|
||||||
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.bottom_sheet_verification_child_fragment.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class VerificationConclusionFragment @Inject constructor() : VectorBaseFragment() {
|
class VerificationConclusionFragment @Inject constructor(
|
||||||
|
val controller: VerificationConclusionController
|
||||||
|
) : VectorBaseFragment(), VerificationConclusionController.Listener {
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Args(
|
data class Args(
|
||||||
|
@ -40,38 +41,39 @@ class VerificationConclusionFragment @Inject constructor() : VectorBaseFragment(
|
||||||
val cancelReason: String?
|
val cancelReason: String?
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_verification_conclusion
|
|
||||||
|
|
||||||
private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
|
private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
|
||||||
|
|
||||||
private val viewModel by fragmentViewModel(VerificationConclusionViewModel::class)
|
private val viewModel by fragmentViewModel(VerificationConclusionViewModel::class)
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) {
|
override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
|
||||||
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())
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
.build()
|
super.onViewCreated(view, savedInstanceState)
|
||||||
.toMarkdown(getString(R.string.verification_conclusion_compromised))
|
|
||||||
}
|
setupRecyclerView()
|
||||||
ConclusionState.CANCELLED -> {
|
}
|
||||||
// Just dismiss in this case
|
|
||||||
sharedViewModel.handle(VerificationAction.GotItConclusion)
|
override fun onDestroyView() {
|
||||||
}
|
bottomSheetVerificationRecyclerView.cleanup()
|
||||||
|
controller.listener = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecyclerView() {
|
||||||
|
bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false)
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.verificationConclusionButton)
|
override fun onButtonTapped() {
|
||||||
fun onButtonTapped() {
|
|
||||||
sharedViewModel.handle(VerificationAction.GotItConclusion)
|
sharedViewModel.handle(VerificationAction.GotItConclusion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.crypto.sas.safeValueOf
|
||||||
import im.vector.riotx.core.platform.EmptyAction
|
import im.vector.riotx.core.platform.EmptyAction
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
|
||||||
data class SASVerificationConclusionViewState(
|
data class VerificationConclusionViewState(
|
||||||
val conclusionState: ConclusionState = ConclusionState.CANCELLED
|
val conclusionState: ConclusionState = ConclusionState.CANCELLED
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
||||||
|
@ -33,22 +33,22 @@ enum class ConclusionState {
|
||||||
CANCELLED
|
CANCELLED
|
||||||
}
|
}
|
||||||
|
|
||||||
class VerificationConclusionViewModel(initialState: SASVerificationConclusionViewState)
|
class VerificationConclusionViewModel(initialState: VerificationConclusionViewState)
|
||||||
: VectorViewModel<SASVerificationConclusionViewState, EmptyAction>(initialState) {
|
: VectorViewModel<VerificationConclusionViewState, EmptyAction>(initialState) {
|
||||||
|
|
||||||
companion object : MvRxViewModelFactory<VerificationConclusionViewModel, SASVerificationConclusionViewState> {
|
companion object : MvRxViewModelFactory<VerificationConclusionViewModel, VerificationConclusionViewState> {
|
||||||
|
|
||||||
override fun initialState(viewModelContext: ViewModelContext): SASVerificationConclusionViewState? {
|
override fun initialState(viewModelContext: ViewModelContext): VerificationConclusionViewState? {
|
||||||
val args = viewModelContext.args<VerificationConclusionFragment.Args>()
|
val args = viewModelContext.args<VerificationConclusionFragment.Args>()
|
||||||
|
|
||||||
return when (safeValueOf(args.cancelReason)) {
|
return when (safeValueOf(args.cancelReason)) {
|
||||||
CancelCode.MismatchedSas,
|
CancelCode.MismatchedSas,
|
||||||
CancelCode.MismatchedCommitment,
|
CancelCode.MismatchedCommitment,
|
||||||
CancelCode.MismatchedKeys -> {
|
CancelCode.MismatchedKeys -> {
|
||||||
SASVerificationConclusionViewState(ConclusionState.WARNING)
|
VerificationConclusionViewState(ConclusionState.WARNING)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
SASVerificationConclusionViewState(
|
VerificationConclusionViewState(
|
||||||
if (args.isSuccessFull) ConclusionState.SUCCESS
|
if (args.isSuccessFull) ConclusionState.SUCCESS
|
||||||
else ConclusionState.CANCELLED
|
else ConclusionState.CANCELLED
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/bottomSheetScrollView"
|
android:id="@+id/bottomSheetScrollView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -9,47 +10,62 @@
|
||||||
android:fadeScrollbars="false"
|
android:fadeScrollbars="false"
|
||||||
android:scrollbars="vertical">
|
android:scrollbars="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
<ImageView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/verificationRequestAvatar"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:background="@drawable/circle"
|
||||||
|
android:contentDescription="@string/avatar"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/verificationRequestShield"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:src="@drawable/ic_shield_trusted"
|
||||||
|
app:layout_constraintCircle="@+id/verificationRequestAvatar"
|
||||||
|
app:layout_constraintCircleAngle="135"
|
||||||
|
app:layout_constraintCircleRadius="16dp"
|
||||||
|
tools:ignore="MissingConstraints" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/verificationRequestName"
|
||||||
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_marginStart="8dp"
|
||||||
android:paddingStart="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:paddingEnd="16dp"
|
android:layout_weight="1"
|
||||||
android:paddingTop="16dp">
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
<ImageView
|
android:textColor="?riotx_text_primary"
|
||||||
android:id="@+id/verificationRequestAvatar"
|
android:textSize="20sp"
|
||||||
android:layout_width="32dp"
|
android:textStyle="bold"
|
||||||
android:layout_height="32dp"
|
app:layout_constraintBottom_toBottomOf="@+id/verificationRequestAvatar"
|
||||||
android:adjustViewBounds="true"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:background="@drawable/circle"
|
app:layout_constraintStart_toEndOf="@+id/verificationRequestAvatar"
|
||||||
android:contentDescription="@string/avatar"
|
app:layout_constraintTop_toTopOf="@+id/verificationRequestAvatar"
|
||||||
android:scaleType="centerCrop"
|
tools:text="@string/verification_verify_user" />
|
||||||
tools:src="@tools:sample/avatars" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/verificationRequestName"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/verification_request_alert_title"
|
|
||||||
android:textColor="?riotx_text_primary"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/bottomSheetFragmentContainer"
|
android:id="@+id/bottomSheetFragmentContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp" />
|
android:layout_marginTop="16dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/verificationRequestAvatar" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
|
@ -46,7 +46,8 @@
|
||||||
|
|
||||||
<string name="aria_qr_code_description">QR code image</string>
|
<string name="aria_qr_code_description">QR code image</string>
|
||||||
|
|
||||||
<string name="verification_request_alert_title">Verify %s</string>
|
<string name="verification_verify_user">Verify %s</string>
|
||||||
|
<string name="verification_verified_user">Verified %s</string>
|
||||||
<string name="verification_request_waiting_for">Waiting for %s…</string>
|
<string name="verification_request_waiting_for">Waiting for %s…</string>
|
||||||
<string name="verification_request_alert_description">For extra security, verify %s by checking a one-time code on both your devices.\n\nFor maximum security, do this in person.</string>
|
<string name="verification_request_alert_description">For extra security, verify %s by checking a one-time code on both your devices.\n\nFor maximum security, do this in person.</string>
|
||||||
<string name="room_profile_not_encrypted_subtitle">Messages in this room are not end-to-end encrypted.</string>
|
<string name="room_profile_not_encrypted_subtitle">Messages in this room are not end-to-end encrypted.</string>
|
||||||
|
@ -90,6 +91,7 @@
|
||||||
<string name="verification_request_start_notice">For maximum security, do this in person.</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_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 the other user\'s screen.</string>
|
<string name="verification_code_notice">Compare the code with the one displayed on the other user\'s screen.</string>
|
||||||
|
<string name="verification_conclusion_ok_notice">Messages with this user are end-to-end encrypted and can\'t be read by third parties.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue