Verification: migrate to Epoxy - Choose Fragment

This commit is contained in:
Benoit Marty 2020-01-15 13:16:05 +01:00 committed by Valere
parent a8e81d95cf
commit 878bae1c45
9 changed files with 187 additions and 222 deletions

View file

@ -0,0 +1,106 @@
/*
* 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.choose
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 javax.inject.Inject
class VerificationChooseMethodController @Inject constructor(
private val stringProvider: StringProvider,
private val colorProvider: ColorProvider
) : EpoxyController() {
var listener: Listener? = null
private var viewState: VerificationChooseMethodViewState? = 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: VerificationChooseMethodViewState) {
this.viewState = viewState
requestModelBuild()
}
override fun buildModels() {
val state = viewState ?: return
if (state.QRModeAvailable) {
bottomSheetVerificationNoticeItem {
id("notice")
notice(stringProvider.getString(R.string.verification_scan_notice))
}
// TODO Generate the QR code
bottomSheetVerificationBigImageItem {
id("qr")
imageRes(R.drawable.riotx_logo)
}
bottomSheetSeparatorItem {
id("sep0")
}
bottomSheetVerificationActionItem {
id("openCamera")
title(stringProvider.getString(R.string.verification_scan_their_code))
titleColor(colorProvider.getColor(R.color.riotx_accent))
iconRes(R.drawable.ic_camera)
iconColor(colorProvider.getColor(R.color.riotx_accent))
listener { listener?.openCamera() }
}
bottomSheetSeparatorItem {
id("sep1")
}
bottomSheetVerificationActionItem {
id("openEmoji")
title(stringProvider.getString(R.string.verification_scan_emoji_title))
titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
subTitle(stringProvider.getString(R.string.verification_scan_emoji_subtitle))
iconRes(R.drawable.ic_arrow_right)
iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
listener { listener?.doVerifyBySas() }
}
} else if (state.SASModeAvailable) {
bottomSheetVerificationActionItem {
id("openEmoji")
title(stringProvider.getString(R.string.verification_no_scan_emoji_title))
titleColor(colorProvider.getColor(R.color.riotx_accent))
iconRes(R.drawable.ic_arrow_right)
iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
listener { listener?.doVerifyBySas() }
}
}
}
interface Listener {
fun openCamera()
fun doVerifyBySas()
}
}

View file

@ -15,56 +15,59 @@
*/
package im.vector.riotx.features.crypto.verification.choose
import android.text.style.ClickableSpan
import android.os.Bundle
import android.view.View
import androidx.core.text.toSpannable
import androidx.core.view.isVisible
import butterknife.OnClick
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.cleanup
import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.utils.tappableMatchingText
import im.vector.riotx.features.crypto.verification.VerificationAction
import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel
import kotlinx.android.synthetic.main.fragment_verification_choose_method.*
import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import javax.inject.Inject
class VerificationChooseMethodFragment @Inject constructor(
val verificationChooseMethodViewModelFactory: VerificationChooseMethodViewModel.Factory
) : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_verification_choose_method
private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
val verificationChooseMethodViewModelFactory: VerificationChooseMethodViewModel.Factory,
val controller: VerificationChooseMethodController
) : VectorBaseFragment(), VerificationChooseMethodController.Listener {
private val viewModel by fragmentViewModel(VerificationChooseMethodViewModel::class)
override fun invalidate() = withState(viewModel) { state ->
if (state.QRModeAvailable) {
val cSpan = object : ClickableSpan() {
override fun onClick(widget: View) {
}
}
val openLink = getString(R.string.verify_open_camera_link)
val descCharSequence =
getString(R.string.verify_by_scanning_description, openLink)
.toSpannable()
.tappableMatchingText(openLink, cSpan)
verifyQRDescription.text = descCharSequence
verifyQRGroup.isVisible = true
} else {
verifyQRGroup.isVisible = false
}
private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
verifyEmojiGroup.isVisible = state.SASMOdeAvailable
override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
}
@OnClick(R.id.verificationByEmojiButton)
fun doVerifyBySas() = withState(sharedViewModel) {
override fun onDestroyView() {
bottomSheetVerificationRecyclerView.cleanup()
controller.listener = null
super.onDestroyView()
}
private fun setupRecyclerView() {
bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
controller.listener = this
}
override fun invalidate() = withState(viewModel) { state ->
controller.update(state)
}
override fun doVerifyBySas() = withState(sharedViewModel) {
sharedViewModel.handle(VerificationAction.StartSASVerification(
it.otherUserMxItem?.id ?: "",
it.pendingRequest?.transactionId ?: ""))
}
override fun openCamera() {
// TODO
}
}

View file

@ -35,7 +35,7 @@ data class VerificationChooseMethodViewState(
val otherUserId: String = "",
val transactionId: String = "",
val QRModeAvailable: Boolean = false,
val SASMOdeAvailable: Boolean = false
val SASModeAvailable: Boolean = false
) : MvRxState
class VerificationChooseMethodViewModel @AssistedInject constructor(
@ -57,7 +57,7 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
setState {
copy(
QRModeAvailable = qrAvailable,
SASMOdeAvailable = emojiAvailable
SASModeAvailable = emojiAvailable
)
}
}
@ -94,7 +94,7 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
return VerificationChooseMethodViewState(otherUserId = args.otherUserId,
transactionId = args.verificationId ?: "",
QRModeAvailable = qrAvailable,
SASMOdeAvailable = emojiAvailable
SASModeAvailable = emojiAvailable
)
}
}

View file

