Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Voice to Content\ Implement elapsed time indicator #20984

Merged
merged 7 commits into from
Jun 17, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import org.wordpress.android.R
import org.wordpress.android.ui.compose.components.buttons.Drawable
import org.wordpress.android.ui.compose.theme.AppTheme
import org.wordpress.android.util.audio.RecordingUpdate
import java.util.Locale

@Composable
fun VoiceToContentScreen(
Expand Down Expand Up @@ -100,7 +101,7 @@ fun VoiceToContentView(state: VoiceToContentUiState, recordingUpdate: RecordingU
VoiceToContentUIStateType.ERROR -> ErrorView(state)
else -> {
Header(state.header)
SecondaryHeader(state.secondaryHeader)
SecondaryHeader(state.secondaryHeader, recordingUpdate)
RecordingPanel(state, recordingUpdate)
}
}
Expand Down Expand Up @@ -159,14 +160,16 @@ fun Header(model: HeaderUIModel) {
}

@Composable
fun SecondaryHeader(model: SecondaryHeaderUIModel?) {
fun SecondaryHeader(model: SecondaryHeaderUIModel?, recordingUpdate: RecordingUpdate) {
model?.let {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(id = model.label), style = secondaryHeaderStyle)
Spacer(modifier = Modifier.width(8.dp)) // Add space between text and progress
if (model.isLabelVisible) {
Text(text = stringResource(id = model.label), style = secondaryHeaderStyle)
Spacer(modifier = Modifier.width(8.dp)) // Add space between text and progress
}
if (model.isProgressIndicatorVisible) {
Box(
modifier = Modifier.size(20.dp) // size the progress indicator
Expand All @@ -176,13 +179,47 @@ fun SecondaryHeader(model: SecondaryHeaderUIModel?) {
)
}
} else {
Text(text = model.requestsAvailable, style = secondaryHeaderStyle)
Text(
text = if (model.isTimeElapsedVisible)
formatTime(recordingUpdate.remainingTimeInSeconds, model.timeMaxDurationInSeconds)
else model.requestsAvailable,
style = secondaryHeaderStyle
)
}
Spacer(modifier = Modifier.height(16.dp))
}
}
}

@Composable
fun formatTime(remainingTimeInSeconds: Int, maxDurationInSeconds: Int): String {
val default = getDefaultTimeString(maxDurationInSeconds)
if (remainingTimeInSeconds == -1) return default

val minutes = remainingTimeInSeconds / 60
val seconds = remainingTimeInSeconds % 60

val value = if (minutes == 1) default
else String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds)

return value
}

@Composable
fun getDefaultTimeString(maxDurationInSeconds: Int): String {
if (maxDurationInSeconds <= 0) {
return "00:00"
}

// Calculate minutes and seconds
val minutes = (maxDurationInSeconds - 1) / 60
val seconds = (maxDurationInSeconds - 1) % 60

// Format and return the default time string
return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds)
}


@Composable
fun RecordingPanel(model: VoiceToContentUiState, recordingUpdate: RecordingUpdate) {
model.recordingPanel?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ data class SecondaryHeaderUIModel(
val isLabelVisible: Boolean = true,
val isProgressIndicatorVisible: Boolean = false,
val requestsAvailable: String = "0",
val timeElapsed: String = "00:00:00",
val timeMaxDurationInSeconds: Int = 0,
val isTimeElapsedVisible: Boolean = false
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.RECORDI
import org.wordpress.android.util.audio.IAudioRecorder
import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Error
import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Success
import org.wordpress.android.util.audio.RecordingStrategy
import org.wordpress.android.util.audio.RecordingUpdate
import org.wordpress.android.viewmodel.ContextProvider
import org.wordpress.android.viewmodel.ScopedViewModel
Expand Down Expand Up @@ -136,6 +137,7 @@ class VoiceToContentViewModel @Inject constructor(
recordingUseCase.startRecording { audioRecorderResult ->
when (audioRecorderResult) {
is Success -> {
transitionToProcessing()
val file = getRecordingFile(audioRecorderResult.recordingPath)
file?.let {
executeVoiceToContent(it)
Expand Down Expand Up @@ -284,8 +286,9 @@ class VoiceToContentViewModel @Inject constructor(
uiStateType = RECORDING,
header = currentState.header.copy(label = R.string.voice_to_content_recording_label),
secondaryHeader = currentState.secondaryHeader?.copy(
timeElapsed = "00:00:00",
isTimeElapsedVisible = true
isTimeElapsedVisible = true,
timeMaxDurationInSeconds = MAX_DURATION,
isLabelVisible = false
),
recordingPanel = currentState.recordingPanel?.copy(
onStopTap = ::onStopTap,
Expand Down Expand Up @@ -338,6 +341,7 @@ class VoiceToContentViewModel @Inject constructor(
private val NetworkUnavailableMsg = R.string.error_network_connection
private val GenericFailureMsg = R.string.voice_to_content_generic_error
private const val VOICE_TO_CONTENT = "Voice to content"
private val MAX_DURATION = RecordingStrategy.VoiceToContentRecordingStrategy().maxDuration
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,16 @@ class AudioRecorder(
@Suppress("MagicNumber")
private fun startRecordingUpdates() {
recordingJob = coroutineScope.launch {
var elapsedTimeInSeconds = 0
val startTime = System.currentTimeMillis()
val amplitudeList = mutableListOf<Float>()
while (recorder != null) {
delay(RECORDING_UPDATE_INTERVAL)
elapsedTimeInSeconds += (RECORDING_UPDATE_INTERVAL / 1000).toInt()
// Calculate elapsed time in seconds
val elapsedTimeInSeconds = ((System.currentTimeMillis() - startTime) / 1000).toInt()

// Calculate remaining time
val remainingTimeInSeconds = recordingStrategy.maxDuration - elapsedTimeInSeconds

val fileSize = File(filePath).length()
val amplitude = recorder?.maxAmplitude?.toFloat() ?: 0f
amplitudeList.add(amplitude)
Expand All @@ -162,7 +167,7 @@ class AudioRecorder(
amplitudeList.removeAt(0)
}
_recordingUpdates.value = RecordingUpdate(
elapsedTime = elapsedTimeInSeconds,
remainingTimeInSeconds = remainingTimeInSeconds,
fileSize = fileSize,
fileSizeLimitExceeded = fileSize >= recordingStrategy.maxFileSize,
amplitudes = amplitudeList.toList()
Expand Down Expand Up @@ -208,7 +213,7 @@ class AudioRecorder(

companion object {
private const val TAG = "AudioRecorder"
private const val RECORDING_UPDATE_INTERVAL = 100L // in milliseconds
private const val RECORDING_UPDATE_INTERVAL = 75L // in milliseconds
private const val RESUME_DELAY = 500L // in milliseconds
private const val FILE_SIZE_THRESHOLD = 100000L
private const val DURATION_THRESHOLD = 1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.wordpress.android.util.audio

data class RecordingUpdate(
val elapsedTime: Int = 0, // in seconds
val remainingTimeInSeconds: Int = -1,
val fileSize: Long = 0L, // in bytes
val fileSizeLimitExceeded: Boolean = false,
val amplitudes: List<Float> = emptyList()
Expand Down
Loading