voice note player ui (#680)

This commit is contained in:
Clare Kinery
2026-03-06 13:07:45 +01:00
committed by GitHub
parent 632ee884e9
commit bc8909bab0
3 changed files with 38 additions and 13 deletions
@@ -1,15 +1,16 @@
package com.bitchat.android.ui.media
import android.media.MediaPlayer
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.border
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Pause
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
@@ -33,6 +34,8 @@ fun VoiceNotePlayer(
var durationMs by remember { mutableStateOf(0) }
val player = remember { MediaPlayer() }
val borderOpacity = if (isSystemInDarkTheme()) 0.3f else 0.2f
// Seek function - position is a fraction from 0.0 to 1.0
val seekTo: (Float) -> Unit = { position ->
if (isPrepared && durationMs > 0) {
@@ -86,20 +89,34 @@ fun VoiceNotePlayer(
DisposableEffect(Unit) { onDispose { try { player.release() } catch (_: Exception) {} } }
Row(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth()
.border(
color = MaterialTheme.colorScheme.primary.copy(alpha = borderOpacity),
width = 1.0.dp,
shape = RoundedCornerShape(14.dp)
)
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
// Disable play/pause while showing send progress override (optional UX choice)
val controlsEnabled = isPrepared && !isError && progressOverride == null
FilledTonalIconButton(onClick = { if (controlsEnabled) isPlaying = !isPlaying }, enabled = controlsEnabled, modifier = Modifier.size(28.dp)) {
FilledTonalIconButton(
onClick = { if (controlsEnabled) isPlaying = !isPlaying },
enabled = controlsEnabled,
modifier = Modifier.size(28.dp),
colors = IconButtonDefaults.filledTonalIconButtonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
),
) {
Icon(
imageVector = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
contentDescription = if (isPlaying) "Pause" else "Play"
)
}
val progressBarColor = progressColor ?: MaterialTheme.colorScheme.primary
com.bitchat.android.ui.media.WaveformPreview(
WaveformPreview(
modifier = Modifier
.height(24.dp)
.weight(1f)
@@ -110,7 +127,12 @@ fun VoiceNotePlayer(
onSeek = seekTo
)
val durText = if (durationMs > 0) String.format("%02d:%02d", (durationMs / 1000) / 60, (durationMs / 1000) % 60) else "--:--"
Text(text = durText, fontFamily = FontFamily.Monospace, fontSize = 12.sp)
Text(
color = MaterialTheme.colorScheme.tertiary,
text = durText,
fontFamily = FontFamily.Monospace,
fontSize = 12.sp,
)
}
}
@@ -11,6 +11,7 @@ import androidx.compose.runtime.withFrameNanos
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
@@ -74,10 +75,10 @@ fun WaveformPreview(
modifier = modifier,
samples = stateSamples,
fillProgress = if (stateSamples.isEmpty()) 0f else progress,
baseColor = Color(0x2200FF7F),
baseColor = Color.Gray.copy(alpha = 0.35f),
fillColor = when {
sendProgress != null -> Color(0xFF1E88E5) // blue while sending
else -> Color(0xFF00C851) // green during playback
else -> MaterialTheme.colorScheme.primary // green during playback
},
onSeek = onSeek
)
@@ -27,7 +27,8 @@ private val DarkColorScheme = darkColorScheme(
surface = Color(0xFF111111), // Very dark gray
onSurface = Color(0xFF39FF14), // Green text
error = Color(0xFFFF5555), // Red for errors
onError = Color.Black
onError = Color.Black,
tertiary = Color(0x99EBEBF5),
)
private val LightColorScheme = lightColorScheme(
@@ -40,7 +41,8 @@ private val LightColorScheme = lightColorScheme(
surface = Color(0xFFF8F8F8), // Very light gray
onSurface = Color(0xFF008000), // Dark green text
error = Color(0xFFCC0000), // Dark red for errors
onError = Color.White
onError = Color.White,
tertiary = Color(0x993C3C43)
)
@Composable