Fix some bugs on discover pages (#1832)

## Description
Fix some bugs on discover pages:
- UI fixes for available seasons
- Fix toggle not updating when removing a season from a pending request
- Don't request images if the path is null
- Better error handling when fetching ratings

### Related issues
Follow up to #1816

### Testing
Emulator

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-08-13 13:57:51 -04:00 • committed by GitHub
parent ba52915cf0
commit 2110d846c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 112 additions and 49 deletions

View file

@ -184,21 +184,23 @@ class SeerrService
)
}
}
val current = seerrServerRepository.current.firstOrNull() ?: return null
val cacheImages = current.serverConfig.cacheImages == true
val base =
if (cacheImages) {
current.server.url.removeSuffix("/") + "/imageproxy/tmdb"
} else {
"https://image.tmdb.org"
}
val prefix =
when (imageType) {
ImageType.PRIMARY -> "/t/p/w500"
ImageType.BACKDROP -> "/t/p/w${backdropWidth}_and_h${backdropHeight}_multi_faces"
else -> throw IllegalArgumentException("Image type not supported: $imageType")
}
return "${base}${prefix}$path"
return path?.takeIf { it.isNotNullOrBlank() }?.let {
val current = seerrServerRepository.current.firstOrNull() ?: return null
val cacheImages = current.serverConfig.cacheImages == true
val base =
if (cacheImages) {
current.server.url.removeSuffix("/") + "/imageproxy/tmdb"
} else {
"https://image.tmdb.org"
}
val prefix =
when (imageType) {
ImageType.PRIMARY -> "/t/p/w500"
ImageType.BACKDROP -> "/t/p/w${backdropWidth}_and_h${backdropHeight}_multi_faces"
else -> throw IllegalArgumentException("Image type not supported: $imageType")
}
return "${base}${prefix}$path"
}
}
suspend fun getProfilesAndFolders(type: SeerrItemType): SeerrRequestData {

View file

@ -11,6 +11,7 @@ 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.RemoteTrailer
import com.github.damontecres.wholphin.data.model.RequestStatus
import com.github.damontecres.wholphin.data.model.SeerrItemType
import com.github.damontecres.wholphin.data.model.SeerrPermission
import com.github.damontecres.wholphin.data.model.Trailer
@ -20,6 +21,7 @@ import com.github.damontecres.wholphin.services.NavigationManager
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.ui.equalsNotNull
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination
@ -100,9 +102,13 @@ class DiscoverMovieViewModel
updateCanCancel()
viewModelScope.launchIO {
val result =
seerrService.api.moviesApi.movieMovieIdRatingsGet(movieId = item.id)
_state.update { it.copy(rating = DiscoverRating(result)) }
val rating =
getDiscoverRating(item.id) {
DiscoverRating(
seerrService.api.moviesApi.movieMovieIdRatingsGet(movieId = item.id),
)
}
_state.update { it.copy(rating = rating) }
}
if (state.value.similar.isEmpty()) {
viewModelScope.launchIO {
@ -248,7 +254,12 @@ fun canUserCancelRequest(
(
// User requested this
user.hasPermission(SeerrPermission.REQUEST) &&
requests?.any { it.requestedBy?.id == user?.id } == true
requests
.orEmpty()
.any {
equalsNotNull(it.requestedBy?.id, user?.id) &&
RequestStatus.from(it.status) < RequestStatus.APPROVED
}
)
data class DiscoverMovieState(

View file

@ -105,8 +105,11 @@ class DiscoverSeriesViewModel
updateCanCancel()
viewModelScope.launchIO {
val result = seerrService.api.tvApi.tvTvIdRatingsGet(tvId = item.id)
_state.update { it.copy(rating = DiscoverRating(result)) }
val rating =
getDiscoverRating(item.id) {
DiscoverRating(seerrService.api.tvApi.tvTvIdRatingsGet(tvId = item.id))
}
_state.update { it.copy(rating = rating) }
}
if (state.value.similar.isEmpty()) {
viewModelScope.launchIO {
@ -183,6 +186,18 @@ class DiscoverSeriesViewModel
seasonAvailability[seasonNumber] = availability
}
}
tv.mediaInfo?.seasons?.forEach {
it.seasonNumber?.let { seasonNumber ->
val status = if (is4k) it.status4k else it.status
val availability =
SeerrAvailability.from(status) ?: SeerrAvailability.UNKNOWN
val current =
seasonAvailability.getOrDefault(seasonNumber, SeerrAvailability.UNKNOWN)
if (availability > current) {
seasonAvailability[seasonNumber] = availability
}
}
}
tv.mediaInfo
?.requests
@ -209,10 +224,6 @@ class DiscoverSeriesViewModel
tv.seasons?.firstOrNull { it.seasonNumber == seasonNumber }?.let {
val availability =
when (status) {
RequestStatus.UNKNOWN -> {
SeerrAvailability.UNKNOWN
}
RequestStatus.PENDING -> {
SeerrAvailability.PENDING
}
@ -229,22 +240,39 @@ class DiscoverSeriesViewModel
SeerrAvailability.UNKNOWN
}
RequestStatus.COMPLETED -> {
RequestStatus.UNKNOWN,
RequestStatus.COMPLETED,
-> {
seasonAvailability.getOrDefault(
seasonNumber,
SeerrAvailability.UNKNOWN,
)
}
}
val defaultEditable =
availability != SeerrAvailability.AVAILABLE &&
availability != SeerrAvailability.PARTIALLY_AVAILABLE &&
availability != SeerrAvailability.PROCESSING &&
availability != SeerrAvailability.BLOCKLISTED
RequestSeason(
season = it,
status = status,
availability = availability,
editable = editable.getOrDefault(seasonNumber, true),
editable = editable.getOrDefault(seasonNumber, defaultEditable),
)
}
}
Timber.v("Got %s seasons, is4k=%s", requestSeasons.size, is4k)
// requestSeasons.forEach {
// Timber.v(
// "is4k=%s, season %s: availability=%s, status=%s, editable=%s",
// is4k,
// it.season.seasonNumber,
// it.availability,
// it.status,
// it.editable,
// )
// }
_state.update {
if (is4k) {
it.copy(seasons4k = requestSeasons)

View file

@ -0,0 +1,26 @@
package com.github.damontecres.wholphin.ui.detail.discover
import com.github.damontecres.wholphin.api.seerr.infrastructure.ClientException
import com.github.damontecres.wholphin.data.model.DiscoverRating
import kotlinx.coroutines.CancellationException
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
}

View file

@ -83,11 +83,13 @@ fun RequestSeasons(
mutableStateSetOf(
*seasons
.filter { season ->
season.status == RequestStatus.PENDING ||
season.status == RequestStatus.APPROVED ||
season.status == RequestStatus.COMPLETED ||
season.availability == SeerrAvailability.PARTIALLY_AVAILABLE ||
season.availability == SeerrAvailability.AVAILABLE
!season.editable && (
season.status == RequestStatus.PENDING ||
season.status == RequestStatus.APPROVED ||
season.status == RequestStatus.COMPLETED ||
season.availability == SeerrAvailability.PARTIALLY_AVAILABLE ||
season.availability == SeerrAvailability.AVAILABLE
)
}.mapNotNull { season -> season.season.seasonNumber }
.toTypedArray(),
)

View file

@ -30,7 +30,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.api.seerr.infrastructure.ClientException
import com.github.damontecres.wholphin.data.filter.DiscoverFilter
import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.DiscoverRating
@ -42,6 +41,7 @@ import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.SeerrService
import com.github.damontecres.wholphin.ui.components.Genre
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
@ -262,13 +262,11 @@ class SeerrDiscoverViewModel
return@launchIO
}
val result =
try {
getDiscoverRating(id) {
when (type) {
SeerrItemType.MOVIE -> {
DiscoverRating(
seerrService.api.moviesApi.movieMovieIdRatingsGet(
movieId = id,
),
seerrService.api.moviesApi.movieMovieIdRatingsGet(movieId = id),
)
}
@ -284,18 +282,8 @@ class SeerrDiscoverViewModel
DiscoverRating(null, null)
}
}
} catch (ex: ClientException) {
if (ex.statusCode == 404) {
Timber.w("No rating found for %s", id)
DiscoverRating(null, null)
} else {
Timber.e(ex, "Error getting rating for %s", id)
return@launchIO
}
} catch (ex: Exception) {
Timber.e(ex, "Error getting rating for %s", id)
return@launchIO
}
} ?: DiscoverRating(null, null)
ratingCache.put(id, result)
rating.update {
ratingCache.asMap().toMap()