From 6df89fe8c9293228109c736e4ae1e91a7b1e9d88 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Sun, 21 Dec 2025 19:00:59 -0500 Subject: [PATCH] Apply combined continue/next up, if enabled, on library recommended tabs (#533) ## Description Respect the combine Continue Watching & Next Up setting on TV Show library recommended tabs. This uses the same implementation on the home screen, but only includes items for that particular tv show library. ### Related issues Fixes #532 --- .../ui/components/RecommendedTvShow.kt | 78 +++++++++++++------ .../wholphin/ui/main/HomeViewModel.kt | 77 ++++++++++-------- 2 files changed, 96 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedTvShow.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedTvShow.kt index cb179860..6e48692a 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedTvShow.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedTvShow.kt @@ -9,14 +9,15 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.viewModelScope import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.ServerRepository -import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.services.BackdropService +import com.github.damontecres.wholphin.services.DatePlayedService import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.ui.SlimItemFields import com.github.damontecres.wholphin.ui.data.RowColumn +import com.github.damontecres.wholphin.ui.main.buildCombinedNextUp import com.github.damontecres.wholphin.ui.setValueOnMain import com.github.damontecres.wholphin.ui.toBaseItems import com.github.damontecres.wholphin.util.ExceptionHandler @@ -57,6 +58,7 @@ class RecommendedTvShowViewModel private val api: ApiClient, private val serverRepository: ServerRepository, private val preferencesDataStore: DataStore, + private val datePlayedService: DatePlayedService, @Assisted val parentId: UUID, navigationManager: NavigationManager, favoriteWatchManager: FavoriteWatchManager, @@ -78,17 +80,17 @@ class RecommendedTvShowViewModel override fun init() { viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) { - val itemsPerRow = - preferencesDataStore.data - .firstOrNull() - ?.homePagePreferences - ?.maxItemsPerRow - ?: AppPreference.HomePageItems.defaultValue.toInt() + val preferences = + preferencesDataStore.data.firstOrNull() ?: AppPreferences.getDefaultInstance() + val combineNextUp = preferences.homePagePreferences.combineContinueNext + val itemsPerRow = preferences.homePagePreferences.maxItemsPerRow + val userId = serverRepository.currentUser.value?.id try { val resumeItemsDeferred = viewModelScope.async(Dispatchers.IO) { val resumeItemsRequest = GetResumeItemsRequest( + userId = userId, parentId = parentId, fields = SlimItemFields, includeItemTypes = listOf(BaseItemKind.EPISODE), @@ -106,12 +108,14 @@ class RecommendedTvShowViewModel viewModelScope.async(Dispatchers.IO) { val nextUpRequest = GetNextUpRequest( - parentId = parentId, + userId = userId, fields = SlimItemFields, - enableUserData = true, - startIndex = 0, + imageTypeLimit = 1, + parentId = parentId, limit = itemsPerRow, - enableTotalRecordCount = false, + enableResumable = false, + enableUserData = true, + enableRewatching = preferences.homePagePreferences.enableRewatchingNextUp, ) GetNextUpRequestHandler @@ -119,21 +123,45 @@ class RecommendedTvShowViewModel .toBaseItems(api, true) } val resumeItems = resumeItemsDeferred.await() - update( - R.string.continue_watching, - HomeRowLoadingState.Success( - context.getString(R.string.continue_watching), - resumeItems, - ), - ) val nextUpItems = nextUpItemsDeferred.await() - update( - R.string.next_up, - HomeRowLoadingState.Success( - context.getString(R.string.next_up), - nextUpItems, - ), - ) + if (combineNextUp) { + val combined = + buildCombinedNextUp( + viewModelScope, + datePlayedService, + resumeItems, + nextUpItems, + ) + update( + R.string.continue_watching, + HomeRowLoadingState.Success( + context.getString(R.string.continue_watching), + combined, + ), + ) + update( + R.string.next_up, + HomeRowLoadingState.Success( + context.getString(R.string.next_up), + listOf(), + ), + ) + } else { + update( + R.string.continue_watching, + HomeRowLoadingState.Success( + context.getString(R.string.continue_watching), + resumeItems, + ), + ) + update( + R.string.next_up, + HomeRowLoadingState.Success( + context.getString(R.string.next_up), + nextUpItems, + ), + ) + } if (resumeItems.isNotEmpty() || nextUpItems.isNotEmpty()) { loading.setValueOnMain(LoadingState.Success) 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 38c1ff4f..f9e1a7d1 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 @@ -24,6 +24,7 @@ import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.supportItemKinds import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.async @@ -109,7 +110,13 @@ class HomeViewModel val watching = buildList { if (prefs.combineContinueNext) { - val items = buildCombined(resume, nextUp) + val items = + buildCombinedNextUp( + viewModelScope, + datePlayedService, + resume, + nextUp, + ) add( HomeRowLoadingState.Success( title = context.getString(R.string.continue_watching), @@ -267,39 +274,6 @@ class HomeViewModel latestRows.setValueOnMain(rows) } - private suspend fun buildCombined( - resume: List, - nextUp: List, - ): List = - withContext(Dispatchers.IO) { - val start = System.currentTimeMillis() - val semaphore = Semaphore(3) - val deferred = - nextUp - .filter { it.data.seriesId != null } - .map { item -> - viewModelScope.async(Dispatchers.IO) { - try { - semaphore.withPermit { - datePlayedService.getLastPlayed(item) - } - } catch (ex: Exception) { - Timber.e(ex, "Error fetching %s", item.id) - null - } - } - } - - val nextUpLastPlayed = deferred.awaitAll() - val timestamps = mutableMapOf() - nextUp.map { it.id }.zip(nextUpLastPlayed).toMap(timestamps) - resume.forEach { timestamps[it.id] = it.data.userData?.lastPlayedDate } - val result = (resume + nextUp).sortedByDescending { timestamps[it.id] } - val duration = (System.currentTimeMillis() - start).milliseconds - Timber.v("buildCombined took %s", duration) - return@withContext result - } - fun setWatched( itemId: UUID, played: Boolean, @@ -340,3 +314,38 @@ data class LatestData( val title: String, val request: GetLatestMediaRequest, ) + +suspend fun buildCombinedNextUp( + scope: CoroutineScope, + datePlayedService: DatePlayedService, + resume: List, + nextUp: List, +): List = + withContext(Dispatchers.IO) { + val start = System.currentTimeMillis() + val semaphore = Semaphore(3) + val deferred = + nextUp + .filter { it.data.seriesId != null } + .map { item -> + scope.async(Dispatchers.IO) { + try { + semaphore.withPermit { + datePlayedService.getLastPlayed(item) + } + } catch (ex: Exception) { + Timber.e(ex, "Error fetching %s", item.id) + null + } + } + } + + val nextUpLastPlayed = deferred.awaitAll() + val timestamps = mutableMapOf() + nextUp.map { it.id }.zip(nextUpLastPlayed).toMap(timestamps) + resume.forEach { timestamps[it.id] = it.data.userData?.lastPlayedDate } + val result = (resume + nextUp).sortedByDescending { timestamps[it.id] } + val duration = (System.currentTimeMillis() - start).milliseconds + Timber.v("buildCombined took %s", duration) + return@withContext result + }