diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-06-02 15:05:24 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2023-06-02 15:05:24 +0000 |
commit | 4e81e0ef26d64ebb7a1b7d4dd873fe00195b840c (patch) | |
tree | 6a83740538327bff77cc0e23bceb6d556e59a89e | |
parent | 95260db65fe23ea0d3860e6158cb146aab5cefbf (diff) | |
parent | a94101469836edb2b716cbfc93a70e42ab243a0f (diff) | |
download | support-snap-temp-L74800000961048458.tar.gz |
Merge "Merge cherrypicks of ['android-review.googlesource.com/2612992'] into androidx-compose-material3-release." into androidx-compose-material3-releasesnap-temp-L84000000961040259snap-temp-L74800000961048458
7 files changed, 244 insertions, 64 deletions
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt index c9a1a36ac72..7e4d1b1ecd9 100644 --- a/compose/material3/material3/api/public_plus_experimental_current.txt +++ b/compose/material3/material3/api/public_plus_experimental_current.txt @@ -86,12 +86,14 @@ package androidx.compose.material3 { method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getHiddenShape(); method @androidx.compose.runtime.Composable public long getScrimColor(); method public float getSheetPeekHeight(); + method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets(); property @androidx.compose.runtime.Composable public final long ContainerColor; property public final float Elevation; property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape ExpandedShape; property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape HiddenShape; property @androidx.compose.runtime.Composable public final long ScrimColor; property public final float SheetPeekHeight; + property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets; field public static final androidx.compose.material3.BottomSheetDefaults INSTANCE; } @@ -686,7 +688,7 @@ package androidx.compose.material3 { } public final class ModalBottomSheet_androidKt { - method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content); + method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content); method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SheetState rememberModalBottomSheetState(optional boolean skipPartiallyExpanded, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange); } diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt index bae18febd92..4a9a7722caf 100644 --- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt +++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -33,6 +34,7 @@ import androidx.compose.foundation.selection.toggleable import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.BottomSheetDefaults import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.Button import androidx.compose.material3.Checkbox @@ -69,6 +71,7 @@ import kotlinx.coroutines.launch fun ModalBottomSheetSample() { var openBottomSheet by rememberSaveable { mutableStateOf(false) } var skipPartiallyExpanded by remember { mutableStateOf(false) } + var edgeToEdgeEnabled by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() val bottomSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = skipPartiallyExpanded @@ -91,6 +94,17 @@ fun ModalBottomSheetSample() { Spacer(Modifier.width(16.dp)) Text("Skip partially expanded State") } + Row( + Modifier.toggleable( + value = edgeToEdgeEnabled, + role = Role.Checkbox, + onValueChange = { checked -> edgeToEdgeEnabled = checked } + ) + ) { + Checkbox(checked = edgeToEdgeEnabled, onCheckedChange = null) + Spacer(Modifier.width(16.dp)) + Text("Toggle edge to edge enabled.") + } Button(onClick = { openBottomSheet = !openBottomSheet }) { Text(text = "Show Bottom Sheet") } @@ -98,9 +112,13 @@ fun ModalBottomSheetSample() { // Sheet content if (openBottomSheet) { + val windowInsets = if (edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + ModalBottomSheet( onDismissRequest = { openBottomSheet = false }, sheetState = bottomSheetState, + windowInsets = windowInsets ) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { Button( diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt index cbbc054736f..34df5b33bc2 100644 --- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt +++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt @@ -24,6 +24,7 @@ import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -70,7 +71,6 @@ import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.height import androidx.compose.ui.unit.width -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice @@ -84,11 +84,12 @@ import kotlinx.coroutines.runBlocking import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.junit.runners.Parameterized @MediumTest -@RunWith(AndroidJUnit4::class) +@RunWith(Parameterized::class) @OptIn(ExperimentalMaterial3Api::class) -class ModalBottomSheetTest { +class ModalBottomSheetTest(private val edgeToEdgeWrapper: EdgeToEdgeWrapper) { @get:Rule val rule = createAndroidComposeRule<ComponentActivity>() @@ -106,10 +107,14 @@ class ModalBottomSheetTest { val sheetState = SheetState(skipPartiallyExpanded = false) rule.setContent { + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + if (showBottomSheet) { ModalBottomSheet( sheetState = sheetState, - onDismissRequest = { showBottomSheet = false } + onDismissRequest = { showBottomSheet = false }, + windowInsets = windowInsets ) { Box( Modifier @@ -143,8 +148,13 @@ class ModalBottomSheetTest { val density = LocalDensity.current val resScreenWidth = context.resources.configuration.screenWidthDp with(density) { screenWidth = resScreenWidth.dp.roundToPx() } + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets - ModalBottomSheet(onDismissRequest = {}) { + ModalBottomSheet( + onDismissRequest = {}, + windowInsets = windowInsets + ) { Box( Modifier .fillMaxWidth() @@ -178,7 +188,12 @@ class ModalBottomSheetTest { try { latch.await(1500, TimeUnit.MILLISECONDS) rule.setContent { - ModalBottomSheet(onDismissRequest = {}) { + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + ModalBottomSheet( + onDismissRequest = {}, + windowInsets = windowInsets + ) { Box( Modifier .testTag(sheetTag) @@ -219,7 +234,15 @@ class ModalBottomSheetTest { val config = LocalContext.current.resources.configuration height = config.screenHeightDp.dp sheetState = rememberModalBottomSheetState() - ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState, dragHandle = null) { + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + + ModalBottomSheet( + onDismissRequest = {}, + sheetState = sheetState, + dragHandle = null, + windowInsets = windowInsets + ) { Box( Modifier .fillMaxWidth() @@ -229,6 +252,7 @@ class ModalBottomSheetTest { } } + height = rule.onNode(isPopup()).getUnclippedBoundsInRoot().height assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded) rule.onNodeWithTag(sheetTag).assertTopPositionInRootIsEqualTo(height - sheetHeight) } @@ -240,13 +264,13 @@ class ModalBottomSheetTest { rule.setContent { sheetState = rememberModalBottomSheetState() - val context = LocalContext.current - val density = LocalDensity.current - val resScreenHeight = context.resources.configuration.screenHeightDp - with(density) { - screenHeightPx = resScreenHeight.dp.roundToPx().toFloat() - } - ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) { + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + ModalBottomSheet( + onDismissRequest = {}, + sheetState = sheetState, + windowInsets = windowInsets + ) { Box( Modifier // Deliberately use fraction != 1f @@ -255,6 +279,9 @@ class ModalBottomSheetTest { } } + screenHeightPx = with(rule.density) { + rule.onNode(isPopup()).getUnclippedBoundsInRoot().height.toPx() + } assertThat(sheetState.currentValue).isEqualTo(SheetValue.PartiallyExpanded) assertThat(sheetState.requireOffset()) .isWithin(1f) @@ -268,10 +295,13 @@ class ModalBottomSheetTest { rule.setContent { val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets if (showBottomSheet) { ModalBottomSheet( sheetState = sheetState, - onDismissRequest = { showBottomSheet = false } + onDismissRequest = { showBottomSheet = false }, + windowInsets = windowInsets ) { Box( Modifier @@ -304,10 +334,13 @@ class ModalBottomSheetTest { rule.setContent { val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets if (showBottomSheet) { ModalBottomSheet( sheetState = sheetState, - onDismissRequest = { showBottomSheet = false } + onDismissRequest = { showBottomSheet = false }, + windowInsets = windowInsets ) { Box( Modifier @@ -346,10 +379,13 @@ class ModalBottomSheetTest { val context = LocalContext.current screenHeight = context.resources.configuration.screenHeightDp.dp state = rememberModalBottomSheetState() + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets ModalBottomSheet( onDismissRequest = {}, sheetState = state, - dragHandle = null + dragHandle = null, + windowInsets = windowInsets ) { Box( Modifier @@ -358,6 +394,7 @@ class ModalBottomSheetTest { ) } } + screenHeight = rule.onNode(isPopup()).getUnclippedBoundsInRoot().height assertThat(state.requireOffset()).isWithin(1f).of(expectedExpandedAnchor) size = 100.dp @@ -376,8 +413,15 @@ class ModalBottomSheetTest { rule.setContent { state = rememberModalBottomSheetState() scope = rememberCoroutineScope() + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets - ModalBottomSheet(onDismissRequest = {}, sheetState = state, dragHandle = null) {} + ModalBottomSheet( + onDismissRequest = {}, + sheetState = state, + dragHandle = null, + windowInsets = windowInsets + ) {} } assertThat(state.swipeableState.currentValue).isEqualTo(SheetValue.Hidden) val hiddenOffset = state.requireOffset() @@ -397,10 +441,14 @@ class ModalBottomSheetTest { lateinit var scope: CoroutineScope rule.setContent { state = rememberModalBottomSheetState() + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + ModalBottomSheet( onDismissRequest = {}, sheetState = state, dragHandle = null, + windowInsets = windowInsets ) { scope = rememberCoroutineScope() LazyColumn { @@ -449,7 +497,7 @@ class ModalBottomSheetTest { sheetState = rememberModalBottomSheetState() ModalBottomSheet( onDismissRequest = {}, - sheetState = sheetState, + sheetState = sheetState ) { scrollState = rememberScrollState() Column( @@ -519,10 +567,14 @@ class ModalBottomSheetTest { rule.setContent { scope = rememberCoroutineScope() + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + ModalBottomSheet( onDismissRequest = {}, modifier = Modifier.testTag(topTag), sheetState = sheetState, + windowInsets = windowInsets ) { if (showShortContent) { Box( @@ -563,7 +615,13 @@ class ModalBottomSheetTest { lateinit var sheetState: SheetState rule.setContent { sheetState = rememberModalBottomSheetState() - ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) { + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + ModalBottomSheet( + onDismissRequest = {}, + sheetState = sheetState, + windowInsets = windowInsets + ) { Box( Modifier .fillMaxSize() @@ -593,13 +651,17 @@ class ModalBottomSheetTest { newState != SheetValue.Hidden } ) + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + ModalBottomSheet( onDismissRequest = {}, sheetState = sheetState, dragHandle = { Box( Modifier .testTag(dragHandleTag) - .size(dragHandleSize)) } + .size(dragHandleSize)) }, + windowInsets = windowInsets ) { Box( Modifier @@ -645,7 +707,13 @@ class ModalBottomSheetTest { rule.setContent { sheetState = rememberModalBottomSheetState() scope = rememberCoroutineScope() - ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) { + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + ModalBottomSheet( + onDismissRequest = {}, + sheetState = sheetState, + windowInsets = windowInsets + ) { Box( Modifier .fillMaxSize() @@ -676,7 +744,14 @@ class ModalBottomSheetTest { lateinit var sheetState: SheetState rule.setContent { sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) - ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) { + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + + ModalBottomSheet( + onDismissRequest = {}, + sheetState = sheetState, + windowInsets = windowInsets + ) { Box( Modifier .fillMaxWidth() @@ -705,7 +780,13 @@ class ModalBottomSheetTest { lateinit var sheetState: SheetState rule.setContent { sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) - ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) { + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + ModalBottomSheet( + onDismissRequest = {}, + sheetState = sheetState, + windowInsets = windowInsets + ) { Box( Modifier .fillMaxSize() @@ -728,7 +809,14 @@ class ModalBottomSheetTest { ) rule.setContent { scope = rememberCoroutineScope() - ModalBottomSheet(onDismissRequest = {}, sheetState = bottomSheetState) { + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + + ModalBottomSheet( + onDismissRequest = {}, + sheetState = bottomSheetState, + windowInsets = windowInsets + ) { Box( Modifier .fillMaxSize() @@ -751,12 +839,15 @@ class ModalBottomSheetTest { @Test fun modalBottomSheet_testDismissAction_tallBottomSheet_whenPartiallyExpanded() { rule.setContent { + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets ModalBottomSheet( onDismissRequest = {}, dragHandle = { Box( Modifier .testTag(dragHandleTag) - .size(dragHandleSize)) } + .size(dragHandleSize)) }, + windowInsets = windowInsets ) { Box( Modifier @@ -778,13 +869,16 @@ class ModalBottomSheetTest { lateinit var sheetState: SheetState rule.setContent { sheetState = rememberModalBottomSheetState() + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets ModalBottomSheet( onDismissRequest = {}, sheetState = sheetState, dragHandle = { Box( Modifier .testTag(dragHandleTag) - .size(dragHandleSize)) } + .size(dragHandleSize)) }, + windowInsets = windowInsets ) { Box( Modifier @@ -815,12 +909,8 @@ class ModalBottomSheetTest { rule.setContent { sheetState = rememberModalBottomSheetState() scope = rememberCoroutineScope() - val context = LocalContext.current - val density = LocalDensity.current - val resScreenHeight = context.resources.configuration.screenHeightDp - with(density) { - screenHeightPx = resScreenHeight.dp.roundToPx().toFloat() - } + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets ModalBottomSheet( onDismissRequest = {}, @@ -828,7 +918,8 @@ class ModalBottomSheetTest { dragHandle = { Box( Modifier .testTag(dragHandleTag) - .size(dragHandleSize)) } + .size(dragHandleSize)) }, + windowInsets = windowInsets ) { Box( Modifier @@ -837,6 +928,9 @@ class ModalBottomSheetTest { ) } } + screenHeightPx = with(rule.density) { + rule.onNode(isPopup()).getUnclippedBoundsInRoot().height.toPx() + } scope.launch { sheetState.expand() } @@ -863,12 +957,8 @@ class ModalBottomSheetTest { rule.setContent { sheetState = rememberModalBottomSheetState() scope = rememberCoroutineScope() - val context = LocalContext.current - val density = LocalDensity.current - val resScreenHeight = context.resources.configuration.screenHeightDp - with(density) { - screenHeightPx = resScreenHeight.dp.roundToPx().toFloat() - } + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets ModalBottomSheet( onDismissRequest = {}, @@ -876,7 +966,8 @@ class ModalBottomSheetTest { dragHandle = { Box( Modifier .testTag(dragHandleTag) - .size(dragHandleSize)) } + .size(dragHandleSize)) }, + windowInsets = windowInsets ) { Box( Modifier @@ -885,6 +976,9 @@ class ModalBottomSheetTest { ) } } + screenHeightPx = with(rule.density) { + rule.onNode(isPopup()).getUnclippedBoundsInRoot().height.toPx() + } scope.launch { sheetState.expand() } @@ -908,10 +1002,14 @@ class ModalBottomSheetTest { lateinit var scope: CoroutineScope rule.setContent { scope = rememberCoroutineScope() + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + ModalBottomSheet( onDismissRequest = {}, sheetState = sheetState, dragHandle = null, + windowInsets = windowInsets ) { if (hasSheetContent) { Box(Modifier.fillMaxHeight(0.4f)) @@ -943,10 +1041,14 @@ class ModalBottomSheetTest { lateinit var scope: CoroutineScope rule.setContent { scope = rememberCoroutineScope() + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + ModalBottomSheet( onDismissRequest = {}, sheetState = sheetState, dragHandle = null, + windowInsets = windowInsets ) { if (hasSheetContent) { Box(Modifier.fillMaxHeight(0.6f)) @@ -985,7 +1087,14 @@ class ModalBottomSheetTest { rule.setContent { scope = rememberCoroutineScope() - ModalBottomSheet(onDismissRequest = { callCount += 1 }, sheetState = sheetState) { + val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled) + WindowInsets(0) else BottomSheetDefaults.windowInsets + + ModalBottomSheet( + onDismissRequest = { callCount += 1 }, + sheetState = sheetState, + windowInsets = windowInsets + ) { Column( Modifier .testTag(sheetTag) @@ -1017,4 +1126,19 @@ class ModalBottomSheetTest { assertThat(sheetState.isVisible).isFalse() assertThat(callCount).isEqualTo(expectedCallCount) } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun parameters() = arrayOf( + EdgeToEdgeWrapper("EdgeToEdge", true), + EdgeToEdgeWrapper("NonEdgeToEdge", false) + ) + } + + class EdgeToEdgeWrapper(val name: String, val edgeToEdgeEnabled: Boolean) { + override fun toString(): String { + return name + } + } }
\ No newline at end of file diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt index 9ba0d942241..42b9c63a0e6 100644 --- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt +++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt @@ -30,12 +30,17 @@ import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.SheetValue.Expanded import androidx.compose.material3.SheetValue.Hidden import androidx.compose.material3.SheetValue.PartiallyExpanded @@ -59,8 +64,6 @@ import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.AbstractComposeView -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.ViewRootForInspector import androidx.compose.ui.semantics.clearAndSetSemantics @@ -72,7 +75,6 @@ import androidx.compose.ui.semantics.popup import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.findViewTreeViewModelStoreOwner import androidx.lifecycle.setViewTreeLifecycleOwner @@ -112,6 +114,8 @@ import kotlinx.coroutines.launch * @param tonalElevation The tonal elevation of this bottom sheet. * @param scrimColor Color of the scrim that obscures content when the bottom sheet is open. * @param dragHandle Optional visual marker to swipe the bottom sheet. + * @param windowInsets window insets to be passed to the bottom sheet window via [PaddingValues] + * params. * @param content The content to be displayed inside the bottom sheet. */ @Composable @@ -126,11 +130,9 @@ fun ModalBottomSheet( tonalElevation: Dp = BottomSheetDefaults.Elevation, scrimColor: Color = BottomSheetDefaults.ScrimColor, dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() }, + windowInsets: WindowInsets = BottomSheetDefaults.windowInsets, content: @Composable ColumnScope.() -> Unit, ) { - val configuration = LocalConfiguration.current - val density = LocalDensity.current - val fullHeight = with(density) { configuration.screenHeightDp.dp.toPx() } val scope = rememberCoroutineScope() val animateToDismiss: () -> Unit = { if (sheetState.swipeableState.confirmValueChange(Hidden)) { @@ -154,7 +156,12 @@ fun ModalBottomSheet( animateTo = { target, velocity -> scope.launch { sheetState.animateTo(target, velocity = velocity) } }, - snapTo = { target -> scope.launch { sheetState.snapTo(target) } } + snapTo = { target -> + val didSnapImmediately = sheetState.trySnapTo(target) + if (!didSnapImmediately) { + scope.launch { sheetState.snapTo(target) } + } + } ) } @@ -165,9 +172,11 @@ fun ModalBottomSheet( } else { // Is expanded without collapsed state or is collapsed. scope.launch { sheetState.hide() }.invokeOnCompletion { onDismissRequest() } } - } + }, + windowInsets = windowInsets, ) { - Box { + BoxWithConstraints(Modifier.fillMaxSize()) { + val fullHeight = constraints.maxHeight Scrim( color = scrimColor, onDismissRequest = animateToDismiss, @@ -200,7 +209,7 @@ fun ModalBottomSheet( .modalBottomSheetSwipeable( sheetState = sheetState, anchorChangeHandler = anchorChangeHandler, - screenHeight = fullHeight, + screenHeight = fullHeight.toFloat(), onDragStopped = { settleToDismiss(it) }, @@ -376,7 +385,8 @@ private fun ModalBottomSheetAnchorChangeHandler( @Composable internal fun ModalBottomSheetPopup( onDismissRequest: () -> Unit, - content: @Composable () -> Unit + windowInsets: WindowInsets, + content: @Composable () -> Unit, ) { val view = LocalView.current val id = rememberSaveable { UUID.randomUUID() } @@ -391,7 +401,12 @@ internal fun ModalBottomSheetPopup( setCustomContent( parent = parentComposition, content = { - Box(Modifier.semantics { this.popup() }) { + Box( + Modifier + .semantics { this.popup() } + .windowInsetsPadding(windowInsets) + .imePadding() + ) { currentContent() } } @@ -437,12 +452,6 @@ private class ModalBottomSheetWindow( return (context.resources.configuration.screenWidthDp * density).roundToInt() } - private val displayHeight: Int - get() { - val density = context.resources.displayMetrics.density - return (context.resources.configuration.screenHeightDp * density).roundToInt() - } - private val params: WindowManager.LayoutParams = WindowManager.LayoutParams().apply { // Position bottom sheet from the bottom of the screen @@ -451,7 +460,7 @@ private class ModalBottomSheetWindow( type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL // Fill up the entire app view width = displayWidth - height = displayHeight + height = WindowManager.LayoutParams.MATCH_PARENT // Format of screen pixels format = PixelFormat.TRANSLUCENT @@ -462,6 +471,14 @@ private class ModalBottomSheetWindow( ) // Get the Window token from the parent view token = composeView.applicationWindowToken + + // Flags specific to modal bottom sheet. + flags = flags and ( + WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES or + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + ).inv() + + flags = flags or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS } private var content: @Composable () -> Unit by mutableStateOf({}) diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt index 8c06454998f..c04b8109e63 100644 --- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt +++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt @@ -154,7 +154,7 @@ internal actual fun getString(string: Strings): String { androidx.compose.material3.R.string.date_range_input_invalid_range_input ) Strings.BottomSheetPaneTitle -> resources.getString( - androidx.compose.material3.R.string.m3_bottom_sheet_pane_title + androidx.compose.material3.R.string.m3c_bottom_sheet_pane_title ) Strings.BottomSheetDragHandleDescription -> resources.getString( androidx.compose.material3.R.string.bottom_sheet_drag_handle_description diff --git a/compose/material3/material3/src/androidMain/res/values/strings.xml b/compose/material3/material3/src/androidMain/res/values/strings.xml index 87c7e856a98..a6ef5ea18ed 100644 --- a/compose/material3/material3/src/androidMain/res/values/strings.xml +++ b/compose/material3/material3/src/androidMain/res/values/strings.xml @@ -73,7 +73,7 @@ --> <string name="date_range_input_invalid_range_input">Invalid date range input</string> <!-- Spoken description of a bottom sheet --> - <string name="m3_bottom_sheet_pane_title">Bottom Sheet</string> + <string name="m3c_bottom_sheet_pane_title">Bottom Sheet</string> <!-- Names the drag handle visual for bottom sheet. --> <string name="bottom_sheet_drag_handle_description">Drag handle</string> <!-- Describes the collapse action for bottom sheet. --> diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt index 71dd117e41f..3609a3c5699 100644 --- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt +++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt @@ -18,6 +18,9 @@ package androidx.compose.material3 import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.SheetValue.PartiallyExpanded @@ -215,6 +218,15 @@ class SheetState( } /** + * Attempt to snap synchronously. Snapping can happen synchronously when there is no other swipe + * transaction like a drag or an animation is progress. If there is another interaction in + * progress, the suspending [snapTo] overload needs to be used. + * + * @return true if the synchronous snap was successful, or false if we couldn't snap synchronous + */ + internal fun trySnapTo(targetValue: SheetValue) = swipeableState.trySnapTo(targetValue) + + /** * Find the closest anchor taking into account the velocity and settle at it with an animation. */ internal suspend fun settle(velocity: Float) { @@ -301,6 +313,13 @@ object BottomSheetDefaults { val SheetPeekHeight = 56.dp /** + * Default insets to be used and consumed by the [ModalBottomSheet] window. + */ + val windowInsets: WindowInsets + @Composable + get() = WindowInsets.systemBarsForVisualComponents.only(WindowInsetsSides.Vertical) + + /** * The optional visual marker placed on top of a bottom sheet to indicate it may be dragged. */ @Composable |