From 0a626e5f66e1b6892a65b618a0554d83a5dc0ba8 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 16 Mar 2024 16:57:12 +0100 Subject: [PATCH 1/9] feat: Add completer to recording save functions Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../organisms/RecorderEventsHandler.kt | 24 +++++++++++++++---- .../alibi/ui/models/BaseRecorderModel.kt | 5 +++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt index d01c5ad..4db1b32 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt @@ -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 @@ -33,6 +30,7 @@ import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.models.BaseRecorderModel import app.myzel394.alibi.ui.models.VideoRecorderModel import app.myzel394.alibi.ui.utils.rememberFileSaverDialog +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -227,6 +225,8 @@ fun RecorderEventsHandler( // Register audio recorder events DisposableEffect(key1 = audioRecorder, key2 = settings) { audioRecorder.onRecordingSave = { justSave -> + val completer = CompletableDeferred() + scope.launch { if (justSave) { saveRecording(audioRecorder as RecorderModel) @@ -239,7 +239,11 @@ fun RecorderEventsHandler( audioRecorder.destroyService(context) } + + completer.complete(Unit) } + + completer } audioRecorder.onRecordingStart = { snackbarHostState.currentSnackbarData?.dismiss() @@ -265,7 +269,9 @@ fun RecorderEventsHandler( } onDispose { - audioRecorder.onRecordingSave = {} + audioRecorder.onRecordingSave = { + throw NotImplementedError("onRecordingSave should not be called now") + } audioRecorder.onError = {} } } @@ -273,6 +279,8 @@ fun RecorderEventsHandler( // Register video recorder events DisposableEffect(key1 = videoRecorder, key2 = settings) { videoRecorder.onRecordingSave = { justSave -> + val completer = CompletableDeferred() + scope.launch { if (justSave) { saveRecording(videoRecorder as RecorderModel) @@ -285,7 +293,11 @@ fun RecorderEventsHandler( videoRecorder.destroyService(context) } + + completer.complete(Unit) } + + completer } videoRecorder.onRecordingStart = { snackbarHostState.currentSnackbarData?.dismiss() @@ -311,7 +323,9 @@ fun RecorderEventsHandler( } onDispose { - videoRecorder.onRecordingSave = {} + videoRecorder.onRecordingSave = { + throw NotImplementedError("onRecordingSave should not be called now") + } videoRecorder.onError = {} } } diff --git a/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt b/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt index 452419b..ebc054e 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt @@ -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.CompletableDeferred import kotlinx.serialization.json.Json abstract class BaseRecorderModel> : @@ -45,7 +46,9 @@ abstract class BaseRecorderModel Unit = {} + var onRecordingSave: (isSavingAsOldRecording: Boolean) -> CompletableDeferred = { + throw NotImplementedError("onRecordingSave not implemented") + } var onRecordingStart: () -> Unit = {} var onError: () -> Unit = {} var onBatchesFolderNotAccessible: () -> Unit = {} From 671c8da56ab43344f5f95c6fd9c1dc3fca866a79 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 16 Mar 2024 17:28:45 +0100 Subject: [PATCH 2/9] refactor: Move recording save stuff out to audio and video recording statuses component Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../java/app/myzel394/alibi/db/AppSettings.kt | 11 ++ .../organisms/AudioRecordingStatus.kt | 103 ++++++++---------- .../organisms/RecorderEventsHandler.kt | 51 +++------ .../organisms/VideoRecordingStatus.kt | 16 ++- .../alibi/ui/models/BaseRecorderModel.kt | 4 +- .../alibi/ui/screens/RecorderScreen.kt | 27 +++-- 6 files changed, 107 insertions(+), 105 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt b/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt index 1f45583..3b00d06 100644 --- a/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt +++ b/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt @@ -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 diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt index 2a639a0..bc97706 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt @@ -22,6 +22,7 @@ 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.molecules.MicrophoneStatus import app.myzel394.alibi.ui.components.RecorderScreen.molecules.RecordingControl @@ -39,8 +40,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 +89,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 +110,54 @@ 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() + + 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 = { + scope.launch { + audioRecorder.stopRecording(context) + + dataStore.updateData { + it.saveLastRecording(audioRecorder as RecorderModel) + } + + audioRecorder.onRecordingSave() + + runCatching { + audioRecorder.destroyService(context) + } + } + } + ) } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt index 4db1b32..bd2c241 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt @@ -30,7 +30,6 @@ import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.models.BaseRecorderModel import app.myzel394.alibi.ui.models.VideoRecorderModel import app.myzel394.alibi.ui.utils.rememberFileSaverDialog -import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -224,26 +223,15 @@ fun RecorderEventsHandler( // Register audio recorder events DisposableEffect(key1 = audioRecorder, key2 = settings) { - audioRecorder.onRecordingSave = { justSave -> - val completer = CompletableDeferred() - + audioRecorder.onRecordingSave = { + // 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) - } - - completer.complete(Unit) + saveRecording(audioRecorder as RecorderModel) } - - completer } audioRecorder.onRecordingStart = { snackbarHostState.currentSnackbarData?.dismiss() @@ -278,26 +266,15 @@ fun RecorderEventsHandler( // Register video recorder events DisposableEffect(key1 = videoRecorder, key2 = settings) { - videoRecorder.onRecordingSave = { justSave -> - val completer = CompletableDeferred() - + videoRecorder.onRecordingSave = { + // 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) - } - - completer.complete(Unit) + saveRecording(videoRecorder as RecorderModel) } - - completer } videoRecorder.onRecordingStart = { snackbarHostState.currentSnackbarData?.dismiss() diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt index 47479d9..e6f1b5a 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt @@ -32,6 +32,7 @@ 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.TorchStatus import app.myzel394.alibi.ui.components.RecorderScreen.molecules.RecordingControl import app.myzel394.alibi.ui.components.RecorderScreen.molecules.RecordingStatus @@ -189,6 +190,7 @@ fun _VideoRecordingStatus(videoRecorder: VideoRecorderModel) { @Composable fun _PrimitiveControls(videoRecorder: VideoRecorderModel) { val context = LocalContext.current + val dataStore = context.dataStore val scope = rememberCoroutineScope() RecordingControl( @@ -218,7 +220,19 @@ fun _PrimitiveControls(videoRecorder: VideoRecorderModel) { } }, onSave = { - videoRecorder.onRecordingSave(false) + scope.launch { + videoRecorder.stopRecording(context) + + dataStore.updateData { + it.saveLastRecording(videoRecorder as RecorderModel) + } + + videoRecorder.onRecordingSave() + + runCatching { + videoRecorder.destroyService(context) + } + } } ) } diff --git a/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt b/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt index ebc054e..1b54229 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt @@ -17,7 +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.CompletableDeferred +import kotlinx.coroutines.Job import kotlinx.serialization.json.Json abstract class BaseRecorderModel> : @@ -46,7 +46,7 @@ abstract class BaseRecorderModel CompletableDeferred = { + var onRecordingSave: () -> Job = { throw NotImplementedError("onRecordingSave not implemented") } var onRecordingStart: () -> Unit = {} diff --git a/app/src/main/java/app/myzel394/alibi/ui/screens/RecorderScreen.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/RecorderScreen.kt index 68752b3..ae5cb01 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/screens/RecorderScreen.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/screens/RecorderScreen.kt @@ -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() - RecordingInformation.Type.VIDEO -> - videoRecorder.onRecordingSave(true) + RecordingInformation.Type.VIDEO -> + videoRecorder.onRecordingSave() + } } }, showAudioRecorder = topBarVisible, From 662728966676019509fa55c710f39486b9d354d2 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 16 Mar 2024 18:43:26 +0100 Subject: [PATCH 3/9] feat: Add lock / unlock method for interval files Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../myzel394/alibi/helpers/BatchesFolder.kt | 25 ++++++++----------- .../alibi/services/IntervalRecorderService.kt | 24 ++++++++++++++++-- .../alibi/services/VideoRecorderService.kt | 2 +- .../organisms/RecorderEventsHandler.kt | 11 +++++--- .../organisms/VideoRecordingStatus.kt | 2 +- 5 files changed, 42 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt b/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt index 70c12f5..ac48807 100644 --- a/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt +++ b/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt @@ -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() 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() } } diff --git a/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt index ed8f9c7..05a6a55 100644 --- a/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt @@ -11,6 +11,9 @@ abstract class IntervalRecorderService : 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 : 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 : 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) } } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt index 5aaf273..7f4ac2d 100644 --- a/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt @@ -139,7 +139,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 { diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt index bd2c241..15eb477 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt @@ -129,15 +129,17 @@ fun RecorderEventsHandler( } } - suspend fun saveRecording(recorder: RecorderModel) { + suspend fun saveRecording(recorder: RecorderModel): Thread { isProcessing = true // Give the user some time to see the processing dialog delay(100) - thread { + return thread { runBlocking { try { + recorder.recorderService?.lockFiles() + val recording = // When new recording created recorder.recorderService?.getRecordingInformation() @@ -215,6 +217,7 @@ fun RecorderEventsHandler( } catch (error: Exception) { Log.getStackTraceString(error) } finally { + recorder.recorderService?.unlockFiles() isProcessing = false } } @@ -230,7 +233,7 @@ fun RecorderEventsHandler( // instead of hoping that the coroutine from where this will be called will be alive // until the end of the saving process scope.launch { - saveRecording(audioRecorder as RecorderModel) + saveRecording(audioRecorder as RecorderModel).join() } } audioRecorder.onRecordingStart = { @@ -273,7 +276,7 @@ fun RecorderEventsHandler( // instead of hoping that the coroutine from where this will be called will be alive // until the end of the saving process scope.launch { - saveRecording(videoRecorder as RecorderModel) + saveRecording(videoRecorder as RecorderModel).join() } } videoRecorder.onRecordingStart = { diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt index e6f1b5a..ec5471b 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt @@ -227,7 +227,7 @@ fun _PrimitiveControls(videoRecorder: VideoRecorderModel) { it.saveLastRecording(videoRecorder as RecorderModel) } - videoRecorder.onRecordingSave() + videoRecorder.onRecordingSave().join() runCatching { videoRecorder.destroyService(context) From fe24f3473f0a6aacb5464c462817e57ece2e97ea Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 16 Mar 2024 19:14:59 +0100 Subject: [PATCH 4/9] feat: Add onLongClick to SaveButton Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../RecorderScreen/atoms/SaveButton.kt | 28 +++++++++++++++---- .../molecules/RecordingControl.kt | 15 ++++++++-- .../organisms/AudioRecordingStatus.kt | 7 +++-- .../organisms/RecorderEventsHandler.kt | 12 ++++---- .../organisms/VideoRecordingStatus.kt | 7 +++-- .../alibi/ui/models/BaseRecorderModel.kt | 2 +- .../alibi/ui/screens/RecorderScreen.kt | 4 +-- 7 files changed, 52 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/SaveButton.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/SaveButton.kt index 28d960d..f8719c1 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/SaveButton.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/SaveButton.kt @@ -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, ) } } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/RecordingControl.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/RecordingControl.kt index 8e1bdde..47764d5 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/RecordingControl.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/RecordingControl.kt @@ -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,10 @@ fun RecordingControl( contentAlignment = Alignment.Center, ) { SaveButton( - onSave = onSave, + onSave = onSaveAndStop, + onLongClick = { + println("onLongClick") + }, modifier = Modifier.fillMaxWidth(), ) } @@ -170,7 +174,12 @@ fun RecordingControl( .alpha(saveButtonAlpha), contentAlignment = Alignment.Center, ) { - SaveButton(onSave = onSave) + SaveButton( + onSave = onSaveAndStop, + onLongClick = { + println("onLongClick") + }, + ) } } } diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt index bc97706..3fc4dba 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt @@ -144,7 +144,7 @@ fun _PrimitiveControls(audioRecorder: AudioRecorderModel) { audioRecorder.pauseRecording() } }, - onSave = { + onSaveAndStop = { scope.launch { audioRecorder.stopRecording(context) @@ -152,12 +152,13 @@ fun _PrimitiveControls(audioRecorder: AudioRecorderModel) { it.saveLastRecording(audioRecorder as RecorderModel) } - audioRecorder.onRecordingSave() + audioRecorder.onRecordingSave(false) runCatching { audioRecorder.destroyService(context) } } - } + }, + onSaveCurrent = {}, ) } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt index 15eb477..1af0f86 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt @@ -129,7 +129,7 @@ fun RecorderEventsHandler( } } - suspend fun saveRecording(recorder: RecorderModel): Thread { + suspend fun saveRecording(recorder: RecorderModel, cleanupOldFiles: Boolean = false): Thread { isProcessing = true // Give the user some time to see the processing dialog @@ -217,7 +217,7 @@ fun RecorderEventsHandler( } catch (error: Exception) { Log.getStackTraceString(error) } finally { - recorder.recorderService?.unlockFiles() + recorder.recorderService?.unlockFiles(cleanupOldFiles) isProcessing = false } } @@ -226,14 +226,14 @@ fun RecorderEventsHandler( // Register audio recorder events DisposableEffect(key1 = audioRecorder, key2 = settings) { - audioRecorder.onRecordingSave = { + 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 { - saveRecording(audioRecorder as RecorderModel).join() + saveRecording(audioRecorder as RecorderModel, cleanupOldFiles).join() } } audioRecorder.onRecordingStart = { @@ -269,14 +269,14 @@ fun RecorderEventsHandler( // Register video recorder events DisposableEffect(key1 = videoRecorder, key2 = settings) { - videoRecorder.onRecordingSave = { + 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 { - saveRecording(videoRecorder as RecorderModel).join() + saveRecording(videoRecorder as RecorderModel, cleanupOldFiles).join() } } videoRecorder.onRecordingStart = { diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt index ec5471b..4245f00 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt @@ -219,7 +219,7 @@ fun _PrimitiveControls(videoRecorder: VideoRecorderModel) { videoRecorder.pauseRecording() } }, - onSave = { + onSaveAndStop = { scope.launch { videoRecorder.stopRecording(context) @@ -227,12 +227,15 @@ fun _PrimitiveControls(videoRecorder: VideoRecorderModel) { it.saveLastRecording(videoRecorder as RecorderModel) } - videoRecorder.onRecordingSave().join() + videoRecorder.onRecordingSave(false).join() runCatching { videoRecorder.destroyService(context) } } + }, + onSaveCurrent = { + //TODO } ) } diff --git a/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt b/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt index 1b54229..e55ec83 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt @@ -46,7 +46,7 @@ abstract class BaseRecorderModel Job = { + var onRecordingSave: (cleanupOldFiles: Boolean) -> Job = { throw NotImplementedError("onRecordingSave not implemented") } var onRecordingStart: () -> Unit = {} diff --git a/app/src/main/java/app/myzel394/alibi/ui/screens/RecorderScreen.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/RecorderScreen.kt index ae5cb01..a086840 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/screens/RecorderScreen.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/screens/RecorderScreen.kt @@ -120,10 +120,10 @@ fun RecorderScreen( scope.launch { when (settings.lastRecording!!.type) { RecordingInformation.Type.AUDIO -> - audioRecorder.onRecordingSave() + audioRecorder.onRecordingSave(false) RecordingInformation.Type.VIDEO -> - videoRecorder.onRecordingSave() + videoRecorder.onRecordingSave(false) } } }, From dad8439d3da667de011567ba924350b4272a9331 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 17 Mar 2024 18:19:38 +0100 Subject: [PATCH 5/9] feat: Add save current audio recording without stopping it (wip) Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../myzel394/alibi/services/IntervalRecorderService.kt | 2 +- .../RecorderScreen/molecules/RecordingControl.kt | 8 ++------ .../RecorderScreen/organisms/AudioRecordingStatus.kt | 10 ++++++++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt index 05a6a55..6783ef2 100644 --- a/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt @@ -35,7 +35,7 @@ abstract class IntervalRecorderService : // Unlocks and deletes the files that were locked using `lockFiles`. fun unlockFiles(cleanupFiles: Boolean = false) { if (cleanupFiles) { - batchesFolder.deleteRecordings(0..lockedIndex!!) + batchesFolder.deleteRecordings(0.. Date: Sun, 17 Mar 2024 21:03:39 +0100 Subject: [PATCH 6/9] feat: Add save current on long press for audio recorder Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../RecorderScreen/organisms/AudioRecordingStatus.kt | 2 +- .../RecorderScreen/organisms/RecorderEventsHandler.kt | 8 ++++++-- .../app/myzel394/alibi/ui/models/BaseRecorderModel.kt | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt index c302f24..664cc38 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt @@ -163,7 +163,7 @@ fun _PrimitiveControls(audioRecorder: AudioRecorderModel) { scope.launch { audioRecorder.recorderService!!.startNewCycle() - audioRecorder.onRecordingSave(true).join() + audioRecorder.onRecordingSave(false).join() } }, ) diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt index 1af0f86..6db673d 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt @@ -138,7 +138,9 @@ fun RecorderEventsHandler( return thread { runBlocking { try { - recorder.recorderService?.lockFiles() + if (recorder.isCurrentlyActivelyRecording) { + recorder.recorderService?.lockFiles() + } val recording = // When new recording created @@ -217,7 +219,9 @@ fun RecorderEventsHandler( } catch (error: Exception) { Log.getStackTraceString(error) } finally { - recorder.recorderService?.unlockFiles(cleanupOldFiles) + if (recorder.isCurrentlyActivelyRecording) { + recorder.recorderService?.unlockFiles(cleanupOldFiles) + } isProcessing = false } } diff --git a/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt b/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt index e55ec83..7796849 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/models/BaseRecorderModel.kt @@ -32,6 +32,9 @@ abstract class BaseRecorderModel Date: Sun, 17 Mar 2024 22:01:54 +0100 Subject: [PATCH 7/9] feat: Add confirmation modal for saving current recording Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../atoms/SaveCurrentNowModal.kt | 58 +++++++++++++++++++ .../organisms/AudioRecordingStatus.kt | 26 +++++++-- app/src/main/res/values/strings.xml | 2 + 3 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/SaveCurrentNowModal.kt diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/SaveCurrentNowModal.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/SaveCurrentNowModal.kt new file mode 100644 index 0000000..8480f55 --- /dev/null +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/SaveCurrentNowModal.kt @@ -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)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt index 664cc38..0e6d983 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/AudioRecordingStatus.kt @@ -24,6 +24,7 @@ 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 @@ -123,6 +124,25 @@ fun _PrimitiveControls(audioRecorder: AudioRecorderModel) { 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, @@ -160,11 +180,7 @@ fun _PrimitiveControls(audioRecorder: AudioRecorderModel) { } }, onSaveCurrent = { - scope.launch { - audioRecorder.recorderService!!.startNewCycle() - - audioRecorder.onRecordingSave(false).join() - } + showConfirmSaveNow = true }, ) } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 15b1d26..edeb3f2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -191,4 +191,6 @@ 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. Please rotate your device to portait mode Back + Save now? + You can save the current ongoing recording by pressing and holding down on the save button. The recording will continue in the background. \ No newline at end of file From 227e075c0baffddd150c2f320ffe3934ec17ec25 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:38:57 +0100 Subject: [PATCH 8/9] feat: Add save current option to VideoRecordingStatus Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../organisms/VideoRecordingStatus.kt | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt index 4245f00..9009a99 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt @@ -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 @@ -33,6 +34,7 @@ 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 @@ -193,6 +195,25 @@ fun _PrimitiveControls(videoRecorder: VideoRecorderModel) { 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 @@ -235,7 +256,7 @@ fun _PrimitiveControls(videoRecorder: VideoRecorderModel) { } }, onSaveCurrent = { - //TODO + showConfirmSaveNow = true } ) } From 41f1aca833b1b29310dbd5354ff41b0de3476e5e Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Mon, 18 Mar 2024 17:02:38 +0100 Subject: [PATCH 9/9] fix: Always listen for video finalization event Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../app/myzel394/alibi/services/VideoRecorderService.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt index 7f4ac2d..aa74867 100644 --- a/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt @@ -50,7 +50,7 @@ class VideoRecorderService : // Used to listen and check if the camera is available private var _cameraAvailableListener = CompletableDeferred() - private var _videoFinalizerListener = CompletableDeferred() + private lateinit var _videoFinalizerListener: CompletableDeferred; // Absolute last completer that can be awaited to ensure that the camera is closed private var _cameraCloserListener = CompletableDeferred() @@ -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) } }