refactor(ui): use compose-unstyled bottomsheet
This commit is contained in:
parent
d4425f5b12
commit
40cfef5f1e
5 changed files with 214 additions and 101 deletions
|
|
@ -53,6 +53,7 @@ kotlin {
|
||||||
implementation(compose.ui)
|
implementation(compose.ui)
|
||||||
implementation(compose.components.resources)
|
implementation(compose.components.resources)
|
||||||
implementation(compose.components.uiToolingPreview)
|
implementation(compose.components.uiToolingPreview)
|
||||||
|
implementation(libs.composeunstyled)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel)
|
implementation(libs.androidx.lifecycle.viewmodel)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
package moe.lava.banksia.ui.layout
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.saveable.Saver
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.backhandler.PredictiveBackHandler
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.composables.core.BottomSheet
|
||||||
|
import com.composables.core.BottomSheetState
|
||||||
|
import com.composables.core.DragIndication
|
||||||
|
import com.composables.core.SheetDetent
|
||||||
|
import com.composables.core.rememberBottomSheetState
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
|
@Composable
|
||||||
|
fun AppBottomSheet(
|
||||||
|
sheetState: SheetStateWrapper,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
var peekHeightMultiplier by remember { mutableFloatStateOf(1f) }
|
||||||
|
var sheetEnabled by remember { mutableStateOf(true) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
BottomSheet(
|
||||||
|
state = sheetState.state,
|
||||||
|
enabled = sheetEnabled,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
// TODO: This recomposes; find a better solution using Modifier.layout
|
||||||
|
.padding(
|
||||||
|
top = 24.dp * (1f - peekHeightMultiplier),
|
||||||
|
end = 24.dp * (1f - peekHeightMultiplier),
|
||||||
|
bottom = 0.dp,
|
||||||
|
start = 24.dp * (1f - peekHeightMultiplier),
|
||||||
|
)
|
||||||
|
.shadow(4.dp, RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp))
|
||||||
|
.clip(RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp))
|
||||||
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.imePadding(),
|
||||||
|
) {
|
||||||
|
Column(Modifier.fillMaxSize().alpha(peekHeightMultiplier)) {
|
||||||
|
DragIndication(
|
||||||
|
Modifier
|
||||||
|
.padding(vertical = 12.dp)
|
||||||
|
.height(4.dp)
|
||||||
|
.width(32.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
.background(MaterialTheme.colorScheme.onSurfaceVariant, RoundedCornerShape(100))
|
||||||
|
)
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PredictiveBackHandler(!sheetState.hidden) { progress ->
|
||||||
|
sheetEnabled = false
|
||||||
|
try {
|
||||||
|
progress.collect { backEvent ->
|
||||||
|
if (sheetState.peeking) {
|
||||||
|
peekHeightMultiplier = 1F - backEvent.progress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sheetState.expanded) {
|
||||||
|
scope.launch { sheetState.peek() }
|
||||||
|
} else if (sheetState.peeking) {
|
||||||
|
scope.launch {
|
||||||
|
sheetState.hide()
|
||||||
|
peekHeightMultiplier = 1F
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_: CancellationException) {
|
||||||
|
peekHeightMultiplier = 1F
|
||||||
|
}
|
||||||
|
sheetEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SheetStateWrapper(
|
||||||
|
val state: BottomSheetState,
|
||||||
|
private val scope: CoroutineScope,
|
||||||
|
private var p1: MutableState<Dp>,
|
||||||
|
private var p2: MutableState<Dp>,
|
||||||
|
private val peek1: SheetDetent,
|
||||||
|
private val peek2: SheetDetent,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val saver = Saver<MutableState<Dp>, Float>(
|
||||||
|
save = { it.value.value },
|
||||||
|
restore = { mutableStateOf(it.dp) }
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun create(): SheetStateWrapper {
|
||||||
|
val p1 = rememberSaveable(saver = saver) { mutableStateOf(0.dp) }
|
||||||
|
val p2 = rememberSaveable(saver = saver) { mutableStateOf(0.dp) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val peek1 = SheetDetent(identifier = "peek1") { containerHeight, sheetHeight ->
|
||||||
|
val res = (p1.value + 40.dp)
|
||||||
|
res
|
||||||
|
}
|
||||||
|
val peek2 = SheetDetent(identifier = "peek2") { containerHeight, sheetHeight ->
|
||||||
|
val res = (p2.value + 40.dp)
|
||||||
|
res
|
||||||
|
}
|
||||||
|
val internalState = rememberBottomSheetState(
|
||||||
|
initialDetent = SheetDetent.Hidden,
|
||||||
|
detents = listOf(SheetDetent.Hidden, peek1, peek2, SheetDetent.FullyExpanded)
|
||||||
|
)
|
||||||
|
return remember { SheetStateWrapper(internalState, scope, p1, p2, peek1, peek2) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
private inline fun stateEither(detent: SheetDetent) = state.currentDetent == detent || state.targetDetent == detent
|
||||||
|
|
||||||
|
private var peek: SheetDetent = peek1
|
||||||
|
|
||||||
|
val current get() = state.currentDetent
|
||||||
|
val target get() = state.targetDetent
|
||||||
|
val expanded get() = stateEither(SheetDetent.FullyExpanded)
|
||||||
|
val peeking get() = stateEither(peek1) || stateEither(peek2)
|
||||||
|
val hidden get() = stateEither(SheetDetent.Hidden)
|
||||||
|
val offset get() = state.offset
|
||||||
|
|
||||||
|
val bottomInset: Int @Composable get() {
|
||||||
|
return if (!hidden) {
|
||||||
|
val sheetOffset = state.offset.roundToInt()
|
||||||
|
val insets = WindowInsets.safeDrawing.getBottom(LocalDensity.current)
|
||||||
|
(sheetOffset - insets)
|
||||||
|
.coerceAtLeast(0)
|
||||||
|
.coerceIn(0, with(LocalDensity.current) { 500.dp.roundToPx() })
|
||||||
|
} else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hide() { state.targetDetent = SheetDetent.Hidden }
|
||||||
|
fun peek() { state.targetDetent = peek }
|
||||||
|
fun peekTo(target: Dp) {
|
||||||
|
if (peek == peek1) {
|
||||||
|
p2.value = target
|
||||||
|
peek = peek2
|
||||||
|
} else {
|
||||||
|
p1.value = target
|
||||||
|
peek = peek1
|
||||||
|
}
|
||||||
|
scope.launch {
|
||||||
|
state.animateTo(peek)
|
||||||
|
p1.value = target
|
||||||
|
p2.value = target
|
||||||
|
state.invalidateDetents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,13 +7,10 @@ import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeContent
|
import androidx.compose.foundation.layout.safeContent
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.windowInsetsBottomHeight
|
import androidx.compose.foundation.layout.windowInsetsBottomHeight
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
|
@ -48,8 +45,6 @@ fun InfoPanel(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 24.dp)
|
.padding(horizontal = 24.dp)
|
||||||
.heightIn(max = 250.dp)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.onSizeChanged {
|
.onSizeChanged {
|
||||||
onPeekHeightChange(with(localDensity) { it.height.toDp().coerceAtMost(250.dp) })
|
onPeekHeightChange(with(localDensity) { it.height.toDp().coerceAtMost(250.dp) })
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +59,7 @@ fun InfoPanel(
|
||||||
|
|
||||||
if (state.loading)
|
if (state.loading)
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier.width(32.dp).align(Alignment.CenterEnd)
|
modifier = Modifier.size(32.dp).align(Alignment.TopEnd)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeContent))
|
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeContent))
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,17 @@ import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.add
|
import androidx.compose.foundation.layout.add
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.safeContent
|
import androidx.compose.foundation.layout.safeContent
|
||||||
import androidx.compose.foundation.layout.safeDrawing
|
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.material3.BottomSheetDefaults
|
|
||||||
import androidx.compose.material3.BottomSheetScaffold
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SearchBarDefaults
|
import androidx.compose.material3.SearchBarDefaults
|
||||||
import androidx.compose.material3.SheetValue
|
|
||||||
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
|
||||||
import androidx.compose.material3.rememberStandardBottomSheetState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
|
@ -31,10 +23,7 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.backhandler.PredictiveBackHandler
|
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import dev.icerock.moko.geo.compose.BindLocationTrackerEffect
|
import dev.icerock.moko.geo.compose.BindLocationTrackerEffect
|
||||||
import dev.icerock.moko.geo.compose.LocationTrackerAccuracy
|
import dev.icerock.moko.geo.compose.LocationTrackerAccuracy
|
||||||
|
|
@ -42,17 +31,16 @@ import dev.icerock.moko.geo.compose.rememberLocationTrackerFactory
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import moe.lava.banksia.resources.Res
|
import moe.lava.banksia.resources.Res
|
||||||
import moe.lava.banksia.resources.my_location_24
|
import moe.lava.banksia.resources.my_location_24
|
||||||
|
import moe.lava.banksia.ui.layout.AppBottomSheet
|
||||||
import moe.lava.banksia.ui.layout.InfoPanel
|
import moe.lava.banksia.ui.layout.InfoPanel
|
||||||
import moe.lava.banksia.ui.layout.Searcher
|
import moe.lava.banksia.ui.layout.Searcher
|
||||||
|
import moe.lava.banksia.ui.layout.SheetStateWrapper
|
||||||
import moe.lava.banksia.ui.platform.BanksiaTheme
|
import moe.lava.banksia.ui.platform.BanksiaTheme
|
||||||
import moe.lava.banksia.ui.platform.maps.Maps
|
import moe.lava.banksia.ui.platform.maps.Maps
|
||||||
import moe.lava.banksia.ui.platform.maps.getScreenHeight
|
|
||||||
import moe.lava.banksia.ui.state.InfoPanelState
|
import moe.lava.banksia.ui.state.InfoPanelState
|
||||||
import moe.lava.banksia.util.Point
|
import moe.lava.banksia.util.Point
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import org.jetbrains.compose.resources.painterResource
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
val MELBOURNE = Point(-37.8136, 144.9631)
|
val MELBOURNE = Point(-37.8136, 144.9631)
|
||||||
|
|
||||||
|
|
@ -73,64 +61,19 @@ fun MapScreen(
|
||||||
val mapState by viewModel.mapState.collectAsStateWithLifecycle()
|
val mapState by viewModel.mapState.collectAsStateWithLifecycle()
|
||||||
val searchState by viewModel.searchState.collectAsStateWithLifecycle()
|
val searchState by viewModel.searchState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val scaffoldState = rememberBottomSheetScaffoldState(
|
val sheetState = SheetStateWrapper.create()
|
||||||
bottomSheetState = rememberStandardBottomSheetState(
|
var searchExpandedState by rememberSaveable { mutableStateOf(false) }
|
||||||
initialValue = SheetValue.Hidden,
|
|
||||||
skipHiddenState = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val sheetState = scaffoldState.bottomSheetState
|
|
||||||
val extInsets = if (
|
|
||||||
sheetState.currentValue != SheetValue.Hidden ||
|
|
||||||
sheetState.targetValue != SheetValue.Hidden
|
|
||||||
) {
|
|
||||||
val offset = runCatching { sheetState.requireOffset() }
|
|
||||||
val scaffoldOffset = offset.getOrDefault(0.0f).roundToInt()
|
|
||||||
(getScreenHeight() - scaffoldOffset - WindowInsets.safeDrawing.getBottom(
|
|
||||||
LocalDensity.current)).coerceAtLeast(0)
|
|
||||||
} else 0
|
|
||||||
|
|
||||||
LaunchedEffect(infoState) {
|
LaunchedEffect(infoState) {
|
||||||
if (infoState !is InfoPanelState.None)
|
if (infoState !is InfoPanelState.None) {
|
||||||
scope.launch { scaffoldState.bottomSheetState.partialExpand() }
|
sheetState.peek()
|
||||||
else
|
} else {
|
||||||
scope.launch { scaffoldState.bottomSheetState.hide() }
|
sheetState.hide()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var searchExpandedState by rememberSaveable { mutableStateOf(false) }
|
|
||||||
var sheetSwipeEnabled by rememberSaveable { mutableStateOf(true) }
|
|
||||||
var handleHeight by remember { mutableStateOf(0.dp) }
|
|
||||||
var peekHeight by remember { mutableStateOf(0.dp) }
|
|
||||||
var peekHeightMultiplier by remember { mutableFloatStateOf(1F) }
|
|
||||||
|
|
||||||
BanksiaTheme {
|
BanksiaTheme {
|
||||||
BottomSheetScaffold(
|
Scaffold {
|
||||||
scaffoldState = scaffoldState,
|
|
||||||
sheetPeekHeight = (handleHeight + peekHeight) * peekHeightMultiplier,
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
sheetContent = {
|
|
||||||
InfoPanel(
|
|
||||||
state = infoState,
|
|
||||||
onEvent = viewModel::handleEvent,
|
|
||||||
onPeekHeightChange = { peekHeight = it },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
sheetDragHandle = {
|
|
||||||
val density = LocalDensity.current
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 10.dp)
|
|
||||||
.onSizeChanged {
|
|
||||||
handleHeight = with(density) { it.height.toDp() }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
BottomSheetDefaults.DragHandle(modifier = Modifier.align(Alignment.Center))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sheetSwipeEnabled = sheetSwipeEnabled,
|
|
||||||
) {
|
|
||||||
Maps(
|
Maps(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
state = mapState,
|
state = mapState,
|
||||||
|
|
@ -138,7 +81,7 @@ fun MapScreen(
|
||||||
cameraPositionFlow = viewModel.cameraChangeEmitter,
|
cameraPositionFlow = viewModel.cameraChangeEmitter,
|
||||||
extInsets = WindowInsets(top = with(LocalDensity.current) {
|
extInsets = WindowInsets(top = with(LocalDensity.current) {
|
||||||
SearchBarDefaults.InputFieldHeight.roundToPx()
|
SearchBarDefaults.InputFieldHeight.roundToPx()
|
||||||
}, bottom = extInsets),
|
}, bottom = sheetState.bottomInset),
|
||||||
setLastKnownLocation = viewModel::setLastKnownLocation,
|
setLastKnownLocation = viewModel::setLastKnownLocation,
|
||||||
)
|
)
|
||||||
Searcher(
|
Searcher(
|
||||||
|
|
@ -147,39 +90,16 @@ fun MapScreen(
|
||||||
expanded = searchExpandedState,
|
expanded = searchExpandedState,
|
||||||
onExpandedChange = {
|
onExpandedChange = {
|
||||||
searchExpandedState = it
|
searchExpandedState = it
|
||||||
if (it)
|
if (it) scope.launch { sheetState.hide() }
|
||||||
scope.launch { scaffoldState.bottomSheetState.hide() }
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
PredictiveBackHandler(scaffoldState.bottomSheetState.currentValue != SheetValue.Hidden) { progress ->
|
|
||||||
sheetSwipeEnabled = false
|
|
||||||
try {
|
|
||||||
progress.collect { backEvent ->
|
|
||||||
if (scaffoldState.bottomSheetState.currentValue == SheetValue.PartiallyExpanded) {
|
|
||||||
peekHeightMultiplier = 1F - backEvent.progress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (scaffoldState.bottomSheetState.currentValue == SheetValue.Expanded)
|
|
||||||
scope.launch { scaffoldState.bottomSheetState.partialExpand() }
|
|
||||||
else if (scaffoldState.bottomSheetState.currentValue == SheetValue.PartiallyExpanded)
|
|
||||||
scope.launch {
|
|
||||||
scaffoldState.bottomSheetState.hide()
|
|
||||||
peekHeightMultiplier = 1F
|
|
||||||
viewModel.handleEvent(MapScreenEvent.DismissState)
|
|
||||||
}
|
|
||||||
} catch (_: CancellationException) {
|
|
||||||
peekHeightMultiplier = 1F
|
|
||||||
}
|
|
||||||
sheetSwipeEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.windowInsetsPadding(
|
.windowInsetsPadding(
|
||||||
WindowInsets.safeContent.add(
|
WindowInsets.safeContent.add(
|
||||||
WindowInsets(bottom = extInsets)
|
WindowInsets(bottom = sheetState.bottomInset)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.BottomEnd
|
contentAlignment = Alignment.BottomEnd
|
||||||
|
|
@ -191,6 +111,17 @@ fun MapScreen(
|
||||||
Icon(painterResource(Res.drawable.my_location_24), "Move to current location")
|
Icon(painterResource(Res.drawable.my_location_24), "Move to current location")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppBottomSheet(
|
||||||
|
sheetState = sheetState,
|
||||||
|
onDismiss = { viewModel.handleEvent(MapScreenEvent.DismissState) }
|
||||||
|
) {
|
||||||
|
InfoPanel(
|
||||||
|
state = infoState,
|
||||||
|
onEvent = viewModel::handleEvent,
|
||||||
|
onPeekHeightChange = { ph -> sheetState.peekTo(ph) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ androidx-lifecycle = "2.9.6"
|
||||||
androidx-material = "1.12.0"
|
androidx-material = "1.12.0"
|
||||||
androidx-test-junit = "1.2.1"
|
androidx-test-junit = "1.2.1"
|
||||||
compose-multiplatform = "1.9.3"
|
compose-multiplatform = "1.9.3"
|
||||||
|
composeunstyled = "1.49.2"
|
||||||
coroutines = "1.10.2"
|
coroutines = "1.10.2"
|
||||||
geo = "0.8.0"
|
geo = "0.8.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
|
|
@ -32,6 +33,7 @@ room = "2.8.4"
|
||||||
secretsGradlePlugin = "2.0.1"
|
secretsGradlePlugin = "2.0.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
composeunstyled = { module = "com.composables:composeunstyled", version.ref = "composeunstyled" }
|
||||||
moko-geo = { module = "dev.icerock.moko:geo", version.ref = "geo" }
|
moko-geo = { module = "dev.icerock.moko:geo", version.ref = "geo" }
|
||||||
moko-geo-compose = { module = "dev.icerock.moko:geo-compose", version.ref = "geo" }
|
moko-geo-compose = { module = "dev.icerock.moko:geo-compose", version.ref = "geo" }
|
||||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue