From 9598cd45fa4a653eca15e7ef62b0162c019b44f4 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sat, 2 Dec 2023 17:40:11 +0100
Subject: [PATCH] feat: Add VideoRecorderBitrateTile
---
.../java/app/myzel394/alibi/db/AppSettings.kt | 18 +++
.../Tiles/VideoRecorderBitrateTile.kt | 145 ++++++++++++++++++
.../alibi/ui/screens/SettingsScreen.kt | 13 +-
app/src/main/res/values/strings.xml | 6 +
4 files changed, 177 insertions(+), 5 deletions(-)
create mode 100644 app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/VideoRecorderBitrateTile.kt
diff --git a/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt b/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt
index 558f605..b59c132 100644
--- a/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt
+++ b/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt
@@ -40,6 +40,10 @@ data class AppSettings(
return copy(audioRecorderSettings = audioRecorderSettings)
}
+ fun setVideoRecorderSettings(videoRecorderSettings: VideoRecorderSettings): AppSettings {
+ return copy(videoRecorderSettings = videoRecorderSettings)
+ }
+
fun setNotificationSettings(notificationSettings: NotificationSettings?): AppSettings {
return copy(notificationSettings = notificationSettings)
}
@@ -401,6 +405,20 @@ data class VideoRecorderSettings(
"FHD" to Quality.FHD,
"UHD" to Quality.UHD,
)
+
+ val EXAMPLE_BITRATE_VALUES = listOf(
+ null,
+ 500 * 1000,
+ // 1 Mbps
+ 1 * 1000 * 1000,
+ 2 * 1000 * 1000,
+ 4 * 1000 * 1000,
+ 8 * 1000 * 1000,
+ 16 * 1000 * 1000,
+ 32 * 1000 * 1000,
+ 50 * 1000 * 1000,
+ 100 * 1000 * 1000,
+ )
}
}
diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/VideoRecorderBitrateTile.kt b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/VideoRecorderBitrateTile.kt
new file mode 100644
index 0000000..a8a842e
--- /dev/null
+++ b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/VideoRecorderBitrateTile.kt
@@ -0,0 +1,145 @@
+package app.myzel394.alibi.ui.components.SettingsScreen.Tiles
+
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Tune
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.KeyboardType
+import app.myzel394.alibi.R
+import app.myzel394.alibi.dataStore
+import app.myzel394.alibi.db.AppSettings
+import app.myzel394.alibi.db.AudioRecorderSettings
+import app.myzel394.alibi.db.VideoRecorderSettings
+import app.myzel394.alibi.ui.components.atoms.ExampleListRoulette
+import app.myzel394.alibi.ui.components.atoms.SettingsTile
+import app.myzel394.alibi.ui.utils.IconResource
+import com.maxkeppeker.sheets.core.models.base.Header
+import com.maxkeppeker.sheets.core.models.base.IconSource
+import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
+import com.maxkeppeler.sheets.input.InputDialog
+import com.maxkeppeler.sheets.input.models.InputHeader
+import com.maxkeppeler.sheets.input.models.InputSelection
+import com.maxkeppeler.sheets.input.models.InputTextField
+import com.maxkeppeler.sheets.input.models.InputTextFieldType
+import com.maxkeppeler.sheets.input.models.ValidationResult
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun VideoRecorderBitrateTile(
+ settings: AppSettings,
+) {
+ val scope = rememberCoroutineScope()
+ val showDialog = rememberUseCaseState()
+ val dataStore = LocalContext.current.dataStore
+
+ fun updateValue(bitRate: Int?) {
+ scope.launch {
+ dataStore.updateData {
+ it.setVideoRecorderSettings(
+ it.videoRecorderSettings.setTargetedVideoBitRate(bitRate)
+ )
+ }
+ }
+ }
+
+ val notNumberLabel = stringResource(R.string.form_error_type_notNumber)
+ InputDialog(
+ state = showDialog,
+ header = Header.Default(
+ title = stringResource(R.string.ui_settings_option_videoTargetedBitrate_title),
+ icon = IconSource(
+ painter = IconResource.fromImageVector(Icons.Default.Tune).asPainterResource(),
+ contentDescription = null,
+ )
+ ),
+ selection = InputSelection(
+ input = listOf(
+ InputTextField(
+ header = InputHeader(
+ title = stringResource(id = R.string.ui_settings_option_videoTargetedBitrate_explanation),
+ ),
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Number,
+ ),
+ type = InputTextFieldType.OUTLINED,
+ text = if (settings.videoRecorderSettings.targetedVideoBitRate == null) "" else (settings.videoRecorderSettings.targetedVideoBitRate / 1000).toString(),
+ validationListener = { text ->
+ val bitRate = text?.toIntOrNull()
+
+ if (bitRate == null) {
+ return@InputTextField ValidationResult.Invalid(notNumberLabel)
+ }
+
+ ValidationResult.Valid
+ },
+ key = "bitrate",
+ )
+ ),
+ ) { result ->
+ val bitRate = result.getString("bitrate")?.toIntOrNull() ?: return@InputSelection
+
+ updateValue(bitRate * 1000)
+ }
+ )
+ SettingsTile(
+ title = stringResource(R.string.ui_settings_option_videoTargetedBitrate_title),
+ description = stringResource(R.string.ui_settings_option_bitrate_description),
+ leading = {
+ Icon(
+ Icons.Default.Tune,
+ contentDescription = null,
+ )
+ },
+ trailing = {
+ Button(
+ onClick = showDialog::show,
+ colors = ButtonDefaults.filledTonalButtonColors(
+ containerColor = MaterialTheme.colorScheme.surfaceVariant,
+ ),
+ shape = MaterialTheme.shapes.medium,
+ ) {
+ Text(formatBitrate(settings.videoRecorderSettings.targetedVideoBitRate))
+ }
+ },
+ extra = {
+ ExampleListRoulette(
+ items = VideoRecorderSettings.EXAMPLE_BITRATE_VALUES,
+ onItemSelected = ::updateValue,
+ ) { bitRate ->
+ Text(formatBitrate(bitRate))
+ }
+ }
+ )
+}
+
+@Composable
+fun formatBitrate(bitrate: Int?): String {
+ return if (bitrate == null)
+ stringResource(R.string.ui_settings_value_auto_label)
+ else if (bitrate >= 1000 * 1000 && bitrate % (1000 * 1000) == 0)
+ stringResource(
+ R.string.format_mbps,
+ bitrate / 1000 / 1000,
+ )
+ else if (bitrate >= 1000 && bitrate % 1000 == 0)
+ stringResource(
+ R.string.format_kbps,
+ bitrate / 1000,
+ )
+ else
+ stringResource(
+ R.string.format_bps,
+ bitrate,
+ )
+}
+
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 400c542..a208a48 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
@@ -1,13 +1,11 @@
package app.myzel394.alibi.ui.screens
import androidx.compose.animation.AnimatedVisibility
-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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -40,7 +38,7 @@ import app.myzel394.alibi.R
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.AudioRecorderBitrateTile
+import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.VideoRecorderBitrateTile
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
@@ -164,15 +162,20 @@ fun SettingsScreen(
title = stringResource(R.string.ui_settings_sections_audio_title),
description = stringResource(R.string.ui_settings_sections_audio_description),
)
-
AudioRecorderShowAllMicrophonesTile(settings = settings)
- AudioRecorderBitrateTile(settings = settings)
+ VideoRecorderBitrateTile(settings = settings)
AudioRecorderSamplingRateTile(settings = settings)
AudioRecorderEncoderTile(
snackbarHostState = snackbarHostState,
settings = settings
)
AudioRecorderOutputFormatTile(settings = settings)
+
+ DividerTitle(
+ title = stringResource(R.string.ui_settings_sections_video_title),
+ description = stringResource(R.string.ui_settings_sections_video_description),
+ )
+ VideoRecorderBitrateTile(settings = settings)
}
Divider(
modifier = Modifier
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d67172c..ee6bbe6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -122,4 +122,10 @@
Open
Audio Recording
Only applies to audio recordings
+ Video Recording
+ Only applies to video recordings
+ Targeted Bitrate
+ Bitrate for the video recording. Only applies to the video itself, not the audio. The actual bitrate may be different depending on what you will be recording.
+ %s MB/s
+ %s B/s
\ No newline at end of file