mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-24 22:25:19 +00:00
voice note player ui (#680)
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user