diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..44ca2d9
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bottom-drawer-scaffold/build.gradle b/bottom-drawer-scaffold/build.gradle
index f2cd22f..2eca6f8 100644
--- a/bottom-drawer-scaffold/build.gradle
+++ b/bottom-drawer-scaffold/build.gradle
@@ -8,7 +8,7 @@ apply from: '../buildCompose.gradle'
ext {
PUBLISH_GROUP_ID = 'de.charlex.compose'
- PUBLISH_VERSION = '2.0.0-beta02'
+ PUBLISH_VERSION = '2.0.0-beta03'
PUBLISH_ARTIFACT_ID = 'bottom-drawer-scaffold'
}
diff --git a/bottom-drawer-scaffold/src/main/java/de/charlex/compose/bottomdrawerscaffold/AnchoredDraggableStateExt.kt b/bottom-drawer-scaffold/src/main/java/de/charlex/compose/bottomdrawerscaffold/AnchoredDraggableStateExt.kt
deleted file mode 100644
index 9346191..0000000
--- a/bottom-drawer-scaffold/src/main/java/de/charlex/compose/bottomdrawerscaffold/AnchoredDraggableStateExt.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-package de.charlex.compose.bottomdrawerscaffold
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.gestures.AnchoredDraggableState
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
-import androidx.compose.ui.unit.Velocity
-
-@OptIn(ExperimentalFoundationApi::class)
-internal fun AnchoredDraggableState.createPreUpPostDownNestedScrollConnection(
- orientation: Orientation,
- onFling: (velocity: Float) -> Unit
-): NestedScrollConnection {
- return object : NestedScrollConnection {
- override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
- val delta = available.toFloat()
- return if (delta < 0 && source == NestedScrollSource.Drag) {
- dispatchRawDelta(delta).toOffset()
- } else {
- Offset.Zero
- }
- }
-
- override fun onPostScroll(
- consumed: Offset,
- available: Offset,
- source: NestedScrollSource
- ): Offset {
- return if (source == NestedScrollSource.Drag) {
- dispatchRawDelta(available.toFloat()).toOffset()
- } else {
- Offset.Zero
- }
- }
-
- override suspend fun onPreFling(available: Velocity): Velocity {
- val toFling = available.toFloat()
- val currentOffset = requireOffset()
- val minAnchor = anchors.minAnchor()
- return if (toFling < 0 && currentOffset > minAnchor) {
- onFling(toFling)
- // since we go to the anchor with tween settling, consume all for the best UX
- available
- } else {
- Velocity.Zero
- }
- }
-
- override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
- onFling(available.toFloat())
- return available
- }
-
- private fun Float.toOffset(): Offset = Offset(
- x = if (orientation == Orientation.Horizontal) this else 0f,
- y = if (orientation == Orientation.Vertical) this else 0f
- )
-
- @JvmName("velocityToFloat")
- private fun Velocity.toFloat() = if (orientation == Orientation.Horizontal) x else y
-
- @JvmName("offsetToFloat")
- private fun Offset.toFloat(): Float = if (orientation == Orientation.Horizontal) x else y
- }
-}
diff --git a/bottom-drawer-scaffold/src/main/java/de/charlex/compose/bottomdrawerscaffold/BottomDrawerScaffold.kt b/bottom-drawer-scaffold/src/main/java/de/charlex/compose/bottomdrawerscaffold/BottomDrawerScaffold.kt
index 248d47a..a82a8e7 100644
--- a/bottom-drawer-scaffold/src/main/java/de/charlex/compose/bottomdrawerscaffold/BottomDrawerScaffold.kt
+++ b/bottom-drawer-scaffold/src/main/java/de/charlex/compose/bottomdrawerscaffold/BottomDrawerScaffold.kt
@@ -1,13 +1,5 @@
package de.charlex.compose.bottomdrawerscaffold
-import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.gestures.AnchoredDraggableState
-import androidx.compose.foundation.gestures.DraggableAnchors
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.anchoredDraggable
-import androidx.compose.foundation.gestures.animateTo
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
@@ -23,73 +15,42 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeightIn
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.BottomSheetScaffold
+import androidx.compose.material3.BottomSheetScaffoldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SheetValue
+import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.contentColorFor
+import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.semantics.collapse
-import androidx.compose.ui.semantics.expand
-import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
-import kotlin.math.roundToInt
-@OptIn(ExperimentalFoundationApi::class)
-@Composable
-fun rememberBottomDrawerScaffoldState(
- initialValue: BottomDrawerValue = BottomDrawerValue.Collapsed,
- positionalThreshold: ((totalDistance: Float) -> Float)? = null,
- velocityThreshold: (() -> Float)? = null,
- animationSpec: AnimationSpec = tween(),
- confirmValueChange: (newValue: BottomDrawerValue) -> Boolean = { true }
-): AnchoredDraggableState {
- val density = LocalDensity.current
- val maxHeight = with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
- return remember {
- AnchoredDraggableState(
- initialValue = initialValue,
- positionalThreshold = positionalThreshold ?: { with(density) { 56.dp.toPx() } },
- velocityThreshold = velocityThreshold ?: { with(density) { 125.dp.toPx() } },
- animationSpec = animationSpec,
- anchors = DraggableAnchors {
- BottomDrawerValue.Collapsed at maxHeight
- BottomDrawerValue.Expanded at 0f
- },
- confirmValueChange = confirmValueChange
- )
- }
-}
-
-@OptIn(ExperimentalFoundationApi::class)
@Composable
@ExperimentalMaterial3Api
fun BottomDrawerScaffold(
modifier: Modifier = Modifier,
- bottomDrawerScaffoldState: AnchoredDraggableState = rememberBottomDrawerScaffoldState(),
+ bottomSheetScaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(),
topBar: @Composable (() -> Unit)? = null,
bottomBar: @Composable (() -> Unit)? = null,
gesturesEnabled: Boolean = true,
drawerModifier: Modifier = Modifier,
- snackbarHost: @Composable () -> Unit = {},
+ snackbarHost: @Composable (SnackbarHostState) -> Unit = {},
floatingActionButton: @Composable (() -> Unit)? = null,
floatingActionButtonPosition: FabPosition = FabPosition.End,
drawerGesturesEnabled: Boolean? = null,
@@ -106,10 +67,6 @@ fun BottomDrawerScaffold(
contentColor: Color = MaterialTheme.colorScheme.contentColorFor(backgroundColor),
content: @Composable (PaddingValues) -> Unit
) {
- val scope = rememberCoroutineScope()
-
- val orientation = Orientation.Vertical
-
Scaffold(
modifier = modifier,
contentWindowInsets = WindowInsets.navigationBars.exclude(WindowInsets.statusBars),
@@ -122,192 +79,119 @@ fun BottomDrawerScaffold(
floatingActionButton = {
floatingActionButton?.invoke()
},
- snackbarHost = snackbarHost,
floatingActionButtonPosition = floatingActionButtonPosition,
) { scaffoldPaddingValues ->
+ val scope = rememberCoroutineScope()
+
BoxWithConstraints(
- modifier = Modifier.padding(scaffoldPaddingValues),
- contentAlignment = Alignment.BottomCenter
+ modifier = Modifier
+ .padding(scaffoldPaddingValues)
) {
val fullHeight = constraints.maxHeight.toFloat()
val peekHeightPx = with(LocalDensity.current) { drawerPeekHeight.toPx() }
var bottomDrawerHeight by remember { mutableStateOf(fullHeight) }
- val density = LocalDensity.current
- val topPadding = if (topBar == null) {
- with(density) { WindowInsets.statusBars.asPaddingValues(density).calculateTopPadding().toPx() }
- } else {
- 0f
- }
-
- LaunchedEffect(fullHeight, peekHeightPx, bottomDrawerHeight) {
- bottomDrawerScaffoldState.updateAnchors(
- newAnchors = DraggableAnchors {
- BottomDrawerValue.Collapsed at (fullHeight - peekHeightPx)
- BottomDrawerValue.Expanded at topPadding
+ BottomSheetScaffold(
+ modifier = Modifier,
+ scaffoldState = bottomSheetScaffoldState,
+ sheetDragHandle = null,
+ sheetSwipeEnabled = drawerGesturesEnabled ?: gesturesEnabled,
+ sheetContainerColor = Color.Transparent,
+ sheetPeekHeight = drawerPeekHeight + 30.dp,
+ sheetTonalElevation = 0.dp,
+ sheetShadowElevation = 0.dp,
+ snackbarHost = snackbarHost,
+ sheetContent = {
+ val topPadding = if (topBar == null) {
+ WindowInsets.statusBars.asPaddingValues(LocalDensity.current).calculateTopPadding()
+ } else {
+ 0.dp
}
- )
- }
-
- val anchoredDraggableModifier = Modifier
- .nestedScroll(
- bottomDrawerScaffoldState.createPreUpPostDownNestedScrollConnection(
- orientation = orientation,
- onFling = { scope.launch { bottomDrawerScaffoldState.settle(it) } }
- )
- )
- .anchoredDraggable(
- state = bottomDrawerScaffoldState,
- orientation = orientation,
- enabled = drawerGesturesEnabled ?: gesturesEnabled,
- )
- .semantics {
- if (peekHeightPx != bottomDrawerHeight) {
- if (bottomDrawerScaffoldState.isCollapsed()) {
- expand {
- scope.launch { bottomDrawerScaffoldState.expand() }
- true
- }
- } else {
- collapse {
- scope.launch { bottomDrawerScaffoldState.collapse() }
- true
+ Surface(
+ Modifier
+ .fillMaxWidth()
+ .requiredHeightIn(
+ min = drawerPeekHeight
+ )
+ .padding(
+ top = drawerPadding + topPadding,
+ start = drawerPadding,
+ end = drawerPadding,
+ )
+ .onGloballyPositioned {
+ bottomDrawerHeight = it.size.height.toFloat()
}
+ .then(
+ drawerModifier
+ ),
+ shape = drawerShape,
+ shadowElevation = drawerShadowElevation,
+ tonalElevation = drawerTonalElevation,
+ color = drawerBackgroundColor,
+ contentColor = drawerContentColor,
+ content = {
+ Column(
+ content = {
+ drawerContent()
+ }
+ )
}
- }
+ )
}
-
- val child = @Composable {
- BottomDrawerScaffoldStack(
- body = {
- Surface(
- color = backgroundColor,
- contentColor = contentColor
- ) {
- Box(Modifier.fillMaxSize()) {
- content(PaddingValues(bottom = drawerPeekHeight))
-
- Scrim(
- open = bottomDrawerScaffoldState.isExpanded(),
- onClose = {
- if (gesturesEnabled) {
- scope.launch { bottomDrawerScaffoldState.collapse() }
- }
- },
- fraction = {
- calculateFraction(fullHeight - peekHeightPx, fullHeight - bottomDrawerHeight, bottomDrawerScaffoldState.requireOffset())
- },
- color = drawerScrimColor
- )
- }
- }
- },
- bottomDrawer = {
- val density = LocalDensity.current
- Surface(
- anchoredDraggableModifier
- .fillMaxWidth()
- .requiredHeightIn(min = drawerPeekHeight, max = with(density) { bottomDrawerHeight.toDp() })
- .onGloballyPositioned {
- bottomDrawerHeight = it.size.height.toFloat()
+ ) {
+ Surface(
+ color = backgroundColor,
+ contentColor = contentColor
+ ) {
+ Box(Modifier.fillMaxSize()) {
+ content(PaddingValues(bottom = drawerPeekHeight))
+
+ Scrim(
+ open = bottomSheetScaffoldState.isExpanded(),
+ onClose = {
+ if (gesturesEnabled) {
+ scope.launch { bottomSheetScaffoldState.collapse() }
}
- .padding(
- top = drawerPadding,
- start = drawerPadding,
- end = drawerPadding,
- bottom = if (topBar == null) WindowInsets.statusBars
- .asPaddingValues()
- .calculateTopPadding() else 0.dp
- )
- .then(
- drawerModifier
- ),
- shape = drawerShape,
- shadowElevation = drawerShadowElevation,
- tonalElevation = drawerTonalElevation,
- color = drawerBackgroundColor,
- contentColor = drawerContentColor,
- content = {
- Column(
- content = {
- drawerContent()
- }
- )
- }
+ },
+ fraction = {
+ calculateFraction(fullHeight - peekHeightPx, fullHeight - bottomDrawerHeight, bottomSheetScaffoldState.bottomSheetState.requireOffset())
+ },
+ color = drawerScrimColor
)
- },
- anchoredDraggableState = bottomDrawerScaffoldState,
- )
+ }
+ }
}
-
- child()
}
}
}
-@OptIn(ExperimentalFoundationApi::class)
-@Composable
-internal fun BottomDrawerScaffoldStack(
- body: @Composable () -> Unit,
- bottomDrawer: @Composable () -> Unit,
- anchoredDraggableState: AnchoredDraggableState
-) {
- Layout(
- content = {
- body()
- bottomDrawer()
- }
- ) { measurables, constraints ->
- val placeable = measurables.first().measure(constraints)
-
- layout(placeable.width, placeable.height) {
- placeable.placeRelative(0, 0)
-
- val (drawerPlaceable) =
- measurables.drop(1).map {
- it.measure(constraints.copy(minWidth = 0, minHeight = 0))
- }
+@OptIn(ExperimentalMaterial3Api::class)
+fun BottomSheetScaffoldState.isCollapsed(): Boolean {
+ return bottomSheetState.hasPartiallyExpandedState
+}
- val drawerOffsetY = anchoredDraggableState.requireOffset().roundToInt()
+@OptIn(ExperimentalMaterial3Api::class)
+fun BottomSheetScaffoldState.isExpanded(): Boolean {
+ return bottomSheetState.hasExpandedState
+}
- drawerPlaceable.placeRelative(0, drawerOffsetY)
- }
+@OptIn(ExperimentalMaterial3Api::class)
+suspend fun BottomSheetScaffoldState.toggle() {
+ if (bottomSheetState.targetValue == SheetValue.Expanded) {
+ bottomSheetState.partialExpand()
+ } else if (bottomSheetState.targetValue == SheetValue.PartiallyExpanded) {
+ bottomSheetState.expand()
}
}
-enum class BottomDrawerValue {
- /**
- * The bottom drawer is visible, but only showing its peek height.
- */
- Collapsed,
-
- /**
- * The bottom drawer is visible at its maximum height.
- */
- Expanded
+@OptIn(ExperimentalMaterial3Api::class)
+suspend fun BottomSheetScaffoldState.collapse() {
+ bottomSheetState.partialExpand()
}
-@OptIn(ExperimentalFoundationApi::class)
-fun AnchoredDraggableState.isExpanded(): Boolean =
- currentValue == BottomDrawerValue.Expanded
-
-@OptIn(ExperimentalFoundationApi::class)
-fun AnchoredDraggableState.isCollapsed(): Boolean =
- currentValue == BottomDrawerValue.Collapsed
-
-@OptIn(ExperimentalFoundationApi::class)
-suspend fun AnchoredDraggableState.expand() = animateTo(BottomDrawerValue.Expanded)
-
-@OptIn(ExperimentalFoundationApi::class)
-suspend fun AnchoredDraggableState.collapse() = animateTo(BottomDrawerValue.Collapsed)
-
-@OptIn(ExperimentalFoundationApi::class)
-suspend fun AnchoredDraggableState.toggle() {
- if (targetValue == BottomDrawerValue.Collapsed) {
- expand()
- } else {
- collapse()
- }
+@OptIn(ExperimentalMaterial3Api::class)
+suspend fun BottomSheetScaffoldState.expand() {
+ bottomSheetState.expand()
}
internal fun calculateFraction(a: Float, b: Float, pos: Float): Float {
diff --git a/example/src/main/java/de/charlex/compose/bottomdrawerscaffold/sample/MainActivity.kt b/example/src/main/java/de/charlex/compose/bottomdrawerscaffold/sample/MainActivity.kt
index 50be99f..db22bb4 100644
--- a/example/src/main/java/de/charlex/compose/bottomdrawerscaffold/sample/MainActivity.kt
+++ b/example/src/main/java/de/charlex/compose/bottomdrawerscaffold/sample/MainActivity.kt
@@ -46,6 +46,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
+import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
@@ -58,7 +59,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import de.charlex.compose.bottomdrawerscaffold.BottomDrawerScaffold
import de.charlex.compose.bottomdrawerscaffold.isCollapsed
-import de.charlex.compose.bottomdrawerscaffold.rememberBottomDrawerScaffoldState
import de.charlex.compose.bottomdrawerscaffold.toggle
import kotlinx.coroutines.launch
@@ -84,11 +84,11 @@ fun Content() {
val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
- val bottomDrawerScaffoldState = rememberBottomDrawerScaffoldState()
+ val bottomSheetScaffoldState = rememberBottomSheetScaffoldState()
BottomDrawerScaffold(
modifier = Modifier,
- bottomDrawerScaffoldState = bottomDrawerScaffoldState,
+ bottomSheetScaffoldState = bottomSheetScaffoldState,
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
@@ -116,7 +116,7 @@ fun Content() {
actions = {
IconButton(onClick = {
coroutineScope.launch {
- bottomDrawerScaffoldState.toggle()
+ bottomSheetScaffoldState.toggle()
}
}) {
Icon(Icons.Filled.Menu, "Menu icon")
@@ -130,7 +130,7 @@ fun Content() {
drawerPadding = 10.dp,
drawerContent = {
val lazyListState = rememberLazyListState()
- val collapsed = bottomDrawerScaffoldState.isCollapsed()
+ val collapsed = bottomSheetScaffoldState.isCollapsed()
LaunchedEffect(collapsed) {
if (collapsed) {