Updates & fixes to discover pages (#1836)

## Description
Fixes several issues on the discover pages:
- Fixes sorting by release date for tv shows
- Fixes automatic go-to for movie & tv show grid
- Fixes tv show discover page's go-to using a movie detail page
- Updates the alignment & padding for the headers on different discover
pages to match the reset of the app
- Use media's logo image if available and the setting is enabled

### Related issues
N/A

### Testing
Emulator mostly

## Screenshots
N/A, headers and logos are unchanged, just moved slightly to match
regular content

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-08-16 10:29:03 -04:00 • committed by GitHub
parent 4e53cbaa7f
commit f89d752d2c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 182 additions and 75 deletions

View file

@ -17,10 +17,11 @@ private val dateFormatter = DateTimeFormatter.ofPattern("yyyy-M-d")
enum class DiscoverSort(
val key: String,
@StringRes val stringRes: Int,
@param:StringRes val stringRes: Int,
val tvKey: String = key,
) {
POPULARITY("popularity", R.string.popularity),
RELEASE_DATE("release_date", R.string.sort_by_date_released),
RELEASE_DATE("release_date", R.string.sort_by_date_released, "first_air_date"),
TMDB_VOTE("vote_average", R.string.community_rating),
ALPHABETICAL("original_title", R.string.sort_by_name),
}
@ -36,7 +37,7 @@ data class DiscoverSortAndDirection(
val sort: DiscoverSort = DiscoverSort.POPULARITY,
val direction: SortOrder = SortOrder.DESCENDING,
) {
val key = "${sort.key}.${direction.key}"
fun key(isTv: Boolean) = if (isTv) "${sort.tvKey}.${direction.key}" else "${sort.key}.${direction.key}"
fun flip() = copy(direction = direction.flip())
}
@ -103,7 +104,7 @@ data class DiscoverFilter(
network = null,
keywords = keywordIds?.joinToString(",") { it.toString() },
excludeKeywords = excludeKeywordIds?.joinToString(",") { it.toString() },
sortBy = sortBy?.key,
sortBy = sortBy?.key(true),
// firstAirDateGte = firstAirDateGte?.let { dateFormatter.format(it) },
// firstAirDateLte = firstAirDateLte?.let { dateFormatter.format(it) },
withRuntimeGte = withRuntimeGte?.inWholeMinutes?.toInt(),
@ -135,7 +136,7 @@ data class DiscoverFilter(
// network = null,
keywords = keywordIds?.joinToString(",") { it.toString() },
excludeKeywords = excludeKeywordIds?.joinToString(",") { it.toString() },
sortBy = sortBy?.key,
sortBy = sortBy?.key(false),
// firstAirDateGte = firstAirDateGte?.let { dateFormatter.format(it) },
// firstAirDateLte = firstAirDateLte?.let { dateFormatter.format(it) },
withRuntimeGte = withRuntimeGte?.inWholeMinutes?.toInt(),

View file

@ -103,6 +103,7 @@ data class DiscoverItem(
@Serializable(LocalDateSerializer::class) val releaseDate: LocalDate?,
val posterUrl: String?,
val backDropUrl: String?,
val logoUrl: String?,
val jellyfinItemId: UUID?,
) : CardGridItem {
override val gridId: String get() = id.toString()

View file

@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.firstOrNull
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
@ -169,14 +170,7 @@ class SeerrService
backdropWidth: Int = 1920,
): String? {
if (mediaInfo != null) {
val itemId =
if (mediaInfo.jellyfinMediaId.isNotNullOrBlank()) {
mediaInfo.jellyfinMediaId.toUUIDOrNull()
} else if (mediaInfo.jellyfinMediaId4k.isNotNullOrBlank()) {
mediaInfo.jellyfinMediaId4k.toUUIDOrNull()
} else {
null
}
val itemId = mediaInfo.jellyfinId
if (itemId != null) {
return imageUrlService.getItemImageUrl(
itemId = itemId,
@ -348,7 +342,8 @@ class SeerrService
releaseDate = toLocalDate(movie.releaseDate),
posterUrl = createImageUrl(ImageType.PRIMARY, movie.posterPath, movie.mediaInfo),
backDropUrl = createImageUrl(ImageType.BACKDROP, movie.backdropPath, movie.mediaInfo),
jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
logoUrl = createImageUrl(ImageType.LOGO, null, movie.mediaInfo),
jellyfinItemId = movie.mediaInfo?.jellyfinId,
)
suspend fun createDiscoverItem(movie: MovieDetails): DiscoverItem =
@ -364,7 +359,8 @@ class SeerrService
releaseDate = toLocalDate(movie.releaseDate),
posterUrl = createImageUrl(ImageType.PRIMARY, movie.posterPath, movie.mediaInfo),
backDropUrl = createImageUrl(ImageType.BACKDROP, movie.backdropPath, movie.mediaInfo),
jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
logoUrl = createImageUrl(ImageType.LOGO, null, movie.mediaInfo),
jellyfinItemId = movie.mediaInfo?.jellyfinId,
)
suspend fun createDiscoverItem(tv: TvResult): DiscoverItem =
@ -380,7 +376,8 @@ class SeerrService
releaseDate = toLocalDate(tv.firstAirDate),
posterUrl = createImageUrl(ImageType.PRIMARY, tv.posterPath, tv.mediaInfo),
backDropUrl = createImageUrl(ImageType.BACKDROP, tv.backdropPath, tv.mediaInfo),
jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
logoUrl = createImageUrl(ImageType.LOGO, null, tv.mediaInfo),
jellyfinItemId = tv.mediaInfo?.jellyfinId,
)
suspend fun createDiscoverItem(tv: TvDetails): DiscoverItem =
@ -396,7 +393,8 @@ class SeerrService
releaseDate = toLocalDate(tv.firstAirDate),
posterUrl = createImageUrl(ImageType.PRIMARY, tv.posterPath, tv.mediaInfo),
backDropUrl = createImageUrl(ImageType.BACKDROP, tv.backdropPath, tv.mediaInfo),
jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
logoUrl = createImageUrl(ImageType.LOGO, null, tv.mediaInfo),
jellyfinItemId = tv.mediaInfo?.jellyfinId,
)
suspend fun createDiscoverItem(search: SeerrSearchResult): DiscoverItem =
@ -412,7 +410,8 @@ class SeerrService
releaseDate = toLocalDate(search.releaseDate ?: search.firstAirDate),
posterUrl = createImageUrl(ImageType.PRIMARY, search.posterPath, search.mediaInfo),
backDropUrl = createImageUrl(ImageType.BACKDROP, search.backdropPath, search.mediaInfo),
jellyfinItemId = search.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
logoUrl = createImageUrl(ImageType.LOGO, null, search.mediaInfo),
jellyfinItemId = search.mediaInfo?.jellyfinId,
)
suspend fun createDiscoverItem(credit: CreditCast): DiscoverItem =
@ -438,7 +437,8 @@ class SeerrService
credit.backdropPath,
credit.mediaInfo,
),
jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
logoUrl = createImageUrl(ImageType.LOGO, null, credit.mediaInfo),
jellyfinItemId = credit.mediaInfo?.jellyfinId,
)
suspend fun createDiscoverItem(credit: CreditCrew): DiscoverItem =
@ -464,6 +464,13 @@ class SeerrService
credit.backdropPath,
credit.mediaInfo,
),
jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
logoUrl = createImageUrl(ImageType.LOGO, null, credit.mediaInfo),
jellyfinItemId = credit.mediaInfo?.jellyfinId,
)
}
val MediaInfo.jellyfinId: UUID?
get() = jellyfinMediaId4k?.toUUIDOrNull() ?: jellyfinMediaId?.toUUIDOrNull()
val MediaInfo.jellyfinIdAsString: String?
get() = jellyfinMediaId4k ?: jellyfinMediaId

