mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-09 15:38:13 -07:00
chore: Android TV UI clean-up
This commit is contained in:
parent
fedc00c65b
commit
721fc28060
9 changed files with 119 additions and 74 deletions
|
|
@ -1,16 +1,16 @@
|
||||||
package nl.jknaapen.fladder.composables.controls
|
package nl.jknaapen.fladder.composables.controls
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
@ -23,42 +23,37 @@ import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import nl.jknaapen.fladder.utility.conditional
|
import nl.jknaapen.fladder.utility.conditional
|
||||||
import nl.jknaapen.fladder.utility.highlightOnFocus
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun CustomIconButton(
|
internal fun CustomIconButton(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
enableFocusIndicator: Boolean = true,
|
|
||||||
enableScaledFocus: Boolean = false,
|
enableScaledFocus: Boolean = false,
|
||||||
backgroundColor: Color = Color.Transparent,
|
backgroundColor: Color = Color.White.copy(alpha = 0.1f),
|
||||||
foreGroundColor: Color = Color.White,
|
foreGroundColor: Color = Color.White,
|
||||||
backgroundFocusedColor: Color = Color.Transparent,
|
backgroundFocusedColor: Color = Color.White,
|
||||||
foreGroundFocusedColor: Color = Color.White,
|
foreGroundFocusedColor: Color = Color.Black,
|
||||||
icon: @Composable () -> Unit,
|
icon: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
var isFocused by remember { mutableStateOf(false) }
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
val currentContentColor by remember {
|
|
||||||
derivedStateOf {
|
|
||||||
if (isFocused) {
|
|
||||||
foreGroundFocusedColor
|
|
||||||
} else {
|
|
||||||
foreGroundColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentBackgroundColor by remember {
|
val currentContentColor by animateColorAsState(
|
||||||
derivedStateOf {
|
if (isFocused) {
|
||||||
if (isFocused) {
|
foreGroundFocusedColor
|
||||||
backgroundFocusedColor
|
} else {
|
||||||
} else {
|
foreGroundColor
|
||||||
backgroundColor
|
}, label = "buttonContentColor"
|
||||||
}
|
)
|
||||||
}
|
|
||||||
}
|
val currentBackgroundColor by animateColorAsState(
|
||||||
|
if (isFocused) {
|
||||||
|
backgroundFocusedColor
|
||||||
|
} else {
|
||||||
|
backgroundColor
|
||||||
|
}, label = "buttonBackground"
|
||||||
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
@ -66,10 +61,7 @@ internal fun CustomIconButton(
|
||||||
.conditional(enableScaledFocus) {
|
.conditional(enableScaledFocus) {
|
||||||
scale(if (isFocused) 1.05f else 1f)
|
scale(if (isFocused) 1.05f else 1f)
|
||||||
}
|
}
|
||||||
.conditional(enableFocusIndicator) {
|
.background(currentBackgroundColor, shape = CircleShape)
|
||||||
highlightOnFocus()
|
|
||||||
}
|
|
||||||
.background(currentBackgroundColor, shape = RoundedCornerShape(8.dp))
|
|
||||||
.onFocusChanged { isFocused = it.isFocused }
|
.onFocusChanged { isFocused = it.isFocused }
|
||||||
.clickable(
|
.clickable(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
|
|
@ -81,7 +73,7 @@ internal fun CustomIconButton(
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(LocalContentColor provides currentContentColor) {
|
CompositionLocalProvider(LocalContentColor provides currentContentColor) {
|
||||||
Box(modifier = Modifier.padding(8.dp)) {
|
Box(modifier = Modifier.padding(16.dp)) {
|
||||||
icon()
|
icon()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import MediaSegment
|
||||||
import MediaSegmentType
|
import MediaSegmentType
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.focusable
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.gestures.detectDragGestures
|
import androidx.compose.foundation.gestures.detectDragGestures
|
||||||
|
|
@ -57,6 +59,7 @@ import androidx.compose.ui.input.key.type
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastCoerceIn
|
import androidx.compose.ui.util.fastCoerceIn
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
|
@ -95,7 +98,9 @@ internal fun ProgressBar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp, alignment = Alignment.CenterVertically)
|
||||||
|
) {
|
||||||
val playbackData by VideoPlayerObject.implementation.playbackData.collectAsState(null)
|
val playbackData by VideoPlayerObject.implementation.playbackData.collectAsState(null)
|
||||||
if (scrubbingTimeLine)
|
if (scrubbingTimeLine)
|
||||||
FilmstripTrickPlayOverlay(
|
FilmstripTrickPlayOverlay(
|
||||||
|
|
@ -108,20 +113,23 @@ internal fun ProgressBar(
|
||||||
trickPlayModel = playbackData?.trickPlayModel
|
trickPlayModel = playbackData?.trickPlayModel
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
val subTitle = playableData?.subTitle
|
val subTitle = playableData?.subTitle
|
||||||
subTitle?.let {
|
subTitle?.let {
|
||||||
Text(
|
Text(
|
||||||
text = it,
|
text = it,
|
||||||
style = MaterialTheme.typography.bodyLarge.copy(color = Color.White),
|
style = MaterialTheme.typography.titleLarge.copy(
|
||||||
|
color = Color.White,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
VideoEndTime()
|
VideoEndTime()
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
8.dp,
|
16.dp,
|
||||||
alignment = Alignment.CenterHorizontally
|
alignment = Alignment.CenterHorizontally
|
||||||
),
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
|
@ -130,7 +138,9 @@ internal fun ProgressBar(
|
||||||
Text(
|
Text(
|
||||||
formatTime(currentPosition),
|
formatTime(currentPosition),
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleLarge.copy(
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
)
|
)
|
||||||
SimpleProgressBar(
|
SimpleProgressBar(
|
||||||
player,
|
player,
|
||||||
|
|
@ -153,7 +163,9 @@ internal fun ProgressBar(
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleLarge.copy(
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,7 +212,7 @@ internal fun RowScope.SimpleProgressBar(
|
||||||
width = it.size.width
|
width = it.size.width
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.heightIn(min = 26.dp)
|
.heightIn(min = 42.dp)
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures { offset ->
|
detectTapGestures { offset ->
|
||||||
onUserInteraction()
|
onUserInteraction()
|
||||||
|
|
@ -241,14 +253,19 @@ internal fun RowScope.SimpleProgressBar(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.focusable(enabled = false)
|
.focusable(enabled = false)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(8.dp)
|
.height(10.dp)
|
||||||
.background(
|
.background(
|
||||||
color = Color.Black.copy(
|
color = Color.White.copy(
|
||||||
alpha = 0.15f
|
alpha = 0.15f
|
||||||
),
|
),
|
||||||
shape = slideBarShape
|
shape = slideBarShape
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val animatedBarColor by animateColorAsState(
|
||||||
|
if (thumbFocused) MaterialTheme.colorScheme.primary else Color.White,
|
||||||
|
label = "progressBarColor"
|
||||||
|
)
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.focusable(enabled = false)
|
.focusable(enabled = false)
|
||||||
|
|
@ -256,9 +273,7 @@ internal fun RowScope.SimpleProgressBar(
|
||||||
.fillMaxWidth(progress)
|
.fillMaxWidth(progress)
|
||||||
.padding(end = 8.dp)
|
.padding(end = 8.dp)
|
||||||
.background(
|
.background(
|
||||||
color = if (thumbFocused) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.primary.copy(
|
color = animatedBarColor,
|
||||||
alpha = 0.75f
|
|
||||||
),
|
|
||||||
shape = slideBarShape
|
shape = slideBarShape
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -292,10 +307,10 @@ internal fun RowScope.SimpleProgressBar(
|
||||||
.focusable(enabled = false)
|
.focusable(enabled = false)
|
||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
translationX = startPx
|
translationX = startPx
|
||||||
translationY = 16.dp.toPx()
|
translationY = 20.dp.toPx()
|
||||||
}
|
}
|
||||||
.width(segDp)
|
.width(segDp)
|
||||||
.height(6.dp)
|
.height(8.dp)
|
||||||
.background(
|
.background(
|
||||||
color = segment.color.copy(alpha = 0.75f),
|
color = segment.color.copy(alpha = 0.75f),
|
||||||
shape = RoundedCornerShape(8.dp)
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
|
@ -318,6 +333,7 @@ internal fun RowScope.SimpleProgressBar(
|
||||||
val durMs = duration.toDouble().coerceAtLeast(1.0)
|
val durMs = duration.toDouble().coerceAtLeast(1.0)
|
||||||
val startPx = (width * (segStartMs / durMs)).toFloat()
|
val startPx = (width * (segStartMs / durMs)).toFloat()
|
||||||
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterStart)
|
.align(Alignment.CenterStart)
|
||||||
|
|
@ -331,7 +347,7 @@ internal fun RowScope.SimpleProgressBar(
|
||||||
.aspectRatio(ratio = 1f)
|
.aspectRatio(ratio = 1f)
|
||||||
.background(
|
.background(
|
||||||
color = if (isAfterCurrentPositon) Color.White.copy(
|
color = if (isAfterCurrentPositon) Color.White.copy(
|
||||||
alpha = 0.5f
|
alpha = 0.15f
|
||||||
) else MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.7f),
|
) else MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.7f),
|
||||||
shape = CircleShape
|
shape = CircleShape
|
||||||
)
|
)
|
||||||
|
|
@ -339,6 +355,12 @@ internal fun RowScope.SimpleProgressBar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val animatedThumbHeight by animateDpAsState(
|
||||||
|
if (thumbFocused) 28.dp else 14.dp,
|
||||||
|
label = "Thumb height"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
//Thumb
|
//Thumb
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -414,8 +436,8 @@ internal fun RowScope.SimpleProgressBar(
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
)
|
)
|
||||||
.width(8.dp)
|
.width(14.dp)
|
||||||
.height(if (thumbFocused) 21.dp else 8.dp)
|
.height(animatedThumbHeight)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||||
internal fun BoxScope.SegmentSkipOverlay(
|
internal fun BoxScope.SegmentSkipOverlay(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val isAndroidTV = leanBackEnabled(LocalContext.current)
|
val isAndroidTV = leanBackEnabled(LocalContext.current)
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import nl.jknaapen.fladder.objects.VideoPlayerObject
|
import nl.jknaapen.fladder.objects.VideoPlayerObject
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
@ -39,6 +40,9 @@ fun VideoEndTime() {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "ends at $formattedEnd",
|
text = "ends at $formattedEnd",
|
||||||
style = MaterialTheme.typography.bodyLarge.copy(color = Color.White),
|
style = MaterialTheme.typography.titleLarge.copy(
|
||||||
|
color = Color.White,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
import androidx.compose.ui.input.key.KeyEvent
|
import androidx.compose.ui.input.key.KeyEvent
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
import androidx.compose.ui.input.key.onKeyEvent
|
import androidx.compose.ui.input.key.onKeyEvent
|
||||||
|
|
@ -96,6 +97,7 @@ fun CustomVideoControls(
|
||||||
|
|
||||||
val buffering by VideoPlayerObject.buffering.collectAsState(true)
|
val buffering by VideoPlayerObject.buffering.collectAsState(true)
|
||||||
val playing by VideoPlayerObject.playing.collectAsState(false)
|
val playing by VideoPlayerObject.playing.collectAsState(false)
|
||||||
|
val controlsPadding = 32.dp
|
||||||
|
|
||||||
ImmersiveSystemBars(isImmersive = !showControls)
|
ImmersiveSystemBars(isImmersive = !showControls)
|
||||||
|
|
||||||
|
|
@ -131,9 +133,12 @@ fun CustomVideoControls(
|
||||||
if (keyEvent.type != KeyEventType.KeyDown) return@onKeyEvent false
|
if (keyEvent.type != KeyEventType.KeyDown) return@onKeyEvent false
|
||||||
if (!showControls) {
|
if (!showControls) {
|
||||||
bottomControlFocusRequester.requestFocus()
|
bottomControlFocusRequester.requestFocus()
|
||||||
|
updateLastInteraction()
|
||||||
|
return@onKeyEvent true
|
||||||
|
} else {
|
||||||
|
updateLastInteraction()
|
||||||
|
return@onKeyEvent false
|
||||||
}
|
}
|
||||||
updateLastInteraction()
|
|
||||||
return@onKeyEvent false
|
|
||||||
}
|
}
|
||||||
.clickable(
|
.clickable(
|
||||||
indication = null,
|
indication = null,
|
||||||
|
|
@ -170,7 +175,7 @@ fun CustomVideoControls(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.safeContentPadding(),
|
.safeContentPadding(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp, alignment = Alignment.Start)
|
horizontalArrangement = Arrangement.spacedBy(16.dp, alignment = Alignment.Start)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
|
|
@ -203,7 +208,7 @@ fun CustomVideoControls(
|
||||||
// Progress Bar
|
// Progress Bar
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = controlsPadding)
|
||||||
.displayCutoutPadding(),
|
.displayCutoutPadding(),
|
||||||
) {
|
) {
|
||||||
ProgressBar(
|
ProgressBar(
|
||||||
|
|
@ -227,8 +232,8 @@ fun CustomVideoControls(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.displayCutoutPadding()
|
.displayCutoutPadding()
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = controlsPadding)
|
||||||
.padding(top = 8.dp, bottom = 16.dp)
|
.padding(top = 8.dp, bottom = controlsPadding)
|
||||||
) {
|
) {
|
||||||
LeftButtons(
|
LeftButtons(
|
||||||
openChapterSelection = {
|
openChapterSelection = {
|
||||||
|
|
@ -245,6 +250,9 @@ fun CustomVideoControls(
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(alignment = Alignment.Center)
|
.align(alignment = Alignment.Center)
|
||||||
|
.size(64.dp),
|
||||||
|
strokeCap = StrokeCap.Round,
|
||||||
|
strokeWidth = 12.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -302,7 +310,7 @@ fun PlaybackButtons(
|
||||||
.padding(horizontal = 4.dp, vertical = 6.dp)
|
.padding(horizontal = 4.dp, vertical = 6.dp)
|
||||||
.wrapContentWidth(),
|
.wrapContentWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
8.dp,
|
16.dp,
|
||||||
alignment = Alignment.CenterHorizontally
|
alignment = Alignment.CenterHorizontally
|
||||||
),
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
|
@ -315,7 +323,6 @@ fun PlaybackButtons(
|
||||||
Iconsax.Filled.Backward,
|
Iconsax.Filled.Backward,
|
||||||
modifier = Modifier.size(32.dp),
|
modifier = Modifier.size(32.dp),
|
||||||
contentDescription = previousVideo,
|
contentDescription = previousVideo,
|
||||||
tint = Color.White
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ internal fun ChapterSelectionSheet(
|
||||||
val chapters = playbackData?.chapters ?: listOf()
|
val chapters = playbackData?.chapters ?: listOf()
|
||||||
val currentPosition by VideoPlayerObject.position.collectAsState(0L)
|
val currentPosition by VideoPlayerObject.position.collectAsState(0L)
|
||||||
|
|
||||||
|
if (chapters.isEmpty()) return
|
||||||
|
|
||||||
var currentChapter: Chapter? by remember {
|
var currentChapter: Chapter? by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
chapters[chapters.indexOfCurrent(
|
chapters[chapters.indexOfCurrent(
|
||||||
|
|
@ -54,7 +56,7 @@ internal fun ChapterSelectionSheet(
|
||||||
|
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(chapters) {
|
||||||
lazyListState.animateScrollToItem(
|
lazyListState.animateScrollToItem(
|
||||||
chapters.indexOfCurrent(currentPosition)
|
chapters.indexOfCurrent(currentPosition)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import androidx.core.content.getSystemService
|
||||||
import androidx.media3.common.AudioAttributes
|
import androidx.media3.common.AudioAttributes
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.TrackSelectionParameters
|
||||||
import androidx.media3.common.Tracks
|
import androidx.media3.common.Tracks
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.datasource.DataSource
|
import androidx.media3.datasource.DataSource
|
||||||
|
|
@ -76,6 +77,7 @@ internal fun ExoPlayer(
|
||||||
|
|
||||||
val audioAttributes = AudioAttributes.Builder()
|
val audioAttributes = AudioAttributes.Builder()
|
||||||
.setUsage(C.USAGE_MEDIA)
|
.setUsage(C.USAGE_MEDIA)
|
||||||
|
.setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val renderersFactory = DefaultRenderersFactory(context)
|
val renderersFactory = DefaultRenderersFactory(context)
|
||||||
|
|
@ -84,6 +86,11 @@ internal fun ExoPlayer(
|
||||||
|
|
||||||
val trackSelector = DefaultTrackSelector(context).apply {
|
val trackSelector = DefaultTrackSelector(context).apply {
|
||||||
setParameters(buildUponParameters().apply {
|
setParameters(buildUponParameters().apply {
|
||||||
|
setAudioOffloadPreferences(
|
||||||
|
TrackSelectionParameters.AudioOffloadPreferences.DEFAULT.buildUpon().apply {
|
||||||
|
setAudioOffloadMode(TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
setTunnelingEnabled(PlayerSettingsObject.settings.value?.enableTunneling ?: false)
|
setTunnelingEnabled(PlayerSettingsObject.settings.value?.enableTunneling ?: false)
|
||||||
setAllowInvalidateSelectionsOnRendererCapabilitiesChange(true)
|
setAllowInvalidateSelectionsOnRendererCapabilitiesChange(true)
|
||||||
})
|
})
|
||||||
|
|
@ -100,6 +107,7 @@ internal fun ExoPlayer(
|
||||||
.buildWithAssSupport(
|
.buildWithAssSupport(
|
||||||
context,
|
context,
|
||||||
renderersFactory = renderersFactory,
|
renderersFactory = renderersFactory,
|
||||||
|
extractorsFactory = extractorsFactory,
|
||||||
renderType = AssRenderType.LEGACY
|
renderType = AssRenderType.LEGACY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,17 +34,26 @@ fun Modifier.highlightOnFocus(
|
||||||
): Modifier = composed {
|
): Modifier = composed {
|
||||||
var hasFocus by remember { mutableStateOf(false) }
|
var hasFocus by remember { mutableStateOf(false) }
|
||||||
val highlightModifier = remember {
|
val highlightModifier = remember {
|
||||||
Modifier
|
if (width != 0.dp) {
|
||||||
.clip(RoundedCornerShape(8.dp))
|
Modifier
|
||||||
.background(
|
.clip(RoundedCornerShape(8.dp))
|
||||||
color = color.copy(alpha = 0.25f),
|
.background(
|
||||||
shape = shape,
|
color = color.copy(alpha = 0.25f),
|
||||||
)
|
shape = shape,
|
||||||
.border(
|
)
|
||||||
width = width,
|
.border(
|
||||||
color = color.copy(alpha = 0.5f),
|
width = width,
|
||||||
shape = shape
|
color = color.copy(alpha = 0.5f),
|
||||||
)
|
shape = shape
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(
|
||||||
|
color = color.copy(alpha = 0.25f),
|
||||||
|
shape = shape,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this
|
this
|
||||||
|
|
|
||||||
|
|
@ -219,15 +219,17 @@ class _HorizontalListState extends ConsumerState<HorizontalList> {
|
||||||
onFocusChange: (value) {
|
onFocusChange: (value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
final nodesOnSameRow = _nodesInRow(parentNode);
|
final nodesOnSameRow = _nodesInRow(parentNode);
|
||||||
final focusNode = lastFocused ?? _firstFullyVisibleNode(context, nodesOnSameRow);
|
final currentNode =
|
||||||
|
nodesOnSameRow.contains(lastFocused) ? lastFocused : _firstFullyVisibleNode(context, nodesOnSameRow);
|
||||||
|
|
||||||
if (focusNode != null) {
|
if (currentNode != null) {
|
||||||
|
lastFocused = currentNode;
|
||||||
if (widget.onFocused != null) {
|
if (widget.onFocused != null) {
|
||||||
widget.onFocused!(nodesOnSameRow.indexOf(focusNode));
|
widget.onFocused!(nodesOnSameRow.indexOf(currentNode));
|
||||||
} else {
|
} else {
|
||||||
context.ensureVisible();
|
context.ensureVisible();
|
||||||
}
|
}
|
||||||
focusNode.requestFocus();
|
currentNode.requestFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -240,7 +242,7 @@ class _HorizontalListState extends ConsumerState<HorizontalList> {
|
||||||
child: FocusTraversalGroup(
|
child: FocusTraversalGroup(
|
||||||
policy: HorizontalRailFocus(
|
policy: HorizontalRailFocus(
|
||||||
parentNode: parentNode,
|
parentNode: parentNode,
|
||||||
throttle: Throttler(duration: const Duration(milliseconds: 125)),
|
throttle: Throttler(duration: const Duration(milliseconds: 100)),
|
||||||
onFocused: (node) {
|
onFocused: (node) {
|
||||||
lastFocused = node;
|
lastFocused = node;
|
||||||
final nodesOnSameRow = _nodesInRow(parentNode);
|
final nodesOnSameRow = _nodesInRow(parentNode);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue