From a35ecb6570fc4ac52fdbc4de9ea2acf6c27c0a18 Mon Sep 17 00:00:00 2001 From: damontecres <154766448+damontecres@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:10:41 -0400 Subject: [PATCH] Fixing a few UI issues (#61) Gathering a bunch of smaller changes into one PR ### Changes * Changes the behavior of the "combine continue watching & next up" to apply only to TV Shows which basically makes the 'Continue Watching' just movies and 'Next Up' just TV episodes * Banner card (Series & Home pages) will stretch images to fit * Close the "more" nav item if navigating to a non-more one (from #60) * Fix episodes not updating/marked played when returning to series page after playing multiple episodes * Don't show the Intro/Outro skip button the Next Up dialog is showing * Always should black background for playback, even when loading --- .../wholphin/preferences/AppPreference.kt | 3 +- .../wholphin/ui/cards/BannerCard.kt | 12 +++- .../ui/detail/CollectionFolderMovie.kt | 25 +++++++- .../wholphin/ui/detail/SeriesViewModel.kt | 3 +- .../ui/detail/series/SeriesOverview.kt | 10 ++++ .../damontecres/wholphin/ui/main/HomePage.kt | 26 ++++---- .../wholphin/ui/main/HomeViewModel.kt | 60 +++++++++++-------- .../damontecres/wholphin/ui/nav/NavDrawer.kt | 5 +- .../wholphin/ui/playback/PlaybackPage.kt | 4 +- app/src/main/res/values/strings.xml | 1 + 10 files changed, 99 insertions(+), 50 deletions(-) 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 dcfe820a..3e275ad3 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 @@ -173,8 +173,7 @@ sealed interface AppPreference { setter = { prefs, value -> prefs.updateHomePagePreferences { combineContinueNext = value } }, - summaryOn = R.string.enabled, - summaryOff = R.string.disabled, + summary = R.string.combine_continue_next_summary, ) val RewatchNextUp = diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/BannerCard.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/BannerCard.kt index 424ac02e..257430c7 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/BannerCard.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/BannerCard.kt @@ -21,12 +21,14 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.tv.material3.Card +import androidx.tv.material3.CardDefaults import androidx.tv.material3.Icon import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text @@ -58,18 +60,22 @@ fun BannerCard( onClick = onClick, onLongClick = onLongClick, interactionSource = interactionSource, + colors = + CardDefaults.colors( + containerColor = Color.Transparent, + ), ) { Box( modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.surfaceVariant), + .fillMaxSize(), +// .background(MaterialTheme.colorScheme.surfaceVariant), ) { if (!imageError && imageUrl.isNotNullOrBlank()) { AsyncImage( model = imageUrl, contentDescription = null, - contentScale = ContentScale.Fit, + contentScale = ContentScale.FillBounds, onError = { imageError = true }, modifier = Modifier.fillMaxSize(), ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderMovie.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderMovie.kt index 73e5cda7..426d8b3b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderMovie.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderMovie.kt @@ -52,7 +52,7 @@ fun CollectionFolderMovie( val rememberedTabIndex = remember { preferencesViewModel.getRememberedTab(preferences, destination.itemId, 0) } - val tabs = listOf("Recommended", "Library", "Genres") + val tabs = listOf("Recommended", "Library", "Collections", "Genres") var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) } var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) } val focusRequester = remember { FocusRequester() } @@ -170,6 +170,29 @@ fun CollectionFolderMovie( ) } 2 -> { + CollectionFolderGrid( + preferences = preferences, + onClickItem = onClickItem, + itemId = destination.itemId, + item = destination.item, + initialFilter = + GetItemsFilter( + includeItemTypes = listOf(BaseItemKind.BOX_SET), + ), + showTitle = false, + recursive = true, + modifier = + Modifier + .padding(start = 16.dp) + .fillMaxSize() + .focusRequester(focusRequester), + positionCallback = { columns, position -> + showHeader = position < columns + }, + ) + } + + 3 -> { GenreCardGrid( itemId = destination.itemId, modifier = diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/SeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/SeriesViewModel.kt index b981229e..c6a7ab52 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/SeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/SeriesViewModel.kt @@ -33,7 +33,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jellyfin.sdk.api.client.ApiClient @@ -219,7 +218,7 @@ class SeriesViewModel fun loadEpisodes(season: Int) { this@SeriesViewModel.episodes.value = EpisodeList.Loading - viewModelScope.async(ExceptionHandler(true)) { + viewModelScope.launch(ExceptionHandler(true)) { val episodes = try { loadEpisodesInternal(season) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt index 7f80c335..199f19c5 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt @@ -72,6 +72,7 @@ fun SeriesOverview( val firstItemFocusRequester = remember { FocusRequester() } val episodeRowFocusRequester = remember { FocusRequester() } + var initialLoadDone by rememberSaveable { mutableStateOf(false) } OneTimeLaunchedEffect { Timber.v("SeriesDetailParent: itemId=${destination.itemId}, initialSeasonEpisode=$initialSeasonEpisode") viewModel.init( @@ -81,7 +82,9 @@ fun SeriesOverview( initialSeasonEpisode?.season, initialSeasonEpisode?.episode, ) + initialLoadDone = true } + val loading by viewModel.loading.observeAsState(LoadingState.Loading) val series by viewModel.item.observeAsState(null) @@ -108,6 +111,13 @@ fun SeriesOverview( ), ) } + if (initialLoadDone) { + LaunchedEffect(Unit) { + seasons.indexToNumber[position.seasonTabIndex]?.let { + viewModel.loadEpisodes(it) + } + } + } var overviewDialog by remember { mutableStateOf(null) } var moreDialog by remember { mutableStateOf(null) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt index 1179cb77..ed749a27 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt @@ -225,21 +225,21 @@ fun HomePageContent( } } } - Box( - modifier = - Modifier - .padding(8.dp) - .size(24.dp) - .align(Alignment.BottomEnd), - ) { - when (loadingState) { - LoadingState.Pending, - LoadingState.Loading, - -> + when (loadingState) { + LoadingState.Pending, + LoadingState.Loading, + -> + Box( + modifier = + Modifier + .padding(16.dp) + .size(40.dp) + .align(Alignment.TopEnd), + ) { CircularProgress(Modifier.fillMaxSize()) + } - else -> {} - } + else -> {} } } } 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 22cb2db4..d8f2d2f1 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 org.jellyfin.sdk.api.client.extensions.itemsApi import org.jellyfin.sdk.api.client.extensions.tvShowsApi import org.jellyfin.sdk.api.client.extensions.userLibraryApi import org.jellyfin.sdk.api.client.extensions.userViewsApi +import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.UserDto import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest import org.jellyfin.sdk.model.api.request.GetNextUpRequest @@ -64,32 +65,28 @@ class HomeViewModel .filter { it is ServerNavDrawerItem } .map { (it as ServerNavDrawerItem).itemId } // TODO data is fetched all together which may be slow for large servers - val resume = getResume(userDto.id, limit) - val nextUp = getNextUp(userDto.id, limit, prefs.enableRewatchingNextUp) + val resume = getResume(userDto.id, limit, !prefs.combineContinueNext) + val nextUp = + getNextUp( + userDto.id, + limit, + prefs.enableRewatchingNextUp, + prefs.combineContinueNext, + ) val latest = getLatest(userDto, limit, includedIds) val homeRows = - if (prefs.combineContinueNext) { - listOf( - HomeRow( - section = HomeSection.NEXT_UP, - items = resume + nextUp, - ), - *latest.toTypedArray(), - ) - } else { - listOf( - HomeRow( - section = HomeSection.RESUME, - items = resume, - ), - HomeRow( - section = HomeSection.NEXT_UP, - items = nextUp, - ), - *latest.toTypedArray(), - ) - } + listOf( + HomeRow( + section = HomeSection.RESUME, + items = resume, + ), + HomeRow( + section = HomeSection.NEXT_UP, + items = nextUp, + ), + *latest.toTypedArray(), + ) withContext(Dispatchers.Main) { this@HomeViewModel.homeRows.value = homeRows loadingState.value = LoadingState.Success @@ -101,13 +98,23 @@ class HomeViewModel private suspend fun getResume( userId: UUID, limit: Int, + includeEpisodes: Boolean, ): List { val request = GetResumeItemsRequest( userId = userId, fields = SlimItemFields, limit = limit, - includeItemTypes = supportItemKinds, + includeItemTypes = + if (includeEpisodes) { + supportItemKinds + } else { + supportItemKinds + .toMutableSet() + .apply { + remove(BaseItemKind.EPISODE) + } + }, ) val items = api.itemsApi @@ -122,6 +129,7 @@ class HomeViewModel userId: UUID, limit: Int, enableRewatching: Boolean, + enableResumable: Boolean, ): List { val request = GetNextUpRequest( @@ -130,7 +138,7 @@ class HomeViewModel imageTypeLimit = 1, parentId = null, limit = limit, - enableResumable = false, + enableResumable = enableResumable, enableUserData = true, enableRewatching = enableRewatching, ) @@ -139,7 +147,7 @@ class HomeViewModel .getNextUp(request) .content .items - .map { BaseItem.Companion.from(it, api, true) } + .map { BaseItem.from(it, api, true) } return nextUp } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt index 195ed5b5..5ade979d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt @@ -287,7 +287,10 @@ fun NavDrawer( library = it, selected = selectedIndex == index, moreExpanded = showMore, - onClick = { onClick.invoke(index, it) }, + onClick = { + onClick.invoke(index, it) + if (it !is NavDrawerItem.More) setShowMore(false) + }, modifier = Modifier .ifElse( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt index ca666e2d..6412b2a4 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt @@ -100,7 +100,7 @@ fun PlaybackPage( is LoadingState.Error -> ErrorMessage(st, modifier) LoadingState.Pending, LoadingState.Loading, - -> LoadingPage(modifier) + -> LoadingPage(modifier.background(Color.Black)) LoadingState.Success -> { val prefs = preferences.appPreferences.playbackPreferences @@ -201,7 +201,7 @@ fun PlaybackPage( val showSegment = !segmentCancelled && currentSegment != null && - !controllerViewState.controlsVisible && skipIndicatorDuration == 0L + nextUp == null && !controllerViewState.controlsVisible && skipIndicatorDuration == 0L BackHandler(showSegment) { segmentCancelled = true } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bec45c38..a65599d8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -91,5 +91,6 @@ Customize Navigation Drawer Items User Profile Settings Choose the default items to show, others will be hidden + Applies to TV Series only