From cd1665a8e829ec5a00c323ffabcfba7110e68e67 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 15 Jan 2020 12:10:55 +0100 Subject: [PATCH] Verification: migrate to Epoxy - Conclusion --- .../verification/VerificationBottomSheet.kt | 13 ++- .../VerificationConclusionController.kt | 102 ++++++++++++++++++ .../VerificationConclusionFragment.kt | 64 +++++------ .../VerificationConclusionViewModel.kt | 14 +-- .../res/layout/bottom_sheet_verification.xml | 86 +++++++++------ vector/src/main/res/values/strings_riotX.xml | 6 +- 6 files changed, 209 insertions(+), 76 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionController.kt diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index 48898a3bf4..22369f37b5 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -21,6 +21,7 @@ import android.view.View import android.widget.ImageView import android.widget.TextView import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.transition.AutoTransition @@ -72,6 +73,9 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { @BindView(R.id.verificationRequestName) lateinit var otherUserNameText: TextView + @BindView(R.id.verificationRequestShield) + lateinit var otherUserShield: View + @BindView(R.id.verificationRequestAvatar) lateinit var otherUserAvatarImageView: ImageView @@ -95,8 +99,15 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { override fun invalidate() = withState(viewModel) { it.otherUserMxItem?.let { matrixItem -> - otherUserNameText.text = getString(R.string.verification_request_alert_title, matrixItem.getBestName()) 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? diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionController.kt new file mode 100644 index 0000000000..744b7ddd6c --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionController.kt @@ -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() + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionFragment.kt index b110773597..87a1a492cb 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionFragment.kt @@ -15,24 +15,25 @@ */ package im.vector.riotx.features.crypto.verification.conclusion +import android.os.Bundle import android.os.Parcelable -import androidx.core.content.ContextCompat -import androidx.core.view.isVisible -import butterknife.OnClick +import android.view.View import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R -import im.vector.riotx.core.extensions.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.features.crypto.verification.VerificationAction import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel -import io.noties.markwon.Markwon 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 -class VerificationConclusionFragment @Inject constructor() : VectorBaseFragment() { +class VerificationConclusionFragment @Inject constructor( + val controller: VerificationConclusionController +) : VectorBaseFragment(), VerificationConclusionController.Listener { @Parcelize data class Args( @@ -40,38 +41,39 @@ class VerificationConclusionFragment @Inject constructor() : VectorBaseFragment( val cancelReason: String? ) : Parcelable - override fun getLayoutResId() = R.layout.fragment_verification_conclusion - private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) private val viewModel by fragmentViewModel(VerificationConclusionViewModel::class) - override fun invalidate() = withState(viewModel) { - when (it.conclusionState) { - ConclusionState.SUCCESS -> { - verificationConclusionTitle.text = getString(R.string.sas_verified) - verifyConclusionDescription.setTextOrHide(getString(R.string.sas_verified_successful_description)) - verifyConclusionBottomDescription.text = getString(R.string.verification_green_shield) - verifyConclusionImageView.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_shield_trusted)) - } - ConclusionState.WARNING -> { - verificationConclusionTitle.text = getString(R.string.verification_conclusion_not_secure) - verifyConclusionDescription.isVisible = false - verifyConclusionImageView.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_shield_warning)) + override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment - verifyConclusionBottomDescription.text = Markwon.builder(requireContext()) - .build() - .toMarkdown(getString(R.string.verification_conclusion_compromised)) - } - ConclusionState.CANCELLED -> { - // Just dismiss in this case - sharedViewModel.handle(VerificationAction.GotItConclusion) - } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupRecyclerView() + } + + override fun onDestroyView() { + bottomSheetVerificationRecyclerView.cleanup() + controller.listener = null + super.onDestroyView() + } + + private fun setupRecyclerView() { + bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false) + 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) - fun onButtonTapped() { + override fun onButtonTapped() { sharedViewModel.handle(VerificationAction.GotItConclusion) } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt index a7176d504c..fc46ba8516 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt @@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.crypto.sas.safeValueOf import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.VectorViewModel -data class SASVerificationConclusionViewState( +data class VerificationConclusionViewState( val conclusionState: ConclusionState = ConclusionState.CANCELLED ) : MvRxState @@ -33,22 +33,22 @@ enum class ConclusionState { CANCELLED } -class VerificationConclusionViewModel(initialState: SASVerificationConclusionViewState) - : VectorViewModel(initialState) { +class VerificationConclusionViewModel(initialState: VerificationConclusionViewState) + : VectorViewModel(initialState) { - companion object : MvRxViewModelFactory { + companion object : MvRxViewModelFactory { - override fun initialState(viewModelContext: ViewModelContext): SASVerificationConclusionViewState? { + override fun initialState(viewModelContext: ViewModelContext): VerificationConclusionViewState? { val args = viewModelContext.args() return when (safeValueOf(args.cancelReason)) { CancelCode.MismatchedSas, CancelCode.MismatchedCommitment, CancelCode.MismatchedKeys -> { - SASVerificationConclusionViewState(ConclusionState.WARNING) + VerificationConclusionViewState(ConclusionState.WARNING) } else -> { - SASVerificationConclusionViewState( + VerificationConclusionViewState( if (args.isSuccessFull) ConclusionState.SUCCESS else ConclusionState.CANCELLED ) diff --git a/vector/src/main/res/layout/bottom_sheet_verification.xml b/vector/src/main/res/layout/bottom_sheet_verification.xml index 7293434f0d..4585865973 100644 --- a/vector/src/main/res/layout/bottom_sheet_verification.xml +++ b/vector/src/main/res/layout/bottom_sheet_verification.xml @@ -1,6 +1,7 @@ - + android:layout_height="wrap_content"> - + + + + - - - - - - + android:layout_marginStart="8dp" + android:layout_marginEnd="16dp" + android:layout_weight="1" + android:ellipsize="end" + android:maxLines="2" + android:textColor="?riotx_text_primary" + android:textSize="20sp" + android:textStyle="bold" + app:layout_constraintBottom_toBottomOf="@+id/verificationRequestAvatar" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/verificationRequestAvatar" + app:layout_constraintTop_toTopOf="@+id/verificationRequestAvatar" + tools:text="@string/verification_verify_user" /> + android:layout_marginTop="16dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/verificationRequestAvatar" /> + + - diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index c94361b801..3a8eae2396 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -46,7 +46,8 @@ QR code image - Verify %s + Verify %s + Verified %s Waiting for %s… For extra security, verify %s by checking a one-time code on both your devices.\n\nFor maximum security, do this in person. Messages in this room are not end-to-end encrypted. @@ -90,6 +91,7 @@ For maximum security, do this in person. Compare the unique emoji, ensuring they appear in the same order. - Compare the code with the one displayed on the the other user\'s screen. + Compare the code with the one displayed on the other user\'s screen. + Messages with this user are end-to-end encrypted and can\'t be read by third parties.