From 5852fca9b5a12837ec2ac8a4b4b5a1f5e31cac7c Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:38:26 +0200 Subject: [PATCH] Fix Wear content clipping on watch shapes --- wear/build.gradle.kts | 4 +- .../java/com/bitchat/watch/ui/ChatScaffold.kt | 11 ++-- .../com/bitchat/watch/ui/PeerDebugScreen.kt | 5 +- .../java/com/bitchat/watch/ui/PeopleScreen.kt | 5 +- .../com/bitchat/watch/ui/UserDetailScreen.kt | 11 ++-- .../watch/ui/VerificationCodeScreen.kt | 11 ++-- .../bitchat/watch/ui/WearContentPadding.kt | 52 +++++++++++++++++++ .../watch/ui/WearContentPaddingTest.kt | 40 ++++++++++++++ 8 files changed, 124 insertions(+), 15 deletions(-) create mode 100644 wear/src/main/java/com/bitchat/watch/ui/WearContentPadding.kt create mode 100644 wear/src/test/java/com/bitchat/watch/ui/WearContentPaddingTest.kt diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts index e177c3e2..9bd87331 100644 --- a/wear/build.gradle.kts +++ b/wear/build.gradle.kts @@ -19,8 +19,8 @@ android { targetSdk = libs.versions.targetSdk.get().toInt() // Wear releases use a separate high range because Play requires every artifact in // one application ID to have a unique version code across all form factors. - versionCode = 1_000_000_002 - versionName = "0.1.1" + versionCode = 1_000_000_003 + versionName = "0.1.2" vectorDrawables { useSupportLibrary = true 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 0bcdfed3..9f7b54f4 100644 --- a/wear/src/main/java/com/bitchat/watch/ui/ChatScaffold.kt +++ b/wear/src/main/java/com/bitchat/watch/ui/ChatScaffold.kt @@ -8,7 +8,6 @@ import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -28,6 +27,7 @@ import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.changedToUp import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.wear.compose.foundation.lazy.TransformingLazyColumn @@ -256,7 +256,8 @@ private fun ChatBody( } } ) { - ScreenScaffold(scrollState = columnState) { + ScreenScaffold(scrollState = columnState) { scaffoldPadding -> + val layoutDirection = LocalLayoutDirection.current TransformingLazyColumn( state = columnState, modifier = Modifier.fillMaxSize(), @@ -265,7 +266,11 @@ private fun ChatBody( // The padding reserves permanent room for the floating header and action // bar; being constant, it never disturbs an in-flight scroll gesture. verticalArrangement = Arrangement.Bottom, - contentPadding = PaddingValues(top = 30.dp, bottom = 64.dp) + contentPadding = scaffoldPadding.withMinimumVerticalPadding( + layoutDirection = layoutDirection, + top = 30.dp, + bottom = 64.dp + ) ) { if (messages.isEmpty()) { item { diff --git a/wear/src/main/java/com/bitchat/watch/ui/PeerDebugScreen.kt b/wear/src/main/java/com/bitchat/watch/ui/PeerDebugScreen.kt index 1994f976..c91a220e 100644 --- a/wear/src/main/java/com/bitchat/watch/ui/PeerDebugScreen.kt +++ b/wear/src/main/java/com/bitchat/watch/ui/PeerDebugScreen.kt @@ -46,10 +46,11 @@ fun PeerDebugScreen() { val rssi = mesh?.getPeerRSSI() ?: emptyMap() val identityRevision by WearPeerIdentityState.revision.collectAsState() - ScreenScaffold(scrollState = listState) { + ScreenScaffold(scrollState = listState) { contentPadding -> ScalingLazyColumn( state = listState, - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), + contentPadding = contentPadding ) { item { ListHeader { diff --git a/wear/src/main/java/com/bitchat/watch/ui/PeopleScreen.kt b/wear/src/main/java/com/bitchat/watch/ui/PeopleScreen.kt index 3cb86df3..b3f912f9 100644 --- a/wear/src/main/java/com/bitchat/watch/ui/PeopleScreen.kt +++ b/wear/src/main/java/com/bitchat/watch/ui/PeopleScreen.kt @@ -67,10 +67,11 @@ fun PeopleScreen(onOpenDm: (String) -> Unit, onEditNickname: () -> Unit) { ) } - ScreenScaffold(scrollState = listState) { + ScreenScaffold(scrollState = listState) { contentPadding -> ScalingLazyColumn( state = listState, - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), + contentPadding = contentPadding ) { item { ListHeader { diff --git a/wear/src/main/java/com/bitchat/watch/ui/UserDetailScreen.kt b/wear/src/main/java/com/bitchat/watch/ui/UserDetailScreen.kt index a36c7ec6..1c2a5616 100644 --- a/wear/src/main/java/com/bitchat/watch/ui/UserDetailScreen.kt +++ b/wear/src/main/java/com/bitchat/watch/ui/UserDetailScreen.kt @@ -1,7 +1,6 @@ package com.bitchat.watch.ui import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -15,6 +14,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -48,11 +48,16 @@ fun UserDetailScreen( val listState = rememberScalingLazyListState() val palette = LocalBitchatPalette.current - ScreenScaffold(scrollState = listState) { + ScreenScaffold(scrollState = listState) { scaffoldPadding -> + val layoutDirection = LocalLayoutDirection.current ScalingLazyColumn( state = listState, modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(horizontal = 10.dp, vertical = 8.dp) + contentPadding = scaffoldPadding.withAdditionalPadding( + layoutDirection = layoutDirection, + horizontal = 10.dp, + vertical = 8.dp + ) ) { item { ListHeader { diff --git a/wear/src/main/java/com/bitchat/watch/ui/VerificationCodeScreen.kt b/wear/src/main/java/com/bitchat/watch/ui/VerificationCodeScreen.kt index c4c4ef0f..67517c91 100644 --- a/wear/src/main/java/com/bitchat/watch/ui/VerificationCodeScreen.kt +++ b/wear/src/main/java/com/bitchat/watch/ui/VerificationCodeScreen.kt @@ -2,7 +2,6 @@ package com.bitchat.watch.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -14,6 +13,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -43,11 +43,16 @@ fun VerificationCodeScreen(peerID: String) { val listState = rememberScalingLazyListState() val palette = LocalBitchatPalette.current - ScreenScaffold(scrollState = listState) { + ScreenScaffold(scrollState = listState) { scaffoldPadding -> + val layoutDirection = LocalLayoutDirection.current ScalingLazyColumn( state = listState, modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(horizontal = 10.dp, vertical = 8.dp) + contentPadding = scaffoldPadding.withAdditionalPadding( + layoutDirection = layoutDirection, + horizontal = 10.dp, + vertical = 8.dp + ) ) { item { ListHeader { diff --git a/wear/src/main/java/com/bitchat/watch/ui/WearContentPadding.kt b/wear/src/main/java/com/bitchat/watch/ui/WearContentPadding.kt new file mode 100644 index 00000000..28425369 --- /dev/null +++ b/wear/src/main/java/com/bitchat/watch/ui/WearContentPadding.kt @@ -0,0 +1,52 @@ +package com.bitchat.watch.ui + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp + +/** + * Preserve the responsive, shape-aware padding supplied by Wear Material while allowing a + * screen to reserve additional room for its own content or floating controls. + */ +internal fun PaddingValues.withAdditionalPadding( + layoutDirection: LayoutDirection, + horizontal: Dp = 0.dp, + vertical: Dp = 0.dp +): PaddingValues { + val start = when (layoutDirection) { + LayoutDirection.Ltr -> calculateLeftPadding(layoutDirection) + LayoutDirection.Rtl -> calculateRightPadding(layoutDirection) + } + val end = when (layoutDirection) { + LayoutDirection.Ltr -> calculateRightPadding(layoutDirection) + LayoutDirection.Rtl -> calculateLeftPadding(layoutDirection) + } + return PaddingValues( + start = start + horizontal, + top = calculateTopPadding() + vertical, + end = end + horizontal, + bottom = calculateBottomPadding() + vertical + ) +} + +internal fun PaddingValues.withMinimumVerticalPadding( + layoutDirection: LayoutDirection, + top: Dp, + bottom: Dp +): PaddingValues { + val start = when (layoutDirection) { + LayoutDirection.Ltr -> calculateLeftPadding(layoutDirection) + LayoutDirection.Rtl -> calculateRightPadding(layoutDirection) + } + val end = when (layoutDirection) { + LayoutDirection.Ltr -> calculateRightPadding(layoutDirection) + LayoutDirection.Rtl -> calculateLeftPadding(layoutDirection) + } + return PaddingValues( + start = start, + top = maxOf(calculateTopPadding(), top), + end = end, + bottom = maxOf(calculateBottomPadding(), bottom) + ) +} diff --git a/wear/src/test/java/com/bitchat/watch/ui/WearContentPaddingTest.kt b/wear/src/test/java/com/bitchat/watch/ui/WearContentPaddingTest.kt new file mode 100644 index 00000000..2b16c876 --- /dev/null +++ b/wear/src/test/java/com/bitchat/watch/ui/WearContentPaddingTest.kt @@ -0,0 +1,40 @@ +package com.bitchat.watch.ui + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import org.junit.Assert.assertEquals +import org.junit.Test + +class WearContentPaddingTest { + + @Test + fun `additional padding preserves scaffold insets`() { + val resolved = PaddingValues(start = 10.dp, top = 18.dp, end = 12.dp, bottom = 20.dp) + .withAdditionalPadding( + layoutDirection = LayoutDirection.Ltr, + horizontal = 10.dp, + vertical = 8.dp + ) + + assertEquals(20.dp, resolved.calculateLeftPadding(LayoutDirection.Ltr)) + assertEquals(22.dp, resolved.calculateRightPadding(LayoutDirection.Ltr)) + assertEquals(26.dp, resolved.calculateTopPadding()) + assertEquals(28.dp, resolved.calculateBottomPadding()) + } + + @Test + fun `chat padding keeps responsive sides and overlay clearances`() { + val resolved = PaddingValues(start = 10.dp, top = 18.dp, end = 12.dp, bottom = 20.dp) + .withMinimumVerticalPadding( + layoutDirection = LayoutDirection.Ltr, + top = 30.dp, + bottom = 64.dp + ) + + assertEquals(10.dp, resolved.calculateLeftPadding(LayoutDirection.Ltr)) + assertEquals(12.dp, resolved.calculateRightPadding(LayoutDirection.Ltr)) + assertEquals(30.dp, resolved.calculateTopPadding()) + assertEquals(64.dp, resolved.calculateBottomPadding()) + } +}