Fix buggy BottomSheet nested scroll connection
Thanks, Albert Chang!
This commit is contained in:
parent
23dcce88ab
commit
ae6babb452
5 changed files with 58 additions and 78 deletions
|
@ -38,7 +38,6 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.Velocity
|
import androidx.compose.ui.unit.Velocity
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -72,36 +71,7 @@ fun BottomSheet(
|
||||||
onDragEnd = {
|
onDragEnd = {
|
||||||
val velocity = -velocityTracker.calculateVelocity().y
|
val velocity = -velocityTracker.calculateVelocity().y
|
||||||
velocityTracker.resetTracking()
|
velocityTracker.resetTracking()
|
||||||
|
state.performFling(velocity, onDismiss)
|
||||||
if (velocity > 250) {
|
|
||||||
state.expand()
|
|
||||||
} else if (velocity < -250) {
|
|
||||||
if (state.value < state.collapsedBound && onDismiss != null) {
|
|
||||||
state.dismiss()
|
|
||||||
onDismiss.invoke()
|
|
||||||
} else {
|
|
||||||
state.collapse()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val l0 = state.dismissedBound
|
|
||||||
val l1 = (state.collapsedBound - state.dismissedBound) / 2
|
|
||||||
val l2 = (state.expandedBound - state.collapsedBound) / 2
|
|
||||||
val l3 = state.expandedBound
|
|
||||||
|
|
||||||
when (state.value) {
|
|
||||||
in l0..l1 -> {
|
|
||||||
if (onDismiss != null) {
|
|
||||||
state.dismiss()
|
|
||||||
onDismiss.invoke()
|
|
||||||
} else {
|
|
||||||
state.collapse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
in l1..l2 -> state.collapse()
|
|
||||||
in l2..l3 -> state.expand()
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -173,11 +143,11 @@ class BottomSheetState(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun collapse() {
|
private fun collapse() {
|
||||||
collapse(SpringSpec())
|
collapse(SpringSpec())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun expand() {
|
private fun expand() {
|
||||||
expand(SpringSpec())
|
expand(SpringSpec())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,21 +172,53 @@ class BottomSheetState(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nestedScrollConnection(initialIsTopReached: Boolean = true): NestedScrollConnection {
|
fun performFling(velocity: Float, onDismiss: (() -> Unit)?) {
|
||||||
return object : NestedScrollConnection {
|
if (velocity > 250) {
|
||||||
var isTopReached = initialIsTopReached
|
expand()
|
||||||
|
} else if (velocity < -250) {
|
||||||
|
if (value < collapsedBound && onDismiss != null) {
|
||||||
|
dismiss()
|
||||||
|
onDismiss.invoke()
|
||||||
|
} else {
|
||||||
|
collapse()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val l0 = dismissedBound
|
||||||
|
val l1 = (collapsedBound - dismissedBound) / 2
|
||||||
|
val l2 = (expandedBound - collapsedBound) / 2
|
||||||
|
val l3 = expandedBound
|
||||||
|
|
||||||
|
when (value) {
|
||||||
|
in l0..l1 -> {
|
||||||
|
if (onDismiss != null) {
|
||||||
|
dismiss()
|
||||||
|
onDismiss.invoke()
|
||||||
|
} else {
|
||||||
|
collapse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in l1..l2 -> collapse()
|
||||||
|
in l2..l3 -> expand()
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val preUpPostDownNestedScrollConnection
|
||||||
|
get() = object : NestedScrollConnection {
|
||||||
|
var isTopReached = false
|
||||||
|
|
||||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||||
if (isExpanded && available.y < 0) {
|
if (isExpanded && available.y < 0) {
|
||||||
isTopReached = false
|
isTopReached = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTopReached) {
|
return if (isTopReached && available.y < 0 && source == NestedScrollSource.Drag) {
|
||||||
dispatchRawDelta(available.y)
|
dispatchRawDelta(available.y)
|
||||||
return available
|
available
|
||||||
|
} else {
|
||||||
|
Offset.Zero
|
||||||
}
|
}
|
||||||
|
|
||||||
return Offset.Zero
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPostScroll(
|
override fun onPostScroll(
|
||||||
|
@ -228,44 +230,30 @@ class BottomSheetState(
|
||||||
isTopReached = consumed.y == 0f && available.y > 0
|
isTopReached = consumed.y == 0f && available.y > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return Offset.Zero
|
return if (isTopReached && source == NestedScrollSource.Drag) {
|
||||||
|
dispatchRawDelta(available.y)
|
||||||
|
available
|
||||||
|
} else {
|
||||||
|
Offset.Zero
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||||
if (isTopReached) {
|
return if (isTopReached) {
|
||||||
val velocity = -available.y
|
val velocity = -available.y
|
||||||
coroutineScope {
|
performFling(velocity, null)
|
||||||
if (velocity > 250) {
|
|
||||||
expand()
|
|
||||||
} else if (velocity < -250) {
|
|
||||||
collapse()
|
|
||||||
} else {
|
|
||||||
val l0 = dismissedBound
|
|
||||||
val l1 = (collapsedBound - dismissedBound) / 2
|
|
||||||
val l2 = (expandedBound - collapsedBound) / 2
|
|
||||||
val l3 = expandedBound
|
|
||||||
|
|
||||||
when (value) {
|
available
|
||||||
in l0..l1 -> collapse()
|
} else {
|
||||||
in l1..l2 -> collapse()
|
Velocity.Zero
|
||||||
in l2..l3 -> expand()
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return available
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Velocity.Zero
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
|
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
|
||||||
isTopReached = false
|
isTopReached = false
|
||||||
return super.onPostFling(consumed, available)
|
return Velocity.Zero
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const val expandedAnchor = 2
|
const val expandedAnchor = 2
|
||||||
|
|
|
@ -37,8 +37,6 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
@ -53,9 +51,9 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
import it.vfsfitvnm.vimusic.query
|
import it.vfsfitvnm.vimusic.query
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Menu
|
import it.vfsfitvnm.vimusic.ui.components.themed.Menu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.MenuEntry
|
import it.vfsfitvnm.vimusic.ui.components.themed.MenuEntry
|
||||||
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.DefaultDarkColorPalette
|
import it.vfsfitvnm.vimusic.ui.styling.DefaultDarkColorPalette
|
||||||
|
@ -89,7 +87,6 @@ fun Lyrics(
|
||||||
mediaMetadataProvider: () -> MediaMetadata,
|
mediaMetadataProvider: () -> MediaMetadata,
|
||||||
durationProvider: () -> Long,
|
durationProvider: () -> Long,
|
||||||
onLyricsUpdate: (Boolean, String, String) -> Unit,
|
onLyricsUpdate: (Boolean, String, String) -> Unit,
|
||||||
nestedScrollConnectionProvider: () -> NestedScrollConnection,
|
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
|
@ -274,7 +271,6 @@ fun Lyrics(
|
||||||
text = lyrics,
|
text = lyrics,
|
||||||
style = typography.xs.center.medium.color(PureBlackColorPalette.text),
|
style = typography.xs.center.medium.color(PureBlackColorPalette.text),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.nestedScroll(remember { nestedScrollConnectionProvider() })
|
|
||||||
.verticalFadingEdge()
|
.verticalFadingEdge()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
|
@ -36,6 +36,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
@ -239,8 +240,8 @@ fun Player(
|
||||||
onShowLyrics = { isShowingLyrics = it },
|
onShowLyrics = { isShowingLyrics = it },
|
||||||
isShowingStatsForNerds = isShowingStatsForNerds,
|
isShowingStatsForNerds = isShowingStatsForNerds,
|
||||||
onShowStatsForNerds = { isShowingStatsForNerds = it },
|
onShowStatsForNerds = { isShowingStatsForNerds = it },
|
||||||
nestedScrollConnectionProvider = layoutState::nestedScrollConnection,
|
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.nestedScroll(layoutState.preUpPostDownNestedScrollConnection)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -140,9 +140,7 @@ fun Queue(
|
||||||
.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top).asPaddingValues(),
|
.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top).asPaddingValues(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.nestedScroll(remember {
|
.nestedScroll(layoutState.preUpPostDownNestedScrollConnection)
|
||||||
layoutState.nestedScrollConnection(reorderingState.lazyListState.firstVisibleItemIndex == 0 && reorderingState.lazyListState.firstVisibleItemScrollOffset == 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
|
|
|
@ -21,7 +21,6 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
@ -48,7 +47,6 @@ fun Thumbnail(
|
||||||
onShowLyrics: (Boolean) -> Unit,
|
onShowLyrics: (Boolean) -> Unit,
|
||||||
isShowingStatsForNerds: Boolean,
|
isShowingStatsForNerds: Boolean,
|
||||||
onShowStatsForNerds: (Boolean) -> Unit,
|
onShowStatsForNerds: (Boolean) -> Unit,
|
||||||
nestedScrollConnectionProvider: () -> NestedScrollConnection,
|
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
@ -145,7 +143,6 @@ fun Thumbnail(
|
||||||
size = thumbnailSizeDp,
|
size = thumbnailSizeDp,
|
||||||
mediaMetadataProvider = mediaItem::mediaMetadata,
|
mediaMetadataProvider = mediaItem::mediaMetadata,
|
||||||
durationProvider = player::getDuration,
|
durationProvider = player::getDuration,
|
||||||
nestedScrollConnectionProvider = nestedScrollConnectionProvider,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
StatsForNerds(
|
StatsForNerds(
|
||||||
|
|
Loading…
Reference in a new issue