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) }, ) } }