mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 20:48:43 +00:00
Add MainContentLayoutData object and proper scaffolding directive.
This commit is contained in:
committed by
Cody Henthorne
parent
49853b2cca
commit
c3d61bece1
@@ -28,10 +28,12 @@ import androidx.compose.foundation.layout.BoxWithConstraintsScope
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.displayCutoutPadding
|
import androidx.compose.foundation.layout.displayCutoutPadding
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||||
|
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||||
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole
|
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole
|
||||||
|
import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth
|
||||||
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
|
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -41,9 +43,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.RectangleShape
|
|
||||||
import androidx.compose.ui.graphics.Shape
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.compose.AndroidFragment
|
import androidx.fragment.compose.AndroidFragment
|
||||||
import androidx.fragment.compose.rememberFragmentState
|
import androidx.fragment.compose.rememberFragmentState
|
||||||
@@ -83,6 +82,7 @@ import org.thoughtcrime.securesms.main.MainActivityListHostFragment
|
|||||||
import org.thoughtcrime.securesms.main.MainBottomChrome
|
import org.thoughtcrime.securesms.main.MainBottomChrome
|
||||||
import org.thoughtcrime.securesms.main.MainBottomChromeCallback
|
import org.thoughtcrime.securesms.main.MainBottomChromeCallback
|
||||||
import org.thoughtcrime.securesms.main.MainBottomChromeState
|
import org.thoughtcrime.securesms.main.MainBottomChromeState
|
||||||
|
import org.thoughtcrime.securesms.main.MainContentLayoutData
|
||||||
import org.thoughtcrime.securesms.main.MainMegaphoneState
|
import org.thoughtcrime.securesms.main.MainMegaphoneState
|
||||||
import org.thoughtcrime.securesms.main.MainNavigationBar
|
import org.thoughtcrime.securesms.main.MainNavigationBar
|
||||||
import org.thoughtcrime.securesms.main.MainNavigationDetailLocation
|
import org.thoughtcrime.securesms.main.MainNavigationDetailLocation
|
||||||
@@ -231,28 +231,29 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Any>()
|
|
||||||
val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
|
val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
|
||||||
|
val contentLayoutData = MainContentLayoutData.rememberContentLayoutData()
|
||||||
val contentClip: Shape = remember(windowSizeClass) {
|
|
||||||
if (windowSizeClass.isExtended()) {
|
|
||||||
RoundedCornerShape(18.dp)
|
|
||||||
} else {
|
|
||||||
RectangleShape
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(detailLocation) {
|
|
||||||
if (detailLocation is MainNavigationDetailLocation.Conversation) {
|
|
||||||
if (SignalStore.internal.largeScreenUi) {
|
|
||||||
scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Primary, detailLocation)
|
|
||||||
} else {
|
|
||||||
startActivity((detailLocation as MainNavigationDetailLocation.Conversation).intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MainContainer {
|
MainContainer {
|
||||||
|
val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Any>(
|
||||||
|
scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
|
||||||
|
currentWindowAdaptiveInfo()
|
||||||
|
).copy(
|
||||||
|
horizontalPartitionSpacerSize = contentLayoutData.partitionWidth,
|
||||||
|
defaultPanePreferredWidth = contentLayoutData.rememberDefaultPanePreferredWidth(maxWidth)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(detailLocation) {
|
||||||
|
if (detailLocation is MainNavigationDetailLocation.Conversation) {
|
||||||
|
if (SignalStore.internal.largeScreenUi) {
|
||||||
|
scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Primary, detailLocation)
|
||||||
|
} else {
|
||||||
|
startActivity((detailLocation as MainNavigationDetailLocation.Conversation).intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AppScaffold(
|
AppScaffold(
|
||||||
navigator = scaffoldNavigator,
|
navigator = scaffoldNavigator,
|
||||||
bottomNavContent = {
|
bottomNavContent = {
|
||||||
@@ -280,9 +281,10 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
|||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.padding(start = contentLayoutData.listPaddingStart)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(listContainerColor)
|
.background(listContainerColor)
|
||||||
.clip(contentClip)
|
.clip(contentLayoutData.shape)
|
||||||
) {
|
) {
|
||||||
MainToolbar(
|
MainToolbar(
|
||||||
state = mainToolbarState,
|
state = mainToolbarState,
|
||||||
@@ -316,13 +318,17 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
|||||||
fragmentState = fragmentState,
|
fragmentState = fragmentState,
|
||||||
arguments = requireNotNull(destination.intent.extras) { "Handed null Conversation intent arguments." },
|
arguments = requireNotNull(destination.intent.extras) { "Handed null Conversation intent arguments." },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.padding(end = contentLayoutData.detailPaddingEnd)
|
||||||
|
.clip(contentLayoutData.shape)
|
||||||
.background(color = MaterialTheme.colorScheme.surface)
|
.background(color = MaterialTheme.colorScheme.surface)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clip(contentClip)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
paneExpansionDragHandle = if (contentLayoutData.hasDragHandle()) {
|
||||||
|
{ }
|
||||||
|
} else null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.main
|
||||||
|
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.thoughtcrime.securesms.window.WindowSizeClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes metrics for the content layout (list and detail) of the main screen.
|
||||||
|
*
|
||||||
|
* @param shape The shape of each of the list and detail fragments
|
||||||
|
* @param partitionWidth The width of the divider between list and detail
|
||||||
|
* @param listPaddingStart The padding between the list pane and the navigation rail
|
||||||
|
* @param detailPaddingEnd The padding at the end of the detail pane
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
data class MainContentLayoutData(
|
||||||
|
val shape: Shape,
|
||||||
|
val partitionWidth: Dp,
|
||||||
|
val listPaddingStart: Dp,
|
||||||
|
val detailPaddingEnd: Dp
|
||||||
|
) {
|
||||||
|
private val extraPadding: Dp = partitionWidth + listPaddingStart + detailPaddingEnd
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the WindowSizeClass supports drag handles.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun hasDragHandle(): Boolean {
|
||||||
|
return WindowSizeClass.rememberWindowSizeClass().isExtended()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the default preferred width
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun rememberDefaultPanePreferredWidth(maxWidth: Dp): Dp {
|
||||||
|
val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
|
||||||
|
|
||||||
|
return remember(maxWidth, windowSizeClass) {
|
||||||
|
when {
|
||||||
|
windowSizeClass.isCompact() -> maxWidth
|
||||||
|
windowSizeClass.isMedium() -> (maxWidth - extraPadding) / 2f
|
||||||
|
else -> 416.dp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Uses the WindowSizeClass to build out a MainContentLayoutData.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun rememberContentLayoutData(): MainContentLayoutData {
|
||||||
|
val windowSizeClass = WindowSizeClass.rememberWindowSizeClass()
|
||||||
|
|
||||||
|
return remember(windowSizeClass) {
|
||||||
|
MainContentLayoutData(
|
||||||
|
shape = when {
|
||||||
|
windowSizeClass.isCompact() -> RectangleShape
|
||||||
|
windowSizeClass.isMedium() -> RoundedCornerShape(18.dp)
|
||||||
|
else -> RoundedCornerShape(14.dp)
|
||||||
|
},
|
||||||
|
partitionWidth = when {
|
||||||
|
windowSizeClass.isCompact() -> 0.dp
|
||||||
|
windowSizeClass.isMedium() -> 13.dp
|
||||||
|
else -> 16.dp
|
||||||
|
},
|
||||||
|
listPaddingStart = when {
|
||||||
|
windowSizeClass.isCompact() -> 0.dp
|
||||||
|
windowSizeClass.isMedium() -> 12.dp
|
||||||
|
else -> 16.dp
|
||||||
|
},
|
||||||
|
detailPaddingEnd = when {
|
||||||
|
windowSizeClass.isCompact() -> 0.dp
|
||||||
|
windowSizeClass.isMedium() -> 12.dp
|
||||||
|
else -> 24.dp
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,10 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||||
import androidx.compose.material3.adaptive.layout.AnimatedPane
|
import androidx.compose.material3.adaptive.layout.AnimatedPane
|
||||||
|
import androidx.compose.material3.adaptive.layout.PaneExpansionState
|
||||||
|
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope
|
||||||
|
import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth
|
||||||
|
import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
|
||||||
import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold
|
import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold
|
||||||
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
|
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
|
||||||
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
|
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
|
||||||
@@ -28,6 +32,7 @@ import androidx.compose.ui.platform.LocalConfiguration
|
|||||||
import androidx.compose.ui.platform.LocalInspectionMode
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.window.core.ExperimentalWindowCoreApi
|
import androidx.window.core.ExperimentalWindowCoreApi
|
||||||
import androidx.window.core.layout.WindowHeightSizeClass
|
import androidx.window.core.layout.WindowHeightSizeClass
|
||||||
import androidx.window.core.layout.WindowWidthSizeClass
|
import androidx.window.core.layout.WindowWidthSizeClass
|
||||||
@@ -57,7 +62,7 @@ enum class WindowSizeClass(
|
|||||||
) {
|
) {
|
||||||
COMPACT_PORTRAIT(Navigation.BAR),
|
COMPACT_PORTRAIT(Navigation.BAR),
|
||||||
COMPACT_LANDSCAPE(Navigation.BAR),
|
COMPACT_LANDSCAPE(Navigation.BAR),
|
||||||
MEDIUM_PORTRAIT(Navigation.BAR),
|
MEDIUM_PORTRAIT(Navigation.RAIL),
|
||||||
MEDIUM_LANDSCAPE(Navigation.RAIL),
|
MEDIUM_LANDSCAPE(Navigation.RAIL),
|
||||||
EXTENDED_PORTRAIT(Navigation.RAIL),
|
EXTENDED_PORTRAIT(Navigation.RAIL),
|
||||||
EXTENDED_LANDSCAPE(Navigation.RAIL);
|
EXTENDED_LANDSCAPE(Navigation.RAIL);
|
||||||
@@ -160,6 +165,7 @@ fun AppScaffold(
|
|||||||
detailContent: @Composable () -> Unit = {},
|
detailContent: @Composable () -> Unit = {},
|
||||||
navRailContent: @Composable () -> Unit = {},
|
navRailContent: @Composable () -> Unit = {},
|
||||||
bottomNavContent: @Composable () -> Unit = {},
|
bottomNavContent: @Composable () -> Unit = {},
|
||||||
|
paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? = null,
|
||||||
listContent: @Composable () -> Unit
|
listContent: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val isForcedCompact = WindowSizeClass.checkForcedCompact()
|
val isForcedCompact = WindowSizeClass.checkForcedCompact()
|
||||||
@@ -176,9 +182,10 @@ fun AppScaffold(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (windowSizeClass.isMedium()) {
|
NavigableListDetailPaneScaffold(
|
||||||
Row {
|
navigator = navigator,
|
||||||
Box(modifier = Modifier.weight(1f)) {
|
listPane = {
|
||||||
|
AnimatedPane {
|
||||||
ListAndNavigation(
|
ListAndNavigation(
|
||||||
listContent = listContent,
|
listContent = listContent,
|
||||||
navRailContent = navRailContent,
|
navRailContent = navRailContent,
|
||||||
@@ -186,31 +193,15 @@ fun AppScaffold(
|
|||||||
windowSizeClass = windowSizeClass
|
windowSizeClass = windowSizeClass
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
Box(modifier = Modifier.weight(1f)) {
|
detailPane = {
|
||||||
|
AnimatedPane {
|
||||||
detailContent()
|
detailContent()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
} else {
|
paneExpansionDragHandle = paneExpansionDragHandle,
|
||||||
NavigableListDetailPaneScaffold(
|
paneExpansionState = rememberPaneExpansionState()
|
||||||
navigator = navigator,
|
)
|
||||||
listPane = {
|
|
||||||
AnimatedPane {
|
|
||||||
ListAndNavigation(
|
|
||||||
listContent = listContent,
|
|
||||||
navRailContent = navRailContent,
|
|
||||||
bottomNavContent = bottomNavContent,
|
|
||||||
windowSizeClass = windowSizeClass
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
detailPane = {
|
|
||||||
AnimatedPane {
|
|
||||||
detailContent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -248,6 +239,13 @@ private fun ListAndNavigation(
|
|||||||
private fun AppScaffoldPreview() {
|
private fun AppScaffoldPreview() {
|
||||||
Previews.Preview {
|
Previews.Preview {
|
||||||
AppScaffold(
|
AppScaffold(
|
||||||
|
navigator = rememberListDetailPaneScaffoldNavigator<Any>(
|
||||||
|
scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
|
||||||
|
currentWindowAdaptiveInfo()
|
||||||
|
).copy(
|
||||||
|
horizontalPartitionSpacerSize = 10.dp
|
||||||
|
)
|
||||||
|
),
|
||||||
listContent = {
|
listContent = {
|
||||||
Box(
|
Box(
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
|
|||||||
Reference in New Issue
Block a user