Improve welcome bottom sheet UI in regV5.

This commit is contained in:
Greyson Parrelli
2026-03-10 16:32:32 -04:00
committed by jeffrey-signal
parent 5d92fb1cc4
commit cd24691130
6 changed files with 108 additions and 52 deletions

View File

@@ -86,6 +86,7 @@ private val lightColorScheme = lightColorScheme(
secondary = Color(0xFF586071), secondary = Color(0xFF586071),
secondaryContainer = Color(0xFFDCE5F9), secondaryContainer = Color(0xFFDCE5F9),
surface = Color(0xFFFBFCFF), surface = Color(0xFFFBFCFF),
surfaceContainerLow = Color(0xFFF2F5F9),
surfaceVariant = Color(0xFFE7EBF3), surfaceVariant = Color(0xFFE7EBF3),
background = Color(0xFFFBFCFF), background = Color(0xFFFBFCFF),
error = Color(0xFFBA1B1B), error = Color(0xFFBA1B1B),
@@ -156,6 +157,7 @@ private val darkColorScheme = darkColorScheme(
secondary = Color(0xFFC1C6DD), secondary = Color(0xFFC1C6DD),
secondaryContainer = Color(0xFF414659), secondaryContainer = Color(0xFF414659),
surface = Color(0xFF1B1C1F), surface = Color(0xFF1B1C1F),
surfaceContainerLow = Color(0xFF23242A),
surfaceVariant = Color(0xFF303133), surfaceVariant = Color(0xFF303133),
background = Color(0xFF1B1C1F), background = Color(0xFF1B1C1F),
error = Color(0xFFFFB4A9), error = Color(0xFFFFB4A9),

View File

@@ -179,21 +179,6 @@ interface NetworkController {
*/ */
suspend fun setAccountAttributes(attributes: AccountAttributes): RegistrationNetworkResult<Unit, SetAccountAttributesError> suspend fun setAccountAttributes(attributes: AccountAttributes): RegistrationNetworkResult<Unit, SetAccountAttributesError>
// TODO
// /**
// * Validates the provided SVR2 auth credentials, returning information on their usability.
// *
// * `POST /v2/svr/auth/check`
// */
// suspend fun validateSvr2AuthCredential(e164: String, usernamePasswords: List<String>)
//
// /**
// * Validates the provided SVR3 auth credentials, returning information on their usability.
// *
// * `POST /v3/backup/auth/check`
// */
// suspend fun validateSvr3AuthCredential(e164: String, usernamePasswords: List<String>)
//
// /** // /**
// * Set [RestoreMethod] enum on the server for use by the old device to update UX. // * Set [RestoreMethod] enum on the server for use by the old device to update UX.
// */ // */

View File

@@ -67,7 +67,7 @@ sealed interface RegistrationRoute : NavKey, Parcelable {
data object Welcome : RegistrationRoute data object Welcome : RegistrationRoute
@Serializable @Serializable
data class Permissions(val forRestore: Boolean = false) : RegistrationRoute data class Permissions(val nextRoute: RegistrationRoute) : RegistrationRoute
@Serializable @Serializable
data object PhoneNumberEntry : RegistrationRoute data object PhoneNumberEntry : RegistrationRoute
@@ -100,10 +100,13 @@ sealed interface RegistrationRoute : NavKey, Parcelable {
data object PinCreate : RegistrationRoute data object PinCreate : RegistrationRoute
@Serializable @Serializable
data object Restore : RegistrationRoute data object ChooseRestoreOptionBeforeRegistration : RegistrationRoute
@Serializable @Serializable
data object RestoreViaQr : RegistrationRoute data object ChooseRestoreOptionAfterRegistration : RegistrationRoute
@Serializable
data object QuickRestoreQrScan : RegistrationRoute
@Serializable @Serializable
data object Transfer : RegistrationRoute data object Transfer : RegistrationRoute
@@ -203,9 +206,9 @@ private fun EntryProviderScope<NavKey>.navigationEntries(
WelcomeScreen( WelcomeScreen(
onEvent = { event -> onEvent = { event ->
when (event) { when (event) {
WelcomeScreenEvents.Continue -> parentEventEmitter.navigateTo(RegistrationRoute.Permissions(forRestore = false)) WelcomeScreenEvents.Continue -> parentEventEmitter.navigateTo(RegistrationRoute.Permissions(nextRoute = RegistrationRoute.PhoneNumberEntry))
WelcomeScreenEvents.DoesNotHaveOldPhone -> parentEventEmitter.navigateTo(RegistrationRoute.Restore) WelcomeScreenEvents.HasOldPhone -> parentEventEmitter.navigateTo(RegistrationRoute.Permissions(nextRoute = RegistrationRoute.QuickRestoreQrScan))
WelcomeScreenEvents.HasOldPhone -> parentEventEmitter.navigateTo(RegistrationRoute.Permissions(forRestore = true)) WelcomeScreenEvents.DoesNotHaveOldPhone -> parentEventEmitter.navigateTo(RegistrationRoute.Permissions(nextRoute = RegistrationRoute.ChooseRestoreOptionBeforeRegistration))
} }
} }
) )
@@ -216,11 +219,7 @@ private fun EntryProviderScope<NavKey>.navigationEntries(
PermissionsScreen( PermissionsScreen(
permissionsState = permissionsState, permissionsState = permissionsState,
onProceed = { onProceed = {
if (key.forRestore) { parentEventEmitter.navigateTo(key.nextRoute)
parentEventEmitter.navigateTo(RegistrationRoute.RestoreViaQr)
} else {
parentEventEmitter.navigateTo(RegistrationRoute.PhoneNumberEntry)
}
} }
) )
} }
@@ -399,11 +398,11 @@ private fun EntryProviderScope<NavKey>.navigationEntries(
) )
} }
entry<RegistrationRoute.Restore> { entry<RegistrationRoute.ChooseRestoreOptionAfterRegistration> {
// TODO: Implement RestoreScreen // TODO: Implement RestoreScreen
} }
entry<RegistrationRoute.RestoreViaQr> { entry<RegistrationRoute.QuickRestoreQrScan> {
RestoreViaQrScreen( RestoreViaQrScreen(
state = RestoreViaQrState(), state = RestoreViaQrState(),
onEvent = { event -> onEvent = { event ->

View File

@@ -8,14 +8,20 @@
package org.signal.registration.screens.welcome package org.signal.registration.screens.welcome
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
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
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SheetState import androidx.compose.material3.SheetState
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -29,6 +35,8 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@@ -40,7 +48,9 @@ import org.signal.core.ui.compose.BottomSheets
import org.signal.core.ui.compose.Buttons import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.DayNightPreviews import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.compose.dismissWithAnimation import org.signal.core.ui.compose.dismissWithAnimation
import org.signal.core.ui.compose.horizontalGutters
import org.signal.core.ui.compose.theme.SignalTheme import org.signal.core.ui.compose.theme.SignalTheme
import org.signal.registration.R import org.signal.registration.R
import org.signal.registration.test.TestTags import org.signal.registration.test.TestTags
@@ -169,40 +179,79 @@ private fun RestoreOrTransferBottomSheetContent(
onEvent: (WelcomeScreenEvents) -> Unit onEvent: (WelcomeScreenEvents) -> Unit
) { ) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 16.dp), .padding(bottom = 54.dp)
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Buttons.LargeTonal( Spacer(modifier = Modifier.size(26.dp))
onClick = {
RestoreActionRow(
icon = SignalIcons.QrCode.painter,
title = stringResource(R.string.WelcomeFragment_restore_action_i_have_my_old_phone),
subtitle = stringResource(R.string.WelcomeFragment_restore_action_scan_qr),
modifier = Modifier.testTag(TestTags.WELCOME_RESTORE_HAS_OLD_PHONE_BUTTON),
onRowClick = {
sheetState.dismissWithAnimation(scope) { sheetState.dismissWithAnimation(scope) {
onEvent(WelcomeScreenEvents.HasOldPhone) onEvent(WelcomeScreenEvents.HasOldPhone)
} }
}, }
modifier = Modifier )
.fillMaxWidth()
.testTag(TestTags.WELCOME_RESTORE_HAS_OLD_PHONE_BUTTON)
) {
Text("I have my old phone")
}
Spacer(modifier = Modifier.height(12.dp)) RestoreActionRow(
icon = painterResource(R.drawable.symbol_no_phone_44),
Buttons.LargeTonal( title = stringResource(R.string.WelcomeFragment_restore_action_i_dont_have_my_old_phone),
onClick = { subtitle = stringResource(R.string.WelcomeFragment_restore_action_reinstalling),
modifier = Modifier.testTag(TestTags.WELCOME_RESTORE_NO_OLD_PHONE_BUTTON),
onRowClick = {
sheetState.dismissWithAnimation(scope) { sheetState.dismissWithAnimation(scope) {
onEvent(WelcomeScreenEvents.DoesNotHaveOldPhone) onEvent(WelcomeScreenEvents.DoesNotHaveOldPhone)
} }
}, }
modifier = Modifier )
.fillMaxWidth() }
.testTag(TestTags.WELCOME_RESTORE_NO_OLD_PHONE_BUTTON) }
) {
Text("I don't have my old phone")
}
Spacer(modifier = Modifier.height(16.dp)) @Composable
private fun RestoreActionRow(
icon: Painter,
title: String,
subtitle: String,
modifier: Modifier = Modifier,
onRowClick: () -> Unit = {}
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.horizontalGutters()
.padding(vertical = 8.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(18.dp))
.background(MaterialTheme.colorScheme.background)
.clickable(enabled = true, onClick = onRowClick)
.padding(horizontal = 24.dp, vertical = 16.dp)
) {
Icon(
painter = icon,
tint = MaterialTheme.colorScheme.primary,
contentDescription = null,
modifier = Modifier.size(44.dp)
)
Column(
modifier = Modifier.padding(start = 16.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.bodyLarge
)
Text(
text = subtitle,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
} }
} }
@@ -217,7 +266,7 @@ private fun WelcomeScreenPreview() {
@DayNightPreviews @DayNightPreviews
@Composable @Composable
private fun RestoreOrTransferBottomSheetPreview() { private fun RestoreOrTransferBottomSheetPreview() {
Previews.BottomSheetPreview(forceRtl = true) { Previews.BottomSheetPreview {
RestoreOrTransferBottomSheetContent( RestoreOrTransferBottomSheetContent(
sheetState = rememberModalBottomSheetState(), sheetState = rememberModalBottomSheetState(),
scope = rememberCoroutineScope(), scope = rememberCoroutineScope(),

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="44dp"
android:height="44dp"
android:viewportWidth="44"
android:viewportHeight="44">
<path
android:pathData="M31.22,26.773V7.624C31.22,5.783 30.252,4.853 28.354,4.853H26.02C25.754,4.853 25.602,5.005 25.602,5.271V5.612C25.602,6.315 25.147,6.808 24.426,6.808H19.605C18.884,6.808 18.429,6.315 18.429,5.612V5.271C18.429,5.005 18.277,4.853 17.992,4.853H15.658C13.76,4.853 12.792,5.764 12.792,7.605V8.326L10.743,6.277L10.837,5.897C11.255,3.677 12.887,2.5 15.373,2.5H28.639C31.694,2.5 33.573,4.341 33.573,7.301V29.126L31.22,26.773ZM36.571,38.273L5.827,7.548C5.391,7.112 5.391,6.371 5.827,5.954C6.283,5.48 7.004,5.517 7.44,5.954L38.166,36.679C38.602,37.115 38.621,37.837 38.166,38.273C37.729,38.71 37.008,38.729 36.571,38.273ZM15.373,41.575C12.337,41.575 10.458,39.753 10.458,36.793V15.101L12.792,17.455V36.451C12.792,38.311 13.76,39.241 15.658,39.241H28.354C30.214,39.241 31.163,38.349 31.22,36.565V35.844L33.212,37.894C33.193,38.007 33.155,38.14 33.136,38.273C32.662,40.418 31.087,41.575 28.639,41.575H15.373ZM18.087,37.894C17.65,37.894 17.328,37.59 17.328,37.153C17.328,36.698 17.65,36.394 18.087,36.394H25.944C26.38,36.394 26.703,36.698 26.703,37.153C26.703,37.59 26.38,37.894 25.944,37.894H18.087Z"
android:strokeWidth="0.4"
android:fillColor="#000000"
android:strokeColor="#000000"/>
</vector>

View File

@@ -84,4 +84,14 @@
<string name="VerificationCodeScreen__registration_error">Registration failed. Please try again.</string> <string name="VerificationCodeScreen__registration_error">Registration failed. Please try again.</string>
<!-- Button text for having trouble with verification --> <!-- Button text for having trouble with verification -->
<string name="VerificationCodeScreen__having_trouble">Having trouble?</string> <string name="VerificationCodeScreen__having_trouble">Having trouble?</string>
<!-- RestoreWelcomeBottomSheet -->
<!-- Row title for restore/transfer using a previous registered phone/device -->
<string name="WelcomeFragment_restore_action_i_have_my_old_phone">I have my old phone</string>
<!-- Row subtitle for restore/transfer using a previous registered phone/device -->
<string name="WelcomeFragment_restore_action_scan_qr">Scan a QR code from your current Signal account to get started quickly</string>
<!-- Row title for restore/transfer without using a previous device -->
<string name="WelcomeFragment_restore_action_i_dont_have_my_old_phone">I don\'t have my old phone</string>
<!-- Row subtitle for restore/transfer without using a previous device -->
<string name="WelcomeFragment_restore_action_reinstalling">Or you\'re reinstalling Signal on the same device</string>
</resources> </resources>