aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-06-02 15:05:24 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2023-06-02 15:05:24 +0000
commit4e81e0ef26d64ebb7a1b7d4dd873fe00195b840c (patch)
tree6a83740538327bff77cc0e23bceb6d556e59a89e
parent95260db65fe23ea0d3860e6158cb146aab5cefbf (diff)
parenta94101469836edb2b716cbfc93a70e42ab243a0f (diff)
downloadsupport-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
-rw-r--r--compose/material3/material3/api/public_plus_experimental_current.txt4
-rw-r--r--compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt18
-rw-r--r--compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt208
-rw-r--r--compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt55
-rw-r--r--compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt2
-rw-r--r--compose/material3/material3/src/androidMain/res/values/strings.xml2
-rw-r--r--compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt19
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