Add breakpoint helper and expand device previews.

Co-authored-by: jeffrey-signal <jeffrey@signal.org>
This commit is contained in:
Alex Hart
2026-04-06 13:18:35 -03:00
committed by Greyson Parrelli
parent a7bb2831f8
commit 264447a6d9
4 changed files with 173 additions and 24 deletions

View File

@@ -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
)
}

View File

@@ -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