Added possibility to save recording and delete call from history
This commit is contained in:
@@ -1116,7 +1116,14 @@ class BaresipService: Service() {
|
|||||||
delay(500)
|
delay(500)
|
||||||
val rxFile = File(call.dumpfiles[0])
|
val rxFile = File(call.dumpfiles[0])
|
||||||
val txFile = File(call.dumpfiles[1])
|
val txFile = File(call.dumpfiles[1])
|
||||||
val mergedFile = File(filesPath, "${rxFile.nameWithoutExtension}_stereo.wav")
|
val mergedFileName = rxFile.name
|
||||||
|
.replace("dump", "rec")
|
||||||
|
.replace("=>", "-")
|
||||||
|
.replace("sip:", "")
|
||||||
|
.replace("-enc", "")
|
||||||
|
.replace("*", "#")
|
||||||
|
.replace(";user=phone", "")
|
||||||
|
val mergedFile = File(filesPath, mergedFileName)
|
||||||
if (Utils.mergeWavFiles(rxFile, txFile, mergedFile)) {
|
if (Utils.mergeWavFiles(rxFile, txFile, mergedFile)) {
|
||||||
Log.d(TAG, "Automatic merge succeeded.")
|
Log.d(TAG, "Automatic merge succeeded.")
|
||||||
history.recording = arrayOf(mergedFile.absolutePath, "")
|
history.recording = arrayOf(mergedFile.absolutePath, "")
|
||||||
|
|||||||
@@ -5,8 +5,12 @@ import android.media.AudioAttributes
|
|||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -50,6 +54,8 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
|
import androidx.compose.runtime.toMutableStateList
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@@ -84,7 +90,7 @@ fun NavGraphBuilder.callDetailsScreenRoute(navController: NavController, viewMod
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun CallDetailsScreen(navController: NavController, callRow: CallRow) {
|
private fun CallDetailsScreen(navController: NavController, callRow: CallRow) {
|
||||||
|
val detailsState = remember { callRow.details.toMutableStateList() }
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -122,13 +128,31 @@ private fun CallDetailsScreen(navController: NavController, callRow: CallRow) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
content = { contentPadding ->
|
content = { contentPadding ->
|
||||||
CallDetailsContent(LocalContext.current, contentPadding, callRow)
|
CallDetailsContent(
|
||||||
|
LocalContext.current,
|
||||||
|
contentPadding,
|
||||||
|
callRow,
|
||||||
|
detailsState,
|
||||||
|
onDelete = { detail ->
|
||||||
|
detailsState.remove(detail)
|
||||||
|
// If the list becomes empty, you might want to pop back
|
||||||
|
if (detailsState.isEmpty()) {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CallDetailsContent(ctx: Context, contentPadding: PaddingValues, callRow: CallRow) {
|
private fun CallDetailsContent(
|
||||||
|
ctx: Context,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
callRow: CallRow,
|
||||||
|
details: SnapshotStateList<Details>,
|
||||||
|
onDelete: (Details) -> Unit
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -137,7 +161,7 @@ private fun CallDetailsContent(ctx: Context, contentPadding: PaddingValues, call
|
|||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
Peer(ctx, callRow)
|
Peer(ctx, callRow)
|
||||||
Details(ctx, callRow.details)
|
Details(ctx, details, onDelete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +179,7 @@ private fun Peer(ctx: Context, callRow: CallRow) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Details(ctx: Context, details: ArrayList<Details>) {
|
private fun Details(ctx: Context, details: SnapshotStateList<Details>, onDelete: (Details) -> Unit) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(text = stringResource(R.string.direction),
|
Text(text = stringResource(R.string.direction),
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
@@ -216,7 +240,7 @@ private fun Details(ctx: Context, details: ArrayList<Details>) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(78.dp))
|
Spacer(modifier = Modifier.width(78.dp))
|
||||||
val durationText = startTime(detail)
|
val durationText = startTime(detail, onDelete)
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Duration(ctx, detail, durationText)
|
Duration(ctx, detail, durationText)
|
||||||
}
|
}
|
||||||
@@ -225,7 +249,8 @@ private fun Details(ctx: Context, details: ArrayList<Details>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun startTime(detail: Details): String {
|
|
||||||
|
private fun startTime(detail: Details, onDelete: (Details) -> Unit): String {
|
||||||
val startTime = detail.startTime
|
val startTime = detail.startTime
|
||||||
val stopTime = detail.stopTime
|
val stopTime = detail.stopTime
|
||||||
val startTimeText: String
|
val startTimeText: String
|
||||||
@@ -257,32 +282,111 @@ private fun startTime(detail: Details): String {
|
|||||||
durationText = DateUtils.formatElapsedTime(duration)
|
durationText = DateUtils.formatElapsedTime(duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text(text = startTimeText)
|
val showDialog = remember { mutableStateOf(false) }
|
||||||
|
val positiveAction = remember { mutableStateOf({}) }
|
||||||
|
|
||||||
|
CustomElements.AlertDialog(
|
||||||
|
showDialog = showDialog,
|
||||||
|
title = stringResource(R.string.confirmation),
|
||||||
|
message = stringResource(R.string.delete_call_alert),
|
||||||
|
positiveButtonText = stringResource(R.string.delete),
|
||||||
|
onPositiveClicked = {
|
||||||
|
CallHistoryNew.remove(detail.startTime, detail.stopTime)
|
||||||
|
onDelete(detail)
|
||||||
|
},
|
||||||
|
negativeButtonText = stringResource(R.string.cancel)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = startTimeText,
|
||||||
|
modifier = Modifier.combinedClickable(
|
||||||
|
onClick = {},
|
||||||
|
onLongClick = {
|
||||||
|
positiveAction.value = {
|
||||||
|
CallHistoryNew.remove(startTime, stopTime)
|
||||||
|
}
|
||||||
|
showDialog.value = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return durationText
|
return durationText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
||||||
|
|
||||||
val showDialog = remember { mutableStateOf(false) }
|
val showPlaybackDialog = remember { mutableStateOf(false) }
|
||||||
|
val showDownloadDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// NOTE: If detail.recording is modified elsewhere, this reference sees the change
|
// NOTE: If detail.recording is modified elsewhere, this reference sees the change
|
||||||
// because Array is mutable, but Compose won't trigger a redraw.
|
|
||||||
val recording = detail.recording
|
val recording = detail.recording
|
||||||
val mediaPlayer = remember { MediaPlayer() }
|
val mediaPlayer = remember { MediaPlayer() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
// 1. Setup the File Saver Launcher
|
||||||
|
val saveLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.CreateDocument("audio/x-wav")
|
||||||
|
) { uri ->
|
||||||
|
uri?.let { destinationUri ->
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val sourceFile = File(recording[0])
|
||||||
|
if (sourceFile.exists()) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to save file: $e")
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
Toast.makeText(ctx, "Save failed", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PlaybackDialog(
|
PlaybackDialog(
|
||||||
showDialog = showDialog,
|
showDialog = showPlaybackDialog,
|
||||||
mediaPlayer = mediaPlayer,
|
mediaPlayer = mediaPlayer,
|
||||||
onStop = {
|
onStop = {
|
||||||
if (mediaPlayer.isPlaying) {
|
if (mediaPlayer.isPlaying) {
|
||||||
mediaPlayer.stop()
|
mediaPlayer.stop()
|
||||||
}
|
}
|
||||||
mediaPlayer.reset()
|
mediaPlayer.reset()
|
||||||
showDialog.value = false
|
showPlaybackDialog.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 2. Download Confirmation Dialog
|
||||||
|
if (showDownloadDialog.value) {
|
||||||
|
CustomElements.AlertDialog(
|
||||||
|
showDialog = showDownloadDialog,
|
||||||
|
title = stringResource(R.string.save_recording),
|
||||||
|
message = stringResource(R.string.save_recording_question),
|
||||||
|
positiveButtonText = stringResource(R.string.save),
|
||||||
|
onPositiveClicked = {
|
||||||
|
showDownloadDialog.value = false
|
||||||
|
val suggestedName = File(recording[0]).name
|
||||||
|
saveLauncher.launch(suggestedName)
|
||||||
|
},
|
||||||
|
negativeButtonText = stringResource(R.string.cancel)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val hasRecording = recording[0] != ""
|
val hasRecording = recording[0] != ""
|
||||||
if (hasRecording) {
|
if (hasRecording) {
|
||||||
Text(
|
Text(
|
||||||
@@ -290,7 +394,11 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 12.dp)
|
.padding(end = 12.dp)
|
||||||
.clickable(onClick = {
|
.combinedClickable(
|
||||||
|
onLongClick = {
|
||||||
|
showDownloadDialog.value = true
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
if (!mediaPlayer.isPlaying) {
|
if (!mediaPlayer.isPlaying) {
|
||||||
mediaPlayer.reset()
|
mediaPlayer.reset()
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
@@ -321,7 +429,6 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
// Normal Raw processing
|
// Normal Raw processing
|
||||||
val mergedFileName = "merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
|
val mergedFileName = "merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav"
|
||||||
val mergedFile = File(BaresipService.filesPath + "/recordings", mergedFileName)
|
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
|
||||||
@@ -345,7 +452,6 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (currentIsMerged) {
|
} else if (currentIsMerged) {
|
||||||
// We are in merged state
|
|
||||||
val f = File(recording[0])
|
val f = File(recording[0])
|
||||||
if (f.exists()) {
|
if (f.exists()) {
|
||||||
Log.d(TAG, "Using already merged file: ${recording[0]}")
|
Log.d(TAG, "Using already merged file: ${recording[0]}")
|
||||||
@@ -370,31 +476,32 @@ private fun Duration(ctx: Context, detail: Details, durationText: String) {
|
|||||||
fis.close()
|
fis.close()
|
||||||
setOnPreparedListener {
|
setOnPreparedListener {
|
||||||
it.start()
|
it.start()
|
||||||
Log.d(TAG, "Started playback")
|
showPlaybackDialog.value = true
|
||||||
showDialog.value = true
|
|
||||||
}
|
}
|
||||||
setOnCompletionListener {
|
setOnCompletionListener {
|
||||||
Log.d(TAG, "Playback complete")
|
showPlaybackDialog.value = false
|
||||||
showDialog.value = false
|
|
||||||
it.reset()
|
it.reset()
|
||||||
}
|
}
|
||||||
prepareAsync()
|
prepareAsync()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Playback failed: $e")
|
Log.e(TAG, "Playback failed: $e")
|
||||||
Toast.makeText(ctx, "Playback error", Toast.LENGTH_SHORT).show()
|
Toast.makeText(ctx, "Playback error",
|
||||||
|
Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(ctx, "Failed to process audio file", Toast.LENGTH_SHORT).show()
|
Toast.makeText(ctx, "Failed to process audio file",
|
||||||
|
Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mediaPlayer.stop()
|
mediaPlayer.stop()
|
||||||
mediaPlayer.reset()
|
mediaPlayer.reset()
|
||||||
showDialog.value = false
|
showPlaybackDialog.value = false
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(text = durationText, modifier = Modifier.padding(end = 12.dp))
|
Text(text = durationText, modifier = Modifier.padding(end = 12.dp))
|
||||||
@@ -410,8 +517,6 @@ private fun PlaybackDialog(
|
|||||||
if (showDialog.value) {
|
if (showDialog.value) {
|
||||||
// State to hold progress (0.0f to 1.0f)
|
// State to hold progress (0.0f to 1.0f)
|
||||||
var currentProgress by remember { mutableFloatStateOf(0f) }
|
var currentProgress by remember { mutableFloatStateOf(0f) }
|
||||||
|
|
||||||
// Formatted time strings
|
|
||||||
var currentPositionText by remember { mutableStateOf("00:00") }
|
var currentPositionText by remember { mutableStateOf("00:00") }
|
||||||
var totalDurationText by remember { mutableStateOf("00:00") }
|
var totalDurationText by remember { mutableStateOf("00:00") }
|
||||||
|
|
||||||
@@ -445,9 +550,7 @@ private fun PlaybackDialog(
|
|||||||
) {
|
) {
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = { currentProgress },
|
progress = { currentProgress },
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().height(8.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.height(8.dp),
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Row(
|
Row(
|
||||||
@@ -461,9 +564,7 @@ private fun PlaybackDialog(
|
|||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = { onStop() }
|
||||||
onStop()
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.stop))
|
Text(stringResource(R.string.stop))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,18 @@ class CallHistoryNew(val aor: String, val peerUri: String, val direction: String
|
|||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun remove(startTime: GregorianCalendar?, stopTime: GregorianCalendar) {
|
||||||
|
val iterator = BaresipService.callHistory.iterator()
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
val h = iterator.next()
|
||||||
|
if (h.startTime == startTime && h.stopTime == stopTime) {
|
||||||
|
deleteRecordingFiles(h.recording)
|
||||||
|
iterator.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
|
||||||
fun save() {
|
fun save() {
|
||||||
Log.d(TAG, "Saving history of ${BaresipService.callHistory.size} calls")
|
Log.d(TAG, "Saving history of ${BaresipService.callHistory.size} calls")
|
||||||
val file = File(BaresipService.filesPath + "/call_history")
|
val file = File(BaresipService.filesPath + "/call_history")
|
||||||
|
|||||||
@@ -254,8 +254,6 @@
|
|||||||
<string name="decrypt_password">Palauta salasanalla</string>
|
<string name="decrypt_password">Palauta salasanalla</string>
|
||||||
<string name="delete_account">Haluatko poistaa tilin \'%1$s\'\?</string>
|
<string name="delete_account">Haluatko poistaa tilin \'%1$s\'\?</string>
|
||||||
<!-- Baresip Service -->
|
<!-- Baresip Service -->
|
||||||
<string name="reply">Vastaa</string>
|
|
||||||
<string name="save">Talleta</string>
|
|
||||||
<string name="is_calling">soittaa</string>
|
<string name="is_calling">soittaa</string>
|
||||||
<string name="missed_call_from">Vastaamaton puhelu soittajalta</string>
|
<string name="missed_call_from">Vastaamaton puhelu soittajalta</string>
|
||||||
<string name="missed_calls">Vastaamattomia puheluita</string>
|
<string name="missed_calls">Vastaamattomia puheluita</string>
|
||||||
@@ -271,8 +269,6 @@
|
|||||||
<string name="peer">Kumppani</string>
|
<string name="peer">Kumppani</string>
|
||||||
<string name="time">Aika</string>
|
<string name="time">Aika</string>
|
||||||
<string name="calls_duration">Kesto</string>
|
<string name="calls_duration">Kesto</string>
|
||||||
<string name="playing_recording">Tallenteen kuuntelu …</string>
|
|
||||||
<string name="stop">Lopeta</string>
|
|
||||||
<string name="calls_add_delete_question">Haluatko luoda uuden yhteystiedon \'%1$s\' tai poistaa
|
<string name="calls_add_delete_question">Haluatko luoda uuden yhteystiedon \'%1$s\' tai poistaa
|
||||||
%2$s puheluhistoriasta\?
|
%2$s puheluhistoriasta\?
|
||||||
</string>
|
</string>
|
||||||
@@ -284,6 +280,12 @@
|
|||||||
<string name="call_answered_elsewhere">Puheluun vastattu muualla</string>
|
<string name="call_answered_elsewhere">Puheluun vastattu muualla</string>
|
||||||
<string name="call_missed">Puheluun ei vastattu</string>
|
<string name="call_missed">Puheluun ei vastattu</string>
|
||||||
<string name="call_rejected">Puhelu hylätty</string>
|
<string name="call_rejected">Puhelu hylätty</string>
|
||||||
|
<!-- Call Details Activity -->
|
||||||
|
<string name="playing_recording">Tallenteen kuuntelu …</string>
|
||||||
|
<string name="save_recording">Talleta tallenne</string>
|
||||||
|
<string name="save_recording_question">Haluatko tallentaa tämän tallenteen?</string>
|
||||||
|
<string name="recording_saved">Tallenne tallennettu</string>
|
||||||
|
<string name="delete_call_alert">Haluatko poistaa tämän puhelun historiata?</string>
|
||||||
<!-- Chat Activity -->
|
<!-- Chat Activity -->
|
||||||
<string name="chat_with">Viestiketju %1$s</string>
|
<string name="chat_with">Viestiketju %1$s</string>
|
||||||
<string name="new_message">Uusi viesti</string>
|
<string name="new_message">Uusi viesti</string>
|
||||||
@@ -460,6 +462,9 @@
|
|||||||
<string name="info">Tieto</string>
|
<string name="info">Tieto</string>
|
||||||
<string name="notice">Huomio</string>
|
<string name="notice">Huomio</string>
|
||||||
<string name="cancel">Peruuta</string>
|
<string name="cancel">Peruuta</string>
|
||||||
|
<string name="stop">Lopeta</string>
|
||||||
|
<string name="reply">Vastaa</string>
|
||||||
|
<string name="save">Talleta</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
<string name="yes">Kyllä</string>
|
<string name="yes">Kyllä</string>
|
||||||
<string name="no">Ei</string>
|
<string name="no">Ei</string>
|
||||||
|
|||||||
@@ -242,8 +242,6 @@
|
|||||||
<string name="decrypt_password">Decrypt Password</string>
|
<string name="decrypt_password">Decrypt Password</string>
|
||||||
<string name="delete_account">Do you want to delete account \'%1$s\'\?</string>
|
<string name="delete_account">Do you want to delete account \'%1$s\'\?</string>
|
||||||
<!-- Baresip Service -->
|
<!-- Baresip Service -->
|
||||||
<string name="reply">Reply</string>
|
|
||||||
<string name="save">Save</string>
|
|
||||||
<string name="is_calling">is calling</string>
|
<string name="is_calling">is calling</string>
|
||||||
<string name="missed_call_from">Missed call from</string>
|
<string name="missed_call_from">Missed call from</string>
|
||||||
<string name="missed_calls">Missed calls</string>
|
<string name="missed_calls">Missed calls</string>
|
||||||
@@ -260,8 +258,6 @@
|
|||||||
<string name="direction">Direction</string>
|
<string name="direction">Direction</string>
|
||||||
<string name="time">Time</string>
|
<string name="time">Time</string>
|
||||||
<string name="calls_duration">Duration</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
|
<string name="calls_add_delete_question">Do you want to add \'%1$s\' to contacts or delete
|
||||||
%2$s from call history\?</string>
|
%2$s from call history\?</string>
|
||||||
<string name="calls_delete_question">Do you want to delete \'%1$s\' %2$s from call history\?</string>
|
<string name="calls_delete_question">Do you want to delete \'%1$s\' %2$s from call history\?</string>
|
||||||
@@ -272,6 +268,12 @@
|
|||||||
<string name="call_answered_elsewhere">Call answered elsewhere</string>
|
<string name="call_answered_elsewhere">Call answered elsewhere</string>
|
||||||
<string name="call_missed">Call missed</string>
|
<string name="call_missed">Call missed</string>
|
||||||
<string name="call_rejected">Call rejected</string>
|
<string name="call_rejected">Call rejected</string>
|
||||||
|
<!-- Call Details Activity -->
|
||||||
|
<string name="playing_recording">Playing recording …</string>
|
||||||
|
<string name="save_recording">Save Recording</string>
|
||||||
|
<string name="save_recording_question">Do you want to save this recording?</string>
|
||||||
|
<string name="recording_saved">Recording saved</string>
|
||||||
|
<string name="delete_call_alert">Do you want to delete this call from history?</string>
|
||||||
<!-- Chat Activity -->
|
<!-- Chat Activity -->
|
||||||
<string name="chat_with">Chat with %1$s</string>
|
<string name="chat_with">Chat with %1$s</string>
|
||||||
<string name="new_message">New message</string>
|
<string name="new_message">New message</string>
|
||||||
@@ -438,6 +440,9 @@
|
|||||||
<string name="info">Info</string>
|
<string name="info">Info</string>
|
||||||
<string name="notice">Notice</string>
|
<string name="notice">Notice</string>
|
||||||
<string name="cancel">Cancel</string>
|
<string name="cancel">Cancel</string>
|
||||||
|
<string name="stop">Stop</string>
|
||||||
|
<string name="reply">Reply</string>
|
||||||
|
<string name="save">Save</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
<string name="yes">Yes</string>
|
<string name="yes">Yes</string>
|
||||||
<string name="no">No</string>
|
<string name="no">No</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user