added removal of next up cards

This commit is contained in:
tapframe 2026-02-16 13:03:12 +05:30
parent f30a9277c2
commit 2df29bf988
6 changed files with 105 additions and 44 deletions

View file

@ -40,7 +40,7 @@ fun ClassicHomeContent(
onNavigateToDetail: (String, String, String) -> Unit,
onContinueWatchingClick: (ContinueWatchingItem) -> Unit,
onNavigateToCatalogSeeAll: (String, String, String) -> Unit,
onRemoveContinueWatching: (String, Int?, Int?) -> Unit,
onRemoveContinueWatching: (String, Int?, Int?, Boolean) -> Unit,
onRequestTrailerPreview: (MetaPreview) -> Unit,
onSaveFocusState: (Int, Int, Int, Int, Map<String, Int>) -> Unit
) {
@ -156,7 +156,8 @@ fun ClassicHomeContent(
is ContinueWatchingItem.InProgress -> item.progress.episode
is ContinueWatchingItem.NextUp -> item.info.episode
}
onRemoveContinueWatching(contentId, season, episode)
val isNextUp = item is ContinueWatchingItem.NextUp
onRemoveContinueWatching(contentId, season, episode, isNextUp)
},
focusedItemIndex = when {
focusState.hasSavedFocus && focusState.focusedRowIndex == -1 -> focusState.focusedItemIndex

View file

@ -65,7 +65,7 @@ fun GridHomeContent(
onNavigateToDetail: (String, String, String) -> Unit,
onContinueWatchingClick: (ContinueWatchingItem) -> Unit,
onNavigateToCatalogSeeAll: (String, String, String) -> Unit,
onRemoveContinueWatching: (String, Int?, Int?) -> Unit,
onRemoveContinueWatching: (String, Int?, Int?, Boolean) -> Unit,
posterCardStyle: PosterCardStyle = PosterCardDefaults.Style,
onSaveGridFocusState: (Int, Int) -> Unit
) {
@ -232,7 +232,8 @@ fun GridHomeContent(
is ContinueWatchingItem.InProgress -> item.progress.episode
is ContinueWatchingItem.NextUp -> item.info.episode
}
onRemoveContinueWatching(contentId, season, episode)
val isNextUp = item is ContinueWatchingItem.NextUp
onRemoveContinueWatching(contentId, season, episode, isNextUp)
}
)
}
@ -353,7 +354,8 @@ fun GridHomeContent(
is ContinueWatchingItem.InProgress -> item.progress.episode
is ContinueWatchingItem.NextUp -> item.info.episode
}
onRemoveContinueWatching(contentId, season, episode)
val isNextUp = item is ContinueWatchingItem.NextUp
onRemoveContinueWatching(contentId, season, episode, isNextUp)
}
)
}

View file

