Add basic tv series discover page

This commit is contained in:
Damontecres 2025-12-23 19:12:58 -05:00
parent ddda10f3a1
commit cf9d0d5286
No known key found for this signature in database
6 changed files with 731 additions and 7 deletions

View file

@ -5,7 +5,9 @@ package com.github.damontecres.wholphin.data.model
import com.github.damontecres.wholphin.api.seerr.model.MovieDetails
import com.github.damontecres.wholphin.api.seerr.model.MovieMovieIdRatingsGet200Response
import com.github.damontecres.wholphin.api.seerr.model.MovieResult
import com.github.damontecres.wholphin.api.seerr.model.TvDetails
import com.github.damontecres.wholphin.api.seerr.model.TvResult
import com.github.damontecres.wholphin.api.seerr.model.TvTvIdRatingsGet200Response
import com.github.damontecres.wholphin.services.SeerrSearchResult
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@ -104,7 +106,20 @@ data class DiscoverItem(
constructor(tv: TvResult) : this(
id = tv.id!!,
type = SeerrItemType.MOVIE,
type = SeerrItemType.TV,
title = tv.name,
subtitle = null,
overview = tv.overview,
availability = SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
releaseDate = tv.firstAirDate,
posterPath = tv.posterPath,
backdropPath = tv.backdropPath,
jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
)
constructor(tv: TvDetails) : this(
id = tv.id!!,
type = SeerrItemType.TV,
title = tv.name,
subtitle = null,
overview = tv.overview,
@ -139,4 +154,8 @@ data class DiscoverRating(
criticRating = rating.criticsScore,
audienceRating = rating.audienceScore?.div(10f),
)
constructor(rating: TvTvIdRatingsGet200Response) : this(
criticRating = rating.criticsScore,
audienceRating = null,
)
}

View file

@ -54,7 +54,6 @@ 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.LoadingPage
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
@ -77,7 +76,6 @@ fun DiscoverMovieDetails(
hiltViewModel<DiscoverMovieViewModel, DiscoverMovieViewModel.Factory>(
creationCallback = { it.create(destination.item) },
),
playlistViewModel: AddPlaylistViewModel = hiltViewModel(),
) {
val context = LocalContext.current
LifecycleResumeEffect(Unit) {

View file

@ -0,0 +1,517 @@
package com.github.damontecres.wholphin.ui.detail.discover
import android.content.Context
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
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.Season
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.Person
import com.github.damontecres.wholphin.data.model.SeerrAvailability
import com.github.damontecres.wholphin.data.model.Trailer
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.TrailerService
import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard
import com.github.damontecres.wholphin.ui.cards.ItemRow
import com.github.damontecres.wholphin.ui.cards.PersonRow
import com.github.damontecres.wholphin.ui.components.DialogItem
import com.github.damontecres.wholphin.ui.components.DialogParams
import com.github.damontecres.wholphin.ui.components.DialogPopup
import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.GenreText
import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.components.OverviewText
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
import com.github.damontecres.wholphin.ui.detail.movie.TrailerRow
import com.github.damontecres.wholphin.ui.letNotEmpty
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.rememberInt
import com.github.damontecres.wholphin.ui.roundMinutes
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import kotlin.time.Duration.Companion.minutes
@Composable
fun DiscoverSeriesDetails(
preferences: UserPreferences,
destination: Destination.DiscoveredItem,
modifier: Modifier = Modifier,
viewModel: DiscoverSeriesViewModel =
hiltViewModel<DiscoverSeriesViewModel, DiscoverSeriesViewModel.Factory>(
creationCallback = { it.create(destination.item) },
),
) {
val context = LocalContext.current
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
val item by viewModel.tvSeries.observeAsState()
val seasons by viewModel.seasons.observeAsState(listOf())
val people by viewModel.people.observeAsState(listOf())
val similar by viewModel.similar.observeAsState(listOf())
val recommended by viewModel.recommended.observeAsState(listOf())
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
var seasonDialog by remember { mutableStateOf<DialogParams?>(null) }
when (val state = loading) {
is LoadingState.Error -> {
ErrorMessage(state)
}
LoadingState.Loading,
LoadingState.Pending,
-> {
LoadingPage()
}
LoadingState.Success -> {
item?.let { item ->
val rating by viewModel.rating.observeAsState(null)
DiscoverSeriesDetailsContent(
preferences = preferences,
series = item,
rating = rating,
seasons = seasons,
people = people,
similar = similar,
recommended = recommended,
modifier = modifier,
onClickItem = { index, item ->
viewModel.navigateTo(Destination.DiscoveredItem(item))
},
onClickPerson = {
viewModel.navigateTo(
Destination.MediaItem(
it.id,
BaseItemKind.PERSON,
),
)
},
goToOnClick = {
item.mediaInfo?.jellyfinMediaId?.toUUIDOrNull()?.let {
viewModel.navigateTo(
Destination.MediaItem(
itemId = it,
type = BaseItemKind.MOVIE,
),
)
}
},
overviewOnClick = {
overviewDialog =
ItemDetailsDialogInfo(
title = item.name ?: context.getString(R.string.unknown),
overview = item.overview,
genres = item.genres?.mapNotNull { it.name }.orEmpty(),
files = listOf(),
)
},
trailerOnClick = {
TrailerService.onClick(context, it, viewModel::navigateTo)
},
trailers = listOf(),
requestOnClick = {
item.id?.let { viewModel.request(it) }
},
cancelOnClick = {
item.id?.let { viewModel.cancelRequest(it) }
},
moreOnClick = {
},
onLongClickPerson = { _, _ -> },
onLongClickSimilar = { _, _ -> },
)
}
}
}
overviewDialog?.let { info ->
ItemDetailsDialog(
info = info,
showFilePath = false,
onDismissRequest = { overviewDialog = null },
)
}
seasonDialog?.let { params ->
DialogPopup(
showDialog = true,
title = params.title,
dialogItems = params.items,
waitToLoad = params.fromLongClick,
onDismissRequest = { seasonDialog = null },
)
}
}
private const val HEADER_ROW = 0
private const val SEASONS_ROW = HEADER_ROW + 1
private const val PEOPLE_ROW = SEASONS_ROW + 1
private const val TRAILER_ROW = PEOPLE_ROW + 1
private const val EXTRAS_ROW = TRAILER_ROW + 1
private const val SIMILAR_ROW = EXTRAS_ROW + 1
private const val RECOMMENDED_ROW = SIMILAR_ROW + 1
@Composable
fun DiscoverSeriesDetailsContent(
preferences: UserPreferences,
series: TvDetails,
rating: DiscoverRating?,
seasons: List<Season>,
similar: List<DiscoverItem>,
recommended: List<DiscoverItem>,
trailers: List<Trailer>,
people: List<Person>,
requestOnClick: () -> Unit,
cancelOnClick: () -> Unit,
trailerOnClick: (Trailer) -> Unit,
overviewOnClick: () -> Unit,
goToOnClick: () -> Unit,
moreOnClick: () -> Unit,
onClickItem: (Int, DiscoverItem) -> Unit,
onClickPerson: (Person) -> Unit,
onLongClickPerson: (Int, Person) -> Unit,
onLongClickSimilar: (Int, DiscoverItem) -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val bringIntoViewRequester = remember { BringIntoViewRequester() }
var position by rememberInt()
val focusRequesters = remember { List(SIMILAR_ROW + 1) { FocusRequester() } }
val playFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
focusRequesters.getOrNull(position)?.tryRequestFocus()
}
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
Box(
modifier = modifier,
) {
Column(
modifier =
Modifier
.padding(16.dp)
.fillMaxSize(),
) {
LazyColumn(
contentPadding = PaddingValues(bottom = 80.dp),
verticalArrangement = Arrangement.spacedBy(0.dp),
modifier = Modifier,
) {
item {
DiscoverSeriesDetailsHeader(
series = series,
rating = rating,
overviewOnClick = overviewOnClick,
modifier =
Modifier
.fillMaxWidth()
.bringIntoViewRequester(bringIntoViewRequester)
.padding(top = 32.dp, bottom = 16.dp),
)
ExpandableDiscoverButtons(
availability =
SeerrAvailability.from(series.mediaInfo?.status)
?: SeerrAvailability.UNKNOWN,
requestOnClick = requestOnClick,
cancelOnClick = cancelOnClick,
moreOnClick = moreOnClick,
goToOnClick = goToOnClick,
buttonOnFocusChanged = {
if (it.isFocused) {
position = HEADER_ROW
scope.launch(ExceptionHandler()) {
bringIntoViewRequester.bringIntoView()
}
}
},
modifier =
Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
.focusRequester(focusRequesters[HEADER_ROW]),
)
}
// item {
// ItemRow(
// title = stringResource(R.string.tv_seasons) + " (${seasons.size})",
// items = seasons,
// onClickItem = { index, item ->
// position = SEASONS_ROW
// // onClickItem.invoke(index, item)
// },
// onLongClickItem = { index, item ->
// position = SEASONS_ROW
// },
// modifier =
// Modifier
// .fillMaxWidth()
// .focusRequester(focusRequesters[SEASONS_ROW]),
// cardContent = @Composable { index, item, mod, onClick, onLongClick ->
// SeasonCard(
// item = item,
// onClick = onClick,
// onLongClick = onLongClick,
// imageHeight = Cards.height2x3,
// imageWidth = Dp.Unspecified,
// showImageOverlay = true,
// modifier = mod,
// )
// },
// )
// }
if (people.isNotEmpty()) {
item {
PersonRow(
people = people,
onClick = {
position = PEOPLE_ROW
onClickPerson.invoke(it)
},
onLongClick = { index, person ->
},
modifier =
Modifier
.fillMaxWidth()
.focusRequester(focusRequesters[PEOPLE_ROW]),
)
}
}
if (trailers.isNotEmpty()) {
item {
TrailerRow(
trailers = trailers,
onClickTrailer = {
position = TRAILER_ROW
trailerOnClick.invoke(it)
},
modifier =
Modifier
.fillMaxWidth()
.focusRequester(focusRequesters[TRAILER_ROW]),
)
}
}
if (similar.isNotEmpty()) {
item {
ItemRow(
title = stringResource(R.string.more_like_this),
items = similar,
onClickItem = { index, item ->
position = SIMILAR_ROW
onClickItem.invoke(index, item)
},
onLongClickItem = { index, similar ->
position = SIMILAR_ROW
onLongClickSimilar.invoke(index, similar)
},
cardContent = { index, item, mod, onClick, onLongClick ->
DiscoverItemCard(
item = item,
onClick = onClick,
onLongClick = onLongClick,
showOverlay = false,
modifier = mod,
)
},
modifier =
Modifier
.fillMaxWidth()
.focusRequester(focusRequesters[SIMILAR_ROW]),
)
}
}
if (recommended.isNotEmpty()) {
item {
ItemRow(
title = stringResource(R.string.recommended),
items = similar,
onClickItem = { index, item ->
position = RECOMMENDED_ROW
onClickItem.invoke(index, item)
},
onLongClickItem = { index, similar ->
position = RECOMMENDED_ROW
onLongClickSimilar.invoke(index, similar)
},
cardContent = { index, item, mod, onClick, onLongClick ->
DiscoverItemCard(
item = item,
onClick = onClick,
onLongClick = onLongClick,
showOverlay = true,
modifier = mod,
)
},
modifier =
Modifier
.fillMaxWidth()
.focusRequester(focusRequesters[RECOMMENDED_ROW]),
)
}
}
}
}
}
moreDialog?.let { params ->
DialogPopup(
showDialog = true,
title = params.title,
dialogItems = params.items,
onDismissRequest = { moreDialog = null },
dismissOnClick = true,
waitToLoad = params.fromLongClick,
)
}
}
@Composable
fun DiscoverSeriesDetailsHeader(
series: TvDetails,
rating: DiscoverRating?,
overviewOnClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier,
) {
Text(
text = series.name ?: stringResource(R.string.unknown),
style = MaterialTheme.typography.displaySmall,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth(.75f),
)
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.fillMaxWidth(.60f),
) {
val padding = 4.dp
val details =
remember(series) {
buildList {
series.firstAirDate?.let(::add)
series.episodeRunTime
?.average()
?.takeIf { !it.isNaN() && it > 0 }
?.minutes
?.roundMinutes
?.toString()
?.let(::add)
// TODO
}
}
DotSeparatedRow(
texts = details,
communityRating = rating?.audienceRating,
criticRating = rating?.criticRating?.toFloat(),
textStyle = MaterialTheme.typography.titleSmall,
modifier = Modifier,
)
series.genres?.mapNotNull { it.name }?.letNotEmpty {
GenreText(it, Modifier.padding(bottom = padding))
}
series.overview?.let { overview ->
OverviewText(
overview = overview,
maxLines = 3,
onClick = overviewOnClick,
textBoxHeight = Dp.Unspecified,
)
}
}
}
}
fun buildDialogForSeason(
context: Context,
s: BaseItem,
onClickItem: (BaseItem) -> Unit,
markPlayed: (Boolean) -> Unit,
onClickPlay: (Boolean) -> Unit,
): DialogParams {
val items =
buildList {
add(
DialogItem(context.getString(R.string.go_to), Icons.Default.PlayArrow) {
onClickItem.invoke(s)
},
)
if (s.data.userData?.played == true) {
add(
DialogItem(context.getString(R.string.mark_unwatched), R.string.fa_eye) {
markPlayed.invoke(false)
},
)
} else {
add(
DialogItem(context.getString(R.string.mark_watched), R.string.fa_eye_slash) {
markPlayed.invoke(true)
},
)
}
add(
DialogItem(
context.getString(R.string.play),
Icons.Default.PlayArrow,
iconColor = Color.Green.copy(alpha = .8f),
) {
onClickPlay.invoke(false)
},
)
add(
DialogItem(
context.getString(R.string.shuffle),
R.string.fa_shuffle,
) {
onClickPlay.invoke(true)
},
)
}
return DialogParams(
title = s.name ?: context.getString(R.string.tv_season),
fromLongClick = true,
items = items,
)
}

