From 15b082ccd8306a91ee7f787c1731d78fe79ee67b Mon Sep 17 00:00:00 2001 From: justinzeus Date: Thu, 29 Oct 2026 20:00:31 +0100 Subject: [PATCH] Show PWA source ratings on Discover screens --- .../wholphin/data/model/DiscoverItem.kt | 19 ------ .../damontecres/wholphin/ui/Formatting.kt | 22 ++---- .../detail/discover/DiscoverMovieDetails.kt | 4 +- .../discover/DiscoverMovieDetailsHeader.kt | 7 +- .../detail/discover/DiscoverMovieViewModel.kt | 17 +++-- .../detail/discover/DiscoverSeriesDetails.kt | 9 ++- .../discover/DiscoverSeriesViewModel.kt | 15 ++-- .../ui/detail/discover/DiscoverUtils.kt | 23 ------- .../wholphin/ui/discover/SeerrDiscoverPage.kt | 68 +++++-------------- 9 files changed, 53 insertions(+), 131 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt index 24d7d340..fe4650a6 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt @@ -3,8 +3,6 @@ package com.github.damontecres.wholphin.data.model import androidx.compose.runtime.Stable -import com.github.damontecres.wholphin.api.seerr.model.MovieMovieIdRatingsGet200Response -import com.github.damontecres.wholphin.api.seerr.model.TvTvIdRatingsGet200Response import com.github.damontecres.wholphin.ui.detail.CardGridItem import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.util.LocalDateSerializer @@ -123,20 +121,3 @@ data class DiscoverItem( } } } - -/** - * A rating for a discovered item which is usually fetched separately from the item - */ -data class DiscoverRating( - val criticRating: Int?, - val audienceRating: Float?, -) { - constructor(rating: MovieMovieIdRatingsGet200Response) : this( - criticRating = rating.criticsScore, - audienceRating = rating.audienceScore?.div(10f), - ) - constructor(rating: TvTvIdRatingsGet200Response) : this( - criticRating = rating.criticsScore, - audienceRating = null, - ) -} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt index 282c161e..6096e967 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt @@ -4,11 +4,12 @@ import android.content.Context import android.content.res.Resources import android.text.format.DateFormat import androidx.annotation.StringRes -import androidx.compose.foundation.text.appendInlineContent import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.buildAnnotatedString import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.WholphinApplication +import com.github.damontecres.wholphin.data.model.SourceRatings +import com.github.damontecres.wholphin.data.model.appendRatings import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.MediaSegmentType @@ -234,27 +235,16 @@ fun AnnotatedString.Builder.dot() = append(" \u2022 ") fun listToDotString( strings: List, - communityRating: Float?, - criticRating: Float?, + ratings: SourceRatings?, ): AnnotatedString = buildAnnotatedString { strings.forEachIndexed { index, string -> append(string) if (index != strings.lastIndex) dot() } - communityRating?.let { - dot() - append(String.format(Locale.getDefault(), "%.1f", it)) - appendInlineContent(id = "star") - } - criticRating?.let { - dot() - append("${it.toInt()}%") - if (it >= 60f) { - appendInlineContent(id = "fresh") - } else { - appendInlineContent(id = "rotten") - } + ratings?.let { + appendRatings(it.communityEntries) + appendRatings(it.criticEntries) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt index e91dfb82..c5686e8f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt @@ -38,7 +38,7 @@ import androidx.tv.material3.Text import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.api.seerr.model.MovieDetails import com.github.damontecres.wholphin.data.model.DiscoverItem -import com.github.damontecres.wholphin.data.model.DiscoverRating +import com.github.damontecres.wholphin.data.model.SourceRatings import com.github.damontecres.wholphin.data.model.LocalTrailer import com.github.damontecres.wholphin.data.model.RemoteTrailer import com.github.damontecres.wholphin.data.model.SeerrAvailability @@ -185,7 +185,7 @@ fun DiscoverMovieDetailsContent( preferences: UserPreferences, userConfig: SeerrUserConfig?, movie: MovieDetails, - rating: DiscoverRating?, + rating: SourceRatings?, canCancel: Boolean, people: List, trailers: List, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetailsHeader.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetailsHeader.kt index 1c0915ec..51a537e3 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetailsHeader.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetailsHeader.kt @@ -20,7 +20,7 @@ import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.api.seerr.model.MovieDetails -import com.github.damontecres.wholphin.data.model.DiscoverRating +import com.github.damontecres.wholphin.data.model.SourceRatings import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.services.jellyfinId import com.github.damontecres.wholphin.ui.LocalImageUrlService @@ -44,7 +44,7 @@ import kotlin.time.Duration.Companion.minutes fun DiscoverMovieDetailsHeader( preferences: UserPreferences, movie: MovieDetails, - rating: DiscoverRating?, + rating: SourceRatings?, bringIntoViewRequester: BringIntoViewRequester, overviewOnClick: () -> Unit, showLogo: Boolean, @@ -107,8 +107,7 @@ fun DiscoverMovieDetailsHeader( }.let { listToDotString( it, - rating?.audienceRating, - rating?.criticRating?.toFloat(), + rating, ) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt index 20cecd12..658e4ffe 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt @@ -10,7 +10,7 @@ import com.github.damontecres.wholphin.api.seerr.model.RelatedVideo import com.github.damontecres.wholphin.api.seerr.model.RequestPostRequest import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.DiscoverItem -import com.github.damontecres.wholphin.data.model.DiscoverRating +import com.github.damontecres.wholphin.data.model.SourceRatings import com.github.damontecres.wholphin.data.model.RemoteTrailer import com.github.damontecres.wholphin.data.model.RequestStatus import com.github.damontecres.wholphin.data.model.SeerrItemType @@ -19,9 +19,11 @@ import com.github.damontecres.wholphin.data.model.Trailer import com.github.damontecres.wholphin.data.model.hasPermission import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.NavigationManager +import com.github.damontecres.wholphin.services.SeerrRatingsService import com.github.damontecres.wholphin.services.SeerrServerRepository import com.github.damontecres.wholphin.services.SeerrService import com.github.damontecres.wholphin.services.SeerrUserConfig +import com.github.damontecres.wholphin.services.TmdbVote import com.github.damontecres.wholphin.ui.equalsNotNull import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.launchDefault @@ -60,6 +62,7 @@ class DiscoverMovieViewModel private val backdropService: BackdropService, val serverRepository: ServerRepository, val seerrService: SeerrService, + private val ratingsService: SeerrRatingsService, private val seerrServerRepository: SeerrServerRepository, @Assisted val item: DiscoverItem, ) : ViewModel() { @@ -106,11 +109,11 @@ class DiscoverMovieViewModel viewModelScope.launchIO { val rating = - getDiscoverRating(item.id) { - DiscoverRating( - seerrService.api.moviesApi.movieMovieIdRatingsGet(movieId = item.id), - ) - } + ratingsService.get( + SeerrItemType.MOVIE, + item.id, + TmdbVote(movie.voteAverage?.toDouble(), movie.voteCount?.toLong()), + ) _state.update { it.copy(rating = rating) } } if (state.value.similar.isEmpty()) { @@ -276,7 +279,7 @@ fun canUserCancelRequest( data class DiscoverMovieState( val movie: DataLoadingState = DataLoadingState.Pending, - val rating: DiscoverRating? = null, + val rating: SourceRatings? = null, val seasons: List = emptyList(), val trailers: List = emptyList(), val people: List = emptyList(), diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt index 049f7610..88fa0b50 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt @@ -34,7 +34,7 @@ import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.api.seerr.model.TvDetails import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.DiscoverItem -import com.github.damontecres.wholphin.data.model.DiscoverRating +import com.github.damontecres.wholphin.data.model.SourceRatings import com.github.damontecres.wholphin.data.model.SeerrAvailability import com.github.damontecres.wholphin.data.model.SeerrPermission import com.github.damontecres.wholphin.data.model.Trailer @@ -211,7 +211,7 @@ fun DiscoverSeriesDetailsContent( preferences: UserPreferences, userConfig: SeerrUserConfig?, series: TvDetails, - rating: DiscoverRating?, + rating: SourceRatings?, canCancel: Boolean, seasons: List, similar: List, @@ -422,7 +422,7 @@ fun DiscoverSeriesDetailsContent( @Composable fun DiscoverSeriesDetailsHeader( series: TvDetails, - rating: DiscoverRating?, + rating: SourceRatings?, overviewOnClick: () -> Unit, showLogo: Boolean, modifier: Modifier = Modifier, @@ -466,8 +466,7 @@ fun DiscoverSeriesDetailsHeader( }.let { listToDotString( it, - rating?.audienceRating, - rating?.criticRating?.toFloat(), + rating, ) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt index ec500351..b4d6f37f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt @@ -10,7 +10,7 @@ import com.github.damontecres.wholphin.api.seerr.model.RequestRequestIdPutReques import com.github.damontecres.wholphin.api.seerr.model.TvDetails import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.DiscoverItem -import com.github.damontecres.wholphin.data.model.DiscoverRating +import com.github.damontecres.wholphin.data.model.SourceRatings import com.github.damontecres.wholphin.data.model.RemoteTrailer import com.github.damontecres.wholphin.data.model.RequestStatus import com.github.damontecres.wholphin.data.model.SeerrAvailability @@ -18,8 +18,10 @@ import com.github.damontecres.wholphin.data.model.SeerrItemType import com.github.damontecres.wholphin.data.model.Trailer import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.NavigationManager +import com.github.damontecres.wholphin.services.SeerrRatingsService import com.github.damontecres.wholphin.services.SeerrServerRepository import com.github.damontecres.wholphin.services.SeerrService +import com.github.damontecres.wholphin.services.TmdbVote import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchIO @@ -60,6 +62,7 @@ class DiscoverSeriesViewModel private val backdropService: BackdropService, val serverRepository: ServerRepository, val seerrService: SeerrService, + private val ratingsService: SeerrRatingsService, private val seerrServerRepository: SeerrServerRepository, @Assisted val item: DiscoverItem, ) : ViewModel() { @@ -109,9 +112,11 @@ class DiscoverSeriesViewModel viewModelScope.launchIO { val rating = - getDiscoverRating(item.id) { - DiscoverRating(seerrService.api.tvApi.tvTvIdRatingsGet(tvId = item.id)) - } + ratingsService.get( + SeerrItemType.TV, + item.id, + TmdbVote(tv.voteAverage?.toDouble(), tv.voteCount?.toLong()), + ) _state.update { it.copy(rating = rating) } } if (state.value.similar.isEmpty()) { @@ -416,7 +421,7 @@ class DiscoverSeriesViewModel data class DiscoverSeriesState( val tvSeries: DataLoadingState = DataLoadingState.Pending, - val rating: DiscoverRating? = null, + val rating: SourceRatings? = null, val seasons: List = emptyList(), val seasons4k: List = emptyList(), val trailers: List = emptyList(), diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverUtils.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverUtils.kt index 3e625d60..cc89159f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverUtils.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverUtils.kt @@ -1,39 +1,16 @@ package com.github.damontecres.wholphin.ui.detail.discover import android.content.Context -import com.github.damontecres.wholphin.api.seerr.infrastructure.ClientException import com.github.damontecres.wholphin.api.seerr.model.MediaInfo -import com.github.damontecres.wholphin.data.model.DiscoverRating import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.jellyfinId import com.github.damontecres.wholphin.services.jellyfinIdAsString import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.showToast -import kotlinx.coroutines.CancellationException import org.jellyfin.sdk.model.api.BaseItemKind import timber.log.Timber -suspend fun getDiscoverRating( - id: Int, - func: suspend () -> DiscoverRating, -): DiscoverRating? = - try { - func.invoke() - } catch (ex: CancellationException) { - throw ex - } catch (ex: ClientException) { - if (ex.statusCode == 404) { - Timber.w("No rating for %s", id) - } else { - Timber.e(ex, "Error fetching rating for %s", id) - } - null - } catch (ex: Exception) { - Timber.e(ex, "Error fetching rating for %s", id) - null - } - /** * Navigates to the Jellyfin media item if the [mediaInfo] has a valid [jellyfinId] * diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/discover/SeerrDiscoverPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/discover/SeerrDiscoverPage.kt index cd7c6a27..4bdc42ba 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/discover/SeerrDiscoverPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/discover/SeerrDiscoverPage.kt @@ -32,17 +32,17 @@ import androidx.lifecycle.viewModelScope import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.filter.DiscoverFilter import com.github.damontecres.wholphin.data.model.DiscoverItem -import com.github.damontecres.wholphin.data.model.DiscoverRating import com.github.damontecres.wholphin.data.model.QuickDetailsData import com.github.damontecres.wholphin.data.model.SeerrItemType +import com.github.damontecres.wholphin.data.model.SourceRatings import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.NavigationManager +import com.github.damontecres.wholphin.services.SeerrRatingsService import com.github.damontecres.wholphin.services.SeerrService import com.github.damontecres.wholphin.ui.components.Genre import com.github.damontecres.wholphin.ui.components.HeaderUtils import com.github.damontecres.wholphin.ui.data.RowColumn -import com.github.damontecres.wholphin.ui.detail.discover.getDiscoverRating import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.listToDotString import com.github.damontecres.wholphin.ui.main.HomePageHeader @@ -57,9 +57,9 @@ import com.github.damontecres.wholphin.ui.util.StringProvider import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.DiscoverRequestType import com.github.damontecres.wholphin.util.successValue -import com.google.common.cache.CacheBuilder import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import org.jellyfin.sdk.model.UUID @@ -73,11 +73,12 @@ class SeerrDiscoverViewModel constructor( @param:ApplicationContext private val context: Context, private val seerrService: SeerrService, + private val ratingsService: SeerrRatingsService, val navigationManager: NavigationManager, private val backdropService: BackdropService, ) : ViewModel() { val state = MutableStateFlow(DiscoverState()) - val rating = MutableStateFlow>(mapOf()) + val rating = MutableStateFlow, SourceRatings>>(mapOf()) init { viewModelScope.launchIO { @@ -246,50 +247,23 @@ class SeerrDiscoverViewModel } } - private val ratingCache = - CacheBuilder - .newBuilder() - .maximumSize(100) - .build() + private var ratingJob: Job? = null - // TODO this is not very efficient + /** + * Fetch the ratings for the focused item, cancelling any previous request that is still running + */ fun fetchRating( id: Int, type: SeerrItemType, ) { - viewModelScope.launchIO { - val cachedResult = ratingCache.getIfPresent(id) - if (cachedResult != null) { - return@launchIO + val key = type to id + if (key in rating.value) return + ratingJob?.cancel() + ratingJob = + viewModelScope.launchIO { + val result = ratingsService.get(type, id) ?: return@launchIO + rating.update { it + (key to result) } } - val result = - getDiscoverRating(id) { - when (type) { - SeerrItemType.MOVIE -> { - DiscoverRating( - seerrService.api.moviesApi.movieMovieIdRatingsGet(movieId = id), - ) - } - - SeerrItemType.TV -> { - DiscoverRating(seerrService.api.tvApi.tvTvIdRatingsGet(tvId = id)) - } - - SeerrItemType.PERSON -> { - DiscoverRating(null, null) - } - - SeerrItemType.UNKNOWN -> { - DiscoverRating(null, null) - } - } - } ?: DiscoverRating(null, null) - - ratingCache.put(id, result) - rating.update { - ratingCache.asMap().toMap() - } - } } } @@ -424,14 +398,8 @@ fun SeerrDiscoverPage( ?.toString() ?.let(::add) }.let { - val rating = focusedItem.id.let { ratingMap[it] } - val str = - listToDotString( - it, - rating?.audienceRating, - rating?.criticRating?.toFloat(), - ) - QuickDetailsData(str) + val rating = ratingMap[focusedItem.item.type to focusedItem.id] + QuickDetailsData(listToDotString(it, rating)) } } }