diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/PlaybackSpeedSetting.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/PlaybackSpeedSetting.kt new file mode 100644 index 000000000..00f129680 --- /dev/null +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/PlaybackSpeedSetting.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.demo.shared.ui.player.settings + +/** + * A possible playback speed value. + */ +data class PlaybackSpeedSetting( + /** + * The formatted speed. + */ + val speed: String, + + /** + * The speed as [Float]. + */ + val rawSpeed: Float, + + /** + * `true` if this speed is selected, `false` otherwise. + */ + val isSelected: Boolean +) diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/PlayerSettingsViewModel.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/PlayerSettingsViewModel.kt new file mode 100644 index 000000000..513716074 --- /dev/null +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/PlayerSettingsViewModel.kt @@ -0,0 +1,252 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.demo.shared.ui.player.settings + +import android.app.Application +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Audiotrack +import androidx.compose.material.icons.filled.Speed +import androidx.compose.material.icons.filled.Subtitles +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.media3.common.Player +import androidx.media3.common.TrackSelectionOverride +import androidx.media3.common.Tracks.Group +import ch.srgssr.pillarbox.demo.shared.R +import ch.srgssr.pillarbox.player.extension.audio +import ch.srgssr.pillarbox.player.extension.disableAudioTrack +import ch.srgssr.pillarbox.player.extension.disableTextTrack +import ch.srgssr.pillarbox.player.extension.displayName +import ch.srgssr.pillarbox.player.extension.getPlaybackSpeed +import ch.srgssr.pillarbox.player.extension.isAudioTrackDisabled +import ch.srgssr.pillarbox.player.extension.isTextTrackDisabled +import ch.srgssr.pillarbox.player.extension.setDefaultAudioTrack +import ch.srgssr.pillarbox.player.extension.setDefaultTextTrack +import ch.srgssr.pillarbox.player.extension.setTrackOverride +import ch.srgssr.pillarbox.player.extension.text +import ch.srgssr.pillarbox.player.getCurrentTracksAsFlow +import ch.srgssr.pillarbox.player.getPlaybackSpeedAsFlow +import ch.srgssr.pillarbox.player.getTrackSelectionParametersAsFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * Player settings view model + * + * @constructor Create empty Player settings view model + */ +class PlayerSettingsViewModel( + private val player: Player, + private val application: Application +) : AndroidViewModel(application) { + private val trackSelectionParameters = player.getTrackSelectionParametersAsFlow() + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), player.trackSelectionParameters) + + private val tracks = player.getCurrentTracksAsFlow() + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), player.currentTracks) + + private val playbackSpeed = player.getPlaybackSpeedAsFlow() + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), player.getPlaybackSpeed()) + + /** + * All the available settings for the current [player]. + */ + val settings = combine( + tracks, + trackSelectionParameters, + playbackSpeed + ) { currentTracks, trackSelectionParameters, playbackSpeed -> + buildList { + add( + SettingItem( + title = application.getString(R.string.speed), + subtitle = getSpeedLabel(playbackSpeed), + icon = Icons.Default.Speed, + destination = SettingsRoutes.PlaybackSpeed + ) + ) + + if (currentTracks.text.isNotEmpty()) { + add( + SettingItem( + title = application.getString(R.string.subtitles), + subtitle = getTracksSubtitle( + tracks = currentTracks.text, + disabled = trackSelectionParameters.isTextTrackDisabled + ), + icon = Icons.Default.Subtitles, + destination = SettingsRoutes.Subtitles + ) + ) + } + + if (currentTracks.audio.isNotEmpty()) { + add( + SettingItem( + title = application.getString(R.string.audio_track), + subtitle = getTracksSubtitle( + tracks = currentTracks.audio, + disabled = trackSelectionParameters.isAudioTrackDisabled + ), + icon = Icons.Default.Audiotrack, + destination = SettingsRoutes.AudioTrack + ) + ) + } + } + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList()) + + /** + * All the available subtitle for the current [player]. + */ + val subtitles = combine( + tracks, + trackSelectionParameters + ) { tracks, trackSelectionParameters -> + TracksSettingItem( + title = application.getString(R.string.subtitles), + tracks = tracks.text, + disabled = trackSelectionParameters.isTextTrackDisabled + ) + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) + + /** + * All the available audio tracks for the current [player]. + */ + val audioTracks = combine( + tracks, + trackSelectionParameters + ) { tracks, trackSelectionParameters -> + TracksSettingItem( + title = application.getString(R.string.audio_track), + tracks = tracks.audio, + disabled = trackSelectionParameters.isAudioTrackDisabled + ) + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) + + /** + * All the available playback speeds for the current [player]. + */ + val playbackSpeeds = playbackSpeed.map { playbackSpeed -> + speeds.map { speed -> + PlaybackSpeedSetting( + speed = getSpeedLabel(speed), + rawSpeed = speed, + isSelected = speed == playbackSpeed + ) + } + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList()) + + /** + * Reset the subtitles. + */ + fun resetSubtitles() { + player.setDefaultTextTrack(application) + } + + /** + * Disable the subtitles. + */ + fun disableSubtitles() { + player.disableTextTrack() + } + + /** + * Set the subtitles. + * + * @param group The selected group. + * @param trackIndex The index of the track in the provided group. + */ + fun setSubtitle(group: Group, trackIndex: Int) { + player.setTrackOverride(TrackSelectionOverride(group.mediaTrackGroup, trackIndex)) + } + + /** + * Reset the audio track. + */ + fun resetAudioTrack() { + player.setDefaultAudioTrack(application) + } + + /** + * Disable the audio track. + */ + fun disableAudioTrack() { + player.disableAudioTrack() + } + + /** + * Set the audio track. + * + * @param group The selected group. + * @param trackIndex The index of the track in the provided group. + */ + fun setAudioTrack(group: Group, trackIndex: Int) { + player.setTrackOverride(TrackSelectionOverride(group.mediaTrackGroup, trackIndex)) + } + + /** + * Set the playback speed. + * + * @param playbackSpeed The selected playback speed. + */ + fun setPlaybackSpeed(playbackSpeed: PlaybackSpeedSetting) { + player.setPlaybackSpeed(playbackSpeed.rawSpeed) + } + + private fun getTracksSubtitle( + tracks: List, + disabled: Boolean + ): String? { + return if (disabled) { + application.getString(R.string.disabled) + } else { + tracks.filter { it.isSelected } + .flatMap { + (0 until it.length).mapNotNull { trackIndex -> + if (it.isTrackSelected(trackIndex)) { + it.getTrackFormat(trackIndex).displayName + } else { + null + } + } + } + .firstOrNull() + } + } + + private fun getSpeedLabel(speed: Float): String { + return if (speed == 1f) { + application.getString(R.string.speed_normal) + } else { + application.getString(R.string.speed_value, speed.toString()) + } + } + + private companion object { + private val speeds = floatArrayOf(0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f) + } + + /** + * Factory + * + * @param player + * @param application + * @constructor Create empty Factory + */ + @Suppress("UndocumentedPublicClass") + class Factory( + private val player: Player, + private val application: Application + ) : ViewModelProvider.NewInstanceFactory() { + override fun create(modelClass: Class): T { + return PlayerSettingsViewModel(player, application) as T + } + } +} diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/SettingItem.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/SettingItem.kt new file mode 100644 index 000000000..e9c66c759 --- /dev/null +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/SettingItem.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.demo.shared.ui.player.settings + +import androidx.compose.ui.graphics.vector.ImageVector + +/** + * Represent a setting item. + */ +data class SettingItem( + /** + * The title of the setting. + */ + val title: String, + + /** + * The optional subtitle of the setting. + */ + val subtitle: String?, + + /** + * The icon of the setting. + */ + val icon: ImageVector, + + /** + * The route of the setting. + */ + val destination: SettingsRoutes +) diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/SettingsRoutes.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/SettingsRoutes.kt new file mode 100644 index 000000000..5be919cc0 --- /dev/null +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/SettingsRoutes.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.demo.shared.ui.player.settings + +/** + * All the routes used in the player's settings. + * + * @property route The route of the setting. + */ +sealed class SettingsRoutes(val route: String) { + /** + * The route for the main screen of the settings. + */ + data object Main : SettingsRoutes(route = "settings") + + /** + * The route for the playback speed setting. + */ + data object PlaybackSpeed : SettingsRoutes(route = "settings/playback_speed") + + /** + * The route for the subtitles setting. + */ + data object Subtitles : SettingsRoutes(route = "settings/subtitles") + + /** + * The route for the audio track setting. + */ + data object AudioTrack : SettingsRoutes(route = "settings/audio_track") +} diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/TracksSettingItem.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/TracksSettingItem.kt new file mode 100644 index 000000000..a335cb5f3 --- /dev/null +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/TracksSettingItem.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ +package ch.srgssr.pillarbox.demo.shared.ui.player.settings + +import androidx.media3.common.Tracks.Group + +/** + * The setting for a specific kind a track (audio/text/video). + */ +data class TracksSettingItem( + /** + * The title of the setting. + */ + val title: String, + + /** + * The list of possible tracks. + */ + val tracks: List, + + /** + * `true` if this kind of tracks is disabled, `false` otherwise. + */ + val disabled: Boolean +) diff --git a/pillarbox-demo-shared/src/main/res/values/strings.xml b/pillarbox-demo-shared/src/main/res/values/strings.xml index 2bc72db8f..9913ff8f3 100644 --- a/pillarbox-demo-shared/src/main/res/values/strings.xml +++ b/pillarbox-demo-shared/src/main/res/values/strings.xml @@ -11,4 +11,12 @@ No results Enter something to search %1$d min + Settings + Audio track + Subtitles + Speed + Normal + %1$s× + Reset to default + Disabled diff --git a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/examples/Examples.kt b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/examples/Examples.kt index b12d5e176..618de5163 100644 --- a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/examples/Examples.kt +++ b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/examples/Examples.kt @@ -27,7 +27,6 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRestorer import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key @@ -98,7 +97,6 @@ fun ExamplesHome( Text( text = item.title, modifier = Modifier.padding(MaterialTheme.paddings.baseline), - color = Color.White, textAlign = TextAlign.Center, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleMedium @@ -134,7 +132,6 @@ fun ExamplesHome( ) { Text( text = item.title, - color = Color.White, overflow = TextOverflow.Ellipsis, maxLines = 2, style = MaterialTheme.typography.bodyMedium @@ -144,7 +141,6 @@ fun ExamplesHome( item.description?.let { description -> Text( text = description, - color = Color.White, overflow = TextOverflow.Ellipsis, maxLines = 2, style = MaterialTheme.typography.bodySmall diff --git a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/player/compose/PlayerSettingDrawer.kt b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/player/compose/PlayerSettingDrawer.kt index aea42cb28..c07f52b30 100644 --- a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/player/compose/PlayerSettingDrawer.kt +++ b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/player/compose/PlayerSettingDrawer.kt @@ -4,6 +4,7 @@ */ package ch.srgssr.pillarbox.demo.tv.player.compose +import android.app.Application import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background @@ -12,10 +13,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Audiotrack import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Speed -import androidx.compose.material.icons.filled.Subtitles import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.collectAsState @@ -27,16 +25,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.LayoutDirection +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.media3.common.Format import androidx.media3.common.Player -import androidx.media3.common.TrackSelectionOverride -import androidx.media3.common.Tracks import androidx.media3.common.Tracks.Group import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -53,15 +50,13 @@ import androidx.tv.material3.ModalNavigationDrawer import androidx.tv.material3.NavigationDrawerItem import androidx.tv.material3.NavigationDrawerScope import androidx.tv.material3.Text -import ch.srgssr.pillarbox.demo.tv.R +import ch.srgssr.pillarbox.demo.shared.R +import ch.srgssr.pillarbox.demo.shared.ui.player.settings.PlayerSettingsViewModel +import ch.srgssr.pillarbox.demo.shared.ui.player.settings.SettingsRoutes +import ch.srgssr.pillarbox.demo.shared.ui.player.settings.TracksSettingItem import ch.srgssr.pillarbox.demo.tv.ui.theme.paddings -import ch.srgssr.pillarbox.player.extension.audio import ch.srgssr.pillarbox.player.extension.displayName import ch.srgssr.pillarbox.player.extension.hasAccessibilityRoles -import ch.srgssr.pillarbox.player.extension.setTrackOverride -import ch.srgssr.pillarbox.player.extension.text -import ch.srgssr.pillarbox.player.getCurrentTracksAsFlow -import ch.srgssr.pillarbox.ui.extension.playbackSpeedAsState /** * Drawer used to display a player's settings. @@ -121,6 +116,8 @@ private fun NavigationDrawerScope.NavigationDrawerNavHost( player: Player, modifier: Modifier = Modifier ) { + val application = LocalContext.current.applicationContext as Application + val settingsViewModel = viewModel(factory = PlayerSettingsViewModel.Factory(player, application)) val focusRequester = remember { FocusRequester() } val navController = rememberNavController() @@ -128,7 +125,7 @@ private fun NavigationDrawerScope.NavigationDrawerNavHost( NavHost( navController = navController, - startDestination = Routes.SETTINGS, + startDestination = SettingsRoutes.Main.route, modifier = modifier .focusRequester(focusRequester) .onFocusChanged { hasFocus = it.hasFocus } @@ -138,13 +135,15 @@ private fun NavigationDrawerScope.NavigationDrawerNavHost( } } ) { - composable(Routes.SETTINGS) { + composable(SettingsRoutes.Main.route) { + val settings by settingsViewModel.settings.collectAsState() + GenericSetting( title = stringResource(R.string.settings), - items = getSettings(player), + items = settings, isItemSelected = { false }, - onItemClick = { - navController.navigate(it.destination) + onItemClick = { setting -> + navController.navigate(setting.destination.route) }, leadingContent = { setting -> Icon( @@ -167,51 +166,50 @@ private fun NavigationDrawerScope.NavigationDrawerNavHost( ) } - composable(Routes.AUDIO_TRACK_SETTING) { - val tracks by player.getCurrentTracksAsFlow().collectAsState(initial = Tracks.EMPTY) + composable(SettingsRoutes.AudioTrack.route) { + val audioTracks by settingsViewModel.audioTracks.collectAsState() - TracksSetting( - title = stringResource(R.string.audio_track), - tracks = tracks.audio, - onTrackClick = { group, trackIndex -> - player.setTrackOverride(TrackSelectionOverride(group.mediaTrackGroup, trackIndex)) - } - ) + audioTracks?.let { + TracksSetting( + tracksSetting = it, + onResetClick = settingsViewModel::resetAudioTrack, + onDisabledClick = settingsViewModel::disableAudioTrack, + onTrackClick = settingsViewModel::setAudioTrack + ) + } } - composable(Routes.SUBTITLE_SETTING) { - val tracks by player.getCurrentTracksAsFlow().collectAsState(initial = Tracks.EMPTY) + composable(SettingsRoutes.Subtitles.route) { + val subtitles by settingsViewModel.subtitles.collectAsState() - TracksSetting( - title = stringResource(R.string.subtitles), - tracks = tracks.text, - onTrackClick = { group, trackIndex -> - player.setTrackOverride(TrackSelectionOverride(group.mediaTrackGroup, trackIndex)) - } - ) + subtitles?.let { + TracksSetting( + tracksSetting = it, + onResetClick = settingsViewModel::resetSubtitles, + onDisabledClick = settingsViewModel::disableSubtitles, + onTrackClick = settingsViewModel::setSubtitle + ) + } } - composable(Routes.SPEED_SETTING) { - val speedOptions = listOf(0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f) - val playbackSpeed by player.playbackSpeedAsState() + composable(SettingsRoutes.PlaybackSpeed.route) { + val playbackSpeeds by settingsViewModel.playbackSpeeds.collectAsState() GenericSetting( title = stringResource(R.string.speed), - items = speedOptions, - isItemSelected = { it == playbackSpeed }, - onItemClick = { speed -> - player.setPlaybackSpeed(speed) - }, - leadingContent = { speed -> - AnimatedVisibility(visible = playbackSpeed == speed) { + items = playbackSpeeds, + isItemSelected = { it.isSelected }, + onItemClick = settingsViewModel::setPlaybackSpeed, + leadingContent = { playbackSpeed -> + AnimatedVisibility(visible = playbackSpeed.isSelected) { Icon( imageVector = Icons.Default.Check, contentDescription = null ) } }, - content = { speed -> - Text(text = getSpeedLabel(speed)) + content = { playbackSpeed -> + Text(text = playbackSpeed.speed) } ) } @@ -259,9 +257,10 @@ private fun NavigationDrawerScope.GenericSetting( @Composable @OptIn(ExperimentalTvMaterial3Api::class) private fun NavigationDrawerScope.TracksSetting( - title: String, + tracksSetting: TracksSettingItem, modifier: Modifier = Modifier, - tracks: List, + onResetClick: () -> Unit, + onDisabledClick: () -> Unit, onTrackClick: (track: Group, trackIndex: Int) -> Unit ) { Column( @@ -270,14 +269,51 @@ private fun NavigationDrawerScope.TracksSetting( .padding(top = MaterialTheme.paddings.baseline) ) { Text( - text = title, + text = tracksSetting.title, style = MaterialTheme.typography.titleMedium ) TvLazyColumn( contentPadding = PaddingValues(vertical = MaterialTheme.paddings.baseline) ) { - tracks.forEach { group -> + item { + NavigationDrawerItem( + selected = false, + onClick = onResetClick, + leadingContent = {}, + content = { + Text( + text = stringResource(R.string.reset_to_default), + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } + ) + } + + item { + NavigationDrawerItem( + selected = tracksSetting.disabled, + onClick = onDisabledClick, + leadingContent = { + AnimatedVisibility(visible = tracksSetting.disabled) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = null + ) + } + }, + content = { + Text( + text = stringResource(R.string.disabled), + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } + ) + } + + tracksSetting.tracks.forEach { group -> items(group.length) { trackIndex -> NavigationDrawerItem( selected = group.isTrackSelected(trackIndex), @@ -318,91 +354,3 @@ private fun NavigationDrawerScope.TracksSetting( } } } - -@Composable -private fun getSettings(player: Player): List { - val tracks by player.getCurrentTracksAsFlow().collectAsState(initial = Tracks.EMPTY) - val playbackSpeed by player.playbackSpeedAsState() - - return buildList { - if (tracks.audio.isNotEmpty()) { - val selectedAudio = tracks.audio - .filter { it.isSelected } - .flatMap { - (0 until it.length).mapNotNull { trackIndex -> - if (it.isTrackSelected(trackIndex)) { - it.getTrackFormat(trackIndex).displayName - } else { - null - } - } - } - .firstOrNull() - - add( - SettingItem( - destination = Routes.AUDIO_TRACK_SETTING, - icon = Icons.Default.Audiotrack, - subtitle = selectedAudio, - title = stringResource(R.string.audio_track) - ) - ) - } - - if (tracks.text.isNotEmpty()) { - val selectedSubtitle = tracks.text - .filter { it.isSelected } - .flatMap { - (0 until it.length).mapNotNull { trackIndex -> - if (it.isTrackSelected(trackIndex)) { - it.getTrackFormat(trackIndex).displayName - } else { - null - } - } - } - .firstOrNull() - - add( - SettingItem( - destination = Routes.SUBTITLE_SETTING, - icon = Icons.Default.Subtitles, - subtitle = selectedSubtitle, - title = stringResource(R.string.subtitles) - ) - ) - } - - add( - SettingItem( - destination = Routes.SPEED_SETTING, - icon = Icons.Default.Speed, - subtitle = getSpeedLabel(playbackSpeed), - title = stringResource(R.string.speed) - ) - ) - } -} - -@Composable -private fun getSpeedLabel(speed: Float): String { - return if (speed == 1f) { - stringResource(R.string.speed_normal) - } else { - stringResource(R.string.speed_value, speed.toString()) - } -} - -private data class SettingItem( - val destination: String, - val icon: ImageVector, - val subtitle: String?, - val title: String -) - -private object Routes { - const val SETTINGS = "settings" - const val AUDIO_TRACK_SETTING = "settings/audio_track" - const val SUBTITLE_SETTING = "settings/subtitles" - const val SPEED_SETTING = "settings/speed" -} diff --git a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/player/compose/TvPlayerView.kt b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/player/compose/TvPlayerView.kt index 082b7e4ca..dda48dc3d 100644 --- a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/player/compose/TvPlayerView.kt +++ b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/player/compose/TvPlayerView.kt @@ -26,7 +26,7 @@ import androidx.tv.material3.Icon import androidx.tv.material3.IconButton import androidx.tv.material3.MaterialTheme import androidx.tv.material3.rememberDrawerState -import ch.srgssr.pillarbox.demo.tv.R +import ch.srgssr.pillarbox.demo.shared.R import ch.srgssr.pillarbox.demo.tv.ui.theme.paddings import ch.srgssr.pillarbox.ui.extension.handleDPadKeyEvents import ch.srgssr.pillarbox.ui.widget.maintainVisibleOnFocus diff --git a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/integrationLayer/ListsHome.kt b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/integrationLayer/ListsHome.kt index d63f6e2a9..0a0a0aefc 100644 --- a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/integrationLayer/ListsHome.kt +++ b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/integrationLayer/ListsHome.kt @@ -334,7 +334,6 @@ private fun ListsSection( Text( text = itemToString(item), modifier = Modifier.padding(MaterialTheme.paddings.baseline), - color = Color.White, textAlign = TextAlign.Center, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleMedium @@ -651,7 +650,7 @@ private fun CategoryContent( end = MaterialTheme.paddings.small, bottom = MaterialTheme.paddings.mini ), - color = Color.White, + color = if (imageUrl != null) Color.White else Color.Unspecified, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleMedium .copy( diff --git a/pillarbox-demo-tv/src/main/res/values/strings.xml b/pillarbox-demo-tv/src/main/res/values/strings.xml index 94a432c58..40e63535f 100644 --- a/pillarbox-demo-tv/src/main/res/values/strings.xml +++ b/pillarbox-demo-tv/src/main/res/values/strings.xml @@ -2,10 +2,4 @@ Pillarbox Demo Tv Loading… No content - Settings - Audio track - Subtitles - Speed - Normal - %1$s× diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/DemoPlayerView.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/DemoPlayerView.kt index d4c778c7d..bfd1a9010 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/DemoPlayerView.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/DemoPlayerView.kt @@ -81,9 +81,7 @@ fun DemoPlayerView( navController.popBackStack() } } - PlaybackSettingsContent(player = player) { - navController.popBackStack() - } + PlaybackSettingsContent(player = player) } } } diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerBottomToolbar.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerBottomToolbar.kt index 1ec382f39..28419c3fe 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerBottomToolbar.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerBottomToolbar.kt @@ -16,6 +16,8 @@ import androidx.compose.material3.IconToggleButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import ch.srgssr.pillarbox.demo.shared.R /** * Player bottom toolbar that contains Picture in Picture and fullscreen buttons. @@ -64,7 +66,7 @@ fun PlayerBottomToolbar( ) { Icon( tint = Color.White, - imageVector = Icons.Default.Settings, contentDescription = "Open playback settings" + imageVector = Icons.Default.Settings, contentDescription = stringResource(R.string.settings) ) } } diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlaybackSettingsContent.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlaybackSettingsContent.kt index 2007cf1e0..e20092ca7 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlaybackSettingsContent.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlaybackSettingsContent.kt @@ -4,74 +4,45 @@ */ package ch.srgssr.pillarbox.demo.ui.player.settings +import android.app.Application import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.foundation.clickable import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Audiotrack -import androidx.compose.material.icons.filled.Speed -import androidx.compose.material.icons.filled.Subtitles import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.semantics.Role import androidx.lifecycle.viewmodel.compose.viewModel import androidx.media3.common.Player -import androidx.media3.common.TrackSelectionOverride -import androidx.navigation.NavController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import ch.srgssr.pillarbox.player.extension.disableAudioTrack -import ch.srgssr.pillarbox.player.extension.disableTextTrack -import ch.srgssr.pillarbox.player.extension.isAudioTrackDisabled -import ch.srgssr.pillarbox.player.extension.isTextTrackDisabled -import ch.srgssr.pillarbox.player.extension.setDefaultAudioTrack -import ch.srgssr.pillarbox.player.extension.setDefaultTextTrack -import ch.srgssr.pillarbox.player.extension.setTrackOverride - -private sealed class SettingDestination(val route: String) { - data object Home : SettingDestination(route = "settings/home") - data object PlaybackSpeed : SettingDestination(route = "settings/speed") - data object Subtitles : SettingDestination(route = "settings/subtitles") - data object Audios : SettingDestination(route = "settings/audios") -} - -private fun NavController.navigate(destination: SettingDestination) { - navigate(route = destination.route) { - launchSingleTop = true - } -} +import ch.srgssr.pillarbox.demo.shared.ui.player.settings.PlayerSettingsViewModel +import ch.srgssr.pillarbox.demo.shared.ui.player.settings.SettingItem +import ch.srgssr.pillarbox.demo.shared.ui.player.settings.SettingsRoutes /** * Playback settings content * * @param player The [Player] actions occurred. - * @param onDismiss The callback to dismiss the settings. */ @Composable -fun PlaybackSettingsContent( - player: Player, - onDismiss: () -> Unit, -) { - val onDismissState = remember { - onDismiss - } +fun PlaybackSettingsContent(player: Player) { + val application = LocalContext.current.applicationContext as Application val navController = rememberNavController() - val settingsViewModel: PlayerSettingsViewModel = viewModel(factory = PlayerSettingsViewModel.Factory(player)) - val currentPlaybackSpeed = settingsViewModel.playbackSpeed.collectAsState().value + val settingsViewModel: PlayerSettingsViewModel = viewModel(factory = PlayerSettingsViewModel.Factory(player, application)) Surface { - NavHost(navController = navController, startDestination = SettingDestination.Home.route) { + NavHost(navController = navController, startDestination = SettingsRoutes.Main.route) { composable( - route = SettingDestination.Home.route, + route = SettingsRoutes.Main.route, exitTransition = { slideOutOfContainer(towards = AnimatedContentTransitionScope.SlideDirection.Down) }, @@ -79,21 +50,18 @@ fun PlaybackSettingsContent( slideIntoContainer(towards = AnimatedContentTransitionScope.SlideDirection.Up) } ) { - val hasAudios = settingsViewModel.hasAudio.collectAsState(initial = false) - val hasSubtitles = settingsViewModel.hasSubtitles.collectAsState(initial = false) + val settings by settingsViewModel.settings.collectAsState() SettingsHome( - settings = createSettingsItems( - playbackSeed = currentPlaybackSpeed, - hasAudios = hasAudios.value, - hasSubtitles = hasSubtitles.value - ), + settings = settings, settingsClicked = { - navController.navigate(it.destination) + navController.navigate(it.destination.route) { + launchSingleTop = true + } }, ) } composable( - route = SettingDestination.PlaybackSpeed.route, + route = SettingsRoutes.PlaybackSpeed.route, exitTransition = { slideOutOfContainer(towards = AnimatedContentTransitionScope.SlideDirection.Down) }, @@ -101,13 +69,14 @@ fun PlaybackSettingsContent( slideIntoContainer(towards = AnimatedContentTransitionScope.SlideDirection.Up) } ) { - PlaybackSpeedSettings(currentSpeed = currentPlaybackSpeed, onSpeedSelected = { - player.setPlaybackSpeed(it) - onDismissState() - }) + val playbackSpeeds by settingsViewModel.playbackSpeeds.collectAsState() + PlaybackSpeedSettings( + playbackSpeeds = playbackSpeeds, + onSpeedSelected = settingsViewModel::setPlaybackSpeed + ) } composable( - route = SettingDestination.Subtitles.route, + route = SettingsRoutes.Subtitles.route, exitTransition = { slideOutOfContainer(towards = AnimatedContentTransitionScope.SlideDirection.Down) }, @@ -115,29 +84,18 @@ fun PlaybackSettingsContent( slideIntoContainer(towards = AnimatedContentTransitionScope.SlideDirection.Up) } ) { - val textTrackState = settingsViewModel.textTracks.collectAsState() - val textTrackSelection = settingsViewModel.trackSelectionParameters.collectAsState() - val disabled = textTrackSelection.value.isTextTrackDisabled - val context = LocalContext.current - TrackSelectionSettings(textTrackState.value, disabled = disabled) { action -> - when (action) { - is TrackSelectionAction.Disable -> { - player.disableTextTrack() - } - - is TrackSelectionAction.Default -> { - player.setDefaultTextTrack(context) - } - - is TrackSelectionAction.Selection -> { - player.setTrackOverride(TrackSelectionOverride(action.group.mediaTrackGroup, action.trackIndex)) - } - } - onDismissState() + val subtitles by settingsViewModel.subtitles.collectAsState() + subtitles?.let { + TrackSelectionSettings( + tracksSetting = it, + onResetClick = settingsViewModel::resetSubtitles, + onDisabledClick = settingsViewModel::disableSubtitles, + onTrackClick = settingsViewModel::setSubtitle + ) } } composable( - route = SettingDestination.Audios.route, + route = SettingsRoutes.AudioTrack.route, exitTransition = { slideOutOfContainer(towards = AnimatedContentTransitionScope.SlideDirection.Down) }, @@ -145,25 +103,14 @@ fun PlaybackSettingsContent( slideIntoContainer(towards = AnimatedContentTransitionScope.SlideDirection.Up) } ) { - val audioTracks = settingsViewModel.audioTracks.collectAsState() - val trackSelectionParametersState = settingsViewModel.trackSelectionParameters.collectAsState() - val disabled = trackSelectionParametersState.value.isAudioTrackDisabled - val context = LocalContext.current - TrackSelectionSettings(audioTracks.value, disabled = disabled) { action -> - when (action) { - is TrackSelectionAction.Disable -> { - player.disableAudioTrack() - } - - is TrackSelectionAction.Default -> { - player.setDefaultAudioTrack(context) - } - - is TrackSelectionAction.Selection -> { - player.setTrackOverride(TrackSelectionOverride(action.group.mediaTrackGroup, action.trackIndex)) - } - } - onDismissState() + val audioTracks by settingsViewModel.audioTracks.collectAsState() + audioTracks?.let { + TrackSelectionSettings( + tracksSetting = it, + onResetClick = settingsViewModel::resetAudioTrack, + onDisabledClick = settingsViewModel::disableAudioTrack, + onTrackClick = settingsViewModel::setAudioTrack + ) } } } @@ -184,8 +131,8 @@ private fun SettingsHome( onClick = { settingsClicked(setting) } ), title = setting.title, - secondaryText = setting.secondaryText, - imageVector = setting.imageVector + secondaryText = setting.subtitle, + imageVector = setting.icon ) } } @@ -213,46 +160,3 @@ private fun SettingsItem( } ) } - -private data class SettingItem( - val title: String, - val imageVector: ImageVector, - val destination: SettingDestination, - val secondaryText: String? = null, -) - -private fun createSettingsItems( - playbackSeed: Float, - hasAudios: Boolean, - hasSubtitles: Boolean -): List { - val list = ArrayList() - list.add( - SettingItem( - title = "Speed", - imageVector = Icons.Default.Speed, - destination = SettingDestination.PlaybackSpeed, - secondaryText = DefaultSpeedLabelProvider(playbackSeed), - ) - ) - if (hasSubtitles) { - list.add( - SettingItem( - title = "Subtitles", - imageVector = Icons.Default.Subtitles, - destination = SettingDestination.Subtitles, - ) - ) - } - if (hasAudios) { - list.add( - SettingItem( - title = "Audios", - imageVector = Icons.Default.Audiotrack, - destination = SettingDestination.Audios, - ) - ) - } - - return list -} diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlaybackSpeedSettings.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlaybackSpeedSettings.kt index 5daf295ad..24e76154c 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlaybackSpeedSettings.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlaybackSpeedSettings.kt @@ -4,9 +4,8 @@ */ package ch.srgssr.pillarbox.demo.ui.player.settings -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.selection.toggleable import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check @@ -17,56 +16,36 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import ch.srgssr.pillarbox.demo.shared.ui.player.settings.PlaybackSpeedSetting import ch.srgssr.pillarbox.demo.ui.theme.PillarboxTheme -/** - * Default speeds - */ -val DefaultSpeeds = listOf( - 0.25f, 0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f, -) - -/** - * Default speed provider - */ -val DefaultSpeedLabelProvider: (Float) -> String = { speed -> - if (speed == 1.0f) { - "Normal" - } else { - "$speed" - } -} - /** * Playback speed settings * - * @param currentSpeed The current speed should be inside [speeds]. + * @param playbackSpeeds The list of possible speeds. + * @param modifier The [Modifier] to layout the view. * @param onSpeedSelected Called when a speed is clicked. - * @param modifier The Modifier to layout the view. - * @param speedLabelProvider Create a String from a speed. - * @param speeds List of possible speeds. * @receiver */ @Composable fun PlaybackSpeedSettings( - currentSpeed: Float, - onSpeedSelected: (Float) -> Unit, + playbackSpeeds: List, modifier: Modifier = Modifier, - speedLabelProvider: (Float) -> String = DefaultSpeedLabelProvider, - speeds: List = DefaultSpeeds + onSpeedSelected: (PlaybackSpeedSetting) -> Unit, ) { - LazyColumn(modifier) { - items(items = speeds) { speed -> - val enabled = speed == currentSpeed + itemsIndexed(items = playbackSpeeds) { index, playbackSpeed -> SettingsOptionItem( - title = speedLabelProvider(speed), - enabled = enabled, - modifier = Modifier.toggleable(enabled) { - onSpeedSelected(speed) + title = playbackSpeed.speed, + enabled = playbackSpeed.isSelected, + modifier = Modifier.toggleable(playbackSpeed.isSelected) { + onSpeedSelected(playbackSpeed) } ) - Divider() + + if (index < playbackSpeeds.lastIndex) { + Divider() + } } } } @@ -76,8 +55,7 @@ private fun SettingsOptionItem(title: String, enabled: Boolean, modifier: Modifi ListItem( modifier = modifier, headlineContent = { Text(text = title) }, - trailingContent = - { + trailingContent = { if (enabled) { Icon(imageVector = Icons.Default.Check, contentDescription = "enabled") } @@ -88,14 +66,28 @@ private fun SettingsOptionItem(title: String, enabled: Boolean, modifier: Modifi @Preview @Composable private fun PlaybackSpeedSettingPreview() { - val speeds = listOf(0.5f, 1.0f, 2.0f) + val playbackSpeeds = listOf( + PlaybackSpeedSetting( + speed = "0.5×", + rawSpeed = 0.5f, + isSelected = false + ), + PlaybackSpeedSetting( + speed = "Normal", + rawSpeed = 1f, + isSelected = true + ), + PlaybackSpeedSetting( + speed = "2×", + rawSpeed = 2f, + isSelected = false + ) + ) + PillarboxTheme { - Column() { - PlaybackSpeedSettings( - currentSpeed = speeds[0], - speeds = speeds, - onSpeedSelected = {} - ) - } + PlaybackSpeedSettings( + playbackSpeeds = playbackSpeeds, + onSpeedSelected = {} + ) } } diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlayerSettingsViewModel.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlayerSettingsViewModel.kt deleted file mode 100644 index 3bb121dd7..000000000 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlayerSettingsViewModel.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) SRG SSR. All rights reserved. - * License information is available from the LICENSE file. - */ -package ch.srgssr.pillarbox.demo.ui.player.settings - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope -import androidx.media3.common.Player -import ch.srgssr.pillarbox.player.extension.audio -import ch.srgssr.pillarbox.player.extension.getPlaybackSpeed -import ch.srgssr.pillarbox.player.extension.text -import ch.srgssr.pillarbox.player.getCurrentTracksAsFlow -import ch.srgssr.pillarbox.player.getPlaybackSpeedAsFlow -import ch.srgssr.pillarbox.player.getTrackSelectionParametersAsFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn - -/** - * Player settings view model - * - * @property player - * @constructor Create empty Player settings view model - */ -class PlayerSettingsViewModel(val player: Player) : ViewModel() { - private val _tracks = player.getCurrentTracksAsFlow() - - /** - * Track selection parameters - */ - val trackSelectionParameters = - player.getTrackSelectionParametersAsFlow().stateIn(viewModelScope, SharingStarted.WhileSubscribed(), player.trackSelectionParameters) - - /** - * Text tracks - */ - val textTracks = _tracks.map { it.text } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), player.currentTracks.text) - - /** - * Audio tracks - */ - val audioTracks = _tracks.map { it.audio } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), player.currentTracks.audio) - - /** - * Has subtitles - */ - val hasSubtitles = textTracks.map { - it.isNotEmpty() - } - - /** - * Has audio - */ - val hasAudio = audioTracks.map { - it.isNotEmpty() - } - - /** - * Playback speed - */ - val playbackSpeed = player.getPlaybackSpeedAsFlow() - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), player.getPlaybackSpeed()) - - /** - * Factory - * - * @param player - * @constructor Create empty Factory - */ - @Suppress("UndocumentedPublicClass") - class Factory( - private var player: Player, - ) : ViewModelProvider.NewInstanceFactory() { - override fun create(modelClass: Class): T { - return PlayerSettingsViewModel(player) as T - } - } -} diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/TrackSelectionSettings.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/TrackSelectionSettings.kt index 247165d87..d98a1d70a 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/TrackSelectionSettings.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/TrackSelectionSettings.kt @@ -20,12 +20,15 @@ import androidx.compose.material3.minimumInteractiveComponentSize 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.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.media3.common.C import androidx.media3.common.Format import androidx.media3.common.TrackGroup import androidx.media3.common.Tracks +import ch.srgssr.pillarbox.demo.shared.R +import ch.srgssr.pillarbox.demo.shared.ui.player.settings.TracksSettingItem import ch.srgssr.pillarbox.demo.ui.theme.PillarboxTheme import ch.srgssr.pillarbox.player.extension.displayName import ch.srgssr.pillarbox.player.extension.hasAccessibilityRoles @@ -33,28 +36,30 @@ import ch.srgssr.pillarbox.player.extension.hasAccessibilityRoles /** * Track selection settings * - * @param listTracksGroup List of tracks. - * @param disabled track type is disabled. - * @param onTrackSelection Action handler. - * @receiver + * @param tracksSetting List of tracks. + * @param modifier The [Modifier] to apply to this screen. + * @param onResetClick The action to perform when clicking on the reset button. + * @param onDisabledClick The action to perform when clicking on the disable button. + * @param onTrackClick The action to perform when clicking on a track. */ @Composable fun TrackSelectionSettings( - listTracksGroup: List, - disabled: Boolean, - onTrackSelection: (TrackSelectionAction) -> Unit + tracksSetting: TracksSettingItem, + modifier: Modifier = Modifier, + onResetClick: () -> Unit, + onDisabledClick: () -> Unit, + onTrackClick: (track: Tracks.Group, trackIndex: Int) -> Unit ) { val itemModifier = Modifier.fillMaxWidth() - LazyColumn { + LazyColumn(modifier = modifier) { item { ListItem( - modifier = Modifier - .fillMaxWidth() + modifier = itemModifier .minimumInteractiveComponentSize() - .clickable { onTrackSelection(TrackSelectionAction.Default) }, + .clickable { onResetClick() }, headlineContent = { Text( - text = "Reset to default" + text = stringResource(R.string.reset_to_default) ) } ) @@ -63,26 +68,24 @@ fun TrackSelectionSettings( item { SettingsOption( modifier = itemModifier, - selected = disabled, - onClick = { - onTrackSelection(TrackSelectionAction.Disable) - }, + selected = tracksSetting.disabled, + onClick = onDisabledClick, content = { - Text(text = "Disabled") + Text(text = stringResource(R.string.disabled)) } ) Divider() } - for (group in listTracksGroup) { + tracksSetting.tracks.forEach { group -> items(group.length) { trackIndex -> - val format = group.getTrackFormat(trackIndex) SettingsOption( modifier = itemModifier, selected = group.isTrackSelected(trackIndex), onClick = { - onTrackSelection(TrackSelectionAction.Selection(trackIndex = trackIndex, format = format, group = group)) + onTrackClick(group, trackIndex) }, content = { + val format = group.getTrackFormat(trackIndex) when (group.type) { C.TRACK_TYPE_AUDIO -> { val str = StringBuilder() @@ -135,34 +138,19 @@ private fun TextTrackSelectionPreview() { .setLanguage("en") .build() val dummyListTrack = listOf( - Tracks.Group(TrackGroup("fr", textTrackFR1), false, arrayOf(C.FORMAT_HANDLED).toIntArray(), arrayOf(true).toBooleanArray()), - Tracks.Group(TrackGroup("en", textTrackEn1), false, arrayOf(C.FORMAT_HANDLED).toIntArray(), arrayOf(false).toBooleanArray()) + Tracks.Group(TrackGroup("fr", textTrackFR1), false, intArrayOf(C.FORMAT_HANDLED), booleanArrayOf(true)), + Tracks.Group(TrackGroup("en", textTrackEn1), false, intArrayOf(C.FORMAT_HANDLED), booleanArrayOf(false)) ) - PillarboxTheme() { - TrackSelectionSettings(dummyListTrack, disabled = false, onTrackSelection = {}) + PillarboxTheme { + TrackSelectionSettings( + tracksSetting = TracksSettingItem( + title = stringResource(R.string.subtitles), + tracks = dummyListTrack, + disabled = false + ), + onResetClick = {}, + onDisabledClick = {}, + onTrackClick = { _, _ -> } + ) } } - -/** - * Track selection action - */ -sealed interface TrackSelectionAction { - /** - * Disable - */ - data object Disable : TrackSelectionAction - - /** - * Automatic - */ - data object Default : TrackSelectionAction - - /** - * Selection - * - * @property trackIndex Track index in [group]. - * @property format Track format. - * @property group Group where belong [format] - */ - data class Selection(val trackIndex: Int, val format: Format, val group: Tracks.Group) : TrackSelectionAction -}