mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Support auto play next up
This commit is contained in:
parent
a963074f07
commit
73bb96adfb
6 changed files with 144 additions and 23 deletions
|
|
@ -200,6 +200,38 @@ sealed interface AppPreference<T> {
|
|||
summaryOff = R.string.hide,
|
||||
)
|
||||
|
||||
val AutoPlayNextUp =
|
||||
AppSwitchPreference(
|
||||
title = R.string.auto_play_next,
|
||||
defaultValue = true,
|
||||
getter = { it.playbackPreferences.autoPlayNext },
|
||||
setter = { prefs, value ->
|
||||
prefs.updatePlaybackPreferences { autoPlayNext = value }
|
||||
},
|
||||
summaryOn = R.string.enabled,
|
||||
summaryOff = R.string.disabled,
|
||||
)
|
||||
|
||||
val AutoPlayNextDelay =
|
||||
AppSliderPreference(
|
||||
title = R.string.auto_play_next_delay,
|
||||
defaultValue = 15,
|
||||
min = 0,
|
||||
max = 60,
|
||||
interval = 5,
|
||||
getter = { it.playbackPreferences.autoPlayNextDelaySeconds },
|
||||
setter = { prefs, value ->
|
||||
prefs.updatePlaybackPreferences { autoPlayNextDelaySeconds = value }
|
||||
},
|
||||
summarizer = { value ->
|
||||
if (value == 0L) {
|
||||
"Immediate"
|
||||
} else {
|
||||
"$value seconds"
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val InstalledVersion =
|
||||
AppClickablePreference(
|
||||
title = R.string.installed_version,
|
||||
|
|
@ -256,6 +288,8 @@ val basicPreferences =
|
|||
AppPreference.SkipForward,
|
||||
AppPreference.SkipBack,
|
||||
AppPreference.ControllerTimeout,
|
||||
AppPreference.AutoPlayNextUp,
|
||||
AppPreference.AutoPlayNextDelay,
|
||||
AppPreference.PlaybackDebugInfo,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ class AppPreferencesSerializer
|
|||
controllerTimeoutMs = AppPreference.ControllerTimeout.defaultValue
|
||||
seekBarSteps = AppPreference.SeekBarSteps.defaultValue.toInt()
|
||||
showDebugInfo = AppPreference.PlaybackDebugInfo.defaultValue
|
||||
autoPlayNext = AppPreference.AutoPlayNextUp.defaultValue
|
||||
autoPlayNextDelaySeconds =
|
||||
AppPreference.AutoPlayNextDelay.defaultValue
|
||||
}.build()
|
||||
homePagePreferences =
|
||||
HomePagePreferences
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ 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.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
|
|
@ -22,17 +23,22 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
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 androidx.tv.material3.surfaceColorAtElevation
|
||||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.dolphin.ui.AppColors
|
||||
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
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Composable
|
||||
fun NextUpEpisode(
|
||||
|
|
@ -40,6 +46,7 @@ fun NextUpEpisode(
|
|||
description: String?,
|
||||
imageUrl: String?,
|
||||
onClick: () -> Unit,
|
||||
timeLeft: Duration?,
|
||||
modifier: Modifier = Modifier,
|
||||
aspectRatio: Float = 16f / 9,
|
||||
) {
|
||||
|
|
@ -67,28 +74,16 @@ fun NextUpEpisode(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(8.dp),
|
||||
) {
|
||||
Box {
|
||||
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),
|
||||
)
|
||||
}
|
||||
NextUpCard(
|
||||
imageUrl = imageUrl,
|
||||
onClick = onClick,
|
||||
timeLeft = timeLeft,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth(.4f)
|
||||
.fillMaxHeight()
|
||||
.focusRequester(focusRequester),
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
|
|
@ -114,6 +109,56 @@ fun NextUpEpisode(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NextUpCard(
|
||||
imageUrl: String?,
|
||||
onClick: () -> Unit,
|
||||
timeLeft: Duration?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
onClick = onClick,
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
AsyncImage(
|
||||
model = imageUrl,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
if (timeLeft != null && timeLeft > Duration.ZERO) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.Center)
|
||||
.background(
|
||||
AppColors.TransparentBlack50,
|
||||
shape = CircleShape,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = timeLeft.toString(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(8.dp),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Default.PlayArrow,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
modifier =
|
||||
Modifier
|
||||
.size(60.dp)
|
||||
.align(Alignment.Center),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewTvSpec
|
||||
@Composable
|
||||
fun NextUpEpisodePreview() {
|
||||
|
|
@ -124,6 +169,7 @@ fun NextUpEpisodePreview() {
|
|||
imageUrl = "",
|
||||
onClick = {},
|
||||
aspectRatio = 4f / 3,
|
||||
timeLeft = 30.seconds,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(16.dp)
|
||||
|
|
|
|||
|
|
@ -62,8 +62,10 @@ import com.github.damontecres.dolphin.ui.components.LoadingPage
|
|||
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||
import kotlinx.coroutines.delay
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
@Composable
|
||||
|
|
@ -322,12 +324,44 @@ fun PlaybackContent(
|
|||
.align(Alignment.BottomCenter),
|
||||
) {
|
||||
nextUp?.let {
|
||||
var autoPlayEnabled by
|
||||
remember {
|
||||
mutableStateOf(
|
||||
preferences.appPreferences.playbackPreferences.autoPlayNext,
|
||||
)
|
||||
}
|
||||
var timeLeft by remember {
|
||||
mutableLongStateOf(
|
||||
preferences.appPreferences.playbackPreferences.autoPlayNextDelaySeconds,
|
||||
)
|
||||
}
|
||||
// TODO need extra back press for some reason
|
||||
BackHandler(timeLeft > 0 && autoPlayEnabled) {
|
||||
timeLeft = -1
|
||||
autoPlayEnabled = false
|
||||
}
|
||||
if (autoPlayEnabled) {
|
||||
LaunchedEffect(Unit) {
|
||||
if (timeLeft == 0L) {
|
||||
viewModel.playUpNextEpisode()
|
||||
} else {
|
||||
while (timeLeft > 0) {
|
||||
delay(1.seconds)
|
||||
timeLeft--
|
||||
}
|
||||
if (timeLeft == 0L && autoPlayEnabled) {
|
||||
viewModel.playUpNextEpisode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NextUpEpisode(
|
||||
title = it.name,
|
||||
description = it.data.overview,
|
||||
imageUrl = it.imageUrl,
|
||||
aspectRatio = it.data.primaryImageAspectRatio?.toFloat() ?: (16f / 9),
|
||||
onClick = { viewModel.playUpNextEpisode() },
|
||||
timeLeft = if (autoPlayEnabled) timeLeft.seconds else null,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(8.dp)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ message PlaybackPreferences {
|
|||
int64 controller_timeout_ms = 3;
|
||||
int32 seek_bar_steps = 4;
|
||||
bool show_debug_info = 5;
|
||||
bool auto_play_next = 6;
|
||||
int64 auto_play_next_delay_seconds = 7;
|
||||
}
|
||||
|
||||
message HomePagePreferences{
|
||||
|
|
|
|||
|
|
@ -45,4 +45,6 @@
|
|||
<string name="play_theme_music">Play theme music</string>
|
||||
<string name="license_info">License information</string>
|
||||
<string name="playback_debug_info">Show playback debug info</string>
|
||||
<string name="auto_play_next_delay">Delay before playing next up</string>
|
||||
<string name="auto_play_next">Auto play next up</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue