feat: refine chat bubble sizing for tablets and media
Some checks failed
Android CI / android (push) Has started running
Android Release / release (push) Has been cancelled
CI / test (push) Has been cancelled

- make text bubbles size closer to their content instead of stretching too wide

- reduce media and circle dimensions in chat messages, especially on tablets

- hide duplicate private info card in tablet split view to avoid repeated header UI
This commit is contained in:
2026-04-05 20:42:30 +03:00
parent 64dfd18ee1
commit cd161b099b

View File

@@ -32,6 +32,7 @@ import androidx.compose.foundation.gestures.calculateZoom
import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -650,6 +651,7 @@ private fun ChatScreen(
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
val allViewerMediaItems = remember(state.messages) { buildChatViewerMediaItems(state.messages) }
val isTabletLayout = LocalConfiguration.current.screenWidthDp >= 840
val isChannelChat = state.chatType.equals("channel", ignoreCase = true)
val isPrivateChat = state.chatType.equals("private", ignoreCase = true)
val showUnknownPrivateChatBanner = isPrivateChat &&
@@ -657,7 +659,7 @@ private fun ChatScreen(
state.isCounterpartRelationshipResolved &&
!state.isCounterpartContact &&
!state.isCounterpartBlocked
val showPrivateInfoCard = isPrivateChat && state.counterpartUserId != null
val showPrivateInfoCard = isPrivateChat && state.counterpartUserId != null && !isTabletLayout
val canShowMembersTab = state.chatType.equals("group", ignoreCase = true) ||
(state.chatType.equals("channel", ignoreCase = true) && state.canManageMembers)
val timelineItems = remember(state.messages, context) { buildChatTimelineItems(state.messages, context) }
@@ -721,7 +723,6 @@ private fun ChatScreen(
val forwardSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val chatInfoSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val attachmentSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val isTabletLayout = LocalConfiguration.current.screenWidthDp >= 840
val adaptiveHorizontalPadding = if (isTabletLayout) 72.dp else 0.dp
val chatInfoEntries = remember(state.messages, context) { buildChatInfoEntries(state.messages, context) }
val giphyApiKey = remember { BuildConfig.GIPHY_API_KEY.trim() }
@@ -4069,6 +4070,16 @@ private fun MessageBubble(
message.type.contains("video_note", ignoreCase = true)
)
val renderWithoutBubble = hasSingleStickerAttachment || hasSingleCircleAttachment
val configuration = LocalConfiguration.current
val isTabletLayout = configuration.screenWidthDp >= 840
val bubbleMaxWidth = when {
hasSingleCircleAttachment -> if (isTabletLayout) 220.dp else 190.dp
renderAsChannelPost -> if (isTabletLayout) 480.dp else 360.dp
else -> if (isTabletLayout) 360.dp else 300.dp
}
val singleMediaMaxWidth = if (isTabletLayout) 320.dp else 240.dp
val singleMediaMaxHeight = if (isTabletLayout) 260.dp else 220.dp
val gridMediaTileHeight = if (isTabletLayout) 96.dp else 112.dp
Row(
modifier = Modifier.fillMaxWidth(),
@@ -4084,16 +4095,10 @@ private fun MessageBubble(
}
Column(
modifier = Modifier
.fillMaxWidth(
if (hasSingleStickerAttachment || hasSingleCircleAttachment) {
if (renderAsChannelPost) 0.58f else 0.52f
} else if (renderAsChannelPost) {
0.94f
} else {
0.8f
},
.widthIn(
min = if (renderWithoutBubble) 96.dp else if (renderAsChannelPost) 120.dp else 82.dp,
max = bubbleMaxWidth,
)
.widthIn(min = if (renderWithoutBubble) 96.dp else if (renderAsChannelPost) 120.dp else 82.dp)
.then(
if (renderWithoutBubble) {
Modifier
@@ -4233,8 +4238,8 @@ private fun MessageBubble(
.size(176.dp)
} else {
Modifier
.fillMaxWidth()
.height(188.dp)
.widthIn(max = singleMediaMaxWidth)
.heightIn(max = singleMediaMaxHeight)
},
)
.clip(RoundedCornerShape(12.dp))
@@ -4338,8 +4343,8 @@ private fun MessageBubble(
.size(176.dp)
} else {
Modifier
.fillMaxWidth()
.height(188.dp)
.widthIn(max = singleMediaMaxWidth)
.heightIn(max = singleMediaMaxHeight)
},
)
.clip(RoundedCornerShape(12.dp))
@@ -4383,7 +4388,7 @@ private fun MessageBubble(
Box(
modifier = Modifier
.weight(1f)
.height(112.dp)
.height(gridMediaTileHeight)
.clip(RoundedCornerShape(10.dp))
.let { base ->
if (openable) base.clickable { onAttachmentImageClick(image.fileUrl) } else base
@@ -4604,6 +4609,8 @@ private fun CircleVideoAttachmentPlayer(
onForceToggleAudioSourceHandled: (String) -> Unit,
onForceCycleSpeedAudioSourceHandled: (String) -> Unit,
) {
val isTabletLayout = LocalConfiguration.current.screenWidthDp >= 840
val circleSize = if (isTabletLayout) 190.dp else 150.dp
val playerState = rememberManagedMediaPlayerState(
url = url,
speedOptions = listOf(1f),
@@ -4648,8 +4655,7 @@ private fun CircleVideoAttachmentPlayer(
val progressActiveColor = MaterialTheme.colorScheme.primary
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
.size(circleSize)
.padding(horizontal = 6.dp, vertical = 2.dp),
contentAlignment = Alignment.Center,
) {