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 3a5d7c5..385f1e4 100644 --- a/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt +++ b/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt @@ -228,6 +228,12 @@ data class AudioRecorderSettings( return copy(forceExactMaxDuration = forceExactMaxDuration) } + fun isOutputFormatCompatible(format: Int): Boolean { + val supportedFormats = ENCODER_SUPPORTED_OUTPUT_FORMATS_MAP[getEncoder()]!! + + return supportedFormats.contains(format) + } + companion object { fun getDefaultInstance(): AudioRecorderSettings = AudioRecorderSettings() val EXAMPLE_MAX_DURATIONS = listOf( @@ -284,5 +290,41 @@ data class AudioRecorderSettings( 6 to "VORBIS", 7 to "OPUS", ) + val ENCODER_SUPPORTED_OUTPUT_FORMATS_MAP: Map> = mutableMapOf( + MediaRecorder.AudioEncoder.AAC to arrayOf( + MediaRecorder.OutputFormat.THREE_GPP, + MediaRecorder.OutputFormat.MPEG_4, + MediaRecorder.OutputFormat.AAC_ADTS, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) MediaRecorder.OutputFormat.MPEG_2_TS else null, + ).filterNotNull().toTypedArray(), + MediaRecorder.AudioEncoder.AAC_ELD to arrayOf( + MediaRecorder.OutputFormat.THREE_GPP, + MediaRecorder.OutputFormat.MPEG_4, + MediaRecorder.OutputFormat.AAC_ADTS, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) MediaRecorder.OutputFormat.MPEG_2_TS else null, + ).filterNotNull().toTypedArray(), + MediaRecorder.AudioEncoder.AMR_NB to arrayOf( + MediaRecorder.OutputFormat.THREE_GPP, + MediaRecorder.OutputFormat.AMR_NB, + ), + MediaRecorder.AudioEncoder.AMR_WB to arrayOf( + MediaRecorder.OutputFormat.THREE_GPP, + MediaRecorder.OutputFormat.AMR_WB, + ), + MediaRecorder.AudioEncoder.HE_AAC to arrayOf( + MediaRecorder.OutputFormat.THREE_GPP, + MediaRecorder.OutputFormat.MPEG_4, + MediaRecorder.OutputFormat.AAC_ADTS, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) MediaRecorder.OutputFormat.MPEG_2_TS else null, + ).filterNotNull().toTypedArray(), + MediaRecorder.AudioEncoder.VORBIS to arrayOf( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) MediaRecorder.OutputFormat.OGG else null, + MediaRecorder.OutputFormat.MPEG_4 + ).filterNotNull().toTypedArray(), + ).apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + put(MediaRecorder.AudioEncoder.OPUS, arrayOf(MediaRecorder.OutputFormat.OGG)) + } + }.toMap() } } diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/atoms/OutputFormatTile.kt b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/atoms/OutputFormatTile.kt index 6e844b9..14c390c 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/atoms/OutputFormatTile.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/atoms/OutputFormatTile.kt @@ -1,5 +1,6 @@ package app.myzel394.alibi.ui.components.SettingsScreen.atoms +import android.media.MediaRecorder import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AudioFile import androidx.compose.material3.Button @@ -7,9 +8,13 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarVisuals import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -19,6 +24,9 @@ import app.myzel394.alibi.db.AppSettings import app.myzel394.alibi.db.AudioRecorderSettings 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.list.ListDialog import com.maxkeppeler.sheets.list.models.ListOption @@ -35,6 +43,9 @@ fun OutputFormatTile() { .data .collectAsState(initial = AppSettings.getDefaultInstance()) .value + val availableOptions = if (settings.audioRecorderSettings.encoder == null) + AudioRecorderSettings.OUTPUT_FORMAT_INDEX_TEXT_MAP.keys.toTypedArray() + else arrayOf(MediaRecorder.OutputFormat.DEFAULT, *AudioRecorderSettings.ENCODER_SUPPORTED_OUTPUT_FORMATS_MAP[settings.audioRecorderSettings.encoder]!!) fun updateValue(outputFormat: Int?) { scope.launch { @@ -48,16 +59,23 @@ fun OutputFormatTile() { ListDialog( state = showDialog, + header = Header.Default( + title = stringResource(R.string.ui_settings_option_outputFormat_title), + icon = IconSource( + painter = IconResource.fromImageVector(Icons.Default.AudioFile).asPainterResource(), + contentDescription = null, + ) + ), selection = ListSelection.Single( showRadioButtons = true, - options = IntRange(0, 11).map { index -> + options = availableOptions.map { option -> ListOption( - titleText = AudioRecorderSettings.OUTPUT_FORMAT_INDEX_TEXT_MAP[index]!!, - selected = settings.audioRecorderSettings.outputFormat == index, + titleText = AudioRecorderSettings.OUTPUT_FORMAT_INDEX_TEXT_MAP[option]!!, + selected = settings.audioRecorderSettings.outputFormat == option, ) }.toList() ) {index, option -> - updateValue(index) + updateValue(availableOptions[index]) }, ) SettingsTile( 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 2a92a54..3287df1 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 @@ -16,11 +16,14 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -51,11 +54,13 @@ fun SettingsScreen( navController: NavController, audioRecorder: AudioRecorderModel, ) { + val snackbarHostState = remember { SnackbarHostState() } val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( rememberTopAppBarState() ) Scaffold( + snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, topBar = { LargeTopAppBar( title = { @@ -124,8 +129,8 @@ fun SettingsScreen( ) BitrateTile() SamplingRateTile() - OutputFormatTile() EncoderTile() + OutputFormatTile() } } } diff --git a/app/src/main/java/app/myzel394/alibi/ui/utils/IconResource.kt b/app/src/main/java/app/myzel394/alibi/ui/utils/IconResource.kt new file mode 100644 index 0000000..eaa0abf --- /dev/null +++ b/app/src/main/java/app/myzel394/alibi/ui/utils/IconResource.kt @@ -0,0 +1,32 @@ +package app.myzel394.alibi.ui.utils + +import androidx.annotation.DrawableRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.painterResource + +class IconResource private constructor( + @DrawableRes private val resID: Int?, + private val imageVector: ImageVector? +) { + + @Composable + fun asPainterResource(): Painter { + resID?.let { + return painterResource(id = resID) + } + return rememberVectorPainter(image = imageVector!!) + } + + companion object { + fun fromDrawableResource(@DrawableRes resID: Int): IconResource { + return IconResource(resID, null) + } + + fun fromImageVector(imageVector: ImageVector?): IconResource { + return IconResource(null, imageVector) + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 451b430..210bcf0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,11 +52,12 @@ A higher bitrate means better quality but also larger file size Set the bitrate for the audio recording Output Format - Auto + Encoder has been changed because the current one was incompatible with this output format Sampling rate Define how many samples per second are taken from the audio signal Set the sampling rate Encoder + Auto Recording paused Audio Recording has been paused \ No newline at end of file