mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Add settings for max days in next up (#850)
## Description Add a settings to set the maximum amount of time items can appear in Next Up. Options range from 7 days to a year, or no limit (current & default behavior) ### Related issues Closes #662 ### Testing Tested on the emulator and verified URLs, added unit tests ## Screenshots N/A, just a new slider bar setting ## AI or LLM usage None
This commit is contained in:
parent
63b37ef3f4
commit
e7da5ca007
11 changed files with 208 additions and 7 deletions
|
|
@ -186,6 +186,45 @@ sealed interface AppPreference<Pref, T> {
|
||||||
summarizer = { value -> value?.toString() },
|
summarizer = { value -> value?.toString() },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val MaxDaysNextUpOptions = listOf(7, 14, 30, 60, 90, 180, 365)
|
||||||
|
val MaxDaysNextUp =
|
||||||
|
AppSliderPreference<AppPreferences>(
|
||||||
|
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 =
|
val CombineContinueNext =
|
||||||
AppSwitchPreference<AppPreferences>(
|
AppSwitchPreference<AppPreferences>(
|
||||||
title = R.string.combine_continue_next,
|
title = R.string.combine_continue_next,
|
||||||
|
|
@ -956,6 +995,7 @@ val basicPreferences =
|
||||||
AppPreference.HomePageItems,
|
AppPreference.HomePageItems,
|
||||||
AppPreference.CombineContinueNext,
|
AppPreference.CombineContinueNext,
|
||||||
AppPreference.RewatchNextUp,
|
AppPreference.RewatchNextUp,
|
||||||
|
AppPreference.MaxDaysNextUp,
|
||||||
AppPreference.PlayThemeMusic,
|
AppPreference.PlayThemeMusic,
|
||||||
AppPreference.RememberSelectedTab,
|
AppPreference.RememberSelectedTab,
|
||||||
AppPreference.SubtitleStyle,
|
AppPreference.SubtitleStyle,
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ class AppPreferencesSerializer
|
||||||
maxItemsPerRow = AppPreference.HomePageItems.defaultValue.toInt()
|
maxItemsPerRow = AppPreference.HomePageItems.defaultValue.toInt()
|
||||||
enableRewatchingNextUp = AppPreference.RewatchNextUp.defaultValue
|
enableRewatchingNextUp = AppPreference.RewatchNextUp.defaultValue
|
||||||
combineContinueNext = AppPreference.CombineContinueNext.defaultValue
|
combineContinueNext = AppPreference.CombineContinueNext.defaultValue
|
||||||
|
maxDaysNextUp = AppPreference.MaxDaysNextUp.defaultValue.toInt()
|
||||||
}.build()
|
}.build()
|
||||||
interfacePreferences =
|
interfacePreferences =
|
||||||
InterfacePreferences
|
InterfacePreferences
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import com.github.damontecres.wholphin.preferences.AppPreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.update
|
import com.github.damontecres.wholphin.preferences.update
|
||||||
import com.github.damontecres.wholphin.preferences.updateAdvancedPreferences
|
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.updateInterfacePreferences
|
||||||
import com.github.damontecres.wholphin.preferences.updateLiveTvPreferences
|
import com.github.damontecres.wholphin.preferences.updateLiveTvPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.updateMpvOptions
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,10 @@ class LatestNextUpService
|
||||||
limit: Int,
|
limit: Int,
|
||||||
enableRewatching: Boolean,
|
enableRewatching: Boolean,
|
||||||
enableResumable: Boolean,
|
enableResumable: Boolean,
|
||||||
|
maxDays: Int,
|
||||||
): List<BaseItem> {
|
): List<BaseItem> {
|
||||||
|
val nextUpDateCutoff =
|
||||||
|
maxDays.takeIf { it > 0 }?.let { LocalDateTime.now().minusDays(it.toLong()) }
|
||||||
val request =
|
val request =
|
||||||
GetNextUpRequest(
|
GetNextUpRequest(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
|
|
@ -86,6 +89,7 @@ class LatestNextUpService
|
||||||
enableResumable = enableResumable,
|
enableResumable = enableResumable,
|
||||||
enableUserData = true,
|
enableUserData = true,
|
||||||
enableRewatching = enableRewatching,
|
enableRewatching = enableRewatching,
|
||||||
|
nextUpDateCutoff = nextUpDateCutoff,
|
||||||
)
|
)
|
||||||
val nextUp =
|
val nextUp =
|
||||||
api.tvShowsApi
|
api.tvShowsApi
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ class TvProviderWorker
|
||||||
userId,
|
userId,
|
||||||
prefs.homePagePreferences.enableRewatchingNextUp,
|
prefs.homePagePreferences.enableRewatchingNextUp,
|
||||||
prefs.homePagePreferences.combineContinueNext,
|
prefs.homePagePreferences.combineContinueNext,
|
||||||
|
prefs.homePagePreferences.maxDaysNextUp,
|
||||||
)
|
)
|
||||||
val potentialItemsToAddIds = potentialItemsToAdd.map { it.id.toString() }
|
val potentialItemsToAddIds = potentialItemsToAdd.map { it.id.toString() }
|
||||||
|
|
||||||
|
|
@ -145,12 +146,13 @@ class TvProviderWorker
|
||||||
userId: UUID,
|
userId: UUID,
|
||||||
enableRewatching: Boolean,
|
enableRewatching: Boolean,
|
||||||
combineContinueNext: Boolean,
|
combineContinueNext: Boolean,
|
||||||
|
maxDaysNextUp: Int,
|
||||||
): List<BaseItem> {
|
): List<BaseItem> {
|
||||||
val resumeItems = latestNextUpService.getResume(userId, 10, true)
|
val resumeItems = latestNextUpService.getResume(userId, 10, true)
|
||||||
val seriesIds = resumeItems.mapNotNull { it.data.seriesId }
|
val seriesIds = resumeItems.mapNotNull { it.data.seriesId }
|
||||||
val nextUpItems =
|
val nextUpItems =
|
||||||
latestNextUpService
|
latestNextUpService
|
||||||
.getNextUp(userId, 10, enableRewatching, false)
|
.getNextUp(userId, 10, enableRewatching, false, maxDaysNextUp)
|
||||||
.filter { it.data.seriesId != null && it.data.seriesId !in seriesIds }
|
.filter { it.data.seriesId != null && it.data.seriesId !in seriesIds }
|
||||||
return if (combineContinueNext) {
|
return if (combineContinueNext) {
|
||||||
latestNextUpService.buildCombined(resumeItems, nextUpItems)
|
latestNextUpService.buildCombined(resumeItems, nextUpItems)
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ class HomeViewModel
|
||||||
limit,
|
limit,
|
||||||
prefs.enableRewatchingNextUp,
|
prefs.enableRewatchingNextUp,
|
||||||
false,
|
false,
|
||||||
|
prefs.maxDaysNextUp,
|
||||||
)
|
)
|
||||||
val watching =
|
val watching =
|
||||||
buildList {
|
buildList {
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ fun SliderPreference(
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ message HomePagePreferences{
|
||||||
int32 max_items_per_row = 1;
|
int32 max_items_per_row = 1;
|
||||||
bool enable_rewatching_next_up = 2;
|
bool enable_rewatching_next_up = 2;
|
||||||
bool combine_continue_next = 3;
|
bool combine_continue_next = 3;
|
||||||
|
int32 max_days_next_up = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ThemeSongVolume {
|
enum ThemeSongVolume {
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,11 @@
|
||||||
<item quantity="one">%d hour</item>
|
<item quantity="one">%d hour</item>
|
||||||
<item quantity="other">%d hours</item>
|
<item quantity="other">%d hours</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="days">
|
||||||
|
<item quantity="zero">%s days</item>
|
||||||
|
<item quantity="one">%s day</item>
|
||||||
|
<item quantity="other">%s days</item>
|
||||||
|
</plurals>
|
||||||
<plurals name="items">
|
<plurals name="items">
|
||||||
<item quantity="zero">%d items</item>
|
<item quantity="zero">%d items</item>
|
||||||
<item quantity="one">%d item</item>
|
<item quantity="one">%d item</item>
|
||||||
|
|
@ -485,6 +490,8 @@
|
||||||
<string name="slideshow_duration">Slideshow duration</string>
|
<string name="slideshow_duration">Slideshow duration</string>
|
||||||
<string name="play_videos_during_slideshow">Play videos during slideshow</string>
|
<string name="play_videos_during_slideshow">Play videos during slideshow</string>
|
||||||
<string name="send_media_info_log_to_server">Send media info log to server</string>
|
<string name="send_media_info_log_to_server">Send media info log to server</string>
|
||||||
|
<string name="no_limit">No limit</string>
|
||||||
|
<string name="max_days_next_up">Max days in Next Up</string>
|
||||||
|
|
||||||
<string-array name="theme_song_volume">
|
<string-array name="theme_song_volume">
|
||||||
<item>Disabled</item>
|
<item>Disabled</item>
|
||||||
|
|
|
||||||
|
|
@ -78,11 +78,6 @@ class SuggestionsWorkerTest {
|
||||||
every { homePagePreferences } returns mockk { every { maxItemsPerRow } returns 25 }
|
every { homePagePreferences } returns mockk { every { maxItemsPerRow } returns 25 }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mockQueryResult(items: List<BaseItemDto> = emptyList()) =
|
|
||||||
mockk<org.jellyfin.sdk.api.client.Response<BaseItemDtoQueryResult>>(relaxed = true) {
|
|
||||||
every { content } returns mockk { every { this@mockk.items } returns items }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun returns_failure_on_invalid_input() =
|
fun returns_failure_on_invalid_input() =
|
||||||
runTest {
|
runTest {
|
||||||
|
|
@ -231,3 +226,8 @@ class SuggestionsWorkerTest {
|
||||||
assertEquals(ListenableWorker.Result.retry(), result)
|
assertEquals(ListenableWorker.Result.retry(), result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun mockQueryResult(items: List<BaseItemDto> = emptyList()) =
|
||||||
|
mockk<org.jellyfin.sdk.api.client.Response<BaseItemDtoQueryResult>>(relaxed = true) {
|
||||||
|
every { content } returns mockk { every { this@mockk.items } returns items }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<TvShowsApi>()
|
||||||
|
private val mockApi = mockk<ApiClient>(relaxed = true)
|
||||||
|
private val mockContext = mockk<Context>()
|
||||||
|
private val mockDatePlayedService = mockk<DatePlayedService>()
|
||||||
|
|
||||||
|
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<GetNextUpRequest>()
|
||||||
|
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<GetNextUpRequest>()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue