mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
feat: implement track selection buttons for quality, audio, and subtitles
This commit is contained in:
@@ -44,7 +44,6 @@ import hu.bbara.purefin.player.ui.components.PlayerControlsOverlay
|
|||||||
import hu.bbara.purefin.player.ui.components.PlayerGesturesLayer
|
import hu.bbara.purefin.player.ui.components.PlayerGesturesLayer
|
||||||
import hu.bbara.purefin.player.ui.components.PlayerLoadingErrorEndCard
|
import hu.bbara.purefin.player.ui.components.PlayerLoadingErrorEndCard
|
||||||
import hu.bbara.purefin.player.ui.components.PlayerQueuePanel
|
import hu.bbara.purefin.player.ui.components.PlayerQueuePanel
|
||||||
import hu.bbara.purefin.player.ui.components.PlayerSettingsSheet
|
|
||||||
import hu.bbara.purefin.player.viewmodel.PlayerViewModel
|
import hu.bbara.purefin.player.viewmodel.PlayerViewModel
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -65,13 +64,11 @@ fun PlayerScreen(
|
|||||||
val maxVolume = remember { audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).coerceAtLeast(1) }
|
val maxVolume = remember { audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).coerceAtLeast(1) }
|
||||||
var volume by remember { mutableStateOf(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) / maxVolume.toFloat()) }
|
var volume by remember { mutableStateOf(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) / maxVolume.toFloat()) }
|
||||||
var brightness by remember { mutableStateOf(readCurrentBrightness(activity)) }
|
var brightness by remember { mutableStateOf(readCurrentBrightness(activity)) }
|
||||||
var showSettings by remember { mutableStateOf(false) }
|
|
||||||
var showQueuePanel by remember { mutableStateOf(false) }
|
var showQueuePanel by remember { mutableStateOf(false) }
|
||||||
var horizontalSeekFeedback by remember { mutableStateOf<Long?>(null) }
|
var horizontalSeekFeedback by remember { mutableStateOf<Long?>(null) }
|
||||||
|
|
||||||
LaunchedEffect(uiState.isPlaying) {
|
LaunchedEffect(uiState.isPlaying) {
|
||||||
if (uiState.isPlaying) {
|
if (uiState.isPlaying) {
|
||||||
showSettings = false
|
|
||||||
showQueuePanel = false
|
showQueuePanel = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,15 +182,7 @@ fun PlayerScreen(
|
|||||||
onSeekLiveEdge = { viewModel.seekToLiveEdge() },
|
onSeekLiveEdge = { viewModel.seekToLiveEdge() },
|
||||||
onNext = { viewModel.next() },
|
onNext = { viewModel.next() },
|
||||||
onPrevious = { viewModel.previous() },
|
onPrevious = { viewModel.previous() },
|
||||||
onToggleCaptions = {
|
onSelectTrack = { viewModel.selectTrack(it) },
|
||||||
val off = uiState.textTracks.firstOrNull { it.isOff }
|
|
||||||
val currentId = uiState.selectedTextTrackId
|
|
||||||
val next = if (currentId == off?.id) {
|
|
||||||
uiState.textTracks.firstOrNull { !it.isOff }
|
|
||||||
} else off
|
|
||||||
next?.let { viewModel.selectTrack(it) }
|
|
||||||
},
|
|
||||||
onShowSettings = { showSettings = true },
|
|
||||||
onQueueSelected = { viewModel.playQueueItem(it) },
|
onQueueSelected = { viewModel.playQueueItem(it) },
|
||||||
onOpenQueue = { showQueuePanel = true }
|
onOpenQueue = { showQueuePanel = true }
|
||||||
)
|
)
|
||||||
@@ -210,14 +199,6 @@ fun PlayerScreen(
|
|||||||
onDismissError = { viewModel.clearError() }
|
onDismissError = { viewModel.clearError() }
|
||||||
)
|
)
|
||||||
|
|
||||||
PlayerSettingsSheet(
|
|
||||||
visible = showSettings,
|
|
||||||
uiState = uiState,
|
|
||||||
onDismiss = { showSettings = false },
|
|
||||||
onSelectTrack = { viewModel.selectTrack(it) },
|
|
||||||
onSpeedSelected = { viewModel.setPlaybackSpeed(it) }
|
|
||||||
)
|
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = showQueuePanel,
|
visible = showQueuePanel,
|
||||||
enter = slideInHorizontally { it },
|
enter = slideInHorizontally { it },
|
||||||
|
|||||||
@@ -20,10 +20,8 @@ import androidx.compose.material.icons.outlined.Pause
|
|||||||
import androidx.compose.material.icons.outlined.PlayArrow
|
import androidx.compose.material.icons.outlined.PlayArrow
|
||||||
import androidx.compose.material.icons.outlined.PlaylistPlay
|
import androidx.compose.material.icons.outlined.PlaylistPlay
|
||||||
import androidx.compose.material.icons.outlined.Replay10
|
import androidx.compose.material.icons.outlined.Replay10
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
|
||||||
import androidx.compose.material.icons.outlined.SkipNext
|
import androidx.compose.material.icons.outlined.SkipNext
|
||||||
import androidx.compose.material.icons.outlined.SkipPrevious
|
import androidx.compose.material.icons.outlined.SkipPrevious
|
||||||
import androidx.compose.material.icons.outlined.Subtitles
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -40,6 +38,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import hu.bbara.purefin.common.ui.components.GhostIconButton
|
import hu.bbara.purefin.common.ui.components.GhostIconButton
|
||||||
import hu.bbara.purefin.common.ui.components.PurefinIconButton
|
import hu.bbara.purefin.common.ui.components.PurefinIconButton
|
||||||
import hu.bbara.purefin.player.model.PlayerUiState
|
import hu.bbara.purefin.player.model.PlayerUiState
|
||||||
|
import hu.bbara.purefin.player.model.TrackOption
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PlayerControlsOverlay(
|
fun PlayerControlsOverlay(
|
||||||
@@ -52,8 +51,7 @@ fun PlayerControlsOverlay(
|
|||||||
onSeekLiveEdge: () -> Unit,
|
onSeekLiveEdge: () -> Unit,
|
||||||
onNext: () -> Unit,
|
onNext: () -> Unit,
|
||||||
onPrevious: () -> Unit,
|
onPrevious: () -> Unit,
|
||||||
onToggleCaptions: () -> Unit,
|
onSelectTrack: (TrackOption) -> Unit,
|
||||||
onShowSettings: () -> Unit,
|
|
||||||
onQueueSelected: (String) -> Unit,
|
onQueueSelected: (String) -> Unit,
|
||||||
onOpenQueue: () -> Unit,
|
onOpenQueue: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
@@ -100,8 +98,7 @@ fun PlayerControlsOverlay(
|
|||||||
onSeekForward = { onSeekRelative(30_000) },
|
onSeekForward = { onSeekRelative(30_000) },
|
||||||
onSeekBackward = { onSeekRelative(-10_000) },
|
onSeekBackward = { onSeekRelative(-10_000) },
|
||||||
onSeekLiveEdge = onSeekLiveEdge,
|
onSeekLiveEdge = onSeekLiveEdge,
|
||||||
onToggleCaptions = onToggleCaptions,
|
onSelectTrack = onSelectTrack,
|
||||||
onShowSettings = onShowSettings,
|
|
||||||
onQueueSelected = onQueueSelected,
|
onQueueSelected = onQueueSelected,
|
||||||
modifier = Modifier.align(Alignment.BottomCenter)
|
modifier = Modifier.align(Alignment.BottomCenter)
|
||||||
)
|
)
|
||||||
@@ -163,8 +160,7 @@ private fun BottomSection(
|
|||||||
onSeekForward: () -> Unit,
|
onSeekForward: () -> Unit,
|
||||||
onSeekBackward: () -> Unit,
|
onSeekBackward: () -> Unit,
|
||||||
onSeekLiveEdge: () -> Unit,
|
onSeekLiveEdge: () -> Unit,
|
||||||
onToggleCaptions: () -> Unit,
|
onSelectTrack: (TrackOption) -> Unit,
|
||||||
onShowSettings: () -> Unit,
|
|
||||||
onQueueSelected: (String) -> Unit,
|
onQueueSelected: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
@@ -259,15 +255,20 @@ private fun BottomSection(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
PurefinIconButton(
|
QualitySelectionButton(
|
||||||
icon = Icons.Outlined.Subtitles,
|
options = uiState.qualityTracks,
|
||||||
contentDescription = "Captions",
|
selectedId = uiState.selectedQualityTrackId,
|
||||||
onClick = onToggleCaptions
|
onSelect = onSelectTrack
|
||||||
)
|
)
|
||||||
PurefinIconButton(
|
AudioSelectionButton(
|
||||||
icon = Icons.Outlined.Settings,
|
options = uiState.audioTracks,
|
||||||
contentDescription = "Settings",
|
selectedId = uiState.selectedAudioTrackId,
|
||||||
onClick = onShowSettings
|
onSelect = onSelectTrack
|
||||||
|
)
|
||||||
|
SubtitlesSelectionButton(
|
||||||
|
options = uiState.textTracks,
|
||||||
|
selectedId = uiState.selectedTextTrackId,
|
||||||
|
onSelect = onSelectTrack
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,196 +0,0 @@
|
|||||||
package hu.bbara.purefin.player.ui.components
|
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.slideInVertically
|
|
||||||
import androidx.compose.animation.slideOutVertically
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
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.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.Close
|
|
||||||
import androidx.compose.material.icons.outlined.ClosedCaption
|
|
||||||
import androidx.compose.material.icons.outlined.HighQuality
|
|
||||||
import androidx.compose.material.icons.outlined.Language
|
|
||||||
import androidx.compose.material.icons.outlined.Speed
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import hu.bbara.purefin.player.model.PlayerUiState
|
|
||||||
import hu.bbara.purefin.player.model.TrackOption
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PlayerSettingsSheet(
|
|
||||||
visible: Boolean,
|
|
||||||
uiState: PlayerUiState,
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
onSelectTrack: (TrackOption) -> Unit,
|
|
||||||
onSpeedSelected: (Float) -> Unit
|
|
||||||
) {
|
|
||||||
val scheme = MaterialTheme.colorScheme
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = visible,
|
|
||||||
enter = slideInVertically(initialOffsetY = { it }) + androidx.compose.animation.fadeIn(),
|
|
||||||
exit = slideOutVertically(targetOffsetY = { it }) + androidx.compose.animation.fadeOut()
|
|
||||||
) {
|
|
||||||
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.BottomCenter) {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)),
|
|
||||||
color = scheme.surface.copy(alpha = 0.98f)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.padding(horizontal = 20.dp, vertical = 16.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(text = "Playback settings", color = scheme.onSurface, style = MaterialTheme.typography.titleMedium)
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.Close,
|
|
||||||
contentDescription = "Close",
|
|
||||||
tint = scheme.onSurface,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(50))
|
|
||||||
.clickable { onDismiss() }
|
|
||||||
.padding(8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
TrackGroup(
|
|
||||||
label = "Audio track",
|
|
||||||
icon = { Icon(Icons.Outlined.Language, contentDescription = null, tint = scheme.onSurface) },
|
|
||||||
options = uiState.audioTracks,
|
|
||||||
selectedId = uiState.selectedAudioTrackId,
|
|
||||||
onSelect = onSelectTrack
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
TrackGroup(
|
|
||||||
label = "Subtitles",
|
|
||||||
icon = { Icon(Icons.Outlined.ClosedCaption, contentDescription = null, tint = scheme.onSurface) },
|
|
||||||
options = uiState.textTracks,
|
|
||||||
selectedId = uiState.selectedTextTrackId,
|
|
||||||
onSelect = onSelectTrack
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
TrackGroup(
|
|
||||||
label = "Quality",
|
|
||||||
icon = { Icon(Icons.Outlined.HighQuality, contentDescription = null, tint = scheme.onSurface) },
|
|
||||||
options = uiState.qualityTracks,
|
|
||||||
selectedId = uiState.selectedQualityTrackId,
|
|
||||||
onSelect = onSelectTrack
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
SpeedGroup(
|
|
||||||
selectedSpeed = uiState.playbackSpeed,
|
|
||||||
onSpeedSelected = onSpeedSelected
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun TrackGroup(
|
|
||||||
label: String,
|
|
||||||
icon: @Composable () -> Unit,
|
|
||||||
options: List<TrackOption>,
|
|
||||||
selectedId: String?,
|
|
||||||
onSelect: (TrackOption) -> Unit
|
|
||||||
) {
|
|
||||||
val scheme = MaterialTheme.colorScheme
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
icon()
|
|
||||||
Text(text = label, color = scheme.onSurface, style = MaterialTheme.typography.titleSmall)
|
|
||||||
}
|
|
||||||
FlowChips(
|
|
||||||
items = options,
|
|
||||||
selectedId = selectedId,
|
|
||||||
onSelect = onSelect
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
private fun FlowChips(
|
|
||||||
items: List<TrackOption>,
|
|
||||||
selectedId: String?,
|
|
||||||
onSelect: (TrackOption) -> Unit
|
|
||||||
) {
|
|
||||||
val scheme = MaterialTheme.colorScheme
|
|
||||||
androidx.compose.foundation.layout.FlowRow(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
items.forEach { option ->
|
|
||||||
val selected = option.id == selectedId
|
|
||||||
Text(
|
|
||||||
text = option.label,
|
|
||||||
color = if (selected) scheme.onPrimary else scheme.onSurface,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(12.dp))
|
|
||||||
.background(if (selected) scheme.primary else scheme.surfaceVariant)
|
|
||||||
.clickable { onSelect(option) }
|
|
||||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
private fun SpeedGroup(
|
|
||||||
selectedSpeed: Float,
|
|
||||||
onSpeedSelected: (Float) -> Unit
|
|
||||||
) {
|
|
||||||
val options = listOf(0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f)
|
|
||||||
val scheme = MaterialTheme.colorScheme
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
Icon(Icons.Outlined.Speed, contentDescription = null, tint = scheme.onSurface)
|
|
||||||
Text(text = "Playback speed", color = scheme.onSurface, style = MaterialTheme.typography.titleSmall)
|
|
||||||
}
|
|
||||||
androidx.compose.foundation.layout.FlowRow(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
options.forEach { speed ->
|
|
||||||
val selected = speed == selectedSpeed
|
|
||||||
Text(
|
|
||||||
text = "${speed}x",
|
|
||||||
color = if (selected) scheme.onPrimary else scheme.onSurface,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(12.dp))
|
|
||||||
.background(if (selected) scheme.primary else scheme.surfaceVariant)
|
|
||||||
.clickable { onSpeedSelected(speed) }
|
|
||||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
package hu.bbara.purefin.player.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.ClosedCaption
|
||||||
|
import androidx.compose.material.icons.outlined.HighQuality
|
||||||
|
import androidx.compose.material.icons.outlined.Language
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import hu.bbara.purefin.common.ui.components.PurefinIconButton
|
||||||
|
import hu.bbara.purefin.player.model.TrackOption
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun QualitySelectionButton(
|
||||||
|
options: List<TrackOption>,
|
||||||
|
selectedId: String?,
|
||||||
|
onSelect: (TrackOption) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
|
||||||
|
Box(modifier = modifier) {
|
||||||
|
PurefinIconButton(
|
||||||
|
icon = Icons.Outlined.HighQuality,
|
||||||
|
contentDescription = "Quality",
|
||||||
|
onClick = { expanded = true }
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false },
|
||||||
|
modifier = Modifier
|
||||||
|
.widthIn(min = 160.dp, max = 280.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(scheme.surface.copy(alpha = 0.98f))
|
||||||
|
.widthIn(min = 160.dp, max = 280.dp)
|
||||||
|
.heightIn(max = 280.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(vertical = 8.dp, horizontal = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
val selected = option.id == selectedId
|
||||||
|
TrackOptionItem(
|
||||||
|
label = option.label,
|
||||||
|
selected = selected,
|
||||||
|
onClick = {
|
||||||
|
onSelect(option)
|
||||||
|
expanded = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AudioSelectionButton(
|
||||||
|
options: List<TrackOption>,
|
||||||
|
selectedId: String?,
|
||||||
|
onSelect: (TrackOption) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
|
||||||
|
Box(modifier = modifier) {
|
||||||
|
PurefinIconButton(
|
||||||
|
icon = Icons.Outlined.Language,
|
||||||
|
contentDescription = "Audio",
|
||||||
|
onClick = { expanded = true }
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false },
|
||||||
|
modifier = Modifier
|
||||||
|
.widthIn(min = 160.dp, max = 280.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(scheme.surface.copy(alpha = 0.98f))
|
||||||
|
.widthIn(min = 160.dp, max = 280.dp)
|
||||||
|
.heightIn(max = 280.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(vertical = 8.dp, horizontal = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
val selected = option.id == selectedId
|
||||||
|
TrackOptionItem(
|
||||||
|
label = option.label,
|
||||||
|
selected = selected,
|
||||||
|
onClick = {
|
||||||
|
onSelect(option)
|
||||||
|
expanded = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SubtitlesSelectionButton(
|
||||||
|
options: List<TrackOption>,
|
||||||
|
selectedId: String?,
|
||||||
|
onSelect: (TrackOption) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
|
||||||
|
Box(modifier = modifier) {
|
||||||
|
PurefinIconButton(
|
||||||
|
icon = Icons.Outlined.ClosedCaption,
|
||||||
|
contentDescription = "Subtitles",
|
||||||
|
onClick = { expanded = true }
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false },
|
||||||
|
modifier = Modifier
|
||||||
|
.widthIn(min = 160.dp, max = 280.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(scheme.surface.copy(alpha = 0.98f))
|
||||||
|
.widthIn(min = 160.dp, max = 280.dp)
|
||||||
|
.heightIn(max = 280.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(vertical = 8.dp, horizontal = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
val selected = option.id == selectedId
|
||||||
|
TrackOptionItem(
|
||||||
|
label = option.label,
|
||||||
|
selected = selected,
|
||||||
|
onClick = {
|
||||||
|
onSelect(option)
|
||||||
|
expanded = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TrackOptionItem(
|
||||||
|
label: String,
|
||||||
|
selected: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
.background(
|
||||||
|
if (selected) {
|
||||||
|
scheme.primary.copy(alpha = 0.15f)
|
||||||
|
} else {
|
||||||
|
scheme.surfaceVariant.copy(alpha = 0.6f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
color = scheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user