Add preset input picker functionality

Added functionality to `Choose preset input` button. The picker allows the user to select a preset input from a list of loaded preset playlists. The selected preset input is then used to populate the ExoPlayer with the corresponding media items.

PiperOrigin-RevId: 704626144
This commit is contained in:
shahddaghash 2024-12-10 02:48:03 -08:00 committed by Copybara-Service
parent 6689fee2b2
commit dc21f3add2
2 changed files with 92 additions and 22 deletions

View File

@ -30,7 +30,10 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.selectable
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
@ -88,10 +91,13 @@ class EffectActivity : ComponentActivity() {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
InputChooser( InputChooser(
onException = { coroutineScope.launch { snackbarHostState.showSnackbar(it) } } playlistHolderList,
) { uri -> onException = { message ->
coroutineScope.launch { snackbarHostState.showSnackbar(message) }
},
) { mediaItems ->
exoPlayer.apply { exoPlayer.apply {
setMediaItem(MediaItem.fromUri(uri)) setMediaItems(mediaItems)
prepare() prepare()
} }
} }
@ -106,44 +112,105 @@ class EffectActivity : ComponentActivity() {
} }
@Composable @Composable
private fun InputChooser(onException: (String) -> Unit, onNewUri: (Uri) -> Unit) { private fun InputChooser(
var showLocalFilePicker by remember { mutableStateOf(false) } playlistHolderList: List<PlaylistHolder>,
onException: (String) -> Unit,
onNewMediaItems: (List<MediaItem>) -> Unit,
) {
var showPresetInputChooser by remember { mutableStateOf(false) }
var showLocalFileChooser by remember { mutableStateOf(false) }
Row( Row(
modifier = Modifier.padding(vertical = dimensionResource(id = R.dimen.small_padding)), modifier = Modifier.padding(vertical = dimensionResource(id = R.dimen.small_padding)),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.small_padding)), horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.small_padding)),
) { ) {
Button(onClick = { onException("Button is not yet implemented.") }) { Button(onClick = { showPresetInputChooser = true }) {
Text(text = stringResource(id = R.string.choose_preset_input)) Text(text = stringResource(id = R.string.choose_preset_input))
} }
Button(onClick = { showLocalFilePicker = true }) { Button(onClick = { showLocalFileChooser = true }) {
Text(text = stringResource(id = R.string.choose_local_file)) Text(text = stringResource(id = R.string.choose_local_file))
} }
} }
if (showLocalFilePicker) { if (showPresetInputChooser) {
LocalFilePicker( if (playlistHolderList.isNotEmpty()) {
onException = { PresetInputChooser(
onException(it) playlistHolderList,
showLocalFilePicker = false onDismissRequest = { showPresetInputChooser = false },
) { mediaItems ->
onNewMediaItems(mediaItems)
showPresetInputChooser = false
} }
) { uri -> } else {
onNewUri(uri) onException(stringResource(id = R.string.no_loaded_playlists_error))
showLocalFilePicker = false showPresetInputChooser = false
}
}
if (showLocalFileChooser) {
LocalFileChooser(
onException = { message ->
onException(message)
showLocalFileChooser = false
}
) { mediaItems ->
onNewMediaItems(mediaItems)
showLocalFileChooser = false
} }
} }
} }
@Composable
private fun PresetInputChooser(
playlistHolderList: List<PlaylistHolder>,
onDismissRequest: () -> Unit,
onInputSelected: (List<MediaItem>) -> Unit,
) {
var selectedOption by remember { mutableStateOf(playlistHolderList.first()) }
AlertDialog(
onDismissRequest = onDismissRequest,
title = { Text(stringResource(id = R.string.choose_preset_input)) },
confirmButton = {
Button(onClick = { onInputSelected(selectedOption.mediaItems) }) {
Text(text = stringResource(id = R.string.ok))
}
},
text = {
Column {
playlistHolderList.forEach { playlistHolder ->
Row(
Modifier.fillMaxWidth()
.selectable(
(playlistHolder == selectedOption),
onClick = { selectedOption = playlistHolder },
),
verticalAlignment = Alignment.CenterVertically,
) {
RadioButton(
selected = (playlistHolder == selectedOption),
onClick = { selectedOption = playlistHolder },
)
Text(playlistHolder.title)
}
}
}
},
)
}
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
@Composable @Composable
private fun LocalFilePicker(onException: (String) -> Unit, onFileSelected: (Uri) -> Unit) { private fun LocalFileChooser(
onException: (String) -> Unit,
onFileSelected: (List<MediaItem>) -> Unit,
) {
val context = LocalContext.current val context = LocalContext.current
val localFilePickerLauncher = val localFileChooserLauncher =
rememberLauncherForActivityResult( rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(), contract = ActivityResultContracts.OpenDocument(),
onResult = { uri: Uri? -> onResult = { uri: Uri? ->
if (uri != null) { if (uri != null) {
onFileSelected(uri) onFileSelected(listOf(MediaItem.fromUri(uri)))
} else { } else {
onException("File couldn't be opened. Please try again.") onException(getString(R.string.can_not_open_file_error))
} }
}, },
) )
@ -152,9 +219,9 @@ class EffectActivity : ComponentActivity() {
contract = ActivityResultContracts.RequestPermission(), contract = ActivityResultContracts.RequestPermission(),
onResult = { isGranted: Boolean -> onResult = { isGranted: Boolean ->
if (isGranted) { if (isGranted) {
localFilePickerLauncher.launch(arrayOf("video/*")) localFileChooserLauncher.launch(arrayOf("video/*"))
} else { } else {
onException("Permission was not granted.") onException(getString(R.string.permission_not_granted_error))
} }
}, },
) )
@ -164,7 +231,7 @@ class EffectActivity : ComponentActivity() {
else Manifest.permission.READ_EXTERNAL_STORAGE else Manifest.permission.READ_EXTERNAL_STORAGE
val permissionCheck = ContextCompat.checkSelfPermission(context, permission) val permissionCheck = ContextCompat.checkSelfPermission(context, permission)
if (permissionCheck == android.content.pm.PackageManager.PERMISSION_GRANTED) { if (permissionCheck == android.content.pm.PackageManager.PERMISSION_GRANTED) {
localFilePickerLauncher.launch(arrayOf("video/*")) localFileChooserLauncher.launch(arrayOf("video/*"))
} else { } else {
permissionLauncher.launch(permission) permissionLauncher.launch(permission)
} }

View File

@ -20,4 +20,7 @@
<string name="apply_effects">Apply effects</string> <string name="apply_effects">Apply effects</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="playlist_loading_error">Error loading playlist from %1$s: %2$s</string> <string name="playlist_loading_error">Error loading playlist from %1$s: %2$s</string>
<string name="no_loaded_playlists_error">There are no loaded preset inputs.</string>
<string name="can_not_open_file_error">"File couldn't be opened. Please try again."</string>
<string name="permission_not_granted_error">"Permission was not granted."</string>
</resources> </resources>