mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-19 08:09:12 +01:00
Add split pane UI for new conversation screen.
This commit is contained in:
committed by
Alex Hart
parent
0f35eb7f7b
commit
534756c833
@@ -38,6 +38,7 @@ 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
|
||||
@@ -86,6 +87,12 @@ enum class WindowSizeClass(
|
||||
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
|
||||
@@ -93,8 +100,11 @@ enum class WindowSizeClass(
|
||||
fun isLandscape(): Boolean = this == COMPACT_LANDSCAPE || this == MEDIUM_LANDSCAPE || this == EXTENDED_LANDSCAPE
|
||||
fun isPortrait(): Boolean = !isLandscape()
|
||||
|
||||
fun isSplitPane(): Boolean {
|
||||
return if (isLargeScreenSupportEnabled() && SignalStore.internal.forceSplitPaneOnCompactLandscape) {
|
||||
@JvmOverloads
|
||||
fun isSplitPane(
|
||||
forceSplitPaneOnCompactLandscape: Boolean = SignalStore.internal.forceSplitPaneOnCompactLandscape
|
||||
): Boolean {
|
||||
return if (isLargeScreenSupportEnabled() && forceSplitPaneOnCompactLandscape) {
|
||||
this != COMPACT_PORTRAIT
|
||||
} else {
|
||||
this.navigation != Navigation.BAR
|
||||
|
||||
@@ -19,7 +19,9 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
/**
|
||||
* AppScaffoldNavigator wraps a delegate navigator (such as the value returned by [rememberThreePaneScaffoldNavigatorDelegate]
|
||||
@@ -85,9 +87,12 @@ open class AppScaffoldNavigator<T> @RememberInComposition constructor(private va
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
@Composable
|
||||
fun rememberAppScaffoldNavigator(
|
||||
isSplitPane: Boolean,
|
||||
horizontalPartitionSpacerSize: Dp,
|
||||
defaultPanePreferredWidth: Dp
|
||||
windowSizeClass: WindowSizeClass = WindowSizeClass.rememberWindowSizeClass(),
|
||||
isSplitPane: Boolean = windowSizeClass.isSplitPane(
|
||||
forceSplitPaneOnCompactLandscape = if (LocalInspectionMode.current) false else SignalStore.internal.forceSplitPaneOnCompactLandscape
|
||||
),
|
||||
horizontalPartitionSpacerSize: Dp = windowSizeClass.horizontalPartitionDefaultSpacerSize,
|
||||
defaultPanePreferredWidth: Dp = windowSizeClass.listPaneDefaultPreferredWidth
|
||||
): AppScaffoldNavigator<Any> {
|
||||
val delegate = rememberThreePaneScaffoldNavigatorDelegate(
|
||||
isSplitPane,
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.window
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||
import androidx.compose.material3.adaptive.layout.PaneExpansionState
|
||||
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope
|
||||
import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import org.signal.core.ui.compose.AllDevicePreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.Scaffolds
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
/**
|
||||
* Wraps [AppScaffold], adding a top app bar that spans across both the list and detail panes.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
@Composable
|
||||
fun AppScaffoldWithTopBar(
|
||||
navigator: AppScaffoldNavigator<Any> = rememberAppScaffoldNavigator(),
|
||||
topBarContent: @Composable () -> Unit = {},
|
||||
detailContent: @Composable () -> Unit = {},
|
||||
navRailContent: @Composable () -> Unit = {},
|
||||
bottomNavContent: @Composable () -> Unit = {},
|
||||
paneExpansionState: PaneExpansionState = rememberPaneExpansionState(),
|
||||
paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? = null,
|
||||
animatorFactory: AppScaffoldAnimationStateFactory = AppScaffoldAnimationStateFactory.Default,
|
||||
listContent: @Composable () -> Unit
|
||||
) {
|
||||
val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
|
||||
val isSplitPane = windowSizeClass.isSplitPane(
|
||||
forceSplitPaneOnCompactLandscape = if (LocalInspectionMode.current) false else SignalStore.internal.forceSplitPaneOnCompactLandscape
|
||||
)
|
||||
|
||||
if (isSplitPane) {
|
||||
Column {
|
||||
topBarContent()
|
||||
|
||||
AppScaffold(
|
||||
navigator = navigator,
|
||||
detailContent = detailContent,
|
||||
navRailContent = navRailContent,
|
||||
bottomNavContent = bottomNavContent,
|
||||
paneExpansionState = paneExpansionState,
|
||||
paneExpansionDragHandle = paneExpansionDragHandle,
|
||||
animatorFactory = animatorFactory,
|
||||
listContent = listContent
|
||||
)
|
||||
}
|
||||
} else {
|
||||
AppScaffold(
|
||||
navigator = navigator,
|
||||
detailContent = detailContent,
|
||||
navRailContent = navRailContent,
|
||||
bottomNavContent = bottomNavContent,
|
||||
paneExpansionState = paneExpansionState,
|
||||
paneExpansionDragHandle = paneExpansionDragHandle,
|
||||
animatorFactory = animatorFactory,
|
||||
listContent = {
|
||||
Scaffold(topBar = topBarContent) { paddingValues ->
|
||||
Box(modifier = Modifier.padding(paddingValues)) {
|
||||
listContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3AdaptiveApi::class)
|
||||
@AllDevicePreviews
|
||||
@Composable
|
||||
private fun AppScaffoldWithTopBarPreview() {
|
||||
Previews.Preview {
|
||||
val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
|
||||
val isSplitPane = windowSizeClass.isSplitPane(forceSplitPaneOnCompactLandscape = false)
|
||||
|
||||
AppScaffoldWithTopBar(
|
||||
navigator = rememberAppScaffoldNavigator(),
|
||||
|
||||
topBarContent = {
|
||||
Scaffolds.DefaultTopAppBar(
|
||||
title = if (!isSplitPane) stringResource(R.string.NewConversationActivity__new_message) else "",
|
||||
titleContent = { _, title -> Text(text = title, style = MaterialTheme.typography.titleLarge) },
|
||||
navigationIcon = ImageVector.vectorResource(R.drawable.symbol_arrow_start_24),
|
||||
navigationContentDescription = "",
|
||||
onNavigationClick = { }
|
||||
)
|
||||
},
|
||||
|
||||
listContent = {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = Color.Red)
|
||||
) {
|
||||
Text(
|
||||
text = "ListContent\n$windowSizeClass",
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
detailContent = {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = Color.Blue)
|
||||
) {
|
||||
Text(
|
||||
text = "DetailContent",
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user