diff --git a/core/ui/src/main/java/org/signal/core/ui/WindowSizeClassExtensions.kt b/core/ui/src/main/java/org/signal/core/ui/WindowSizeClassExtensions.kt index c0911db628..2a34aec6ce 100644 --- a/core/ui/src/main/java/org/signal/core/ui/WindowSizeClassExtensions.kt +++ b/core/ui/src/main/java/org/signal/core/ui/WindowSizeClassExtensions.kt @@ -6,6 +6,10 @@ package org.signal.core.ui import android.content.res.Resources +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.window.core.layout.WindowSizeClass @@ -31,6 +35,16 @@ fun Resources.getWindowSizeClass(): WindowSizeClass { ) } +@Composable +fun rememberWindowBreakpoint(): WindowBreakpoint { + val resources = LocalResources.current + val configuration = LocalConfiguration.current + + return remember(resources, configuration) { + resources.getWindowBreakpoint() + } +} + /** * Determines the device's form factor (PHONE, FOLDABLE, or TABLET) based on the current * [Resources] and window size class. @@ -39,7 +53,7 @@ fun Resources.getWindowSizeClass(): WindowSizeClass { * - Returns [WindowBreakpoint.SMALL] if the width or height is compact. * - Returns [WindowBreakpoint.LARGE] if the height is at least the expanded lower bound. * - Returns [WindowBreakpoint.MEDIUM] if the width is at least the medium lower bound. - * - Otherwise, falls back to aspect ratio heuristics: wider (≥ 1.6) is [WindowBreakpoint.LARGE], else [WindowBreakpoint.MEDIUM]. + * - Otherwise, falls back to aspect ratio heuristics: wider (≥ 1.5) is [WindowBreakpoint.LARGE], else [WindowBreakpoint.MEDIUM]. * * @return the inferred [WindowBreakpoint] for the current device. */ @@ -58,7 +72,7 @@ fun Resources.getWindowBreakpoint(): WindowBreakpoint { val denominator = minOf(displayMetrics.widthPixels, displayMetrics.heightPixels) val aspectRatio = numerator.toFloat() / denominator - return if (aspectRatio >= 1.6f) { + return if (aspectRatio >= 1.5f) { WindowBreakpoint.LARGE } else { WindowBreakpoint.MEDIUM diff --git a/core/ui/src/main/java/org/signal/core/ui/compose/Layouts.kt b/core/ui/src/main/java/org/signal/core/ui/compose/Layouts.kt new file mode 100644 index 0000000000..4b278a4469 --- /dev/null +++ b/core/ui/src/main/java/org/signal/core/ui/compose/Layouts.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.core.ui.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.tooling.preview.Preview + +/** + * Lays out [primary] and [secondary] side by side in a row. [primaryProportion] controls what + * fraction of the total width [primary] receives (e.g. 0.5f = equal split). + */ +@Composable +fun SideBySideLayout( + primary: @Composable () -> Unit, + secondary: @Composable () -> Unit, + modifier: Modifier = Modifier, + primaryProportion: Float = 0.5f +) { + SubcomposeLayout(modifier = modifier) { constraints -> + val primaryWidth = (constraints.maxWidth * primaryProportion).toInt() + val secondaryWidth = constraints.maxWidth - primaryWidth + + val primaryPlaceables = subcompose("primary", primary).map { m -> + m.measure(constraints.copy(minWidth = primaryWidth, maxWidth = primaryWidth, minHeight = 0)) + } + val primaryHeight = primaryPlaceables.maxOfOrNull { it.height } ?: 0 + + val secondaryMeasurables = subcompose("secondary", secondary) + val secondaryMinHeight = secondaryMeasurables.maxOfOrNull { it.minIntrinsicHeight(secondaryWidth) } ?: 0 + val layoutHeight = maxOf(primaryHeight, secondaryMinHeight) + + val secondaryPlaceables = secondaryMeasurables.map { m -> + m.measure(constraints.copy(minWidth = secondaryWidth, maxWidth = secondaryWidth, minHeight = layoutHeight, maxHeight = layoutHeight)) + } + + layout(constraints.maxWidth, layoutHeight) { + primaryPlaceables.forEach { it.placeRelative(0, 0) } + secondaryPlaceables.forEach { it.placeRelative(primaryWidth, 0) } + } + } +} + +@Preview +@Composable +private fun SideBySideLayoutPreview() { + Previews.Preview { + SideBySideLayout( + primary = { + Box( + modifier = Modifier + .fillMaxSize() + .background(color = Color.Red) + ) + }, + secondary = { + Box( + modifier = Modifier + .fillMaxSize() + .background(color = Color.Blue) + ) + }, + modifier = Modifier.fillMaxSize() + ) + } +} diff --git a/core/ui/src/main/java/org/signal/core/ui/compose/SignalPreviews.kt b/core/ui/src/main/java/org/signal/core/ui/compose/SignalPreviews.kt index 19e9b0fabc..d96f93bf5d 100644 --- a/core/ui/src/main/java/org/signal/core/ui/compose/SignalPreviews.kt +++ b/core/ui/src/main/java/org/signal/core/ui/compose/SignalPreviews.kt @@ -100,10 +100,10 @@ annotation class TabletLandscapeDayPreview @TabletLandscapeDayPreview annotation class TabletDayPreviews -@Preview(name = "tablet portrait (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=1280dp,height=840dp,orientation=portrait") +@Preview(name = "tablet portrait (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=840dp,height=1280dp,orientation=portrait") annotation class TabletPortraitNightPreview -@Preview(name = "tablet landscape (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=840dp,height=1280dp,orientation=landscape") +@Preview(name = "tablet landscape (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=1280dp,height=840dp,orientation=landscape") annotation class TabletLandscapeNightPreview @TabletPortraitNightPreview diff --git a/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt b/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt index 29f743ee94..490ab78f49 100644 --- a/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt +++ b/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt @@ -249,6 +249,7 @@ private fun EntryProviderScope.navigationEntries( onEvent = { event -> when (event) { WelcomeScreenEvents.Continue -> parentEventEmitter.navigateTo(RegistrationRoute.Permissions(nextRoute = RegistrationRoute.PhoneNumberEntry)) + WelcomeScreenEvents.LinkDevice -> parentEventEmitter.navigateTo(RegistrationRoute.Permissions(nextRoute = RegistrationRoute.QuickRestoreQrScan)) // TODO - Replace this with the device-link QR code WelcomeScreenEvents.HasOldPhone -> parentEventEmitter.navigateTo(RegistrationRoute.Permissions(nextRoute = RegistrationRoute.QuickRestoreQrScan)) WelcomeScreenEvents.DoesNotHaveOldPhone -> parentEventEmitter.navigateTo(RegistrationRoute.Permissions(nextRoute = RegistrationRoute.ChooseRestoreOptionBeforeRegistration)) } diff --git a/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScreen.kt b/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScreen.kt new file mode 100644 index 0000000000..cb1cef9bc4 --- /dev/null +++ b/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScreen.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.registration.screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.signal.core.ui.compose.Previews + +/** + * Scaffold for registration flow screens. + */ +@Composable +fun RegistrationScreen( + content: @Composable () -> Unit, + modifier: Modifier = Modifier, + footer: (@Composable () -> Unit)? = null +) { + SubcomposeLayout(modifier = modifier.imePadding()) { constraints -> + val footerPlaceables = footer?.let { + subcompose("footer", it).map { m -> m.measure(constraints.copy(minHeight = 0)) } + } ?: emptyList() + val footerHeight = footerPlaceables.maxOfOrNull { it.height } ?: 0 + + val contentHeight = (constraints.maxHeight - footerHeight).coerceAtLeast(0) + val contentPlaceables = subcompose("content", content).map { m -> + m.measure(constraints.copy(minHeight = contentHeight, maxHeight = contentHeight)) + } + + layout(constraints.maxWidth, constraints.maxHeight) { + contentPlaceables.forEach { it.placeRelative(0, 0) } + footerPlaceables.forEach { it.placeRelative(0, contentHeight) } + } + } +} + +@Preview +@Composable +private fun RegistrationScreenPreview() { + Previews.Preview { + RegistrationScreen( + content = { + Box( + modifier = Modifier + .fillMaxSize() + .background(color = Color.Red) + ) + }, + footer = { + Box( + modifier = Modifier + .fillMaxWidth() + .height(100.dp) + .background(color = Color.Green) + ) + }, + modifier = Modifier.fillMaxSize() + ) + } +} diff --git a/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreen.kt b/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreen.kt index e96e15ccd2..c09bb49ca8 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreen.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreen.kt @@ -10,7 +10,9 @@ package org.signal.registration.screens.welcome import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -18,6 +20,7 @@ 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.shape.RoundedCornerShape import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api @@ -41,18 +44,23 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import kotlinx.coroutines.CoroutineScope +import org.signal.core.ui.WindowBreakpoint import org.signal.core.ui.compose.AllDevicePreviews import org.signal.core.ui.compose.BottomSheets import org.signal.core.ui.compose.Buttons import org.signal.core.ui.compose.Previews +import org.signal.core.ui.compose.SideBySideLayout import org.signal.core.ui.compose.SignalIcons 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.rememberWindowBreakpoint import org.signal.registration.R +import org.signal.registration.screens.RegistrationScreen import org.signal.registration.test.TestTags /** @@ -65,74 +73,37 @@ fun WelcomeScreen( modifier: Modifier = Modifier ) { var showBottomSheet by remember { mutableStateOf(false) } + val windowBreakpoint = rememberWindowBreakpoint() + val onRestoreOrTransferClick = { showBottomSheet = true } + val onTermsAndPrivacyClick: () -> Unit = {} - Column( - modifier = modifier - .fillMaxSize() - .testTag(TestTags.WELCOME_SCREEN), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Image( - painter = painterResource(R.drawable.welcome), - contentDescription = null, - modifier = Modifier - .weight(1f) - .fillMaxWidth() - .padding(16.dp), - contentScale = ContentScale.Fit - ) - - Text( - text = stringResource(R.string.RegistrationActivity_take_privacy_with_you_be_yourself_in_every_message), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center, - modifier = Modifier - .padding(horizontal = 32.dp) - .testTag(TestTags.WELCOME_HEADLINE) - ) - - Spacer(modifier = Modifier.height(40.dp)) - - TextButton( - onClick = { /* Terms & Privacy link */ }, - colors = ButtonDefaults.textButtonColors( - contentColor = MaterialTheme.colorScheme.onSurfaceVariant - ) - ) { - Text( - text = stringResource(R.string.RegistrationActivity_terms_and_privacy), - textAlign = TextAlign.Center + when (windowBreakpoint) { + WindowBreakpoint.SMALL -> { + CompactLayout( + onEvent = onEvent, + onRestoreOrTransferClick = onRestoreOrTransferClick, + onTermsAndPrivacyClick = onTermsAndPrivacyClick, + modifier = modifier ) } - Spacer(modifier = Modifier.height(24.dp)) - - Buttons.LargeTonal( - onClick = { onEvent(WelcomeScreenEvents.Continue) }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 32.dp) - .testTag(TestTags.WELCOME_GET_STARTED_BUTTON) - ) { - Text(stringResource(R.string.RegistrationActivity_continue)) + WindowBreakpoint.MEDIUM -> { + MediumLayout( + onEvent = onEvent, + onRestoreOrTransferClick = onRestoreOrTransferClick, + onTermsAndPrivacyClick = onTermsAndPrivacyClick, + modifier = modifier + ) } - Spacer(modifier = Modifier.height(17.dp)) - - Buttons.LargeTonal( - onClick = { showBottomSheet = true }, - colors = ButtonDefaults.filledTonalButtonColors( - containerColor = SignalTheme.colors.colorSurface2 - ), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 32.dp) - .testTag(TestTags.WELCOME_RESTORE_OR_TRANSFER_BUTTON) - ) { - Text(stringResource(R.string.registration_activity__restore_or_transfer)) + WindowBreakpoint.LARGE -> { + LargeLayout( + onEvent = onEvent, + onTermsAndPrivacyClick = onTermsAndPrivacyClick, + onRestoreOrTransferClick = onRestoreOrTransferClick, + modifier = modifier + ) } - - Spacer(modifier = Modifier.height(48.dp)) } if (showBottomSheet) { @@ -146,6 +117,261 @@ fun WelcomeScreen( } } +@Composable +private fun CompactLayout( + onEvent: (WelcomeScreenEvents) -> Unit, + onTermsAndPrivacyClick: () -> Unit, + onRestoreOrTransferClick: () -> Unit, + modifier: Modifier = Modifier +) { + RegistrationScreen( + modifier = modifier + .fillMaxSize() + .testTag(TestTags.WELCOME_SCREEN), + content = { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + HeroImage( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .padding(16.dp) + ) + + Headline( + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = 32.dp) + ) + + Spacer(modifier = Modifier.height(40.dp)) + } + }, + footer = { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + TermsAndPrivacy(onTermsAndPrivacyClick = onTermsAndPrivacyClick) + + Spacer(modifier = Modifier.height(24.dp)) + + PrimaryDeviceCallToActionButtons( + onEvent = onEvent, + onRestoreOrTransferClick = onRestoreOrTransferClick + ) + + Spacer(modifier = Modifier.height(48.dp)) + } + } + ) +} + +@Composable +private fun MediumLayout( + onEvent: (WelcomeScreenEvents) -> Unit, + onTermsAndPrivacyClick: () -> Unit, + onRestoreOrTransferClick: () -> Unit, + modifier: Modifier = Modifier +) { + RegistrationScreen( + modifier = modifier + .fillMaxSize() + .padding(bottom = 56.dp), + content = { + SideBySideLayout( + modifier = Modifier.fillMaxSize(), + primary = { + HeroImage() + }, + secondary = { + Headline( + modifier = Modifier.padding(top = 88.dp) + ) + } + ) + }, + footer = { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + TermsAndPrivacy(onTermsAndPrivacyClick = onTermsAndPrivacyClick) + + PrimaryDeviceCallToActionButtons( + onEvent = onEvent, + onRestoreOrTransferClick = onRestoreOrTransferClick + ) + } + } + ) +} + +@Composable +private fun LargeLayout( + onEvent: (WelcomeScreenEvents) -> Unit, + onTermsAndPrivacyClick: () -> Unit, + onRestoreOrTransferClick: () -> Unit, + modifier: Modifier = Modifier +) { + RegistrationScreen( + modifier = modifier.fillMaxSize(), + content = { + SideBySideLayout( + modifier = Modifier.fillMaxSize(), + primary = { + HeroImage( + modifier = Modifier.padding(vertical = 10.dp) + ) + }, + secondary = { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxWidth() + ) { + Column( + horizontalAlignment = Alignment.Start, + modifier = Modifier + .widthIn(max = 380.dp) + .fillMaxWidth() + .padding(top = 98.dp) + ) { + Headline( + style = MaterialTheme.typography.headlineLarge, + modifier = Modifier.padding(start = 10.dp) + ) + + Spacer(modifier = Modifier.weight(1f)) + + TermsAndPrivacy( + onTermsAndPrivacyClick = onTermsAndPrivacyClick, + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(bottom = 8.dp) + ) + + PrimaryDeviceCallToActionButtons( + onEvent = onEvent, + onRestoreOrTransferClick = onRestoreOrTransferClick + ) + } + } + } + ) + } + ) +} + +@Composable +private fun HeroImage( + modifier: Modifier = Modifier +) { + Image( + painter = painterResource(R.drawable.welcome), + contentDescription = null, + modifier = modifier, + contentScale = ContentScale.Fit + ) +} + +@Composable +private fun Headline( + modifier: Modifier = Modifier, + style: TextStyle = MaterialTheme.typography.headlineMedium, + textAlign: TextAlign = TextAlign.Start +) { + Text( + text = stringResource(R.string.RegistrationActivity_take_privacy_with_you_be_yourself_in_every_message), + style = style, + textAlign = textAlign, + modifier = modifier + .testTag(TestTags.WELCOME_HEADLINE) + ) +} + +@Composable +private fun TermsAndPrivacy( + onTermsAndPrivacyClick: () -> Unit, + modifier: Modifier = Modifier +) { + TextButton( + onClick = onTermsAndPrivacyClick, + colors = ButtonDefaults.textButtonColors( + contentColor = MaterialTheme.colorScheme.onSurfaceVariant + ), + modifier = modifier + ) { + Text( + text = stringResource(R.string.RegistrationActivity_terms_and_privacy), + textAlign = TextAlign.Center + ) + } +} + +@Composable +private fun ColumnScope.PrimaryDeviceCallToActionButtons( + onEvent: (WelcomeScreenEvents) -> Unit, + onRestoreOrTransferClick: () -> Unit +) { + Buttons.LargeTonal( + onClick = { onEvent(WelcomeScreenEvents.Continue) }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp) + .testTag(TestTags.WELCOME_GET_STARTED_BUTTON) + ) { + Text(stringResource(R.string.RegistrationActivity_continue)) + } + + Spacer(modifier = Modifier.height(17.dp)) + + Buttons.LargeTonal( + onClick = onRestoreOrTransferClick, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = SignalTheme.colors.colorSurface2 + ), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp) + .testTag(TestTags.WELCOME_RESTORE_OR_TRANSFER_BUTTON) + ) { + Text(stringResource(R.string.registration_activity__restore_or_transfer)) + } +} + +@Composable +private fun ColumnScope.SecondaryDeviceCallToActionButtons( + onEvent: (WelcomeScreenEvents) -> Unit +) { + Buttons.LargeTonal( + onClick = { onEvent(WelcomeScreenEvents.LinkDevice) }, + modifier = Modifier + .fillMaxWidth() + .testTag(TestTags.WELCOME_LINK_DEVICE_BUTTON) + ) { + Text(stringResource(R.string.WelcomeScreen__link_your_account)) + } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) { + Text( + text = stringResource(R.string.WelcomeScreen__not_on_signal_yet) + ) + + TextButton( + onClick = { onEvent(WelcomeScreenEvents.Continue) }, + modifier = Modifier.testTag(TestTags.WELCOME_GET_STARTED_BUTTON) + ) { + Text( + text = stringResource(R.string.WelcomeScreen__create_account) + ) + } + } +} + /** * Bottom sheet for restore or transfer options. */ diff --git a/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreenEvents.kt b/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreenEvents.kt index d091c7bffd..8a70c1eb6d 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreenEvents.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/welcome/WelcomeScreenEvents.kt @@ -9,6 +9,7 @@ import org.signal.registration.util.DebugLoggableModel sealed class WelcomeScreenEvents : DebugLoggableModel() { data object Continue : WelcomeScreenEvents() + data object LinkDevice : WelcomeScreenEvents() data object HasOldPhone : WelcomeScreenEvents() data object DoesNotHaveOldPhone : WelcomeScreenEvents() } diff --git a/feature/registration/src/main/java/org/signal/registration/test/TestTags.kt b/feature/registration/src/main/java/org/signal/registration/test/TestTags.kt index b24728beda..e8570c5f8a 100644 --- a/feature/registration/src/main/java/org/signal/registration/test/TestTags.kt +++ b/feature/registration/src/main/java/org/signal/registration/test/TestTags.kt @@ -14,6 +14,7 @@ object TestTags { const val WELCOME_SCREEN = "welcome_screen" const val WELCOME_HEADLINE = "welcome_headline" const val WELCOME_GET_STARTED_BUTTON = "welcome_get_started_button" + const val WELCOME_LINK_DEVICE_BUTTON = "welcome_link_device_button" const val WELCOME_RESTORE_OR_TRANSFER_BUTTON = "welcome_restore_or_transfer_button" const val WELCOME_RESTORE_HAS_OLD_PHONE_BUTTON = "welcome_restore_has_old_phone_button" const val WELCOME_RESTORE_NO_OLD_PHONE_BUTTON = "welcome_restore_no_old_phone_button" diff --git a/feature/registration/src/main/res/values/strings.xml b/feature/registration/src/main/res/values/strings.xml index 8304f2ab9a..2abf049f55 100644 --- a/feature/registration/src/main/res/values/strings.xml +++ b/feature/registration/src/main/res/values/strings.xml @@ -205,4 +205,10 @@ An error occurred while restoring your backup. Please try again. Try again + + Link your account + + Not on Signal yet? + + Create account diff --git a/feature/registration/src/test/java/org/signal/registration/screens/welcome/WelcomeScreenTest.kt b/feature/registration/src/test/java/org/signal/registration/screens/welcome/WelcomeScreenTest.kt index e04524f11d..8dd9cd13c4 100644 --- a/feature/registration/src/test/java/org/signal/registration/screens/welcome/WelcomeScreenTest.kt +++ b/feature/registration/src/test/java/org/signal/registration/screens/welcome/WelcomeScreenTest.kt @@ -56,6 +56,29 @@ class WelcomeScreenTest { assert(emittedEvent == WelcomeScreenEvents.Continue) } + @Config(qualifiers = "w1280dp-h800dp-xhdpi") + @Test + fun `when Link Your Account is clicked, LinkDevice event is emitted`() { + // Given + var emittedEvent: WelcomeScreenEvents? = null + + composeTestRule.setContent { + SignalTheme { + WelcomeScreen( + onEvent = { event -> + emittedEvent = event + } + ) + } + } + + // When + composeTestRule.onNodeWithTag(TestTags.WELCOME_GET_STARTED_BUTTON).performClick() + + // Then + assert(emittedEvent == WelcomeScreenEvents.Continue) + } + @Test fun `when Restore or transfer is clicked, bottom sheet is shown`() { // Given