mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Add discover person page
This commit is contained in:
parent
d175e3f471
commit
ec7d012c22
10 changed files with 296 additions and 160 deletions
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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') {
|
||||||
|
|
|
||||||
|
|
@ -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]),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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]),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue