Iterate on login error: add a cancel button that fully close the flow.

tom
This commit is contained in:
Benoit Marty
2026-01-28 16:09:31 +01:00
committed by Benoit Marty
parent 692302df5c
commit 36d5500a65
10 changed files with 120 additions and 28 deletions

View File

@@ -193,7 +193,8 @@ class LinkNewDeviceFlowNode(
is ErrorType.Unknown -> ErrorScreenType.UnknownError
is ErrorType.UnsupportedProtocol -> ErrorScreenType.UnknownError
}
// It is OK to push on backstack, since when user leaves the error screen, a new root will be set
// It is OK to push on backstack, since when user leaves the error screen, a new root will be set,
// or the whole flow will be popped.
backstack.push(NavTarget.Error(error))
}
@@ -263,6 +264,12 @@ class LinkNewDeviceFlowNode(
linkNewDesktopHandler.reset()
backstack.newRoot(NavTarget.Root)
}
override fun onCancel() {
linkNewMobileHandler.reset()
linkNewDesktopHandler.reset()
callback.onDone()
}
}
createNode<ErrorNode>(buildContext, listOf(callback, navTarget.errorScreenType))
}

View File

@@ -27,6 +27,7 @@ class ErrorNode(
) : Node(buildContext = buildContext, plugins = plugins) {
interface Callback : Plugin {
fun onRetry()
fun onCancel()
}
private val callback: Callback = callback()
@@ -38,6 +39,7 @@ class ErrorNode(
modifier = modifier,
errorScreenType = errorScreenType,
onRetry = callback::onRetry,
onCancel = callback::onCancel,
)
}
}

View File

@@ -33,6 +33,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.persistentListOf
@@ -41,17 +42,23 @@ import kotlinx.collections.immutable.persistentListOf
fun ErrorView(
errorScreenType: ErrorScreenType,
onRetry: () -> Unit,
onCancel: () -> Unit,
modifier: Modifier = Modifier,
) {
val appName = LocalBuildMeta.current.applicationName
BackHandler(onBack = onRetry)
BackHandler(onBack = onCancel)
FlowStepPage(
modifier = modifier,
iconStyle = BigIcon.Style.AlertSolid,
title = titleText(errorScreenType, appName),
subTitle = subtitleText(errorScreenType, appName),
content = { Content(errorScreenType) },
buttons = { Buttons(onRetry) },
buttons = {
Buttons(
onRetry = onRetry,
onCancel = onCancel,
)
},
)
}
@@ -118,11 +125,19 @@ private fun Content(errorScreenType: ErrorScreenType) {
}
@Composable
private fun Buttons(onRetry: () -> Unit) {
private fun Buttons(
onRetry: () -> Unit,
onCancel: () -> Unit,
) {
Button(
modifier = Modifier.fillMaxWidth(),
text = stringResource(CommonStrings.action_start_over),
onClick = onRetry
text = stringResource(CommonStrings.action_try_again),
onClick = onRetry,
)
OutlinedButton(
modifier = Modifier.fillMaxWidth(),
text = stringResource(CommonStrings.action_cancel),
onClick = onCancel,
)
}
@@ -133,6 +148,7 @@ internal fun ErrorViewPreview(@PreviewParameter(ErrorScreenTypeProvider::class)
ErrorView(
errorScreenType = errorScreenType,
onRetry = {},
onCancel = {},
)
}
}

View File

@@ -12,6 +12,7 @@ import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBackKey
@@ -26,33 +27,45 @@ class ErrorViewTest {
val rule = createAndroidComposeRule<ComponentActivity>()
@Test
fun `on back pressed - calls the onRetry callback`() {
fun `on back pressed - calls the onCancel callback`() {
ensureCalledOnce { callback ->
rule.setErrorView(
onRetry = callback
onCancel = callback,
)
rule.pressBackKey()
}
}
@Test
fun `on start over button clicked - calls the expected callback`() {
fun `on try again button clicked - calls the expected callback`() {
ensureCalledOnce { callback ->
rule.setErrorView(
onRetry = callback
)
rule.clickOn(CommonStrings.action_start_over)
rule.clickOn(CommonStrings.action_try_again)
}
}
@Test
fun `on cancel button clicked - calls the expected callback`() {
ensureCalledOnce { callback ->
rule.setErrorView(
onCancel = callback
)
rule.clickOn(CommonStrings.action_cancel)
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setErrorView(
onRetry: () -> Unit,
onRetry: () -> Unit = EnsureNeverCalled(),
onCancel: () -> Unit = EnsureNeverCalled(),
errorScreenType: ErrorScreenType = ErrorScreenType.UnknownError,
) {
setContent {
ErrorView(
errorScreenType = errorScreenType,
onRetry = onRetry,
onCancel = onCancel,
)
}
}