|
import androidx.annotation.DrawableRes |
|
import androidx.compose.foundation.background |
|
import androidx.compose.foundation.clickable |
|
import androidx.compose.foundation.gestures.detectDragGestures |
|
import androidx.compose.foundation.layout.* |
|
import androidx.compose.foundation.shape.RoundedCornerShape |
|
import androidx.compose.material.* |
|
import androidx.compose.runtime.Composable |
|
import androidx.compose.runtime.mutableStateOf |
|
import androidx.compose.runtime.remember |
|
import androidx.compose.runtime.rememberCoroutineScope |
|
import androidx.compose.ui.Modifier |
|
import androidx.compose.ui.draw.alpha |
|
import androidx.compose.ui.graphics.Color |
|
import androidx.compose.ui.input.pointer.consumeAllChanges |
|
import androidx.compose.ui.input.pointer.pointerInput |
|
import androidx.compose.ui.layout.onGloballyPositioned |
|
import androidx.compose.ui.platform.LocalDensity |
|
import androidx.compose.ui.res.painterResource |
|
import androidx.compose.ui.text.style.TextOverflow |
|
import androidx.compose.ui.unit.dp |
|
import kotlinx.coroutines.launch |
|
|
|
|
|
object SimpleBottomSheet { |
|
|
|
@Composable |
|
fun BuildSheet( |
|
isVisible: Boolean, |
|
bottomSheetManager: SimpleBottomSheetManager, |
|
bottomContainerData: BottomContainerData, |
|
titleContainerData: TitleContainerData, |
|
){ |
|
val coroutineScope = rememberCoroutineScope() |
|
val density = LocalDensity.current |
|
|
|
val screenHeight = remember{ mutableStateOf(0f) } |
|
// get screen height and initialize (once) |
|
if(screenHeight.value == 0f){ |
|
BoxWithConstraints { |
|
screenHeight.value = maxHeight.value |
|
bottomSheetManager.setup(screenHeight.value) |
|
} |
|
} |
|
|
|
val paddingHorizontal = remember{8} |
|
Box( |
|
modifier = Modifier.fillMaxSize(), |
|
) { |
|
Column( |
|
modifier = Modifier |
|
.alpha(if(isVisible) 1f else 0f) |
|
){ |
|
val modifier = Modifier |
|
.padding(start = paddingHorizontal.dp, end = paddingHorizontal.dp) |
|
.fillMaxWidth() |
|
.background(color = Color.White) |
|
Surface( |
|
modifier = Modifier |
|
.offset(y = bottomSheetManager.sheetPosition.value.dp) |
|
.pointerInput(Unit) { |
|
detectDragGestures( |
|
onDrag = { change, dragAmount -> |
|
change.consumeAllChanges() |
|
bottomSheetManager.onDrag(dragAmount.y) |
|
}, |
|
onDragEnd = { |
|
coroutineScope.launch{ |
|
bottomSheetManager.onDragEnd() |
|
} |
|
}, |
|
onDragStart = { |
|
bottomSheetManager.onDragStart() |
|
}, |
|
) |
|
} |
|
, |
|
shape = RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp), |
|
) { |
|
Box( |
|
modifier = Modifier |
|
.onGloballyPositioned { layoutCoordinates -> |
|
val height = with(density) { (layoutCoordinates.size.height).toDp() } |
|
bottomSheetManager.setTitleContainerHeight(height.value) |
|
} |
|
){ |
|
BottomSheetTitleContainer( |
|
titleContainerData = titleContainerData, |
|
modifier = modifier |
|
.padding(top = 16.dp, bottom = 16.dp) |
|
, |
|
) |
|
} |
|
} |
|
Surface( |
|
modifier = Modifier |
|
.offset(y = bottomSheetManager.sheetPosition.value.dp) |
|
.pointerInput(Unit) { |
|
detectDragGestures( |
|
onDrag = { change, dragAmount -> |
|
change.consumeAllChanges() |
|
bottomSheetManager.onDrag(dragAmount.y) |
|
}, |
|
onDragEnd = { |
|
coroutineScope.launch { |
|
bottomSheetManager.onDragEnd() |
|
} |
|
}, |
|
onDragStart = { |
|
bottomSheetManager.onDragStart() |
|
} |
|
) |
|
} |
|
, |
|
) { |
|
Box( |
|
modifier = Modifier |
|
.onGloballyPositioned { layoutCoordinates -> |
|
val height = with(density) { (layoutCoordinates.size.height).toDp() } |
|
bottomSheetManager.setBottomContainerHeight(height.value) |
|
} |
|
){ |
|
BottomSheetBottomContainer( |
|
modifier = modifier, |
|
bottomContainerData = bottomContainerData |
|
) |
|
} |
|
} |
|
} |
|
} |
|
bottomSheetManager.animate() |
|
} |
|
|
|
@Composable |
|
private fun BottomSheetBottomContainer( |
|
modifier: Modifier, |
|
bottomContainerData: BottomContainerData, |
|
){ |
|
Column( |
|
modifier = modifier |
|
.padding(bottom = 16.dp) |
|
){ |
|
Divider( |
|
color = Color(0xFFc2c2c2), |
|
thickness = 1.dp |
|
) |
|
Column { |
|
Text( |
|
modifier = Modifier |
|
.fillMaxWidth(.85f) |
|
.padding(top = 8.dp) |
|
, |
|
text = bottomContainerData.title, |
|
maxLines = 1, |
|
overflow = TextOverflow.Ellipsis, |
|
style = MaterialTheme.typography.subtitle1, |
|
) |
|
bottomContainerData.subText1?.let { |
|
Text( |
|
modifier = Modifier |
|
.fillMaxWidth(.85f) |
|
.padding(top = 8.dp) |
|
, |
|
text = it , |
|
maxLines = 2, |
|
overflow = TextOverflow.Ellipsis, |
|
style = MaterialTheme.typography.body1, |
|
) |
|
} |
|
|
|
} |
|
Column { |
|
bottomContainerData.subText2?.let { |
|
Text( |
|
modifier = Modifier |
|
.fillMaxWidth(.85f) |
|
.padding(top = 8.dp) |
|
, |
|
text = it, |
|
maxLines = 1, |
|
overflow = TextOverflow.Ellipsis, |
|
style = MaterialTheme.typography.body1, |
|
) |
|
} |
|
bottomContainerData.subText3?.let { |
|
Text( |
|
modifier = Modifier |
|
.fillMaxWidth(.85f) |
|
.padding(top = 8.dp) |
|
, |
|
text = it, |
|
maxLines = 2, |
|
overflow = TextOverflow.Ellipsis, |
|
style = MaterialTheme.typography.body1, |
|
) |
|
} |
|
} |
|
} |
|
} |
|
|
|
@Composable |
|
private fun BottomSheetTitleContainer( |
|
titleContainerData: TitleContainerData, |
|
modifier: Modifier, |
|
){ |
|
val verticalPadding = remember {8} |
|
Column (modifier = modifier){ |
|
Row{ |
|
Text( |
|
modifier = Modifier |
|
.fillMaxWidth(if(titleContainerData.iconResource != null) .85f else 1f) |
|
, |
|
text = titleContainerData.title, |
|
maxLines = 2, |
|
overflow = TextOverflow.Ellipsis, |
|
style = MaterialTheme.typography.h3, |
|
) |
|
titleContainerData.iconResource?.let { drawable -> |
|
Row( |
|
modifier = Modifier.fillMaxWidth(), |
|
horizontalArrangement= Arrangement.End |
|
){ |
|
Icon( |
|
modifier = Modifier |
|
.width(35.dp) |
|
.height(35.dp) |
|
.clickable { |
|
if(titleContainerData.onClickIcon != null){ |
|
titleContainerData.onClickIcon.invoke() |
|
} |
|
} |
|
, |
|
painter = painterResource(id = drawable), |
|
contentDescription = "Bottom Sheet Icon", |
|
) |
|
} |
|
} |
|
} |
|
titleContainerData.subtitle?.let { |
|
Text( |
|
modifier = Modifier |
|
.fillMaxWidth() |
|
.padding(top = verticalPadding.dp) |
|
, |
|
text = it, |
|
maxLines = 1, |
|
overflow = TextOverflow.Ellipsis, |
|
style = MaterialTheme.typography.body1, |
|
) |
|
} |
|
} |
|
} |
|
|
|
data class TitleContainerData( |
|
val title: String, |
|
val subtitle: String? = null, |
|
@DrawableRes val iconResource: Int? = null, |
|
val onClickIcon: (() -> Unit)? = null, |
|
) |
|
data class BottomContainerData( |
|
val title: String, |
|
val subText1: String? = null, |
|
val subText2: String? = null, |
|
val subText3: String? = null, |
|
) |
|
|
|
} |