diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt index 6015a128..4d7dd76f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt @@ -186,6 +186,45 @@ sealed interface AppPreference { summarizer = { value -> value?.toString() }, ) + val MaxDaysNextUpOptions = listOf(7, 14, 30, 60, 90, 180, 365) + val MaxDaysNextUp = + AppSliderPreference( + title = R.string.max_days_next_up, + defaultValue = -1, + min = 0, + // Max is "no limit" stored as -1 + max = MaxDaysNextUpOptions.lastIndex + 1L, + interval = 1, + getter = { + MaxDaysNextUpOptions + .indexOf(it.homePagePreferences.maxDaysNextUp) + .takeIf { it in MaxDaysNextUpOptions.indices } + ?.toLong() + ?: MaxDaysNextUpOptions.size.toLong() + }, + setter = { prefs, index -> + prefs.updateHomePagePreferences { + maxDaysNextUp = MaxDaysNextUpOptions.getOrNull(index.toInt()) ?: -1 + } + }, + summarizer = { value -> + if (value != null) { + val v = MaxDaysNextUpOptions.getOrNull(value.toInt()) ?: -1 + if (v == -1) { + WholphinApplication.instance.getString(R.string.no_limit) + } else { + WholphinApplication.instance.resources.getQuantityString( + R.plurals.days, + v, + v.toString(), + ) + } + } else { + null + } + }, + ) + val CombineContinueNext = AppSwitchPreference( title = R.string.combine_continue_next, @@ -956,6 +995,7 @@ val basicPreferences = AppPreference.HomePageItems, AppPreference.CombineContinueNext, AppPreference.RewatchNextUp, + AppPreference.MaxDaysNextUp, AppPreference.PlayThemeMusic, AppPreference.RememberSelectedTab, AppPreference.SubtitleStyle, diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt index e2182aa3..6adcdca4 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt @@ -82,6 +82,7 @@ class AppPreferencesSerializer maxItemsPerRow = AppPreference.HomePageItems.defaultValue.toInt() enableRewatchingNextUp = AppPreference.RewatchNextUp.defaultValue combineContinueNext = AppPreference.CombineContinueNext.defaultValue + maxDaysNextUp = AppPreference.MaxDaysNextUp.defaultValue.toInt() }.build() interfacePreferences = InterfacePreferences diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt b/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt index 96130f48..057ec550 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt @@ -10,6 +10,7 @@ import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.update import com.github.damontecres.wholphin.preferences.updateAdvancedPreferences +import com.github.damontecres.wholphin.preferences.updateHomePagePreferences import com.github.damontecres.wholphin.preferences.updateInterfacePreferences import com.github.damontecres.wholphin.preferences.updateLiveTvPreferences import com.github.damontecres.wholphin.preferences.updateMpvOptions @@ -237,4 +238,12 @@ suspend fun upgradeApp( } } } + + if (previous.isEqualOrBefore(Version.fromString("0.4.1-14-g0"))) { + appPreferences.updateData { + it.updateHomePagePreferences { + maxDaysNextUp = AppPreference.MaxDaysNextUp.defaultValue.toInt() + } + } + } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/LatestNextUpService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/LatestNextUpService.kt index 44ed1a58..6ac016a3 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/LatestNextUpService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/LatestNextUpService.kt @@ -75,7 +75,10 @@ class LatestNextUpService limit: Int, enableRewatching: Boolean, enableResumable: Boolean, + maxDays: Int, ): List { + val nextUpDateCutoff = + maxDays.takeIf { it > 0 }?.let { LocalDateTime.now().minusDays(it.toLong()) } val request = GetNextUpRequest( userId = userId, @@ -86,6 +89,7 @@ class LatestNextUpService enableResumable = enableResumable, enableUserData = true, enableRewatching = enableRewatching, + nextUpDateCutoff = nextUpDateCutoff, ) val nextUp = api.tvShowsApi diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/tvprovider/TvProviderWorker.kt b/app/src/main/java/com/github/damontecres/wholphin/services/tvprovider/TvProviderWorker.kt index 134e4903..d8816e71 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/tvprovider/TvProviderWorker.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/tvprovider/TvProviderWorker.kt @@ -81,6 +81,7 @@ class TvProviderWorker userId, prefs.homePagePreferences.enableRewatchingNextUp, prefs.homePagePreferences.combineContinueNext, + prefs.homePagePreferences.maxDaysNextUp, ) val potentialItemsToAddIds = potentialItemsToAdd.map { it.id.toString() } @@ -145,12 +146,13 @@ class TvProviderWorker userId: UUID, enableRewatching: Boolean, combineContinueNext: Boolean, + maxDaysNextUp: Int, ): List { val resumeItems = latestNextUpService.getResume(userId, 10, true) val seriesIds = resumeItems.mapNotNull { it.data.seriesId } val nextUpItems = latestNextUpService - .getNextUp(userId, 10, enableRewatching, false) + .getNextUp(userId, 10, enableRewatching, false, maxDaysNextUp) .filter { it.data.seriesId != null && it.data.seriesId !in seriesIds } return if (combineContinueNext) { latestNextUpService.buildCombined(resumeItems, nextUpItems) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt index 8e7846b8..0e571544 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt @@ -98,6 +98,7 @@ class HomeViewModel limit, prefs.enableRewatchingNextUp, false, + prefs.maxDaysNextUp, ) val watching = buildList { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/SliderPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/SliderPreference.kt index e5564ee2..ce686708 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/SliderPreference.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/SliderPreference.kt @@ -65,7 +65,7 @@ fun SliderPreference( } Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier diff --git a/app/src/main/proto/WholphinDataStore.proto b/app/src/main/proto/WholphinDataStore.proto index 41fd78ef..737dbb57 100644 --- a/app/src/main/proto/WholphinDataStore.proto +++ b/app/src/main/proto/WholphinDataStore.proto @@ -82,6 +82,7 @@ message HomePagePreferences{ int32 max_items_per_row = 1; bool enable_rewatching_next_up = 2; bool combine_continue_next = 3; + int32 max_days_next_up = 4; } enum ThemeSongVolume { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 63c7c3d2..2dbad4f1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -260,6 +260,11 @@ %d hour %d hours + + %s days + %s day + %s days + %d items %d item @@ -485,6 +490,8 @@ Slideshow duration Play videos during slideshow Send media info log to server + No limit + Max days in Next Up Disabled diff --git a/app/src/test/java/com/github/damontecres/wholphin/services/SuggestionsWorkerTest.kt b/app/src/test/java/com/github/damontecres/wholphin/services/SuggestionsWorkerTest.kt index ebef9aec..7e5d2af0 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/services/SuggestionsWorkerTest.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/services/SuggestionsWorkerTest.kt @@ -78,11 +78,6 @@ class SuggestionsWorkerTest { every { homePagePreferences } returns mockk { every { maxItemsPerRow } returns 25 } } - private fun mockQueryResult(items: List = emptyList()) = - mockk>(relaxed = true) { - every { content } returns mockk { every { this@mockk.items } returns items } - } - @Test fun returns_failure_on_invalid_input() = runTest { @@ -231,3 +226,8 @@ class SuggestionsWorkerTest { assertEquals(ListenableWorker.Result.retry(), result) } } + +fun mockQueryResult(items: List = emptyList()) = + mockk>(relaxed = true) { + every { content } returns mockk { every { this@mockk.items } returns items } + } diff --git a/app/src/test/java/com/github/damontecres/wholphin/test/NextUpTest.kt b/app/src/test/java/com/github/damontecres/wholphin/test/NextUpTest.kt new file mode 100644 index 00000000..0e9bb92e --- /dev/null +++ b/app/src/test/java/com/github/damontecres/wholphin/test/NextUpTest.kt @@ -0,0 +1,136 @@ +package com.github.damontecres.wholphin.test + +import android.content.Context +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.github.damontecres.wholphin.preferences.AppPreference +import com.github.damontecres.wholphin.preferences.AppPreferences +import com.github.damontecres.wholphin.preferences.updateHomePagePreferences +import com.github.damontecres.wholphin.services.DatePlayedService +import com.github.damontecres.wholphin.services.LatestNextUpService +import com.github.damontecres.wholphin.services.mockQueryResult +import io.mockk.CapturingSlot +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest +import org.jellyfin.sdk.api.client.ApiClient +import org.jellyfin.sdk.api.client.extensions.tvShowsApi +import org.jellyfin.sdk.api.operations.TvShowsApi +import org.jellyfin.sdk.model.UUID +import org.jellyfin.sdk.model.api.request.GetNextUpRequest +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.time.LocalDate + +class NextUpTest { + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + private val testDispatcher = StandardTestDispatcher() + + private val mockTvShowsApi = mockk() + private val mockApi = mockk(relaxed = true) + private val mockContext = mockk() + private val mockDatePlayedService = mockk() + + private val latestNextUpService = + LatestNextUpService(mockContext, mockApi, mockDatePlayedService) + + @Before + fun setUp() { + every { mockApi.tvShowsApi } returns mockTvShowsApi + } + + @Test + fun `Test max 30 days in next up`() = + runTest { + val maxDays = 30 + val nextUpSlot = CapturingSlot() + coEvery { mockTvShowsApi.getNextUp(capture(nextUpSlot)) } returns mockQueryResult() + latestNextUpService.getNextUp( + userId = UUID.randomUUID(), + limit = 10, + enableRewatching = true, + enableResumable = true, + maxDays = maxDays, + ) + Assert.assertEquals(10, nextUpSlot.captured.limit) + val expected = LocalDate.now().minusDays(maxDays.toLong()) + Assert.assertEquals(expected, nextUpSlot.captured.nextUpDateCutoff?.toLocalDate()) + } + + @Test + fun `Test no limit in next up`() = + runTest { + val nextUpSlot = CapturingSlot() + coEvery { mockTvShowsApi.getNextUp(capture(nextUpSlot)) } returns mockQueryResult() + latestNextUpService.getNextUp( + userId = UUID.randomUUID(), + limit = 10, + enableRewatching = true, + enableResumable = true, + maxDays = -1, + ) + Assert.assertEquals(10, nextUpSlot.captured.limit) + Assert.assertNull(nextUpSlot.captured.nextUpDateCutoff) + } + + @Test + fun `Test storing preference`() { + AppPreference.MaxDaysNextUp.setter.invoke(AppPreferences.getDefaultInstance(), 0).let { + Assert.assertEquals(7, it.homePagePreferences.maxDaysNextUp) + } + + AppPreference.MaxDaysNextUp.setter + .invoke( + AppPreferences.getDefaultInstance(), + AppPreference.MaxDaysNextUpOptions.lastIndex.toLong(), + ).let { + Assert.assertEquals(365, it.homePagePreferences.maxDaysNextUp) + } + + AppPreference.MaxDaysNextUp.setter + .invoke(AppPreferences.getDefaultInstance(), 3) + .let { + Assert.assertEquals(60, it.homePagePreferences.maxDaysNextUp) + } + + AppPreference.MaxDaysNextUp.setter + .invoke( + AppPreferences.getDefaultInstance(), + AppPreference.MaxDaysNextUpOptions.lastIndex + 1L, + ).let { + Assert.assertEquals(-1, it.homePagePreferences.maxDaysNextUp) + } + } + + @Test + fun `Test getting preference`() { + AppPreferences + .getDefaultInstance() + .updateHomePagePreferences { maxDaysNextUp = 7 } + .let { + val result = AppPreference.MaxDaysNextUp.getter.invoke(it) + Assert.assertEquals(0, result) + } + + AppPreferences + .getDefaultInstance() + .updateHomePagePreferences { maxDaysNextUp = 60 } + .let { + val result = AppPreference.MaxDaysNextUp.getter.invoke(it) + Assert.assertEquals(3, result) + } + + AppPreferences + .getDefaultInstance() + .updateHomePagePreferences { maxDaysNextUp = -1 } + .let { + val result = AppPreference.MaxDaysNextUp.getter.invoke(it) + Assert.assertEquals(AppPreference.MaxDaysNextUpOptions.lastIndex + 1L, result) + } + } +}