mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Better next up layout
This commit is contained in:
parent
628771211d
commit
a963074f07
4 changed files with 125 additions and 43 deletions
|
|
@ -31,12 +31,12 @@ import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
|||
@Composable
|
||||
fun BannerCard(
|
||||
imageUrl: String?,
|
||||
cornerText: String?,
|
||||
played: Boolean,
|
||||
playPercent: Double,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
cornerText: String? = null,
|
||||
played: Boolean = false,
|
||||
playPercent: Double = 0.0,
|
||||
cardHeight: Dp = 140.dp * .85f,
|
||||
aspectRatio: Float = 16f / 9,
|
||||
interactionSource: MutableInteractionSource? = null,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,20 @@
|
|||
package com.github.damontecres.dolphin.ui.playback
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -17,60 +25,86 @@ import androidx.compose.ui.focus.focusRequester
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.Card
|
||||
import androidx.tv.material3.Icon
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.dolphin.ui.PreviewTvSpec
|
||||
import com.github.damontecres.dolphin.ui.cards.BannerCard
|
||||
import com.github.damontecres.dolphin.ui.theme.DolphinTheme
|
||||
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||
|
||||
@Composable
|
||||
fun NextUpEpisode(
|
||||
ep: BaseItem,
|
||||
title: String?,
|
||||
description: String?,
|
||||
imageUrl: String?,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
aspectRatio: Float = 16f / 9,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||
Card(
|
||||
onClick = onClick,
|
||||
modifier = modifier.focusRequester(focusRequester),
|
||||
Box(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(8.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "Up Next...",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier,
|
||||
modifier = Modifier.padding(8.dp),
|
||||
) {
|
||||
Box {
|
||||
AsyncImage(
|
||||
model = ep.imageUrl,
|
||||
contentDescription = ep.name,
|
||||
BannerCard(
|
||||
imageUrl = imageUrl,
|
||||
onClick = onClick,
|
||||
onLongClick = {},
|
||||
cardHeight = 100.dp,
|
||||
aspectRatio = aspectRatio,
|
||||
modifier =
|
||||
Modifier
|
||||
.focusRequester(focusRequester)
|
||||
.align(Alignment.Center),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.PlayArrow,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.border,
|
||||
modifier =
|
||||
Modifier
|
||||
.size(60.dp)
|
||||
.align(Alignment.Center),
|
||||
)
|
||||
}
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
) {
|
||||
Text(
|
||||
text = ep.data.name ?: "",
|
||||
text = title ?: "",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier,
|
||||
)
|
||||
Text(
|
||||
text = ep.data.overview ?: "",
|
||||
text = description ?: "",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier,
|
||||
)
|
||||
|
|
@ -79,3 +113,28 @@ fun NextUpEpisode(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewTvSpec
|
||||
@Composable
|
||||
fun NextUpEpisodePreview() {
|
||||
DolphinTheme(true) {
|
||||
NextUpEpisode(
|
||||
title = "Episode Title",
|
||||
description = "This is the description of the episode. It might be long.",
|
||||
imageUrl = "",
|
||||
onClick = {},
|
||||
aspectRatio = 4f / 3,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(16.dp)
|
||||
.height(200.dp)
|
||||
.width(400.dp)
|
||||
// .fillMaxWidth(.4f)
|
||||
// .align(Alignment.BottomCenter)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,13 @@ import androidx.compose.foundation.focusable
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
|
|
@ -53,6 +55,7 @@ import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
|
|||
import androidx.media3.ui.compose.state.rememberPresentationState
|
||||
import androidx.media3.ui.compose.state.rememberPreviousButtonState
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.dolphin.ui.components.LoadingPage
|
||||
|
|
@ -320,14 +323,22 @@ fun PlaybackContent(
|
|||
) {
|
||||
nextUp?.let {
|
||||
NextUpEpisode(
|
||||
ep = it,
|
||||
title = it.name,
|
||||
description = it.data.overview,
|
||||
imageUrl = it.imageUrl,
|
||||
aspectRatio = it.data.primaryImageAspectRatio?.toFloat() ?: (16f / 9),
|
||||
onClick = { viewModel.playUpNextEpisode() },
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(16.dp)
|
||||
.height(128.dp)
|
||||
.fillMaxWidth(.4f)
|
||||
.align(Alignment.BottomCenter),
|
||||
.padding(8.dp)
|
||||
// .height(128.dp)
|
||||
.fillMaxHeight(.3f)
|
||||
.fillMaxWidth(.5f)
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ import com.github.damontecres.dolphin.util.subtitleMimeTypes
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
|
|
@ -54,9 +52,6 @@ import timber.log.Timber
|
|||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
enum class TranscodeType {
|
||||
DIRECT_PLAY,
|
||||
|
|
@ -451,23 +446,40 @@ class PlaybackViewModel
|
|||
Timber.v("Current episode is $currentEpisodeIndex of ${episodes.size}")
|
||||
val nextIndex = currentEpisodeIndex + 1
|
||||
if (nextIndex < episodes.size) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
while (this.isActive) {
|
||||
delay(5.seconds)
|
||||
val remaining =
|
||||
withContext(Dispatchers.Main) {
|
||||
(player.duration - player.currentPosition).milliseconds
|
||||
val listener =
|
||||
object : Player.Listener {
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
if (playbackState == Player.STATE_ENDED) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val nextItem = episodes.getBlocking(nextIndex)
|
||||
Timber.v("Setting next up episode to ${nextItem?.id}")
|
||||
withContext(Dispatchers.Main) {
|
||||
nextUpEpisode.value = nextItem
|
||||
}
|
||||
}
|
||||
player.removeListener(this)
|
||||
}
|
||||
if (remaining < 2.minutes) { // TODO time & preference
|
||||
val nextItem = episodes.getBlocking(nextIndex)
|
||||
Timber.v("Setting next up episode to ${nextItem?.id}")
|
||||
withContext(Dispatchers.Main) {
|
||||
nextUpEpisode.value = nextItem
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
player.addListener(listener)
|
||||
|
||||
// viewModelScope.launch(Dispatchers.IO) {
|
||||
// while (this.isActive) {
|
||||
// delay(5.seconds)
|
||||
// val remaining =
|
||||
// withContext(Dispatchers.Main) {
|
||||
// (player.duration - player.currentPosition).milliseconds
|
||||
// }
|
||||
// if (remaining < 2.minutes) { // TODO time & preference
|
||||
// val nextItem = episodes.getBlocking(nextIndex)
|
||||
// Timber.v("Setting next up episode to ${nextItem?.id}")
|
||||
// withContext(Dispatchers.Main) {
|
||||
// nextUpEpisode.value = nextItem
|
||||
// }
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue