From 2cd34692e914b8fd4c6fd9601e6059a33d00f4c5 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Fri, 9 Jan 2026 14:01:37 -0500 Subject: [PATCH] Show indicator for multiple versions of media file (#652) ## Description Adds an indicator to cards on grid pages if there are more than a single version of the item. Also adds the count to the video stream label, much like for multiple audio/subtitle tracks. No behavior changes, just visual changes. You still need to select the version from More->Choose Version. ### Related issues Closes #612 ### Screenshots image image --- .../damontecres/wholphin/ui/UiConstants.kt | 2 + .../wholphin/ui/cards/EpisodeCard.kt | 1 + .../wholphin/ui/cards/ExtrasRow.kt | 1 + .../damontecres/wholphin/ui/cards/GridCard.kt | 1 + .../wholphin/ui/cards/ItemCardImage.kt | 51 +++++++++++++++---- .../wholphin/ui/cards/PersonCard.kt | 1 + .../wholphin/ui/cards/SeasonCard.kt | 3 ++ .../ui/components/VideoStreamDetails.kt | 18 ++++--- .../wholphin/ui/detail/PlaylistDetails.kt | 1 + .../ui/detail/episode/EpisodeDetailsHeader.kt | 1 + .../ui/detail/movie/MovieDetailsHeader.kt | 1 + .../ui/detail/series/FocusedEpisodeHeader.kt | 1 + .../wholphin/ui/theme/ThemePreview.kt | 4 +- 13 files changed, 67 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/UiConstants.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/UiConstants.kt index beafccb2..6b33ad49 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/UiConstants.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/UiConstants.kt @@ -51,6 +51,7 @@ val DefaultItemFields = ItemFields.SORT_NAME, ItemFields.CHAPTERS, ItemFields.MEDIA_SOURCES, + ItemFields.MEDIA_SOURCE_COUNT, ) /** @@ -63,6 +64,7 @@ val SlimItemFields = ItemFields.CHILD_COUNT, ItemFields.OVERVIEW, ItemFields.SORT_NAME, + ItemFields.MEDIA_SOURCE_COUNT, ) object Cards { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/EpisodeCard.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/EpisodeCard.kt index 8cc3ad12..e4ffa5ee 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/EpisodeCard.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/EpisodeCard.kt @@ -97,6 +97,7 @@ fun EpisodeCard( watched = dto?.userData?.played ?: false, unwatchedCount = dto?.userData?.unplayedItemCount ?: -1, watchedPercent = dto?.userData?.playedPercentage, + numberOfVersions = dto?.mediaSourceCount ?: 0, useFallbackText = false, modifier = Modifier diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ExtrasRow.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ExtrasRow.kt index 39750da3..adfff582 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ExtrasRow.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ExtrasRow.kt @@ -68,6 +68,7 @@ fun ExtrasRow( isPlayed = false, unplayedItemCount = -1, playedPercentage = -1.0, + numberOfVersions = -1, aspectRatio = AspectRatios.FOUR_THREE, // TODO ) }, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/GridCard.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/GridCard.kt index 1bc35a08..7b653865 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/GridCard.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/GridCard.kt @@ -91,6 +91,7 @@ fun GridCard( watched = dto?.userData?.played ?: false, unwatchedCount = dto?.userData?.unplayedItemCount ?: -1, watchedPercent = dto?.userData?.playedPercentage, + numberOfVersions = dto?.mediaSourceCount ?: 0, useFallbackText = false, contentScale = imageContentScale, modifier = diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ItemCardImage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ItemCardImage.kt index 9254fbd3..1a2bbacf 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ItemCardImage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ItemCardImage.kt @@ -58,6 +58,7 @@ fun ItemCardImage( watched: Boolean, unwatchedCount: Int, watchedPercent: Double?, + numberOfVersions: Int, modifier: Modifier = Modifier, imageType: ImageType = ImageType.PRIMARY, useFallbackText: Boolean = true, @@ -86,6 +87,7 @@ fun ItemCardImage( watched = watched, unwatchedCount = unwatchedCount, watchedPercent = watchedPercent, + numberOfVersions = numberOfVersions, modifier = modifier.onLayoutRectChanged( throttleMillis = 100, @@ -107,6 +109,7 @@ fun ItemCardImage( watched: Boolean, unwatchedCount: Int, watchedPercent: Double?, + numberOfVersions: Int, modifier: Modifier = Modifier, useFallbackText: Boolean = true, contentScale: ContentScale = ContentScale.Fit, @@ -143,6 +146,7 @@ fun ItemCardImage( watched = watched, unwatchedCount = unwatchedCount, watchedPercent = watchedPercent, + numberOfVersions = numberOfVersions, modifier = Modifier, ) } @@ -198,20 +202,45 @@ fun ItemCardImageOverlay( watched: Boolean, unwatchedCount: Int, watchedPercent: Double?, + numberOfVersions: Int, modifier: Modifier = Modifier, ) { Box(modifier = modifier.fillMaxSize()) { - if (favorite) { - Text( - modifier = - Modifier - .align(Alignment.TopStart) - .padding(8.dp), - color = colorResource(android.R.color.holo_red_light), - text = stringResource(R.string.fa_heart), - fontSize = 20.sp, - fontFamily = FontAwesome, - ) + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .align(Alignment.TopStart) + .padding(4.dp), + ) { + if (numberOfVersions > 1) { + Box( + modifier = + Modifier + .background( + AppColors.TransparentBlack50, + shape = RoundedCornerShape(25), + ), + ) { + Text( + text = numberOfVersions.toString(), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyMedium, +// fontSize = 16.sp, + modifier = Modifier.padding(4.dp), + ) + } + } + if (favorite) { + Text( + color = colorResource(android.R.color.holo_red_light), + text = stringResource(R.string.fa_heart), + fontSize = 20.sp, + fontFamily = FontAwesome, + modifier = Modifier, + ) + } } Row( horizontalArrangement = Arrangement.spacedBy(4.dp), diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/PersonCard.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/PersonCard.kt index 83d72010..4d0c0fe5 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/PersonCard.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/PersonCard.kt @@ -77,6 +77,7 @@ fun PersonCard( favorite = item.favorite, watched = false, unwatchedCount = -1, + numberOfVersions = -1, watchedPercent = null, useFallbackText = false, modifier = diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/SeasonCard.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/SeasonCard.kt index 243bb6d6..e3e6059e 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/SeasonCard.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/SeasonCard.kt @@ -88,6 +88,7 @@ fun SeasonCard( isPlayed = item?.data?.userData?.played ?: false, unplayedItemCount = item?.data?.userData?.unplayedItemCount ?: 0, playedPercentage = item?.data?.userData?.playedPercentage ?: 0.0, + numberOfVersions = item?.data?.mediaSourceCount ?: 0, onClick = onClick, onLongClick = onLongClick, modifier = modifier, @@ -112,6 +113,7 @@ fun SeasonCard( isPlayed: Boolean, unplayedItemCount: Int, playedPercentage: Double, + numberOfVersions: Int, onClick: () -> Unit, onLongClick: () -> Unit, modifier: Modifier = Modifier, @@ -172,6 +174,7 @@ fun SeasonCard( watched = isPlayed, unwatchedCount = unplayedItemCount, watchedPercent = playedPercentage, + numberOfVersions = numberOfVersions, useFallbackText = false, modifier = Modifier diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/VideoStreamDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/VideoStreamDetails.kt index a557d7f0..e9a11d54 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/VideoStreamDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/VideoStreamDetails.kt @@ -47,12 +47,14 @@ import org.jellyfin.sdk.model.api.VideoRangeType @NonRestartableComposable fun VideoStreamDetails( chosenStreams: ChosenStreams?, + numberOfVersions: Int, modifier: Modifier = Modifier, ) = VideoStreamDetails( chosenStreams?.source, chosenStreams?.videoStream, chosenStreams?.audioStream, chosenStreams?.subtitleStream, + numberOfVersions, modifier, ) @@ -62,6 +64,7 @@ fun VideoStreamDetails( videoStream: MediaStream?, audioStream: MediaStream?, subtitleStream: MediaStream?, + numberOfVersions: Int = 0, modifier: Modifier = Modifier, ) { val context = LocalContext.current @@ -86,13 +89,16 @@ fun VideoStreamDetails( null } val range = formatVideoRange(context, it.videoRange, it.videoRangeType, it.videoDoViTitle) - listOfNotNull( - resName.concatWithSpace(range), - it.codec?.uppercase(), - ) - }.orEmpty() + resName.concatWithSpace(range) + } } - video.forEach { + video?.let { + StreamLabel( + text = it, + count = numberOfVersions, + ) + } + videoStream?.codec?.uppercase()?.let { StreamLabel(it) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/PlaylistDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/PlaylistDetails.kt index 4907d71e..5bf996a3 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/PlaylistDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/PlaylistDetails.kt @@ -444,6 +444,7 @@ fun PlaylistItem( watched = item?.data?.userData?.played ?: false, unwatchedCount = item?.data?.userData?.unplayedItemCount ?: -1, watchedPercent = 0.0, + numberOfVersions = item?.data?.mediaSourceCount ?: 0, modifier = Modifier.width(160.dp), useFallbackText = false, ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetailsHeader.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetailsHeader.kt index d3c77b67..09c8ed0f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetailsHeader.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetailsHeader.kt @@ -59,6 +59,7 @@ fun EpisodeDetailsHeader( VideoStreamDetails( chosenStreams = chosenStreams, + numberOfVersions = dto.mediaSourceCount ?: 0, modifier = Modifier.padding(bottom = padding), ) dto.taglines?.firstOrNull()?.let { tagline -> diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetailsHeader.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetailsHeader.kt index c976e79b..df30078c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetailsHeader.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetailsHeader.kt @@ -73,6 +73,7 @@ fun MovieDetailsHeader( VideoStreamDetails( chosenStreams = chosenStreams, + numberOfVersions = movie.data.mediaSourceCount ?: 0, modifier = Modifier.padding(bottom = padding), ) dto.taglines?.firstOrNull()?.let { tagline -> diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/FocusedEpisodeHeader.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/FocusedEpisodeHeader.kt index 5b25783b..97791749 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/FocusedEpisodeHeader.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/FocusedEpisodeHeader.kt @@ -38,6 +38,7 @@ fun FocusedEpisodeHeader( if (dto != null) { VideoStreamDetails( chosenStreams = chosenStreams, + numberOfVersions = dto.mediaSourceCount ?: 0, modifier = Modifier, ) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/theme/ThemePreview.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/theme/ThemePreview.kt index dfae73c5..8d3735e9 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/theme/ThemePreview.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/theme/ThemePreview.kt @@ -30,10 +30,8 @@ import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppThemeColors import com.github.damontecres.wholphin.ui.AspectRatios -import com.github.damontecres.wholphin.ui.cards.BannerCard import com.github.damontecres.wholphin.ui.cards.SeasonCard import com.github.damontecres.wholphin.ui.cards.WatchedIcon -import com.github.damontecres.wholphin.ui.components.ExpandableFaButton import com.github.damontecres.wholphin.ui.nav.NavDrawerItem import com.github.damontecres.wholphin.ui.nav.NavItem import com.github.damontecres.wholphin.ui.playback.PlaybackButton @@ -109,6 +107,7 @@ private fun ThemeExample(theme: AppThemeColors) { isPlayed = true, unplayedItemCount = 2, playedPercentage = 50.0, + numberOfVersions = 2, onClick = { }, onLongClick = {}, imageHeight = 120.dp, @@ -125,6 +124,7 @@ private fun ThemeExample(theme: AppThemeColors) { isPlayed = true, unplayedItemCount = 2, playedPercentage = 0.0, + numberOfVersions = 0, onClick = { }, onLongClick = {}, imageHeight = 120.dp,