From c4e64c33676b3e362d27d52954fa622eb5c7f9b0 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Tue, 13 Jan 2026 16:05:09 -0500 Subject: [PATCH] Fixes to genre grids (#693) ## Description Use only TV series genres for a TV library's genre tab. This ensures only series are shown, not episodes, which removes some of the seemingly empty genres. Also fixes the image filtering to ensure an item with a backdrop is always used if available. ### Related issues Fixes #689 Fixes #690 --- .../ui/components/CollectionFolderGrid.kt | 9 ++- .../wholphin/ui/components/GenreCardGrid.kt | 55 +++++++++++-------- .../ui/detail/CollectionFolderMovie.kt | 1 + .../wholphin/ui/detail/CollectionFolderTv.kt | 1 + 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt index c6eeedca..ce8db419 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt @@ -30,6 +30,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -783,12 +784,15 @@ fun CollectionFolderGridContent( var showHeader by rememberSaveable { mutableStateOf(true) } var showViewOptions by rememberSaveable { mutableStateOf(false) } var viewOptions by remember { mutableStateOf(viewOptions) } + val headerRowFocusRequester = remember { FocusRequester() } val gridFocusRequester = remember { FocusRequester() } if (pager.isNotEmpty()) { RequestOrRestoreFocus(gridFocusRequester) } else { - focusRequesterOnEmpty?.tryRequestFocus() + LaunchedEffect(Unit) { + (focusRequesterOnEmpty ?: headerRowFocusRequester).tryRequestFocus() + } } var backdropImageUrl by remember { mutableStateOf(null) } @@ -833,7 +837,8 @@ fun CollectionFolderGridContent( modifier = Modifier .padding(start = 16.dp, end = endPadding) - .fillMaxWidth(), + .fillMaxWidth() + .focusRequester(headerRowFocusRequester), ) { if (sortOptions.isNotEmpty() || filterOptions.isNotEmpty()) { Row( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/GenreCardGrid.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/GenreCardGrid.kt index ec2f4453..cdf18969 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/GenreCardGrid.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/GenreCardGrid.kt @@ -37,6 +37,9 @@ import com.github.damontecres.wholphin.util.GetGenresRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.LoadingExceptionHandler import com.github.damontecres.wholphin.util.LoadingState +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -55,29 +58,32 @@ import org.jellyfin.sdk.model.api.request.GetGenresRequest import org.jellyfin.sdk.model.api.request.GetItemsRequest import java.util.UUID import java.util.concurrent.ConcurrentHashMap -import javax.inject.Inject -@HiltViewModel +@HiltViewModel(assistedFactory = GenreViewModel.Factory::class) class GenreViewModel - @Inject + @AssistedInject constructor( private val api: ApiClient, private val imageUrlService: ImageUrlService, private val serverRepository: ServerRepository, val navigationManager: NavigationManager, + @Assisted private val itemId: UUID, + @Assisted private val includeItemTypes: List?, ) : ViewModel() { - private lateinit var itemId: UUID + @AssistedFactory + interface Factory { + fun create( + itemId: UUID, + includeItemTypes: List?, + ): GenreViewModel + } val item = MutableLiveData(null) val loading = MutableLiveData(LoadingState.Pending) val genres = MutableLiveData>(listOf()) - fun init( - itemId: UUID, - cardWidthPx: Int, - ) { + fun init(cardWidthPx: Int) { loading.value = LoadingState.Loading - this.itemId = itemId viewModelScope.launch(Dispatchers.IO + LoadingExceptionHandler(loading, "Failed to fetch genres")) { val item = api.userLibraryApi.getItem(itemId = itemId).content.let { @@ -89,6 +95,7 @@ class GenreViewModel userId = serverRepository.currentUser.value?.id, parentId = itemId, fields = SlimItemFields, + includeItemTypes = includeItemTypes, ) val genres = GetGenresRequestHandler @@ -97,12 +104,10 @@ class GenreViewModel .map { Genre(it.id, it.name ?: "", null, Color.Black) } -// val pager = ApiRequestPager(api, request, GetGenresRequestHandler, viewModelScope).init() withContext(Dispatchers.Main) { this@GenreViewModel.genres.value = genres loading.value = LoadingState.Success } -// val excludeItemIds = mutableSetOf() val genreToUrl = ConcurrentHashMap() val semaphore = Semaphore(4) genres @@ -114,34 +119,28 @@ class GenreViewModel .execute( api, GetItemsRequest( -// excludeItemIds = excludeItemIds, parentId = itemId, recursive = true, limit = 1, sortBy = listOf(ItemSortBy.RANDOM), fields = listOf(ItemFields.GENRES), - imageTypes = listOf(ImageType.THUMB), + imageTypes = listOf(ImageType.BACKDROP), imageTypeLimit = 1, - includeItemTypes = - listOf( - BaseItemKind.MOVIE, - BaseItemKind.SERIES, - ), + includeItemTypes = includeItemTypes, genreIds = listOf(genre.id), enableTotalRecordCount = false, ), ).content.items .firstOrNull() if (item != null) { -// excludeItemIds.add(item.id) genreToUrl[genre.id] = imageUrlService.getItemImageUrl( itemId = item.id, itemType = item.type, seriesId = null, - useSeriesForPrimary = false, + useSeriesForPrimary = true, imageType = ImageType.BACKDROP, - imageTags = emptyMap(), + imageTags = item.imageTags.orEmpty(), fillWidth = cardWidthPx, ) } @@ -186,8 +185,12 @@ data class Genre( @Composable fun GenreCardGrid( itemId: UUID, + includeItemTypes: List?, modifier: Modifier = Modifier, - viewModel: GenreViewModel = hiltViewModel(), + viewModel: GenreViewModel = + hiltViewModel( + creationCallback = { it.create(itemId, includeItemTypes) }, + ), ) { val columns = 4 val spacing = 16.dp @@ -205,7 +208,7 @@ fun GenreCardGrid( } } OneTimeLaunchedEffect { - viewModel.init(itemId, cardWidthPx) + viewModel.init(cardWidthPx) } val loading by viewModel.loading.observeAsState(LoadingState.Pending) val genres by viewModel.genres.observeAsState(listOf()) @@ -239,7 +242,11 @@ fun GenreCardGrid( genre.name, item?.title, ).joinToString(" "), - filter = GetItemsFilter(genres = listOf(genre.id)), + filter = + GetItemsFilter( + genres = listOf(genre.id), + includeItemTypes = includeItemTypes, + ), useSavedLibraryDisplayInfo = false, ), recursive = true, 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 d3c52bf7..96e1172e 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 @@ -179,6 +179,7 @@ fun CollectionFolderMovie( 3 -> { GenreCardGrid( itemId = destination.itemId, + includeItemTypes = listOf(BaseItemKind.MOVIE), modifier = Modifier .padding(start = 16.dp) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderTv.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderTv.kt index 42b13d96..c69fecb1 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderTv.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderTv.kt @@ -148,6 +148,7 @@ fun CollectionFolderTv( 2 -> { GenreCardGrid( itemId = destination.itemId, + includeItemTypes = listOf(BaseItemKind.SERIES), modifier = Modifier .padding(start = 16.dp)