From 65817ad73b6660427615fdc02627e9839ccdd626 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Wed, 22 Jul 2026 19:30:45 +0530 Subject: [PATCH] fix(simkl): separate completion from library status --- .../composeResources/values/strings.xml | 1 + .../commonMain/kotlin/com/nuvio/app/App.kt | 4 ++- .../app/core/ui/TrackingListPickerDialog.kt | 6 ++-- .../app/features/details/MetaDetailsScreen.kt | 4 ++- .../app/features/library/LibraryRepository.kt | 11 ++++-- .../library/TrackingMembershipFeedback.kt | 36 +++++++++++++++++++ .../app/features/simkl/SimklLibraryAdapter.kt | 26 ++++++++++---- .../features/simkl/SimklLibraryProjection.kt | 5 +++ .../features/simkl/SimklMutationRepository.kt | 26 ++++++++++++-- .../tracking/TrackingLibraryMembership.kt | 17 +++++++++ .../app/features/tracking/TrackingReads.kt | 7 +++- .../app/features/tracking/TrackingWrites.kt | 8 ++++- .../trakt/TraktTrackingLibraryProvider.kt | 4 ++- .../simkl/SimklMutationRepositoryTest.kt | 3 +- .../features/simkl/SimklProjectionsTest.kt | 3 ++ .../features/tracking/TrackingReadsTest.kt | 19 ++++++++++ 16 files changed, 161 insertions(+), 19 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/TrackingMembershipFeedback.kt create mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingLibraryMembership.kt diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 1083f44c6..e6315c3c9 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -1446,6 +1446,7 @@ Failed to load Trakt lists Failed to update Trakt lists Failed to update tracking lists + %1$s placed this title in %2$s instead of %3$s %1$s • %2$s Update check failed Download failed diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index c6dbd361d..d403ff8bb 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -162,6 +162,7 @@ import com.nuvio.app.features.library.LibraryRepository import com.nuvio.app.features.library.LibrarySection import com.nuvio.app.features.library.LibrarySortOption import com.nuvio.app.features.library.LibrarySourceMode +import com.nuvio.app.features.library.showTrackingMembershipRewriteFeedback import com.nuvio.app.features.library.LibraryScreen import com.nuvio.app.features.library.toLibraryItem import com.nuvio.app.features.library.toMetaPreview @@ -3523,7 +3524,8 @@ private fun MainAppContent( item = item, desiredMembership = pickerMembership, ) - }.onSuccess { + }.onSuccess { result -> + showTrackingMembershipRewriteFeedback(result) showLibraryListPicker = false pickerItem = null pickerError = null diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/TrackingListPickerDialog.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/TrackingListPickerDialog.kt index 135ee9516..f29694de1 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/TrackingListPickerDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/TrackingListPickerDialog.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.nuvio.app.features.tracking.TrackingLibraryTab +import com.nuvio.app.features.tracking.trackingMembershipDestinations import nuvio.composeapp.generated.resources.Res import nuvio.composeapp.generated.resources.action_cancel import nuvio.composeapp.generated.resources.action_save @@ -48,6 +49,7 @@ fun TrackingListPickerDialog( ) { if (!visible) return val tokens = MaterialTheme.nuvio + val destinations = trackingMembershipDestinations(tabs) BasicAlertDialog( onDismissRequest = onDismiss, @@ -80,7 +82,7 @@ fun TrackingListPickerDialog( ) } - if (isPending && tabs.isEmpty()) { + if (isPending && destinations.isEmpty()) { Box( modifier = Modifier .fillMaxWidth() @@ -108,7 +110,7 @@ fun TrackingListPickerDialog( .height(NuvioTokens.Space.s80 + NuvioTokens.Space.s80 + NuvioTokens.Space.s80 + NuvioTokens.Space.s40), verticalArrangement = Arrangement.spacedBy(tokens.spacing.controlGap), ) { - items(items = tabs, key = { it.key }) { tab -> + items(items = destinations, key = { it.key }) { tab -> val selected = membership[tab.key] == true Row( modifier = Modifier diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt index 34d43c36c..314ecf8f4 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt @@ -106,6 +106,7 @@ import com.nuvio.app.features.details.components.SeasonWatchedActionSheet import com.nuvio.app.features.details.components.TrailerPlayerPopup import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.library.LibraryRepository +import com.nuvio.app.features.library.showTrackingMembershipRewriteFeedback import com.nuvio.app.features.library.toLibraryItem import com.nuvio.app.features.player.PlayerSettingsRepository import com.nuvio.app.features.streams.StreamAutoPlayPolicy @@ -1299,7 +1300,8 @@ fun MetaDetailsScreen( item = meta.toLibraryItem(savedAtEpochMs = 0L), desiredMembership = pickerMembership, ) - }.onSuccess { + }.onSuccess { result -> + showTrackingMembershipRewriteFeedback(result) showLibraryListPicker = false }.onFailure { error -> pickerError = error.message ?: getString(Res.string.tracking_lists_update_failed) 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 138149880..7cf306098 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 @@ -12,6 +12,8 @@ import com.nuvio.app.features.profiles.ProfileRepository import com.nuvio.app.features.tracking.TrackingLibraryProvider import com.nuvio.app.features.tracking.TrackingLibraryTab import com.nuvio.app.features.tracking.TrackingLibraryTabKind +import com.nuvio.app.features.tracking.TrackingMembershipApplyResult +import com.nuvio.app.features.tracking.TrackingMembershipResolution import com.nuvio.app.features.tracking.TrackingProviderRegistry import com.nuvio.app.features.tracking.TrackingRefreshIntent import com.nuvio.app.features.tracking.TrackingSettingsRepository @@ -387,7 +389,10 @@ object LibraryRepository { return libraryMembershipWithLocal(inLocal = inLocal, providerMembership = memberships) } - suspend fun applyMembershipChanges(item: LibraryItem, desiredMembership: Map) { + suspend fun applyMembershipChanges( + item: LibraryItem, + desiredMembership: Map, + ): TrackingMembershipApplyResult { ensureLoaded() val localDesired = desiredMembership[LOCAL_LIBRARY_LIST_KEY] == true val currentlyInLocal = localState.contains(item.id, item.type) @@ -406,6 +411,7 @@ object LibraryRepository { } var firstFailure: Throwable? = null + val resolutions = mutableListOf() TrackingProviderRegistry.connectedLibraryProviders().forEach { provider -> val providerListKeys = provider.snapshot().tabs.mapTo(mutableSetOf(), TrackingLibraryTab::key) val providerMembership = desiredMembership.filterKeys(providerListKeys::contains) @@ -415,7 +421,7 @@ object LibraryRepository { profileId = profileId, item = item, desiredMembership = providerMembership, - ) + )?.let(resolutions::add) } catch (error: CancellationException) { throw error } catch (error: Throwable) { @@ -426,6 +432,7 @@ object LibraryRepository { } publish() firstFailure?.let { throw it } + return TrackingMembershipApplyResult(resolutions = resolutions) } suspend fun removeFromList(item: LibraryItem, listKey: String) { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/TrackingMembershipFeedback.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/TrackingMembershipFeedback.kt new file mode 100644 index 000000000..0d83618df --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/TrackingMembershipFeedback.kt @@ -0,0 +1,36 @@ +package com.nuvio.app.features.library + +import com.nuvio.app.core.ui.NuvioToastController +import com.nuvio.app.features.tracking.TrackingLibraryTab +import com.nuvio.app.features.tracking.TrackingMembershipApplyResult +import com.nuvio.app.features.tracking.TrackingProviderRegistry +import nuvio.composeapp.generated.resources.Res +import nuvio.composeapp.generated.resources.tracking_list_status_rewritten +import org.jetbrains.compose.resources.getString + +internal suspend fun showTrackingMembershipRewriteFeedback(result: TrackingMembershipApplyResult) { + val rewrite = result.rewrites.firstOrNull() ?: return + val providerName = TrackingProviderRegistry.authProvider(rewrite.providerId) + ?.descriptor + ?.displayName + ?: rewrite.providerId.storageId.replaceFirstChar { char -> char.titlecase() } + val tabs = TrackingProviderRegistry.libraryProvider(rewrite.providerId)?.snapshot()?.tabs.orEmpty() + val requestedTitle = tabs.statusTitle(rewrite.requestedListKey, providerName) + val resolvedTitle = tabs.statusTitle(rewrite.resolvedListKey, providerName) + NuvioToastController.show( + getString( + Res.string.tracking_list_status_rewritten, + providerName, + resolvedTitle, + requestedTitle, + ), + ) +} + +private fun List.statusTitle( + key: String, + providerName: String, +): String = firstOrNull { tab -> tab.key == key } + ?.title + ?.removePrefix("$providerName ") + ?: key.substringAfterLast(':') diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklLibraryAdapter.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklLibraryAdapter.kt index 674a3baaf..28ace3bb9 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklLibraryAdapter.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklLibraryAdapter.kt @@ -7,6 +7,7 @@ import com.nuvio.app.features.tracking.TrackingLibraryProvider import com.nuvio.app.features.tracking.TrackingLibrarySnapshot import com.nuvio.app.features.tracking.TrackingLibraryTab import com.nuvio.app.features.tracking.TrackingLibraryTabKind +import com.nuvio.app.features.tracking.TrackingMembershipResolution import com.nuvio.app.features.tracking.TrackingProviderId import com.nuvio.app.features.tracking.TrackingRefreshIntent import kotlinx.coroutines.CoroutineScope @@ -67,8 +68,8 @@ object SimklLibraryRepository { profileId: Int, item: LibraryItem, desiredMembership: Map, - ) { - if (profileId != ProfileRepository.activeProfileId) return + ): TrackingMembershipResolution? { + if (profileId != ProfileRepository.activeProfileId) return null ensureLoaded() val desiredStatuses = simklLibraryStatusDefinitions.filter { definition -> desiredMembership[definition.key] == true @@ -80,7 +81,7 @@ object SimklLibraryRepository { }) { "${desiredStatus?.title} does not support ${item.type}" } val currentStatus = findItem(item.id, item.type)?.listKeys.orEmpty() .firstNotNullOfOrNull(::simklLibraryStatusDefinition) - if (desiredStatus == currentStatus) return + if (desiredStatus == currentStatus) return null val snapshot = SimklSyncRepository.state.value.snapshot val media = snapshot.mediaReference( @@ -103,12 +104,25 @@ object SimklLibraryRepository { SimklMutationRepository.removeFromList(profileId = profileId, items = listOf(media)) } - else -> return + else -> return null } check(result.isComplete) { "Simkl could not match ${result.notFoundCount} of ${result.attemptedCount} library items" } + val resolution = desiredStatus?.let { requested -> + result.resolvedListStatuses + .singleOrNull() + ?.let(::simklLibraryStatusDefinition) + ?.let { resolved -> + TrackingMembershipResolution( + providerId = TrackingProviderId.SIMKL, + requestedListKey = requested.key, + resolvedListKey = resolved.key, + ) + } + } refresh(TrackingRefreshIntent.INVALIDATED) + return resolution } private fun publish(syncState: SimklSyncUiState) { @@ -157,6 +171,7 @@ object SimklTrackingLibraryProvider : TrackingLibraryProvider { }, selectionGroup = SIMKL_STATUS_SELECTION_GROUP, supportedContentTypes = definition.supportedContentTypes, + isMembershipDestination = definition.isMembershipDestination, ) }, hasLoaded = state.hasLoaded, @@ -180,13 +195,12 @@ object SimklTrackingLibraryProvider : TrackingLibraryProvider { profileId: Int, item: LibraryItem, desiredMembership: Map, - ) { + ): TrackingMembershipResolution? = SimklLibraryRepository.applyStatusMembership( profileId = profileId, item = item, desiredMembership = desiredMembership, ) - } override suspend fun toggleDefaultMembership(profileId: Int, item: LibraryItem) { val current = SimklLibraryRepository.statusMembership(item.id, item.type) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklLibraryProjection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklLibraryProjection.kt index 497b0f8dd..26b46a0c8 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklLibraryProjection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklLibraryProjection.kt @@ -13,6 +13,7 @@ internal data class SimklLibraryStatusDefinition( val title: String, val trackingStatus: TrackingListStatus, val supportedContentTypes: Set, + val isMembershipDestination: Boolean = true, ) internal data class SimklLibraryProjection( @@ -48,6 +49,7 @@ internal val simklLibraryStatusDefinitions = listOf( title = "Simkl Completed", trackingStatus = TrackingListStatus.COMPLETED, supportedContentTypes = setOf("movie", "series"), + isMembershipDestination = false, ), SimklLibraryStatusDefinition( status = SimklListStatus.DROPPED, @@ -87,6 +89,9 @@ internal fun SimklSyncSnapshot.toSimklLibraryProjection(): SimklLibraryProjectio internal fun simklLibraryStatusDefinition(key: String): SimklLibraryStatusDefinition? = simklLibraryStatusDefinitions.firstOrNull { definition -> definition.key == key } +internal fun simklLibraryStatusDefinition(status: TrackingListStatus): SimklLibraryStatusDefinition? = + simklLibraryStatusDefinitions.firstOrNull { definition -> definition.trackingStatus == status } + private fun SimklLibraryEntry.toLibraryItem( listKey: String, lastSyncedAtEpochMs: Long?, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklMutationRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklMutationRepository.kt index ed73aaee7..5aebd8d17 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklMutationRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklMutationRepository.kt @@ -24,6 +24,7 @@ import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlin.math.round @@ -363,14 +364,33 @@ private fun TrackingExternalIds.toSimklJsonObjectOrNull(): JsonObject? { } private fun SimklApiResponse.toMutationResult(attemptedCount: Int, json: Json): TrackingMutationResult { - val notFound = body + val payload = body .takeIf(String::isNotBlank) - ?.let { payload -> runCatching { json.parseToJsonElement(payload).jsonObject["not_found"]?.jsonObject }.getOrNull() } + ?.let { value -> runCatching { json.parseToJsonElement(value).jsonObject }.getOrNull() } + val notFound = payload + ?.get("not_found") + ?.let { value -> runCatching { value.jsonObject }.getOrNull() } val notFoundCount = notFound ?.values ?.sumOf { value -> (value as? JsonArray)?.size ?: 0 } ?: 0 - return TrackingMutationResult(attemptedCount = attemptedCount, notFoundCount = notFoundCount) + val resolvedListStatuses = payload + ?.get("added") + ?.let { value -> runCatching { value.jsonObject }.getOrNull() } + ?.values + .orEmpty() + .flatMap { value -> (value as? JsonArray).orEmpty() } + .mapNotNull { value -> + val wireValue = runCatching { + value.jsonObject["to"]?.jsonPrimitive?.content + }.getOrNull() + TrackingListStatus.fromWireValue(wireValue) + } + return TrackingMutationResult( + attemptedCount = attemptedCount, + notFoundCount = notFoundCount, + resolvedListStatuses = resolvedListStatuses, + ) } private fun Double.clampAndRoundProgress(): Double = round(coerceIn(0.0, 100.0) * 100.0) / 100.0 diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingLibraryMembership.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingLibraryMembership.kt new file mode 100644 index 000000000..9d72aec4c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingLibraryMembership.kt @@ -0,0 +1,17 @@ +package com.nuvio.app.features.tracking + +data class TrackingMembershipResolution( + val providerId: TrackingProviderId, + val requestedListKey: String, + val resolvedListKey: String, +) { + val wasRewritten: Boolean + get() = requestedListKey != resolvedListKey +} + +data class TrackingMembershipApplyResult( + val resolutions: List = emptyList(), +) { + val rewrites: List + get() = resolutions.filter(TrackingMembershipResolution::wasRewritten) +} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingReads.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingReads.kt index 373109b35..b0d89b665 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingReads.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingReads.kt @@ -20,6 +20,7 @@ data class TrackingLibraryTab( val kind: TrackingLibraryTabKind, val selectionGroup: String? = null, val supportedContentTypes: Set? = null, + val isMembershipDestination: Boolean = true, ) fun TrackingLibraryTab.supportsContentType(contentType: String): Boolean = @@ -27,6 +28,10 @@ fun TrackingLibraryTab.supportsContentType(contentType: String): Boolean = supported.equals(contentType, ignoreCase = true) } +internal fun trackingMembershipDestinations( + tabs: List, +): List = tabs.filter(TrackingLibraryTab::isMembershipDestination) + fun toggleTrackingLibraryMembership( tabs: List, membership: Map, @@ -88,7 +93,7 @@ interface TrackingLibraryProvider { profileId: Int, item: LibraryItem, desiredMembership: Map, - ) + ): TrackingMembershipResolution? suspend fun toggleDefaultMembership(profileId: Int, item: LibraryItem) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingWrites.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingWrites.kt index a3e246815..51a3298b1 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingWrites.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingWrites.kt @@ -5,7 +5,12 @@ enum class TrackingListStatus(val wireValue: String) { PLAN_TO_WATCH("plantowatch"), ON_HOLD("hold"), COMPLETED("completed"), - DROPPED("dropped"), + DROPPED("dropped"); + + companion object { + internal fun fromWireValue(value: String?): TrackingListStatus? = + entries.firstOrNull { status -> status.wireValue.equals(value, ignoreCase = true) } + } } enum class TrackingScrobbleAction(val wireValue: String) { @@ -27,6 +32,7 @@ data class TrackingScrobbleEvent( data class TrackingMutationResult( val attemptedCount: Int, val notFoundCount: Int = 0, + val resolvedListStatuses: List = emptyList(), ) { val isComplete: Boolean get() = notFoundCount == 0 diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktTrackingLibraryProvider.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktTrackingLibraryProvider.kt index a8fe68c8f..d8b2b1aad 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktTrackingLibraryProvider.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktTrackingLibraryProvider.kt @@ -6,6 +6,7 @@ import com.nuvio.app.features.tracking.TrackingLibraryProvider import com.nuvio.app.features.tracking.TrackingLibrarySnapshot import com.nuvio.app.features.tracking.TrackingLibraryTab import com.nuvio.app.features.tracking.TrackingLibraryTabKind +import com.nuvio.app.features.tracking.TrackingMembershipResolution import com.nuvio.app.features.tracking.TrackingProviderId import com.nuvio.app.features.tracking.TrackingRefreshIntent import kotlinx.coroutines.flow.Flow @@ -69,11 +70,12 @@ object TraktTrackingLibraryProvider : TrackingLibraryProvider { profileId: Int, item: LibraryItem, desiredMembership: Map, - ) { + ): TrackingMembershipResolution? { TraktLibraryRepository.applyMembershipChanges( item = item, changes = TraktMembershipChanges(desiredMembership = desiredMembership), ) + return null } override suspend fun toggleDefaultMembership(profileId: Int, item: LibraryItem) = diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/simkl/SimklMutationRepositoryTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/simkl/SimklMutationRepositoryTest.kt index fcde57503..981b73c4c 100644 --- a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/simkl/SimklMutationRepositoryTest.kt +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/simkl/SimklMutationRepositoryTest.kt @@ -105,7 +105,7 @@ class SimklMutationRepositoryTest { val engine = RecordingEngine( response( status = 201, - body = """{"added":{"movies":1},"not_found":{"movies":[{"title":"Missing"}],"shows":[]}}""", + body = """{"added":{"movies":[{"to":"completed"}]},"not_found":{"movies":[{"title":"Missing"}],"shows":[]}}""", ), response(status = 409), ) @@ -134,6 +134,7 @@ class SimklMutationRepositoryTest { assertEquals(2, result.attemptedCount) assertEquals(1, result.notFoundCount) + assertEquals(listOf(TrackingListStatus.COMPLETED), result.resolvedListStatuses) assertFalse(result.isComplete) assertEquals(listOf("/sync/add-to-list", "/scrobble/stop"), engine.paths) assertEquals(2, committed) diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/simkl/SimklProjectionsTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/simkl/SimklProjectionsTest.kt index 6440c724f..8f66b3ee4 100644 --- a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/simkl/SimklProjectionsTest.kt +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/simkl/SimklProjectionsTest.kt @@ -71,6 +71,9 @@ class SimklProjectionsTest { setOf(watchingDefinition.key), projection.items.single { candidate -> candidate.id == "tt1520211" }.listKeys, ) + assertTrue(watchingDefinition.isMembershipDestination) + assertTrue(planDefinition.isMembershipDestination) + assertFalse(completedDefinition.isMembershipDestination) } @Test diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/tracking/TrackingReadsTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/tracking/TrackingReadsTest.kt index 030ce7d5b..ae4e17ff1 100644 --- a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/tracking/TrackingReadsTest.kt +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/tracking/TrackingReadsTest.kt @@ -53,6 +53,25 @@ class TrackingReadsTest { assertTrue(tab("trakt:watchlist").supportsContentType("movie")) } + @Test + fun `non destination status stays in selection group without appearing in picker`() { + val watching = tab("simkl:watching", selectionGroup = "simkl:status") + val completed = tab("simkl:completed", selectionGroup = "simkl:status") + .copy(isMembershipDestination = false) + val tabs = listOf(watching, completed) + + assertEquals(listOf(watching), trackingMembershipDestinations(tabs)) + + val updated = toggleTrackingLibraryMembership( + tabs = tabs, + membership = mapOf(watching.key to false, completed.key to true), + key = watching.key, + ) + + assertEquals(true, updated[watching.key]) + assertEquals(false, updated[completed.key]) + } + private fun tab( key: String, providerId: TrackingProviderId? = TrackingProviderId.SIMKL,