mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-07-12 08:47:04 +01:00
Add breakpoint helper and expand device previews.
Co-authored-by: jeffrey-signal <jeffrey@signal.org>
This commit is contained in:
committed by
Greyson Parrelli
parent
a7bb2831f8
commit
264447a6d9
@@ -12,9 +12,12 @@ import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.window.core.layout.WindowSizeClass
|
||||
import org.signal.core.ui.WindowBreakpoint
|
||||
import org.signal.core.ui.getWindowBreakpoint
|
||||
import org.signal.core.ui.isSplitPane
|
||||
|
||||
private val MEDIUM_CONTENT_CORNERS = 18.dp
|
||||
@@ -70,25 +73,27 @@ data class MainContentLayoutData(
|
||||
@Composable
|
||||
fun rememberContentLayoutData(mode: MainToolbarMode): MainContentLayoutData {
|
||||
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
|
||||
val resources = LocalResources.current
|
||||
val breakpoint = resources.getWindowBreakpoint()
|
||||
|
||||
return remember(windowSizeClass, mode) {
|
||||
return remember(windowSizeClass, mode, breakpoint) {
|
||||
val isSplitPane = windowSizeClass.isSplitPane()
|
||||
val isWidthExpanded = windowSizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND)
|
||||
val isLargeWindowSize = breakpoint == WindowBreakpoint.LARGE
|
||||
|
||||
MainContentLayoutData(
|
||||
shape = when {
|
||||
!isSplitPane -> RectangleShape
|
||||
isWidthExpanded -> RoundedCornerShape(EXTENDED_CONTENT_CORNERS)
|
||||
isLargeWindowSize -> RoundedCornerShape(EXTENDED_CONTENT_CORNERS)
|
||||
else -> RoundedCornerShape(MEDIUM_CONTENT_CORNERS)
|
||||
},
|
||||
navigationBarShape = when {
|
||||
!isSplitPane -> RectangleShape
|
||||
isWidthExpanded -> RoundedCornerShape(0.dp, 0.dp, EXTENDED_CONTENT_CORNERS, EXTENDED_CONTENT_CORNERS)
|
||||
isLargeWindowSize -> RoundedCornerShape(0.dp, 0.dp, EXTENDED_CONTENT_CORNERS, EXTENDED_CONTENT_CORNERS)
|
||||
else -> RoundedCornerShape(0.dp, 0.dp, MEDIUM_CONTENT_CORNERS, MEDIUM_CONTENT_CORNERS)
|
||||
},
|
||||
partitionWidth = when {
|
||||
!isSplitPane -> 0.dp
|
||||
isWidthExpanded -> 24.dp
|
||||
isLargeWindowSize -> 24.dp
|
||||
else -> 13.dp
|
||||
},
|
||||
listPaddingStart = when {
|
||||
@@ -102,7 +107,7 @@ data class MainContentLayoutData(
|
||||
},
|
||||
detailPaddingEnd = when {
|
||||
!isSplitPane -> 0.dp
|
||||
isWidthExpanded -> 24.dp
|
||||
isLargeWindowSize -> 24.dp
|
||||
else -> 12.dp
|
||||
}
|
||||
)
|
||||
|
||||
@@ -38,12 +38,17 @@ import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.layout
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import org.signal.core.ui.WindowBreakpoint
|
||||
import org.signal.core.ui.compose.AllDevicePreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.getWindowBreakpoint
|
||||
import org.signal.core.ui.isSplitPane
|
||||
import org.signal.core.ui.isWidthExpanded
|
||||
import org.thoughtcrime.securesms.main.MainFloatingActionButtonsCallback
|
||||
import org.thoughtcrime.securesms.main.MainNavigationBar
|
||||
import org.thoughtcrime.securesms.main.MainNavigationRail
|
||||
@@ -57,14 +62,21 @@ enum class NavigationType {
|
||||
companion object {
|
||||
@Composable
|
||||
fun rememberNavigationType(): NavigationType {
|
||||
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
|
||||
val resources = LocalResources.current
|
||||
val config = LocalConfiguration.current
|
||||
val windowBreakpoint = remember(config) { resources.getWindowBreakpoint() }
|
||||
|
||||
return remember(windowSizeClass) {
|
||||
if (windowSizeClass.isSplitPane()) {
|
||||
RAIL
|
||||
} else {
|
||||
BAR
|
||||
return when (windowBreakpoint) {
|
||||
WindowBreakpoint.SMALL -> BAR
|
||||
WindowBreakpoint.MEDIUM -> {
|
||||
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
|
||||
if (windowSizeClass.isWidthExpanded) {
|
||||
RAIL
|
||||
} else {
|
||||
BAR
|
||||
}
|
||||
}
|
||||
WindowBreakpoint.LARGE -> RAIL
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,7 +277,7 @@ private fun AppScaffoldPreview() {
|
||||
|
||||
AppScaffold(
|
||||
navigator = rememberAppScaffoldNavigator(
|
||||
isSplitPane = windowSizeClass.isSplitPane(),
|
||||
isSplitPane = windowSizeClass.isSplitPane(false),
|
||||
defaultPanePreferredWidth = 416.dp,
|
||||
horizontalPartitionSpacerSize = 16.dp
|
||||
),
|
||||
|
||||
@@ -21,6 +21,9 @@ val WindowSizeClass.isWidthCompact
|
||||
val WindowSizeClass.isHeightCompact
|
||||
get() = !isHeightAtLeastBreakpoint(WindowSizeClass.HEIGHT_DP_MEDIUM_LOWER_BOUND)
|
||||
|
||||
val WindowSizeClass.isWidthExpanded
|
||||
get() = isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND)
|
||||
|
||||
fun Resources.getWindowSizeClass(): WindowSizeClass {
|
||||
return WindowSizeClass.BREAKPOINTS_V1.computeWindowSizeClass(
|
||||
widthDp = displayMetrics.widthPixels / displayMetrics.density,
|
||||
@@ -28,6 +31,53 @@ fun Resources.getWindowSizeClass(): WindowSizeClass {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the device's form factor (PHONE, FOLDABLE, or TABLET) based on the current
|
||||
* [Resources] and window size class.
|
||||
*
|
||||
* This function uses several heuristics:
|
||||
* - 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].
|
||||
*
|
||||
* @return the inferred [WindowBreakpoint] for the current device.
|
||||
*/
|
||||
fun Resources.getWindowBreakpoint(): WindowBreakpoint {
|
||||
val windowSizeClass = getWindowSizeClass()
|
||||
|
||||
if (windowSizeClass.isWidthCompact || windowSizeClass.isHeightCompact) {
|
||||
return WindowBreakpoint.SMALL
|
||||
}
|
||||
|
||||
if (windowSizeClass.isHeightAtLeastBreakpoint(WindowSizeClass.HEIGHT_DP_EXPANDED_LOWER_BOUND)) {
|
||||
return WindowBreakpoint.LARGE
|
||||
}
|
||||
|
||||
val numerator = maxOf(displayMetrics.widthPixels, displayMetrics.heightPixels)
|
||||
val denominator = minOf(displayMetrics.widthPixels, displayMetrics.heightPixels)
|
||||
val aspectRatio = numerator.toFloat() / denominator
|
||||
|
||||
return if (aspectRatio >= 1.6f) {
|
||||
WindowBreakpoint.LARGE
|
||||
} else {
|
||||
WindowBreakpoint.MEDIUM
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the general form factor of the device for responsive UI purposes.
|
||||
*
|
||||
* - [SMALL]: A window similar to a phone-sized device, typically with a compact width or height.
|
||||
* - [MEDIUM]: A window similar to a foldable or medium-size device, or a device which doesn't obviously fit into phone or tablet by heuristics.
|
||||
* - [LARGE]: A window similar to a large-screen tablet device, typically with an expanded height or wide aspect ratio.
|
||||
*/
|
||||
enum class WindowBreakpoint {
|
||||
SMALL,
|
||||
MEDIUM,
|
||||
LARGE
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the UI should display in split-pane mode based on available screen space.
|
||||
*/
|
||||
@@ -40,7 +90,7 @@ fun WindowSizeClass.isSplitPane(
|
||||
}
|
||||
|
||||
return isAtLeastBreakpoint(
|
||||
widthDpBreakpoint = WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND,
|
||||
widthDpBreakpoint = WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND,
|
||||
heightDpBreakpoint = WindowSizeClass.HEIGHT_DP_MEDIUM_LOWER_BOUND
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,27 +19,109 @@ annotation class NightPreview()
|
||||
annotation class DayNightPreviews
|
||||
|
||||
@Preview(name = "phone portrait (day)", uiMode = Configuration.UI_MODE_NIGHT_NO, device = "spec:width=360dp,height=640dp,orientation=portrait")
|
||||
@Preview(name = "phone portrait (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=360dp,height=640dp,orientation=portrait")
|
||||
annotation class PhonePortraitDayPreview
|
||||
|
||||
@Preview(name = "phone landscape (day)", uiMode = Configuration.UI_MODE_NIGHT_NO, device = "spec:width=640dp,height=360dp,orientation=landscape")
|
||||
annotation class PhoneLandscapeDayPreview
|
||||
|
||||
@PhonePortraitDayPreview
|
||||
@PhoneLandscapeDayPreview
|
||||
annotation class PhoneDayPreviews
|
||||
|
||||
@Preview(name = "phone portrait (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=360dp,height=640dp,orientation=portrait")
|
||||
annotation class PhonePortraitNightPreview
|
||||
|
||||
@Preview(name = "phone landscape (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=640dp,height=360dp,orientation=landscape")
|
||||
annotation class PhoneLandscapeNightPreview
|
||||
|
||||
@PhonePortraitNightPreview
|
||||
@PhoneLandscapeNightPreview
|
||||
annotation class PhoneNightPreviews
|
||||
|
||||
@PhoneDayPreviews
|
||||
@PhoneLandscapeNightPreview
|
||||
annotation class PhonePreviews
|
||||
|
||||
@Preview(name = "foldable portrait (day)", uiMode = Configuration.UI_MODE_NIGHT_NO, device = "spec:width=600dp,height=1024dp,orientation=portrait")
|
||||
@Preview(name = "foldable landscape (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=1024dp,height=600dp,orientation=landscape")
|
||||
@Preview(name = "small foldable portrait (day)", uiMode = Configuration.UI_MODE_NIGHT_NO, device = "spec:width=620dp,height=720dp,orientation=portrait")
|
||||
annotation class SmallFoldablePortraitDayPreview
|
||||
|
||||
@Preview(name = "small foldable landscape (day)", uiMode = Configuration.UI_MODE_NIGHT_NO, device = "spec:width=720dp,height=620dp,orientation=landscape")
|
||||
annotation class SmallFoldableLandscapeDayPreview
|
||||
|
||||
@SmallFoldablePortraitDayPreview
|
||||
@SmallFoldableLandscapeDayPreview
|
||||
annotation class SmallFoldableDayPreviews
|
||||
|
||||
@Preview(name = "small foldable portrait (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=620dp,height=720dp,orientation=portrait")
|
||||
annotation class SmallFoldablePortraitNightPreview
|
||||
|
||||
@Preview(name = "small foldable landscape (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=720dp,height=620dp,orientation=landscape")
|
||||
annotation class SmallFoldableLandscapeNightPreview
|
||||
|
||||
@SmallFoldablePortraitNightPreview
|
||||
@SmallFoldableLandscapeNightPreview
|
||||
annotation class SmallFoldableNightPreviews
|
||||
|
||||
@SmallFoldableDayPreviews
|
||||
@SmallFoldableLandscapeNightPreview
|
||||
annotation class SmallFoldablePreviews
|
||||
|
||||
@Preview(name = "foldable portrait (day)", uiMode = Configuration.UI_MODE_NIGHT_NO, device = "spec:width=850dp,height=881dp,orientation=portrait")
|
||||
annotation class FoldablePortraitDayPreview
|
||||
|
||||
@Preview(name = "foldable landscape (day)", uiMode = Configuration.UI_MODE_NIGHT_NO, device = "spec:width=881dp,height=850dp,orientation=landscape")
|
||||
annotation class FoldableLandscapeDayPreview
|
||||
|
||||
@FoldablePortraitDayPreview
|
||||
@FoldableLandscapeDayPreview
|
||||
annotation class FoldableDayPreviews
|
||||
|
||||
@Preview(name = "foldable portrait (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=850dp,height=881dp,orientation=portrait")
|
||||
annotation class FoldablePortraitNightPreview
|
||||
|
||||
@Preview(name = "foldable landscape (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=881dp,height=850dp,orientation=landscape")
|
||||
annotation class FoldableLandscapeNightPreview
|
||||
|
||||
@FoldablePortraitNightPreview
|
||||
@FoldableLandscapeNightPreview
|
||||
annotation class FoldableNightPreviews
|
||||
|
||||
@FoldableDayPreviews
|
||||
@FoldableLandscapeNightPreview
|
||||
annotation class FoldablePreviews
|
||||
|
||||
@Preview(name = "tablet portrait (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=840dp,height=1280dp,orientation=portrait")
|
||||
@Preview(name = "tablet portrait (day)", uiMode = Configuration.UI_MODE_NIGHT_NO, device = "spec:width=840dp,height=1280dp,orientation=portrait")
|
||||
annotation class TabletPortraitDayPreview
|
||||
|
||||
@Preview(name = "tablet landscape (day)", uiMode = Configuration.UI_MODE_NIGHT_NO, device = "spec:width=1280dp,height=840dp,orientation=landscape")
|
||||
annotation class TabletLandscapeDayPreview
|
||||
|
||||
@TabletPortraitDayPreview
|
||||
@TabletLandscapeDayPreview
|
||||
annotation class TabletDayPreviews
|
||||
|
||||
@Preview(name = "tablet portrait (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=1280dp,height=840dp,orientation=portrait")
|
||||
annotation class TabletPortraitNightPreview
|
||||
|
||||
@Preview(name = "tablet landscape (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=840dp,height=1280dp,orientation=landscape")
|
||||
annotation class TabletLandscapeNightPreview
|
||||
|
||||
@TabletPortraitNightPreview
|
||||
@TabletLandscapeNightPreview
|
||||
annotation class TabletNightPreviews
|
||||
|
||||
@TabletDayPreviews
|
||||
@TabletLandscapeNightPreview
|
||||
annotation class TabletPreviews
|
||||
|
||||
@Preview(name = "phone portrait (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=360dp,height=640dp,orientation=portrait")
|
||||
@Preview(name = "phone landscape (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=640dp,height=360dp,orientation=landscape")
|
||||
@Preview(name = "foldable portrait (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=600dp,height=1024dp,orientation=portrait")
|
||||
@Preview(name = "foldable landscape (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=1024dp,height=600dp,orientation=landscape")
|
||||
@Preview(name = "tablet portrait (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=840dp,height=1280dp,orientation=portrait")
|
||||
@Preview(name = "tablet landscape (night)", uiMode = Configuration.UI_MODE_NIGHT_YES, device = "spec:width=1280dp,height=840dp,orientation=landscape")
|
||||
@PhoneNightPreviews
|
||||
@SmallFoldableNightPreviews
|
||||
@FoldableNightPreviews
|
||||
@TabletNightPreviews
|
||||
annotation class AllNightPreviews
|
||||
|
||||
@PhonePreviews
|
||||
@SmallFoldablePreviews
|
||||
@FoldablePreviews
|
||||
@TabletPreviews
|
||||
annotation class AllDevicePreviews
|
||||
|
||||
Reference in New Issue
Block a user