From 7951315048d74266074bc5f83aa2d733bf1449a7 Mon Sep 17 00:00:00 2001 From: Barnabas Balogh Date: Sat, 7 Feb 2026 00:10:12 +0100 Subject: [PATCH] feat: add unwatched episode count indicators to PosterItems and enhance media item data structure --- .../bbara/purefin/client/JellyfinApiClient.kt | 3 +- .../hu/bbara/purefin/common/ui/PosterCard.kt | 48 ++++++++++++++----- .../components/UnwatchedEpisodeIndicator.kt | 43 +++++++++++++++++ .../purefin/data/InMemoryMediaRepository.kt | 2 + .../local/room/RoomMediaLocalDataSource.kt | 4 ++ .../purefin/data/local/room/SeasonEntity.kt | 1 + .../purefin/data/local/room/SeriesEntity.kt | 1 + .../hu/bbara/purefin/data/model/Season.kt | 4 +- .../hu/bbara/purefin/data/model/Series.kt | 4 +- 9 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/hu/bbara/purefin/common/ui/components/UnwatchedEpisodeIndicator.kt diff --git a/app/src/main/java/hu/bbara/purefin/client/JellyfinApiClient.kt b/app/src/main/java/hu/bbara/purefin/client/JellyfinApiClient.kt index 7129281..b13ca78 100644 --- a/app/src/main/java/hu/bbara/purefin/client/JellyfinApiClient.kt +++ b/app/src/main/java/hu/bbara/purefin/client/JellyfinApiClient.kt @@ -106,7 +106,8 @@ class JellyfinApiClient @Inject constructor( ItemFields.CHILD_COUNT, ItemFields.PARENT_ID, ItemFields.DATE_LAST_REFRESHED, - ItemFields.OVERVIEW + ItemFields.OVERVIEW, + ItemFields.SEASON_USER_DATA ) suspend fun getLibraryContent(libraryId: UUID): List { diff --git a/app/src/main/java/hu/bbara/purefin/common/ui/PosterCard.kt b/app/src/main/java/hu/bbara/purefin/common/ui/PosterCard.kt index 403db07..76b2a1d 100644 --- a/app/src/main/java/hu/bbara/purefin/common/ui/PosterCard.kt +++ b/app/src/main/java/hu/bbara/purefin/common/ui/PosterCard.kt @@ -3,6 +3,7 @@ package hu.bbara.purefin.common.ui import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.padding @@ -11,6 +12,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale @@ -23,6 +25,8 @@ import androidx.compose.ui.unit.sp import coil3.request.ImageRequest import hu.bbara.purefin.app.home.ui.PosterItem import hu.bbara.purefin.common.ui.components.PurefinAsyncImage +import hu.bbara.purefin.common.ui.components.UnwatchedEpisodeIndicator +import hu.bbara.purefin.common.ui.components.WatchStateIndicator import org.jellyfin.sdk.model.UUID import org.jellyfin.sdk.model.api.BaseItemKind @@ -62,17 +66,39 @@ fun PosterCard( modifier = Modifier .width(posterWidth) ) { - PurefinAsyncImage( - model = imageRequest, - contentDescription = null, - modifier = Modifier - .aspectRatio(2f / 3f) - .clip(RoundedCornerShape(14.dp)) - .border(1.dp, scheme.outlineVariant.copy(alpha = 0.3f), RoundedCornerShape(14.dp)) - .background(scheme.surfaceVariant) - .clickable(onClick = { openItem(item) }), - contentScale = ContentScale.Crop - ) + Box() { + PurefinAsyncImage( + model = imageRequest, + contentDescription = null, + modifier = Modifier + .aspectRatio(2f / 3f) + .clip(RoundedCornerShape(14.dp)) + .border(1.dp, scheme.outlineVariant.copy(alpha = 0.3f), RoundedCornerShape(14.dp)) + .background(scheme.surfaceVariant) + .clickable(onClick = { openItem(item) }), + contentScale = ContentScale.Crop + ) + when (item.type) { + BaseItemKind.MOVIE -> WatchStateIndicator( + modifier = Modifier.align(Alignment.TopEnd) + .padding(8.dp), + watched = item.movie!!.watched, + started = (item.movie.progress ?: 0.0) > 0 + ) + BaseItemKind.EPISODE -> WatchStateIndicator( + modifier = Modifier.align(Alignment.TopEnd) + .padding(8.dp), + watched = item.episode!!.watched, + started = (item.episode.progress ?: 0.0) > 0 + ) + BaseItemKind.SERIES -> UnwatchedEpisodeIndicator( + modifier = Modifier.align(Alignment.TopEnd) + .padding(8.dp), + unwatchedCount = item.series!!.unwatchedEpisodeCount + ) + else -> {} + } + } Text( text = item.title, color = scheme.onBackground, diff --git a/app/src/main/java/hu/bbara/purefin/common/ui/components/UnwatchedEpisodeIndicator.kt b/app/src/main/java/hu/bbara/purefin/common/ui/components/UnwatchedEpisodeIndicator.kt new file mode 100644 index 0000000..9709536 --- /dev/null +++ b/app/src/main/java/hu/bbara/purefin/common/ui/components/UnwatchedEpisodeIndicator.kt @@ -0,0 +1,43 @@ +package hu.bbara.purefin.common.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun UnwatchedEpisodeIndicator( + unwatchedCount: Int, + foregroundColor: Color = MaterialTheme.colorScheme.onPrimary, + backgroundColor: Color = MaterialTheme.colorScheme.primary, + size: Int = 24, + modifier: Modifier = Modifier +) { + if (unwatchedCount == 0) { + return + } + + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .border(1.dp, backgroundColor.copy(alpha = 0.8f), CircleShape) + .background(backgroundColor.copy(alpha = 0.8f), CircleShape) + .size(size.dp) + .clip(CircleShape) + ) { + Text( + text = unwatchedCount.toString(), + color = foregroundColor.copy(alpha = 0.8f), + style = MaterialTheme.typography.bodyMedium + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/hu/bbara/purefin/data/InMemoryMediaRepository.kt b/app/src/main/java/hu/bbara/purefin/data/InMemoryMediaRepository.kt index e5e83ea..e44d415 100644 --- a/app/src/main/java/hu/bbara/purefin/data/InMemoryMediaRepository.kt +++ b/app/src/main/java/hu/bbara/purefin/data/InMemoryMediaRepository.kt @@ -364,6 +364,7 @@ class InMemoryMediaRepository @Inject constructor( itemId = this.id, type = ImageType.PRIMARY ), + unwatchedEpisodeCount = this.userData!!.unplayedItemCount!!, seasonCount = this.childCount!!, seasons = emptyList(), cast = emptyList() @@ -376,6 +377,7 @@ class InMemoryMediaRepository @Inject constructor( seriesId = this.seriesId!!, name = this.name ?: "Unknown", index = this.indexNumber!!, + unwatchedEpisodeCount = this.userData!!.unplayedItemCount!!, episodeCount = this.childCount!!, episodes = emptyList() ) diff --git a/app/src/main/java/hu/bbara/purefin/data/local/room/RoomMediaLocalDataSource.kt b/app/src/main/java/hu/bbara/purefin/data/local/room/RoomMediaLocalDataSource.kt index b7ad287..d50f340 100644 --- a/app/src/main/java/hu/bbara/purefin/data/local/room/RoomMediaLocalDataSource.kt +++ b/app/src/main/java/hu/bbara/purefin/data/local/room/RoomMediaLocalDataSource.kt @@ -161,6 +161,7 @@ class RoomMediaLocalDataSource @Inject constructor( synopsis = synopsis, year = year, heroImageUrl = heroImageUrl, + unwatchedEpisodeCount = unwatchedEpisodeCount, seasonCount = seasonCount ) @@ -169,6 +170,7 @@ class RoomMediaLocalDataSource @Inject constructor( seriesId = seriesId, name = name, index = index, + unwatchedEpisodeCount = unwatchedEpisodeCount, episodeCount = episodeCount ) @@ -212,6 +214,7 @@ class RoomMediaLocalDataSource @Inject constructor( synopsis = synopsis, year = year, heroImageUrl = heroImageUrl, + unwatchedEpisodeCount = unwatchedEpisodeCount, seasonCount = seasonCount, seasons = seasons, cast = cast @@ -222,6 +225,7 @@ class RoomMediaLocalDataSource @Inject constructor( seriesId = seriesId, name = name, index = index, + unwatchedEpisodeCount = unwatchedEpisodeCount, episodeCount = episodeCount, episodes = episodes ) diff --git a/app/src/main/java/hu/bbara/purefin/data/local/room/SeasonEntity.kt b/app/src/main/java/hu/bbara/purefin/data/local/room/SeasonEntity.kt index 8b6507e..8601ef06 100644 --- a/app/src/main/java/hu/bbara/purefin/data/local/room/SeasonEntity.kt +++ b/app/src/main/java/hu/bbara/purefin/data/local/room/SeasonEntity.kt @@ -23,5 +23,6 @@ data class SeasonEntity( val seriesId: UUID, val name: String, val index: Int, + val unwatchedEpisodeCount: Int, val episodeCount: Int ) diff --git a/app/src/main/java/hu/bbara/purefin/data/local/room/SeriesEntity.kt b/app/src/main/java/hu/bbara/purefin/data/local/room/SeriesEntity.kt index 862d47a..8419aae 100644 --- a/app/src/main/java/hu/bbara/purefin/data/local/room/SeriesEntity.kt +++ b/app/src/main/java/hu/bbara/purefin/data/local/room/SeriesEntity.kt @@ -12,5 +12,6 @@ data class SeriesEntity( val synopsis: String, val year: String, val heroImageUrl: String, + val unwatchedEpisodeCount: Int, val seasonCount: Int ) diff --git a/app/src/main/java/hu/bbara/purefin/data/model/Season.kt b/app/src/main/java/hu/bbara/purefin/data/model/Season.kt index f505580..85f9b44 100644 --- a/app/src/main/java/hu/bbara/purefin/data/model/Season.kt +++ b/app/src/main/java/hu/bbara/purefin/data/model/Season.kt @@ -7,6 +7,8 @@ data class Season( val seriesId: UUID, val name: String, val index: Int, + val unwatchedEpisodeCount: Int, val episodeCount: Int, val episodes: List -) +) { +} diff --git a/app/src/main/java/hu/bbara/purefin/data/model/Series.kt b/app/src/main/java/hu/bbara/purefin/data/model/Series.kt index 81eba57..2b6b3a9 100644 --- a/app/src/main/java/hu/bbara/purefin/data/model/Series.kt +++ b/app/src/main/java/hu/bbara/purefin/data/model/Series.kt @@ -9,7 +9,9 @@ data class Series( val synopsis: String, val year: String, val heroImageUrl: String, + val unwatchedEpisodeCount: Int, val seasonCount: Int, val seasons: List, val cast: List -) +) { +}