Merge remote-tracking branch 'origin/master'

This commit is contained in:
Myzel394 2024-03-19 22:31:03 +01:00
commit e7961c436b
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
13 changed files with 303 additions and 127 deletions

View File

@ -11,6 +11,7 @@ import app.myzel394.alibi.helpers.AudioBatchesFolder
import app.myzel394.alibi.helpers.VideoBatchesFolder
import app.myzel394.alibi.ui.RECORDER_MEDIA_SELECTED_VALUE
import app.myzel394.alibi.ui.SUPPORTS_SCOPED_STORAGE
import app.myzel394.alibi.ui.components.RecorderScreen.organisms.RecorderModel
import app.myzel394.alibi.ui.utils.PermissionHelper
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@ -102,6 +103,16 @@ data class AppSettings(
return copy(appLockSettings = appLockSettings)
}
fun saveLastRecording(recorder: RecorderModel): AppSettings {
return if (deleteRecordingsImmediately) {
this
} else {
setLastRecording(
recorder.recorderService!!.getRecordingInformation()
)
}
}
// If the object is present, biometric authentication is enabled.
// To disable biometric authentication, set the instance to null.
fun isAppLockEnabled() = appLockSettings != null

View File

@ -3,28 +3,26 @@ package app.myzel394.alibi.helpers
import android.Manifest
import android.content.ContentUris
import android.content.ContentValues
import app.myzel394.alibi.ui.MEDIA_RECORDINGS_PREFIX
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.provider.MediaStore.Video.Media
import androidx.documentfile.provider.DocumentFile
import java.io.File
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import com.arthenica.ffmpegkit.FFmpegKitConfig
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import app.myzel394.alibi.ui.MEDIA_RECORDINGS_PREFIX
import app.myzel394.alibi.ui.RECORDER_INTERNAL_SELECTED_VALUE
import app.myzel394.alibi.ui.RECORDER_MEDIA_SELECTED_VALUE
import app.myzel394.alibi.ui.SUPPORTS_SCOPED_STORAGE
import app.myzel394.alibi.ui.utils.PermissionHelper
import com.arthenica.ffmpegkit.FFprobeKit
import com.arthenica.ffmpegkit.FFmpegKitConfig
import kotlinx.coroutines.CompletableDeferred
import java.io.File
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import kotlin.reflect.KFunction4
abstract class BatchesFolder(
@ -197,7 +195,6 @@ abstract class BatchesFolder(
createNewFile()
}
fun checkIfOutputAlreadyExists(
date: LocalDateTime,
extension: String
@ -388,12 +385,12 @@ abstract class BatchesFolder(
}
}
fun deleteOldRecordings(earliestCounter: Long) {
fun deleteRecordings(range: LongRange) {
when (type) {
BatchType.INTERNAL -> getInternalFolder().listFiles()?.forEach {
val fileCounter = it.nameWithoutExtension.toIntOrNull() ?: return@forEach
if (fileCounter < earliestCounter) {
if (fileCounter in range) {
it.delete()
}
}
@ -401,7 +398,7 @@ abstract class BatchesFolder(
BatchType.CUSTOM -> getCustomDefinedFolder().listFiles().forEach {
val fileCounter = it.name?.substringBeforeLast(".")?.toIntOrNull() ?: return@forEach
if (fileCounter < earliestCounter) {
if (fileCounter in range) {
it.delete()
}
}
@ -411,7 +408,7 @@ abstract class BatchesFolder(
val deletableNames = mutableListOf<String>()
queryMediaContent { rawName, counter, _, _ ->
if (counter < earliestCounter) {
if (counter in range) {
deletableNames.add(rawName)
}
}
@ -428,7 +425,7 @@ abstract class BatchesFolder(
it.nameWithoutExtension.substring(mediaPrefix.length).toIntOrNull()
?: return@forEach
if (fileCounter < earliestCounter) {
if (fileCounter in range) {
it.delete()
}
}

View File

@ -11,6 +11,9 @@ abstract class IntervalRecorderService<I, B : BatchesFolder> :
protected var counter = 0L
private set
// Tracks the index of the currently locked file
private var lockedIndex: Long? = null
lateinit var settings: AppSettings
private lateinit var cycleTimer: ScheduledExecutorService
@ -21,6 +24,23 @@ abstract class IntervalRecorderService<I, B : BatchesFolder> :
abstract fun getRecordingInformation(): I
// When saving the recording, the files should be locked.
// This prevents the service from deleting the currently available files, so that
// they can be safely used to save the recording.
// Once finished, make sure to unlock the files using `unlockFiles`.
fun lockFiles() {
lockedIndex = counter
}
// Unlocks and deletes the files that were locked using `lockFiles`.
fun unlockFiles(cleanupFiles: Boolean = false) {
if (cleanupFiles) {
batchesFolder.deleteRecordings(0..<lockedIndex!!)
}
lockedIndex = null
}
// Make overrideable
open fun startNewCycle() {
counter += 1
@ -72,12 +92,12 @@ abstract class IntervalRecorderService<I, B : BatchesFolder> :
private fun deleteOldRecordings() {
val timeMultiplier = settings.maxDuration / settings.intervalDuration
val earliestCounter = counter - timeMultiplier
val earliestCounter = Math.max(counter - timeMultiplier, lockedIndex ?: 0)
if (earliestCounter <= 0) {
return
}
batchesFolder.deleteOldRecordings(earliestCounter)
batchesFolder.deleteRecordings(0..earliestCounter)
}
}

View File

@ -50,7 +50,7 @@ class VideoRecorderService :
// Used to listen and check if the camera is available
private var _cameraAvailableListener = CompletableDeferred<Unit>()
private var _videoFinalizerListener = CompletableDeferred<Unit>()
private lateinit var _videoFinalizerListener: CompletableDeferred<Unit>;
// Absolute last completer that can be awaited to ensure that the camera is closed
private var _cameraCloserListener = CompletableDeferred<Unit>()
@ -129,8 +129,10 @@ class VideoRecorderService :
stopActiveRecording()
val newRecording = prepareVideoRecording()
_videoFinalizerListener = CompletableDeferred()
activeRecording = newRecording.start(ContextCompat.getMainExecutor(this)) { event ->
if (event is VideoRecordEvent.Finalize && this@VideoRecorderService.state == RecorderState.STOPPED) {
if (event is VideoRecordEvent.Finalize && this@VideoRecorderService.state == RecorderState.STOPPED || this@VideoRecorderService.state == RecorderState.PAUSED) {
_videoFinalizerListener.complete(Unit)
}
}
@ -139,7 +141,7 @@ class VideoRecorderService :
if (_cameraAvailableListener.isCompleted) {
action()
} else {
// Race condition of `startNewCycle` being called before `invpkeOnCompletion`
// Race condition of `startNewCycle` being called before `invokeOnCompletion`
// has been called can be ignored, as the camera usually opens within 5 seconds
// and the interval can't be set shorter than 10 seconds.
_cameraAvailableListener.invokeOnCompletion {

View File

@ -1,35 +1,51 @@
package app.myzel394.alibi.ui.components.RecorderScreen.atoms
import androidx.compose.material3.Button
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import app.myzel394.alibi.R
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SaveButton(
modifier: Modifier = Modifier,
onSave: () -> Unit,
onLongClick: () -> Unit = {},
) {
val label = stringResource(R.string.ui_recorder_action_save_label)
TextButton(
Box(
modifier = Modifier
.clip(ButtonDefaults.textShape)
.semantics {
contentDescription = label
}
.then(modifier),
onClick = onSave,
.combinedClickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(color = MaterialTheme.colorScheme.primary),
onClick = onSave,
onLongClick = onLongClick,
)
.padding(ButtonDefaults.TextButtonContentPadding)
.then(modifier)
) {
Text(
label,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
)
}
}

View File

@ -0,0 +1,58 @@
package app.myzel394.alibi.ui.components.RecorderScreen.atoms
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import app.myzel394.alibi.R
import app.myzel394.alibi.ui.SHEET_BOTTOM_OFFSET
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SaveCurrentNowModal(
onDismiss: () -> Unit,
onConfirm: () -> Unit,
) {
val sheetState = rememberModalBottomSheetState(true)
// Auto save on specific events
ModalBottomSheet(
onDismissRequest = onDismiss,
sheetState = sheetState,
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = SHEET_BOTTOM_OFFSET)
.padding(16.dp)
) {
Text(
stringResource(R.string.ui_recorder_action_saveCurrent),
style = MaterialTheme.typography.headlineMedium,
textAlign = TextAlign.Center,
)
Text(
stringResource(R.string.ui_recorder_action_saveCurrent_explanation),
)
TextButton(onClick = onConfirm) {
Text(stringResource(R.string.ui_recorder_action_save_label))
}
}
}
}

View File

@ -35,7 +35,8 @@ fun RecordingControl(
recordingTime: Long,
onDelete: () -> Unit,
onPauseResume: () -> Unit,
onSave: () -> Unit,
onSaveAndStop: () -> Unit,
onSaveCurrent: () -> Unit,
) {
val animateIn = rememberInitialRecordingAnimation(recordingTime)
@ -106,7 +107,8 @@ fun RecordingControl(
contentAlignment = Alignment.Center,
) {
SaveButton(
onSave = onSave,
onSave = onSaveAndStop,
onLongClick = onSaveCurrent,
modifier = Modifier.fillMaxWidth(),
)
}
@ -170,7 +172,10 @@ fun RecordingControl(
.alpha(saveButtonAlpha),
contentAlignment = Alignment.Center,
) {
SaveButton(onSave = onSave)
SaveButton(
onSave = onSaveAndStop,
onLongClick = onSaveCurrent,
)
}
}
}

View File

@ -22,7 +22,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import app.myzel394.alibi.dataStore
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.RealtimeAudioVisualizer
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.SaveCurrentNowModal
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.MicrophoneStatus
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.RecordingControl
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.RecordingStatus
@ -39,8 +41,6 @@ fun AudioRecordingStatus(
val context = LocalContext.current
val configuration = LocalConfiguration.current.orientation
val scope = rememberCoroutineScope()
var now by remember { mutableStateOf(LocalDateTime.now()) }
LaunchedEffect(Unit) {
@ -90,34 +90,7 @@ fun AudioRecordingStatus(
MicrophoneStatus(audioRecorder)
}
RecordingControl(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
isPaused = audioRecorder.isPaused,
recordingTime = audioRecorder.recordingTime,
onDelete = {
scope.launch {
runCatching {
audioRecorder.stopRecording(context)
}
runCatching {
audioRecorder.destroyService(context)
}
audioRecorder.batchesFolder!!.deleteRecordings()
}
},
onPauseResume = {
if (audioRecorder.isPaused) {
audioRecorder.resumeRecording()
} else {
audioRecorder.pauseRecording()
}
},
onSave = {
audioRecorder.onRecordingSave(false)
}
)
_PrimitiveControls(audioRecorder)
}
}
@ -138,33 +111,76 @@ fun AudioRecordingStatus(
HorizontalDivider()
RecordingControl(
isPaused = audioRecorder.isPaused,
recordingTime = audioRecorder.recordingTime,
onDelete = {
scope.launch {
runCatching {
audioRecorder.stopRecording(context)
}
runCatching {
audioRecorder.destroyService(context)
}
audioRecorder.batchesFolder!!.deleteRecordings()
}
},
onPauseResume = {
if (audioRecorder.isPaused) {
audioRecorder.resumeRecording()
} else {
audioRecorder.pauseRecording()
}
},
onSave = {
audioRecorder.onRecordingSave(false)
}
)
_PrimitiveControls(audioRecorder)
}
}
}
}
}
@Composable
fun _PrimitiveControls(audioRecorder: AudioRecorderModel) {
val context = LocalContext.current
val dataStore = context.dataStore
val scope = rememberCoroutineScope()
var showConfirmSaveNow by remember { mutableStateOf(false) }
if (showConfirmSaveNow) {
SaveCurrentNowModal(
onDismiss = {
showConfirmSaveNow = false
},
onConfirm = {
showConfirmSaveNow = false
scope.launch {
audioRecorder.recorderService!!.startNewCycle()
audioRecorder.onRecordingSave(false).join()
}
},
)
}
RecordingControl(
isPaused = audioRecorder.isPaused,
recordingTime = audioRecorder.recordingTime,
onDelete = {
scope.launch {
runCatching {
audioRecorder.stopRecording(context)
}
runCatching {
audioRecorder.destroyService(context)
}
audioRecorder.batchesFolder!!.deleteRecordings()
}
},
onPauseResume = {
if (audioRecorder.isPaused) {
audioRecorder.resumeRecording()
} else {
audioRecorder.pauseRecording()
}
},
onSaveAndStop = {
scope.launch {
audioRecorder.stopRecording(context)
dataStore.updateData {
it.saveLastRecording(audioRecorder as RecorderModel)
}
audioRecorder.onRecordingSave(false).join()
runCatching {
audioRecorder.destroyService(context)
}
}
},
onSaveCurrent = {
showConfirmSaveNow = true
},
)
}

View File

@ -1,6 +1,5 @@
package app.myzel394.alibi.ui.components.RecorderScreen.organisms
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.compose.material3.SnackbarDuration
@ -9,8 +8,6 @@ import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@ -132,15 +129,19 @@ fun RecorderEventsHandler(
}
}
suspend fun saveRecording(recorder: RecorderModel) {
suspend fun saveRecording(recorder: RecorderModel, cleanupOldFiles: Boolean = false): Thread {
isProcessing = true
// Give the user some time to see the processing dialog
delay(100)
thread {
return thread {
runBlocking {
try {
if (recorder.isCurrentlyActivelyRecording) {
recorder.recorderService?.lockFiles()
}
val recording =
// When new recording created
recorder.recorderService?.getRecordingInformation()
@ -218,6 +219,9 @@ fun RecorderEventsHandler(
} catch (error: Exception) {
Log.getStackTraceString(error)
} finally {
if (recorder.isCurrentlyActivelyRecording) {
recorder.recorderService?.unlockFiles(cleanupOldFiles)
}
isProcessing = false
}
}
@ -226,19 +230,14 @@ fun RecorderEventsHandler(
// Register audio recorder events
DisposableEffect(key1 = audioRecorder, key2 = settings) {
audioRecorder.onRecordingSave = { justSave ->
audioRecorder.onRecordingSave = { cleanupOldFiles ->
// We create our own coroutine because we show our own dialog and we want to
// keep saving until it's finished.
// So it's smarter to take things into our own hands and use our local coroutine,
// instead of hoping that the coroutine from where this will be called will be alive
// until the end of the saving process
scope.launch {
if (justSave) {
saveRecording(audioRecorder as RecorderModel)
} else {
audioRecorder.stopRecording(context)
saveAsLastRecording(audioRecorder as RecorderModel)
saveRecording(audioRecorder)
audioRecorder.destroyService(context)
}
saveRecording(audioRecorder as RecorderModel, cleanupOldFiles).join()
}
}
audioRecorder.onRecordingStart = {
@ -265,26 +264,23 @@ fun RecorderEventsHandler(
}
onDispose {
audioRecorder.onRecordingSave = {}
audioRecorder.onRecordingSave = {
throw NotImplementedError("onRecordingSave should not be called now")
}
audioRecorder.onError = {}
}
}
// Register video recorder events
DisposableEffect(key1 = videoRecorder, key2 = settings) {
videoRecorder.onRecordingSave = { justSave ->
videoRecorder.onRecordingSave = { cleanupOldFiles ->
// We create our own coroutine because we show our own dialog and we want to
// keep saving until it's finished.
// So it's smarter to take things into our own hands and use our local coroutine,
// instead of hoping that the coroutine from where this will be called will be alive
// until the end of the saving process
scope.launch {
if (justSave) {
saveRecording(videoRecorder as RecorderModel)
} else {
videoRecorder.stopRecording(context)
saveAsLastRecording(videoRecorder as RecorderModel)
saveRecording(videoRecorder)
videoRecorder.destroyService(context)
}
saveRecording(videoRecorder as RecorderModel, cleanupOldFiles).join()
}
}
videoRecorder.onRecordingStart = {
@ -311,7 +307,9 @@ fun RecorderEventsHandler(
}
onDispose {
videoRecorder.onRecordingSave = {}
videoRecorder.onRecordingSave = {
throw NotImplementedError("onRecordingSave should not be called now")
}
videoRecorder.onError = {}
}
}

View File

@ -21,6 +21,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
@ -32,6 +33,8 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import app.myzel394.alibi.R
import app.myzel394.alibi.dataStore
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.SaveCurrentNowModal
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.TorchStatus
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.RecordingControl
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.RecordingStatus
@ -189,8 +192,28 @@ fun _VideoRecordingStatus(videoRecorder: VideoRecorderModel) {
@Composable
fun _PrimitiveControls(videoRecorder: VideoRecorderModel) {
val context = LocalContext.current
val dataStore = context.dataStore
val scope = rememberCoroutineScope()
var showConfirmSaveNow by remember { mutableStateOf(false) }
if (showConfirmSaveNow) {
SaveCurrentNowModal(
onDismiss = {
showConfirmSaveNow = false
},
onConfirm = {
showConfirmSaveNow = false
scope.launch {
videoRecorder.recorderService!!.startNewCycle()
videoRecorder.onRecordingSave(false).join()
}
},
)
}
RecordingControl(
orientation = Configuration.ORIENTATION_PORTRAIT,
// There may be some edge cases where the app may crash if the
@ -217,8 +240,23 @@ fun _PrimitiveControls(videoRecorder: VideoRecorderModel) {
videoRecorder.pauseRecording()
}
},
onSave = {
videoRecorder.onRecordingSave(false)
onSaveAndStop = {
scope.launch {
videoRecorder.stopRecording(context)
dataStore.updateData {
it.saveLastRecording(videoRecorder as RecorderModel)
}
videoRecorder.onRecordingSave(false).join()
runCatching {
videoRecorder.destroyService(context)
}
}
},
onSaveCurrent = {
showConfirmSaveNow = true
}
)
}

View File

@ -17,6 +17,7 @@ import app.myzel394.alibi.helpers.BatchesFolder
import app.myzel394.alibi.services.IntervalRecorderService
import app.myzel394.alibi.services.RecorderNotificationHelper
import app.myzel394.alibi.services.RecorderService
import kotlinx.coroutines.Job
import kotlinx.serialization.json.Json
abstract class BaseRecorderModel<I, B : BatchesFolder, T : IntervalRecorderService<I, B>> :
@ -31,6 +32,9 @@ abstract class BaseRecorderModel<I, B : BatchesFolder, T : IntervalRecorderServi
open val isInRecording: Boolean
get() = recorderService != null
open val isCurrentlyActivelyRecording
get() = recorderState === RecorderState.RECORDING
val isPaused: Boolean
get() = recorderState === RecorderState.PAUSED
@ -45,7 +49,9 @@ abstract class BaseRecorderModel<I, B : BatchesFolder, T : IntervalRecorderServi
// If `isSavingAsOldRecording` is true, the user is saving an old recording,
// thus the service is not running and thus doesn't need to be stopped or destroyed
var onRecordingSave: (isSavingAsOldRecording: Boolean) -> Unit = {}
var onRecordingSave: (cleanupOldFiles: Boolean) -> Job = {
throw NotImplementedError("onRecordingSave not implemented")
}
var onRecordingStart: () -> Unit = {}
var onError: () -> Unit = {}
var onBatchesFolderNotAccessible: () -> Unit = {}

View File

@ -16,22 +16,26 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController
import app.myzel394.alibi.ui.components.RecorderScreen.organisms.AudioRecordingStatus
import app.myzel394.alibi.ui.components.RecorderScreen.organisms.StartRecording
import app.myzel394.alibi.ui.enums.Screen
import app.myzel394.alibi.R
import app.myzel394.alibi.dataStore
import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.db.RecordingInformation
import app.myzel394.alibi.ui.components.RecorderScreen.organisms.AudioRecordingStatus
import app.myzel394.alibi.ui.components.RecorderScreen.organisms.RecorderEventsHandler
import app.myzel394.alibi.ui.components.RecorderScreen.organisms.StartRecording
import app.myzel394.alibi.ui.components.RecorderScreen.organisms.VideoRecordingStatus
import app.myzel394.alibi.ui.models.AudioRecorderModel
import app.myzel394.alibi.ui.models.VideoRecorderModel
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -43,6 +47,7 @@ fun RecorderScreen(
) {
val snackbarHostState = remember { SnackbarHostState() }
val context = LocalContext.current
val scope = rememberCoroutineScope()
RecorderEventsHandler(
settings = settings,
@ -112,12 +117,14 @@ fun RecorderScreen(
videoRecorder = videoRecorder,
appSettings = appSettings,
onSaveLastRecording = {
when (settings.lastRecording!!.type) {
RecordingInformation.Type.AUDIO ->
audioRecorder.onRecordingSave(true)
scope.launch {
when (settings.lastRecording!!.type) {
RecordingInformation.Type.AUDIO ->
audioRecorder.onRecordingSave(false)
RecordingInformation.Type.VIDEO ->
videoRecorder.onRecordingSave(true)
RecordingInformation.Type.VIDEO ->
videoRecorder.onRecordingSave(false)
}
}
},
showAudioRecorder = topBarVisible,

View File

@ -191,4 +191,6 @@
<string name="ui_settings_option_saveFolder_explainInternalFolder_explanation">To protect your privacy, Alibi stores its batches into its own private, encrypted storage. This storage is only accessible by Alibi and can\'t be accessed by other apps or by a possible intruder. Once you save the recording, you will be asked where you want to save the recording to.</string>
<string name="ui_rotateDevice_portrait_label">Please rotate your device to portait mode</string>
<string name="goBack">Back</string>
<string name="ui_recorder_action_saveCurrent">Save now?</string>
<string name="ui_recorder_action_saveCurrent_explanation">You can save the current ongoing recording by pressing and holding down on the save button. The recording will continue in the background.</string>
</resources>