Verification: migrate to Epoxy - Conclusion

This commit is contained in:
Benoit Marty 2020-01-15 12:10:55 +01:00 committed by Valere
parent 7170471686
commit cd1665a8e8
6 changed files with 209 additions and 76 deletions

View file

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

View file

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

View file

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

View file

@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.crypto.sas.safeValueOf
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
data class SASVerificationConclusionViewState( data class VerificationConclusionViewState(
val conclusionState: ConclusionState = ConclusionState.CANCELLED val conclusionState: ConclusionState = ConclusionState.CANCELLED
) : MvRxState ) : MvRxState
@ -33,22 +33,22 @@ enum class ConclusionState {
CANCELLED CANCELLED
} }
class VerificationConclusionViewModel(initialState: SASVerificationConclusionViewState) class VerificationConclusionViewModel(initialState: VerificationConclusionViewState)
: VectorViewModel<SASVerificationConclusionViewState, EmptyAction>(initialState) { : VectorViewModel<VerificationConclusionViewState, EmptyAction>(initialState) {
companion object : MvRxViewModelFactory<VerificationConclusionViewModel, SASVerificationConclusionViewState> { companion object : MvRxViewModelFactory<VerificationConclusionViewModel, VerificationConclusionViewState> {
override fun initialState(viewModelContext: ViewModelContext): SASVerificationConclusionViewState? { override fun initialState(viewModelContext: ViewModelContext): VerificationConclusionViewState? {
val args = viewModelContext.args<VerificationConclusionFragment.Args>() val args = viewModelContext.args<VerificationConclusionFragment.Args>()
return when (safeValueOf(args.cancelReason)) { return when (safeValueOf(args.cancelReason)) {
CancelCode.MismatchedSas, CancelCode.MismatchedSas,
CancelCode.MismatchedCommitment, CancelCode.MismatchedCommitment,
CancelCode.MismatchedKeys -> { CancelCode.MismatchedKeys -> {
SASVerificationConclusionViewState(ConclusionState.WARNING) VerificationConclusionViewState(ConclusionState.WARNING)
} }
else -> { else -> {
SASVerificationConclusionViewState( VerificationConclusionViewState(
if (args.isSuccessFull) ConclusionState.SUCCESS if (args.isSuccessFull) ConclusionState.SUCCESS
else ConclusionState.CANCELLED else ConclusionState.CANCELLED
) )

View file

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

View file

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