Show PWA source ratings on Discover screens
This commit is contained in:
parent
e92ade99df
commit
15b082ccd8
9 changed files with 53 additions and 131 deletions
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<DiscoverItem>,
|
||||
trailers: List<Trailer>,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<MovieDetails> = DataLoadingState.Pending,
|
||||
val rating: DiscoverRating? = null,
|
||||
val rating: SourceRatings? = null,
|
||||
val seasons: List<RequestSeason> = emptyList(),
|
||||
val trailers: List<Trailer> = emptyList(),
|
||||
val people: List<DiscoverItem> = emptyList(),
|
||||
|
|
|
|||
|
|
@ -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<RequestSeason>,
|
||||
similar: List<DiscoverItem>,
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<TvDetails> = DataLoadingState.Pending,
|
||||
val rating: DiscoverRating? = null,
|
||||
val rating: SourceRatings? = null,
|
||||
val seasons: List<RequestSeason> = emptyList(),
|
||||
val seasons4k: List<RequestSeason> = emptyList(),
|
||||
val trailers: List<Trailer> = emptyList(),
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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>(DiscoverState())
|
||||
val rating = MutableStateFlow<Map<Int, DiscoverRating>>(mapOf())
|
||||
val rating = MutableStateFlow<Map<Pair<SeerrItemType, Int>, SourceRatings>>(mapOf())
|
||||
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
|
|
@ -246,50 +247,23 @@ class SeerrDiscoverViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private val ratingCache =
|
||||
CacheBuilder
|
||||
.newBuilder()
|
||||
.maximumSize(100)
|
||||
.build<Int, DiscoverRating>()
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue