mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-07-12 16:57:18 +01:00
Welcome screen polish.
This commit is contained in:
committed by
jeffrey-signal
parent
969635d942
commit
faa6a1d3f0
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
|
||||
+94
-67
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user