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 package im.vector.riotx.features.crypto.verification.choose
import android.text.style.ClickableSpan import android.os.Bundle
import android.view.View 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.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.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.core.utils.tappableMatchingText
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 kotlinx.android.synthetic.main.fragment_verification_choose_method.* import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.*
import javax.inject.Inject import javax.inject.Inject
class VerificationChooseMethodFragment @Inject constructor( class VerificationChooseMethodFragment @Inject constructor(
val verificationChooseMethodViewModelFactory: VerificationChooseMethodViewModel.Factory val verificationChooseMethodViewModelFactory: VerificationChooseMethodViewModel.Factory,
) : VectorBaseFragment() { val controller: VerificationChooseMethodController
) : VectorBaseFragment(), VerificationChooseMethodController.Listener {
override fun getLayoutResId() = R.layout.fragment_verification_choose_method
private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
private val viewModel by fragmentViewModel(VerificationChooseMethodViewModel::class) private val viewModel by fragmentViewModel(VerificationChooseMethodViewModel::class)
override fun invalidate() = withState(viewModel) { state -> private val sharedViewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class)
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
}
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) override fun onDestroyView() {
fun doVerifyBySas() = withState(sharedViewModel) { 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( sharedViewModel.handle(VerificationAction.StartSASVerification(
it.otherUserMxItem?.id ?: "", it.otherUserMxItem?.id ?: "",
it.pendingRequest?.transactionId ?: "")) it.pendingRequest?.transactionId ?: ""))
} }
override fun openCamera() {
// TODO
}
} }

View file

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

View file

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

View file

@ -17,6 +17,7 @@
package im.vector.riotx.features.crypto.verification.epoxy package im.vector.riotx.features.crypto.verification.epoxy
import android.widget.ImageView import android.widget.ImageView
import androidx.core.view.ViewCompat
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R import im.vector.riotx.R
@ -32,8 +33,18 @@ abstract class BottomSheetVerificationBigImageItem : VectorEpoxyModel<BottomShee
@EpoxyAttribute @EpoxyAttribute
var imageRes: Int = 0 var imageRes: Int = 0
@EpoxyAttribute
var contentDescription: String? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
holder.image.setImageResource(imageRes) 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() { 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!--> <!-- Sender name of a message when it is send by you, e.g. You: Hello!-->
<string name="you">You</string> <string name="you">You</string>
<string name="verify_by_scanning_title">Verify by scanning</string> <string name="verification_scan_notice">Scan the code with the other user\'s device to securely verify each other</string>
<!-- the %s will be replaced by verify_open_camera_link that will be clickable --> <string name="verification_scan_their_code">Scan their code</string>
<string name="verify_by_scanning_description">Ask the other user to scan this code, or %s to scan theirs</string> <string name="verification_scan_emoji_title">Can\'t scan</string>
<!-- This part is inserted in verify_by_scanning_description--> <string name="verification_scan_emoji_subtitle">If you\'re not in person, compare emoji instead</string>
<string name="verify_open_camera_link">open your camera</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_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="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_verify_user">Verify %s</string>
<string name="verification_verified_user">Verified %s</string> <string name="verification_verified_user">Verified %s</string>