View file

@ -0,0 +1,177 @@
package com.github.damontecres.wholphin.ui.detail.discover
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.api.seerr.model.RequestPostRequest
import com.github.damontecres.wholphin.api.seerr.model.Season
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.Person
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.SeerrService
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.setValueOnMain
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.UUID
import org.jellyfin.sdk.model.api.PersonKind
@HiltViewModel(assistedFactory = DiscoverSeriesViewModel.Factory::class)
class DiscoverSeriesViewModel
@AssistedInject
constructor(
private val api: ApiClient,
@param:ApplicationContext private val context: Context,
private val navigationManager: NavigationManager,
private val backdropService: BackdropService,
val serverRepository: ServerRepository,
val seerrService: SeerrService,
@Assisted val item: DiscoverItem,
) : ViewModel() {
@AssistedFactory
interface Factory {
fun create(item: DiscoverItem): DiscoverSeriesViewModel
}
val loading = MutableLiveData<LoadingState>(LoadingState.Pending)
val tvSeries = MutableLiveData<TvDetails?>(null)
val rating = MutableLiveData<DiscoverRating?>(null)
val seasons = MutableLiveData<List<Season>>(listOf())
val trailers = MutableLiveData<List<Trailer>>(listOf())
val people = MutableLiveData<List<Person>>(listOf())
val similar = MutableLiveData<List<DiscoverItem>>(listOf())
val recommended = MutableLiveData<List<DiscoverItem>>(listOf())
init {
init()
}
private fun fetchAndSetItem(): Deferred<TvDetails> =
viewModelScope.async(
Dispatchers.IO +
LoadingExceptionHandler(
loading,
"Error fetching movie",
),
) {
val tv = seerrService.api.tvApi.tvTvIdGet(tvId = item.id)
this@DiscoverSeriesViewModel.tvSeries.setValueOnMain(tv)
tv
}
fun init(): Job =
viewModelScope.launch(
Dispatchers.IO +
LoadingExceptionHandler(
loading,
"Error fetching movie",
),
) {
val tv = fetchAndSetItem().await()
val discoveredItem = DiscoverItem(tv)
backdropService.submit(discoveredItem)
withContext(Dispatchers.Main) {
loading.value = LoadingState.Success
}
viewModelScope.launchIO {
val result = seerrService.api.tvApi.tvTvIdRatingsGet(tvId = item.id)
rating.setValueOnMain(DiscoverRating(result))
}
if (!similar.isInitialized) {
viewModelScope.launchIO {
val result =
seerrService.api.moviesApi
.movieMovieIdSimilarGet(movieId = item.id, page = 2)
.results
?.map(::DiscoverItem)
.orEmpty()
similar.setValueOnMain(result)
}
viewModelScope.launchIO {
val result =
seerrService.api.moviesApi
.movieMovieIdRecommendationsGet(movieId = item.id, page = 2)
.results
?.map(::DiscoverItem)
.orEmpty()
similar.setValueOnMain(result)
}
}
val people =
tv.credits
?.cast
?.map {
Person(
id = UUID.randomUUID(),
name = it.name,
role = it.character,
type = PersonKind.UNKNOWN,
imageUrl = "https://image.tmdb.org/t/p/w600_and_h900_bestv2${it.profilePath}",
favorite = false,
)
}.orEmpty() +
tv.credits
?.crew
?.map {
Person(
id = UUID.randomUUID(),
name = it.name,
role = it.job,
type = PersonKind.UNKNOWN,
imageUrl = "https://image.tmdb.org/t/p/w600_and_h900_bestv2${it.profilePath}",
favorite = false,
)
}.orEmpty()
this@DiscoverSeriesViewModel.people.setValueOnMain(people)
}
fun navigateTo(destination: Destination) {
navigationManager.navigateTo(destination)
}
fun request(id: Int) {
viewModelScope.launchIO {
val request =
seerrService.api.requestApi.requestPost(
RequestPostRequest(
is4k = false,
mediaId = id,
mediaType = RequestPostRequest.MediaType.TV,
seasons = RequestPostRequest.Seasons.ALL, // TODO handle picking seasons
),
)
fetchAndSetItem().await()
}
}
fun cancelRequest(id: Int) {
viewModelScope.launchIO {
tvSeries.value?.mediaInfo?.requests?.firstOrNull()?.let {
// TODO handle multiple requests? Or just delete self's request?
seerrService.api.requestApi.requestRequestIdDelete(it.id.toString())
fetchAndSetItem().await()
}
}
}
}

