mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-06 02:40:58 +01:00
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:
parent
4e53cbaa7f
commit
f89d752d2c
14 changed files with 182 additions and 75 deletions
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = { _, _ -> },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue