Add discover person page

This commit is contained in:
Damontecres 2026-01-06 19:17:40 -05:00
parent d175e3f471
commit ec7d012c22
No known key found for this signature in database
10 changed files with 296 additions and 160 deletions

View file

@ -11,6 +11,7 @@ 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.TvResult
import com.github.damontecres.wholphin.api.seerr.model.TvTvIdRatingsGet200Response import com.github.damontecres.wholphin.api.seerr.model.TvTvIdRatingsGet200Response
import com.github.damontecres.wholphin.services.SeerrSearchResult import com.github.damontecres.wholphin.services.SeerrSearchResult
import com.github.damontecres.wholphin.ui.detail.CardGridItem
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.toLocalDate import com.github.damontecres.wholphin.ui.toLocalDate
import com.github.damontecres.wholphin.util.LocalDateSerializer import com.github.damontecres.wholphin.util.LocalDateSerializer
@ -41,12 +42,14 @@ enum class SeerrItemType(
; ;
companion object { companion object {
fun fromString(str: String?) = fun fromString(
when (str) { str: String?,
fallback: SeerrItemType = UNKNOWN,
) = when (str) {
"movie" -> MOVIE "movie" -> MOVIE
"tv" -> TV "tv" -> TV
"person" -> PERSON "person" -> PERSON
else -> UNKNOWN else -> fallback
} }
} }
} }
@ -80,7 +83,11 @@ data class DiscoverItem(
val posterPath: String?, val posterPath: String?,
val backdropPath: String?, val backdropPath: String?,
val jellyfinItemId: UUID?, val jellyfinItemId: UUID?,
) { ) : CardGridItem {
override val gridId: String get() = id.toString()
override val playable: Boolean = false
override val sortName: String get() = title ?: ""
val backDropUrl: String? get() = backdropPath?.let { "https://image.tmdb.org/t/p/w1920_and_h800_multi_faces$it" } val backDropUrl: String? get() = backdropPath?.let { "https://image.tmdb.org/t/p/w1920_and_h800_multi_faces$it" }
val posterUrl: String? get() = posterPath?.let { "https://image.tmdb.org/t/p/w500$it" } val posterUrl: String? get() = posterPath?.let { "https://image.tmdb.org/t/p/w500$it" }
@ -172,30 +179,30 @@ data class DiscoverItem(
constructor(credit: CreditCast) : this( constructor(credit: CreditCast) : this(
id = credit.id!!, id = credit.id!!,
type = SeerrItemType.TV, type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON),
title = credit.name, title = credit.name ?: credit.title,
subtitle = null, subtitle = null,
overview = credit.overview, overview = credit.overview,
availability = availability =
SeerrAvailability.from(credit.mediaInfo?.status) SeerrAvailability.from(credit.mediaInfo?.status)
?: SeerrAvailability.UNKNOWN, ?: SeerrAvailability.UNKNOWN,
releaseDate = toLocalDate(credit.firstAirDate), releaseDate = toLocalDate(credit.firstAirDate),
posterPath = credit.posterPath, posterPath = credit.posterPath ?: credit.profilePath,
backdropPath = credit.backdropPath, backdropPath = credit.backdropPath,
jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
) )
constructor(credit: CreditCrew) : this( constructor(credit: CreditCrew) : this(
id = credit.id!!, id = credit.id!!,
type = SeerrItemType.TV, type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON),
title = credit.name, title = credit.name ?: credit.title,
subtitle = null, subtitle = null,
overview = credit.overview, overview = credit.overview,
availability = availability =
SeerrAvailability.from(credit.mediaInfo?.status) SeerrAvailability.from(credit.mediaInfo?.status)
?: SeerrAvailability.UNKNOWN, ?: SeerrAvailability.UNKNOWN,
releaseDate = toLocalDate(credit.firstAirDate), releaseDate = toLocalDate(credit.firstAirDate),
posterPath = credit.posterPath, posterPath = credit.posterPath ?: credit.profilePath,
backdropPath = credit.backdropPath, backdropPath = credit.backdropPath,
jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
) )

View file

@ -374,7 +374,7 @@ fun <T : CardGridItem> CardGrid(
pager pager
.getOrNull(focusedIndex) .getOrNull(focusedIndex)
?.sortName ?.sortName
?.first() ?.firstOrNull()
?.uppercaseChar() ?.uppercaseChar()
?.let { ?.let {
if (it >= '0' && it <= '9') { if (it >= '0' && it <= '9') {

View file

@ -40,7 +40,6 @@ import com.github.damontecres.wholphin.api.seerr.model.MovieDetails
import com.github.damontecres.wholphin.data.model.DiscoverItem import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.DiscoverRating import com.github.damontecres.wholphin.data.model.DiscoverRating
import com.github.damontecres.wholphin.data.model.LocalTrailer import com.github.damontecres.wholphin.data.model.LocalTrailer
import com.github.damontecres.wholphin.data.model.Person
import com.github.damontecres.wholphin.data.model.RemoteTrailer import com.github.damontecres.wholphin.data.model.RemoteTrailer
import com.github.damontecres.wholphin.data.model.SeerrAvailability import com.github.damontecres.wholphin.data.model.SeerrAvailability
import com.github.damontecres.wholphin.data.model.SeerrPermission import com.github.damontecres.wholphin.data.model.SeerrPermission
@ -52,7 +51,6 @@ import com.github.damontecres.wholphin.services.TrailerService
import com.github.damontecres.wholphin.ui.Cards import com.github.damontecres.wholphin.ui.Cards
import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard
import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.cards.ItemRow
import com.github.damontecres.wholphin.ui.cards.PersonRow
import com.github.damontecres.wholphin.ui.cards.SeasonCard import com.github.damontecres.wholphin.ui.cards.SeasonCard
import com.github.damontecres.wholphin.ui.components.DialogParams import com.github.damontecres.wholphin.ui.components.DialogParams
import com.github.damontecres.wholphin.ui.components.DialogPopup import com.github.damontecres.wholphin.ui.components.DialogPopup
@ -61,10 +59,12 @@ import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson import com.github.damontecres.wholphin.ui.discover.DiscoverRow
import com.github.damontecres.wholphin.ui.discover.DiscoverRowData
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.rememberInt import com.github.damontecres.wholphin.ui.rememberInt
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -137,7 +137,8 @@ fun DiscoverMovieDetails(
onClickItem = { index, item -> onClickItem = { index, item ->
viewModel.navigateTo(Destination.DiscoveredItem(item)) viewModel.navigateTo(Destination.DiscoveredItem(item))
}, },
onClickPerson = { onClickPerson = { item ->
viewModel.navigateTo(Destination.DiscoveredItem(item))
}, },
overviewOnClick = { overviewOnClick = {
overviewDialog = overviewDialog =
@ -166,20 +167,7 @@ fun DiscoverMovieDetails(
items = listOf(), items = listOf(),
) )
}, },
onLongClickPerson = { index, person -> onLongClickPerson = { index, person -> },
val items =
buildMoreDialogItemsForPerson(
context = context,
person = person,
actions = moreActions,
)
moreDialog =
DialogParams(
fromLongClick = true,
title = person.name ?: "",
items = items,
)
},
onLongClickSimilar = { index, similar -> onLongClickSimilar = { index, similar ->
}, },
trailerOnClick = { trailerOnClick = {
@ -223,7 +211,7 @@ fun DiscoverMovieDetailsContent(
userConfig: SeerrUserConfig?, userConfig: SeerrUserConfig?,
movie: MovieDetails, movie: MovieDetails,
rating: DiscoverRating?, rating: DiscoverRating?,
people: List<Person>, people: List<DiscoverItem>,
trailers: List<Trailer>, trailers: List<Trailer>,
similar: List<DiscoverItem>, similar: List<DiscoverItem>,
recommended: List<DiscoverItem>, recommended: List<DiscoverItem>,
@ -234,8 +222,8 @@ fun DiscoverMovieDetailsContent(
goToOnClick: () -> Unit, goToOnClick: () -> Unit,
moreOnClick: () -> Unit, moreOnClick: () -> Unit,
onClickItem: (Int, DiscoverItem) -> Unit, onClickItem: (Int, DiscoverItem) -> Unit,
onClickPerson: (Person) -> Unit, onClickPerson: (DiscoverItem) -> Unit,
onLongClickPerson: (Int, Person) -> Unit, onLongClickPerson: (Int, DiscoverItem) -> Unit,
onLongClickSimilar: (Int, DiscoverItem) -> Unit, onLongClickSimilar: (Int, DiscoverItem) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -310,20 +298,22 @@ fun DiscoverMovieDetailsContent(
} }
if (people.isNotEmpty()) { if (people.isNotEmpty()) {
item { item {
PersonRow( DiscoverRow(
people = people, row =
onClick = { DiscoverRowData(
stringResource(R.string.people),
DataLoadingState.Success(people),
),
onClickItem = { index: Int, item: DiscoverItem ->
position = PEOPLE_ROW position = PEOPLE_ROW
onClickPerson.invoke(it) onClickPerson.invoke(item)
}, },
onLongClick = { index, person -> onLongClickItem = { index, person ->
position = PEOPLE_ROW position = PEOPLE_ROW
onLongClickPerson.invoke(index, person) onLongClickPerson.invoke(index, person)
}, },
modifier = onCardFocus = {},
Modifier focusRequester = focusRequesters[PEOPLE_ROW],
.fillMaxWidth()
.focusRequester(focusRequesters[PEOPLE_ROW]),
) )
} }
} }

View file

@ -10,7 +10,6 @@ import com.github.damontecres.wholphin.api.seerr.model.RequestPostRequest
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.DiscoverItem import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.DiscoverRating import com.github.damontecres.wholphin.data.model.DiscoverRating
import com.github.damontecres.wholphin.data.model.Person
import com.github.damontecres.wholphin.data.model.RemoteTrailer import com.github.damontecres.wholphin.data.model.RemoteTrailer
import com.github.damontecres.wholphin.data.model.Trailer import com.github.damontecres.wholphin.data.model.Trailer
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
@ -36,8 +35,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.UUID
import org.jellyfin.sdk.model.api.PersonKind
@HiltViewModel(assistedFactory = DiscoverMovieViewModel.Factory::class) @HiltViewModel(assistedFactory = DiscoverMovieViewModel.Factory::class)
class DiscoverMovieViewModel class DiscoverMovieViewModel
@ -62,7 +59,7 @@ class DiscoverMovieViewModel
val rating = MutableLiveData<DiscoverRating?>(null) val rating = MutableLiveData<DiscoverRating?>(null)
val trailers = MutableLiveData<List<Trailer>>(listOf()) val trailers = MutableLiveData<List<Trailer>>(listOf())
val people = MutableLiveData<List<Person>>(listOf()) val people = MutableLiveData<List<DiscoverItem>>(listOf())
val similar = MutableLiveData<List<DiscoverItem>>(listOf()) val similar = MutableLiveData<List<DiscoverItem>>(listOf())
val recommended = MutableLiveData<List<DiscoverItem>>(listOf()) val recommended = MutableLiveData<List<DiscoverItem>>(listOf())
@ -127,28 +124,12 @@ class DiscoverMovieViewModel
val people = val people =
movie.credits movie.credits
?.cast ?.cast
?.map { ?.map(::DiscoverItem)
Person( .orEmpty() +
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() +
movie.credits movie.credits
?.crew ?.crew
?.map { ?.map(::DiscoverItem)
Person( .orEmpty()
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@DiscoverMovieViewModel.people.setValueOnMain(people) this@DiscoverMovieViewModel.people.setValueOnMain(people)
val trailers = val trailers =
movie.relatedVideos movie.relatedVideos

View file

@ -0,0 +1,161 @@
package com.github.damontecres.wholphin.ui.detail.discover
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.DiscoverItem
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.cards.DiscoverItemCard
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.detail.CardGrid
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.DataLoadingState
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
@HiltViewModel(assistedFactory = DiscoverPersonViewModel.Factory::class)
class DiscoverPersonViewModel
@AssistedInject
constructor(
val navigationManager: NavigationManager,
val serverRepository: ServerRepository,
val seerrService: SeerrService,
private val backdropService: BackdropService,
@Assisted val item: DiscoverItem,
) : ViewModel() {
@AssistedFactory
interface Factory {
fun create(item: DiscoverItem): DiscoverPersonViewModel
}
val credits = MutableStateFlow<DataLoadingState<List<DiscoverItem>>>(DataLoadingState.Pending)
init {
viewModelScope.launchIO {
backdropService.clearBackdrop()
val credits =
seerrService.api.personApi
.personPersonIdCombinedCreditsGet(personId = item.id)
.let { credits ->
val cast =
credits.cast
?.map(::DiscoverItem)
.orEmpty()
val crew =
credits.crew
?.map(::DiscoverItem)
.orEmpty()
cast + crew
}
this@DiscoverPersonViewModel.credits.update {
DataLoadingState.Success(credits)
}
}
}
}
@Composable
fun DiscoverPersonPage(
person: DiscoverItem,
modifier: Modifier = Modifier,
viewModel: DiscoverPersonViewModel =
hiltViewModel<DiscoverPersonViewModel, DiscoverPersonViewModel.Factory>(
creationCallback = { it.create(person) },
),
) {
val credits by viewModel.credits.collectAsState()
when (val state = credits) {
is DataLoadingState.Error -> {
ErrorMessage(state.message, state.exception, modifier.focusable())
}
DataLoadingState.Loading,
DataLoadingState.Pending,
-> {
LoadingPage(modifier.focusable())
}
is DataLoadingState.Success<List<DiscoverItem>> -> {
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
if (state.data.isNotEmpty()) {
focusRequester.tryRequestFocus()
}
}
Column(modifier = modifier) {
Text(
text = stringResource(R.string.discover) + (person.title?.let { ": $it" } ?: ""),
style = MaterialTheme.typography.displaySmall,
color = MaterialTheme.colorScheme.onBackground,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
)
if (state.data.isEmpty()) {
Text(
text = stringResource(R.string.no_results),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onBackground,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxSize(),
)
} else {
CardGrid(
pager = state.data,
onClickItem = { index: Int, item: DiscoverItem ->
viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(item))
},
onLongClickItem = { index: Int, item: DiscoverItem ->
},
onClickPlay = { _, item ->
},
letterPosition = { c: Char -> 0 },
gridFocusRequester = focusRequester,
showJumpButtons = false,
showLetterButtons = false,
spacing = 16.dp,
cardContent = @Composable { item, onClick, onLongClick, mod ->
DiscoverItemCard(
item = item,
onClick = onClick,
onLongClick = onLongClick,
showOverlay = true,
modifier = mod,
)
},
columns = 6,
modifier = Modifier.fillMaxSize(),
)
}
}
}
}
}

View file

@ -40,7 +40,6 @@ import com.github.damontecres.wholphin.api.seerr.model.TvDetails
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.DiscoverItem import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.DiscoverRating 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.SeerrAvailability
import com.github.damontecres.wholphin.data.model.SeerrPermission import com.github.damontecres.wholphin.data.model.SeerrPermission
import com.github.damontecres.wholphin.data.model.Trailer import com.github.damontecres.wholphin.data.model.Trailer
@ -50,7 +49,6 @@ import com.github.damontecres.wholphin.services.SeerrUserConfig
import com.github.damontecres.wholphin.services.TrailerService import com.github.damontecres.wholphin.services.TrailerService
import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard
import com.github.damontecres.wholphin.ui.cards.ItemRow 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.DialogItem
import com.github.damontecres.wholphin.ui.components.DialogParams import com.github.damontecres.wholphin.ui.components.DialogParams
import com.github.damontecres.wholphin.ui.components.DialogPopup import com.github.damontecres.wholphin.ui.components.DialogPopup
@ -61,11 +59,14 @@ import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.components.OverviewText import com.github.damontecres.wholphin.ui.components.OverviewText
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
import com.github.damontecres.wholphin.ui.discover.DiscoverRow
import com.github.damontecres.wholphin.ui.discover.DiscoverRowData
import com.github.damontecres.wholphin.ui.letNotEmpty import com.github.damontecres.wholphin.ui.letNotEmpty
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.rememberInt import com.github.damontecres.wholphin.ui.rememberInt
import com.github.damontecres.wholphin.ui.roundMinutes import com.github.damontecres.wholphin.ui.roundMinutes
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -124,12 +125,7 @@ fun DiscoverSeriesDetails(
viewModel.navigateTo(Destination.DiscoveredItem(item)) viewModel.navigateTo(Destination.DiscoveredItem(item))
}, },
onClickPerson = { onClickPerson = {
viewModel.navigateTo( viewModel.navigateTo(Destination.DiscoveredItem(it))
Destination.MediaItem(
it.id,
BaseItemKind.PERSON,
),
)
}, },
goToOnClick = { goToOnClick = {
item.mediaInfo?.jellyfinMediaId?.toUUIDOrNull()?.let { item.mediaInfo?.jellyfinMediaId?.toUUIDOrNull()?.let {
@ -204,7 +200,7 @@ fun DiscoverSeriesDetailsContent(
similar: List<DiscoverItem>, similar: List<DiscoverItem>,
recommended: List<DiscoverItem>, recommended: List<DiscoverItem>,
trailers: List<Trailer>, trailers: List<Trailer>,
people: List<Person>, people: List<DiscoverItem>,
requestOnClick: () -> Unit, requestOnClick: () -> Unit,
cancelOnClick: () -> Unit, cancelOnClick: () -> Unit,
trailerOnClick: (Trailer) -> Unit, trailerOnClick: (Trailer) -> Unit,
@ -212,8 +208,8 @@ fun DiscoverSeriesDetailsContent(
goToOnClick: () -> Unit, goToOnClick: () -> Unit,
moreOnClick: () -> Unit, moreOnClick: () -> Unit,
onClickItem: (Int, DiscoverItem) -> Unit, onClickItem: (Int, DiscoverItem) -> Unit,
onClickPerson: (Person) -> Unit, onClickPerson: (DiscoverItem) -> Unit,
onLongClickPerson: (Int, Person) -> Unit, onLongClickPerson: (Int, DiscoverItem) -> Unit,
onLongClickSimilar: (Int, DiscoverItem) -> Unit, onLongClickSimilar: (Int, DiscoverItem) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -244,6 +240,13 @@ fun DiscoverSeriesDetailsContent(
modifier = Modifier, modifier = Modifier,
) { ) {
item { item {
Column(
verticalArrangement = Arrangement.spacedBy(0.dp),
modifier =
Modifier
.fillMaxWidth()
.bringIntoViewRequester(bringIntoViewRequester),
) {
DiscoverSeriesDetailsHeader( DiscoverSeriesDetailsHeader(
series = series, series = series,
rating = rating, rating = rating,
@ -251,7 +254,6 @@ fun DiscoverSeriesDetailsContent(
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.bringIntoViewRequester(bringIntoViewRequester)
.padding(top = 32.dp, bottom = 16.dp), .padding(top = 32.dp, bottom = 16.dp),
) )
val canCancel = val canCancel =
@ -288,6 +290,7 @@ fun DiscoverSeriesDetailsContent(
.focusRequester(focusRequesters[HEADER_ROW]), .focusRequester(focusRequesters[HEADER_ROW]),
) )
} }
}
// item { // item {
// ItemRow( // ItemRow(
// title = stringResource(R.string.tv_seasons) + " (${seasons.size})", // title = stringResource(R.string.tv_seasons) + " (${seasons.size})",
@ -318,18 +321,22 @@ fun DiscoverSeriesDetailsContent(
// } // }
if (people.isNotEmpty()) { if (people.isNotEmpty()) {
item { item {
PersonRow( DiscoverRow(
people = people, row =
onClick = { DiscoverRowData(
stringResource(R.string.people),
DataLoadingState.Success(people),
),
onClickItem = { index: Int, item: DiscoverItem ->
position = PEOPLE_ROW position = PEOPLE_ROW
onClickPerson.invoke(it) onClickPerson.invoke(item)
}, },
onLongClick = { index, person -> onLongClickItem = { index, person ->
position = PEOPLE_ROW
onLongClickPerson.invoke(index, person)
}, },
modifier = onCardFocus = {},
Modifier focusRequester = focusRequesters[PEOPLE_ROW],
.fillMaxWidth()
.focusRequester(focusRequesters[PEOPLE_ROW]),
) )
} }
} }

View file

@ -10,7 +10,6 @@ import com.github.damontecres.wholphin.api.seerr.model.TvDetails
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.DiscoverItem import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.DiscoverRating 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.data.model.Trailer
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
@ -34,8 +33,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient 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) @HiltViewModel(assistedFactory = DiscoverSeriesViewModel.Factory::class)
class DiscoverSeriesViewModel class DiscoverSeriesViewModel
@ -61,7 +58,7 @@ class DiscoverSeriesViewModel
val seasons = MutableLiveData<List<Season>>(listOf()) val seasons = MutableLiveData<List<Season>>(listOf())
val trailers = MutableLiveData<List<Trailer>>(listOf()) val trailers = MutableLiveData<List<Trailer>>(listOf())
val people = MutableLiveData<List<Person>>(listOf()) val people = MutableLiveData<List<DiscoverItem>>(listOf())
val similar = MutableLiveData<List<DiscoverItem>>(listOf()) val similar = MutableLiveData<List<DiscoverItem>>(listOf())
val recommended = MutableLiveData<List<DiscoverItem>>(listOf()) val recommended = MutableLiveData<List<DiscoverItem>>(listOf())
@ -126,28 +123,12 @@ class DiscoverSeriesViewModel
val people = val people =
tv.credits tv.credits
?.cast ?.cast
?.map { ?.map(::DiscoverItem)
Person( .orEmpty() +
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 tv.credits
?.crew ?.crew
?.map { ?.map(::DiscoverItem)
Person( .orEmpty()
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) this@DiscoverSeriesViewModel.people.setValueOnMain(people)
} }

View file

@ -196,7 +196,7 @@ fun SeerrRequestsPage(
gridFocusRequester = focusRequester, gridFocusRequester = focusRequester,
showJumpButtons = false, showJumpButtons = false,
showLetterButtons = false, showLetterButtons = false,
spacing = 24.dp, spacing = 16.dp,
cardContent = @Composable { item, onClick, onLongClick, mod -> cardContent = @Composable { item, onClick, onLongClick, mod ->
DiscoverItemCard( DiscoverItemCard(
item = item?.item, item = item?.item,

View file

@ -23,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.PersonPage
import com.github.damontecres.wholphin.ui.detail.PlaylistDetails 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.DiscoverMovieDetails
import com.github.damontecres.wholphin.ui.detail.discover.DiscoverPersonPage
import com.github.damontecres.wholphin.ui.detail.discover.DiscoverSeriesDetails 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.episode.EpisodeDetails
import com.github.damontecres.wholphin.ui.detail.movie.MovieDetails import com.github.damontecres.wholphin.ui.detail.movie.MovieDetails
@ -265,6 +266,7 @@ fun DestinationContent(
DiscoverMovieDetails( DiscoverMovieDetails(
preferences = preferences, preferences = preferences,
destination = destination, destination = destination,
modifier = modifier,
) )
} }
@ -272,13 +274,14 @@ fun DestinationContent(
DiscoverSeriesDetails( DiscoverSeriesDetails(
preferences = preferences, preferences = preferences,
destination = destination, destination = destination,
modifier = modifier,
) )
} }
SeerrItemType.PERSON -> { SeerrItemType.PERSON -> {
Text( DiscoverPersonPage(
text = "Person not supported yet", person = destination.item,
color = MaterialTheme.colorScheme.onSurface, modifier = modifier,
) )
} }

View file

@ -1009,11 +1009,11 @@ components:
cast: cast:
type: array type: array
items: items:
$ref: '#/components/schemas/Cast' $ref: '#/components/schemas/CreditCast'
crew: crew:
type: array type: array
items: items:
$ref: '#/components/schemas/Crew' $ref: '#/components/schemas/CreditCrew'
collection: collection:
type: object type: object
properties: properties:
@ -1205,11 +1205,11 @@ components:
cast: cast:
type: array type: array
items: items:
$ref: '#/components/schemas/Cast' $ref: '#/components/schemas/CreditCast'
crew: crew:
type: array type: array
items: items:
$ref: '#/components/schemas/Crew' $ref: '#/components/schemas/CreditCrew'
externalIds: externalIds:
$ref: '#/components/schemas/ExternalIds' $ref: '#/components/schemas/ExternalIds'
keywords: keywords:
@ -1691,6 +1691,9 @@ components:
type: integer type: integer
posterPath: posterPath:
type: string type: string
profilePath:
type: string
nullable: true
originalTitle: originalTitle:
type: string type: string
video: video:
@ -1745,6 +1748,9 @@ components:
type: integer type: integer
posterPath: posterPath:
type: string type: string
profilePath:
type: string
nullable: true
originalTitle: originalTitle:
type: string type: string
video: video: