diff --git a/app/src/main/java/org/thoughtcrime/securesms/main/MainContentLayoutData.kt b/app/src/main/java/org/thoughtcrime/securesms/main/MainContentLayoutData.kt index a456f2ff32..a6f28fd7f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/main/MainContentLayoutData.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/main/MainContentLayoutData.kt @@ -16,6 +16,7 @@ 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.rememberIsSplitPane @@ -78,7 +79,7 @@ data class MainContentLayoutData( val isSplitPane = resources.rememberIsSplitPane() return remember(windowSizeClass, mode, breakpoint, isSplitPane) { - val isLargeWindowSize = breakpoint.isLargeWindow + val isLargeWindowSize = breakpoint is WindowBreakpoint.Large MainContentLayoutData( shape = when { diff --git a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffold.kt b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffold.kt index d2414428ce..54a08fe9f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffold.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffold.kt @@ -81,8 +81,9 @@ enum class NavigationType { val windowBreakpoint = remember(config) { resources.getWindowBreakpoint() } return when (windowBreakpoint) { - WindowBreakpoint.SMALL, WindowBreakpoint.MEDIUM -> BAR - WindowBreakpoint.LARGE_WIDTH, WindowBreakpoint.LARGE_HEIGHT -> RAIL + is WindowBreakpoint.Small -> BAR + is WindowBreakpoint.Medium -> if (windowBreakpoint.isWidthExpanded) RAIL else BAR + is WindowBreakpoint.Large -> RAIL } } } diff --git a/core/ui/src/main/java/org/signal/core/ui/WindowSizeClassExtensions.kt b/core/ui/src/main/java/org/signal/core/ui/WindowSizeClassExtensions.kt index 0eeabfb68c..b621e88502 100644 --- a/core/ui/src/main/java/org/signal/core/ui/WindowSizeClassExtensions.kt +++ b/core/ui/src/main/java/org/signal/core/ui/WindowSizeClassExtensions.kt @@ -54,47 +54,61 @@ fun rememberWindowBreakpoint(): WindowBreakpoint { * Determines the device's form factor based on the current [Resources] and window size class. * * This function uses several heuristics: - * - Returns [WindowBreakpoint.SMALL] if the width or height is compact - * - Otherwise, falls back to aspect ratio heuristics: wide windows use [WindowBreakpoint.LARGE_WIDTH], tall windows use [WindowBreakpoint.LARGE_HEIGHT], else [WindowBreakpoint.MEDIUM]. + * - Returns [WindowBreakpoint.Small] if the window width or height is compact. + * - Otherwise, falls back to window aspect ratio heuristics: + * aspect ratio < [TABLET_ASPECT_RATIO] is [WindowBreakpoint.Medium] + * aspect ratio >= [TABLET_ASPECT_RATIO] is [WindowBreakpoint.Large] * * @return the inferred [WindowBreakpoint] for the current device. */ fun Resources.getWindowBreakpoint(): WindowBreakpoint { val windowSizeClass = getWindowSizeClass() + val isWidthExpanded = windowSizeClass.isWidthExpanded + val isHeightExpanded = windowSizeClass.isHeightExpanded if (windowSizeClass.isWidthCompact || windowSizeClass.isHeightCompact) { - return WindowBreakpoint.SMALL + return WindowBreakpoint.Small(isWidthExpanded, isHeightExpanded) } val numerator = maxOf(displayMetrics.widthPixels, displayMetrics.heightPixels) val denominator = minOf(displayMetrics.widthPixels, displayMetrics.heightPixels) val aspectRatio = numerator.toFloat() / denominator - return when { - aspectRatio < TABLET_ASPECT_RATIO -> WindowBreakpoint.MEDIUM - else -> { - if (displayMetrics.widthPixels >= displayMetrics.heightPixels) { - WindowBreakpoint.LARGE_WIDTH - } else { - WindowBreakpoint.LARGE_HEIGHT - } - } + return if (aspectRatio < TABLET_ASPECT_RATIO) { + WindowBreakpoint.Medium(isWidthExpanded, isHeightExpanded) + } else { + WindowBreakpoint.Large(isWidthExpanded, isHeightExpanded) } } /** * Indicates the general form factor of the device for responsive UI purposes. * - * - [SMALL]: A window with a compact width or height, typical of phone-sized devices. - * - [MEDIUM]: A window where neither width nor height is compact or expanded, typical of foldables. - * - [LARGE_WIDTH]: A window with expanded width and medium height, typical of tablets in landscape orientation. - * - [LARGE_HEIGHT]: A window with medium width and expanded height, typical of tablets in portrait orientation. + * - [Small]: A window with a compact width or height, typical of phone-sized devices. + * - [Medium]: A non-compact window with a near-square aspect ratio (< [TABLET_ASPECT_RATIO]), typical of open foldable devices. + * - [Large]: A window with an aspect ratio >= [TABLET_ASPECT_RATIO], typical of tablets. */ -enum class WindowBreakpoint(val isLargeWindow: Boolean) { - SMALL(isLargeWindow = false), - MEDIUM(isLargeWindow = false), - LARGE_WIDTH(isLargeWindow = true), - LARGE_HEIGHT(isLargeWindow = true) +sealed interface WindowBreakpoint { + /** True when the [WindowSizeClass] width is >= the expanded breakpoint (e.g., a tablet in landscape orientation) */ + val isWidthExpanded: Boolean + + /** True when the [WindowSizeClass] height is >= the expanded breakpoint (e.g., a tablet in landscape orientation) */ + val isHeightExpanded: Boolean + + data class Small( + override val isWidthExpanded: Boolean, + override val isHeightExpanded: Boolean + ) : WindowBreakpoint + + data class Medium( + override val isWidthExpanded: Boolean, + override val isHeightExpanded: Boolean + ) : WindowBreakpoint + + data class Large( + override val isWidthExpanded: Boolean, + override val isHeightExpanded: Boolean + ) : WindowBreakpoint } @Composable @@ -117,8 +131,9 @@ fun Resources.isSplitPane( return true } - return when (getWindowBreakpoint()) { - WindowBreakpoint.SMALL, WindowBreakpoint.LARGE_HEIGHT -> false - WindowBreakpoint.MEDIUM, WindowBreakpoint.LARGE_WIDTH -> true + return when (val breakpoint = getWindowBreakpoint()) { + is WindowBreakpoint.Small -> false + is WindowBreakpoint.Medium -> true + is WindowBreakpoint.Large -> breakpoint.isWidthExpanded } } diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/HSVColorBar.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/HSVColorBar.kt index f249508c2b..2df18378ae 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/edit/HSVColorBar.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/edit/HSVColorBar.kt @@ -43,7 +43,7 @@ fun HSVColorBar( onColorChanged: (Int) -> Unit, modifier: Modifier = Modifier ) { - val orientation = if (rememberWindowBreakpoint() == WindowBreakpoint.SMALL) { + val orientation = if (rememberWindowBreakpoint() is WindowBreakpoint.Small) { ColorBarOrientation.HORIZONTAL } else { ColorBarOrientation.VERTICAL diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorToolbar.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorToolbar.kt index 2b5177dc5b..4d29d59cb0 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorToolbar.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorToolbar.kt @@ -292,7 +292,7 @@ private fun OrientedImageEditorToolbar( content: @Composable () -> Unit ) { val windowBreakpoint = rememberWindowBreakpoint() - val isRow = windowBreakpoint == WindowBreakpoint.SMALL + val isRow = windowBreakpoint is WindowBreakpoint.Small if (isRow) { Row(modifier = modifier.height(48.dp)) { diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt index 31681bd1ef..363f50e2e2 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt @@ -59,7 +59,7 @@ fun MediaEditScreen( .fillMaxSize() .navigationBarsPadding() ) { - val isSmallWindowBreakpoint = rememberWindowBreakpoint() == WindowBreakpoint.SMALL + val isSmallWindowBreakpoint = rememberWindowBreakpoint() is WindowBreakpoint.Small val controllers = remember { EditorController.Container() } val currentController = state.focusedMedia?.let { diff --git a/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScaffold.kt b/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScaffold.kt index 50a772a3b8..0553c0b527 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScaffold.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/RegistrationScaffold.kt @@ -125,11 +125,10 @@ object RegistrationScaffold { @Composable fun rememberLayoutParams(): Params { - return when (rememberWindowBreakpoint()) { - WindowBreakpoint.SMALL -> smallLayoutParams - WindowBreakpoint.MEDIUM -> mediumLayoutParams - WindowBreakpoint.LARGE_WIDTH -> largeWidthLayoutParams - WindowBreakpoint.LARGE_HEIGHT -> largeHeightLayoutParams + return when (val breakpoint = rememberWindowBreakpoint()) { + is WindowBreakpoint.Small -> smallLayoutParams + is WindowBreakpoint.Medium -> mediumLayoutParams + is WindowBreakpoint.Large -> if (breakpoint.isWidthExpanded) largeWidthLayoutParams else largeHeightLayoutParams } } diff --git a/feature/registration/src/main/java/org/signal/registration/screens/linkaccount/LinkAccountScreen.kt b/feature/registration/src/main/java/org/signal/registration/screens/linkaccount/LinkAccountScreen.kt index d2158fd972..c5b4c31bd7 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/linkaccount/LinkAccountScreen.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/linkaccount/LinkAccountScreen.kt @@ -417,10 +417,9 @@ fun getQrOuterBorderSize(isInOverlay: Boolean): Dp { val breakpoint = rememberWindowBreakpoint() return when (breakpoint) { - WindowBreakpoint.SMALL -> 296.dp - WindowBreakpoint.MEDIUM -> 296.dp - WindowBreakpoint.LARGE_WIDTH -> 364.dp - WindowBreakpoint.LARGE_HEIGHT -> 364.dp + is WindowBreakpoint.Small -> 296.dp + is WindowBreakpoint.Medium -> 296.dp + is WindowBreakpoint.Large -> 364.dp } } @@ -432,10 +431,9 @@ fun getQrInnerBorderSize(isInOverlay: Boolean): Dp { val breakpoint = rememberWindowBreakpoint() return when (breakpoint) { - WindowBreakpoint.SMALL -> 232.dp - WindowBreakpoint.MEDIUM -> 232.dp - WindowBreakpoint.LARGE_WIDTH -> 284.dp - WindowBreakpoint.LARGE_HEIGHT -> 284.dp + is WindowBreakpoint.Small -> 232.dp + is WindowBreakpoint.Medium -> 232.dp + is WindowBreakpoint.Large -> 284.dp } } @@ -447,10 +445,9 @@ fun getQrCodeSize(isInOverlay: Boolean): Dp { val breakpoint = rememberWindowBreakpoint() return when (breakpoint) { - WindowBreakpoint.SMALL -> 208.dp - WindowBreakpoint.MEDIUM -> 208.dp - WindowBreakpoint.LARGE_WIDTH -> 256.dp - WindowBreakpoint.LARGE_HEIGHT -> 256.dp + is WindowBreakpoint.Small -> 208.dp + is WindowBreakpoint.Medium -> 208.dp + is WindowBreakpoint.Large -> 256.dp } } diff --git a/feature/registration/src/main/java/org/signal/registration/screens/messagesync/MessageSyncScreen.kt b/feature/registration/src/main/java/org/signal/registration/screens/messagesync/MessageSyncScreen.kt index a5f53f114a..02aca97c21 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/messagesync/MessageSyncScreen.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/messagesync/MessageSyncScreen.kt @@ -173,10 +173,9 @@ private fun FooterContent( val breakpoint = rememberWindowBreakpoint() when (breakpoint) { - WindowBreakpoint.SMALL -> StackedFooter(params, modifier, onEvent) - WindowBreakpoint.MEDIUM -> StackedFooter(params, modifier, onEvent) - WindowBreakpoint.LARGE_WIDTH -> InlineFooter(modifier, onEvent) - WindowBreakpoint.LARGE_HEIGHT -> InlineFooter(modifier, onEvent) + is WindowBreakpoint.Small -> StackedFooter(params, modifier, onEvent) + is WindowBreakpoint.Medium -> StackedFooter(params, modifier, onEvent) + is WindowBreakpoint.Large -> InlineFooter(modifier, onEvent) } } 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 313ddb798e..c16f05effa 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 @@ -84,7 +84,7 @@ fun WelcomeScreen( val onTermsAndPrivacyClick = { onEvent(WelcomeScreenEvents.ViewTermsAndPrivacy) } when (windowBreakpoint) { - WindowBreakpoint.SMALL -> { + is WindowBreakpoint.Small -> { CompactLayout( onEvent = onEvent, onRestoreOrTransferClick = onRestoreOrTransferClick, @@ -93,7 +93,7 @@ fun WelcomeScreen( ) } - WindowBreakpoint.MEDIUM -> { + is WindowBreakpoint.Medium -> { MediumLayout( onEvent = onEvent, onRestoreOrTransferClick = onRestoreOrTransferClick, @@ -102,7 +102,7 @@ fun WelcomeScreen( ) } - WindowBreakpoint.LARGE_WIDTH, WindowBreakpoint.LARGE_HEIGHT -> { + is WindowBreakpoint.Large -> { LargeLayout( isLinkAndSyncAvailable = isLinkAndSyncAvailable, onEvent = onEvent,