From ec7d012c2270209f040de706bea2a000cbcd86e3 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Tue, 6 Jan 2026 19:17:40 -0500 Subject: [PATCH] Add discover person page --- .../wholphin/data/model/DiscoverItem.kt | 35 ++-- .../wholphin/ui/detail/CardGrid.kt | 2 +- .../detail/discover/DiscoverMovieDetails.kt | 50 +++--- .../detail/discover/DiscoverMovieViewModel.kt | 29 +--- .../ui/detail/discover/DiscoverPersonPage.kt | 161 ++++++++++++++++++ .../detail/discover/DiscoverSeriesDetails.kt | 125 +++++++------- .../discover/DiscoverSeriesViewModel.kt | 29 +--- .../wholphin/ui/discover/SeerrRequestsPage.kt | 2 +- .../wholphin/ui/nav/DestinationContent.kt | 9 +- app/src/main/seerr/seerr-api.yml | 14 +- 10 files changed, 296 insertions(+), 160 deletions(-) create mode 100644 app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverPersonPage.kt diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt index 10362b41..9c1960ec 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt @@ -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.TvTvIdRatingsGet200Response 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.toLocalDate import com.github.damontecres.wholphin.util.LocalDateSerializer @@ -41,13 +42,15 @@ enum class SeerrItemType( ; companion object { - fun fromString(str: String?) = - when (str) { - "movie" -> MOVIE - "tv" -> TV - "person" -> PERSON - else -> UNKNOWN - } + fun fromString( + str: String?, + fallback: SeerrItemType = UNKNOWN, + ) = when (str) { + "movie" -> MOVIE + "tv" -> TV + "person" -> PERSON + else -> fallback + } } } @@ -80,7 +83,11 @@ data class DiscoverItem( val posterPath: String?, val backdropPath: String?, 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 posterUrl: String? get() = posterPath?.let { "https://image.tmdb.org/t/p/w500$it" } @@ -172,30 +179,30 @@ data class DiscoverItem( constructor(credit: CreditCast) : this( id = credit.id!!, - type = SeerrItemType.TV, - title = credit.name, + type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON), + title = credit.name ?: credit.title, subtitle = null, overview = credit.overview, availability = SeerrAvailability.from(credit.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, releaseDate = toLocalDate(credit.firstAirDate), - posterPath = credit.posterPath, + posterPath = credit.posterPath ?: credit.profilePath, backdropPath = credit.backdropPath, jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), ) constructor(credit: CreditCrew) : this( id = credit.id!!, - type = SeerrItemType.TV, - title = credit.name, + type = SeerrItemType.fromString(credit.mediaType, SeerrItemType.PERSON), + title = credit.name ?: credit.title, subtitle = null, overview = credit.overview, availability = SeerrAvailability.from(credit.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, releaseDate = toLocalDate(credit.firstAirDate), - posterPath = credit.posterPath, + posterPath = credit.posterPath ?: credit.profilePath, backdropPath = credit.backdropPath, jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(), ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CardGrid.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CardGrid.kt index c9af784f..f028ed1e 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CardGrid.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CardGrid.kt @@ -374,7 +374,7 @@ fun CardGrid( pager .getOrNull(focusedIndex) ?.sortName - ?.first() + ?.firstOrNull() ?.uppercaseChar() ?.let { if (it >= '0' && it <= '9') { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt index c1999f04..26963141 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt @@ -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.DiscoverRating 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.SeerrAvailability 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.DiscoverItemCard 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.components.DialogParams 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.ItemDetailsDialogInfo 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.rememberInt 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.LoadingState import kotlinx.coroutines.launch @@ -137,7 +137,8 @@ fun DiscoverMovieDetails( onClickItem = { index, item -> viewModel.navigateTo(Destination.DiscoveredItem(item)) }, - onClickPerson = { + onClickPerson = { item -> + viewModel.navigateTo(Destination.DiscoveredItem(item)) }, overviewOnClick = { overviewDialog = @@ -166,20 +167,7 @@ fun DiscoverMovieDetails( items = listOf(), ) }, - onLongClickPerson = { index, person -> - val items = - buildMoreDialogItemsForPerson( - context = context, - person = person, - actions = moreActions, - ) - moreDialog = - DialogParams( - fromLongClick = true, - title = person.name ?: "", - items = items, - ) - }, + onLongClickPerson = { index, person -> }, onLongClickSimilar = { index, similar -> }, trailerOnClick = { @@ -223,7 +211,7 @@ fun DiscoverMovieDetailsContent( userConfig: SeerrUserConfig?, movie: MovieDetails, rating: DiscoverRating?, - people: List, + people: List, trailers: List, similar: List, recommended: List, @@ -234,8 +222,8 @@ fun DiscoverMovieDetailsContent( goToOnClick: () -> Unit, moreOnClick: () -> Unit, onClickItem: (Int, DiscoverItem) -> Unit, - onClickPerson: (Person) -> Unit, - onLongClickPerson: (Int, Person) -> Unit, + onClickPerson: (DiscoverItem) -> Unit, + onLongClickPerson: (Int, DiscoverItem) -> Unit, onLongClickSimilar: (Int, DiscoverItem) -> Unit, modifier: Modifier = Modifier, ) { @@ -310,20 +298,22 @@ fun DiscoverMovieDetailsContent( } if (people.isNotEmpty()) { item { - PersonRow( - people = people, - onClick = { + DiscoverRow( + row = + DiscoverRowData( + stringResource(R.string.people), + DataLoadingState.Success(people), + ), + onClickItem = { index: Int, item: DiscoverItem -> position = PEOPLE_ROW - onClickPerson.invoke(it) + onClickPerson.invoke(item) }, - onLongClick = { index, person -> + onLongClickItem = { index, person -> position = PEOPLE_ROW onLongClickPerson.invoke(index, person) }, - modifier = - Modifier - .fillMaxWidth() - .focusRequester(focusRequesters[PEOPLE_ROW]), + onCardFocus = {}, + focusRequester = focusRequesters[PEOPLE_ROW], ) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt index 37b91816..b267e74e 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt @@ -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.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.RemoteTrailer import com.github.damontecres.wholphin.data.model.Trailer import com.github.damontecres.wholphin.services.BackdropService @@ -36,8 +35,6 @@ import kotlinx.coroutines.flow.map 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 = DiscoverMovieViewModel.Factory::class) class DiscoverMovieViewModel @@ -62,7 +59,7 @@ class DiscoverMovieViewModel val rating = MutableLiveData(null) val trailers = MutableLiveData>(listOf()) - val people = MutableLiveData>(listOf()) + val people = MutableLiveData>(listOf()) val similar = MutableLiveData>(listOf()) val recommended = MutableLiveData>(listOf()) @@ -127,28 +124,12 @@ class DiscoverMovieViewModel val people = movie.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() + + ?.map(::DiscoverItem) + .orEmpty() + movie.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() + ?.map(::DiscoverItem) + .orEmpty() this@DiscoverMovieViewModel.people.setValueOnMain(people) val trailers = movie.relatedVideos diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverPersonPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverPersonPage.kt new file mode 100644 index 00000000..32d38aa5 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverPersonPage.kt @@ -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.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( + 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> -> { + 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(), + ) + } + } + } + } +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt index 5ef26aa4..5aaf882d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt @@ -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.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.SeerrPermission 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.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 @@ -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.data.ItemDetailsDialog 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.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.DataLoadingState import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.LoadingState import kotlinx.coroutines.launch @@ -124,12 +125,7 @@ fun DiscoverSeriesDetails( viewModel.navigateTo(Destination.DiscoveredItem(item)) }, onClickPerson = { - viewModel.navigateTo( - Destination.MediaItem( - it.id, - BaseItemKind.PERSON, - ), - ) + viewModel.navigateTo(Destination.DiscoveredItem(it)) }, goToOnClick = { item.mediaInfo?.jellyfinMediaId?.toUUIDOrNull()?.let { @@ -204,7 +200,7 @@ fun DiscoverSeriesDetailsContent( similar: List, recommended: List, trailers: List, - people: List, + people: List, requestOnClick: () -> Unit, cancelOnClick: () -> Unit, trailerOnClick: (Trailer) -> Unit, @@ -212,8 +208,8 @@ fun DiscoverSeriesDetailsContent( goToOnClick: () -> Unit, moreOnClick: () -> Unit, onClickItem: (Int, DiscoverItem) -> Unit, - onClickPerson: (Person) -> Unit, - onLongClickPerson: (Int, Person) -> Unit, + onClickPerson: (DiscoverItem) -> Unit, + onLongClickPerson: (Int, DiscoverItem) -> Unit, onLongClickSimilar: (Int, DiscoverItem) -> Unit, modifier: Modifier = Modifier, ) { @@ -244,49 +240,56 @@ fun DiscoverSeriesDetailsContent( modifier = Modifier, ) { item { - DiscoverSeriesDetailsHeader( - series = series, - rating = rating, - overviewOnClick = overviewOnClick, + Column( + verticalArrangement = Arrangement.spacedBy(0.dp), modifier = Modifier .fillMaxWidth() - .bringIntoViewRequester(bringIntoViewRequester) - .padding(top = 32.dp, bottom = 16.dp), - ) - val canCancel = - remember(series, userConfig) { - ( - // User requested this - userConfig.hasPermission(SeerrPermission.REQUEST) && - series.mediaInfo?.requests?.any { it.requestedBy?.id == userConfig?.id } == true - ) || - userConfig.hasPermission(SeerrPermission.MANAGE_REQUESTS) - } - 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() - } + .bringIntoViewRequester(bringIntoViewRequester), + ) { + DiscoverSeriesDetailsHeader( + series = series, + rating = rating, + overviewOnClick = overviewOnClick, + modifier = + Modifier + .fillMaxWidth() + .padding(top = 32.dp, bottom = 16.dp), + ) + val canCancel = + remember(series, userConfig) { + ( + // User requested this + userConfig.hasPermission(SeerrPermission.REQUEST) && + series.mediaInfo?.requests?.any { it.requestedBy?.id == userConfig?.id } == true + ) || + userConfig.hasPermission(SeerrPermission.MANAGE_REQUESTS) } - }, - canRequest = userConfig.hasPermission(SeerrPermission.REQUEST), - canCancel = canCancel, - modifier = - Modifier - .fillMaxWidth() - .padding(bottom = 16.dp) - .focusRequester(focusRequesters[HEADER_ROW]), - ) + 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() + } + } + }, + canRequest = userConfig.hasPermission(SeerrPermission.REQUEST), + canCancel = canCancel, + modifier = + Modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + .focusRequester(focusRequesters[HEADER_ROW]), + ) + } } // item { // ItemRow( @@ -318,18 +321,22 @@ fun DiscoverSeriesDetailsContent( // } if (people.isNotEmpty()) { item { - PersonRow( - people = people, - onClick = { + DiscoverRow( + row = + DiscoverRowData( + stringResource(R.string.people), + DataLoadingState.Success(people), + ), + onClickItem = { index: Int, item: DiscoverItem -> position = PEOPLE_ROW - onClickPerson.invoke(it) + onClickPerson.invoke(item) }, - onLongClick = { index, person -> + onLongClickItem = { index, person -> + position = PEOPLE_ROW + onLongClickPerson.invoke(index, person) }, - modifier = - Modifier - .fillMaxWidth() - .focusRequester(focusRequesters[PEOPLE_ROW]), + onCardFocus = {}, + focusRequester = focusRequesters[PEOPLE_ROW], ) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt index 2f8fb5e0..bfbb5bf6 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt @@ -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.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 @@ -34,8 +33,6 @@ import kotlinx.coroutines.flow.map 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 @@ -61,7 +58,7 @@ class DiscoverSeriesViewModel val seasons = MutableLiveData>(listOf()) val trailers = MutableLiveData>(listOf()) - val people = MutableLiveData>(listOf()) + val people = MutableLiveData>(listOf()) val similar = MutableLiveData>(listOf()) val recommended = MutableLiveData>(listOf()) @@ -126,28 +123,12 @@ class DiscoverSeriesViewModel 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() + + ?.map(::DiscoverItem) + .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() + ?.map(::DiscoverItem) + .orEmpty() this@DiscoverSeriesViewModel.people.setValueOnMain(people) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/discover/SeerrRequestsPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/discover/SeerrRequestsPage.kt index d6bc41f2..b8b1066e 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/discover/SeerrRequestsPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/discover/SeerrRequestsPage.kt @@ -196,7 +196,7 @@ fun SeerrRequestsPage( gridFocusRequester = focusRequester, showJumpButtons = false, showLetterButtons = false, - spacing = 24.dp, + spacing = 16.dp, cardContent = @Composable { item, onClick, onLongClick, mod -> DiscoverItemCard( item = item?.item, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt index 8ad3b46a..43b8841d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt @@ -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.PlaylistDetails 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.episode.EpisodeDetails import com.github.damontecres.wholphin.ui.detail.movie.MovieDetails @@ -265,6 +266,7 @@ fun DestinationContent( DiscoverMovieDetails( preferences = preferences, destination = destination, + modifier = modifier, ) } @@ -272,13 +274,14 @@ fun DestinationContent( DiscoverSeriesDetails( preferences = preferences, destination = destination, + modifier = modifier, ) } SeerrItemType.PERSON -> { - Text( - text = "Person not supported yet", - color = MaterialTheme.colorScheme.onSurface, + DiscoverPersonPage( + person = destination.item, + modifier = modifier, ) } diff --git a/app/src/main/seerr/seerr-api.yml b/app/src/main/seerr/seerr-api.yml index cc4dc2a0..f8efad03 100644 --- a/app/src/main/seerr/seerr-api.yml +++ b/app/src/main/seerr/seerr-api.yml @@ -1009,11 +1009,11 @@ components: cast: type: array items: - $ref: '#/components/schemas/Cast' + $ref: '#/components/schemas/CreditCast' crew: type: array items: - $ref: '#/components/schemas/Crew' + $ref: '#/components/schemas/CreditCrew' collection: type: object properties: @@ -1205,11 +1205,11 @@ components: cast: type: array items: - $ref: '#/components/schemas/Cast' + $ref: '#/components/schemas/CreditCast' crew: type: array items: - $ref: '#/components/schemas/Crew' + $ref: '#/components/schemas/CreditCrew' externalIds: $ref: '#/components/schemas/ExternalIds' keywords: @@ -1691,6 +1691,9 @@ components: type: integer posterPath: type: string + profilePath: + type: string + nullable: true originalTitle: type: string video: @@ -1745,6 +1748,9 @@ components: type: integer posterPath: type: string + profilePath: + type: string + nullable: true originalTitle: type: string video: