mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-14 19:40:23 +00:00
refactor(tracking): register watched read providers
This commit is contained in:
parent
410a9d4c4b
commit
617bfe298e
10 changed files with 180 additions and 221 deletions
|
|
@ -5,11 +5,13 @@ import com.nuvio.app.features.simkl.SimklMutationRepository
|
|||
import com.nuvio.app.features.simkl.SimklLibraryRepository
|
||||
import com.nuvio.app.features.simkl.SimklProgressRepository
|
||||
import com.nuvio.app.features.simkl.SimklTrackingLibraryProvider
|
||||
import com.nuvio.app.features.simkl.SimklWatchedSyncAdapter
|
||||
import com.nuvio.app.features.simkl.SimklSyncRepository
|
||||
import com.nuvio.app.features.tracking.TrackingProviderRegistry
|
||||
import com.nuvio.app.features.trakt.TraktAuthRepository
|
||||
import com.nuvio.app.features.trakt.TraktScrobbleRepository
|
||||
import com.nuvio.app.features.trakt.TraktTrackingLibraryProvider
|
||||
import com.nuvio.app.features.watching.sync.TraktWatchedSyncAdapter
|
||||
|
||||
fun ensureTrackingProvidersRegistered() {
|
||||
TraktAuthRepository.descriptor
|
||||
|
|
@ -21,4 +23,6 @@ fun ensureTrackingProvidersRegistered() {
|
|||
SimklMutationRepository.ensureRegistered()
|
||||
TrackingProviderRegistry.registerLibraryProvider(TraktTrackingLibraryProvider)
|
||||
TrackingProviderRegistry.registerLibraryProvider(SimklTrackingLibraryProvider)
|
||||
TrackingProviderRegistry.registerWatchedProvider(TraktWatchedSyncAdapter)
|
||||
TrackingProviderRegistry.registerWatchedProvider(SimklWatchedSyncAdapter)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import com.nuvio.app.features.tracking.TrackingLibraryTab
|
|||
import com.nuvio.app.features.tracking.TrackingLibraryTabKind
|
||||
import com.nuvio.app.features.tracking.TrackingListStatus
|
||||
import com.nuvio.app.features.tracking.TrackingProviderId
|
||||
import com.nuvio.app.features.tracking.TrackingWatchedProvider
|
||||
import com.nuvio.app.features.watched.WatchedItem
|
||||
import com.nuvio.app.features.watching.sync.WatchedSyncAdapter
|
||||
import com.nuvio.app.features.watchprogress.WatchProgressEntry
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -171,7 +171,8 @@ object SimklTrackingLibraryProvider : TrackingLibraryProvider {
|
|||
}
|
||||
}
|
||||
|
||||
object SimklWatchedSyncAdapter : WatchedSyncAdapter {
|
||||
object SimklWatchedSyncAdapter : TrackingWatchedProvider {
|
||||
override val providerId: TrackingProviderId = TrackingProviderId.SIMKL
|
||||
override suspend fun pull(profileId: Int, pageSize: Int): List<WatchedItem> {
|
||||
if (profileId != ProfileRepository.activeProfileId) return emptyList()
|
||||
SimklSyncRepository.ensureFresh()
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ object TrackingProviderRegistry {
|
|||
private val historyWriters = mutableMapOf<TrackingProviderId, TrackingHistoryWriter>()
|
||||
private val scrobblers = mutableMapOf<TrackingProviderId, TrackingScrobbler>()
|
||||
private val libraryProviders = mutableMapOf<TrackingProviderId, TrackingLibraryProvider>()
|
||||
private val watchedProviders = mutableMapOf<TrackingProviderId, TrackingWatchedProvider>()
|
||||
|
||||
private val _connectedProviderIds = MutableStateFlow<Set<TrackingProviderId>>(emptySet())
|
||||
val connectedProviderIds: StateFlow<Set<TrackingProviderId>> = _connectedProviderIds.asStateFlow()
|
||||
|
|
@ -118,6 +119,10 @@ object TrackingProviderRegistry {
|
|||
libraryProviders[provider.providerId] = provider
|
||||
}
|
||||
|
||||
fun registerWatchedProvider(provider: TrackingWatchedProvider) = synchronized(lock) {
|
||||
watchedProviders[provider.providerId] = provider
|
||||
}
|
||||
|
||||
fun authProvider(id: TrackingProviderId): TrackingAuthProvider? = synchronized(lock) {
|
||||
authProviders[id]
|
||||
}
|
||||
|
|
@ -159,6 +164,10 @@ object TrackingProviderRegistry {
|
|||
.map { (_, provider) -> provider }
|
||||
}
|
||||
|
||||
fun watchedProvider(id: TrackingProviderId): TrackingWatchedProvider? = synchronized(lock) {
|
||||
watchedProviders[id]
|
||||
}
|
||||
|
||||
fun connectedListWriters(): List<TrackingListWriter> =
|
||||
connectedPorts(listWriters, TrackingCapability.LIBRARY_WRITE)
|
||||
|
||||
|
|
@ -171,6 +180,9 @@ object TrackingProviderRegistry {
|
|||
fun connectedLibraryProviders(): List<TrackingLibraryProvider> =
|
||||
connectedPorts(libraryProviders, TrackingCapability.LIBRARY_READ)
|
||||
|
||||
fun connectedWatchedProviders(): List<TrackingWatchedProvider> =
|
||||
connectedPorts(watchedProviders, TrackingCapability.WATCHED_READ)
|
||||
|
||||
fun handleAuthCallback(url: String): Boolean =
|
||||
providersWith(TrackingCapability.AUTHENTICATION)
|
||||
.any { provider -> provider.handleAuthCallback(url) }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.nuvio.app.features.tracking
|
|||
|
||||
import com.nuvio.app.features.library.LibraryItem
|
||||
import com.nuvio.app.features.library.LibrarySection
|
||||
import com.nuvio.app.features.watching.sync.WatchedSyncAdapter
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
enum class TrackingLibraryTabKind {
|
||||
|
|
@ -51,3 +52,8 @@ interface TrackingLibraryProvider {
|
|||
)
|
||||
suspend fun toggleDefaultMembership(profileId: Int, item: LibraryItem)
|
||||
}
|
||||
|
||||
/** Provider adapter for watched-history projection and explicit history mutations. */
|
||||
interface TrackingWatchedProvider : WatchedSyncAdapter {
|
||||
val providerId: TrackingProviderId
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ object TrackingSettingsRepository {
|
|||
fun setLibrarySourceMode(source: LibrarySourceMode) =
|
||||
TraktSettingsRepository.setLibrarySourceMode(source)
|
||||
|
||||
fun setWatchProgressSource(source: WatchProgressSource, profileId: Int) =
|
||||
TraktSettingsRepository.setWatchProgressSource(source, profileId)
|
||||
|
||||
fun setContinueWatchingDaysCap(days: Int) =
|
||||
TraktSettingsRepository.setContinueWatchingDaysCap(days)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,14 +7,13 @@ import com.nuvio.app.core.tracking.ensureTrackingProvidersRegistered
|
|||
import com.nuvio.app.features.details.MetaDetails
|
||||
import com.nuvio.app.features.details.MetaVideo
|
||||
import com.nuvio.app.features.profiles.ProfileRepository
|
||||
import com.nuvio.app.features.simkl.SimklWatchedSyncAdapter
|
||||
import com.nuvio.app.features.tracking.TrackingProviderId
|
||||
import com.nuvio.app.features.tracking.TrackingProviderRegistry
|
||||
import com.nuvio.app.features.tracking.TrackingSettingsRepository
|
||||
import com.nuvio.app.features.tracking.WatchProgressSource
|
||||
import com.nuvio.app.features.tracking.effectiveWatchProgressSource
|
||||
import com.nuvio.app.features.trakt.TraktSettingsRepository
|
||||
import com.nuvio.app.features.tracking.providerId
|
||||
import com.nuvio.app.features.watching.sync.SupabaseWatchedSyncAdapter
|
||||
import com.nuvio.app.features.watching.sync.TraktWatchedSyncAdapter
|
||||
import com.nuvio.app.features.watching.sync.WatchedDeltaEvent
|
||||
import com.nuvio.app.features.watching.sync.WatchedSyncAdapter
|
||||
import kotlinx.atomicfu.locks.SynchronizedObject
|
||||
|
|
@ -43,15 +42,15 @@ private data class StoredWatchedPayload(
|
|||
val dirtyWatchedKeys: Set<String> = emptySet(),
|
||||
)
|
||||
|
||||
internal enum class WatchedTraktHistorySync {
|
||||
internal enum class WatchedTrackerHistorySync {
|
||||
Mirror,
|
||||
Skip,
|
||||
}
|
||||
|
||||
internal fun shouldMirrorWatchedMarkToTraktHistory(
|
||||
sync: WatchedTraktHistorySync,
|
||||
isTraktAuthenticated: Boolean,
|
||||
): Boolean = sync == WatchedTraktHistorySync.Mirror && isTraktAuthenticated
|
||||
internal fun shouldMirrorWatchedMarkToTrackers(
|
||||
sync: WatchedTrackerHistorySync,
|
||||
hasConnectedTracker: Boolean,
|
||||
): Boolean = sync == WatchedTrackerHistorySync.Mirror && hasConnectedTracker
|
||||
|
||||
internal data class WatchedSourceOperation(
|
||||
val source: WatchProgressSource,
|
||||
|
|
@ -67,29 +66,23 @@ internal fun isWatchedSourceOperationCurrent(
|
|||
internal fun watchedItemsForSource(
|
||||
source: WatchProgressSource,
|
||||
nuvioItems: Collection<WatchedItem>,
|
||||
traktItems: Collection<WatchedItem>,
|
||||
simklItems: Collection<WatchedItem> = emptyList(),
|
||||
): Collection<WatchedItem> = when (source) {
|
||||
WatchProgressSource.NUVIO_SYNC -> nuvioItems
|
||||
WatchProgressSource.TRAKT -> traktItems
|
||||
WatchProgressSource.SIMKL -> simklItems
|
||||
}
|
||||
providerItems: Map<TrackingProviderId, Collection<WatchedItem>>,
|
||||
): Collection<WatchedItem> = source.providerId
|
||||
?.let { providerId -> providerItems[providerId].orEmpty() }
|
||||
?: nuvioItems
|
||||
|
||||
internal fun shouldPersistWatchedSource(source: WatchProgressSource): Boolean =
|
||||
source == WatchProgressSource.NUVIO_SYNC
|
||||
source.providerId == null
|
||||
|
||||
internal fun replaceWatchedItemsForSource(
|
||||
source: WatchProgressSource,
|
||||
nuvioItems: MutableMap<String, WatchedItem>,
|
||||
traktItems: MutableMap<String, WatchedItem>,
|
||||
simklItems: MutableMap<String, WatchedItem>,
|
||||
providerItems: MutableMap<TrackingProviderId, MutableMap<String, WatchedItem>>,
|
||||
replacement: Map<String, WatchedItem>,
|
||||
) {
|
||||
val target = when (source) {
|
||||
WatchProgressSource.NUVIO_SYNC -> nuvioItems
|
||||
WatchProgressSource.TRAKT -> traktItems
|
||||
WatchProgressSource.SIMKL -> simklItems
|
||||
}
|
||||
val target = source.providerId
|
||||
?.let { providerId -> providerItems.getOrPut(providerId, ::mutableMapOf) }
|
||||
?: nuvioItems
|
||||
target.clear()
|
||||
target.putAll(replacement)
|
||||
}
|
||||
|
|
@ -126,34 +119,28 @@ object WatchedRepository {
|
|||
private var activeSource: WatchProgressSource = WatchProgressSource.NUVIO_SYNC
|
||||
private var sourceGeneration: Long = 0L
|
||||
private var nuvioItemsByKey: MutableMap<String, WatchedItem> = mutableMapOf()
|
||||
private var traktItemsByKey: MutableMap<String, WatchedItem> = mutableMapOf()
|
||||
private var simklItemsByKey: MutableMap<String, WatchedItem> = mutableMapOf()
|
||||
private var providerItemsByKey: MutableMap<TrackingProviderId, MutableMap<String, WatchedItem>> = mutableMapOf()
|
||||
private var nuvioFullyWatchedSeriesKeys: Set<String> = emptySet()
|
||||
private var traktFullyWatchedSeriesKeys: Set<String> = emptySet()
|
||||
private var simklFullyWatchedSeriesKeys: Set<String> = emptySet()
|
||||
private var providerFullyWatchedSeriesKeys: MutableMap<TrackingProviderId, Set<String>> = mutableMapOf()
|
||||
private var nuvioHasLoaded: Boolean = false
|
||||
private var traktHasLoaded: Boolean = false
|
||||
private var simklHasLoaded: Boolean = false
|
||||
private var loadedProviders: MutableSet<TrackingProviderId> = mutableSetOf()
|
||||
private var nuvioHasLoadedRemote: Boolean = false
|
||||
private var traktHasLoadedRemote: Boolean = false
|
||||
private var simklHasLoadedRemote: Boolean = false
|
||||
private var providersLoadedFromRemote: MutableSet<TrackingProviderId> = mutableSetOf()
|
||||
private var nuvioDirtyWatchedKeys: MutableSet<String> = mutableSetOf()
|
||||
private var lastSuccessfulPushEpochMs: Long = 0L
|
||||
private var deltaCursorEventId: Long = 0L
|
||||
private var deltaInitialized: Boolean = false
|
||||
internal var syncAdapter: WatchedSyncAdapter = SupabaseWatchedSyncAdapter
|
||||
internal var traktSyncAdapter: WatchedSyncAdapter = TraktWatchedSyncAdapter
|
||||
internal var simklSyncAdapter: WatchedSyncAdapter = SimklWatchedSyncAdapter
|
||||
|
||||
fun ensureLoaded() {
|
||||
ensureTrackingProvidersRegistered()
|
||||
TrackingProviderRegistry.ensureLoaded()
|
||||
TraktSettingsRepository.ensureLoaded()
|
||||
TrackingSettingsRepository.ensureLoaded()
|
||||
if (!hasLoaded) {
|
||||
loadFromDisk(ProfileRepository.activeProfileId)
|
||||
activateEffectiveSource(
|
||||
effectiveWatchedSource(
|
||||
requestedSource = TraktSettingsRepository.uiState.value.watchProgressSource,
|
||||
requestedSource = TrackingSettingsRepository.uiState.value.watchProgressSource,
|
||||
connectedProviderIds = TrackingProviderRegistry.connectedProviderIdsSnapshot(),
|
||||
),
|
||||
)
|
||||
|
|
@ -179,17 +166,13 @@ object WatchedRepository {
|
|||
activeSource = WatchProgressSource.NUVIO_SYNC
|
||||
sourceGeneration += 1L
|
||||
nuvioItemsByKey.clear()
|
||||
traktItemsByKey.clear()
|
||||
simklItemsByKey.clear()
|
||||
providerItemsByKey.clear()
|
||||
nuvioFullyWatchedSeriesKeys = emptySet()
|
||||
traktFullyWatchedSeriesKeys = emptySet()
|
||||
simklFullyWatchedSeriesKeys = emptySet()
|
||||
providerFullyWatchedSeriesKeys.clear()
|
||||
nuvioHasLoaded = false
|
||||
traktHasLoaded = false
|
||||
simklHasLoaded = false
|
||||
loadedProviders.clear()
|
||||
nuvioHasLoadedRemote = false
|
||||
traktHasLoadedRemote = false
|
||||
simklHasLoadedRemote = false
|
||||
providersLoadedFromRemote.clear()
|
||||
nuvioDirtyWatchedKeys.clear()
|
||||
lastSuccessfulPushEpochMs = 0L
|
||||
deltaCursorEventId = 0L
|
||||
|
|
@ -205,17 +188,13 @@ object WatchedRepository {
|
|||
sourceGeneration += 1L
|
||||
hasLoaded = true
|
||||
nuvioItemsByKey.clear()
|
||||
traktItemsByKey.clear()
|
||||
simklItemsByKey.clear()
|
||||
providerItemsByKey.clear()
|
||||
nuvioFullyWatchedSeriesKeys = emptySet()
|
||||
traktFullyWatchedSeriesKeys = emptySet()
|
||||
simklFullyWatchedSeriesKeys = emptySet()
|
||||
providerFullyWatchedSeriesKeys.clear()
|
||||
nuvioHasLoaded = true
|
||||
traktHasLoaded = false
|
||||
simklHasLoaded = false
|
||||
loadedProviders.clear()
|
||||
nuvioHasLoadedRemote = false
|
||||
traktHasLoadedRemote = false
|
||||
simklHasLoadedRemote = false
|
||||
providersLoadedFromRemote.clear()
|
||||
nuvioDirtyWatchedKeys.clear()
|
||||
|
||||
val payload = WatchedStorage.loadPayload(profileId).orEmpty().trim()
|
||||
|
|
@ -253,20 +232,13 @@ object WatchedRepository {
|
|||
|
||||
private fun activateEffectiveSource(source: WatchProgressSource): WatchProgressSource {
|
||||
if (activeSource == source) return source
|
||||
when (source) {
|
||||
WatchProgressSource.TRAKT -> {
|
||||
traktItemsByKey.clear()
|
||||
traktFullyWatchedSeriesKeys = emptySet()
|
||||
traktHasLoaded = false
|
||||
traktHasLoadedRemote = false
|
||||
}
|
||||
WatchProgressSource.SIMKL -> {
|
||||
simklItemsByKey.clear()
|
||||
simklFullyWatchedSeriesKeys = emptySet()
|
||||
simklHasLoaded = false
|
||||
simklHasLoadedRemote = false
|
||||
}
|
||||
WatchProgressSource.NUVIO_SYNC -> nuvioHasLoadedRemote = false
|
||||
source.providerId?.let { providerId ->
|
||||
providerItemsByKey.getOrPut(providerId, ::mutableMapOf).clear()
|
||||
providerFullyWatchedSeriesKeys[providerId] = emptySet()
|
||||
loadedProviders -= providerId
|
||||
providersLoadedFromRemote -= providerId
|
||||
} ?: run {
|
||||
nuvioHasLoadedRemote = false
|
||||
}
|
||||
activeSource = source
|
||||
sourceGeneration += 1L
|
||||
|
|
@ -299,11 +271,11 @@ object WatchedRepository {
|
|||
|
||||
suspend fun pullFromServer(profileId: Int) {
|
||||
TrackingProviderRegistry.ensureLoaded()
|
||||
TraktSettingsRepository.ensureLoaded()
|
||||
TrackingSettingsRepository.ensureLoaded()
|
||||
refreshForSource(
|
||||
profileId = profileId,
|
||||
source = effectiveWatchedSource(
|
||||
requestedSource = TraktSettingsRepository.uiState.value.watchProgressSource,
|
||||
requestedSource = TrackingSettingsRepository.uiState.value.watchProgressSource,
|
||||
connectedProviderIds = TrackingProviderRegistry.connectedProviderIdsSnapshot(),
|
||||
),
|
||||
forceSnapshot = false,
|
||||
|
|
@ -312,11 +284,11 @@ object WatchedRepository {
|
|||
|
||||
suspend fun forceSnapshotRefreshFromServer(profileId: Int) {
|
||||
TrackingProviderRegistry.ensureLoaded()
|
||||
TraktSettingsRepository.ensureLoaded()
|
||||
TrackingSettingsRepository.ensureLoaded()
|
||||
refreshForSource(
|
||||
profileId = profileId,
|
||||
source = effectiveWatchedSource(
|
||||
requestedSource = TraktSettingsRepository.uiState.value.watchProgressSource,
|
||||
requestedSource = TrackingSettingsRepository.uiState.value.watchProgressSource,
|
||||
connectedProviderIds = TrackingProviderRegistry.connectedProviderIdsSnapshot(),
|
||||
),
|
||||
forceSnapshot = true,
|
||||
|
|
@ -329,7 +301,7 @@ object WatchedRepository {
|
|||
forceSnapshot: Boolean = true,
|
||||
): Boolean {
|
||||
TrackingProviderRegistry.ensureLoaded()
|
||||
TraktSettingsRepository.ensureLoaded()
|
||||
TrackingSettingsRepository.ensureLoaded()
|
||||
if (ProfileRepository.activeProfileId != profileId) {
|
||||
log.d { "Skipping watched refresh for inactive profile $profileId" }
|
||||
return false
|
||||
|
|
@ -340,7 +312,7 @@ object WatchedRepository {
|
|||
|
||||
val effectiveSource = activateEffectiveSource(source)
|
||||
val operation = newRefreshOperation(profileId) ?: return false
|
||||
if (effectiveSource == WatchProgressSource.NUVIO_SYNC) {
|
||||
if (effectiveSource.providerId == null) {
|
||||
val authState = AuthRepository.state.value
|
||||
if (authState !is AuthState.Authenticated || authState.isAnonymous) {
|
||||
// Local watched state is authoritative when this account has no Nuvio upstream.
|
||||
|
|
@ -351,30 +323,25 @@ object WatchedRepository {
|
|||
}
|
||||
}
|
||||
return try {
|
||||
when (effectiveSource) {
|
||||
WatchProgressSource.TRAKT -> pullSnapshotFromAdapter(
|
||||
adapter = traktSyncAdapter,
|
||||
effectiveSource.providerId?.let { providerId ->
|
||||
val provider = TrackingProviderRegistry.watchedProvider(providerId)
|
||||
?: return false
|
||||
pullSnapshotFromAdapter(
|
||||
adapter = provider,
|
||||
operation = operation,
|
||||
profileId = profileId,
|
||||
resetDeltaState = true,
|
||||
)
|
||||
WatchProgressSource.SIMKL -> pullSnapshotFromAdapter(
|
||||
adapter = simklSyncAdapter,
|
||||
} ?: if (forceSnapshot) {
|
||||
refreshNuvioSnapshot(
|
||||
operation = operation,
|
||||
profileId = profileId,
|
||||
)
|
||||
} else {
|
||||
pullSupabaseDeltaFromServer(
|
||||
operation = operation,
|
||||
profileId = profileId,
|
||||
resetDeltaState = true,
|
||||
)
|
||||
WatchProgressSource.NUVIO_SYNC -> if (forceSnapshot) {
|
||||
refreshNuvioSnapshot(
|
||||
operation = operation,
|
||||
profileId = profileId,
|
||||
)
|
||||
} else {
|
||||
pullSupabaseDeltaFromServer(
|
||||
operation = operation,
|
||||
profileId = profileId,
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (error: CancellationException) {
|
||||
throw error
|
||||
|
|
@ -438,30 +405,22 @@ object WatchedRepository {
|
|||
replaceWatchedItemsForSource(
|
||||
source = operation.sourceOperation.source,
|
||||
nuvioItems = nuvioItemsByKey,
|
||||
traktItems = traktItemsByKey,
|
||||
simklItems = simklItemsByKey,
|
||||
providerItems = providerItemsByKey,
|
||||
replacement = mergedSnapshot.items,
|
||||
)
|
||||
fullyWatchedSeriesKeys?.let { keys ->
|
||||
setFullyWatchedSeriesKeysForSource(operation.sourceOperation.source, keys)
|
||||
}
|
||||
when (operation.sourceOperation.source) {
|
||||
WatchProgressSource.NUVIO_SYNC -> {
|
||||
nuvioDirtyWatchedKeys = mergedSnapshot.dirtyKeys.toMutableSet()
|
||||
nuvioHasLoaded = true
|
||||
nuvioHasLoadedRemote = true
|
||||
if (resetDeltaState) {
|
||||
deltaCursorEventId = 0L
|
||||
deltaInitialized = false
|
||||
}
|
||||
}
|
||||
WatchProgressSource.TRAKT -> {
|
||||
traktHasLoaded = true
|
||||
traktHasLoadedRemote = true
|
||||
}
|
||||
WatchProgressSource.SIMKL -> {
|
||||
simklHasLoaded = true
|
||||
simklHasLoadedRemote = true
|
||||
operation.sourceOperation.source.providerId?.let { providerId ->
|
||||
loadedProviders += providerId
|
||||
providersLoadedFromRemote += providerId
|
||||
} ?: run {
|
||||
nuvioDirtyWatchedKeys = mergedSnapshot.dirtyKeys.toMutableSet()
|
||||
nuvioHasLoaded = true
|
||||
nuvioHasLoadedRemote = true
|
||||
if (resetDeltaState) {
|
||||
deltaCursorEventId = 0L
|
||||
deltaInitialized = false
|
||||
}
|
||||
}
|
||||
publish()
|
||||
|
|
@ -645,36 +604,28 @@ object WatchedRepository {
|
|||
}
|
||||
|
||||
private fun itemsForSource(source: WatchProgressSource): MutableMap<String, WatchedItem> =
|
||||
when (source) {
|
||||
WatchProgressSource.NUVIO_SYNC -> nuvioItemsByKey
|
||||
WatchProgressSource.TRAKT -> traktItemsByKey
|
||||
WatchProgressSource.SIMKL -> simklItemsByKey
|
||||
}
|
||||
source.providerId
|
||||
?.let { providerId -> providerItemsByKey.getOrPut(providerId, ::mutableMapOf) }
|
||||
?: nuvioItemsByKey
|
||||
|
||||
private fun fullyWatchedSeriesKeysForSource(source: WatchProgressSource): Set<String> =
|
||||
when (source) {
|
||||
WatchProgressSource.NUVIO_SYNC -> nuvioFullyWatchedSeriesKeys
|
||||
WatchProgressSource.TRAKT -> traktFullyWatchedSeriesKeys
|
||||
WatchProgressSource.SIMKL -> simklFullyWatchedSeriesKeys
|
||||
}
|
||||
source.providerId
|
||||
?.let { providerId -> providerFullyWatchedSeriesKeys[providerId].orEmpty() }
|
||||
?: nuvioFullyWatchedSeriesKeys
|
||||
|
||||
private fun setFullyWatchedSeriesKeysForSource(
|
||||
source: WatchProgressSource,
|
||||
keys: Set<String>,
|
||||
) {
|
||||
when (source) {
|
||||
WatchProgressSource.NUVIO_SYNC -> nuvioFullyWatchedSeriesKeys = keys
|
||||
WatchProgressSource.TRAKT -> traktFullyWatchedSeriesKeys = keys
|
||||
WatchProgressSource.SIMKL -> simklFullyWatchedSeriesKeys = keys
|
||||
source.providerId?.let { providerId ->
|
||||
providerFullyWatchedSeriesKeys[providerId] = keys
|
||||
} ?: run {
|
||||
nuvioFullyWatchedSeriesKeys = keys
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasLoadedSource(source: WatchProgressSource): Boolean =
|
||||
when (source) {
|
||||
WatchProgressSource.NUVIO_SYNC -> nuvioHasLoaded
|
||||
WatchProgressSource.TRAKT -> traktHasLoaded
|
||||
WatchProgressSource.SIMKL -> simklHasLoaded
|
||||
}
|
||||
source.providerId?.let(loadedProviders::contains) ?: nuvioHasLoaded
|
||||
|
||||
fun toggleWatched(item: WatchedItem) {
|
||||
ensureLoaded()
|
||||
|
|
@ -693,16 +644,20 @@ object WatchedRepository {
|
|||
}
|
||||
|
||||
fun markWatched(items: Collection<WatchedItem>) {
|
||||
markWatched(items = items, traktHistorySync = WatchedTraktHistorySync.Mirror)
|
||||
markWatched(items = items, trackerHistorySync = WatchedTrackerHistorySync.Mirror)
|
||||
}
|
||||
|
||||
internal fun markWatchedFromPlaybackCompletion(item: WatchedItem, syncRemote: Boolean = true) {
|
||||
markWatched(items = listOf(item), traktHistorySync = WatchedTraktHistorySync.Skip, syncRemote = syncRemote)
|
||||
markWatched(
|
||||
items = listOf(item),
|
||||
trackerHistorySync = WatchedTrackerHistorySync.Skip,
|
||||
syncRemote = syncRemote,
|
||||
)
|
||||
}
|
||||
|
||||
private fun markWatched(
|
||||
items: Collection<WatchedItem>,
|
||||
traktHistorySync: WatchedTraktHistorySync,
|
||||
trackerHistorySync: WatchedTrackerHistorySync,
|
||||
syncRemote: Boolean = true,
|
||||
) {
|
||||
ensureLoaded()
|
||||
|
|
@ -716,7 +671,7 @@ object WatchedRepository {
|
|||
timestampedItems.forEach { watchedItem ->
|
||||
val key = watchedItemKey(watchedItem.type, watchedItem.id, watchedItem.season, watchedItem.episode)
|
||||
targetItems[key] = watchedItem
|
||||
if (source == WatchProgressSource.NUVIO_SYNC) {
|
||||
if (source.providerId == null) {
|
||||
nuvioDirtyWatchedKeys += key
|
||||
}
|
||||
}
|
||||
|
|
@ -727,7 +682,7 @@ object WatchedRepository {
|
|||
if (syncRemote) {
|
||||
pushMarksToServer(
|
||||
items = timestampedItems,
|
||||
traktHistorySync = traktHistorySync,
|
||||
trackerHistorySync = trackerHistorySync,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
|
@ -765,7 +720,7 @@ object WatchedRepository {
|
|||
val removedItems = items.mapNotNull { watchedItem ->
|
||||
val key = watchedItemKey(watchedItem.type, watchedItem.id, watchedItem.season, watchedItem.episode)
|
||||
targetItems.remove(key)?.also {
|
||||
if (source == WatchProgressSource.NUVIO_SYNC) {
|
||||
if (source.providerId == null) {
|
||||
nuvioDirtyWatchedKeys -= key
|
||||
}
|
||||
}
|
||||
|
|
@ -869,7 +824,7 @@ object WatchedRepository {
|
|||
|
||||
private fun pushMarksToServer(
|
||||
items: Collection<WatchedItem>,
|
||||
traktHistorySync: WatchedTraktHistorySync,
|
||||
trackerHistorySync: WatchedTrackerHistorySync,
|
||||
source: WatchProgressSource,
|
||||
) {
|
||||
val profileId = currentProfileId
|
||||
|
|
@ -880,7 +835,7 @@ object WatchedRepository {
|
|||
val pushed = pushToTargetsForSource(
|
||||
profileId = profileId,
|
||||
items = items,
|
||||
traktHistorySync = traktHistorySync,
|
||||
trackerHistorySync = trackerHistorySync,
|
||||
source = source,
|
||||
)
|
||||
if (pushed && shouldPersistWatchedSource(source)) {
|
||||
|
|
@ -919,8 +874,7 @@ object WatchedRepository {
|
|||
val items = watchedItemsForSource(
|
||||
source = activeSource,
|
||||
nuvioItems = nuvioItemsByKey.values,
|
||||
traktItems = traktItemsByKey.values,
|
||||
simklItems = simklItemsByKey.values,
|
||||
providerItems = providerItemsByKey.mapValues { (_, itemsByKey) -> itemsByKey.values },
|
||||
)
|
||||
.map(WatchedItem::normalizedMarkedAt)
|
||||
.sortedByDescending { it.markedAtEpochMs }
|
||||
|
|
@ -931,11 +885,9 @@ object WatchedRepository {
|
|||
watchedItemKey(it.type, it.id, it.season, it.episode)
|
||||
},
|
||||
isLoaded = hasLoadedSource(activeSource),
|
||||
hasLoadedRemoteItems = when (activeSource) {
|
||||
WatchProgressSource.NUVIO_SYNC -> nuvioHasLoadedRemote
|
||||
WatchProgressSource.TRAKT -> traktHasLoadedRemote
|
||||
WatchProgressSource.SIMKL -> simklHasLoadedRemote
|
||||
},
|
||||
hasLoadedRemoteItems = activeSource.providerId
|
||||
?.let(providersLoadedFromRemote::contains)
|
||||
?: nuvioHasLoadedRemote,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -988,11 +940,11 @@ object WatchedRepository {
|
|||
private suspend fun pushToTargetsForSource(
|
||||
profileId: Int,
|
||||
items: Collection<WatchedItem>,
|
||||
traktHistorySync: WatchedTraktHistorySync,
|
||||
trackerHistorySync: WatchedTrackerHistorySync,
|
||||
source: WatchProgressSource,
|
||||
): Boolean {
|
||||
var anySucceeded = false
|
||||
if (source == WatchProgressSource.NUVIO_SYNC) {
|
||||
if (source.providerId == null) {
|
||||
try {
|
||||
syncAdapter.push(profileId = profileId, items = items)
|
||||
anySucceeded = true
|
||||
|
|
@ -1003,15 +955,15 @@ object WatchedRepository {
|
|||
}
|
||||
}
|
||||
|
||||
if (traktHistorySync == WatchedTraktHistorySync.Mirror) {
|
||||
connectedTrackerSyncAdapters().forEach { (providerId, adapter) ->
|
||||
if (trackerHistorySync == WatchedTrackerHistorySync.Mirror) {
|
||||
TrackingProviderRegistry.connectedWatchedProviders().forEach { provider ->
|
||||
try {
|
||||
adapter.push(profileId = profileId, items = items)
|
||||
provider.push(profileId = profileId, items = items)
|
||||
anySucceeded = true
|
||||
} catch (error: CancellationException) {
|
||||
throw error
|
||||
} catch (error: Throwable) {
|
||||
log.e(error) { "Failed to push watched items to ${providerId.storageId}" }
|
||||
log.e(error) { "Failed to push watched items to ${provider.providerId.storageId}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1023,7 +975,7 @@ object WatchedRepository {
|
|||
items: Collection<WatchedItem>,
|
||||
source: WatchProgressSource,
|
||||
) {
|
||||
if (source == WatchProgressSource.NUVIO_SYNC) {
|
||||
if (source.providerId == null) {
|
||||
try {
|
||||
syncAdapter.delete(profileId = profileId, items = items)
|
||||
} catch (error: CancellationException) {
|
||||
|
|
@ -1033,23 +985,17 @@ object WatchedRepository {
|
|||
}
|
||||
}
|
||||
|
||||
connectedTrackerSyncAdapters().forEach { (providerId, adapter) ->
|
||||
TrackingProviderRegistry.connectedWatchedProviders().forEach { provider ->
|
||||
try {
|
||||
adapter.delete(profileId = profileId, items = items)
|
||||
provider.delete(profileId = profileId, items = items)
|
||||
} catch (error: CancellationException) {
|
||||
throw error
|
||||
} catch (error: Throwable) {
|
||||
log.e(error) { "Failed to delete watched items from ${providerId.storageId}" }
|
||||
log.e(error) { "Failed to delete watched items from ${provider.providerId.storageId}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun connectedTrackerSyncAdapters(): List<Pair<TrackingProviderId, WatchedSyncAdapter>> =
|
||||
listOf(
|
||||
TrackingProviderId.TRAKT to traktSyncAdapter,
|
||||
TrackingProviderId.SIMKL to simklSyncAdapter,
|
||||
).filter { (providerId, _) -> TrackingProviderRegistry.isAuthenticated(providerId) }
|
||||
|
||||
private fun accountScopeSnapshot(): CoroutineScope =
|
||||
synchronized(accountScopeLock) {
|
||||
accountScope
|
||||
|
|
@ -1115,19 +1061,6 @@ internal fun acknowledgeSuccessfulWatchedPush(
|
|||
return remainingDirtyKeys
|
||||
}
|
||||
|
||||
internal fun shouldUseTraktWatchedSync(
|
||||
isAuthenticated: Boolean,
|
||||
source: WatchProgressSource,
|
||||
): Boolean = isAuthenticated && source == WatchProgressSource.TRAKT
|
||||
|
||||
internal fun effectiveWatchedSource(
|
||||
requestedSource: WatchProgressSource,
|
||||
isTraktAuthenticated: Boolean,
|
||||
): WatchProgressSource = effectiveWatchedSource(
|
||||
requestedSource = requestedSource,
|
||||
connectedProviderIds = if (isTraktAuthenticated) setOf(TrackingProviderId.TRAKT) else emptySet(),
|
||||
)
|
||||
|
||||
internal fun effectiveWatchedSource(
|
||||
requestedSource: WatchProgressSource,
|
||||
connectedProviderIds: Set<TrackingProviderId>,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import co.touchlab.kermit.Logger
|
|||
import com.nuvio.app.features.addons.RawHttpResponse
|
||||
import com.nuvio.app.features.addons.httpRequestRaw
|
||||
import com.nuvio.app.features.tmdb.TmdbService
|
||||
import com.nuvio.app.features.tracking.TrackingProviderId
|
||||
import com.nuvio.app.features.tracking.TrackingWatchedProvider
|
||||
import com.nuvio.app.features.trakt.TraktAuthRepository
|
||||
import com.nuvio.app.features.trakt.TraktEpisodeMappingService
|
||||
import com.nuvio.app.features.trakt.TraktPlatformClock
|
||||
|
|
@ -23,7 +25,8 @@ private const val WATCHED_MAX_PAGES = 1_000
|
|||
private const val WATCHED_SHOWS_EXTENDED = "progress"
|
||||
|
||||
|
||||
object TraktWatchedSyncAdapter : WatchedSyncAdapter {
|
||||
object TraktWatchedSyncAdapter : TrackingWatchedProvider {
|
||||
override val providerId: TrackingProviderId = TrackingProviderId.TRAKT
|
||||
private val log = Logger.withTag("TraktWatchedSync")
|
||||
private val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import com.nuvio.app.features.profiles.ProfileRepository
|
|||
import com.nuvio.app.features.tracking.DEFAULT_WATCH_PROGRESS_SOURCE
|
||||
import com.nuvio.app.features.tracking.TrackingProviderId
|
||||
import com.nuvio.app.features.tracking.TrackingProviderRegistry
|
||||
import com.nuvio.app.features.tracking.TrackingSettingsRepository
|
||||
import com.nuvio.app.features.tracking.WatchProgressSource
|
||||
import com.nuvio.app.features.tracking.effectiveWatchProgressSource
|
||||
import com.nuvio.app.features.trakt.TraktSettingsRepository
|
||||
import com.nuvio.app.features.watched.WatchedRepository
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.atomicfu.locks.SynchronizedObject
|
||||
|
|
@ -235,7 +235,7 @@ object WatchProgressSourceCoordinator {
|
|||
if (observeJob?.isActive == true) return
|
||||
observeJob = scope.launch {
|
||||
combine(
|
||||
TraktSettingsRepository.uiState,
|
||||
TrackingSettingsRepository.uiState,
|
||||
TrackingProviderRegistry.connectedProviderIds,
|
||||
AuthRepository.state,
|
||||
ProfileRepository.state,
|
||||
|
|
@ -264,7 +264,7 @@ object WatchProgressSourceCoordinator {
|
|||
private fun ensureSourceStateLoaded() {
|
||||
ensureTrackingProvidersRegistered()
|
||||
TrackingProviderRegistry.ensureLoaded()
|
||||
TraktSettingsRepository.ensureLoaded()
|
||||
TrackingSettingsRepository.ensureLoaded()
|
||||
}
|
||||
|
||||
suspend fun selectSource(
|
||||
|
|
@ -275,7 +275,7 @@ object WatchProgressSourceCoordinator {
|
|||
ensureSourceStateLoadedForGeneration(operationGeneration)
|
||||
synchronized(startLock) {
|
||||
ensureCoordinatorGeneration(operationGeneration)
|
||||
TraktSettingsRepository.setWatchProgressSource(source, profileId)
|
||||
TrackingSettingsRepository.setWatchProgressSource(source, profileId)
|
||||
}
|
||||
val context = currentContext(profileId)
|
||||
return try {
|
||||
|
|
@ -481,7 +481,7 @@ object WatchProgressSourceCoordinator {
|
|||
|
||||
private fun currentContext(profileId: Int): WatchProgressSourceContext = buildContext(
|
||||
profileId = profileId,
|
||||
requestedSource = TraktSettingsRepository.uiState.value.watchProgressSource,
|
||||
requestedSource = TrackingSettingsRepository.uiState.value.watchProgressSource,
|
||||
connectedProviderIds = TrackingProviderRegistry.connectedProviderIdsSnapshot(),
|
||||
authState = AuthRepository.state.value,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.nuvio.app.features.watched
|
||||
|
||||
import com.nuvio.app.features.trakt.TraktPlatformClock
|
||||
import com.nuvio.app.features.tracking.TrackingProviderId
|
||||
import com.nuvio.app.features.tracking.WatchProgressSource
|
||||
import com.nuvio.app.features.tracking.providerId
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
|
|
@ -21,24 +23,9 @@ class WatchedModelsTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `Trakt watched sync follows selected watch progress source`() {
|
||||
assertTrue(
|
||||
shouldUseTraktWatchedSync(
|
||||
isAuthenticated = true,
|
||||
source = WatchProgressSource.TRAKT,
|
||||
),
|
||||
)
|
||||
assertFalse(
|
||||
shouldUseTraktWatchedSync(
|
||||
isAuthenticated = true,
|
||||
source = WatchProgressSource.NUVIO_SYNC,
|
||||
),
|
||||
)
|
||||
assertFalse(
|
||||
shouldUseTraktWatchedSync(
|
||||
isAuthenticated = false,
|
||||
source = WatchProgressSource.TRAKT,
|
||||
),
|
||||
)
|
||||
fun `remote watched sources carry provider identity`() {
|
||||
assertEquals(TrackingProviderId.TRAKT, WatchProgressSource.TRAKT.providerId)
|
||||
assertEquals(TrackingProviderId.SIMKL, WatchProgressSource.SIMKL.providerId)
|
||||
assertEquals(null, WatchProgressSource.NUVIO_SYNC.providerId)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.nuvio.app.features.watched
|
|||
|
||||
import com.nuvio.app.features.details.MetaDetails
|
||||
import com.nuvio.app.features.details.MetaVideo
|
||||
import com.nuvio.app.features.tracking.TrackingProviderId
|
||||
import com.nuvio.app.features.tracking.WatchProgressSource
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
|
@ -136,23 +137,23 @@ class WatchedRepositoryTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun playbackCompletionWatchedMarks_doNotMirrorToTraktHistory() {
|
||||
fun playbackCompletionWatchedMarks_doNotMirrorToTrackerHistory() {
|
||||
assertFalse(
|
||||
shouldMirrorWatchedMarkToTraktHistory(
|
||||
sync = WatchedTraktHistorySync.Skip,
|
||||
isTraktAuthenticated = true,
|
||||
shouldMirrorWatchedMarkToTrackers(
|
||||
sync = WatchedTrackerHistorySync.Skip,
|
||||
hasConnectedTracker = true,
|
||||
),
|
||||
)
|
||||
assertTrue(
|
||||
shouldMirrorWatchedMarkToTraktHistory(
|
||||
sync = WatchedTraktHistorySync.Mirror,
|
||||
isTraktAuthenticated = true,
|
||||
shouldMirrorWatchedMarkToTrackers(
|
||||
sync = WatchedTrackerHistorySync.Mirror,
|
||||
hasConnectedTracker = true,
|
||||
),
|
||||
)
|
||||
assertFalse(
|
||||
shouldMirrorWatchedMarkToTraktHistory(
|
||||
sync = WatchedTraktHistorySync.Mirror,
|
||||
isTraktAuthenticated = false,
|
||||
shouldMirrorWatchedMarkToTrackers(
|
||||
sync = WatchedTrackerHistorySync.Mirror,
|
||||
hasConnectedTracker = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -168,8 +169,10 @@ class WatchedRepositoryTest {
|
|||
watchedItemsForSource(
|
||||
source = WatchProgressSource.NUVIO_SYNC,
|
||||
nuvioItems = listOf(nuvioItem),
|
||||
traktItems = listOf(traktItem),
|
||||
simklItems = listOf(simklItem),
|
||||
providerItems = mapOf(
|
||||
TrackingProviderId.TRAKT to listOf(traktItem),
|
||||
TrackingProviderId.SIMKL to listOf(simklItem),
|
||||
),
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
|
|
@ -177,8 +180,10 @@ class WatchedRepositoryTest {
|
|||
watchedItemsForSource(
|
||||
source = WatchProgressSource.TRAKT,
|
||||
nuvioItems = listOf(nuvioItem),
|
||||
traktItems = listOf(traktItem),
|
||||
simklItems = listOf(simklItem),
|
||||
providerItems = mapOf(
|
||||
TrackingProviderId.TRAKT to listOf(traktItem),
|
||||
TrackingProviderId.SIMKL to listOf(simklItem),
|
||||
),
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
|
|
@ -186,8 +191,10 @@ class WatchedRepositoryTest {
|
|||
watchedItemsForSource(
|
||||
source = WatchProgressSource.SIMKL,
|
||||
nuvioItems = listOf(nuvioItem),
|
||||
traktItems = listOf(traktItem),
|
||||
simklItems = listOf(simklItem),
|
||||
providerItems = mapOf(
|
||||
TrackingProviderId.TRAKT to listOf(traktItem),
|
||||
TrackingProviderId.SIMKL to listOf(simklItem),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -205,19 +212,22 @@ class WatchedRepositoryTest {
|
|||
val previousTraktItem = watchedItem(id = "old-trakt", markedAtEpochMs = 2_000L)
|
||||
val refreshedTraktItem = watchedItem(id = "new-trakt", markedAtEpochMs = 3_000L)
|
||||
val nuvioItems = mutableMapOf("nuvio" to nuvioItem)
|
||||
val traktItems = mutableMapOf("old-trakt" to previousTraktItem)
|
||||
val simklItems = mutableMapOf<String, WatchedItem>()
|
||||
val providerItems = mutableMapOf(
|
||||
TrackingProviderId.TRAKT to mutableMapOf("old-trakt" to previousTraktItem),
|
||||
)
|
||||
|
||||
replaceWatchedItemsForSource(
|
||||
source = WatchProgressSource.TRAKT,
|
||||
nuvioItems = nuvioItems,
|
||||
traktItems = traktItems,
|
||||
simklItems = simklItems,
|
||||
providerItems = providerItems,
|
||||
replacement = mapOf("new-trakt" to refreshedTraktItem),
|
||||
)
|
||||
|
||||
assertEquals(mapOf("nuvio" to nuvioItem), nuvioItems)
|
||||
assertEquals(mapOf("new-trakt" to refreshedTraktItem), traktItems)
|
||||
assertEquals(
|
||||
mapOf("new-trakt" to refreshedTraktItem),
|
||||
providerItems[TrackingProviderId.TRAKT].orEmpty(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -226,14 +236,14 @@ class WatchedRepositoryTest {
|
|||
WatchProgressSource.NUVIO_SYNC,
|
||||
effectiveWatchedSource(
|
||||
requestedSource = WatchProgressSource.TRAKT,
|
||||
isTraktAuthenticated = false,
|
||||
connectedProviderIds = emptySet(),
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
WatchProgressSource.TRAKT,
|
||||
effectiveWatchedSource(
|
||||
requestedSource = WatchProgressSource.TRAKT,
|
||||
isTraktAuthenticated = true,
|
||||
connectedProviderIds = setOf(TrackingProviderId.TRAKT),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -244,7 +254,7 @@ class WatchedRepositoryTest {
|
|||
WatchProgressSource.SIMKL,
|
||||
effectiveWatchedSource(
|
||||
requestedSource = WatchProgressSource.SIMKL,
|
||||
connectedProviderIds = setOf(com.nuvio.app.features.tracking.TrackingProviderId.SIMKL),
|
||||
connectedProviderIds = setOf(TrackingProviderId.SIMKL),
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
|
|
|
|||
Loading…
Reference in a new issue