Show duration & ends at time on next up card (#1690)

## Description
Adds the duration & ends at time to the next up card

### Related issues
Closes #1664

### Testing
<!-- Describe how this change was tested and on what device(s) -->

## Screenshots
<img width="660" height="241" alt="image"
src="https://github.com/user-attachments/assets/7551843e-4f53-43fc-af2a-3e503ba6d03c"
/>

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-07-13 01:30:54 -04:00 • committed by GitHub
parent f74306a12c
commit 077b7e4570
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 73 additions and 27 deletions

View file

@ -10,10 +10,8 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -21,6 +19,7 @@ import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -29,7 +28,10 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -47,10 +49,17 @@ import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.ui.AppColors import com.github.damontecres.wholphin.ui.AppColors
import com.github.damontecres.wholphin.ui.AspectRatios import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.PreviewTvSpec import com.github.damontecres.wholphin.ui.PreviewTvSpec
import com.github.damontecres.wholphin.ui.dot
import com.github.damontecres.wholphin.ui.formatDuration
import com.github.damontecres.wholphin.ui.formatTime
import com.github.damontecres.wholphin.ui.theme.WholphinTheme import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.ui.util.Clock
import com.github.damontecres.wholphin.ui.util.LocalClock
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
/** /**
* Layout for showing the next up episode during playback * Layout for showing the next up episode during playback
@ -64,6 +73,7 @@ fun NextUpEpisode(
timeLeft: Duration?, timeLeft: Duration?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
aspectRatio: Float = AspectRatios.WIDE, aspectRatio: Float = AspectRatios.WIDE,
runtime: Duration? = null,
) { ) {
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
@ -122,9 +132,36 @@ fun NextUpEpisode(
text = description ?: "", text = description ?: "",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onSurface,
maxLines = 4,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier, modifier = Modifier,
) )
runtime?.let {
val context = LocalContext.current
val resources = LocalResources.current
val now by LocalClock.current.now
val text =
remember(now, runtime) {
buildAnnotatedString {
append(resources.formatDuration(runtime))
dot()
val endsAt = now.plus(runtime.toJavaDuration())
val endTimeStr = formatTime(context, endsAt)
append(resources.getString(R.string.ends_at, endTimeStr))
}
}
Text(
text = text,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier =
Modifier
.padding(top = 4.dp)
.align(Alignment.Start),
)
}
} }
} }
} }
@ -189,34 +226,41 @@ fun NextUpCard(
@Composable @Composable
private fun NextUpEpisodePreview() { private fun NextUpEpisodePreview() {
WholphinTheme(true) { WholphinTheme(true) {
val theme = MaterialTheme.colorScheme
val previewHandler = val previewHandler =
AsyncImagePreviewHandler { AsyncImagePreviewHandler {
ColorImage(Color.Blue.toArgb()) ColorImage(theme.surfaceVariant.toArgb())
}
Box(
modifier = Modifier.fillMaxSize().background(Color.Black),
) {
CompositionLocalProvider(
LocalAsyncImagePreviewHandler provides previewHandler,
LocalClock provides Clock(),
) {
NextUpEpisode(
title = "Episode Title",
description =
"This is the description of the episode. It might be long. " +
"It might be very, very long and overflow the available space. " +
"But it just keeps going and going.",
imageUrl = "",
onClick = {},
aspectRatio = AspectRatios.WIDE,
timeLeft = 30.seconds,
runtime = 32.minutes + 20.seconds,
modifier =
Modifier
.padding(8.dp)
.fillMaxHeight(.4f)
.fillMaxWidth(.66f)
.align(Alignment.BottomCenter)
.background(
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp),
shape = RoundedCornerShape(8.dp),
),
)
} }
CompositionLocalProvider(LocalAsyncImagePreviewHandler provides previewHandler) {
NextUpEpisode(
title = "Episode Title",
description =
"This is the description of the episode. It might be long. " +
"It might be very, very long and overflow the available space. " +
"But it just keeps going and going.",
imageUrl = "",
onClick = {},
aspectRatio = AspectRatios.FOUR_THREE,
timeLeft = 30.seconds,
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),
),
)
} }
} }
} }

View file

@ -94,6 +94,7 @@ import com.github.damontecres.wholphin.util.Media3SubtitleOverride
import io.github.peerless2012.ass.media.widget.AssSubtitleView import io.github.peerless2012.ass.media.widget.AssSubtitleView
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.extensions.ticks
import timber.log.Timber import timber.log.Timber
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@ -600,6 +601,7 @@ fun PlaybackPageContent(
viewModel.playNextUp() viewModel.playNextUp()
}, },
timeLeft = if (autoPlayEnabled) timeLeft.seconds else null, timeLeft = if (autoPlayEnabled) timeLeft.seconds else null,
runtime = it.data.runTimeTicks?.ticks,
modifier = modifier =
Modifier Modifier
.padding(8.dp) .padding(8.dp)