From 9089cc393edbda170814e91e02c0f4f17c3012e0 Mon Sep 17 00:00:00 2001 From: Michelle Tang Date: Wed, 20 May 2026 14:51:59 -0400 Subject: [PATCH] Update reg v5 UI for locked account. --- .../accountlocked/AccountLockedScreen.kt | 217 ++++++++++++++---- 1 file changed, 167 insertions(+), 50 deletions(-) diff --git a/feature/registration/src/main/java/org/signal/registration/screens/accountlocked/AccountLockedScreen.kt b/feature/registration/src/main/java/org/signal/registration/screens/accountlocked/AccountLockedScreen.kt index 33d083950d..3abd60dfb9 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/accountlocked/AccountLockedScreen.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/accountlocked/AccountLockedScreen.kt @@ -7,24 +7,39 @@ package org.signal.registration.screens.accountlocked import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withLink +import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import org.signal.core.ui.compose.AllDevicePreviews import org.signal.core.ui.compose.Previews +import org.signal.core.ui.compose.horizontalGutters import org.signal.registration.R +import org.signal.registration.screens.OnePaneRegistrationScaffold +import org.signal.registration.screens.RegistrationScaffold +import org.signal.registration.screens.TwoPaneRegistrationScaffold /** * Screen shown when the user's account is locked due to too many failed PIN attempts @@ -36,58 +51,160 @@ fun AccountLockedScreen( onEvent: (AccountLockedScreenEvents) -> Unit, modifier: Modifier = Modifier ) { - Column( - modifier = modifier - .fillMaxSize() - .padding(horizontal = 32.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceBetween - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Spacer(modifier = Modifier.height(49.dp)) - - Text( - text = stringResource(R.string.AccountLockedScreen__account_locked), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center - ) - - Spacer(modifier = Modifier.height(12.dp)) - - Text( - text = stringResource(R.string.AccountLockedScreen__your_account, state.daysRemaining), - style = MaterialTheme.typography.bodyLarge, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Button( - onClick = { onEvent(AccountLockedScreenEvents.Next) }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.RegistrationActivity_next)) - } - - Spacer(modifier = Modifier.height(16.dp)) - - OutlinedButton( - onClick = { onEvent(AccountLockedScreenEvents.LearnMore) }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.RegistrationActivity_learn_more)) - } - - Spacer(modifier = Modifier.height(16.dp)) - } + when (val layoutParams = RegistrationScaffold.rememberLayoutParams()) { + is RegistrationScaffold.Params.OnePane -> OnePaneLayout(layoutParams, state, onEvent, modifier) + is RegistrationScaffold.Params.TwoPane -> TwoPaneLayout(layoutParams, state, onEvent, modifier) } } +@Composable +private fun OnePaneLayout( + params: RegistrationScaffold.Params.OnePane, + state: AccountLockedState, + onEvent: (AccountLockedScreenEvents) -> Unit, + modifier: Modifier +) { + val scrollState = rememberScrollState() + OnePaneRegistrationScaffold( + modifier = modifier.fillMaxSize(), + params = params, + content = { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(paddingValues), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Title() + Spacer(modifier = Modifier.height(12.dp)) + Description(state) + } + }, + footer = { + Column( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 16.dp) + .horizontalGutters(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + NextButton(onEvent, modifier = Modifier.widthIn(max = params.maxButtonWidth).fillMaxWidth()) + Spacer(modifier = Modifier.height(16.dp)) + LearnMore(onEvent) + } + } + ) +} + +@Composable +private fun TwoPaneLayout( + params: RegistrationScaffold.Params.TwoPane, + state: AccountLockedState, + onEvent: (AccountLockedScreenEvents) -> Unit, + modifier: Modifier +) { + val firstPaneScrollState = rememberScrollState() + val secondPaneScrollState = rememberScrollState() + + TwoPaneRegistrationScaffold( + modifier = modifier.fillMaxSize(), + params = params, + firstPane = { paddingValues -> + Column( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .verticalScroll(firstPaneScrollState) + .padding(paddingValues), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Title() + } + }, + secondPane = { paddingValues -> + Column( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .verticalScroll(secondPaneScrollState) + .padding(paddingValues) + ) { + Description(state) + } + }, + footer = { + Row( + modifier = modifier + .padding(vertical = 16.dp) + .fillMaxWidth() + .horizontalGutters(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + LearnMore(onEvent) + Spacer(modifier = Modifier.size(16.dp)) + NextButton(onEvent, modifier = Modifier.widthIn(max = params.maxButtonWidth)) + } + } + ) +} + +@Composable +private fun Title() { + Text( + text = stringResource(R.string.AccountLockedScreen__account_locked), + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier.fillMaxWidth() + ) +} + +@Composable +private fun Description(state: AccountLockedState) { + Text( + text = stringResource(R.string.AccountLockedScreen__your_account, state.daysRemaining), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.fillMaxWidth() + ) +} + +@Composable +private fun NextButton(onEvent: (AccountLockedScreenEvents) -> Unit, modifier: Modifier = Modifier) { + Button( + onClick = { onEvent(AccountLockedScreenEvents.Next) }, + modifier = modifier + ) { + Text(stringResource(R.string.RegistrationActivity_next)) + } +} + +@Composable +private fun LearnMore(onEvent: (AccountLockedScreenEvents) -> Unit) { + Text( + text = buildAnnotatedString { + withLink( + LinkAnnotation.Clickable( + tag = "learn-more", + styles = TextLinkStyles( + style = SpanStyle( + color = MaterialTheme.colorScheme.primary + ) + ) + ) { + onEvent(AccountLockedScreenEvents.LearnMore) + } + ) { + withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) { + append(stringResource(id = R.string.RegistrationActivity_learn_more)) + } + } + }, + textAlign = TextAlign.Center + ) +} + @AllDevicePreviews @Composable private fun AccountLockedScreenPreview() {