From 0b845349e8f22eb4eede01a598278ead178be59a Mon Sep 17 00:00:00 2001 From: nandhanay Date: Sun, 9 Aug 2026 23:15:33 +0530 Subject: [PATCH] fix: auto-mark items watched in MDBList on playback completion --- .../repository/sync/MdbListRemoteProvider.kt | 12 ++- .../sync/MdbListRemoteProviderTest.kt | 100 ++++++++++++++++++ web/lib/mdblist.ts | 3 + 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 app/src/test/kotlin/com/arflix/tv/data/repository/sync/MdbListRemoteProviderTest.kt diff --git a/app/src/main/kotlin/com/arflix/tv/data/repository/sync/MdbListRemoteProvider.kt b/app/src/main/kotlin/com/arflix/tv/data/repository/sync/MdbListRemoteProvider.kt index 7801ebf86..f1b38de52 100644 --- a/app/src/main/kotlin/com/arflix/tv/data/repository/sync/MdbListRemoteProvider.kt +++ b/app/src/main/kotlin/com/arflix/tv/data/repository/sync/MdbListRemoteProvider.kt @@ -38,7 +38,17 @@ class MdbListRemoteProvider @Inject constructor( override suspend fun scrobbleStop( mediaType: MediaType, tmdbId: Int, progress: Float, season: Int?, episode: Int? - ) = repository.scrobble("stop", mediaType, tmdbId, progress, season, episode) + ) { + repository.scrobble("stop", mediaType, tmdbId, progress, season, episode) + if (progress >= com.arflix.tv.util.Constants.WATCHED_THRESHOLD) { + if (mediaType == MediaType.MOVIE) { + repository.markMovieWatched(tmdbId) + } else if (season != null && episode != null) { + repository.markEpisodeWatched(tmdbId, season, episode) + } + repository.clearPlayback(mediaType, tmdbId, season, episode) + } + } override suspend fun getWatchedMovies(): Set = repository.getWatchedMovies() diff --git a/app/src/test/kotlin/com/arflix/tv/data/repository/sync/MdbListRemoteProviderTest.kt b/app/src/test/kotlin/com/arflix/tv/data/repository/sync/MdbListRemoteProviderTest.kt new file mode 100644 index 000000000..9833bef28 --- /dev/null +++ b/app/src/test/kotlin/com/arflix/tv/data/repository/sync/MdbListRemoteProviderTest.kt @@ -0,0 +1,100 @@ +package com.arflix.tv.data.repository.sync + +import com.arflix.tv.data.model.MediaType +import com.arflix.tv.data.repository.MdbListRepository +import com.arflix.tv.util.Constants +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class MdbListRemoteProviderTest { + + @Test + fun scrobbleStop_belowWatchedThreshold_doesNotMarkWatched() = runBlocking { + var scrobbleCalled = false + var markedWatchedCalled = false + + val provider = MdbListRemoteProvider(object : MdbListRepository(createMockClient(), createMockApi(), createMockStore()) { + override suspend fun scrobble(action: String, mediaType: MediaType, tmdbId: Int, progress: Float, season: Int?, episode: Int?) { + scrobbleCalled = true + assertEquals("stop", action) + } + + override suspend fun markMovieWatched(tmdbId: Int): Boolean { + markedWatchedCalled = true + return true + } + }) + + provider.scrobbleStop(MediaType.MOVIE, 100, 50f, null, null) + + assertTrue(scrobbleCalled) + assertFalse(markedWatchedCalled) + } + + @Test + fun scrobbleStop_movieAtOrAboveWatchedThreshold_marksMovieWatchedAndClearsPlayback() = runBlocking { + var scrobbleCalled = false + var markedMovieId = 0 + var cleared = false + + val provider = MdbListRemoteProvider(object : MdbListRepository(createMockClient(), createMockApi(), createMockStore()) { + override suspend fun scrobble(action: String, mediaType: MediaType, tmdbId: Int, progress: Float, season: Int?, episode: Int?) { + scrobbleCalled = true + } + + override suspend fun markMovieWatched(tmdbId: Int): Boolean { + markedMovieId = tmdbId + return true + } + + override suspend fun clearPlayback(mediaType: MediaType, tmdbId: Int, season: Int?, episode: Int?) { + cleared = true + } + }) + + provider.scrobbleStop(MediaType.MOVIE, 500, Constants.WATCHED_THRESHOLD.toFloat(), null, null) + + assertTrue(scrobbleCalled) + assertEquals(500, markedMovieId) + assertTrue(cleared) + } + + @Test + fun scrobbleStop_episodeAtOrAboveWatchedThreshold_marksEpisodeWatchedAndClearsPlayback() = runBlocking { + var markedEpisodeShowId = 0 + var markedSeason = 0 + var markedEpisodeNum = 0 + var cleared = false + + val provider = MdbListRemoteProvider(object : MdbListRepository(createMockClient(), createMockApi(), createMockStore()) { + override suspend fun scrobble(action: String, mediaType: MediaType, tmdbId: Int, progress: Float, season: Int?, episode: Int?) {} + + override suspend fun markEpisodeWatched(showTmdbId: Int, season: Int, episode: Int): Boolean { + markedEpisodeShowId = showTmdbId + markedSeason = season + markedEpisodeNum = episode + return true + } + + override suspend fun clearPlayback(mediaType: MediaType, tmdbId: Int, season: Int?, episode: Int?) { + cleared = true + } + }) + + provider.scrobbleStop(MediaType.TV, 700, 95f, 2, 5) + + assertEquals(700, markedEpisodeShowId) + assertEquals(2, markedSeason) + assertEquals(5, markedEpisodeNum) + assertTrue(cleared) + } + + private companion object { + fun createMockClient(): okhttp3.OkHttpClient = org.mockito.Mockito.mock(okhttp3.OkHttpClient::class.java) + fun createMockApi(): com.arflix.tv.data.api.MdbListApi = org.mockito.Mockito.mock(com.arflix.tv.data.api.MdbListApi::class.java) + fun createMockStore(): com.arflix.tv.data.repository.sync.SyncProviderStore = org.mockito.Mockito.mock(com.arflix.tv.data.repository.sync.SyncProviderStore::class.java) + } +} diff --git a/web/lib/mdblist.ts b/web/lib/mdblist.ts index 620715203..0c01894a3 100644 --- a/web/lib/mdblist.ts +++ b/web/lib/mdblist.ts @@ -208,6 +208,9 @@ export class MdbListClient { body = { progress, movie: { ids: { tmdb: item.tmdbId } } }; } await this.request(`scrobble/${action}`, { method: "POST", body: JSON.stringify(body) }); + if (action === "stop" && progress >= 80) { + await this.addToHistory(item).catch(() => undefined); + } } private async request(path: string, init: RequestInit): Promise {