From cc4af773fbc78bddd76f4f7ab63bf056256bc24c Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 21:54:07 +0100 Subject: [PATCH 01/13] fix(ui): Make StartRecording more responsive for landscape mode Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../java/app/myzel394/alibi/ui/Constants.kt | 1 + .../RecorderScreen/atoms/BigButton.kt | 13 ++-- .../organisms/StartRecording.kt | 59 ++++++++++++++----- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/ui/Constants.kt b/app/src/main/java/app/myzel394/alibi/ui/Constants.kt index 8d7f7a9..979ee51 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/Constants.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/Constants.kt @@ -4,6 +4,7 @@ import android.os.Build import androidx.compose.ui.unit.dp val BIG_PRIMARY_BUTTON_SIZE = 64.dp +val BIG_PRIMARY_BUTTON_MAX_WIDTH = 450.dp val SHEET_BOTTOM_OFFSET = 24.dp val MAX_AMPLITUDE = 20000 diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/BigButton.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/BigButton.kt index e2f9c3f..3856550 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/BigButton.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/BigButton.kt @@ -1,6 +1,6 @@ package app.myzel394.alibi.ui.components.RecorderScreen.atoms -import android.Manifest +import android.content.res.Configuration import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -11,10 +11,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Mic import androidx.compose.material.ripple.rememberRipple -import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -25,12 +22,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp -import app.myzel394.alibi.R -import app.myzel394.alibi.ui.utils.PermissionHelper @OptIn(ExperimentalFoundationApi::class) @Composable @@ -41,8 +36,10 @@ fun BigButton( onClick: () -> Unit, onLongClick: () -> Unit = {}, ) { + val orientation = LocalConfiguration.current.orientation + BoxWithConstraints { - val isLarge = maxWidth > 500.dp + val isLarge = maxWidth > 500.dp && orientation == Configuration.ORIENTATION_PORTRAIT Column( modifier = Modifier diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/StartRecording.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/StartRecording.kt index 575f22c..a57d274 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/StartRecording.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/StartRecording.kt @@ -1,5 +1,6 @@ package app.myzel394.alibi.ui.components.RecorderScreen.organisms +import android.content.res.Configuration import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -9,13 +10,13 @@ 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.requiredWidthIn import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.text.ClickableText import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Save -import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -29,6 +30,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -40,6 +42,7 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import app.myzel394.alibi.R import app.myzel394.alibi.db.AppSettings +import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_MAX_WIDTH import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE import app.myzel394.alibi.ui.components.RecorderScreen.molecules.AudioRecordingStart import app.myzel394.alibi.ui.components.RecorderScreen.molecules.QuickMaxDurationSelector @@ -64,6 +67,7 @@ fun StartRecording( showAudioRecorder: Boolean, ) { val context = LocalContext.current + val orientation = LocalConfiguration.current.orientation val label = stringResource( R.string.ui_recorder_action_start_description_2, @@ -98,24 +102,50 @@ fun StartRecording( Column( modifier = Modifier .fillMaxSize() - .padding(bottom = 32.dp), + .padding(bottom = if (orientation == Configuration.ORIENTATION_PORTRAIT) 32.dp else 16.dp), verticalArrangement = Arrangement.SpaceBetween, horizontalAlignment = Alignment.CenterHorizontally, ) { Spacer(modifier = Modifier.weight(1f)) - if (showAudioRecorder) - AudioRecordingStart( - audioRecorder = audioRecorder, - appSettings = appSettings, - ) - VideoRecordingStart( - videoRecorder = videoRecorder, - appSettings = appSettings, - onHideAudioRecording = onHideTopBar, - onShowAudioRecording = onShowTopBar, - showPreview = !showAudioRecorder, - ) + when (orientation) { + Configuration.ORIENTATION_LANDSCAPE -> { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + ) { + if (showAudioRecorder) + AudioRecordingStart( + audioRecorder = audioRecorder, + appSettings = appSettings, + ) + VideoRecordingStart( + videoRecorder = videoRecorder, + appSettings = appSettings, + onHideAudioRecording = onHideTopBar, + onShowAudioRecording = onShowTopBar, + showPreview = !showAudioRecorder, + ) + } + } + + else -> { + if (showAudioRecorder) + AudioRecordingStart( + audioRecorder = audioRecorder, + appSettings = appSettings, + ) + VideoRecordingStart( + videoRecorder = videoRecorder, + appSettings = appSettings, + onHideAudioRecording = onHideTopBar, + onShowAudioRecording = onShowTopBar, + showPreview = !showAudioRecorder, + ) + } + } + val forceUpdate = rememberForceUpdateOnLifeCycleChange() Column( @@ -133,6 +163,7 @@ fun StartRecording( TextButton( modifier = Modifier .fillMaxWidth() + .requiredWidthIn(max = BIG_PRIMARY_BUTTON_MAX_WIDTH) .height(BIG_PRIMARY_BUTTON_SIZE) .semantics { contentDescription = label From af19eec613d33900af67a3e323992f7dd6e13973 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 22:09:31 +0100 Subject: [PATCH 02/13] fix(ui): Make AudioRecordingStatus more responsive for landscape mode Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../molecules/RecordingControl.kt | 114 +++++++++++---- .../molecules/RecordingStatus.kt | 6 +- .../organisms/AudioRecordingStatus.kt | 138 +++++++++++++----- 3 files changed, 188 insertions(+), 70 deletions(-) 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 ad9fa7b..92140d9 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 @@ -1,8 +1,11 @@ package app.myzel394.alibi.ui.components.RecorderScreen.molecules +import android.content.res.Configuration import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable @@ -14,6 +17,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp import app.myzel394.alibi.ui.components.RecorderScreen.atoms.DeleteButton import app.myzel394.alibi.ui.components.RecorderScreen.atoms.PauseResumeButton import app.myzel394.alibi.ui.components.RecorderScreen.atoms.SaveButton @@ -23,6 +28,7 @@ import kotlinx.coroutines.delay @Composable fun RecordingControl( + modifier: Modifier = Modifier, initialDelay: Long = 0L, isPaused: Boolean, recordingTime: Long, @@ -30,6 +36,7 @@ fun RecordingControl( onPauseResume: () -> Unit, onSave: () -> Unit, ) { + val orientation = LocalConfiguration.current.orientation val animateIn = rememberInitialRecordingAnimation(recordingTime) var deleteButtonAlphaIsIn by rememberSaveable { @@ -85,38 +92,87 @@ fun RecordingControl( } } - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - .alpha(deleteButtonAlpha), - contentAlignment = Alignment.Center, - ) { - DeleteButton(onDelete = onDelete) + when (orientation) { + Configuration.ORIENTATION_LANDSCAPE -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = modifier, + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .alpha(saveButtonAlpha), + contentAlignment = Alignment.Center, + ) { + SaveButton( + onSave = onSave, + modifier = Modifier.fillMaxWidth(), + ) + } + + Box( + modifier = Modifier + .fillMaxWidth() + .alpha(pauseButtonAlpha), + contentAlignment = Alignment.Center, + ) { + PauseResumeButton( + isPaused = isPaused, + onChange = onPauseResume, + ) + } + + Box( + modifier = Modifier + .fillMaxWidth() + .alpha(deleteButtonAlpha), + contentAlignment = Alignment.Center, + ) { + DeleteButton( + onDelete = onDelete, + modifier = Modifier.fillMaxWidth(), + ) + } + } } - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .alpha(pauseButtonAlpha), - ) { - PauseResumeButton( - isPaused = isPaused, - onChange = onPauseResume, - ) - } + else -> { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier, + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .alpha(deleteButtonAlpha), + contentAlignment = Alignment.Center, + ) { + DeleteButton(onDelete = onDelete) + } - Box( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - .alpha(saveButtonAlpha), - contentAlignment = Alignment.Center, - ) { - SaveButton(onSave = onSave) + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .alpha(pauseButtonAlpha), + ) { + PauseResumeButton( + isPaused = isPaused, + onChange = onPauseResume, + ) + } + + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .alpha(saveButtonAlpha), + contentAlignment = Alignment.Center, + ) { + SaveButton(onSave = onSave) + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/RecordingStatus.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/RecordingStatus.kt index b170d61..b5ad34d 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/RecordingStatus.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/RecordingStatus.kt @@ -40,6 +40,7 @@ fun RecordingStatus( progress: Float, recordingStart: LocalDateTime, maxDuration: Long, + progressModifier: Modifier = Modifier.width(300.dp), ) { val animateIn = rememberInitialRecordingAnimation(recordingTime) @@ -73,9 +74,8 @@ fun RecordingStatus( ) ) { LinearProgressIndicator( - progress = progress, - modifier = Modifier - .width(300.dp) + progress = { progress }, + modifier = progressModifier, ) } 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 b3d805b..2a639a0 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 @@ -1,9 +1,12 @@ package app.myzel394.alibi.ui.components.RecorderScreen.organisms +import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.HorizontalDivider @@ -16,6 +19,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment 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.ui.components.RecorderScreen.atoms.RealtimeAudioVisualizer @@ -33,6 +37,7 @@ fun AudioRecordingStatus( audioRecorder: AudioRecorderModel, ) { val context = LocalContext.current + val configuration = LocalConfiguration.current.orientation val scope = rememberCoroutineScope() @@ -62,47 +67,104 @@ fun AudioRecordingStatus( .weight(1f), ) - RecordingStatus( - recordingTime = audioRecorder.recordingTime, - progress = audioRecorder.progress, - recordingStart = audioRecorder.recordingStart, - maxDuration = audioRecorder.settings!!.maxDuration, - ) + when (configuration) { + Configuration.ORIENTATION_LANDSCAPE -> { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Column( + verticalArrangement = Arrangement + .spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.weight(3f), + ) { + RecordingStatus( + recordingTime = audioRecorder.recordingTime, + progress = audioRecorder.progress, + recordingStart = audioRecorder.recordingStart, + maxDuration = audioRecorder.settings!!.maxDuration, + progressModifier = Modifier.fillMaxWidth(.9f), + ) - Column( - verticalArrangement = Arrangement - .spacedBy(32.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - MicrophoneStatus(audioRecorder) - - HorizontalDivider() - - RecordingControl( - isPaused = audioRecorder.isPaused, - recordingTime = audioRecorder.recordingTime, - onDelete = { - scope.launch { - runCatching { - audioRecorder.stopRecording(context) - } - runCatching { - audioRecorder.destroyService(context) - } - audioRecorder.batchesFolder!!.deleteRecordings() + MicrophoneStatus(audioRecorder) } - }, - onPauseResume = { - if (audioRecorder.isPaused) { - audioRecorder.resumeRecording() - } else { - audioRecorder.pauseRecording() - } - }, - onSave = { - audioRecorder.onRecordingSave(false) + + 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) + } + ) } - ) + } + + else -> { + RecordingStatus( + recordingTime = audioRecorder.recordingTime, + progress = audioRecorder.progress, + recordingStart = audioRecorder.recordingStart, + maxDuration = audioRecorder.settings!!.maxDuration, + ) + + Column( + verticalArrangement = Arrangement + .spacedBy(32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + MicrophoneStatus(audioRecorder) + + 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) + } + ) + } + } } } } \ No newline at end of file From e5d594a273087b5945bb5b23d122481baefac077 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 22:12:28 +0100 Subject: [PATCH 03/13] fix(ui): Make RealtimeAudioVisualizer store more amplitudes than current width would allow to allow user to rotate its device Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../RecorderScreen/atoms/RealTimeAudioVisualizer.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/RealTimeAudioVisualizer.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/RealTimeAudioVisualizer.kt index e1bd20f..b2d0808 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/RealTimeAudioVisualizer.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/atoms/RealTimeAudioVisualizer.kt @@ -61,11 +61,18 @@ fun RealtimeAudioVisualizer( } val configuration = LocalConfiguration.current - val screenWidth = with(LocalDensity.current) { configuration.screenWidthDp.dp.toPx() } + // Use greater value of width and height to make sure the amplitudes are shown + // when the user rotates the device + val availableSpace = with(LocalDensity.current) { + Math.max( + configuration.screenWidthDp.dp.toPx(), + configuration.screenHeightDp.dp.toPx() + ) + } - LaunchedEffect(screenWidth) { + LaunchedEffect(availableSpace) { // Add 1 to allow the visualizer to overflow the screen - audioRecorder.setMaxAmplitudesAmount(ceil(screenWidth.toInt() / BOX_DIFF).toInt() + 1) + audioRecorder.setMaxAmplitudesAmount(ceil(availableSpace.toInt() / BOX_DIFF).toInt() + 1) } Canvas(modifier = modifier) { From 590f3f2d13094b7cddf47c3f1949113688ad7171 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 22:41:31 +0100 Subject: [PATCH 04/13] fix(ui): Make VideoRecordingStatus more responsive for landscape Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../molecules/RecordingControl.kt | 2 +- .../organisms/VideoRecordingStatus.kt | 314 +++++++++++------- 2 files changed, 190 insertions(+), 126 deletions(-) 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 92140d9..8e1bdde 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 @@ -29,6 +29,7 @@ import kotlinx.coroutines.delay @Composable fun RecordingControl( modifier: Modifier = Modifier, + orientation: Int = LocalConfiguration.current.orientation, initialDelay: Long = 0L, isPaused: Boolean, recordingTime: Long, @@ -36,7 +37,6 @@ fun RecordingControl( onPauseResume: () -> Unit, onSave: () -> Unit, ) { - val orientation = LocalConfiguration.current.orientation val animateIn = rememberInitialRecordingAnimation(recordingTime) var deleteButtonAlphaIsIn by rememberSaveable { 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 c552cfc..5b43548 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 @@ -1,10 +1,13 @@ package app.myzel394.alibi.ui.components.RecorderScreen.organisms +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row 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 @@ -19,6 +22,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource @@ -37,141 +41,201 @@ import kotlinx.coroutines.launch fun VideoRecordingStatus( videoRecorder: VideoRecorderModel, ) { - val context = LocalContext.current - val scope = rememberCoroutineScope() - val availableCameras = CameraInfo.queryAvailableCameras(context) + val orientation = LocalConfiguration.current.orientation KeepScreenOn() - Column( - modifier = Modifier - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceBetween, - ) { - Box {} - Column( - verticalArrangement = Arrangement - .spacedBy(24.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Icon( - Icons.Default.CameraAlt, - contentDescription = null, - modifier = Modifier.size(64.dp) - ) - - if (videoRecorder.isStartingRecording) { + when (orientation) { + Configuration.ORIENTATION_LANDSCAPE -> { + Row( + modifier = Modifier.fillMaxSize(), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement + .spacedBy(32.dp), + modifier = Modifier + .weight(1f) + .fillMaxWidth(0.9f) + .align(Alignment.CenterVertically), + ) { + _VideoGeneralInfo(videoRecorder) + _VideoRecordingStatus(videoRecorder) + } Box( modifier = Modifier - .width(128.dp) - .height( - with(LocalDensity.current) { - MaterialTheme.typography.labelMedium.fontSize.toDp() - } - ) - .shimmer() - .background( - MaterialTheme.colorScheme.surfaceVariant, - MaterialTheme.shapes.small - ) - ) - } else { - Text( - stringResource( - R.string.form_value_selected, - if (CameraInfo.checkHasNormalCameras(availableCameras)) { - videoRecorder.cameraID.let { - if (it == CameraInfo.Lens.BACK.androidValue) - stringResource(R.string.ui_videoRecorder_action_start_settings_cameraLens_back_label) - else - stringResource(R.string.ui_videoRecorder_action_start_settings_cameraLens_front_label) - } - } else { - stringResource( - R.string.ui_videoRecorder_action_start_settings_cameraLens_label, - videoRecorder.cameraID - ) - } - ), - style = MaterialTheme.typography.labelMedium, - ) - } - } - - if (videoRecorder.isStartingRecording) { - Text( - stringResource(R.string.ui_videoRecorder_info_starting), - style = MaterialTheme.typography.labelMedium, - ) - } else { - RecordingStatus( - recordingTime = videoRecorder.recordingTime, - progress = videoRecorder.progress, - recordingStart = videoRecorder.recordingStart, - maxDuration = videoRecorder.settings!!.maxDuration, - ) - } - - Column( - verticalArrangement = Arrangement - .spacedBy(32.dp), - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(bottom = 32.dp), - ) { - if (!videoRecorder.isStartingRecording) { - val cameraControl = videoRecorder.recorderService!!.cameraControl!! - if (cameraControl.hasTorchAvailable()) { - val isTorchEnabled = cameraControl.isTorchEnabled() - - TorchStatus( - enabled = isTorchEnabled, - onChange = { - if (isTorchEnabled) { - cameraControl.disableTorch() - } else { - cameraControl.enableTorch() - } - }, - ) + .weight(1f) + .fillMaxWidth(0.9f) + ) { + Column( + verticalArrangement = Arrangement + .spacedBy(32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + _VideoControls(videoRecorder) + HorizontalDivider() + _PrimitiveControls(videoRecorder) + } } } + } - HorizontalDivider() - - if (!videoRecorder.isStartingRecording) { - RecordingControl( - // There may be some edge cases where the app may crash if the - // user stops or pauses the recording too soon, so we simply add a - // small delay to prevent that - initialDelay = 1000L, - isPaused = videoRecorder.isPaused, - recordingTime = videoRecorder.recordingTime, - onDelete = { - scope.launch { - runCatching { - videoRecorder.stopRecording(context) - } - runCatching { - videoRecorder.destroyService(context) - } - videoRecorder.batchesFolder!!.deleteRecordings() - } - }, - onPauseResume = { - if (videoRecorder.isPaused) { - videoRecorder.resumeRecording() - } else { - videoRecorder.pauseRecording() - } - }, - onSave = { - videoRecorder.onRecordingSave(false) - } - ) - } else { + else -> { + Column( + modifier = Modifier + .fillMaxSize() + .padding(bottom = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween, + ) { Box {} + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement + .spacedBy(16.dp), + ) { + _VideoGeneralInfo(videoRecorder) + _VideoRecordingStatus(videoRecorder) + } + + Column( + verticalArrangement = Arrangement + .spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + _VideoControls(videoRecorder) + HorizontalDivider() + _PrimitiveControls(videoRecorder) + } } } } + +} + +@Composable +fun _VideoGeneralInfo(videoRecorder: VideoRecorderModel) { + val context = LocalContext.current + val availableCameras = CameraInfo.queryAvailableCameras(context) + val orientation = LocalConfiguration.current.orientation + + Column( + verticalArrangement = Arrangement + .spacedBy(if (orientation == Configuration.ORIENTATION_LANDSCAPE) 12.dp else 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + Icons.Default.CameraAlt, + contentDescription = null, + modifier = Modifier.size(if (orientation == Configuration.ORIENTATION_LANDSCAPE) 48.dp else 64.dp) + ) + + if (videoRecorder.isStartingRecording) { + Box( + modifier = Modifier + .width(128.dp) + .height( + with(LocalDensity.current) { + MaterialTheme.typography.labelMedium.fontSize.toDp() + } + ) + .shimmer() + .background( + MaterialTheme.colorScheme.surfaceVariant, + MaterialTheme.shapes.small + ) + ) + } else { + Text( + stringResource( + R.string.form_value_selected, + if (CameraInfo.checkHasNormalCameras(availableCameras)) { + videoRecorder.cameraID.let { + if (it == CameraInfo.Lens.BACK.androidValue) + stringResource(R.string.ui_videoRecorder_action_start_settings_cameraLens_back_label) + else + stringResource(R.string.ui_videoRecorder_action_start_settings_cameraLens_front_label) + } + } else { + stringResource( + R.string.ui_videoRecorder_action_start_settings_cameraLens_label, + videoRecorder.cameraID + ) + } + ), + style = MaterialTheme.typography.labelMedium, + ) + } + } +} + +@Composable +fun _VideoRecordingStatus(videoRecorder: VideoRecorderModel) { + RecordingStatus( + recordingTime = videoRecorder.recordingTime, + progress = videoRecorder.progress, + recordingStart = videoRecorder.recordingStart, + maxDuration = videoRecorder.settings!!.maxDuration, + ) +} + +@Composable +fun _PrimitiveControls(videoRecorder: VideoRecorderModel) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + + RecordingControl( + orientation = Configuration.ORIENTATION_PORTRAIT, + // There may be some edge cases where the app may crash if the + // user stops or pauses the recording too soon, so we simply add a + // small delay to prevent that + initialDelay = 1000L, + isPaused = videoRecorder.isPaused, + recordingTime = videoRecorder.recordingTime, + onDelete = { + scope.launch { + runCatching { + videoRecorder.stopRecording(context) + } + runCatching { + videoRecorder.destroyService(context) + } + videoRecorder.batchesFolder!!.deleteRecordings() + } + }, + onPauseResume = { + if (videoRecorder.isPaused) { + videoRecorder.resumeRecording() + } else { + videoRecorder.pauseRecording() + } + }, + onSave = { + videoRecorder.onRecordingSave(false) + } + ) +} + +@Composable +fun _VideoControls(videoRecorder: VideoRecorderModel) { + if (!videoRecorder.isStartingRecording) { + val cameraControl = videoRecorder.recorderService!!.cameraControl!! + if (cameraControl.hasTorchAvailable()) { + val isTorchEnabled = cameraControl.isTorchEnabled() + + TorchStatus( + enabled = isTorchEnabled, + onChange = { + if (isTorchEnabled) { + cameraControl.disableTorch() + } else { + cameraControl.enableTorch() + } + }, + ) + } + } } \ No newline at end of file From f426cfe287e262426dd1b1887addb9133b2691f3 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 22:46:13 +0100 Subject: [PATCH 05/13] refactor: Use optimistic updates for torch Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../alibi/services/VideoRecorderService.kt | 16 +++++++++++++--- .../organisms/VideoRecordingStatus.kt | 12 +++++++++--- 2 files changed, 22 insertions(+), 6 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 abcadf4..5aaf273 100644 --- a/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt @@ -194,7 +194,9 @@ class VideoRecorderService : selectedCamera, videoCapture ) - cameraControl = CameraControl(camera!!) + cameraControl = CameraControl(camera!!).also { + it.init() + } onCameraControlAvailable() _cameraAvailableListener.complete(Unit) @@ -309,17 +311,25 @@ class VideoRecorderService : } class CameraControl( - val camera: Camera + val camera: Camera, + // Save state for optimistic updates + var torchEnabled: Boolean = false, ) { + fun init() { + torchEnabled = camera.cameraInfo.torchState.value == TorchState.ON + } + fun enableTorch() { + torchEnabled = true camera.cameraControl.enableTorch(true) } fun disableTorch() { + torchEnabled = false camera.cameraControl.enableTorch(false) } - fun isTorchEnabled(): Boolean { + fun isHardwareTorchReallyEnabled(): Boolean { return camera.cameraInfo.torchState.value == TorchState.ON } 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 5b43548..47479d9 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 @@ -19,7 +19,11 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration @@ -224,14 +228,16 @@ fun _VideoControls(videoRecorder: VideoRecorderModel) { if (!videoRecorder.isStartingRecording) { val cameraControl = videoRecorder.recorderService!!.cameraControl!! if (cameraControl.hasTorchAvailable()) { - val isTorchEnabled = cameraControl.isTorchEnabled() + var torchEnabled by rememberSaveable { mutableStateOf(cameraControl.torchEnabled) } TorchStatus( - enabled = isTorchEnabled, + enabled = torchEnabled, onChange = { - if (isTorchEnabled) { + if (torchEnabled) { + torchEnabled = false cameraControl.disableTorch() } else { + torchEnabled = true cameraControl.enableTorch() } }, From ba50d48f2336ae1b3d5652a2c9f4a9790212bb15 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 22:51:31 +0100 Subject: [PATCH 06/13] chore(docs): Add documentation for ui folder Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- app/src/main/java/app/myzel394/alibi/ui/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 app/src/main/java/app/myzel394/alibi/ui/README.md diff --git a/app/src/main/java/app/myzel394/alibi/ui/README.md b/app/src/main/java/app/myzel394/alibi/ui/README.md new file mode 100644 index 0000000..bbf323c --- /dev/null +++ b/app/src/main/java/app/myzel394/alibi/ui/README.md @@ -0,0 +1,14 @@ +# ui + +This folder contains all user interfaces. The folder is structured as follows: + +* `components`: contains all reusable components + * `atoms`, `molecules`, `organisms`, `pages`: contains components that are generic and can be + reused in different contexts + * `Screen/{atoms,molecules,organisms,pages}`: contains components that are specific to a + screen +* `screens`: contains all screens. Screens are composed of components from the `components` folder +* `models`: contains view models used by the screens +* `utils`: contains general utility functions + +The root Kotlin files are used for the general setup of the UI. \ No newline at end of file From 08edde342195c7a18ae07a3deb61b9c2161f486e Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 22:51:42 +0100 Subject: [PATCH 07/13] cleanup(docs): Improve services README Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- app/src/main/java/app/myzel394/alibi/services/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/app/myzel394/alibi/services/README.md b/app/src/main/java/app/myzel394/alibi/services/README.md index bac59f3..f354d91 100644 --- a/app/src/main/java/app/myzel394/alibi/services/README.md +++ b/app/src/main/java/app/myzel394/alibi/services/README.md @@ -1,3 +1,9 @@ +# services + +This folder contains all available services. + +## VideoRecorderService + I found it a bit confusing on how to properly handle the services, so I made this diagram to help me understand it better. I hope it helps you too. From 54c1e526ed41fa67d373c4a5dcdac3ed886b653e Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 22:55:38 +0100 Subject: [PATCH 08/13] fix(ui): Make SaveFolderTile explanation scrollable Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../ui/components/SettingsScreen/Tiles/SaveFolderTile.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/SaveFolderTile.kt b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/SaveFolderTile.kt index dd3aed4..8609dc5 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/SaveFolderTile.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/SaveFolderTile.kt @@ -4,8 +4,6 @@ import android.Manifest import android.content.Intent import android.net.Uri import android.os.Build -import android.os.Environment -import android.provider.MediaStore import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -282,7 +280,9 @@ fun MediaFoldersExplanationDialog( }, text = { Column( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(32.dp), ) { From 797ecce9ca908b3320d5d1d9b5fe6227794b60c6 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:07:00 +0100 Subject: [PATCH 09/13] fix(ui): Remove Spacer on StartRecording on landscape to provider more space for the buttons Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../ui/components/RecorderScreen/organisms/StartRecording.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/StartRecording.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/StartRecording.kt index a57d274..0ea2ddc 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/StartRecording.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/StartRecording.kt @@ -106,8 +106,6 @@ fun StartRecording( verticalArrangement = Arrangement.SpaceBetween, horizontalAlignment = Alignment.CenterHorizontally, ) { - Spacer(modifier = Modifier.weight(1f)) - when (orientation) { Configuration.ORIENTATION_LANDSCAPE -> { Row( @@ -131,6 +129,8 @@ fun StartRecording( } else -> { + Spacer(modifier = Modifier.weight(1f)) + if (showAudioRecorder) AudioRecordingStart( audioRecorder = audioRecorder, From b0f7d98d1f2290188e7b180c590db926183344f6 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:07:16 +0100 Subject: [PATCH 10/13] fix(ui): Make InternalFolderExplanationDialog scrollable Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../ui/components/SettingsScreen/Tiles/SaveFolderTile.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/SaveFolderTile.kt b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/SaveFolderTile.kt index 1b7863c..ec9f11d 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/SaveFolderTile.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/SaveFolderTile.kt @@ -410,7 +410,9 @@ fun InternalFolderExplanationDialog( }, text = { Column( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(32.dp), ) { @@ -527,7 +529,8 @@ fun SelectionSheet( ) if (!SUPPORTS_SAVING_VIDEOS_IN_CUSTOM_FOLDERS) { Column( - modifier = Modifier.padding(horizontal = 32.dp, vertical = 12.dp), + modifier = Modifier + .padding(horizontal = 32.dp, vertical = 12.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp), ) { From 86382afc1be436d955a88b326b4925dee487435c Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:18:52 +0100 Subject: [PATCH 11/13] feat: Add RotateDeviceToPortrait to CustomRecordingNotificationsScreen Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../atoms/RotateDeviceToPortrait.kt | 39 +++++++++ .../CustomRecordingNotificationsScreen.kt | 86 ++++++++++--------- app/src/main/res/values/strings.xml | 3 +- 3 files changed, 88 insertions(+), 40 deletions(-) create mode 100644 app/src/main/java/app/myzel394/alibi/ui/components/atoms/RotateDeviceToPortrait.kt diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/atoms/RotateDeviceToPortrait.kt b/app/src/main/java/app/myzel394/alibi/ui/components/atoms/RotateDeviceToPortrait.kt new file mode 100644 index 0000000..232bde3 --- /dev/null +++ b/app/src/main/java/app/myzel394/alibi/ui/components/atoms/RotateDeviceToPortrait.kt @@ -0,0 +1,39 @@ +package app.myzel394.alibi.ui.components.atoms + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Rotate90DegreesCw +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +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.unit.dp +import app.myzel394.alibi.R + +@Composable +fun RotateDeviceToPortrait( + modifier: Modifier = Modifier + .fillMaxWidth() + .widthIn(max = 300.dp), +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + Icons.Default.Rotate90DegreesCw, + contentDescription = stringResource(R.string.ui_rotateDevice_portrait_label), + modifier = Modifier.size(48.dp), + tint = MaterialTheme.colorScheme.tertiary, + ) + Text(stringResource(R.string.ui_rotateDevice_portrait_label)) + } +} \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/screens/CustomRecordingNotificationsScreen.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/CustomRecordingNotificationsScreen.kt index 3d119f9..ca71964 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/screens/CustomRecordingNotificationsScreen.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/screens/CustomRecordingNotificationsScreen.kt @@ -1,18 +1,11 @@ package app.myzel394.alibi.ui.screens -import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.scrollable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column +import android.content.res.Configuration +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -29,22 +22,20 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import app.myzel394.alibi.R import app.myzel394.alibi.dataStore import app.myzel394.alibi.db.AppSettings import app.myzel394.alibi.db.NotificationSettings import app.myzel394.alibi.ui.components.CustomRecordingNotificationsScreen.atoms.LandingElement -import app.myzel394.alibi.ui.components.CustomRecordingNotificationsScreen.atoms.NotificationPresetSelect -import app.myzel394.alibi.ui.components.CustomRecordingNotificationsScreen.molecules.EditNotificationInput import app.myzel394.alibi.ui.components.CustomRecordingNotificationsScreen.organisms.NotificationEditor +import app.myzel394.alibi.ui.components.atoms.RotateDeviceToPortrait import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -81,7 +72,7 @@ fun CustomRecordingNotificationsScreen( navigationIcon = { IconButton(onClick = onBackNavigate) { Icon( - Icons.Default.ArrowBack, + Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back" ) } @@ -92,33 +83,50 @@ fun CustomRecordingNotificationsScreen( modifier = Modifier .nestedScroll(scrollBehavior.nestedScrollConnection) ) { padding -> - if (showEditor) { - val scope = rememberCoroutineScope() + val orientation = LocalConfiguration.current.orientation - NotificationEditor( - modifier = Modifier - .padding(padding) - .padding(vertical = 16.dp), - onNotificationChange = { notificationSettings -> - scope.launch { - dataStore.updateData { settings -> - settings.setNotificationSettings(notificationSettings.let { - if (it.preset == NotificationSettings.Preset.Default) - null - else - it - }) + when (orientation) { + Configuration.ORIENTATION_PORTRAIT -> { + if (showEditor) { + val scope = rememberCoroutineScope() + + NotificationEditor( + modifier = Modifier + .padding(padding) + .padding(vertical = 16.dp), + onNotificationChange = { notificationSettings -> + scope.launch { + dataStore.updateData { settings -> + settings.setNotificationSettings(notificationSettings.let { + if (it.preset == NotificationSettings.Preset.Default) + null + else + it + }) + } + } + onBackNavigate() } - } - onBackNavigate() + ) + } else { + LandingElement( + onOpenEditor = { + showEditor = true + } + ) } - ) - } else { - LandingElement( - onOpenEditor = { - showEditor = true + } + + else -> { + Box( + modifier = Modifier + .fillMaxSize() + .padding(padding), + contentAlignment = Alignment.Center, + ) { + RotateDeviceToPortrait() } - ) + } } } } \ 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 f17128b..73a1745 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - + Alibi Cancel @@ -189,4 +189,5 @@ Tap on a folder to open it in the Files app Unknown 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 \ No newline at end of file From 8d070b370e680d3bf22b4ff5a3e78ce95e7b6120 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:20:16 +0100 Subject: [PATCH 12/13] fix: Add missing i18n strings to CustomRecordingNotificationsScreen Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../alibi/ui/screens/CustomRecordingNotificationsScreen.kt | 3 ++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/myzel394/alibi/ui/screens/CustomRecordingNotificationsScreen.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/CustomRecordingNotificationsScreen.kt index ca71964..56fab20 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/screens/CustomRecordingNotificationsScreen.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/screens/CustomRecordingNotificationsScreen.kt @@ -71,9 +71,10 @@ fun CustomRecordingNotificationsScreen( }, navigationIcon = { IconButton(onClick = onBackNavigate) { + val label = stringResource(R.string.goBack) Icon( Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Back" + contentDescription = label, ) } }, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 73a1745..15b1d26 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -190,4 +190,5 @@ Unknown 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 \ No newline at end of file From a73a1efcd9be5ea9b3a75c099956a6fb51a7e5d4 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:31:30 +0100 Subject: [PATCH 13/13] chore(ui): Update icons to auto mirrored version Signed-off-by: Myzel394 <50424412+Myzel394@users.noreply.github.com> --- .../molecules/AudioRecordingStart.kt | 27 ++------------ .../molecules/VideoRecordingStart.kt | 36 ++----------------- .../components/atoms/PermissionRequester.kt | 9 +++-- .../myzel394/alibi/ui/screens/AboutScreen.kt | 19 +++++----- .../alibi/ui/screens/SettingsScreen.kt | 21 +++++------ 5 files changed, 28 insertions(+), 84 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/AudioRecordingStart.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/AudioRecordingStart.kt index c5612c2..2eba285 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/AudioRecordingStart.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/AudioRecordingStart.kt @@ -1,45 +1,22 @@ package app.myzel394.alibi.ui.components.RecorderScreen.molecules import android.Manifest -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.InsertDriveFile +import androidx.compose.material.icons.automirrored.filled.InsertDriveFile import androidx.compose.material.icons.filled.Mic -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -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.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp import app.myzel394.alibi.R import app.myzel394.alibi.db.AppSettings -import app.myzel394.alibi.ui.SUPPORTS_SCOPED_STORAGE import app.myzel394.alibi.ui.components.RecorderScreen.atoms.BigButton import app.myzel394.alibi.ui.components.atoms.PermissionRequester import app.myzel394.alibi.ui.models.AudioRecorderModel -import app.myzel394.alibi.ui.utils.PermissionHelper -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext @Composable fun AudioRecordingStart( @@ -62,7 +39,7 @@ fun AudioRecordingStart( PermissionRequester( permission = Manifest.permission.WRITE_EXTERNAL_STORAGE, - icon = Icons.Default.InsertDriveFile, + icon = Icons.AutoMirrored.Filled.InsertDriveFile, onPermissionAvailable = { startRecording = true } diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/VideoRecordingStart.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/VideoRecordingStart.kt index d6acb1e..40fd269 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/VideoRecordingStart.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/VideoRecordingStart.kt @@ -2,50 +2,18 @@ package app.myzel394.alibi.ui.components.RecorderScreen.molecules import android.Manifest import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.indication -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -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.shape.CircleShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.InsertDriveFile import androidx.compose.material.icons.filled.CameraAlt -import androidx.compose.material.icons.filled.InsertDriveFile -import androidx.compose.material.ripple.rememberRipple -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -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.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import app.myzel394.alibi.R import app.myzel394.alibi.db.AppSettings -import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE -import app.myzel394.alibi.ui.SUPPORTS_SCOPED_STORAGE import app.myzel394.alibi.ui.components.RecorderScreen.atoms.BigButton import app.myzel394.alibi.ui.components.atoms.PermissionRequester import app.myzel394.alibi.ui.models.VideoRecorderModel @@ -83,7 +51,7 @@ fun VideoRecordingStart( PermissionRequester( permission = Manifest.permission.WRITE_EXTERNAL_STORAGE, - icon = Icons.Default.InsertDriveFile, + icon = Icons.AutoMirrored.Filled.InsertDriveFile, onPermissionAvailable = { showSheet = true } diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/atoms/PermissionRequester.kt b/app/src/main/java/app/myzel394/alibi/ui/components/atoms/PermissionRequester.kt index a493c02..969292e 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/atoms/PermissionRequester.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/atoms/PermissionRequester.kt @@ -1,7 +1,6 @@ package app.myzel394.alibi.ui.components.atoms import android.app.Activity -import android.content.pm.PackageManager import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Column @@ -10,9 +9,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.OpenInNew import androidx.compose.material.icons.filled.Cancel import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.OpenInNew import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -21,14 +20,14 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext -import androidx.compose.runtime.getValue import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.app.ActivityCompat @@ -173,7 +172,7 @@ fun PermissionRequester( contentPadding = ButtonDefaults.ButtonWithIconContentPadding, ) { Icon( - Icons.Default.OpenInNew, + Icons.AutoMirrored.Filled.OpenInNew, contentDescription = null, modifier = Modifier.size(ButtonDefaults.IconSize), ) diff --git a/app/src/main/java/app/myzel394/alibi/ui/screens/AboutScreen.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/AboutScreen.kt index 019611b..4df51fb 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/screens/AboutScreen.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/screens/AboutScreen.kt @@ -1,6 +1,5 @@ package app.myzel394.alibi.ui.screens -import android.widget.Space import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -17,8 +16,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.OpenInNew +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.OpenInNew import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -42,11 +41,10 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import app.myzel394.alibi.R import app.myzel394.alibi.BuildConfig -import app.myzel394.alibi.ui.TRANSLATION_HELP_URL +import app.myzel394.alibi.R import app.myzel394.alibi.ui.REPO_URL +import app.myzel394.alibi.ui.TRANSLATION_HELP_URL import app.myzel394.alibi.ui.components.AboutScreen.atoms.DonationsTile import app.myzel394.alibi.ui.components.AboutScreen.atoms.GPGKeyOverview @@ -68,9 +66,10 @@ fun AboutScreen( }, navigationIcon = { IconButton(onClick = onBackNavigate) { + val label = stringResource(R.string.goBack) Icon( - Icons.Default.ArrowBack, - contentDescription = "Back" + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = label, ) } }, @@ -159,7 +158,7 @@ fun AboutScreen( fontWeight = FontWeight.Bold, ) Icon( - Icons.Default.OpenInNew, + Icons.AutoMirrored.Filled.OpenInNew, contentDescription = null, modifier = Modifier.size(ButtonDefaults.IconSize) ) @@ -196,7 +195,7 @@ fun AboutScreen( fontWeight = FontWeight.Bold, ) Icon( - Icons.Default.OpenInNew, + Icons.AutoMirrored.Filled.OpenInNew, contentDescription = null, modifier = Modifier.size(ButtonDefaults.IconSize) ) diff --git a/app/src/main/java/app/myzel394/alibi/ui/screens/SettingsScreen.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/SettingsScreen.kt index d95edfb..8881c1e 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/screens/SettingsScreen.kt @@ -10,7 +10,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -38,21 +38,21 @@ import app.myzel394.alibi.dataStore import app.myzel394.alibi.ui.SUPPORTS_DARK_MODE_NATIVELY import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.AboutTile import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.AudioRecorderEncoderTile -import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.VideoRecorderFrameRateTile +import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.AudioRecorderOutputFormatTile +import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.AudioRecorderSamplingRateTile +import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.AudioRecorderShowAllMicrophonesTile import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.CustomNotificationTile import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.DeleteRecordingsImmediatelyTile import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.DividerTitle -import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.VideoRecorderQualityTile +import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.EnableAppLockTile import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.ImportExport -import app.myzel394.alibi.ui.components.SettingsScreen.atoms.InAppLanguagePicker import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.IntervalDurationTile import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.MaxDurationTile -import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.AudioRecorderOutputFormatTile -import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.AudioRecorderSamplingRateTile import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.SaveFolderTile -import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.AudioRecorderShowAllMicrophonesTile -import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.EnableAppLockTile import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.VideoRecorderBitrateTile +import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.VideoRecorderFrameRateTile +import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.VideoRecorderQualityTile +import app.myzel394.alibi.ui.components.SettingsScreen.atoms.InAppLanguagePicker import app.myzel394.alibi.ui.components.SettingsScreen.atoms.ThemeSelector import app.myzel394.alibi.ui.components.atoms.GlobalSwitch import app.myzel394.alibi.ui.components.atoms.MessageBox @@ -99,9 +99,10 @@ fun SettingsScreen( }, navigationIcon = { IconButton(onClick = onBackNavigate) { + val label = stringResource(R.string.goBack) Icon( - Icons.Default.ArrowBack, - contentDescription = "Back" + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = label, ) } },