@ -145,8 +145,8 @@ fun HomeScreen(
onNavigateToDetail = onNavigateToDetail,
onContinueWatchingClick = onContinueWatchingClick,
onNavigateToCatalogSeeAll = onNavigateToCatalogSeeAll,
onRemoveContinueWatching = { contentId, season, episode ->
viewModel.onEvent(HomeEvent.OnRemoveContinueWatching(contentId, season, episode))
onRemoveContinueWatching = { contentId, season, episode, isNextUp ->
viewModel.onEvent(HomeEvent.OnRemoveContinueWatching(contentId, season, episode, isNextUp))
},
onRequestTrailerPreview = { item ->
viewModel.requestTrailerPreview(item)
@ -162,8 +162,8 @@ fun HomeScreen(
onNavigateToDetail = onNavigateToDetail,
onContinueWatchingClick = onContinueWatchingClick,
onNavigateToCatalogSeeAll = onNavigateToCatalogSeeAll,
onRemoveContinueWatching = { contentId, season, episode ->
viewModel.onEvent(HomeEvent.OnRemoveContinueWatching(contentId, season, episode))
onRemoveContinueWatching = { contentId, season, episode, isNextUp ->
viewModel.onEvent(HomeEvent.OnRemoveContinueWatching(contentId, season, episode, isNextUp))
},
onSaveGridFocusState = { vi, vo ->
viewModel.saveGridFocusState(vi, vo)

View file

@ -86,6 +86,11 @@ sealed class GridItem {
sealed class HomeEvent {
data class OnItemClick(val itemId: String, val itemType: String) : HomeEvent()
data class OnLoadMoreCatalog(val catalogId: String, val addonId: String, val type: String) : HomeEvent()
data class OnRemoveContinueWatching(val contentId: String, val season: Int? = null, val episode: Int? = null) : HomeEvent()
data class OnRemoveContinueWatching(
val contentId: String,
val season: Int? = null,
val episode: Int? = null,
val isNextUp: Boolean = false
) : HomeEvent()
data object OnRetry : HomeEvent()
}

View file

@ -96,6 +96,7 @@ class HomeViewModel @Inject constructor(
private val trailerPreviewUrlsState = mutableStateMapOf<String, String>()
private var activeTrailerPreviewItemId: String? = null
private var trailerPreviewRequestVersion: Long = 0L
private val dismissedNextUpKeys = MutableStateFlow<Set<String>>(emptySet())
val trailerPreviewUrls: Map<String, String>
get() = trailerPreviewUrlsState
@ -288,7 +289,13 @@ class HomeViewModel @Inject constructor(
when (event) {
is HomeEvent.OnItemClick -> navigateToDetail(event.itemId, event.itemType)
is HomeEvent.OnLoadMoreCatalog -> loadMoreCatalogItems(event.catalogId, event.addonId, event.type)
is HomeEvent.OnRemoveContinueWatching -> removeContinueWatching(event.contentId, event.season, event.episode)
is HomeEvent.OnRemoveContinueWatching ->
removeContinueWatching(
contentId = event.contentId,
season = event.season,
episode = event.episode,
isNextUp = event.isNextUp
)
HomeEvent.OnRetry -> viewModelScope.launch { loadAllCatalogs(addonsCache) }
}
}
@ -297,10 +304,11 @@ class HomeViewModel @Inject constructor(
viewModelScope.launch {
combine(
watchProgressRepository.allProgress,
traktSettingsDataStore.continueWatchingDaysCap
) { items, daysCap ->
items to daysCap
}.collectLatest { (items, daysCap) ->
traktSettingsDataStore.continueWatchingDaysCap,
dismissedNextUpKeys
) { items, daysCap, dismissedNextUp ->
Triple(items, daysCap, dismissedNextUp)
}.collectLatest { (items, daysCap, dismissedNextUp) ->
val windowMs = daysCap.toLong() * 24L * 60L * 60L * 1000L
val cutoffMs = System.currentTimeMillis() - windowMs
val recentItems = items
@ -324,7 +332,8 @@ class HomeViewModel @Inject constructor(
// Then enrich Next Up in background with bounded concurrency.
enrichContinueWatchingProgressively(
allProgress = recentItems,
inProgressItems = inProgressOnly
inProgressItems = inProgressOnly,
dismissedNextUp = dismissedNextUp
)
}
}
@ -340,7 +349,8 @@ class HomeViewModel @Inject constructor(
private suspend fun enrichContinueWatchingProgressively(
allProgress: List<WatchProgress>,
inProgressItems: List<ContinueWatchingItem.InProgress>
inProgressItems: List<ContinueWatchingItem.InProgress>,
dismissedNextUp: Set<String>
) = coroutineScope {
val inProgressIds = inProgressItems.map { it.progress.contentId }.toSet()
@ -355,6 +365,7 @@ class HomeViewModel @Inject constructor(
.groupBy { it.contentId }
.mapNotNull { (_, items) -> items.maxByOrNull { it.lastWatched } }
.filter { it.contentId !in inProgressIds }
.filter { progress -> nextUpDismissKey(progress.contentId) !in dismissedNextUp }
.sortedByDescending { it.lastWatched }
.take(MAX_NEXT_UP_LOOKUPS)
@ -465,7 +476,29 @@ class HomeViewModel @Inject constructor(
return type == "series" || type == "tv"
}
private fun removeContinueWatching(contentId: String, season: Int? = null, episode: Int? = null) {
private fun removeContinueWatching(
contentId: String,
season: Int? = null,
episode: Int? = null,
isNextUp: Boolean = false
) {
if (isNextUp) {
val dismissKey = nextUpDismissKey(contentId)
dismissedNextUpKeys.update { it + dismissKey }
_uiState.update { state ->
state.copy(
continueWatchingItems = state.continueWatchingItems.filterNot { item ->
when (item) {
is ContinueWatchingItem.NextUp ->
nextUpDismissKey(item.info.contentId) == dismissKey
is ContinueWatchingItem.InProgress -> false
}
}
)
}
return
}
viewModelScope.launch {
Log.d(
TAG,
@ -475,6 +508,8 @@ class HomeViewModel @Inject constructor(
}
}
private fun nextUpDismissKey(contentId: String): String = contentId
private fun observeInstalledAddons() {
viewModelScope.launch {
addonRepository.getInstalledAddons()

View file

@ -240,12 +240,14 @@ fun TraktScreen(
}
}
SettingsActionRow(
title = "Continue Watching Window",
subtitle = "Days of Trakt progress considered for continue watching",
value = "${uiState.continueWatchingDaysCap} days",
onClick = { showDaysCapDialog = true }
)
if (uiState.mode == TraktConnectionMode.CONNECTED) {
SettingsActionRow(
title = "Continue Watching Window",
subtitle = "Days of Trakt progress considered for continue watching",
value = "${uiState.continueWatchingDaysCap} days",
onClick = { showDaysCapDialog = true }
)
}
if (uiState.mode != TraktConnectionMode.CONNECTED) {
uiState.statusMessage?.let { status ->
@ -294,7 +296,7 @@ fun TraktScreen(
Dialog(onDismissRequest = { showDaysCapDialog = false }) {
Column(
modifier = Modifier
.width(560.dp)
.width(620.dp)
.background(NuvioColors.BackgroundElevated, RoundedCornerShape(16.dp))
.border(1.dp, NuvioColors.Border, RoundedCornerShape(16.dp))
.padding(24.dp),
@ -311,30 +313,46 @@ fun TraktScreen(
color = NuvioColors.TextSecondary
)
continueWatchingDayOptions.forEach { days ->
val selected = uiState.continueWatchingDaysCap == days
Button(
onClick = {
viewModel.onContinueWatchingDaysCapSelected(days)
showDaysCapDialog = false
},
colors = ButtonDefaults.colors(
containerColor = if (selected) NuvioColors.Primary else NuvioColors.BackgroundCard,
contentColor = if (selected) Color.Black else NuvioColors.TextPrimary
)
continueWatchingDayOptions.chunked(2).forEach { rowOptions ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Text("$days days")
rowOptions.forEach { days ->
val selected = uiState.continueWatchingDaysCap == days
Button(
onClick = {
viewModel.onContinueWatchingDaysCapSelected(days)
showDaysCapDialog = false
},
modifier = Modifier.weight(1f),
colors = ButtonDefaults.colors(
containerColor = if (selected) NuvioColors.Primary else NuvioColors.BackgroundCard,
contentColor = if (selected) Color.Black else NuvioColors.TextPrimary
)
) {
Text("$days days")
}
}
if (rowOptions.size == 1) {
Spacer(modifier = Modifier.weight(1f))
}
}
}
Button(
onClick = { showDaysCapDialog = false },
colors = ButtonDefaults.colors(
containerColor = NuvioColors.BackgroundCard,
contentColor = NuvioColors.TextPrimary
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
Text("Cancel")
Button(
onClick = { showDaysCapDialog = false },
colors = ButtonDefaults.colors(
containerColor = NuvioColors.BackgroundCard,
contentColor = NuvioColors.TextPrimary
)
) {
Text("Cancel")
}
}
}
}