mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-19 08:09:12 +01:00
Remove custom WindowSizeClass and just depend on Material Adaptive WindowSizeClass.
Co-authored-by: jeffrey-signal <jeffrey@signal.org>
This commit is contained in:
committed by
jeffrey-signal
parent
95c9776b4d
commit
109f651681
@@ -5,8 +5,6 @@
|
||||
|
||||
package org.thoughtcrime.securesms.window
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.foundation.background
|
||||
@@ -16,7 +14,6 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.displayCutoutPadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -42,23 +39,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.LocalInspectionMode
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.window.core.ExperimentalWindowCoreApi
|
||||
import androidx.window.core.layout.WindowHeightSizeClass
|
||||
import androidx.window.core.layout.WindowWidthSizeClass
|
||||
import org.signal.core.ui.compose.AllDevicePreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.main.MainFloatingActionButtonsCallback
|
||||
import org.thoughtcrime.securesms.main.MainNavigationBar
|
||||
import org.thoughtcrime.securesms.main.MainNavigationRail
|
||||
import org.thoughtcrime.securesms.main.MainNavigationState
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import kotlin.math.max
|
||||
|
||||
enum class Navigation {
|
||||
@@ -68,141 +59,15 @@ enum class Navigation {
|
||||
companion object {
|
||||
@Composable
|
||||
fun rememberNavigation(): Navigation {
|
||||
val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
|
||||
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
|
||||
|
||||
return remember(windowSizeClass) { windowSizeClass.navigation }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the size of screen we are displaying, and what components should be displayed.
|
||||
*
|
||||
* Screens should utilize this class by convention instead of calling [currentWindowAdaptiveInfo]
|
||||
* themselves, as this class includes checks with [RemoteConfig] to ensure we're allowed to display
|
||||
* content in different screen sizes.
|
||||
*
|
||||
* https://developer.android.com/develop/ui/compose/layouts/adaptive/use-window-size-classes
|
||||
*/
|
||||
enum class WindowSizeClass(
|
||||
val navigation: Navigation
|
||||
) {
|
||||
COMPACT_PORTRAIT(Navigation.BAR),
|
||||
COMPACT_LANDSCAPE(Navigation.BAR),
|
||||
MEDIUM_PORTRAIT(Navigation.RAIL),
|
||||
MEDIUM_LANDSCAPE(Navigation.RAIL),
|
||||
EXTENDED_PORTRAIT(Navigation.RAIL),
|
||||
EXTENDED_LANDSCAPE(Navigation.RAIL);
|
||||
|
||||
val listPaneDefaultPreferredWidth: Dp
|
||||
get() = if (isExtended()) 416.dp else 316.dp
|
||||
|
||||
val detailPaneMaxContentWidth: Dp = 624.dp
|
||||
val horizontalPartitionDefaultSpacerSize: Dp = 12.dp
|
||||
|
||||
fun isCompact(): Boolean = this == COMPACT_PORTRAIT || this == COMPACT_LANDSCAPE
|
||||
fun isMedium(): Boolean = this == MEDIUM_PORTRAIT || this == MEDIUM_LANDSCAPE
|
||||
fun isExtended(): Boolean = this == EXTENDED_PORTRAIT || this == EXTENDED_LANDSCAPE
|
||||
|
||||
fun isLandscape(): Boolean = this == COMPACT_LANDSCAPE || this == MEDIUM_LANDSCAPE || this == EXTENDED_LANDSCAPE
|
||||
fun isPortrait(): Boolean = !isLandscape()
|
||||
|
||||
@JvmOverloads
|
||||
fun isSplitPane(
|
||||
forceSplitPaneOnCompactLandscape: Boolean = SignalStore.internal.forceSplitPaneOnCompactLandscape
|
||||
): Boolean {
|
||||
return if (isLargeScreenSupportEnabled() && forceSplitPaneOnCompactLandscape) {
|
||||
this != COMPACT_PORTRAIT
|
||||
} else {
|
||||
this.navigation != Navigation.BAR
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@OptIn(ExperimentalWindowCoreApi::class)
|
||||
fun Resources.getWindowSizeClass(): WindowSizeClass {
|
||||
val orientation = configuration.orientation
|
||||
|
||||
if (isForcedCompact()) {
|
||||
return getCompactSizeClassForOrientation(orientation)
|
||||
}
|
||||
|
||||
val windowSizeClass = androidx.window.core.layout.WindowSizeClass.compute(
|
||||
displayMetrics.widthPixels,
|
||||
displayMetrics.heightPixels,
|
||||
displayMetrics.density
|
||||
)
|
||||
|
||||
return getSizeClassForOrientationAndSystemSizeClass(orientation, windowSizeClass)
|
||||
}
|
||||
|
||||
fun isLargeScreenSupportEnabled(): Boolean {
|
||||
return RemoteConfig.largeScreenUi && SignalStore.internal.largeScreenUi
|
||||
}
|
||||
|
||||
fun isForcedCompact(): Boolean {
|
||||
return !isLargeScreenSupportEnabled()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun checkForcedCompact(): Boolean {
|
||||
return !LocalInspectionMode.current && isForcedCompact()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberWindowSizeClass(forceCompact: Boolean = checkForcedCompact()): WindowSizeClass {
|
||||
val orientation = LocalConfiguration.current.orientation
|
||||
|
||||
if (forceCompact) {
|
||||
return remember(orientation) {
|
||||
getCompactSizeClassForOrientation(orientation)
|
||||
return remember(windowSizeClass) {
|
||||
if (windowSizeClass.isSplitPane() && windowSizeClass.windowHeightSizeClass.isAtLeast(WindowHeightSizeClass.MEDIUM)) {
|
||||
RAIL
|
||||
} else {
|
||||
BAR
|
||||
}
|
||||
}
|
||||
|
||||
val wsc = currentWindowAdaptiveInfo().windowSizeClass
|
||||
|
||||
return remember(orientation, wsc) {
|
||||
getSizeClassForOrientationAndSystemSizeClass(orientation, wsc)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCompactSizeClassForOrientation(orientation: Int): WindowSizeClass {
|
||||
return when (orientation) {
|
||||
Configuration.ORIENTATION_PORTRAIT, Configuration.ORIENTATION_UNDEFINED, Configuration.ORIENTATION_SQUARE -> {
|
||||
COMPACT_PORTRAIT
|
||||
}
|
||||
|
||||
Configuration.ORIENTATION_LANDSCAPE -> COMPACT_LANDSCAPE
|
||||
else -> error("Unexpected orientation: $orientation")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSizeClassForOrientationAndSystemSizeClass(orientation: Int, windowSizeClass: androidx.window.core.layout.WindowSizeClass): WindowSizeClass {
|
||||
if (windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT || windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT) {
|
||||
return getCompactSizeClassForOrientation(orientation)
|
||||
}
|
||||
|
||||
return when (orientation) {
|
||||
Configuration.ORIENTATION_PORTRAIT, Configuration.ORIENTATION_UNDEFINED, Configuration.ORIENTATION_SQUARE -> {
|
||||
when (windowSizeClass.windowWidthSizeClass) {
|
||||
WindowWidthSizeClass.COMPACT -> COMPACT_PORTRAIT
|
||||
WindowWidthSizeClass.MEDIUM -> MEDIUM_PORTRAIT
|
||||
WindowWidthSizeClass.EXPANDED -> EXTENDED_PORTRAIT
|
||||
else -> error("Unsupported.")
|
||||
}
|
||||
}
|
||||
|
||||
Configuration.ORIENTATION_LANDSCAPE -> {
|
||||
when (windowSizeClass.windowWidthSizeClass) {
|
||||
WindowWidthSizeClass.COMPACT -> COMPACT_LANDSCAPE
|
||||
WindowWidthSizeClass.MEDIUM -> MEDIUM_LANDSCAPE
|
||||
WindowWidthSizeClass.EXPANDED -> EXTENDED_LANDSCAPE
|
||||
else -> error("Unsupported.")
|
||||
}
|
||||
}
|
||||
|
||||
else -> error("Unexpected orientation: $orientation")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,8 +105,8 @@ fun AppScaffold(
|
||||
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||
animatorFactory: AppScaffoldAnimationStateFactory = AppScaffoldAnimationStateFactory.Default
|
||||
) {
|
||||
val isForcedCompact = WindowSizeClass.checkForcedCompact()
|
||||
val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
|
||||
val isForcedCompact = !LocalInspectionMode.current && !isLargeScreenSupportEnabled()
|
||||
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
|
||||
|
||||
if (isForcedCompact) {
|
||||
ListAndNavigation(
|
||||
@@ -249,7 +114,6 @@ fun AppScaffold(
|
||||
listContent = secondaryContent,
|
||||
navRailContent = navRailContent,
|
||||
bottomNavContent = bottomNavContent,
|
||||
windowSizeClass = windowSizeClass,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
modifier = modifier
|
||||
)
|
||||
@@ -314,7 +178,6 @@ fun AppScaffold(
|
||||
listContent = secondaryContent,
|
||||
navRailContent = navRailContent,
|
||||
bottomNavContent = bottomNavContent,
|
||||
windowSizeClass = windowSizeClass,
|
||||
contentWindowInsets = WindowInsets() // parent scaffold already applies the necessary insets
|
||||
)
|
||||
}
|
||||
@@ -380,10 +243,11 @@ private fun ListAndNavigation(
|
||||
navRailContent: @Composable () -> Unit,
|
||||
bottomNavContent: @Composable () -> Unit,
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
windowSizeClass: WindowSizeClass,
|
||||
contentWindowInsets: WindowInsets,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val navigation = Navigation.rememberNavigation()
|
||||
|
||||
Scaffold(
|
||||
containerColor = Color.Transparent,
|
||||
topBar = topBarContent,
|
||||
@@ -394,9 +258,8 @@ private fun ListAndNavigation(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.then(if (windowSizeClass.isLandscape()) Modifier.displayCutoutPadding() else Modifier)
|
||||
) {
|
||||
if (windowSizeClass.navigation == Navigation.RAIL) {
|
||||
if (navigation == Navigation.RAIL) {
|
||||
navRailContent()
|
||||
}
|
||||
|
||||
@@ -405,7 +268,7 @@ private fun ListAndNavigation(
|
||||
listContent()
|
||||
}
|
||||
|
||||
if (windowSizeClass.navigation == Navigation.BAR) {
|
||||
if (navigation == Navigation.BAR) {
|
||||
bottomNavContent()
|
||||
}
|
||||
}
|
||||
@@ -418,11 +281,11 @@ private fun ListAndNavigation(
|
||||
@Composable
|
||||
private fun AppScaffoldPreview() {
|
||||
Previews.Preview {
|
||||
val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
|
||||
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
|
||||
|
||||
AppScaffold(
|
||||
navigator = rememberAppScaffoldNavigator(
|
||||
isSplitPane = windowSizeClass.navigation != Navigation.BAR,
|
||||
isSplitPane = windowSizeClass.isSplitPane(),
|
||||
defaultPanePreferredWidth = 416.dp,
|
||||
horizontalPartitionSpacerSize = 16.dp
|
||||
),
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.window.core.layout.WindowSizeClass
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
/**
|
||||
@@ -94,9 +95,9 @@ open class AppScaffoldNavigator<T> @RememberInComposition constructor(private va
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
@Composable
|
||||
fun rememberAppScaffoldNavigator(
|
||||
windowSizeClass: WindowSizeClass = WindowSizeClass.rememberWindowSizeClass(),
|
||||
windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass,
|
||||
isSplitPane: Boolean = windowSizeClass.isSplitPane(
|
||||
forceSplitPaneOnCompactLandscape = if (LocalInspectionMode.current) false else SignalStore.internal.forceSplitPaneOnCompactLandscape
|
||||
forceSplitPane = if (LocalInspectionMode.current) false else SignalStore.internal.forceSplitPane
|
||||
),
|
||||
horizontalPartitionSpacerSize: Dp = windowSizeClass.horizontalPartitionDefaultSpacerSize,
|
||||
defaultPanePreferredWidth: Dp = windowSizeClass.listPaneDefaultPreferredWidth
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -29,7 +30,7 @@ import org.thoughtcrime.securesms.R
|
||||
@Composable
|
||||
private fun AppScaffoldWithTopBarPreview() {
|
||||
Previews.Preview {
|
||||
val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
|
||||
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
|
||||
|
||||
AppScaffold(
|
||||
navigator = rememberAppScaffoldNavigator(),
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.window
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.window.core.ExperimentalWindowCoreApi
|
||||
import androidx.window.core.layout.WindowHeightSizeClass
|
||||
import androidx.window.core.layout.WindowSizeClass
|
||||
import androidx.window.core.layout.WindowWidthSizeClass
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
|
||||
val WindowSizeClass.listPaneDefaultPreferredWidth: Dp get() = if (windowWidthSizeClass.isAtLeast(WindowWidthSizeClass.EXPANDED)) 416.dp else 316.dp
|
||||
val WindowSizeClass.horizontalPartitionDefaultSpacerSize: Dp get() = 12.dp
|
||||
val WindowSizeClass.detailPaneMaxContentWidth: Dp get() = 624.dp
|
||||
|
||||
fun WindowHeightSizeClass.isAtLeast(other: WindowHeightSizeClass): Boolean {
|
||||
return hashCode() >= other.hashCode()
|
||||
}
|
||||
|
||||
fun WindowWidthSizeClass.isAtLeast(other: WindowWidthSizeClass): Boolean {
|
||||
return hashCode() >= other.hashCode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Global check for large screen support, can be inlined after production release.
|
||||
*/
|
||||
fun isLargeScreenSupportEnabled(): Boolean {
|
||||
return RemoteConfig.largeScreenUi && SignalStore.internal.largeScreenUi
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalWindowCoreApi::class)
|
||||
fun Resources.getWindowSizeClass(): WindowSizeClass {
|
||||
return WindowSizeClass.compute(displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.density)
|
||||
}
|
||||
|
||||
/**
|
||||
* Split Pane is enabled as long as the width size class is MEDIUM or greater
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun WindowSizeClass.isSplitPane(
|
||||
forceSplitPane: Boolean = SignalStore.internal.forceSplitPane
|
||||
): Boolean {
|
||||
if (forceSplitPane) {
|
||||
return true
|
||||
}
|
||||
|
||||
return windowWidthSizeClass.isAtLeast(WindowWidthSizeClass.MEDIUM)
|
||||
}
|
||||
Reference in New Issue
Block a user