mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Discover on person page
This commit is contained in:
parent
e7060cedfd
commit
d454cf0e3d
4 changed files with 112 additions and 0 deletions
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
package com.github.damontecres.wholphin.data.model
|
package com.github.damontecres.wholphin.data.model
|
||||||
|
|
||||||
|
import com.github.damontecres.wholphin.api.seerr.model.CreditCast
|
||||||
|
import com.github.damontecres.wholphin.api.seerr.model.CreditCrew
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.MovieDetails
|
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.MovieMovieIdRatingsGet200Response
|
||||||
import com.github.damontecres.wholphin.api.seerr.model.MovieResult
|
import com.github.damontecres.wholphin.api.seerr.model.MovieResult
|
||||||
|
|
@ -164,6 +166,36 @@ data class DiscoverItem(
|
||||||
backdropPath = search.backdropPath,
|
backdropPath = search.backdropPath,
|
||||||
jellyfinItemId = search.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
jellyfinItemId = search.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
constructor(credit: CreditCast) : this(
|
||||||
|
id = credit.id!!,
|
||||||
|
type = SeerrItemType.TV,
|
||||||
|
title = credit.name,
|
||||||
|
subtitle = null,
|
||||||
|
overview = credit.overview,
|
||||||
|
availability =
|
||||||
|
SeerrAvailability.from(credit.mediaInfo?.status)
|
||||||
|
?: SeerrAvailability.UNKNOWN,
|
||||||
|
releaseDate = credit.firstAirDate,
|
||||||
|
posterPath = credit.posterPath,
|
||||||
|
backdropPath = credit.backdropPath,
|
||||||
|
jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor(credit: CreditCrew) : this(
|
||||||
|
id = credit.id!!,
|
||||||
|
type = SeerrItemType.TV,
|
||||||
|
title = credit.name,
|
||||||
|
subtitle = null,
|
||||||
|
overview = credit.overview,
|
||||||
|
availability =
|
||||||
|
SeerrAvailability.from(credit.mediaInfo?.status)
|
||||||
|
?: SeerrAvailability.UNKNOWN,
|
||||||
|
releaseDate = credit.firstAirDate,
|
||||||
|
posterPath = credit.posterPath,
|
||||||
|
backdropPath = credit.backdropPath,
|
||||||
|
jellyfinItemId = credit.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DiscoverRating(
|
data class DiscoverRating(
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,24 @@ class SeerrService
|
||||||
?.map(::DiscoverItem)
|
?.map(::DiscoverItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BaseItemKind.PERSON -> {
|
||||||
|
api.personApi
|
||||||
|
.personPersonIdCombinedCreditsGet(personId = it)
|
||||||
|
.let { credits ->
|
||||||
|
val cast =
|
||||||
|
credits.cast
|
||||||
|
?.take(25)
|
||||||
|
?.map(::DiscoverItem)
|
||||||
|
.orEmpty()
|
||||||
|
val crew =
|
||||||
|
credits.crew
|
||||||
|
?.take(25)
|
||||||
|
?.map(::DiscoverItem)
|
||||||
|
.orEmpty()
|
||||||
|
cast + crew
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ val SlimItemFields =
|
||||||
|
|
||||||
object Cards {
|
object Cards {
|
||||||
val height2x3 = 172.dp
|
val height2x3 = 172.dp
|
||||||
|
val heightEpisode = height2x3 * .75f
|
||||||
val playedPercentHeight = 6.dp
|
val playedPercentHeight = 6.dp
|
||||||
val serverUserCircle = height2x3 * .75f
|
val serverUserCircle = height2x3 * .75f
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import androidx.compose.foundation.relocation.bringIntoViewRequester
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
@ -40,13 +41,17 @@ import androidx.tv.material3.surfaceColorAtElevation
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
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.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||||
import com.github.damontecres.wholphin.services.NavigationManager
|
import com.github.damontecres.wholphin.services.NavigationManager
|
||||||
|
import com.github.damontecres.wholphin.services.SeerrService
|
||||||
|
import com.github.damontecres.wholphin.ui.Cards
|
||||||
import com.github.damontecres.wholphin.ui.LocalImageUrlService
|
import com.github.damontecres.wholphin.ui.LocalImageUrlService
|
||||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
|
import com.github.damontecres.wholphin.ui.cards.SeasonCard
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||||
|
|
@ -54,7 +59,11 @@ import com.github.damontecres.wholphin.ui.components.LoadingRow
|
||||||
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.data.RowColumn
|
||||||
|
import com.github.damontecres.wholphin.ui.discover.DiscoverRow
|
||||||
|
import com.github.damontecres.wholphin.ui.discover.DiscoverRowData
|
||||||
import com.github.damontecres.wholphin.ui.formatDate
|
import com.github.damontecres.wholphin.ui.formatDate
|
||||||
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
|
|
@ -63,11 +72,14 @@ import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||||
|
import com.github.damontecres.wholphin.util.DataLoadingState
|
||||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import com.github.damontecres.wholphin.util.RowLoadingState
|
import com.github.damontecres.wholphin.util.RowLoadingState
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.ImageType
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
|
|
@ -86,10 +98,12 @@ class PersonViewModel
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
private val favoriteWatchManager: FavoriteWatchManager,
|
private val favoriteWatchManager: FavoriteWatchManager,
|
||||||
|
private val seerrService: SeerrService,
|
||||||
) : LoadingItemViewModel(api) {
|
) : LoadingItemViewModel(api) {
|
||||||
val movies = MutableLiveData<RowLoadingState>(RowLoadingState.Pending)
|
val movies = MutableLiveData<RowLoadingState>(RowLoadingState.Pending)
|
||||||
val series = MutableLiveData<RowLoadingState>(RowLoadingState.Pending)
|
val series = MutableLiveData<RowLoadingState>(RowLoadingState.Pending)
|
||||||
val episodes = MutableLiveData<RowLoadingState>(RowLoadingState.Pending)
|
val episodes = MutableLiveData<RowLoadingState>(RowLoadingState.Pending)
|
||||||
|
val discovered = MutableStateFlow<List<DiscoverItem>>(listOf())
|
||||||
|
|
||||||
fun init(itemId: UUID) {
|
fun init(itemId: UUID) {
|
||||||
viewModelScope.launchIO(
|
viewModelScope.launchIO(
|
||||||
|
|
@ -115,6 +129,10 @@ class PersonViewModel
|
||||||
} else {
|
} else {
|
||||||
episodes.setValueOnMain(RowLoadingState.Success(listOf()))
|
episodes.setValueOnMain(RowLoadingState.Success(listOf()))
|
||||||
}
|
}
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
val results = seerrService.similar(person)
|
||||||
|
discovered.update { results }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -177,6 +195,7 @@ fun PersonPage(
|
||||||
val movies by viewModel.movies.observeAsState(RowLoadingState.Pending)
|
val movies by viewModel.movies.observeAsState(RowLoadingState.Pending)
|
||||||
val series by viewModel.series.observeAsState(RowLoadingState.Pending)
|
val series by viewModel.series.observeAsState(RowLoadingState.Pending)
|
||||||
val episodes by viewModel.episodes.observeAsState(RowLoadingState.Pending)
|
val episodes by viewModel.episodes.observeAsState(RowLoadingState.Pending)
|
||||||
|
val discovered by viewModel.discovered.collectAsState()
|
||||||
|
|
||||||
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
||||||
when (val state = loading) {
|
when (val state = loading) {
|
||||||
|
|
@ -215,6 +234,10 @@ fun PersonPage(
|
||||||
favoriteOnClick = {
|
favoriteOnClick = {
|
||||||
viewModel.setFavorite(!person.favorite)
|
viewModel.setFavorite(!person.favorite)
|
||||||
},
|
},
|
||||||
|
discovered = discovered,
|
||||||
|
onClickDiscover = { index, item ->
|
||||||
|
viewModel.navigationManager.navigateTo(item.destination)
|
||||||
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
AnimatedVisibility(showOverviewDialog) {
|
AnimatedVisibility(showOverviewDialog) {
|
||||||
|
|
@ -239,6 +262,7 @@ private const val HEADER_ROW = 0
|
||||||
private const val MOVIE_ROW = 1
|
private const val MOVIE_ROW = 1
|
||||||
private const val SERIES_ROW = 2
|
private const val SERIES_ROW = 2
|
||||||
private const val EPISODE_ROW = 3
|
private const val EPISODE_ROW = 3
|
||||||
|
private const val DISCOVER_ROW = EPISODE_ROW + 1
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PersonPageContent(
|
fun PersonPageContent(
|
||||||
|
|
@ -253,9 +277,11 @@ fun PersonPageContent(
|
||||||
movies: RowLoadingState,
|
movies: RowLoadingState,
|
||||||
series: RowLoadingState,
|
series: RowLoadingState,
|
||||||
episodes: RowLoadingState,
|
episodes: RowLoadingState,
|
||||||
|
discovered: List<DiscoverItem>,
|
||||||
onClickItem: (Int, BaseItem) -> Unit,
|
onClickItem: (Int, BaseItem) -> Unit,
|
||||||
overviewOnClick: () -> Unit,
|
overviewOnClick: () -> Unit,
|
||||||
favoriteOnClick: () -> Unit,
|
favoriteOnClick: () -> Unit,
|
||||||
|
onClickDiscover: (Int, DiscoverItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
@ -336,8 +362,43 @@ fun PersonPageContent(
|
||||||
showIfEmpty = false,
|
showIfEmpty = false,
|
||||||
horizontalPadding = 32.dp,
|
horizontalPadding = 32.dp,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
cardContent = { index, item, mod, onClick, onLongClick ->
|
||||||
|
SeasonCard(
|
||||||
|
item = item,
|
||||||
|
onClick = {
|
||||||
|
position = RowColumn(EPISODE_ROW, index)
|
||||||
|
onClick.invoke()
|
||||||
|
},
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
imageHeight = Cards.heightEpisode,
|
||||||
|
modifier =
|
||||||
|
mod
|
||||||
|
.ifElse(
|
||||||
|
position.row == EPISODE_ROW && position.column == index,
|
||||||
|
Modifier.focusRequester(focusRequester),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (discovered.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
DiscoverRow(
|
||||||
|
row =
|
||||||
|
DiscoverRowData(
|
||||||
|
stringResource(R.string.discover),
|
||||||
|
DataLoadingState.Success(discovered),
|
||||||
|
),
|
||||||
|
onClickItem = { index: Int, item: DiscoverItem ->
|
||||||
|
position = RowColumn(DISCOVER_ROW, index)
|
||||||
|
onClickDiscover.invoke(index, item)
|
||||||
|
},
|
||||||
|
onLongClickItem = { _, _ -> },
|
||||||
|
onCardFocus = {},
|
||||||
|
focusRequester = focusRequester,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue