ref: cw ui an caching updatges

This commit is contained in:
tapframe 2026-04-09 02:07:56 +05:30
parent 55bd4a4bfe
commit 013a179429
3 changed files with 101 additions and 27 deletions

View file

@ -132,6 +132,11 @@ fun HomeScreen(
cached.contentId to (cached.sortTimestamp to item)
}.toMap()
}
val cachedInProgressItems = remember(cachedSnapshots.second) {
cachedSnapshots.second.associate { cached ->
cached.videoId to cached.toContinueWatchingItem()
}
}
val effectivNextUpItems = remember(
nextUpItemsBySeries,
@ -145,15 +150,24 @@ fun HomeScreen(
item.nextUpSeedEpisodeNumber,
) !in continueWatchingPreferences.dismissedNextUpKeys
}
if (liveNextUpItems.isNotEmpty()) liveNextUpItems else cachedNextUpItems
if (liveNextUpItems.isNotEmpty()) {
liveNextUpItems.mapValues { (contentId, pair) ->
val cachedItem = cachedNextUpItems[contentId]?.second
pair.first to pair.second.withFallbackMetadata(cachedItem)
}
} else {
cachedNextUpItems
}
}
val continueWatchingItems = remember(
visibleContinueWatchingEntries,
cachedInProgressItems,
effectivNextUpItems,
) {
buildHomeContinueWatchingItems(
visibleEntries = visibleContinueWatchingEntries,
cachedInProgressByVideoId = cachedInProgressItems,
nextUpItemsBySeries = effectivNextUpItems,
)
}
@ -446,14 +460,16 @@ private const val TRAKT_CONTINUE_WATCHING_DAYS_CAP_DEFAULT = 60
internal fun buildHomeContinueWatchingItems(
visibleEntries: List<WatchProgressEntry>,
cachedInProgressByVideoId: Map<String, ContinueWatchingItem> = emptyMap(),
nextUpItemsBySeries: Map<String, Pair<Long, ContinueWatchingItem>>,
): List<ContinueWatchingItem> {
return buildList {
addAll(
visibleEntries.map { entry ->
val liveItem = entry.toContinueWatchingItem()
HomeContinueWatchingCandidate(
lastUpdatedEpochMs = entry.lastUpdatedEpochMs,
item = entry.toContinueWatchingItem(),
item = liveItem.withFallbackMetadata(cachedInProgressByVideoId[entry.videoId]),
isProgressEntry = true,
)
},
@ -579,3 +595,72 @@ private fun CachedNextUpItem.toContinueWatchingItem(): ContinueWatchingItem? {
progressFraction = 0f,
)
}
private fun CachedInProgressItem.toContinueWatchingItem(): ContinueWatchingItem {
val subtitle = if (season != null && episode != null) {
buildString {
append("S")
append(season)
append("E")
append(episode)
episodeTitle?.takeIf { it.isNotBlank() }?.let {
append("")
append(it)
}
}
} else {
"Movie"
}
val explicitResumeProgressFraction = progressPercent
?.takeIf { duration <= 0L && it > 0f }
?.let { (it / 100f).coerceIn(0f, 1f) }
val normalizedProgressFraction = progressPercent
?.let { (it / 100f).coerceIn(0f, 1f) }
?: if (duration > 0L) {
(position.toFloat() / duration.toFloat()).coerceIn(0f, 1f)
} else {
0f
}
return ContinueWatchingItem(
parentMetaId = contentId,
parentMetaType = contentType,
videoId = videoId,
title = name,
subtitle = subtitle,
imageUrl = episodeThumbnail ?: backdrop ?: poster,
logo = logo,
poster = poster,
background = backdrop,
seasonNumber = season,
episodeNumber = episode,
episodeTitle = episodeTitle,
episodeThumbnail = episodeThumbnail,
pauseDescription = pauseDescription,
isNextUp = false,
nextUpSeedSeasonNumber = null,
nextUpSeedEpisodeNumber = null,
resumePositionMs = if (explicitResumeProgressFraction != null) 0L else position,
resumeProgressFraction = explicitResumeProgressFraction,
durationMs = duration,
progressFraction = normalizedProgressFraction,
)
}
private fun ContinueWatchingItem.withFallbackMetadata(
fallback: ContinueWatchingItem?,
): ContinueWatchingItem {
if (fallback == null) return this
return copy(
title = title.ifBlank { fallback.title },
subtitle = subtitle.ifBlank { fallback.subtitle },
imageUrl = imageUrl ?: fallback.imageUrl,
logo = logo ?: fallback.logo,
poster = poster ?: fallback.poster,
background = background ?: fallback.background,
episodeTitle = episodeTitle ?: fallback.episodeTitle,
episodeThumbnail = episodeThumbnail ?: fallback.episodeThumbnail,
pauseDescription = pauseDescription ?: fallback.pauseDescription,
)
}

View file

@ -408,25 +408,6 @@ private fun ContinueWatchingPosterCard(
contentScale = ContentScale.Crop,
)
}
if (item.seasonNumber != null && item.episodeNumber != null) {
Box(
modifier = Modifier
.align(Alignment.BottomStart)
.padding(8.dp)
.clip(RoundedCornerShape(4.dp))
.background(Color.Black)
.padding(horizontal = 6.dp, vertical = 3.dp),
) {
Text(
text = "S${item.seasonNumber} E${item.episodeNumber}",
style = MaterialTheme.typography.labelSmall.copy(
fontSize = layout.posterMetaSize,
fontWeight = FontWeight.SemiBold,
),
color = Color.White,
)
}
}
if (item.progressFraction <= 0f && item.seasonNumber != null && item.episodeNumber != null) {
Box(
modifier = Modifier
@ -480,13 +461,13 @@ private fun ContinueWatchingPosterCard(
overflow = TextOverflow.Ellipsis,
)
}
if (item.progressFraction > 0f) {
if (item.seasonNumber != null && item.episodeNumber != null) {
Text(
text = "${(item.progressFraction * 100).roundToInt()}%",
text = "S${item.seasonNumber} E${item.episodeNumber}",
modifier = Modifier.padding(start = 6.dp),
style = MaterialTheme.typography.labelSmall.copy(
fontSize = layout.progressLabelSize,
fontWeight = FontWeight.Medium,
fontSize = layout.posterMetaSize,
fontWeight = FontWeight.SemiBold,
),
color = MaterialTheme.colorScheme.onSurfaceVariant,
)

View file

@ -12,6 +12,7 @@ import com.nuvio.app.features.watching.sync.ProgressSyncAdapter
import com.nuvio.app.features.watching.sync.SupabaseProgressSyncAdapter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.MutableStateFlow
@ -30,6 +31,7 @@ object WatchProgressRepository {
private var hasLoaded = false
private var currentProfileId: Int = 1
private var entriesByVideoId: MutableMap<String, WatchProgressEntry> = mutableMapOf()
private var metadataResolutionJob: Job? = null
internal var syncAdapter: ProgressSyncAdapter = SupabaseProgressSyncAdapter
init {
@ -72,6 +74,7 @@ object WatchProgressRepository {
}
fun clearLocalState() {
metadataResolutionJob?.cancel()
hasLoaded = false
currentProfileId = 1
entriesByVideoId.clear()
@ -91,6 +94,7 @@ object WatchProgressRepository {
.toMutableMap()
}
publish()
resolveRemoteMetadata()
}
suspend fun pullFromServer(profileId: Int) {
@ -151,12 +155,13 @@ object WatchProgressRepository {
private fun resolveRemoteMetadata() {
val needsResolution = entriesByVideoId.values
.filter { it.poster == null && it.background == null }
.filter { it.poster.isNullOrBlank() || it.background.isNullOrBlank() }
.groupBy { it.parentMetaId to it.contentType }
if (needsResolution.isEmpty()) return
syncScope.launch {
metadataResolutionJob?.cancel()
metadataResolutionJob = syncScope.launch {
withTimeoutOrNull(30_000L) {
AddonRepository.awaitManifestsLoaded()
} ?: run {
@ -355,6 +360,9 @@ object WatchProgressRepository {
}
publish()
if (persist) persist()
if (entry.poster.isNullOrBlank() || entry.background.isNullOrBlank()) {
resolveRemoteMetadata()
}
pushScrobbleToServer(entry)
WatchingActions.onProgressEntryUpdated(entry)
}