From 2e8f6ffc35c013e1bf2b5f5f4d595661895c9c1b Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Jul 2026 01:08:11 +0200 Subject: [PATCH] wear: shrink header to dense form on scroll-up instead of hiding it - overlay-only size animation, list geometry untouched --- .../java/com/bitchat/watch/ui/ChatScaffold.kt | 21 ++++---------- .../java/com/bitchat/watch/ui/ChatScreen.kt | 24 ++++++++++++---- .../java/com/bitchat/watch/ui/DmScreen.kt | 28 ++++++++++++++----- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/wear/src/main/java/com/bitchat/watch/ui/ChatScaffold.kt b/wear/src/main/java/com/bitchat/watch/ui/ChatScaffold.kt index cd1e9c1f..52b8e1fc 100644 --- a/wear/src/main/java/com/bitchat/watch/ui/ChatScaffold.kt +++ b/wear/src/main/java/com/bitchat/watch/ui/ChatScaffold.kt @@ -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( diff --git a/wear/src/main/java/com/bitchat/watch/ui/ChatScreen.kt b/wear/src/main/java/com/bitchat/watch/ui/ChatScreen.kt index e983e34a..4ee26fff 100644 --- a/wear/src/main/java/com/bitchat/watch/ui/ChatScreen.kt +++ b/wear/src/main/java/com/bitchat/watch/ui/ChatScreen.kt @@ -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( + 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 ) { diff --git a/wear/src/main/java/com/bitchat/watch/ui/DmScreen.kt b/wear/src/main/java/com/bitchat/watch/ui/DmScreen.kt index f663205d..f6cd7154 100644 --- a/wear/src/main/java/com/bitchat/watch/ui/DmScreen.kt +++ b/wear/src/main/java/com/bitchat/watch/ui/DmScreen.kt @@ -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( + 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 ) {