diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index fe63bb6..148fdd2 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 0ad17cb..8978d23 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/bottom-drawer-scaffold/build.gradle b/bottom-drawer-scaffold/build.gradle index 2eca6f8..c632605 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-beta03' + PUBLISH_VERSION = '2.0.0-rc01' PUBLISH_ARTIFACT_ID = 'bottom-drawer-scaffold' } 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 a82a8e7..9e1c2da 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 @@ -6,14 +6,14 @@ 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.asPaddingValues import androidx.compose.foundation.layout.exclude import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.requiredHeightIn import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.BottomSheetScaffoldState @@ -21,12 +21,15 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FabPosition import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.SheetState 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.material3.rememberStandardBottomSheetState 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 @@ -36,17 +39,39 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch + +@Composable +@ExperimentalMaterial3Api +fun rememberBottomSheetScaffoldState( + bottomSheetState: SheetState = rememberStandardBottomSheetState(), + snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, + onSheetStateChanged: (SheetValue) -> Unit +): BottomSheetScaffoldState { + val state = remember(bottomSheetState, snackbarHostState) { + BottomSheetScaffoldState( + bottomSheetState = bottomSheetState, + snackbarHostState = snackbarHostState + ) + } + + LaunchedEffect(state.bottomSheetState.currentValue) { + onSheetStateChanged(state.bottomSheetState.currentValue) + } + + return state +} + @Composable @ExperimentalMaterial3Api fun BottomDrawerScaffold( modifier: Modifier = Modifier, + contentWindowInsets: WindowInsets = WindowInsets.systemBars.exclude(WindowInsets.statusBars).exclude(WindowInsets.navigationBars), bottomSheetScaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(), - topBar: @Composable (() -> Unit)? = null, + topBar: (@Composable () -> Unit)? = null, bottomBar: @Composable (() -> Unit)? = null, gesturesEnabled: Boolean = true, drawerModifier: Modifier = Modifier, @@ -69,7 +94,7 @@ fun BottomDrawerScaffold( ) { Scaffold( modifier = modifier, - contentWindowInsets = WindowInsets.navigationBars.exclude(WindowInsets.statusBars), + contentWindowInsets = contentWindowInsets, topBar = { topBar?.invoke() }, @@ -88,78 +113,78 @@ fun BottomDrawerScaffold( .padding(scaffoldPaddingValues) ) { val fullHeight = constraints.maxHeight.toFloat() - val peekHeightPx = with(LocalDensity.current) { drawerPeekHeight.toPx() } var bottomDrawerHeight by remember { mutableStateOf(fullHeight) } + var initialOffset by remember { mutableStateOf(-1f) } + + Surface( + color = backgroundColor, + contentColor = contentColor + ) { + Box(Modifier.fillMaxSize()) { + content(PaddingValues(bottom = drawerPeekHeight)) - 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 - } - Surface( - Modifier - .fillMaxWidth() - .requiredHeightIn( - min = drawerPeekHeight - ) - .padding( - top = drawerPadding + topPadding, - start = drawerPadding, - end = drawerPadding, - ) - .onGloballyPositioned { - bottomDrawerHeight = it.size.height.toFloat() + Scrim( + open = bottomSheetScaffoldState.isExpanded(), + onClose = { + if (gesturesEnabled) { + scope.launch { bottomSheetScaffoldState.collapse() } } - .then( - drawerModifier - ), - shape = drawerShape, - shadowElevation = drawerShadowElevation, - tonalElevation = drawerTonalElevation, - color = drawerBackgroundColor, - contentColor = drawerContentColor, - content = { - Column( - content = { - drawerContent() - } - ) - } + }, + fraction = { + calculateFraction(initialOffset, 0f, bottomSheetScaffoldState.bottomSheetState.requireOffset()) + }, + color = drawerScrimColor ) } + } + + Box( + modifier = if(topBar == null) Modifier.windowInsetsPadding(WindowInsets.statusBars) else Modifier ) { - Surface( - color = backgroundColor, - contentColor = contentColor - ) { - Box(Modifier.fillMaxSize()) { - content(PaddingValues(bottom = drawerPeekHeight)) - - Scrim( - open = bottomSheetScaffoldState.isExpanded(), - onClose = { - if (gesturesEnabled) { - scope.launch { bottomSheetScaffoldState.collapse() } + BottomSheetScaffold( + modifier = Modifier, + scaffoldState = bottomSheetScaffoldState, + sheetDragHandle = null, + sheetSwipeEnabled = drawerGesturesEnabled ?: gesturesEnabled, + sheetContainerColor = Color.Transparent, + sheetPeekHeight = drawerPeekHeight, + sheetTonalElevation = 0.dp, + sheetShadowElevation = 0.dp, + snackbarHost = snackbarHost, + sheetContent = { + Surface( + Modifier + .fillMaxWidth() + .padding( + top = drawerPadding, + start = drawerPadding, + end = drawerPadding + ) + .onGloballyPositioned { + if(initialOffset == -1f) { + initialOffset = bottomSheetScaffoldState.bottomSheetState.requireOffset() + } + bottomDrawerHeight = it.size.height.toFloat() } - }, - fraction = { - calculateFraction(fullHeight - peekHeightPx, fullHeight - bottomDrawerHeight, bottomSheetScaffoldState.bottomSheetState.requireOffset()) - }, - color = drawerScrimColor + .then( + drawerModifier + ), + shape = drawerShape, + shadowElevation = drawerShadowElevation, + tonalElevation = drawerTonalElevation, + color = drawerBackgroundColor, + contentColor = drawerContentColor, + content = { + Column( + content = { + drawerContent() + } + ) + } ) - } - } + }, + content = {} + ) } } } 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 db22bb4..5bce607 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 @@ -1,6 +1,7 @@ package de.charlex.compose.bottomdrawerscaffold.sample import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge @@ -43,12 +44,13 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SheetValue import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text -import androidx.compose.material3.rememberBottomSheetScaffoldState +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -58,7 +60,6 @@ import androidx.compose.ui.graphics.vector.ImageVector 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.toggle import kotlinx.coroutines.launch @@ -75,16 +76,23 @@ class MainActivity : ComponentActivity() { } } -@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class, - ExperimentalLayoutApi::class -) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun Content() { MaterialTheme { val coroutineScope = rememberCoroutineScope() val snackbarHostState = remember { SnackbarHostState() } - val bottomSheetScaffoldState = rememberBottomSheetScaffoldState() + + val lazyListState = rememberLazyListState() + + val bottomSheetScaffoldState = de.charlex.compose.bottomdrawerscaffold.rememberBottomSheetScaffoldState { + if (it == SheetValue.PartiallyExpanded) { + coroutineScope.launch { + lazyListState.scrollToItem(0) + } + } + } BottomDrawerScaffold( modifier = Modifier, @@ -102,6 +110,7 @@ fun Content() { // }, bottomBar = { BottomAppBar( + containerColor = MaterialTheme.colorScheme.errorContainer, floatingActionButton = { FloatingActionButton( onClick = { @@ -126,22 +135,34 @@ fun Content() { }, backgroundColor = Color(0xFFF2F2F2), drawerTonalElevation = 10.dp, - drawerPeekHeight = 80.dp, + drawerPeekHeight = 100.dp, drawerPadding = 10.dp, drawerContent = { - val lazyListState = rememberLazyListState() - val collapsed = bottomSheetScaffoldState.isCollapsed() - - LaunchedEffect(collapsed) { - if (collapsed) { - lazyListState.scrollToItem(0) - } - } - LazyColumn( modifier = Modifier.fillMaxSize(), state = lazyListState ) { + item { + Group(title = "Category A") { + RowEntry(icon = Icons.Default.Build, label = "Build the app") + RowEntry(icon = Icons.Default.Send, label = "Send a message") + RowEntry(icon = Icons.Default.PlayArrow, label = "Play some stuff") + RowEntry(icon = Icons.Default.Phone, label = "Call a person") + } + } + item { + Group(title = "Other") { + RowEntry(icon = Icons.Default.Place, label = "Place or Location") + RowEntry(icon = Icons.Default.LocationOn, label = "Some text here") + } + } + item { + Group(title = "Setting") { + RowEntry(icon = Icons.Default.Email, label = "Email config") + RowEntry(icon = Icons.Default.DateRange, label = "Date Range") + RowEntry(icon = Icons.Default.ShoppingCart, label = "Subscriptions") + } + } item { Group(title = "Category A") { RowEntry(icon = Icons.Default.Build, label = "Build the app") @@ -175,7 +196,7 @@ fun Content() { modifier = Modifier .fillMaxWidth() .height(10.dp) - .background(color = MaterialTheme.colorScheme.secondaryContainer) + .background(color = MaterialTheme.colorScheme.primary) ) } } @@ -193,7 +214,7 @@ fun Content() { .fillMaxWidth() .align(Alignment.TopCenter) .height(10.dp) - .background(color = MaterialTheme.colorScheme.secondaryContainer) + .background(color = MaterialTheme.colorScheme.primary) ) Text( modifier = Modifier.align(Alignment.Center), @@ -205,7 +226,7 @@ fun Content() { .fillMaxWidth() .align(Alignment.BottomCenter) .height(10.dp) - .background(color = MaterialTheme.colorScheme.secondaryContainer) + .background(color = MaterialTheme.colorScheme.primary) ) } } @@ -240,11 +261,11 @@ fun RowEntry( println("Click on $label") } .padding( - start = 40.dp, - end = 40.dp, - top = 10.dp, - bottom = 10.dp - ), + start = 40.dp, + end = 40.dp, + top = 10.dp, + bottom = 10.dp + ), verticalAlignment = Alignment.CenterVertically ) { if (icon != null) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 155fab6..f5668c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,14 +6,16 @@ compileSdk = "34" minSdk = "21" targetSdk = "33" -composeBom = "2024.04.00" # https://developer.android.com/jetpack/compose/bom/bom-mapping -composeCompiler = "1.5.11" # https://developer.android.com/jetpack/androidx/releases/compose-kotlin -kotlin = "1.9.23" # https://developer.android.com/jetpack/androidx/releases/compose-kotlin -gradlePlugin = "8.1.4" # https://developer.android.com/build/releases/gradle-plugin +composeBom = "2024.05.00" # https://developer.android.com/jetpack/compose/bom/bom-mapping +composeCompiler = "1.5.14" # https://developer.android.com/jetpack/androidx/releases/compose-kotlin +kotlin = "1.9.24" # https://developer.android.com/jetpack/androidx/releases/compose-kotlin +gradlePlugin = "8.4.1" # https://developer.android.com/build/releases/gradle-plugin -activityCompose = "1.8.2" -coreKtx = "1.12.0" -ktlint = "0.42.1" +material3 = "1.3.0-beta02" # https://developer.android.com/build/releases/gradle-plugin + +activityCompose = "1.9.0" +coreKtx = "1.13.1" +ktlint = "0.49.1" nexusStaging = "0.30.0" @@ -22,7 +24,7 @@ compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeB compose-ui-ui = { module = "androidx.compose.ui:ui" } compose-ui-util = { module = "androidx.compose.ui:ui-util" } compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } -compose-material3-material3 = { module = "androidx.compose.material3:material3" } +compose-material3-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 84e0af4..21607e5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sun Aug 22 22:07:44 CEST 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME