Fix buggy BottomSheet nested scroll connection

Thanks, Albert Chang!
This commit is contained in:
vfsfitvnm 2022-10-08 18:13:11 +02:00
parent 23dcce88ab
commit ae6babb452
5 changed files with 58 additions and 78 deletions

View file

@ -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,42 +230,28 @@ 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() available
} else if (velocity < -250) {
collapse()
} else { } else {
val l0 = dismissedBound Velocity.Zero
val l1 = (collapsedBound - dismissedBound) / 2
val l2 = (expandedBound - collapsedBound) / 2
val l3 = expandedBound
when (value) {
in l0..l1 -> collapse()
in l1..l2 -> collapse()
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
}
} }
} }
} }

View file

@ -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()

View file

@ -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)
) )
} }

View file

@ -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(

View file

@ -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(