mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-19 22:07:14 +00:00
ref: cw ui an caching updatges
This commit is contained in:
parent
55bd4a4bfe
commit
013a179429
3 changed files with 101 additions and 27 deletions
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue