moved re-record button inline and added to VoiceMessagePreview.kt

This commit is contained in:
davotoula
2026-01-15 13:56:49 +01:00
parent 5be1633843
commit 76a05f7d71
3 changed files with 148 additions and 154 deletions
@@ -35,8 +35,10 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.Pause
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Stop
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -67,6 +69,8 @@ fun VoiceMessagePreview(
voiceMetadata: AudioMeta,
localFile: File? = null,
onRemove: () -> Unit,
onReRecord: ((RecordingResult) -> Unit)? = null,
isUploading: Boolean = false,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
@@ -101,77 +105,159 @@ fun VoiceMessagePreview(
shape = RoundedCornerShape(8.dp),
).padding(12.dp),
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
// Play/Pause Button
IconButton(
onClick = {
handlePlayPauseClick(
mediaPlayer = mediaPlayer,
isPlaying = isPlaying,
progress = progress,
onProgressReset = { progress = 0f },
onPlayingChanged = { isPlaying = it },
)
},
modifier = Modifier.size(48.dp),
Column {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Icon(
imageVector = if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
contentDescription = if (isPlaying) stringRes(context, R.string.pause) else stringRes(context, R.string.play),
tint = MaterialTheme.colorScheme.primary,
)
}
Spacer(modifier = Modifier.width(8.dp))
// Waveform and Duration
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.Center,
) {
AudioWaveformReadOnly(
amplitudes = voiceMetadata.waveform ?: emptyList(),
progress = progress,
waveformBrush = Brush.linearGradient(listOf(MaterialTheme.colorScheme.onSurfaceVariant, MaterialTheme.colorScheme.onSurfaceVariant)),
progressBrush = Brush.linearGradient(listOf(MaterialTheme.colorScheme.primary, MaterialTheme.colorScheme.primary)),
onProgressChange = { newProgress ->
handleWaveformScrub(
newProgress = newProgress,
// Play/Pause Button
IconButton(
onClick = {
handlePlayPauseClick(
mediaPlayer = mediaPlayer,
onProgressChanged = { progress = it },
isPlaying = isPlaying,
progress = progress,
onProgressReset = { progress = 0f },
onPlayingChanged = { isPlaying = it },
)
},
)
modifier = Modifier.size(48.dp),
) {
Icon(
imageVector = if (isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
contentDescription = if (isPlaying) stringRes(context, R.string.pause) else stringRes(context, R.string.play),
tint = MaterialTheme.colorScheme.primary,
)
}
Text(
text = formatSecondsToTime(voiceMetadata.duration ?: 0),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 4.dp),
)
Spacer(modifier = Modifier.width(8.dp))
// Waveform and Duration
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.Center,
) {
AudioWaveformReadOnly(
amplitudes = voiceMetadata.waveform ?: emptyList(),
progress = progress,
waveformBrush = Brush.linearGradient(listOf(MaterialTheme.colorScheme.onSurfaceVariant, MaterialTheme.colorScheme.onSurfaceVariant)),
progressBrush = Brush.linearGradient(listOf(MaterialTheme.colorScheme.primary, MaterialTheme.colorScheme.primary)),
onProgressChange = { newProgress ->
handleWaveformScrub(
newProgress = newProgress,
mediaPlayer = mediaPlayer,
onProgressChanged = { progress = it },
)
},
)
Text(
text = formatSecondsToTime(voiceMetadata.duration ?: 0),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 4.dp),
)
}
Spacer(modifier = Modifier.width(8.dp))
// Remove Button
IconButton(
onClick = onRemove,
modifier = Modifier.size(48.dp),
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringRes(context, R.string.remove),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
Spacer(modifier = Modifier.width(8.dp))
// Remove Button
IconButton(
onClick = onRemove,
modifier = Modifier.size(48.dp),
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringRes(context, R.string.remove),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
if (onReRecord != null) {
Spacer(modifier = Modifier.size(8.dp))
ReRecordButton(
isUploading = isUploading,
isPlaying = isPlaying,
onRecordTaken = onReRecord,
)
}
}
}
}
@Composable
private fun ReRecordButton(
isUploading: Boolean,
isPlaying: Boolean,
onRecordTaken: (RecordingResult) -> Unit,
) {
if (isUploading || isPlaying) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Icon(
imageVector = Icons.Default.Mic,
contentDescription = stringRes(id = R.string.record_a_message),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
Text(
text = stringRes(id = R.string.re_record),
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
return
}
RecordAudioBox(
modifier = Modifier,
onRecordTaken = onRecordTaken,
maxDurationSeconds = MAX_VOICE_RECORD_SECONDS,
) { isRecording, elapsedSeconds ->
val contentColor =
if (isRecording) {
MaterialTheme.colorScheme.onPrimary
} else {
MaterialTheme.colorScheme.onSurfaceVariant
}
val icon =
if (isRecording) {
Icons.Default.Stop
} else {
Icons.Default.Mic
}
val label =
if (isRecording) {
formatSecondsToTime(elapsedSeconds)
} else {
stringRes(id = R.string.re_record)
}
val iconDescription =
if (isRecording) {
stringRes(id = R.string.recording_indicator_description)
} else {
stringRes(id = R.string.record_a_message)
}
Row(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Icon(
imageVector = icon,
contentDescription = iconDescription,
tint = contentColor,
)
Text(
text = label,
color = contentColor,
)
}
}
}
@Composable
private fun ManageMediaPlayer(
voiceMetadata: AudioMeta,
@@ -375,6 +375,8 @@ private fun NewPostScreenBody(
VoiceMessagePreview(
voiceMetadata = displayMetadata,
localFile = postViewModel.activeFile,
onReRecord = { recording -> postViewModel.selectVoiceRecording(recording) },
isUploading = postViewModel.isUploadingVoice,
onRemove = { postViewModel.removeVoiceMessage() },
)
@@ -21,47 +21,32 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.home
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.Stop
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.mediaServers.FileServerSelectionRow
import com.vitorpamplona.amethyst.ui.actions.uploads.MAX_VOICE_RECORD_SECONDS
import com.vitorpamplona.amethyst.ui.actions.uploads.RecordAudioBox
import com.vitorpamplona.amethyst.ui.actions.uploads.UploadProgressIndicator
import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceAnonymizationSection
import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceMessagePreview
import com.vitorpamplona.amethyst.ui.actions.uploads.formatSecondsToTime
import com.vitorpamplona.amethyst.ui.navigation.navs.Nav
import com.vitorpamplona.amethyst.ui.navigation.topbars.PostingTopBar
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.replyModifier
@@ -104,9 +89,6 @@ fun VoiceReplyScreen(
},
)
},
bottomBar = {
ReRecordButton(viewModel)
},
) { pad ->
Surface(
modifier =
@@ -162,6 +144,8 @@ private fun VoiceReplyScreenBody(
VoiceMessagePreview(
voiceMetadata = displayMetadata,
localFile = viewModel.activeFile,
onReRecord = { recording -> viewModel.selectRecording(recording) },
isUploading = viewModel.isUploading,
onRemove = {
viewModel.cancel()
nav.popBack()
@@ -193,81 +177,3 @@ private fun VoiceReplyScreenBody(
Spacer(modifier = Modifier.height(80.dp))
}
}
@Composable
private fun ReRecordButton(viewModel: VoiceReplyViewModel) {
Column(
modifier =
Modifier
.fillMaxWidth()
.navigationBarsPadding()
.padding(vertical = 16.dp, horizontal = Size10dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (viewModel.isUploading) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Icon(
imageVector = Icons.Default.Mic,
contentDescription = stringRes(id = R.string.record_a_message),
tint = MaterialTheme.colorScheme.onBackground,
)
Text(
text = stringRes(id = R.string.re_record),
color = MaterialTheme.colorScheme.onBackground,
)
}
return
}
RecordAudioBox(
modifier = Modifier,
onRecordTaken = { recording ->
viewModel.selectRecording(recording)
},
maxDurationSeconds = MAX_VOICE_RECORD_SECONDS,
) { isRecording, elapsedSeconds ->
val contentColor =
if (isRecording) {
MaterialTheme.colorScheme.onPrimary
} else {
MaterialTheme.colorScheme.onBackground
}
val icon =
if (isRecording) {
Icons.Default.Stop
} else {
Icons.Default.Mic
}
val label =
if (isRecording) {
formatSecondsToTime(elapsedSeconds)
} else {
stringRes(id = R.string.re_record)
}
val iconDescription =
if (isRecording) {
stringRes(id = R.string.recording_indicator_description)
} else {
stringRes(id = R.string.record_a_message)
}
Row(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Icon(
imageVector = icon,
contentDescription = iconDescription,
tint = contentColor,
)
Text(
text = label,
color = contentColor,
)
}
}
}
}