wear: shrink header to dense form on scroll-up instead of hiding it - overlay-only size animation, list geometry untouched

This commit is contained in:
callebtc 2026-07-29 01:08:11 +02:00
parent 92f60bd02f
commit 2e8f6ffc35
3 changed files with 46 additions and 27 deletions

View File

@ -58,7 +58,7 @@ fun ChatScaffold(
emptyText: String,
voice: VoiceNoteController,
onOpenImage: (String) -> Unit,
header: @Composable () -> Unit,
header: @Composable (expanded: Boolean) -> Unit,
actionBar: @Composable () -> Unit
) {
val haptics = LocalHapticFeedback.current
@ -133,7 +133,7 @@ private fun ChatBody(
onOpenImage: (String) -> Unit,
columnState: TransformingLazyColumnState,
controlsVisible: Boolean,
header: @Composable () -> Unit,
header: @Composable (expanded: Boolean) -> Unit,
actionBar: @Composable () -> Unit,
modifier: Modifier = Modifier
) {
@ -181,19 +181,10 @@ private fun ChatBody(
}
}
AnimatedVisibility(
visible = controlsVisible,
modifier = Modifier.align(Alignment.TopCenter),
enter = slideInVertically(
initialOffsetY = { -it },
animationSpec = tween(BitchatMotion.STANDARD_MS)
) + fadeIn(animationSpec = tween(BitchatMotion.STANDARD_MS)),
exit = slideOutVertically(
targetOffsetY = { -it },
animationSpec = tween(BitchatMotion.STANDARD_MS)
) + fadeOut(animationSpec = tween(BitchatMotion.STANDARD_MS))
) {
header()
// The header stays put and shrinks to its dense form instead of disappearing;
// as an overlay its size animation never touches the list's scroll geometry.
Box(modifier = Modifier.align(Alignment.TopCenter)) {
header(controlsVisible)
}
AnimatedVisibility(

View File

@ -75,10 +75,11 @@ fun ChatScreen(onOpenPeople: () -> Unit, onOpenTextInput: () -> Unit) {
emptyText = "no messages yet\nsay hi to the mesh",
voice = voice,
onOpenImage = { viewerPath = it },
header = {
header = { expanded ->
ChatHeader(
peerCount = peers.size,
unreadDms = unreadDms.values.sum(),
expanded = expanded,
onOpenPeople = onOpenPeople
)
},
@ -96,16 +97,29 @@ fun ChatScreen(onOpenPeople: () -> Unit, onOpenTextInput: () -> Unit) {
private fun ChatHeader(
peerCount: Int,
unreadDms: Int,
expanded: Boolean,
onOpenPeople: () -> Unit
) {
// Floating title row; the scaffold slides/fades it as a unit when the user scrolls.
val iconSize = 16.dp
val titleSize = 15.dp
// Floating title row: full-size at the newest messages, shrinks to its dense form
// while scrolling up into history. Rendered as an overlay, so the animation only
// relayouts this row, never the message list.
val spec = androidx.compose.animation.core.tween<androidx.compose.ui.unit.Dp>(
BitchatMotion.STANDARD_MS
)
val iconSize by androidx.compose.animation.core.animateDpAsState(
targetValue = if (expanded) 16.dp else 11.dp, animationSpec = spec, label = "hdrIcon"
)
val titleSize by androidx.compose.animation.core.animateDpAsState(
targetValue = if (expanded) 15.dp else 11.dp, animationSpec = spec, label = "hdrTitle"
)
val vPadding by androidx.compose.animation.core.animateDpAsState(
targetValue = if (expanded) 6.dp else 1.dp, animationSpec = spec, label = "hdrPad"
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 6.dp),
.padding(horizontal = 8.dp, vertical = vPadding),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {

View File

@ -78,11 +78,12 @@ fun DmScreen(peerID: String, onOpenTextInput: () -> Unit) {
else "setting up encryption…",
voice = voice,
onOpenImage = { viewerPath = it },
header = {
header = { expanded ->
DmHeader(
nickname = nickname,
peerID = peerID,
sessionEstablished = sessionEstablished
sessionEstablished = sessionEstablished,
expanded = expanded
)
},
actionBar = {
@ -99,17 +100,30 @@ fun DmScreen(peerID: String, onOpenTextInput: () -> Unit) {
private fun DmHeader(
nickname: String,
peerID: String,
sessionEstablished: Boolean
sessionEstablished: Boolean,
expanded: Boolean
) {
val palette = LocalBitchatPalette.current
// Floating title row; the scaffold slides/fades it as a unit when the user scrolls.
val headerIconSize = 16.dp
val headerTitleSize = 15.dp
// Floating title row: full-size at the newest messages, shrinks to its dense form
// while scrolling up into history. Rendered as an overlay, so the animation only
// relayouts this row, never the message list.
val spec = androidx.compose.animation.core.tween<androidx.compose.ui.unit.Dp>(
BitchatMotion.STANDARD_MS
)
val headerIconSize by androidx.compose.animation.core.animateDpAsState(
targetValue = if (expanded) 16.dp else 11.dp, animationSpec = spec, label = "dmHdrIcon"
)
val headerTitleSize by androidx.compose.animation.core.animateDpAsState(
targetValue = if (expanded) 15.dp else 11.dp, animationSpec = spec, label = "dmHdrTitle"
)
val headerVPadding by androidx.compose.animation.core.animateDpAsState(
targetValue = if (expanded) 6.dp else 1.dp, animationSpec = spec, label = "dmHdrPad"
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 6.dp),
.padding(horizontal = 8.dp, vertical = headerVPadding),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {