Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.boundsInParent
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
Expand Down Expand Up @@ -102,7 +104,11 @@ private fun HomeScreen(
StickyHeader(
modifier = Modifier
.padding(top = 14.dp)
.height(collapsibleHeaderState.stickyHeaderHeightDp),
.height(collapsibleHeaderState.initialStickyHeaderHeightDp)
.onGloballyPositioned { coordinates ->
collapsibleHeaderState.stickyHeaderActualBottomPx =
coordinates.boundsInParent().bottom
},
onHelpClick = onHelpClick,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,23 @@ import androidx.compose.ui.unit.dp
@Stable
internal class CollapsibleHeaderState(
private val density: Density,
val stickyHeaderHeightDp: Dp,
val initialStickyHeaderHeightDp: Dp,
val expandedHeaderHeightDp: Dp,
) {
private val stickyHeaderHeightPx: Float = with(density) { stickyHeaderHeightDp.toPx() }

private val expandedHeaderHeightPx: Float = with(density) { expandedHeaderHeightDp.toPx() }
private val initialStickyHeaderHeightPx: Float = with(density) { initialStickyHeaderHeightDp.toPx() }

var stickyHeaderActualBottomPx by mutableFloatStateOf(initialStickyHeaderHeightPx)
internal set

val collapsedContentOffsetDp: Dp = with(density) { stickyHeaderHeightPx.toDp() + 18.dp }
val collapsedContentOffsetDp: Dp
get() = with(density) { stickyHeaderActualBottomPx.toDp() }

var currentHeightPx by mutableFloatStateOf(expandedHeaderHeightPx)
private set

val expansionProgress: Float
get() = if (expandedHeaderHeightPx > 0f) (currentHeightPx / expandedHeaderHeightPx).coerceIn(0f, 1f) else 1f
get() = (currentHeightPx / expandedHeaderHeightPx).coerceIn(0f, 1f)

val nestedScrollConnection = object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource) =
Expand All @@ -46,33 +49,22 @@ internal class CollapsibleHeaderState(
if (available.y > 0) consumeDelta(available.y) else Offset.Zero

override suspend fun onPreFling(available: Velocity): Velocity {
if (currentHeightPx <= 0f || currentHeightPx >= expandedHeaderHeightPx) return Velocity.Zero
val isFullyCollapsed = currentHeightPx < 1f
val isFullyExpanded = currentHeightPx > expandedHeaderHeightPx - 1f

val collapse = 0f
val expand = expandedHeaderHeightPx
if (isFullyCollapsed || isFullyExpanded) return Velocity.Zero

val target = when {
available.y < -50f -> collapse
available.y > 50f -> expand
else -> if (currentHeightPx - collapse < expand - currentHeightPx) collapse else expand
available.y < -50f -> 0f
available.y > 50f -> expandedHeaderHeightPx
else -> if (currentHeightPx < expandedHeaderHeightPx / 2) 0f else expandedHeaderHeightPx
}

snapTo(targetHeight = target, velocity = available.y)

return Velocity(0f, available.y)
return Velocity.Zero
}

override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
if (available.y > 0 && currentHeightPx < expandedHeaderHeightPx) {
snapTo(targetHeight = expandedHeaderHeightPx, velocity = available.y)
return Velocity(0f, available.y)
}

if (available.y < 0 && currentHeightPx > 0f) {
snapTo(targetHeight = 0f, velocity = available.y)
return Velocity(0f, available.y)
}

return Velocity.Zero
}
}
Expand All @@ -94,27 +86,25 @@ internal class CollapsibleHeaderState(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMediumLow,
),
) { value, _ ->
currentHeightPx = value
}
) { value, _ -> currentHeightPx = value.coerceIn(0f, expandedHeaderHeightPx) }
}
}

@Composable
internal fun rememberCollapsibleHeaderState(
density: Density = LocalDensity.current,
windowInfo: WindowInfo = LocalWindowInfo.current,
stickyHeaderHeight: Dp = 48.dp,
initialStickyHeaderHeightDp: Dp = 48.dp,
minExpandedHeaderHeight: Dp = 146.dp,
): CollapsibleHeaderState {
val containerSize = windowInfo.containerSize
return remember(density, containerSize, minExpandedHeaderHeight, stickyHeaderHeight) {
return remember(density, containerSize, minExpandedHeaderHeight, initialStickyHeaderHeightDp) {
val screenHeightDp = with(density) { containerSize.height.toDp() }
val expandedHeaderHeightDp = (screenHeightDp * 0.18f).coerceAtLeast(minExpandedHeaderHeight)

CollapsibleHeaderState(
density = density,
stickyHeaderHeightDp = stickyHeaderHeight,
initialStickyHeaderHeightDp = initialStickyHeaderHeightDp,
expandedHeaderHeightDp = expandedHeaderHeightDp,
)
}
Expand Down
Loading