Browse Source

Add "Learn more" on identity verification first screen.

pull/3649/head
Benoit Marty 1 week ago
parent
commit
fd819c2381
  1. 1
      appconfig/src/main/kotlin/io/element/android/appconfig/LearnMoreConfig.kt
  2. 2
      features/verifysession/impl/build.gradle.kts
  3. 9
      features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt
  4. 1
      features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt
  5. 89
      features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt
  6. 18
      features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt

1
appconfig/src/main/kotlin/io/element/android/appconfig/LearnMoreConfig.kt

@ -8,6 +8,7 @@
package io.element.android.appconfig package io.element.android.appconfig
object LearnMoreConfig { object LearnMoreConfig {
const val ENCRYPTION_URL: String = "https://element.io/help#encryption"
const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5" const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5"
const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18" const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18"
} }

2
features/verifysession/impl/build.gradle.kts

@ -23,6 +23,8 @@ android {
setupAnvil() setupAnvil()
dependencies { dependencies {
implementation(projects.appconfig)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.core) implementation(projects.libraries.core)
implementation(projects.libraries.architecture) implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)

9
features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt

@ -18,9 +18,11 @@ import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.appconfig.LearnMoreConfig
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.features.logout.api.util.onSuccessLogout import io.element.android.features.logout.api.util.onSuccessLogout
import io.element.android.features.verifysession.api.VerifySessionEntryPoint import io.element.android.features.verifysession.api.VerifySessionEntryPoint
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
import io.element.android.libraries.architecture.inputs import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
@ -36,6 +38,10 @@ class VerifySelfSessionNode @AssistedInject constructor(
showDeviceVerifiedScreen = inputs<VerifySessionEntryPoint.Params>().showDeviceVerifiedScreen, showDeviceVerifiedScreen = inputs<VerifySessionEntryPoint.Params>().showDeviceVerifiedScreen,
) )
private fun onLearnMoreClick(activity: Activity, dark: Boolean) {
activity.openUrlInChromeCustomTab(null, dark, LearnMoreConfig.ENCRYPTION_URL)
}
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
@ -44,6 +50,9 @@ class VerifySelfSessionNode @AssistedInject constructor(
VerifySelfSessionView( VerifySelfSessionView(
state = state, state = state,
modifier = modifier, modifier = modifier,
onLearnMoreClick = {
onLearnMoreClick(activity, isDark)
},
onEnterRecoveryKey = callback::onEnterRecoveryKey, onEnterRecoveryKey = callback::onEnterRecoveryKey,
onResetKey = callback::onResetKey, onResetKey = callback::onResetKey,
onFinish = callback::onDone, onFinish = callback::onDone,

1
features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt

@ -23,6 +23,7 @@ data class VerifySelfSessionState(
@Stable @Stable
sealed interface VerificationStep { sealed interface VerificationStep {
data object Loading : VerificationStep data object Loading : VerificationStep
// FIXME canEnterRecoveryKey value is never read.
data class Initial(val canEnterRecoveryKey: Boolean, val isLastDevice: Boolean = false) : VerificationStep data class Initial(val canEnterRecoveryKey: Boolean, val isLastDevice: Boolean = false) : VerificationStep
data object Canceled : VerificationStep data object Canceled : VerificationStep
data object AwaitingOtherDeviceResponse : VerificationStep data object AwaitingOtherDeviceResponse : VerificationStep

89
features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt

@ -9,13 +9,13 @@ package io.element.android.features.verifysession.impl
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@ -64,6 +64,7 @@ import io.element.android.features.verifysession.impl.VerifySelfSessionState.Ver
@Composable @Composable
fun VerifySelfSessionView( fun VerifySelfSessionView(
state: VerifySelfSessionState, state: VerifySelfSessionState,
onLearnMoreClick: () -> Unit,
onEnterRecoveryKey: () -> Unit, onEnterRecoveryKey: () -> Unit,
onResetKey: () -> Unit, onResetKey: () -> Unit,
onFinish: () -> Unit, onFinish: () -> Unit,
@ -140,7 +141,10 @@ fun VerifySelfSessionView(
) )
} }
) { ) {
Content(flowState = verificationFlowStep) Content(
flowState = verificationFlowStep,
onLearnMoreClick = onLearnMoreClick,
)
} }
} }
@ -203,38 +207,68 @@ private fun HeaderContent(verificationFlowStep: FlowStep) {
} }
@Composable @Composable
private fun Content(flowState: FlowStep) { private fun Content(
Column(Modifier.fillMaxHeight(), verticalArrangement = Arrangement.Center) { flowState: FlowStep,
if (flowState is FlowStep.Verifying) { onLearnMoreClick: () -> Unit,
) {
when (flowState) {
is VerifySelfSessionState.VerificationStep.Initial -> {
ContentInitial(onLearnMoreClick)
}
is FlowStep.Verifying -> {
ContentVerifying(flowState) ContentVerifying(flowState)
} }
else -> Unit
}
}
@Composable
fun ContentInitial(
onLearnMoreClick: () -> Unit,
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
Text(
modifier = Modifier
.clickable { onLearnMoreClick() }
.padding(vertical = 4.dp, horizontal = 16.dp),
text = stringResource(CommonStrings.action_learn_more),
style = ElementTheme.typography.fontBodyLgMedium
)
} }
} }
@Composable @Composable
private fun ContentVerifying(verificationFlowStep: FlowStep.Verifying) { private fun ContentVerifying(verificationFlowStep: FlowStep.Verifying) {
when (verificationFlowStep.data) { Box(
is SessionVerificationData.Decimals -> { modifier = Modifier.fillMaxSize(),
val text = verificationFlowStep.data.decimals.joinToString(separator = " - ") { it.toString() } contentAlignment = Alignment.Center
Text( ) {
modifier = Modifier.fillMaxWidth(), when (verificationFlowStep.data) {
text = text, is SessionVerificationData.Decimals -> {
style = ElementTheme.typography.fontHeadingLgBold, val text = verificationFlowStep.data.decimals.joinToString(separator = " - ") { it.toString() }
color = MaterialTheme.colorScheme.primary, Text(
textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth(),
) text = text,
} style = ElementTheme.typography.fontHeadingLgBold,
is SessionVerificationData.Emojis -> { color = MaterialTheme.colorScheme.primary,
// We want each row to have up to 4 emojis textAlign = TextAlign.Center,
val rows = verificationFlowStep.data.emojis.chunked(4) )
Column( }
modifier = Modifier.fillMaxWidth(), is SessionVerificationData.Emojis -> {
verticalArrangement = Arrangement.spacedBy(40.dp), // We want each row to have up to 4 emojis
) { val rows = verificationFlowStep.data.emojis.chunked(4)
rows.forEach { emojis -> Column(
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { modifier = Modifier.fillMaxWidth(),
for (emoji in emojis) { verticalArrangement = Arrangement.spacedBy(40.dp),
EmojiItemView(emoji = emoji, modifier = Modifier.widthIn(max = 60.dp)) ) {
rows.forEach { emojis ->
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
for (emoji in emojis) {
EmojiItemView(emoji = emoji, modifier = Modifier.widthIn(max = 60.dp))
}
} }
} }
} }
@ -402,6 +436,7 @@ private fun BottomMenu(
internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionStateProvider::class) state: VerifySelfSessionState) = ElementPreview { internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionStateProvider::class) state: VerifySelfSessionState) = ElementPreview {
VerifySelfSessionView( VerifySelfSessionView(
state = state, state = state,
onLearnMoreClick = {},
onEnterRecoveryKey = {}, onEnterRecoveryKey = {},
onResetKey = {}, onResetKey = {},
onFinish = {}, onFinish = {},

18
features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt

@ -146,6 +146,22 @@ class VerifySelfSessionViewTest {
} }
} }
@Config(qualifiers = "h1024dp")
@Test
fun `clicking on learn more invokes the expected callback`() {
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>(expectEvents = false)
ensureCalledOnce { callback ->
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true),
eventSink = eventsRecorder
),
onLearnMoreClick = callback,
)
rule.clickOn(CommonStrings.action_learn_more)
}
}
@Test @Test
fun `clicking on they match emits the expected event`() { fun `clicking on they match emits the expected event`() {
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>() val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
@ -222,6 +238,7 @@ class VerifySelfSessionViewTest {
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setVerifySelfSessionView( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setVerifySelfSessionView(
state: VerifySelfSessionState, state: VerifySelfSessionState,
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(), onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(),
onFinished: () -> Unit = EnsureNeverCalled(), onFinished: () -> Unit = EnsureNeverCalled(),
onResetKey: () -> Unit = EnsureNeverCalled(), onResetKey: () -> Unit = EnsureNeverCalled(),
@ -230,6 +247,7 @@ class VerifySelfSessionViewTest {
setContent { setContent {
VerifySelfSessionView( VerifySelfSessionView(
state = state, state = state,
onLearnMoreClick = onLearnMoreClick,
onEnterRecoveryKey = onEnterRecoveryKey, onEnterRecoveryKey = onEnterRecoveryKey,
onFinish = onFinished, onFinish = onFinished,
onResetKey = onResetKey, onResetKey = onResetKey,

Loading…
Cancel
Save