View file

@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.ui.nav
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.data.filter.DefaultForGenresFilterOptions
import com.github.damontecres.wholphin.data.model.SeerrItemType
@ -22,6 +23,7 @@ import com.github.damontecres.wholphin.ui.detail.FavoritesPage
import com.github.damontecres.wholphin.ui.detail.PersonPage
import com.github.damontecres.wholphin.ui.detail.PlaylistDetails
import com.github.damontecres.wholphin.ui.detail.discover.DiscoverMovieDetails
import com.github.damontecres.wholphin.ui.detail.discover.DiscoverSeriesDetails
import com.github.damontecres.wholphin.ui.detail.episode.EpisodeDetails
import com.github.damontecres.wholphin.ui.detail.movie.MovieDetails
import com.github.damontecres.wholphin.ui.detail.series.SeriesDetails
@ -268,15 +270,24 @@ fun DestinationContent(
}
SeerrItemType.TV -> {
TODO()
DiscoverSeriesDetails(
preferences = preferences,
destination = destination,
)
}
SeerrItemType.PERSON -> {
TODO()
Text(
text = "Person not supported yet",
color = MaterialTheme.colorScheme.onSurface,
)
}
SeerrItemType.UNKNOWN -> {
TODO()
Text(
text = "Unknown discover type",
color = MaterialTheme.colorScheme.onSurface,
)
}
}
}

View file

@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.ui.seerr
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@ -135,7 +136,8 @@ fun SeerrDiscoverPage(
modifier =
Modifier
.verticalScroll(scrollState)
.padding(horizontal = 16.dp),
.fillMaxSize()
.padding(start = 16.dp, end = 16.dp, bottom = 40.dp),
) {
ItemRow(
title = stringResource(R.string.movies),