From daedf05ea516a1661550c9032d3deac384a4507b Mon Sep 17 00:00:00 2001
From: tapframe <85391825+tapframe@users.noreply.github.com>
Date: Sat, 9 May 2026 01:18:21 +0530
Subject: [PATCH] feat: support for trakt library items removal from library
screen
fixes #962
---
.../composeResources/values/strings.xml | 1 +
.../app/features/library/LibraryRepository.kt | 16 +++++
.../app/features/library/LibraryScreen.kt | 59 +++++++++++++++----
3 files changed, 64 insertions(+), 12 deletions(-)
diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index 78461786..55d0bbf0 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -1114,6 +1114,7 @@
Download failed
Paused %1$s
Remove
+ Remove %1$s from %2$s?
Remove %1$s from your library?
Remove from Library?
Movie
diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryRepository.kt
index c93d5caa..46c2acdc 100644
--- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryRepository.kt
+++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryRepository.kt
@@ -296,6 +296,14 @@ object LibraryRepository {
}
}
+ suspend fun removeFromList(item: LibraryItem, listKey: String) {
+ val desiredMembership = libraryMembershipWithRemovedList(
+ currentMembership = getMembershipSnapshot(item),
+ listKey = listKey,
+ )
+ applyMembershipChanges(item, desiredMembership)
+ }
+
private fun pushToServer() {
syncScope.launch {
runCatching {
@@ -417,6 +425,14 @@ internal fun libraryMembershipWithLocal(
putAll(traktMembership)
}
+internal fun libraryMembershipWithRemovedList(
+ currentMembership: Map,
+ listKey: String,
+): Map =
+ currentMembership.toMutableMap().apply {
+ this[listKey] = false
+ }
+
private fun LibrarySyncItem.toLibraryItem(): LibraryItem = LibraryItem(
id = contentId,
type = contentType,
diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt
index efe6ded9..4a8f78c3 100644
--- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt
+++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt
@@ -25,6 +25,7 @@ import com.nuvio.app.core.ui.NuvioScreen
import com.nuvio.app.core.ui.NuvioNetworkOfflineCard
import com.nuvio.app.core.ui.NuvioScreenHeader
import com.nuvio.app.core.ui.NuvioStatusModal
+import com.nuvio.app.core.ui.NuvioToastController
import com.nuvio.app.core.ui.NuvioViewAllPillSize
import com.nuvio.app.core.ui.NuvioShelfSection
import com.nuvio.app.features.home.components.HomeEmptyStateCard
@@ -33,8 +34,15 @@ import com.nuvio.app.features.home.components.HomeSkeletonRow
import com.nuvio.app.features.profiles.ProfileRepository
import kotlinx.coroutines.launch
import nuvio.composeapp.generated.resources.*
+import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.stringResource
+private data class LibraryRemovalTarget(
+ val item: LibraryItem,
+ val listKey: String? = null,
+ val listTitle: String? = null,
+)
+
@Composable
fun LibraryScreen(
modifier: Modifier = Modifier,
@@ -46,7 +54,7 @@ fun LibraryScreen(
LibraryRepository.uiState
}.collectAsStateWithLifecycle()
val networkStatusUiState by NetworkStatusRepository.uiState.collectAsStateWithLifecycle()
- var pendingRemovalItem by remember { mutableStateOf(null) }
+ var pendingRemovalTarget by remember { mutableStateOf(null) }
var observedOfflineState by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val isTraktSource = uiState.sourceMode == LibrarySourceMode.TRAKT
@@ -165,9 +173,15 @@ fun LibraryScreen(
sections = uiState.sections,
onPosterClick = onPosterClick,
onSectionViewAllClick = onSectionViewAllClick,
- onPosterLongClick = { item ->
- if (!isTraktSource) {
- pendingRemovalItem = item
+ onPosterLongClick = { item, section ->
+ pendingRemovalTarget = if (isTraktSource) {
+ LibraryRemovalTarget(
+ item = item,
+ listKey = section.type,
+ listTitle = section.displayTitle,
+ )
+ } else {
+ LibraryRemovalTarget(item = item)
}
},
)
@@ -177,17 +191,38 @@ fun LibraryScreen(
NuvioStatusModal(
title = stringResource(Res.string.library_remove_title),
- message = pendingRemovalItem?.let {
- stringResource(Res.string.library_remove_message, it.name)
+ message = pendingRemovalTarget?.let { target ->
+ val listTitle = target.listTitle
+ if (listTitle.isNullOrBlank()) {
+ stringResource(Res.string.library_remove_message, target.item.name)
+ } else {
+ stringResource(Res.string.library_remove_from_list_message, target.item.name, listTitle)
+ }
}.orEmpty(),
- isVisible = pendingRemovalItem != null,
+ isVisible = pendingRemovalTarget != null,
confirmText = stringResource(Res.string.library_remove_confirm),
dismissText = stringResource(Res.string.action_cancel),
onConfirm = {
- pendingRemovalItem?.id?.let(LibraryRepository::remove)
- pendingRemovalItem = null
+ val target = pendingRemovalTarget
+ pendingRemovalTarget = null
+ target?.let {
+ val listKey = target.listKey
+ if (listKey.isNullOrBlank()) {
+ LibraryRepository.remove(target.item.id)
+ } else {
+ coroutineScope.launch {
+ runCatching {
+ LibraryRepository.removeFromList(target.item, listKey)
+ }.onFailure { error ->
+ NuvioToastController.show(
+ error.message ?: getString(Res.string.trakt_lists_update_failed),
+ )
+ }
+ }
+ }
+ }
},
- onDismiss = { pendingRemovalItem = null },
+ onDismiss = { pendingRemovalTarget = null },
)
}
@@ -195,7 +230,7 @@ private fun LazyListScope.librarySections(
sections: List,
onPosterClick: ((LibraryItem) -> Unit)?,
onSectionViewAllClick: ((LibrarySection) -> Unit)?,
- onPosterLongClick: (LibraryItem) -> Unit,
+ onPosterLongClick: (LibraryItem, LibrarySection) -> Unit,
) {
items(
items = sections,
@@ -218,7 +253,7 @@ private fun LazyListScope.librarySections(
HomePosterCard(
item = item.toMetaPreview(),
onClick = onPosterClick?.let { { it(item) } },
- onLongClick = { onPosterLongClick(item) },
+ onLongClick = { onPosterLongClick(item, section) },
)
}
}