mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 17:29:32 +01:00
Adaptive welcome screen with compact, medium, and large layouts.
This commit is contained in:
committed by
Greyson Parrelli
parent
38baf17938
commit
d90e9919ae
@@ -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
|
||||
|
||||
74
core/ui/src/main/java/org/signal/core/ui/compose/Layouts.kt
Normal file
74
core/ui/src/main/java/org/signal/core/ui/compose/Layouts.kt
Normal file
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user