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. 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/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/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 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/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) { 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/RecordingControl.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/molecules/RecordingControl.kt index ad9fa7b..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 @@ -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,8 @@ import kotlinx.coroutines.delay @Composable fun RecordingControl( + modifier: Modifier = Modifier, + orientation: Int = LocalConfiguration.current.orientation, initialDelay: Long = 0L, isPaused: Boolean, recordingTime: Long, @@ -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/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/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 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..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 @@ -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)) + 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 -> { + Spacer(modifier = Modifier.weight(1f)) + + if (showAudioRecorder) + AudioRecordingStart( + audioRecorder = audioRecorder, + appSettings = appSettings, + ) + VideoRecordingStart( + videoRecorder = videoRecorder, + appSettings = appSettings, + onHideAudioRecording = onHideTopBar, + onShowAudioRecording = onShowTopBar, + showPreview = !showAudioRecorder, + ) + } + } - 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 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..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 @@ -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 @@ -16,9 +19,14 @@ 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 import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource @@ -37,141 +45,203 @@ 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()) { + var torchEnabled by rememberSaveable { mutableStateOf(cameraControl.torchEnabled) } + + TorchStatus( + enabled = torchEnabled, + onChange = { + if (torchEnabled) { + torchEnabled = false + cameraControl.disableTorch() + } else { + torchEnabled = true + cameraControl.enableTorch() + } + }, + ) + } + } } \ No newline at end of file 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 f9cbcae..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 @@ -305,7 +305,9 @@ fun DCIMFolderExplanationDialog( }, text = { Column( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(32.dp), ) { @@ -408,7 +410,9 @@ fun InternalFolderExplanationDialog( }, text = { Column( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(32.dp), ) { @@ -525,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), ) { 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/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/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/CustomRecordingNotificationsScreen.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/CustomRecordingNotificationsScreen.kt index 3d119f9..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 @@ -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) @@ -80,9 +71,10 @@ fun CustomRecordingNotificationsScreen( }, navigationIcon = { IconButton(onClick = onBackNavigate) { + val label = stringResource(R.string.goBack) Icon( - Icons.Default.ArrowBack, - contentDescription = "Back" + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = label, ) } }, @@ -92,33 +84,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/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, ) } }, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f17128b..15b1d26 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,6 @@ 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 + Back \ No newline at end of file