Fix person credits: movie release dates, full filmography, sorted order (#1950)
<!-- By submitting this pull request, you acknowledge that you have read the [contributing guide](https://github.com/damontecres/Wholphin/blob/main/CONTRIBUTING.md, including the AI/LLM policy, and [developer's guide](https://github.com/damontecres/Wholphin/blob/main/DEVELOPMENT.md) --> ## Description Person pages that get a filmography from Seerr had three problems, all in the same place. The release date was taken from `firstAirDate` alone — the field TMDB fills for series — so movie cards showed no year at all. The library person page capped the row at 25 cast + 25 crew while `DiscoverPersonPage` had no cap, because each screen carried its own copy of the same logic. And `cast + crew` left the row in no order a viewer can read, with a title appearing twice when a person is both cast and crew on it. This PR: * maps credit dates as `releaseDate ?: firstAirDate` — the expression the search result mapping already uses; * moves the credits logic into a single `SeerrService.personCredits()` used by both screens, so they cannot drift apart again; * drops the 25 + 25 cap, de-duplicates by TMDB id and sorts by release date descending, with undated entries last. Rows on movie and series pages are untouched: only the `BaseItemKind.PERSON` branch of `similar()` changed. Two things worth your opinion: * Without the cap, the row for a person with a long filmography is long — 135 cards for Cameron Diaz, for example. If you would rather keep a cap, the row could keep the newest 25 and gain a `ViewMoreCard` opening the full grid — `Destination.DiscoveredItem` with a `PERSON` item already routes to `DiscoverPersonPage`. I am happy to add that in a second commit. * The sort is fixed to release date descending. It now lives in one function, so a different key or a user-facing choice would be cheap to add later. ### Related issues Fixes #1873 ### Testing CI in my fork ran the repository's own PR workflow on this branch: `pre-commit` (ktlint) and `assembleDefaultDebug` with `testDefaultDebugUnitTest` are both green. I then installed a debug build of the same code on an NVIDIA Shield TV Pro 2019 (Android 11), against Jellyfin 10.11.11 with Jellyseerr, and opened Cameron Diaz from the cast row of *Charlie's Angels* before and after the change: * movie cards show a year now; before, the only two cards in the row with a year were the two TV entries; * the row is ordered by release date descending, movies and series interleaved, instead of the API order of all cast followed by all crew; * the row is no longer cut at 25 + 25 — it now runs back to her 1952 credit, and the entries TMDB has no date for sit at the very end of the row. Not tested on any other device; nothing in the change is device specific. ## Screenshots Person page of Cameron Diaz, reached from the cast row of *Charlie's Angels*. The library rows above the Discover row are the same in every shot and are useful as a reference: they always had their years. Before, start of the Discover row — no year under any card: <img width="1920" height="1080" alt="pr1873-1-before-start" src="https://github.com/user-attachments/assets/585d0670-ae39-4214-9004-0c92262991da" /> Before, end of the Discover row — the row stops here, and the only two years in it belong to the two TV entries: <img width="1920" height="1080" alt="pr1873-2-before-end" src="https://github.com/user-attachments/assets/85c58575-2ad0-4369-a103-60f04c8d2350" /> After, start of the row — years on the movie cards, ordered by date descending: <img width="1920" height="1080" alt="pr1873-3-after-start" src="https://github.com/user-attachments/assets/804745c5-e1a6-4fe4-93bd-dc6c97a7a876" /> After, end of the row — the list now reaches 1952, with the undated entries last: <img width="1920" height="1080" alt="pr1873-4-after-end" src="https://github.com/user-attachments/assets/2c32389c-ec0b-4df5-b9d1-f564ef10d8b3" /> ## AI or LLM usage The code was written with Claude Opus 5 (Claude Code): the two mapping lines, the extracted `personCredits()` function and the two call sites. I reviewed every line and can explain it. This description was written by me in Russian and translated into English with the same assistant. --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Damontecres <damontecres@gmail.com>
This commit is contained in:
parent
9c56965c82
commit
6486bd19ed
5 changed files with 77 additions and 32 deletions
|
|
@ -157,6 +157,7 @@ class PersonViewModel
|
|||
_state.update {
|
||||
it.copy(
|
||||
discovered = results,
|
||||
discoverPerson = seerrService.discoverPerson(person),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -221,6 +222,7 @@ data class PersonState(
|
|||
val series: RowLoadingState = RowLoadingState.Pending,
|
||||
val episodes: RowLoadingState = RowLoadingState.Pending,
|
||||
val discovered: List<DiscoverItem> = emptyList(),
|
||||
val discoverPerson: DiscoverItem? = null,
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
@ -257,6 +259,12 @@ fun PersonPage(
|
|||
imageType = ImageType.PRIMARY,
|
||||
)
|
||||
}
|
||||
// A long filmography is hard to navigate as a row, so it is cut at the same
|
||||
// limit as the home page rows and the rest is left to the full grid
|
||||
val maxItems = preferences.appPreferences.homePagePreferences.maxItemsPerRow
|
||||
val discovered =
|
||||
remember(state.discovered, maxItems) { state.discovered.take(maxItems) }
|
||||
|
||||
PersonPageContent(
|
||||
preferences = preferences,
|
||||
name = name,
|
||||
|
|
@ -276,10 +284,18 @@ fun PersonPage(
|
|||
favoriteOnClick = {
|
||||
viewModel.setFavorite(!person.favorite)
|
||||
},
|
||||
discovered = state.discovered,
|
||||
discovered = discovered,
|
||||
onClickDiscover = { index, item ->
|
||||
viewModel.navigationManager.navigateTo(item.destination)
|
||||
},
|
||||
onClickViewMoreDiscover = {
|
||||
state.discoverPerson?.let {
|
||||
viewModel.navigationManager.navigateTo(
|
||||
Destination.DiscoveredItem(it, maxItems),
|
||||
)
|
||||
}
|
||||
},
|
||||
enableViewMoreDiscover = state.discovered.size > discovered.size,
|
||||
modifier = modifier,
|
||||
)
|
||||
AnimatedVisibility(showOverviewDialog) {
|
||||
|
|
@ -323,6 +339,8 @@ fun PersonPageContent(
|
|||
overviewOnClick: () -> Unit,
|
||||
favoriteOnClick: () -> Unit,
|
||||
onClickDiscover: (Int, DiscoverItem) -> Unit,
|
||||
onClickViewMoreDiscover: () -> Unit,
|
||||
enableViewMoreDiscover: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
|
@ -437,6 +455,11 @@ fun PersonPageContent(
|
|||
onLongClickItem = { _, _ -> },
|
||||
onCardFocus = {},
|
||||
focusRequester = focusRequester,
|
||||
enableViewMore = enableViewMoreDiscover,
|
||||
onClickViewMore = {
|
||||
position = RowColumn(DISCOVER_ROW, discovered.size)
|
||||
onClickViewMoreDiscover.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,20 +61,7 @@ class DiscoverPersonViewModel
|
|||
viewModelScope.launchIO {
|
||||
backdropService.clearBackdrop()
|
||||
|
||||
val credits =
|
||||
seerrService.api.personApi
|
||||
.personPersonIdCombinedCreditsGet(personId = item.id)
|
||||
.let { credits ->
|
||||
val cast =
|
||||
credits.cast
|
||||
?.map { seerrService.createDiscoverItem(it) }
|
||||
.orEmpty()
|
||||
val crew =
|
||||
credits.crew
|
||||
?.map { seerrService.createDiscoverItem(it) }
|
||||
.orEmpty()
|
||||
cast + crew
|
||||
}
|
||||
val credits = seerrService.personCredits(item.id)
|
||||
this@DiscoverPersonViewModel.credits.update {
|
||||
DataLoadingState.Success(credits)
|
||||
}
|
||||
|
|
@ -85,6 +72,7 @@ class DiscoverPersonViewModel
|
|||
@Composable
|
||||
fun DiscoverPersonPage(
|
||||
person: DiscoverItem,
|
||||
startIndex: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: DiscoverPersonViewModel =
|
||||
hiltViewModel<DiscoverPersonViewModel, DiscoverPersonViewModel.Factory>(
|
||||
|
|
@ -137,6 +125,7 @@ fun DiscoverPersonPage(
|
|||
},
|
||||
onClickPlay = { _, item ->
|
||||
},
|
||||
initialPosition = startIndex,
|
||||
letterPosition = { c: Char -> 0 },
|
||||
gridFocusRequester = focusRequester,
|
||||
showJumpButtons = false,
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@ sealed class Destination(
|
|||
@Serializable
|
||||
data class DiscoveredItem(
|
||||
val item: DiscoverItem,
|
||||
// Index to scroll to on the person page grid
|
||||
val startIndex: Int = 0,
|
||||
) : Destination(false)
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -405,6 +405,7 @@ fun DestinationContent(
|
|||
LaunchedEffect(Unit) { onClearBackdrop.invoke() }
|
||||
DiscoverPersonPage(
|
||||
person = destination.item,
|
||||
startIndex = destination.startIndex,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue