mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-29 07:16:08 +00:00
feat(chat): align self media rows with bubbles chat UI mode
In bubbles mode, self-authored image, voice-note, and file rows now align to the end side with the same tail-corner cue as text bubbles, instead of sitting on the received side where they read as someone else's content. Grouped self voice notes also keep the run on the correct side. VoiceNotePlayer gains an optional modifier so the player can hug the end side at a capped width. Received media and matrix mode are unchanged.
This commit is contained in:
parent
94d5fa3084
commit
7569fba7b4
@ -506,6 +506,7 @@ fun MessageItem(
|
||||
colorScheme = colorScheme,
|
||||
timeFormatter = timeFormatter,
|
||||
showSender = showSender,
|
||||
bubbles = bubbles,
|
||||
onNicknameClick = onNicknameClick,
|
||||
onMessageLongPress = onMessageLongPress,
|
||||
onCancelTransfer = onCancelTransfer,
|
||||
@ -524,6 +525,7 @@ fun MessageItem(
|
||||
colorScheme = colorScheme,
|
||||
timeFormatter = timeFormatter,
|
||||
showSender = showSender,
|
||||
bubbles = bubbles,
|
||||
onNicknameClick = onNicknameClick,
|
||||
onMessageLongPress = onMessageLongPress,
|
||||
onCancelTransfer = onCancelTransfer,
|
||||
@ -544,7 +546,15 @@ fun MessageItem(
|
||||
}
|
||||
else -> null to null
|
||||
}
|
||||
Column(modifier = modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
// Bubble mode aligns self-authored file rows to the end side, mirroring text bubbles.
|
||||
horizontalAlignment = if (bubbles && message.isFromSelf(currentUserNickname, meshService.myPeerID)) {
|
||||
Alignment.End
|
||||
} else {
|
||||
Alignment.Start
|
||||
},
|
||||
) {
|
||||
// Header: nickname + timestamp line above the file, identical styling to text messages
|
||||
val headerText = formatMessageHeaderAnnotatedString(
|
||||
message = message,
|
||||
@ -590,7 +600,14 @@ fun MessageItem(
|
||||
null
|
||||
}
|
||||
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Start) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = if (bubbles && message.isFromSelf(currentUserNickname, meshService.myPeerID)) {
|
||||
Arrangement.End
|
||||
} else {
|
||||
Arrangement.Start
|
||||
}
|
||||
) {
|
||||
Box {
|
||||
if (packet != null) {
|
||||
if (overrideProgress != null) {
|
||||
|
||||
@ -22,6 +22,7 @@ import com.bitchat.android.mesh.MeshService
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import com.bitchat.android.ui.theme.LocalBitchatPalette
|
||||
import com.bitchat.android.ui.isFromSelf
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
@Composable
|
||||
@ -35,10 +36,13 @@ fun AudioMessageItem(
|
||||
onMessageLongPress: ((BitchatMessage) -> Unit)?,
|
||||
onCancelTransfer: ((BitchatMessage) -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
showSender: Boolean = true
|
||||
showSender: Boolean = true,
|
||||
bubbles: Boolean = false
|
||||
) {
|
||||
val palette = LocalBitchatPalette.current
|
||||
val path = message.content.trim()
|
||||
// Bubble mode aligns self-authored voice notes to the end side, mirroring text bubbles.
|
||||
val isSelfInBubbles = bubbles && message.isFromSelf(currentUserNickname, meshService.myPeerID)
|
||||
// Derive sending progress if applicable
|
||||
val (overrideProgress, overrideColor) = when (val st = message.deliveryStatus) {
|
||||
is com.bitchat.android.model.DeliveryStatus.PartiallyDelivered -> {
|
||||
@ -48,7 +52,10 @@ fun AudioMessageItem(
|
||||
}
|
||||
else -> null to null
|
||||
}
|
||||
Column(modifier = modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalAlignment = if (isSelfInBubbles) Alignment.End else Alignment.Start,
|
||||
) {
|
||||
// Header: nickname + timestamp line above the audio note, identical styling to text messages
|
||||
val headerText = com.bitchat.android.ui.formatMessageHeaderAnnotatedString(
|
||||
message = message,
|
||||
@ -81,7 +88,10 @@ fun AudioMessageItem(
|
||||
VoiceNotePlayer(
|
||||
path = path,
|
||||
progressOverride = overrideProgress,
|
||||
progressColor = overrideColor
|
||||
progressColor = overrideColor,
|
||||
// Self voice notes hug the end side like other self content instead of
|
||||
// spanning the full row.
|
||||
modifier = if (isSelfInBubbles) Modifier.widthIn(max = 300.dp) else Modifier
|
||||
)
|
||||
val showCancel = message.sender == currentUserNickname && (message.deliveryStatus is com.bitchat.android.model.DeliveryStatus.PartiallyDelivered)
|
||||
if (showCancel) {
|
||||
|
||||
@ -28,6 +28,7 @@ import com.bitchat.android.model.BitchatMessageType
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import com.bitchat.android.core.ui.component.text.AnnotatedClickableText
|
||||
import com.bitchat.android.ui.theme.LocalBitchatPalette
|
||||
import com.bitchat.android.ui.isFromSelf
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
@Composable
|
||||
@ -43,11 +44,23 @@ fun ImageMessageItem(
|
||||
onCancelTransfer: ((BitchatMessage) -> Unit)?,
|
||||
onImageClick: ((String, List<String>, Int) -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
showSender: Boolean = true
|
||||
showSender: Boolean = true,
|
||||
bubbles: Boolean = false
|
||||
) {
|
||||
val palette = LocalBitchatPalette.current
|
||||
val path = message.content.trim()
|
||||
Column(modifier = modifier.fillMaxWidth()) {
|
||||
// Bubble mode aligns self-authored media to the end side, mirroring text bubbles; the
|
||||
// corner on the speaker's side is tightened into the same subtle tail.
|
||||
val isSelfInBubbles = bubbles && message.isFromSelf(currentUserNickname, meshService.myPeerID)
|
||||
val imageShape = when {
|
||||
!bubbles -> androidx.compose.foundation.shape.RoundedCornerShape(10.dp)
|
||||
isSelfInBubbles -> androidx.compose.foundation.shape.RoundedCornerShape(10.dp, 10.dp, 3.dp, 10.dp)
|
||||
else -> androidx.compose.foundation.shape.RoundedCornerShape(10.dp, 10.dp, 10.dp, 3.dp)
|
||||
}
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalAlignment = if (isSelfInBubbles) Alignment.End else Alignment.Start,
|
||||
) {
|
||||
val headerText = com.bitchat.android.ui.formatMessageHeaderAnnotatedString(
|
||||
message = message,
|
||||
currentUserNickname = currentUserNickname,
|
||||
@ -91,7 +104,10 @@ fun ImageMessageItem(
|
||||
is com.bitchat.android.model.DeliveryStatus.PartiallyDelivered -> if (st.total > 0) st.reached.toFloat() / st.total.toFloat() else 0f
|
||||
else -> null
|
||||
}
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Start) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = if (isSelfInBubbles) Arrangement.End else Arrangement.Start
|
||||
) {
|
||||
Box {
|
||||
if (progressFraction != null && progressFraction < 1f && message.sender == currentUserNickname) {
|
||||
// Cyberpunk block-reveal while sending
|
||||
@ -103,7 +119,7 @@ fun ImageMessageItem(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 300.dp)
|
||||
.aspectRatio(aspect)
|
||||
.clip(androidx.compose.foundation.shape.RoundedCornerShape(10.dp))
|
||||
.clip(imageShape)
|
||||
.clickable {
|
||||
val currentIndex = imagePaths.indexOf(path)
|
||||
onImageClick?.invoke(path, imagePaths, currentIndex)
|
||||
@ -117,7 +133,7 @@ fun ImageMessageItem(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 300.dp)
|
||||
.aspectRatio(aspect)
|
||||
.clip(androidx.compose.foundation.shape.RoundedCornerShape(10.dp))
|
||||
.clip(imageShape)
|
||||
.clickable {
|
||||
val currentIndex = imagePaths.indexOf(path)
|
||||
onImageClick?.invoke(path, imagePaths, currentIndex)
|
||||
|
||||
@ -23,6 +23,7 @@ import androidx.compose.ui.unit.sp
|
||||
@Composable
|
||||
fun VoiceNotePlayer(
|
||||
path: String,
|
||||
modifier: Modifier = Modifier,
|
||||
progressOverride: Float? = null,
|
||||
progressColor: Color? = null
|
||||
) {
|
||||
@ -86,7 +87,7 @@ fun VoiceNotePlayer(
|
||||
DisposableEffect(Unit) { onDispose { try { player.release() } catch (_: Exception) {} } }
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user