voice: fix waveform display + text buffer, 75% width, play/pause state
- bridge: txt buffer 256→2048, limit 250→2000 (voice d-field can be ~900 bytes) - VoiceBubble: Column layout — play+waveform on top, duration|timestamp on bottom - Play/pause toggle with ⏸/▶ icons - Voice messages use 75% screen width max (300dp for others)
This commit is contained in:
1 parent
94a537ebdd
commit
491fa84db8
2 files changed
+60
-36
No files matched your search
+58
-34
@@ -37,6 +37,7 @@ fun MessageBubble(message: Message, onPlayVoice: (String) -> Unit = {}) {
|
||||
val textColor = if (message.isOutgoing) Color.White else Color.Black
|
||||
val alignment = if (message.isOutgoing) Arrangement.End else Arrangement.Start
|
||||
val minBubbleWidth = (LocalConfiguration.current.screenWidthDp / 3).dp
|
||||
val voiceMaxWidth = (LocalConfiguration.current.screenWidthDp * 0.75f).dp
|
||||
|
||||
val isVoice = message.contentType == "audio/opus"
|
||||
val isMedia = message.contentType.isNotEmpty() && !isVoice && message.contentType != "text/plain"
|
||||
@@ -47,7 +48,7 @@ fun MessageBubble(message: Message, onPlayVoice: (String) -> Unit = {}) {
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.widthIn(min = minBubbleWidth, max = 300.dp)
|
||||
.widthIn(min = minBubbleWidth, max = if (isVoice) voiceMaxWidth else 300.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(bgColor)
|
||||
.padding(12.dp)
|
||||
@@ -65,40 +66,51 @@ fun MessageBubble(message: Message, onPlayVoice: (String) -> Unit = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(
|
||||
formatTime(message.ts),
|
||||
fontSize = 11.sp,
|
||||
color = textColor.copy(alpha = 0.5f),
|
||||
modifier = Modifier.align(Alignment.End)
|
||||
)
|
||||
if (!isVoice) {
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(
|
||||
formatTime(message.ts),
|
||||
fontSize = 11.sp,
|
||||
color = textColor.copy(alpha = 0.5f),
|
||||
modifier = Modifier.align(Alignment.End)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun VoiceBubble(message: Message, textColor: Color, onPlay: (String) -> Unit) {
|
||||
val durationSec = message.voiceDurationMs / 1000f
|
||||
val wf = message.waveform
|
||||
val wfCount = wf.size
|
||||
val durMs = message.voiceDurationMs
|
||||
val durSec = durMs / 1000f
|
||||
var playing by remember { mutableStateOf(false) }
|
||||
var elapsedMs by remember { mutableStateOf(0) }
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
/* Play button */
|
||||
Box(
|
||||
modifier = Modifier.size(32.dp).background(Color(0xFF3390EC), CircleShape)
|
||||
.clickable { if (message.filePath.isNotEmpty()) onPlay(message.filePath) },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text("\u25B6", fontSize = 14.sp, color = Color.White)
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Column {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
/* Play/Pause button */
|
||||
Box(
|
||||
modifier = Modifier.size(32.dp).background(Color(0xFF3390EC), CircleShape)
|
||||
.clickable {
|
||||
if (!playing && message.filePath.isNotEmpty()) {
|
||||
playing = true; elapsedMs = 0; onPlay(message.filePath)
|
||||
} else {
|
||||
playing = false
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(if (playing) "\u23F8" else "\u25B6", fontSize = 14.sp, color = Color.White)
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
|
||||
/* Waveform */
|
||||
if (wfCount >= 2) {
|
||||
Box(modifier = Modifier.weight(1f).height(32.dp)) {
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
/* Waveform */
|
||||
if (wfCount >= 2) {
|
||||
Canvas(modifier = Modifier.weight(1f).height(28.dp)) {
|
||||
val barW = size.width / wfCount
|
||||
val ph = size.height.toFloat()
|
||||
val ph = size.height
|
||||
for (i in 0 until wfCount) {
|
||||
val v = wf[i]
|
||||
val db = if (v > 0.00001f) 20f * kotlin.math.log10(v.toDouble()).toFloat() else -100f
|
||||
@@ -107,23 +119,35 @@ private fun VoiceBubble(message: Message, textColor: Color, onPlay: (String) ->
|
||||
val x = i.toFloat() * barW
|
||||
val y = ph - barH
|
||||
drawRect(
|
||||
color = Color(0xFF3390EC).copy(alpha = 0.5f),
|
||||
color = Color(0xFF3390EC).copy(alpha = 0.35f),
|
||||
topLeft = Offset(x, y),
|
||||
size = Size(barW * 0.8f, barH)
|
||||
size = Size(barW * 0.75f, barH)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(4.dp))
|
||||
}
|
||||
|
||||
/* Duration */
|
||||
Text(
|
||||
formatDurationMs(message.voiceDurationMs),
|
||||
fontSize = 13.sp,
|
||||
color = textColor.copy(alpha = 0.7f),
|
||||
modifier = Modifier.padding(start = 4.dp)
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
|
||||
/* Duration + timestamp */
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
Text(
|
||||
formatDurationMs(durMs),
|
||||
fontSize = 12.sp,
|
||||
color = textColor.copy(alpha = 0.55f)
|
||||
)
|
||||
Text(
|
||||
formatTime(message.ts),
|
||||
fontSize = 11.sp,
|
||||
color = textColor.copy(alpha = 0.4f),
|
||||
modifier = Modifier.align(Alignment.CenterVertically).padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user