feat: add unwatched episode count indicators to PosterItems and enhance media item data structure

This commit is contained in:
2026-02-07 00:10:12 +01:00
parent 4c7d6317c8
commit 7951315048
9 changed files with 96 additions and 14 deletions

View File

@@ -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<BaseItemDto> {

View File

@@ -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,6 +66,7 @@ fun PosterCard(
modifier = Modifier
.width(posterWidth)
) {
Box() {
PurefinAsyncImage(
model = imageRequest,
contentDescription = null,
@@ -73,6 +78,27 @@ fun PosterCard(
.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,

View File

@@ -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
)
}
}

View File

@@ -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()
)

View File

@@ -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
)

View File

@@ -23,5 +23,6 @@ data class SeasonEntity(
val seriesId: UUID,
val name: String,
val index: Int,
val unwatchedEpisodeCount: Int,
val episodeCount: Int
)

View File

@@ -12,5 +12,6 @@ data class SeriesEntity(
val synopsis: String,
val year: String,
val heroImageUrl: String,
val unwatchedEpisodeCount: Int,
val seasonCount: Int
)

View File

@@ -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<Episode>
)
) {
}

View File

@@ -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<Season>,
val cast: List<CastMember>
)
) {
}