View file

@ -21,6 +21,7 @@ import androidx.tv.material3.Text
import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.LocalImageUrlService
import com.github.damontecres.wholphin.ui.logCoilError
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ImageType
@ -40,6 +41,10 @@ fun TitleOrLogo(
model = logoImageUrl,
contentDescription = title,
contentScale = ContentScale.Fit,
onError = {
logCoilError(logoImageUrl, it.result)
imageError = true
},
modifier =
Modifier
.height(HeaderUtils.logoHeight)
@ -113,6 +118,10 @@ fun SimpleTitleOrLogo(
model = logoImageUrl,
contentDescription = item?.title,
contentScale = ContentScale.Fit,
onError = {
logCoilError(logoImageUrl, it.result)
imageError = true
},
modifier = Modifier.fillMaxSize(),
)
} else {

View file

@ -54,6 +54,7 @@ import com.github.damontecres.wholphin.ui.cards.DiscoverPersonRow
import com.github.damontecres.wholphin.ui.cards.ItemRow
import com.github.damontecres.wholphin.ui.cards.SeasonCard
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.HeaderUtils
import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
@ -64,7 +65,6 @@ import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.ExceptionHandler
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
@Composable
fun DiscoverMovieDetails(
@ -138,14 +138,7 @@ fun DiscoverMovieDetails(
)
},
goToOnClick = {
movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull()?.let {
viewModel.navigateTo(
Destination.MediaItem(
itemId = it,
type = BaseItemKind.MOVIE,
),
)
}
viewModel.goTo(movie.mediaInfo, BaseItemKind.MOVIE)
},
moreOnClick = {},
onLongClickPerson = { index, person -> },
@ -238,10 +231,11 @@ fun DiscoverMovieDetailsContent(
rating = rating,
bringIntoViewRequester = bringIntoViewRequester,
overviewOnClick = overviewOnClick,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
modifier =
Modifier
.fillMaxWidth()
.padding(top = 32.dp, bottom = 16.dp),
.padding(top = HeaderUtils.topPadding, bottom = 16.dp),
)
ExpandableDiscoverButtons(
availability =

View file

@ -14,7 +14,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme
@ -23,9 +22,13 @@ 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.preferences.UserPreferences
import com.github.damontecres.wholphin.services.jellyfinId
import com.github.damontecres.wholphin.ui.LocalImageUrlService
import com.github.damontecres.wholphin.ui.components.GenreText
import com.github.damontecres.wholphin.ui.components.HeaderUtils
import com.github.damontecres.wholphin.ui.components.OverviewText
import com.github.damontecres.wholphin.ui.components.QuickDetailsText
import com.github.damontecres.wholphin.ui.components.TitleOrLogo
import com.github.damontecres.wholphin.ui.formatDuration
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.letNotEmpty
@ -33,6 +36,7 @@ import com.github.damontecres.wholphin.ui.listToDotString
import com.github.damontecres.wholphin.ui.roundMinutes
import com.github.damontecres.wholphin.util.ExceptionHandler
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.ImageType
import java.util.Locale
import kotlin.time.Duration.Companion.minutes
@ -43,29 +47,36 @@ fun DiscoverMovieDetailsHeader(
rating: DiscoverRating?,
bringIntoViewRequester: BringIntoViewRequester,
overviewOnClick: () -> Unit,
showLogo: Boolean,
modifier: Modifier = Modifier,
) {
val imageUrlService = LocalImageUrlService.current
val logoImageUrl =
remember(movie) {
movie.mediaInfo?.jellyfinId?.let {
imageUrlService.getItemImageUrl(it, ImageType.LOGO)
}
}
val context = LocalContext.current
val scope = rememberCoroutineScope()
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = modifier,
) {
// Title
Text(
text = movie.title ?: "",
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.displaySmall,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth(.75f),
TitleOrLogo(
title = movie.title,
logoImageUrl = logoImageUrl,
showLogo = showLogo,
modifier =
Modifier
.fillMaxWidth(.75f)
.padding(start = HeaderUtils.startPadding),
)
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.fillMaxWidth(.60f),
) {
val padding = 4.dp
val resources = LocalResources.current
val details =
remember(movie, rating, resources) {
@ -102,9 +113,9 @@ fun DiscoverMovieDetailsHeader(
}
}
QuickDetailsText(details)
QuickDetailsText(details, Modifier.padding(start = HeaderUtils.startPadding))
movie.genres?.mapNotNull { it.name }?.letNotEmpty {
GenreText(it, Modifier.padding(bottom = padding))
GenreText(it, Modifier.padding(start = HeaderUtils.startPadding))
}
val tagline = remember { movie.tagline?.takeIf { it.isNotNullOrBlank() } }
@ -113,7 +124,7 @@ fun DiscoverMovieDetailsHeader(
text = tagline,
style = MaterialTheme.typography.bodyLarge,
fontStyle = FontStyle.Italic,
modifier = Modifier,
modifier = Modifier.padding(start = HeaderUtils.startPadding),
)
}
@ -150,6 +161,7 @@ fun DiscoverMovieDetailsHeader(
text = stringResource(R.string.directed_by, it),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(start = HeaderUtils.startPadding),
)
}
}

View file

@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.ui.detail.discover
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.api.seerr.model.MediaInfo
import com.github.damontecres.wholphin.api.seerr.model.MediaRequest
import com.github.damontecres.wholphin.api.seerr.model.MovieDetails
import com.github.damontecres.wholphin.api.seerr.model.RelatedVideo
@ -23,6 +24,7 @@ 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.launchDefault
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.showToast
@ -44,6 +46,7 @@ import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.api.BaseItemKind
import timber.log.Timber
import kotlin.coroutines.cancellation.CancellationException
@ -172,6 +175,15 @@ class DiscoverMovieViewModel
navigationManager.navigateTo(destination)
}
fun goTo(
mediaInfo: MediaInfo?,
type: BaseItemKind,
) {
viewModelScope.launchDefault {
goToButtonDiscover(mediaInfo, type, context, navigationManager)
}
}
fun request(request: MovieRequest) {
viewModelScope.launchIO {
if (request.movieId == null) {

View file

@ -27,12 +27,9 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.api.seerr.model.TvDetails
import com.github.damontecres.wholphin.data.model.BaseItem
@ -45,6 +42,8 @@ import com.github.damontecres.wholphin.data.model.hasPermission
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.SeerrUserConfig
import com.github.damontecres.wholphin.services.TrailerService
import com.github.damontecres.wholphin.services.jellyfinId
import com.github.damontecres.wholphin.ui.LocalImageUrlService
import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard
import com.github.damontecres.wholphin.ui.cards.DiscoverPersonRow
import com.github.damontecres.wholphin.ui.cards.ItemRow
@ -53,9 +52,11 @@ import com.github.damontecres.wholphin.ui.components.DialogParams
import com.github.damontecres.wholphin.ui.components.DialogPopup
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.GenreText
import com.github.damontecres.wholphin.ui.components.HeaderUtils
import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.components.OverviewText
import com.github.damontecres.wholphin.ui.components.QuickDetailsText
import com.github.damontecres.wholphin.ui.components.TitleOrLogo
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
import com.github.damontecres.wholphin.ui.formatDuration
@ -70,7 +71,7 @@ import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.successValue
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import org.jellyfin.sdk.model.api.ImageType
import kotlin.time.Duration.Companion.minutes
@Composable
@ -125,14 +126,7 @@ fun DiscoverSeriesDetails(
viewModel.navigateTo(Destination.DiscoveredItem(it))
},
goToOnClick = {
item.mediaInfo?.jellyfinMediaId?.toUUIDOrNull()?.let {
viewModel.navigateTo(
Destination.MediaItem(
itemId = it,
type = BaseItemKind.MOVIE,
),
)
}
viewModel.goTo(item.mediaInfo, BaseItemKind.SERIES)
},
overviewOnClick = {
overviewDialog =
@ -254,7 +248,6 @@ fun DiscoverSeriesDetailsContent(
Column(
modifier =
Modifier
.padding(16.dp)
.fillMaxSize(),
) {
LazyColumn(
@ -274,10 +267,11 @@ fun DiscoverSeriesDetailsContent(
series = series,
rating = rating,
overviewOnClick = overviewOnClick,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
modifier =
Modifier
.fillMaxWidth()
.padding(top = 32.dp, bottom = 16.dp),
.padding(top = HeaderUtils.topPadding, bottom = 16.dp),
)
ExpandableDiscoverButtons(
availability =
@ -430,24 +424,33 @@ fun DiscoverSeriesDetailsHeader(
series: TvDetails,
rating: DiscoverRating?,
overviewOnClick: () -> Unit,
showLogo: Boolean,
modifier: Modifier = Modifier,
) {
val imageUrlService = LocalImageUrlService.current
val logoImageUrl =
remember(series) {
series.mediaInfo?.jellyfinId?.let {
imageUrlService.getItemImageUrl(it, ImageType.LOGO)
}
}
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = modifier,
) {
Text(
text = series.name ?: stringResource(R.string.unknown),
style = MaterialTheme.typography.displaySmall,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth(.75f),
TitleOrLogo(
title = series.name,
logoImageUrl = logoImageUrl,
showLogo = showLogo,
modifier =
Modifier
.fillMaxWidth(.75f)
.padding(start = HeaderUtils.startPadding),
)
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.fillMaxWidth(.60f),
) {
val padding = 4.dp
val resources = LocalResources.current
val details =
remember(series, rating, resources) {
@ -469,9 +472,9 @@ fun DiscoverSeriesDetailsHeader(
}
}
QuickDetailsText(details)
QuickDetailsText(details, Modifier.padding(start = HeaderUtils.startPadding))
series.genres?.mapNotNull { it.name }?.letNotEmpty {
GenreText(it, Modifier.padding(bottom = padding))
GenreText(it, Modifier.padding(start = HeaderUtils.startPadding, bottom = 4.dp))
}
series.overview?.let { overview ->
OverviewText(

View file

@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.ui.detail.discover
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.api.seerr.model.MediaInfo
import com.github.damontecres.wholphin.api.seerr.model.RelatedVideo
import com.github.damontecres.wholphin.api.seerr.model.RequestPostRequest
import com.github.damontecres.wholphin.api.seerr.model.RequestRequestIdPutRequest
@ -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.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.showToast
@ -45,6 +47,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.api.BaseItemKind
import timber.log.Timber
@HiltViewModel(assistedFactory = DiscoverSeriesViewModel.Factory::class)
@ -162,6 +165,15 @@ class DiscoverSeriesViewModel
navigationManager.navigateTo(destination)
}
fun goTo(
mediaInfo: MediaInfo?,
type: BaseItemKind,
) {
viewModelScope.launchDefault {
goToButtonDiscover(mediaInfo, type, context, navigationManager)
}
}
private suspend fun updateSeasonStatus(tv: TvDetails) {
if (request4kEnabled.first()) {
updateSeasonStatus(tv, true)

View file

@ -1,8 +1,17 @@
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(
@ -24,3 +33,40 @@ suspend fun getDiscoverRating(
Timber.e(ex, "Error fetching rating for %s", id)
null
}
/**
* Navigates to the Jellyfin media item if the [mediaInfo] has a valid [jellyfinId]
*
* If not, a error toast is shown
*/
suspend fun goToButtonDiscover(
mediaInfo: MediaInfo?,
type: BaseItemKind,
context: Context,
navigationManager: NavigationManager,
) {
val id = mediaInfo?.jellyfinId
if (id != null) {
navigationManager.navigateTo(
Destination.MediaItem(
itemId = id,
type = type,
),
)
} else {
val stringId = mediaInfo?.jellyfinIdAsString
val msg =
if (stringId.isNotNullOrBlank()) {
"Unknown Jellyfin ID: $stringId"
} else {
"No Jellyfin ID found"
}
Timber.w(
"Unknown jellyfinId for tmdb=%s: %s/%s",
mediaInfo?.tmdbId,
mediaInfo?.jellyfinMediaId,
mediaInfo?.jellyfinMediaId4k,
)
showToast(context, msg)
}
}

View file

@ -13,6 +13,7 @@ sealed interface DiscoverFocusedItem {
val subtitle: String?
val overview: String?
val backDropUrl: String?
val logoUrl: String?
data class Item(
val item: DiscoverItem,
@ -26,6 +27,8 @@ sealed interface DiscoverFocusedItem {
get() = item.overview
override val backDropUrl: String?
get() = item.backDropUrl
override val logoUrl: String?
get() = item.logoUrl
}
data class Genre(
@ -39,6 +42,8 @@ sealed interface DiscoverFocusedItem {
get() = null
override val overview: String?
get() = null
override val logoUrl: String?
get() = null
}
}

View file

@ -299,7 +299,7 @@ fun DiscoverRequestGrid(
initialPosition = state.startIndex,
pager = s.data,
onClickItem = { index, item ->
viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(item))
viewModel.navigationManager.navigateTo(item.destination)
},
onLongClickItem = { index, item -> },
onClickPlay = { _, _ -> },

View file

@ -6,9 +6,9 @@ import androidx.compose.foundation.gestures.LocalBringIntoViewSpec
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
@ -40,6 +40,7 @@ import com.github.damontecres.wholphin.services.BackdropService
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.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
@ -438,16 +439,20 @@ fun SeerrDiscoverPage(
title = focusedItem?.title?.getString(),
subtitle = focusedItem?.subtitle,
overview = focusedItem?.overview,
overviewTwoLines = true,
overviewTwoLines = false,
quickDetails = details,
timeRemaining = null,
endsAt = null,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
logoImageUrl = null, // TODO
logoImageUrl = focusedItem?.logoUrl,
modifier =
Modifier
.padding(top = 24.dp, bottom = 16.dp, start = 32.dp)
.fillMaxHeight(.25f),
.padding(
top = HeaderUtils.topPadding,
// Intentionally no bottom padding because of tabs
bottom = 0.dp,
start = HeaderUtils.startPadding,
).height(HeaderUtils.height),
)
val density = LocalDensity.current