From 4e43b14b09fa26eb7835fb20001033d63a018886 Mon Sep 17 00:00:00 2001 From: davotoula Date: Tue, 24 Mar 2026 09:08:58 +0100 Subject: [PATCH 1/2] entire solid recording indicator bar stops recording when tapped, not just the small stop icon. --- .../amethyst/ui/actions/uploads/RecordAudio.kt | 10 ++++++++-- .../amethyst/ui/actions/uploads/RecordVoiceButton.kt | 8 +++++--- .../amethyst/ui/actions/uploads/RecordingIndicators.kt | 8 ++++++++ .../amethyst/ui/actions/uploads/VoiceMessagePreview.kt | 2 +- .../com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt | 3 ++- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordAudio.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordAudio.kt index 9eaa1a5dae..82888202a0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordAudio.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordAudio.kt @@ -50,7 +50,7 @@ fun RecordAudioBox( modifier: Modifier, onRecordTaken: (RecordingResult) -> Unit, maxDurationSeconds: Int? = null, - content: @Composable (Boolean, Int) -> Unit, + content: @Composable (Boolean, Int, () -> Unit) -> Unit, ) { val mediaRecorder = remember { mutableStateOf(null) } val context = LocalContext.current @@ -136,6 +136,12 @@ fun RecordAudioBox( } } }, - content = { active -> content(active, elapsedSeconds) }, + content = { active -> + content(active, elapsedSeconds) { + if (isRecording) { + stopRecording() + } + } + }, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordVoiceButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordVoiceButton.kt index 32613496f9..856e36e473 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordVoiceButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordVoiceButton.kt @@ -50,15 +50,17 @@ fun RecordVoiceButton( ) { var isRecording by remember { mutableStateOf(false) } var elapsedSeconds by remember { mutableIntStateOf(0) } + var onStopRecording by remember { mutableStateOf({}) } Column( verticalArrangement = Arrangement.Center, ) { - // Floating recording indicator at the top + // Floating recording indicator at the top (outside ToggleableBox to avoid scale/circle) FloatingRecordingIndicator( modifier = Modifier.height(50.dp), isRecording = isRecording, elapsedSeconds = elapsedSeconds, + onClick = onStopRecording, ) RecordAudioBox( @@ -69,8 +71,7 @@ fun RecordVoiceButton( onVoiceTaken(recording) }, maxDurationSeconds = maxDurationSeconds, - ) { recordingState, elapsed -> - // Update parent state after composition completes + ) { recordingState, elapsed, onStop -> SideEffect { if (isRecording != recordingState) { isRecording = recordingState @@ -78,6 +79,7 @@ fun RecordVoiceButton( if (elapsedSeconds != elapsed) { elapsedSeconds = elapsed } + onStopRecording = onStop } Box( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordingIndicators.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordingIndicators.kt index 8981e320f0..1fb909cc35 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordingIndicators.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordingIndicators.kt @@ -27,6 +27,7 @@ import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -176,6 +177,7 @@ fun FloatingRecordingIndicator( isRecording: Boolean, elapsedSeconds: Int, isCompact: Boolean = false, + onClick: (() -> Unit)? = null, ) { if (!isRecording) return @@ -199,6 +201,12 @@ fun FloatingRecordingIndicator( .background( color = MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(12.dp), + ).then( + if (onClick != null) { + Modifier.clickable(onClick = onClick) + } else { + Modifier + }, ), contentAlignment = Alignment.Center, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/VoiceMessagePreview.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/VoiceMessagePreview.kt index 198cf07113..c87d34b751 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/VoiceMessagePreview.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/VoiceMessagePreview.kt @@ -215,7 +215,7 @@ private fun ReRecordButton( modifier = Modifier, onRecordTaken = onRecordTaken, maxDurationSeconds = MAX_VOICE_RECORD_SECONDS, - ) { isRecording, elapsedSeconds -> + ) { isRecording, elapsedSeconds, _ -> val contentColor = if (isRecording) { MaterialTheme.colorScheme.onPrimary diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index f6e92f3c44..76b943fa7f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -672,7 +672,7 @@ fun ReplyViaVoiceReaction( } }, maxDurationSeconds = MAX_VOICE_RECORD_SECONDS, - ) { isRecording, elapsedSeconds -> + ) { isRecording, elapsedSeconds, onStop -> if (voiceRecordingState != null) { SideEffect { if (voiceRecordingState.value != isRecording) { @@ -689,6 +689,7 @@ fun ReplyViaVoiceReaction( isRecording = true, elapsedSeconds = elapsedSeconds, isCompact = true, + onClick = onStop, ) } else { VoiceReplyIcon(iconSizeModifier, grayTint) From e976bf9e2e0b6c737299ff7d36aa3ac96fcab690 Mon Sep 17 00:00:00 2001 From: davotoula Date: Tue, 24 Mar 2026 09:24:28 +0100 Subject: [PATCH 2/2] =?UTF-8?q?code=20review:=20-=20Removed=20redundant=20?= =?UTF-8?q?equality=20guards=20in=20SideEffect=20=E2=80=94=20MutableState?= =?UTF-8?q?=20already=20suppresses=20no-op=20writes=20for=20value=20types?= =?UTF-8?q?=20-=20Changed=20mutableStateOf({})=20to=20mutableStateOf(null)?= =?UTF-8?q?=20with=20explicit=20nullable=20type=20=E2=80=94=20clearer=20in?= =?UTF-8?q?tent,=20no=20accidental=20no-op=20invocation=20-=20=20stopRecor?= =?UTF-8?q?ding()=20now=20early-returns=20with=20=3F:=20return=20when=20no?= =?UTF-8?q?t=20recording,=20so=20the=20Toast=20only=20shows=20for=20genuin?= =?UTF-8?q?ely=20failed=20recordings=20(too=20short)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../amethyst/ui/actions/uploads/RecordAudio.kt | 7 +++---- .../amethyst/ui/actions/uploads/RecordVoiceButton.kt | 10 +++------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordAudio.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordAudio.kt index 82888202a0..0154ca0499 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordAudio.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordAudio.kt @@ -79,7 +79,8 @@ fun RecordAudioBox( } fun stopRecording() { - val result = mediaRecorder.value?.stop() + val recorder = mediaRecorder.value ?: return + val result = recorder.stop() mediaRecorder.value = null if (result != null) { onRecordTaken(result) @@ -138,9 +139,7 @@ fun RecordAudioBox( }, content = { active -> content(active, elapsedSeconds) { - if (isRecording) { - stopRecording() - } + stopRecording() } }, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordVoiceButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordVoiceButton.kt index 856e36e473..c4ae7a504f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordVoiceButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/RecordVoiceButton.kt @@ -50,7 +50,7 @@ fun RecordVoiceButton( ) { var isRecording by remember { mutableStateOf(false) } var elapsedSeconds by remember { mutableIntStateOf(0) } - var onStopRecording by remember { mutableStateOf({}) } + var onStopRecording: (() -> Unit)? by remember { mutableStateOf(null) } Column( verticalArrangement = Arrangement.Center, @@ -73,12 +73,8 @@ fun RecordVoiceButton( maxDurationSeconds = maxDurationSeconds, ) { recordingState, elapsed, onStop -> SideEffect { - if (isRecording != recordingState) { - isRecording = recordingState - } - if (elapsedSeconds != elapsed) { - elapsedSeconds = elapsed - } + isRecording = recordingState + elapsedSeconds = elapsed onStopRecording = onStop }