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 deleted file mode 100644 index 4b278a4469..0000000000 --- a/core/ui/src/main/java/org/signal/core/ui/compose/Layouts.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScreen.kt b/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScreen.kt index cb1cef9bc4..4b431e6f74 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScreen.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScreen.kt @@ -30,7 +30,7 @@ fun RegistrationScreen( ) { SubcomposeLayout(modifier = modifier.imePadding()) { constraints -> val footerPlaceables = footer?.let { - subcompose("footer", it).map { m -> m.measure(constraints.copy(minHeight = 0)) } + subcompose("footer", it).map { m -> m.measure(constraints.copy(minWidth = 0, minHeight = 0)) } } ?: emptyList() val footerHeight = footerPlaceables.maxOfOrNull { it.height } ?: 0 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 c09bb49ca8..0f1b7c8dee 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,11 +10,14 @@ 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.Arrangement +import androidx.compose.foundation.layout.Arrangement.SpaceAround 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.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -29,6 +32,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SheetState import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -53,11 +57,11 @@ 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.isWidthExpanded import org.signal.core.ui.rememberWindowBreakpoint import org.signal.registration.R import org.signal.registration.screens.RegistrationScreen @@ -149,20 +153,27 @@ private fun CompactLayout( } }, footer = { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp), + contentAlignment = Alignment.Center ) { - TermsAndPrivacy(onTermsAndPrivacyClick = onTermsAndPrivacyClick) + Column( + modifier = Modifier.widthIn(max = 320.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + TermsAndPrivacy(onTermsAndPrivacyClick = onTermsAndPrivacyClick) - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.height(24.dp)) - PrimaryDeviceCallToActionButtons( - onEvent = onEvent, - onRestoreOrTransferClick = onRestoreOrTransferClick - ) + PrimaryDeviceCallToActionButtons( + onEvent = onEvent, + onRestoreOrTransferClick = onRestoreOrTransferClick + ) - Spacer(modifier = Modifier.height(48.dp)) + Spacer(modifier = Modifier.height(48.dp)) + } } } ) @@ -180,29 +191,45 @@ private fun MediumLayout( .fillMaxSize() .padding(bottom = 56.dp), content = { - SideBySideLayout( - modifier = Modifier.fillMaxSize(), - primary = { - HeroImage() - }, - secondary = { + Box { + Row(modifier = Modifier.fillMaxWidth()) { + HeroImage( + modifier = Modifier + .fillMaxHeight() + .weight(1f) + .padding(horizontal = 24.dp) + ) + Headline( - modifier = Modifier.padding(top = 88.dp) + modifier = Modifier + .align(Alignment.CenterVertically) + .weight(1f) + .padding(horizontal = 24.dp) ) } - ) + + TermsAndPrivacy( + onTermsAndPrivacyClick = onTermsAndPrivacyClick, + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 24.dp) + ) + } }, footer = { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - TermsAndPrivacy(onTermsAndPrivacyClick = onTermsAndPrivacyClick) + val isWidthExpanded = currentWindowAdaptiveInfo().windowSizeClass.isWidthExpanded - PrimaryDeviceCallToActionButtons( - onEvent = onEvent, - onRestoreOrTransferClick = onRestoreOrTransferClick - ) + Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + Column( + modifier = Modifier + .widthIn(max = if (isWidthExpanded) 412.dp else 320.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + PrimaryDeviceCallToActionButtons( + onEvent = onEvent, + onRestoreOrTransferClick = onRestoreOrTransferClick + ) + } } } ) @@ -218,47 +245,49 @@ private fun LargeLayout( 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() + Row( + horizontalArrangement = SpaceAround, + modifier = Modifier.padding(vertical = 56.dp) + ) { + HeroImage( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + ) + + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .weight(1f) + ) { + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxHeight() + .widthIn(max = 320.dp) + .fillMaxWidth() ) { - Column( - horizontalAlignment = Alignment.Start, + Headline( + style = MaterialTheme.typography.headlineLarge + ) + + Spacer(modifier = Modifier.height(77.dp)) + + TermsAndPrivacy( + onTermsAndPrivacyClick = onTermsAndPrivacyClick, modifier = Modifier - .widthIn(max = 380.dp) - .fillMaxWidth() - .padding(top = 98.dp) - ) { - Headline( - style = MaterialTheme.typography.headlineLarge, - modifier = Modifier.padding(start = 10.dp) - ) + .align(Alignment.CenterHorizontally) + .padding(bottom = 8.dp) + ) - Spacer(modifier = Modifier.weight(1f)) - - TermsAndPrivacy( - onTermsAndPrivacyClick = onTermsAndPrivacyClick, - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(bottom = 8.dp) - ) - - PrimaryDeviceCallToActionButtons( - onEvent = onEvent, - onRestoreOrTransferClick = onRestoreOrTransferClick - ) - } + PrimaryDeviceCallToActionButtons( + onEvent = onEvent, + onRestoreOrTransferClick = onRestoreOrTransferClick + ) } } - ) + } } ) } @@ -310,7 +339,7 @@ private fun TermsAndPrivacy( } @Composable -private fun ColumnScope.PrimaryDeviceCallToActionButtons( +private fun PrimaryDeviceCallToActionButtons( onEvent: (WelcomeScreenEvents) -> Unit, onRestoreOrTransferClick: () -> Unit ) { @@ -318,7 +347,6 @@ private fun ColumnScope.PrimaryDeviceCallToActionButtons( onClick = { onEvent(WelcomeScreenEvents.Continue) }, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 32.dp) .testTag(TestTags.WELCOME_GET_STARTED_BUTTON) ) { Text(stringResource(R.string.RegistrationActivity_continue)) @@ -333,7 +361,6 @@ private fun ColumnScope.PrimaryDeviceCallToActionButtons( ), modifier = Modifier .fillMaxWidth() - .padding(horizontal = 32.dp) .testTag(TestTags.WELCOME_RESTORE_OR_TRANSFER_BUTTON) ) { Text(stringResource(R.string.registration_activity__restore_or_transfer))