fix(android): make top audio strip controls functional
Some checks failed
Android CI / android (push) Has started running
Android Release / release (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
Codex
2026-03-10 21:03:03 +03:00
parent 58b554731d
commit 27fba86915

View File

@@ -317,6 +317,8 @@ fun ChatScreen(
var dismissedPinnedMessageId by remember { mutableStateOf<Long?>(null) }
var topAudioStrip by remember { mutableStateOf<TopAudioStrip?>(null) }
var forceStopAudioSourceId by remember { mutableStateOf<String?>(null) }
var forceToggleAudioSourceId by remember { mutableStateOf<String?>(null) }
var forceCycleSpeedAudioSourceId by remember { mutableStateOf<String?>(null) }
var actionMenuMessage by remember { mutableStateOf<MessageItem?>(null) }
var pendingDeleteForAll by remember { mutableStateOf(false) }
var showDeleteDialog by remember { mutableStateOf(false) }
@@ -662,12 +664,15 @@ fun ChatScreen(
Surface(
shape = CircleShape,
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f),
modifier = Modifier.size(30.dp),
modifier = Modifier
.size(30.dp)
.clip(CircleShape)
.clickable { forceToggleAudioSourceId = strip.sourceId },
) {
Box(contentAlignment = Alignment.Center) {
Icon(
imageVector = Icons.Filled.PlayArrow,
contentDescription = "Play top audio",
imageVector = if (strip.isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
contentDescription = if (strip.isPlaying) "Pause top audio" else "Play top audio",
tint = MaterialTheme.colorScheme.primary,
)
}
@@ -688,9 +693,12 @@ fun ChatScreen(
Surface(
shape = RoundedCornerShape(8.dp),
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.65f),
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.clickable { forceCycleSpeedAudioSourceId = strip.sourceId },
) {
Text(
text = "1x",
text = strip.speedLabel,
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
style = MaterialTheme.typography.labelSmall,
)
@@ -698,6 +706,8 @@ fun ChatScreen(
IconButton(
onClick = {
forceStopAudioSourceId = strip.sourceId
forceToggleAudioSourceId = null
forceCycleSpeedAudioSourceId = null
topAudioStrip = null
},
) {
@@ -762,11 +772,23 @@ fun ChatScreen(
}
},
forceStopAudioSourceId = forceStopAudioSourceId,
forceToggleAudioSourceId = forceToggleAudioSourceId,
forceCycleSpeedAudioSourceId = forceCycleSpeedAudioSourceId,
onForceStopAudioSourceHandled = { sourceId ->
if (forceStopAudioSourceId == sourceId) {
forceStopAudioSourceId = null
}
},
onForceToggleAudioSourceHandled = { sourceId ->
if (forceToggleAudioSourceId == sourceId) {
forceToggleAudioSourceId = null
}
},
onForceCycleSpeedAudioSourceHandled = { sourceId ->
if (forceCycleSpeedAudioSourceId == sourceId) {
forceCycleSpeedAudioSourceId = null
}
},
onClick = {
if (state.actionState.mode == MessageSelectionMode.MULTI) {
onToggleMessageMultiSelection(message)
@@ -1789,7 +1811,11 @@ private fun MessageBubble(
onAttachmentVideoClick: (String) -> Unit,
onAudioPlaybackChanged: (TopAudioStrip) -> Unit,
forceStopAudioSourceId: String?,
forceToggleAudioSourceId: String?,
forceCycleSpeedAudioSourceId: String?,
onForceStopAudioSourceHandled: (String) -> Unit,
onForceToggleAudioSourceHandled: (String) -> Unit,
onForceCycleSpeedAudioSourceHandled: (String) -> Unit,
onClick: () -> Unit,
onLongPress: () -> Unit,
) {
@@ -2009,7 +2035,11 @@ private fun MessageBubble(
messageId = message.id,
onPlaybackChanged = onAudioPlaybackChanged,
forceStopAudioSourceId = forceStopAudioSourceId,
forceToggleAudioSourceId = forceToggleAudioSourceId,
forceCycleSpeedAudioSourceId = forceCycleSpeedAudioSourceId,
onForceStopAudioSourceHandled = onForceStopAudioSourceHandled,
onForceToggleAudioSourceHandled = onForceToggleAudioSourceHandled,
onForceCycleSpeedAudioSourceHandled = onForceCycleSpeedAudioSourceHandled,
)
}
@@ -2484,7 +2514,11 @@ private fun AudioAttachmentPlayer(
messageId: Long,
onPlaybackChanged: (TopAudioStrip) -> Unit,
forceStopAudioSourceId: String?,
forceToggleAudioSourceId: String?,
forceCycleSpeedAudioSourceId: String?,
onForceStopAudioSourceHandled: (String) -> Unit,
onForceToggleAudioSourceHandled: (String) -> Unit,
onForceCycleSpeedAudioSourceHandled: (String) -> Unit,
) {
var isPlaying by remember(url) { mutableStateOf(false) }
var isPrepared by remember(url) { mutableStateOf(false) }
@@ -2494,6 +2528,18 @@ private fun AudioAttachmentPlayer(
var speedIndex by remember(url) { mutableStateOf(0) }
var isSeeking by remember(url) { mutableStateOf(false) }
var seekFraction by remember(url) { mutableStateOf(0f) }
fun emitTopStrip(playing: Boolean) {
onPlaybackChanged(
TopAudioStrip(
messageId = messageId,
sourceId = "player:$url",
title = playbackTitle,
subtitle = playbackSubtitle,
isPlaying = playing,
speedLabel = formatAudioSpeed(speedOptions[speedIndex]),
)
)
}
val mediaPlayer = remember(url) {
MediaPlayer().apply {
setAudioAttributes(
@@ -2511,15 +2557,7 @@ private fun AudioAttachmentPlayer(
runCatching { player.seekTo(0) }
positionMs = 0
AppAudioFocusCoordinator.release("player:$url")
onPlaybackChanged(
TopAudioStrip(
messageId = messageId,
sourceId = "player:$url",
title = playbackTitle,
subtitle = playbackSubtitle,
isPlaying = false,
)
)
emitTopStrip(false)
}
setDataSource(url)
prepareAsync()
@@ -2531,15 +2569,7 @@ private fun AudioAttachmentPlayer(
if (activeId != null && activeId != currentId && isPlaying) {
runCatching { mediaPlayer.pause() }
isPlaying = false
onPlaybackChanged(
TopAudioStrip(
messageId = messageId,
sourceId = currentId,
title = playbackTitle,
subtitle = playbackSubtitle,
isPlaying = false,
)
)
emitTopStrip(false)
}
}
}
@@ -2551,17 +2581,48 @@ private fun AudioAttachmentPlayer(
positionMs = 0
isPlaying = false
AppAudioFocusCoordinator.release(currentId)
onPlaybackChanged(
TopAudioStrip(
messageId = messageId,
sourceId = currentId,
title = playbackTitle,
subtitle = playbackSubtitle,
isPlaying = false,
)
)
emitTopStrip(false)
onForceStopAudioSourceHandled(currentId)
}
LaunchedEffect(forceToggleAudioSourceId) {
val currentId = "player:$url"
if (forceToggleAudioSourceId != currentId) return@LaunchedEffect
if (!isPrepared) {
onForceToggleAudioSourceHandled(currentId)
return@LaunchedEffect
}
if (isPlaying) {
runCatching { mediaPlayer.pause() }
isPlaying = false
AppAudioFocusCoordinator.release(currentId)
emitTopStrip(false)
} else {
if (durationMs > 0 && positionMs >= durationMs - 200) {
runCatching { mediaPlayer.seekTo(0) }
positionMs = 0
}
runCatching {
mediaPlayer.playbackParams = mediaPlayer.playbackParams.setSpeed(speedOptions[speedIndex])
}
AppAudioFocusCoordinator.request(currentId)
runCatching { mediaPlayer.start() }
isPlaying = true
emitTopStrip(true)
}
onForceToggleAudioSourceHandled(currentId)
}
LaunchedEffect(forceCycleSpeedAudioSourceId) {
val currentId = "player:$url"
if (forceCycleSpeedAudioSourceId != currentId) return@LaunchedEffect
speedIndex = (speedIndex + 1) % speedOptions.size
if (isPrepared) {
runCatching {
mediaPlayer.playbackParams = mediaPlayer.playbackParams.setSpeed(speedOptions[speedIndex])
}
}
emitTopStrip(isPlaying)
onForceCycleSpeedAudioSourceHandled(currentId)
}
LaunchedEffect(isPlaying, isPrepared, isSeeking) {
if (!isPlaying || !isPrepared || isSeeking) return@LaunchedEffect
while (isPlaying) {
@@ -2582,15 +2643,7 @@ private fun AudioAttachmentPlayer(
mediaPlayer.stop()
}
AppAudioFocusCoordinator.release("player:$url")
onPlaybackChanged(
TopAudioStrip(
messageId = messageId,
sourceId = "player:$url",
title = playbackTitle,
subtitle = playbackSubtitle,
isPlaying = false,
)
)
emitTopStrip(false)
mediaPlayer.release()
}
}
@@ -2612,15 +2665,7 @@ private fun AudioAttachmentPlayer(
mediaPlayer.pause()
isPlaying = false
AppAudioFocusCoordinator.release("player:$url")
onPlaybackChanged(
TopAudioStrip(
messageId = messageId,
sourceId = "player:$url",
title = playbackTitle,
subtitle = playbackSubtitle,
isPlaying = false,
)
)
emitTopStrip(false)
} else {
if (durationMs > 0 && positionMs >= durationMs - 200) {
runCatching { mediaPlayer.seekTo(0) }
@@ -2632,15 +2677,7 @@ private fun AudioAttachmentPlayer(
AppAudioFocusCoordinator.request("player:$url")
mediaPlayer.start()
isPlaying = true
onPlaybackChanged(
TopAudioStrip(
messageId = messageId,
sourceId = "player:$url",
title = playbackTitle,
subtitle = playbackSubtitle,
isPlaying = true,
)
)
emitTopStrip(true)
}
},
enabled = isPrepared,
@@ -2659,10 +2696,11 @@ private fun AudioAttachmentPlayer(
mediaPlayer.playbackParams = mediaPlayer.playbackParams.setSpeed(speedOptions[speedIndex])
}
}
emitTopStrip(isPlaying)
},
enabled = isPrepared,
) {
Text("${speedOptions[speedIndex]}x")
Text(formatAudioSpeed(speedOptions[speedIndex]))
}
}
if (isVoice) {
@@ -2794,6 +2832,14 @@ private fun formatDuration(ms: Int): String {
return "$min:${sec.toString().padStart(2, '0')}"
}
private fun formatAudioSpeed(speed: Float): String {
return if (speed % 1f == 0f) {
"${speed.toInt()}x"
} else {
"${speed}x"
}
}
private fun resolveRemoteAudioDurationMs(url: String): Int? {
return runCatching {
val retriever = MediaMetadataRetriever()
@@ -3008,7 +3054,11 @@ private fun ChatInfoTabContent(
messageId = entry.sourceMessageId ?: entry.title.hashCode().toLong(),
onPlaybackChanged = {},
forceStopAudioSourceId = null,
forceToggleAudioSourceId = null,
forceCycleSpeedAudioSourceId = null,
onForceStopAudioSourceHandled = {},
onForceToggleAudioSourceHandled = {},
onForceCycleSpeedAudioSourceHandled = {},
)
}
}
@@ -3152,4 +3202,5 @@ private data class TopAudioStrip(
val title: String,
val subtitle: String,
val isPlaying: Boolean,
val speedLabel: String = "1x",
)