Improved Playing recording dialog
This commit is contained in:
@ -99,4 +99,5 @@ dependencies {
|
||||
implementation(libs.androidx.lifecycle.viewmodel.ktx)
|
||||
implementation(libs.androidx.compose.material.icons.core)
|
||||
implementation(libs.androidx.compose.material.icons.extended)
|
||||
implementation(libs.androidx.compose.runtime)
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
@ -29,18 +30,26 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.CallMade
|
||||
import androidx.compose.material.icons.automirrored.filled.CallReceived
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@ -58,6 +67,7 @@ import com.tutpro.baresip.CallRow.Details
|
||||
import com.tutpro.baresip.CustomElements.AlertDialog
|
||||
import com.tutpro.baresip.CustomElements.verticalScrollbar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
@ -260,10 +270,16 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
||||
val mediaPlayer = remember { MediaPlayer() }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
AlertDialog(
|
||||
PlaybackDialog(
|
||||
showDialog = showDialog,
|
||||
title = stringResource(R.string.playing_recording),
|
||||
message = "",
|
||||
mediaPlayer = mediaPlayer,
|
||||
onStop = {
|
||||
if (mediaPlayer.isPlaying) {
|
||||
mediaPlayer.stop()
|
||||
}
|
||||
mediaPlayer.reset()
|
||||
showDialog.value = false
|
||||
}
|
||||
)
|
||||
|
||||
val isRawState = recording[0] != "" && recording[1] != ""
|
||||
@ -284,13 +300,15 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
||||
if (isRawState) {
|
||||
val fileIn = File(recording[0])
|
||||
val fileOut = File(recording[1])
|
||||
val mergedFileName = "merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
|
||||
val mergedFileName =
|
||||
"merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
|
||||
val mergedFile = File(BaresipService.filesPath + "/tmp", mergedFileName)
|
||||
|
||||
if (mergedFile.exists()) {
|
||||
Log.d(TAG, "Using already merged file: ${mergedFile.name}")
|
||||
finalFile = mergedFile
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
File(BaresipService.filesPath + "/tmp").mkdirs()
|
||||
if (Utils.mergeWavFiles(fileIn, fileOut, mergedFile)) {
|
||||
finalFile = mergedFile
|
||||
@ -315,7 +333,8 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
||||
Log.w(TAG, "MergeWav: Failed to delete original files: ${e.message}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// We are already in merged state
|
||||
Log.d(TAG, "Using already merged file: ${recording[0]}")
|
||||
finalFile = File(recording[0])
|
||||
@ -350,12 +369,14 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
||||
Log.e(TAG, "Playback failed: $e")
|
||||
Toast.makeText(ctx, "Playback error", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
Toast.makeText(ctx, "Failed to process audio file", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
mediaPlayer.stop()
|
||||
mediaPlayer.reset()
|
||||
showDialog.value = false
|
||||
@ -367,3 +388,73 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PlaybackDialog(
|
||||
showDialog: MutableState<Boolean>,
|
||||
mediaPlayer: MediaPlayer,
|
||||
onStop: () -> Unit
|
||||
) {
|
||||
if (showDialog.value) {
|
||||
// State to hold progress (0.0f to 1.0f)
|
||||
var currentProgress by remember { mutableFloatStateOf(0f) }
|
||||
|
||||
// Formatted time strings
|
||||
var currentPositionText by remember { mutableStateOf("00:00") }
|
||||
var totalDurationText by remember { mutableStateOf("00:00") }
|
||||
|
||||
// Update progress every 100ms
|
||||
LaunchedEffect(showDialog.value) {
|
||||
if (mediaPlayer.isPlaying) {
|
||||
val duration = mediaPlayer.duration
|
||||
totalDurationText = DateUtils.formatElapsedTime(duration / 1000L)
|
||||
|
||||
while (mediaPlayer.isPlaying) {
|
||||
val current = mediaPlayer.currentPosition
|
||||
currentProgress = if (duration > 0) current.toFloat() / duration.toFloat() else 0f
|
||||
currentPositionText = DateUtils.formatElapsedTime(current / 1000L)
|
||||
delay(100) // Poll every 100ms
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
// If user clicks outside, stop playback
|
||||
onStop()
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.playing_recording))
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
progress = { currentProgress },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(text = currentPositionText, style = MaterialTheme.typography.bodySmall)
|
||||
Text(text = totalDurationText, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onStop()
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.stop))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,6 +261,7 @@
|
||||
<string name="time">Time</string>
|
||||
<string name="calls_duration">Duration</string>
|
||||
<string name="playing_recording">Playing recording …</string>
|
||||
<string name="stop">Stop</string>
|
||||
<string name="calls_add_delete_question">Do you want to add \'%1$s\' to contacts or delete
|
||||
%2$s from call history\?</string>
|
||||
<string name="calls_delete_question">Do you want to delete \'%1$s\' %2$s from call history\?</string>
|
||||
|
||||
@ -21,6 +21,7 @@ runtimeLivedata = "1.10.0"
|
||||
composeMaterial3 = "1.4.0"
|
||||
navigationRuntimeAndroid = "2.9.6"
|
||||
composeMaterialIcons = "1.7.8"
|
||||
runtime = "1.10.0"
|
||||
|
||||
[libraries]
|
||||
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
|
||||
@ -47,6 +48,7 @@ androidx-compose-material3 = { group = "androidx.compose.material3", name = "mat
|
||||
androidx-navigation-runtime-android = { group = "androidx.navigation", name = "navigation-runtime-android", version.ref = "navigationRuntimeAndroid" }
|
||||
androidx-compose-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core", version.ref = "composeMaterialIcons" }
|
||||
androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "composeMaterialIcons" }
|
||||
androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" }
|
||||
|
||||
[plugins]
|
||||
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
|
||||
Reference in New Issue
Block a user