Store CallRow recording to list instead of array
This commit is contained in:
@ -317,8 +317,6 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
val showPlaybackDialog = remember { mutableStateOf(false) }
|
val showPlaybackDialog = remember { mutableStateOf(false) }
|
||||||
val showDownloadDialog = remember { mutableStateOf(false) }
|
val showDownloadDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// NOTE: If detail.recording is modified elsewhere, this reference sees the change
|
|
||||||
val recording = detail.recording
|
|
||||||
val mediaPlayer = remember { MediaPlayer() }
|
val mediaPlayer = remember { MediaPlayer() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
@ -329,21 +327,26 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
uri?.let { destinationUri ->
|
uri?.let { destinationUri ->
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val sourceFile = File(recording[0])
|
val sourcePath = detail.recording.firstOrNull { it.isNotEmpty() }
|
||||||
if (sourceFile.exists()) {
|
if (sourcePath != null) {
|
||||||
ctx.contentResolver.openOutputStream(destinationUri)?.use { output ->
|
val sourceFile = File(sourcePath)
|
||||||
FileInputStream(sourceFile).use { input ->
|
if (sourceFile.exists()) {
|
||||||
input.copyTo(output)
|
ctx.contentResolver.openOutputStream(destinationUri)?.use { output ->
|
||||||
|
FileInputStream(sourceFile).use { input ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
Toast.makeText(
|
||||||
|
ctx, ctx.getString(R.string.recording_saved),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
Toast.makeText(ctx, "Source file not found", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
Toast.makeText(ctx, ctx.getString(R.string.recording_saved),
|
|
||||||
Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
Toast.makeText(ctx, "Source file not found",
|
|
||||||
Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -376,14 +379,16 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
positiveButtonText = stringResource(R.string.save),
|
positiveButtonText = stringResource(R.string.save),
|
||||||
onPositiveClicked = {
|
onPositiveClicked = {
|
||||||
showDownloadDialog.value = false
|
showDownloadDialog.value = false
|
||||||
val suggestedName = File(recording[0]).name
|
detail.recording.firstOrNull { it.isNotEmpty() }?.let {
|
||||||
saveLauncher.launch(suggestedName)
|
val suggestedName = File(it).name
|
||||||
|
saveLauncher.launch(suggestedName)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
negativeButtonText = stringResource(R.string.cancel)
|
negativeButtonText = stringResource(R.string.cancel)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasRecording = recording[0] != ""
|
val hasRecording = detail.recording.isNotEmpty() && detail.recording[0].isNotEmpty()
|
||||||
if (hasRecording) {
|
if (hasRecording) {
|
||||||
Text(
|
Text(
|
||||||
text = durationText,
|
text = durationText,
|
||||||
@ -399,34 +404,56 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
mediaPlayer.reset()
|
mediaPlayer.reset()
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
var finalFile: File? = null
|
var finalFile: File? = null
|
||||||
// RE-EVALUATE STATE INSIDE THE CLICK LISTENER
|
// Use a local copy of the recording state for this operation
|
||||||
val currentIsRaw = recording[0] != "" && recording[1] != ""
|
val currentRecording = detail.recording
|
||||||
val currentIsMerged = recording[0] != "" && recording[1] == ""
|
|
||||||
|
val currentIsRaw = currentRecording.size > 1 &&
|
||||||
|
currentRecording[0].isNotEmpty() &&
|
||||||
|
currentRecording[1].isNotEmpty()
|
||||||
|
val currentIsMerged = currentRecording.isNotEmpty() &&
|
||||||
|
currentRecording[0].isNotEmpty() &&
|
||||||
|
(currentRecording.size == 1 || currentRecording[1].isEmpty())
|
||||||
|
|
||||||
if (currentIsRaw) {
|
if (currentIsRaw) {
|
||||||
val fileIn = File(recording[0])
|
val fileIn = File(currentRecording[0])
|
||||||
val fileOut = File(recording[1])
|
val fileOut = File(currentRecording[1])
|
||||||
// SAFETY CHECK: If the raw file is gone, the background service
|
// SAFETY CHECK: If the raw file is gone, the background service
|
||||||
// likely finished merging just now.
|
// likely finished merging just now.
|
||||||
if (!fileIn.exists()) {
|
if (!fileIn.exists()) {
|
||||||
// Try to find the merged file based on naming convention as fallback
|
val expectedMergedName =
|
||||||
val expectedMergedName = "merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
|
"merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
|
||||||
val fallbackMerged = File(BaresipService.filesPath + "/recordings", expectedMergedName)
|
val fallbackMerged = File(
|
||||||
|
BaresipService.filesPath + "/recordings",
|
||||||
|
expectedMergedName
|
||||||
|
)
|
||||||
if (fallbackMerged.exists()) {
|
if (fallbackMerged.exists()) {
|
||||||
Log.d(TAG, "Raw file missing, found merged fallback: ${fallbackMerged.name}")
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Raw file missing, found merged fallback: ${fallbackMerged.name}"
|
||||||
|
)
|
||||||
finalFile = fallbackMerged
|
finalFile = fallbackMerged
|
||||||
// Update state to match reality
|
// Update state to match reality by creating a new List
|
||||||
recording[0] = fallbackMerged.absolutePath
|
detail.recording =
|
||||||
recording[1] = ""
|
listOf(fallbackMerged.absolutePath, "")
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Raw file missing and fallback not found: ${recording[0]}")
|
Log.e(
|
||||||
|
TAG,
|
||||||
|
"Raw file missing and fallback not found: ${currentRecording[0]}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Normal Raw processing
|
// Normal Raw processing
|
||||||
val mergedFileName = "merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
|
val mergedFileName =
|
||||||
val mergedFile = File(BaresipService.filesPath + "/recordings", mergedFileName)
|
"merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
|
||||||
|
val mergedFile = File(
|
||||||
|
BaresipService.filesPath + "/recordings",
|
||||||
|
mergedFileName
|
||||||
|
)
|
||||||
if (mergedFile.exists()) {
|
if (mergedFile.exists()) {
|
||||||
Log.d(TAG, "Using already merged file: ${mergedFile.name}")
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Using already merged file: ${mergedFile.name}"
|
||||||
|
)
|
||||||
finalFile = mergedFile
|
finalFile = mergedFile
|
||||||
} else {
|
} else {
|
||||||
if (Utils.mergeWavFiles(fileIn, fileOut, mergedFile))
|
if (Utils.mergeWavFiles(fileIn, fileOut, mergedFile))
|
||||||
@ -435,24 +462,30 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
|
|
||||||
// If merge successful, update state and delete originals
|
// If merge successful, update state and delete originals
|
||||||
if (finalFile != null && finalFile.exists()) {
|
if (finalFile != null && finalFile.exists()) {
|
||||||
recording[0] = finalFile.absolutePath
|
detail.recording =
|
||||||
recording[1] = ""
|
listOf(finalFile.absolutePath, "")
|
||||||
try {
|
try {
|
||||||
if (fileIn.exists()) fileIn.delete()
|
if (fileIn.exists()) fileIn.delete()
|
||||||
if (fileOut.exists()) fileOut.delete()
|
if (fileOut.exists()) fileOut.delete()
|
||||||
CallHistoryNew.save()
|
CallHistoryNew.save()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "MergeWav: Failed to delete original files: ${e.message}")
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"MergeWav: Failed to delete original files: ${e.message}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (currentIsMerged) {
|
} else if (currentIsMerged) {
|
||||||
val f = File(recording[0])
|
val f = File(currentRecording[0])
|
||||||
if (f.exists()) {
|
if (f.exists()) {
|
||||||
Log.d(TAG, "Using already merged file: ${recording[0]}")
|
Log.d(TAG, "Using already merged file: ${currentRecording[0]}")
|
||||||
finalFile = f
|
finalFile = f
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Merged file record exists but file is missing: ${recording[0]}")
|
Log.e(
|
||||||
|
TAG,
|
||||||
|
"Merged file record exists but file is missing: ${currentRecording[0]}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,12 +514,16 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Playback failed: $e")
|
Log.e(TAG, "Playback failed: $e")
|
||||||
Toast.makeText(ctx, "Playback error",
|
Toast.makeText(
|
||||||
Toast.LENGTH_SHORT).show()
|
ctx, "Playback error",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(ctx, "Failed to process audio file",
|
Toast.makeText(
|
||||||
Toast.LENGTH_SHORT).show()
|
ctx, "Failed to process audio file",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,13 +8,13 @@ data class CallRow(
|
|||||||
var direction: Int,
|
var direction: Int,
|
||||||
var startTime: GregorianCalendar?,
|
var startTime: GregorianCalendar?,
|
||||||
var stopTime: GregorianCalendar,
|
var stopTime: GregorianCalendar,
|
||||||
var recording: Array<String>
|
var recording: List<String>
|
||||||
) {
|
) {
|
||||||
data class Details(
|
data class Details(
|
||||||
var direction: Int,
|
var direction: Int,
|
||||||
var startTime: GregorianCalendar?,
|
var startTime: GregorianCalendar?,
|
||||||
var stopTime: GregorianCalendar,
|
var stopTime: GregorianCalendar,
|
||||||
var recording: Array<String>
|
var recording: List<String>
|
||||||
)
|
)
|
||||||
val details = mutableListOf(Details(direction, startTime, stopTime, recording))
|
val details = mutableListOf(Details(direction, startTime, stopTime, recording))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -418,7 +418,7 @@ private fun Calls(
|
|||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
var count = 1
|
var count = 1
|
||||||
for (d in callRow.details) {
|
for (d in callRow.details) {
|
||||||
if (d.recording[0] != "")
|
if (d.recording.isNotEmpty() && d.recording[0] != "")
|
||||||
recordings = true
|
recordings = true
|
||||||
if (count > 3)
|
if (count > 3)
|
||||||
continue
|
continue
|
||||||
@ -486,10 +486,10 @@ private fun loadCallHistory(aor: String): MutableList<CallRow> {
|
|||||||
if (res.isNotEmpty() && res.last().peerUri == h.peerUri)
|
if (res.isNotEmpty() && res.last().peerUri == h.peerUri)
|
||||||
res.last().details.add(CallRow.Details(
|
res.last().details.add(CallRow.Details(
|
||||||
direction, h.startTime,
|
direction, h.startTime,
|
||||||
h.stopTime, h.recording
|
h.stopTime, h.recording.toList()
|
||||||
))
|
))
|
||||||
else
|
else
|
||||||
res.add(CallRow(h.aor, h.peerUri, direction, h.startTime, h.stopTime, h.recording))
|
res.add(CallRow(h.aor, h.peerUri, direction, h.startTime, h.stopTime, h.recording.toList()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
@ -497,12 +497,12 @@ private fun loadCallHistory(aor: String): MutableList<CallRow> {
|
|||||||
|
|
||||||
private fun removeFromHistory(callHistory: MutableState<List<CallRow>>, callRow: CallRow) {
|
private fun removeFromHistory(callHistory: MutableState<List<CallRow>>, callRow: CallRow) {
|
||||||
for (details in callRow.details) {
|
for (details in callRow.details) {
|
||||||
CallHistoryNew.deleteRecordingFiles(details.recording)
|
CallHistoryNew.deleteRecordingFiles(details.recording.toTypedArray())
|
||||||
BaresipService.callHistory.removeAll {
|
BaresipService.callHistory.removeAll {
|
||||||
it.startTime == details.startTime && it.stopTime == details.stopTime
|
it.startTime == details.startTime && it.stopTime == details.stopTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CallHistoryNew.deleteRecordingFiles(callRow.recording)
|
CallHistoryNew.deleteRecordingFiles(callRow.recording.toTypedArray())
|
||||||
val updatedList = callHistory.value.filterNot { it == callRow }
|
val updatedList = callHistory.value.filterNot { it == callRow }
|
||||||
callHistory.value = updatedList
|
callHistory.value = updatedList
|
||||||
CallHistoryNew.save()
|
CallHistoryNew.save()
|
||||||
|
|||||||
Reference in New Issue
Block a user