@ -45,7 +45,7 @@ abstract class BottomSheetVerificationActionItem : VectorEpoxyModel<BottomSheetV
@EpoxyAttribute
var titleColor: Int = 0
@EpoxyAttribute
var iconColor: Int = 0
var iconColor: Int = -1
@EpoxyAttribute
lateinit var listener: () -> Unit
@ -63,7 +63,9 @@ abstract class BottomSheetVerificationActionItem : VectorEpoxyModel<BottomSheetV
if (iconRes != -1) {
holder.icon.isVisible = true
holder.icon.setImageResource(iconRes)
ImageViewCompat.setImageTintList(holder.icon, ColorStateList.valueOf(iconColor))
if (iconColor != -1) {
ImageViewCompat.setImageTintList(holder.icon, ColorStateList.valueOf(iconColor))
}
} else {
holder.icon.isVisible = false
}

View file

@ -17,6 +17,7 @@
package im.vector.riotx.features.crypto.verification.epoxy
import android.widget.ImageView
import androidx.core.view.ViewCompat
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
@ -32,8 +33,18 @@ abstract class BottomSheetVerificationBigImageItem : VectorEpoxyModel<BottomShee
@EpoxyAttribute
var imageRes: Int = 0
@EpoxyAttribute
var contentDescription: String? = null
override fun bind(holder: Holder) {
holder.image.setImageResource(imageRes)
if (contentDescription == null) {
ViewCompat.setImportantForAccessibility(holder.image, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO)
} else {
ViewCompat.setImportantForAccessibility(holder.image, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES)
holder.image.contentDescription = contentDescription
}
}
class Holder : VectorEpoxyHolder() {

View file

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M23,19C23,20.1046 22.1046,21 21,21H3C1.8954,21 1,20.1046 1,19V8C1,6.8954 1.8954,6 3,6H7L9,3H15L17,6H21C22.1046,6 23,6.8954 23,8V19Z"
android:strokeWidth="2"
android:strokeColor="#03B381"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M12,17C14.2091,17 16,15.2091 16,13C16,10.7909 14.2091,9 12,9C9.7909,9 8,10.7909 8,13C8,15.2091 9.7909,17 12,17Z"
android:strokeWidth="2"
android:strokeColor="#03B381"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View file

@ -1,117 +0,0 @@
<?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/verificationQRTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/verify_by_scanning_title"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/verifyQRDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/verify_by_scanning_description"
android:textColor="?riotx_text_secondary"
app:layout_constraintTop_toBottomOf="@id/verificationQRTitle"
tools:text="@string/verify_by_scanning_description" />
<ImageView
android:id="@+id/verifyQRImageView"
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:background="?riotx_header_panel_background"
android:contentDescription="@string/aria_qr_code_description"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/verifyQRDescription" />
<TextView
android:id="@+id/verificationEmojiTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="@string/verify_by_emoji_title"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/verifyQRImageView"
app:layout_goneMarginTop="0dp" />
<TextView
android:id="@+id/verifyEmojiDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/verify_by_emoji_description"
android:textColor="?riotx_text_secondary"
app:layout_constraintTop_toBottomOf="@id/verificationEmojiTitle" />
<com.google.android.material.button.MaterialButton
android:id="@+id/verificationByEmojiButton"
style="@style/VectorButtonStylePositive"
android:layout_width="match_parent"
android:layout_marginTop="16dp"
android:text="@string/verify_by_emoji_title"
app:layout_constraintTop_toBottomOf="@id/verifyEmojiDescription" />
<androidx.constraintlayout.widget.Group
android:id="@+id/verifyQRGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:constraint_referenced_ids="verifyQRDescription,verificationQRTitle,verifyQRImageView" />
<androidx.constraintlayout.widget.Group
android:id="@+id/verifyEmojiGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:constraint_referenced_ids="verifyEmojiDescription,verificationEmojiTitle,verificationByEmojiButton" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,63 +0,0 @@
<?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">
<TextView
android:id="@+id/verificationConclusionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/sas_verified"
android:textColor="?riotx_text_primary"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/sas_verified" />
<TextView
android:id="@+id/verifyConclusionDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/sas_verified_successful_description"
android:textColor="?riotx_text_secondary"
app:layout_constraintTop_toBottomOf="@id/verificationConclusionTitle"
tools:text="@string/sas_verified_successful_description" />
<ImageView
android:id="@+id/verifyConclusionImageView"
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/verifyConclusionDescription"
tools:background="@drawable/ic_shield_trusted" />
<TextView
android:id="@+id/verifyConclusionBottomDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="?riotx_text_secondary"
app:layout_constraintTop_toBottomOf="@id/verifyConclusionImageView"
tools:text="@string/verification_green_shield" />
<com.google.android.material.button.MaterialButton
android:id="@+id/verificationConclusionButton"
style="@style/VectorButtonStylePositive"
android:layout_width="match_parent"
android:layout_marginTop="16dp"
android:text="@string/sas_got_it"
app:layout_constraintTop_toBottomOf="@id/verifyConclusionBottomDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -35,16 +35,17 @@
<!-- 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="verification_scan_notice">Scan the code with the other user\'s device to securely verify each other</string>
<string name="verification_scan_their_code">Scan their code</string>
<string name="verification_scan_emoji_title">Can\'t scan</string>
<string name="verification_scan_emoji_subtitle">If you\'re not in person, compare emoji instead</string>
<string name="verification_no_scan_emoji_title">Continue</string>
<string name="verify_by_emoji_title">Verify by Emoji</string>
<string name="verify_by_emoji_description">If you cant 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="a13n_qr_code_description">QR code image</string>
<string name="verification_verify_user">Verify %s</string>
<string name="verification_verified_user">Verified %s</string>