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 b59c132..1bc97bf 100644 --- a/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt +++ b/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt @@ -419,6 +419,15 @@ data class VideoRecorderSettings( 50 * 1000 * 1000, 100 * 1000 * 1000, ) + + val EXAMPLE_FRAME_RATE_VALUES = listOf( + null, + 24, + 30, + 60, + 120, + 240, + ) } } diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/VideoFrameRate.kt b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/VideoFrameRate.kt new file mode 100644 index 0000000..1ae5cda --- /dev/null +++ b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/Tiles/VideoFrameRate.kt @@ -0,0 +1,127 @@ +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.BrokenImage +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.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 VideoFrameRate( + settings: AppSettings, +) { + val scope = rememberCoroutineScope() + val showDialog = rememberUseCaseState() + val dataStore = LocalContext.current.dataStore + + fun updateValue(frameRate: Int?) { + scope.launch { + dataStore.updateData { + it.setVideoRecorderSettings( + it.videoRecorderSettings.setTargetFrameRate(frameRate) + ) + } + } + } + + val notNumberLabel = stringResource(R.string.form_error_type_notNumber) + InputDialog( + state = showDialog, + header = Header.Default( + title = stringResource(R.string.ui_settings_option_videoTargetedFrameRate_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_videoTargetedFrameRate_explanation), + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + ), + type = InputTextFieldType.OUTLINED, + text = if (settings.videoRecorderSettings.targetFrameRate == null) "" else settings.videoRecorderSettings.targetFrameRate.toString(), + validationListener = { text -> + val frameRate = text?.toIntOrNull() + + if (frameRate == null) { + return@InputTextField ValidationResult.Invalid(notNumberLabel) + } + + ValidationResult.Valid + }, + key = "framerate", + ) + ), + ) { result -> + val frameRate = result.getString("framerate")?.toIntOrNull() ?: return@InputSelection + + updateValue(frameRate) + } + ) + SettingsTile( + title = stringResource(R.string.ui_settings_option_videoTargetedFrameRate_title), + leading = { + Icon( + Icons.Default.BrokenImage, + contentDescription = null, + ) + }, + trailing = { + Button( + onClick = showDialog::show, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + ), + shape = MaterialTheme.shapes.medium, + ) { + if (settings.videoRecorderSettings.targetFrameRate == null) + Text(stringResource(R.string.ui_settings_value_auto_label)) + else + Text(settings.videoRecorderSettings.targetFrameRate.toString()) + } + }, + extra = { + ExampleListRoulette( + items = VideoRecorderSettings.EXAMPLE_FRAME_RATE_VALUES, + onItemSelected = ::updateValue, + ) { frameRate -> + Text( + frameRate?.toString() ?: stringResource(R.string.ui_settings_value_auto_label) + ) + } + } + ) +} 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 a208a48..0248ce1 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 @@ -38,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.VideoRecorderBitrateTile +import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.VideoFrameRate 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 @@ -51,6 +51,7 @@ import app.myzel394.alibi.ui.components.SettingsScreen.Tiles.AudioRecorderOutput 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.VideoRecorderBitrateTile 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 @@ -163,7 +164,7 @@ fun SettingsScreen( description = stringResource(R.string.ui_settings_sections_audio_description), ) AudioRecorderShowAllMicrophonesTile(settings = settings) - VideoRecorderBitrateTile(settings = settings) + VideoFrameRate(settings = settings) AudioRecorderSamplingRateTile(settings = settings) AudioRecorderEncoderTile( snackbarHostState = snackbarHostState, @@ -176,6 +177,7 @@ fun SettingsScreen( description = stringResource(R.string.ui_settings_sections_video_description), ) VideoRecorderBitrateTile(settings = settings) + VideoFrameRate(settings = settings) } Divider( modifier = Modifier diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ee6bbe6..bc51404 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -128,4 +128,7 @@ 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 + Targeted Frame Rate + How many frames per second should be recorded. + The actual frame rate may be different. This can for example happen if the device is not able to record with the specified frame rate. \ No newline at end of file