Fix only loading 100 episode in a season (#187)

Fixes #184

Data was being fetched, but the UI wasn't being properly notified to
display the data.

This PR also adjusts so that "focused episode header" still takes up the
space even if the focused episode hasn't fully loaded yet. This prevents
the UI from jumping up and down.

Also #42 is _not_ fixed by this PR because it is a different issue.
This commit is contained in:
damontecres 2025-11-09 17:40:13 -05:00 committed by GitHub
parent 5720603447
commit 0fb1a69556
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 47 additions and 28 deletions

View file

@ -24,18 +24,18 @@ import org.jellyfin.sdk.model.extensions.ticks
@Composable
fun FocusedEpisodeHeader(
ep: BaseItem,
ep: BaseItem?,
overviewOnClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val dto = ep.data
val dto = ep?.data
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = modifier,
) {
Text(
text = dto.episodeTitle ?: dto.name ?: "",
text = dto?.episodeTitle ?: dto?.name ?: "",
style = MaterialTheme.typography.headlineSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
@ -46,17 +46,17 @@ fun FocusedEpisodeHeader(
) {
val details =
buildList {
dto.seasonEpisode?.let(::add)
dto.premiereDate?.let { add(formatDateTime(it)) }
val duration = dto.runTimeTicks?.ticks
dto?.seasonEpisode?.let(::add)
dto?.premiereDate?.let { add(formatDateTime(it)) }
val duration = dto?.runTimeTicks?.ticks
duration
?.roundMinutes
?.toString()
?.let {
add(it)
}
dto.officialRating?.let(::add)
dto.timeRemaining?.roundMinutes?.let { add("$it left") }
dto?.officialRating?.let(::add)
dto?.timeRemaining?.roundMinutes?.let { add("$it left") }
}
DotSeparatedRow(
texts = details,
@ -68,12 +68,12 @@ fun FocusedEpisodeHeader(
verticalAlignment = Alignment.CenterVertically,
) {
SimpleStarRating(
dto.communityRating,
dto?.communityRating,
Modifier.height(20.dp),
)
}
OverviewText(
overview = dto.overview ?: "",
overview = dto?.overview ?: "",
maxLines = 3,
onClick = overviewOnClick,
)

View file

@ -162,14 +162,12 @@ fun SeriesOverviewContent(
}
item {
// Episode header
focusedEpisode?.let { ep ->
FocusedEpisodeHeader(
ep = ep,
ep = focusedEpisode,
overviewOnClick = overviewOnClick,
modifier = Modifier.fillMaxWidth(.66f),
)
}
}
item {
key(position.seasonTabIndex) {
when (val eps = episodes) {

View file

@ -293,17 +293,11 @@ class SeriesViewModel
itemId: UUID,
listIndex: Int,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
val base = api.userLibraryApi.getItem(itemId).content
val item = BaseItem.Companion.from(base, api)
val eps = episodes.value
if (eps is EpisodeList.Success) {
val newItems =
eps.episodes.toMutableList().apply {
this[listIndex] = item
}
val newValue = eps.copy(episodes = newItems)
eps.episodes.refreshItem(listIndex, itemId)
withContext(Dispatchers.Main) {
episodes.value = newValue
episodes.value = eps
}
}
}
@ -409,7 +403,7 @@ sealed interface EpisodeList {
}
data class Success(
val episodes: List<BaseItem?>,
val episodes: ApiRequestPager<GetEpisodesRequest>,
val initialIndex: Int,
) : EpisodeList
}

View file

@ -21,6 +21,7 @@ import org.jellyfin.sdk.api.client.extensions.liveTvApi
import org.jellyfin.sdk.api.client.extensions.playlistsApi
import org.jellyfin.sdk.api.client.extensions.suggestionsApi
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.model.api.BaseItemDtoQueryResult
import org.jellyfin.sdk.model.api.GetProgramsDto
import org.jellyfin.sdk.model.api.request.GetEpisodesRequest
@ -31,6 +32,7 @@ import org.jellyfin.sdk.model.api.request.GetPlaylistItemsRequest
import org.jellyfin.sdk.model.api.request.GetResumeItemsRequest
import org.jellyfin.sdk.model.api.request.GetSuggestionsRequest
import timber.log.Timber
import java.util.UUID
import java.util.function.Predicate
/**
@ -58,7 +60,7 @@ class ApiRequestPager<T>(
CacheBuilder
.newBuilder()
.maximumSize(cacheSize)
.build<Int, List<BaseItem>>()
.build<Int, MutableList<BaseItem>>()
suspend fun init(): ApiRequestPager<T> {
if (totalCount < 0) {
@ -122,13 +124,38 @@ class ApiRequestPager<T>(
false,
)
val result = requestHandler.execute(api, newRequest).content
val data = result.items.map { BaseItem.from(it, api, useSeriesForPrimary) }
val data = mutableListOf<BaseItem>()
result.items.forEach { data.add(BaseItem.from(it, api, useSeriesForPrimary)) }
cachedPages.put(pageNumber, data)
items = ItemList(totalCount, pageSize, cachedPages.asMap())
}
}
}
suspend fun refreshItem(
position: Int,
itemId: UUID,
) {
val item =
api.userLibraryApi.getItem(itemId).content.let {
BaseItem.from(
it,
api,
useSeriesForPrimary,
)
}
val pageNumber = position / pageSize
val index = position - pageNumber * pageSize
mutex.withLock {
val page = cachedPages.getIfPresent(pageNumber)
if (page != null && index in page.indices) {
page[index] = item
cachedPages.put(pageNumber, page)
items = ItemList(totalCount, pageSize, cachedPages.asMap())
}
}
}
companion object {
private const val DEBUG = false
}