feat: Add save recording button

This commit is contained in:
Myzel394 2023-08-08 19:50:45 +02:00
parent 0bfed18314
commit 09f9be8414
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
6 changed files with 135 additions and 25 deletions

View File

@ -5,6 +5,7 @@ import android.os.Build
import android.util.Log
import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.ReturnCode
import kotlinx.coroutines.delay
import kotlinx.serialization.Serializable
import java.io.File
import java.time.LocalDateTime

View File

@ -9,6 +9,10 @@ import androidx.compose.foundation.background
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
@ -16,6 +20,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import app.myzel394.alibi.dataStore
import app.myzel394.alibi.db.LastRecording
import app.myzel394.alibi.ui.enums.Screen
import app.myzel394.alibi.ui.models.AudioRecorderModel
import app.myzel394.alibi.ui.screens.AudioRecorder

View File

@ -13,12 +13,15 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
@ -43,6 +46,7 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import app.myzel394.alibi.R
import app.myzel394.alibi.services.RecorderService
import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE
import app.myzel394.alibi.ui.components.AudioRecorder.atoms.ConfirmDeletionDialog
import app.myzel394.alibi.ui.components.AudioRecorder.atoms.RealtimeAudioVisualizer
import app.myzel394.alibi.ui.components.AudioRecorder.atoms.SaveRecordingButton
@ -135,7 +139,7 @@ fun RecordingStatus(
},
onConfirm = {
showDeleteDialog = false
audioRecorder.stopRecording(context)
audioRecorder.stopRecording(context, saveAsLastRecording = false)
},
)
}
@ -159,6 +163,31 @@ fun RecordingStatus(
Text(label)
}
}
val alpha by animateFloatAsState(if (progressVisible) 1f else 0f, tween(1000))
val label = stringResource(R.string.ui_audioRecorder_action_save_label)
Button(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.height(BIG_PRIMARY_BUTTON_SIZE)
.alpha(alpha)
.semantics {
contentDescription = label
},
onClick = {
audioRecorder.stopRecording(context)
audioRecorder.onRecordingSave()
},
) {
Icon(
Icons.Default.Save,
contentDescription = null,
modifier = Modifier.size(ButtonDefaults.IconSize),
)
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.ui_audioRecorder_action_save_label))
}
}
}

View File

@ -1,14 +1,7 @@
package app.myzel394.alibi.ui.components.AudioRecorder.molecules
import android.Manifest
import android.content.ServiceConnection
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandHorizontally
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@ -28,14 +21,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.autoSaver
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -48,10 +34,7 @@ import androidx.compose.ui.unit.dp
import app.myzel394.alibi.R
import app.myzel394.alibi.dataStore
import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.services.RecorderService
import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE
import app.myzel394.alibi.ui.components.AudioRecorder.atoms.AudioVisualizer
import app.myzel394.alibi.ui.components.AudioRecorder.atoms.SaveRecordingButton
import app.myzel394.alibi.ui.components.atoms.PermissionRequester
import app.myzel394.alibi.ui.models.AudioRecorderModel
import app.myzel394.alibi.ui.utils.rememberFileSaverDialog
@ -130,13 +113,39 @@ fun StartRecording(
.fillMaxWidth(),
textAlign = TextAlign.Center,
)
if (false)
if (audioRecorder.lastRecording != null && audioRecorder.lastRecording!!.hasRecordingAvailable) {
Column(
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Bottom,
) {
val label = stringResource(
R.string.ui_audioRecorder_action_saveOldRecording_label,
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(audioRecorder.lastRecording!!.recordingStart),
)
Button(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.height(BIG_PRIMARY_BUTTON_SIZE)
.semantics {
contentDescription = label
},
onClick = {
audioRecorder.stopRecording(context)
audioRecorder.onRecordingSave()
},
) {
Icon(
Icons.Default.Save,
contentDescription = null,
modifier = Modifier.size(ButtonDefaults.IconSize),
)
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
Text(label)
}
}
}
else
Spacer(modifier = Modifier.weight(1f))
}

View File

@ -12,6 +12,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel
import app.myzel394.alibi.db.LastRecording
import app.myzel394.alibi.enums.RecorderState
import app.myzel394.alibi.services.AudioRecorderService
import app.myzel394.alibi.services.RecorderService
@ -36,6 +37,11 @@ class AudioRecorderModel: ViewModel() {
var recorderService: AudioRecorderService? = null
private set
var lastRecording: LastRecording? by mutableStateOf<LastRecording?>(null)
private set
var onRecordingSave: () -> Unit = {}
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
recorderService = ((service as RecorderService.RecorderBinder).getService() as AudioRecorderService).also {recorder ->
@ -77,7 +83,11 @@ class AudioRecorderModel: ViewModel() {
context.bindService(intent!!, connection, Context.BIND_AUTO_CREATE)
}
fun stopRecording(context: Context) {
fun stopRecording(context: Context, saveAsLastRecording: Boolean = true) {
if (saveAsLastRecording) {
lastRecording = recorderService!!.createLastRecording()
}
runCatching {
context.unbindService(connection)
context.stopService(intent)

View File

@ -1,21 +1,30 @@
package app.myzel394.alibi.ui.screens
import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Memory
import androidx.compose.material.icons.filled.Settings
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.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import app.myzel394.alibi.ui.components.AudioRecorder.molecules.RecordingStatus
@ -23,17 +32,66 @@ import app.myzel394.alibi.ui.components.AudioRecorder.molecules.StartRecording
import app.myzel394.alibi.ui.enums.Screen
import app.myzel394.alibi.ui.utils.rememberFileSaverDialog
import app.myzel394.alibi.R
import app.myzel394.alibi.db.LastRecording
import app.myzel394.alibi.ui.models.AudioRecorderModel
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AudioRecorder(
navController: NavController,
audioRecorder: AudioRecorderModel
audioRecorder: AudioRecorderModel,
) {
val context = LocalContext.current
val saveFile = rememberFileSaverDialog("audio/aac")
val scope = rememberCoroutineScope()
var isProcessingAudio by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
audioRecorder.onRecordingSave = {
scope.launch {
isProcessingAudio = true
try {
val file = audioRecorder.lastRecording!!.concatenateFiles()
saveFile(file)
} catch (error: Exception) {
Log.getStackTraceString(error)
} finally {
isProcessingAudio = false
}
}
}
}
if (isProcessingAudio)
AlertDialog(
onDismissRequest = { },
icon = {
Icon(
Icons.Default.Memory,
contentDescription = null,
)
},
title = {
Text(
stringResource(R.string.ui_audioRecorder_action_save_processing_dialog_title),
)
},
text = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
stringResource(R.string.ui_audioRecorder_action_save_processing_dialog_description),
)
Spacer(modifier = Modifier.height(32.dp))
LinearProgressIndicator()
}
},
confirmButton = {}
)
Scaffold(
topBar = {
TopAppBar(
@ -61,9 +119,7 @@ fun AudioRecorder(
.padding(padding),
) {
if (audioRecorder.isInRecording)
RecordingStatus(
audioRecorder = audioRecorder,
)
RecordingStatus(audioRecorder = audioRecorder)
else
StartRecording(audioRecorder = audioRecorder)
}