Don't use saved library info for genre grids (#540)

## Description
The filtering & sorting for the genre results grid is now independent
from the actual library.

This prevents having a filter saved on the actual library that conflicts
with the Genre grid.

Also shows the genre name in the title of the page

### Related issues
Fixes #536
This commit is contained in:
Ray 2025-12-22 15:44:08 -05:00 committed by GitHub
parent af54e777dc
commit 9a08cf3f25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 139 additions and 63 deletions

View file

@ -14,6 +14,16 @@ import org.jellyfin.sdk.model.api.request.GetPersonsRequest
import org.jellyfin.sdk.model.serializer.UUIDSerializer
import java.util.UUID
@Serializable
data class CollectionFolderFilter(
val nameOverride: String? = null,
val filter: GetItemsFilter = GetItemsFilter(),
/**
* Whether to use the libray's saved sort & filter
*/
val useSavedLibraryDisplayInfo: Boolean = true,
)
@Serializable
data class GetItemsFilter(
val favorite: Boolean? = null,

View file

@ -65,7 +65,6 @@ fun GenreCard(
.fillMaxSize()
.clip(RoundedCornerShape(8.dp)),
) {
Timber.v("genre image=${genre?.imageUrl}")
if (genre?.imageUrl.isNotNullOrBlank()) {
AsyncImage(
model =

View file

@ -58,6 +58,7 @@ import com.github.damontecres.wholphin.data.filter.PlayedFilter
import com.github.damontecres.wholphin.data.filter.VideoTypeFilter
import com.github.damontecres.wholphin.data.filter.YearFilter
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.data.model.GetItemsFilterOverride
import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
@ -136,12 +137,13 @@ class CollectionFolderViewModel
val viewOptions = MutableLiveData<ViewOptions>()
private var useSeriesForPrimary: Boolean = true
private lateinit var collectionFilter: CollectionFolderFilter
fun init(
itemId: String,
initialSortAndDirection: SortAndDirection?,
recursive: Boolean,
filter: GetItemsFilter,
collectionFilter: CollectionFolderFilter,
useSeriesForPrimary: Boolean,
defaultViewOptions: ViewOptions,
): Job =
@ -151,6 +153,7 @@ class CollectionFolderViewModel
context.getString(R.string.error_loading_collection, itemId),
) + Dispatchers.IO,
) {
this@CollectionFolderViewModel.collectionFilter = collectionFilter
this@CollectionFolderViewModel.useSeriesForPrimary = useSeriesForPrimary
this@CollectionFolderViewModel.itemId = itemId
itemId.toUUIDOrNull()?.let {
@ -166,15 +169,17 @@ class CollectionFolderViewModel
)
val sortAndDirection =
libraryDisplayInfo?.sortAndDirection
?: initialSortAndDirection
?: SortAndDirection.DEFAULT
if (collectionFilter.useSavedLibraryDisplayInfo) {
libraryDisplayInfo?.sortAndDirection
} else {
null
} ?: initialSortAndDirection ?: SortAndDirection.DEFAULT
val filterToUse =
if (libraryDisplayInfo?.filter != null) {
filter.merge(libraryDisplayInfo.filter)
if (collectionFilter.useSavedLibraryDisplayInfo && libraryDisplayInfo?.filter != null) {
collectionFilter.filter.merge(libraryDisplayInfo.filter)
} else {
filter
collectionFilter.filter
}
loadResults(true, sortAndDirection, recursive, filterToUse, useSeriesForPrimary)
@ -185,18 +190,20 @@ class CollectionFolderViewModel
newSort: SortAndDirection = this.sortAndDirection.value!!,
viewOptions: ViewOptions? = this.viewOptions.value,
) {
serverRepository.currentUser.value?.let { user ->
viewModelScope.launch(Dispatchers.IO) {
val libraryDisplayInfo =
LibraryDisplayInfo(
userId = user.rowId,
itemId = itemId,
sort = newSort.sort,
direction = newSort.direction,
filter = newFilter,
viewOptions = viewOptions,
)
libraryDisplayInfoDao.saveItem(libraryDisplayInfo)
if (collectionFilter.useSavedLibraryDisplayInfo) {
serverRepository.currentUser.value?.let { user ->
viewModelScope.launch(Dispatchers.IO) {
val libraryDisplayInfo =
LibraryDisplayInfo(
userId = user.rowId,
itemId = itemId,
sort = newSort.sort,
direction = newSort.direction,
filter = newFilter,
viewOptions = viewOptions,
)
libraryDisplayInfoDao.saveItem(libraryDisplayInfo)
}
}
}
}
@ -490,7 +497,7 @@ class CollectionFolderViewModel
fun CollectionFolderGrid(
preferences: UserPreferences,
itemId: UUID,
initialFilter: GetItemsFilter,
initialFilter: CollectionFolderFilter,
recursive: Boolean,
onClickItem: (Int, BaseItem) -> Unit,
sortOptions: List<ItemSortBy>,
@ -523,7 +530,7 @@ fun CollectionFolderGrid(
fun CollectionFolderGrid(
preferences: UserPreferences,
itemId: String,
initialFilter: GetItemsFilter,
initialFilter: CollectionFolderFilter,
recursive: Boolean,
onClickItem: (Int, BaseItem) -> Unit,
sortOptions: List<ItemSortBy>,
@ -550,7 +557,7 @@ fun CollectionFolderGrid(
)
}
val sortAndDirection by viewModel.sortAndDirection.observeAsState(SortAndDirection.DEFAULT)
val filter by viewModel.filter.observeAsState(initialFilter)
val filter by viewModel.filter.observeAsState(initialFilter.filter)
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
val backgroundLoading by viewModel.backgroundLoading.observeAsState(LoadingState.Loading)
val item by viewModel.item.observeAsState()
@ -574,11 +581,17 @@ fun CollectionFolderGrid(
LoadingState.Success -> {
pager?.let { pager ->
val title =
initialFilter.nameOverride
?: item?.name
?: item?.data?.collectionType?.name
?: stringResource(R.string.collection)
Box(modifier = modifier) {
CollectionFolderGridContent(
preferences,
item,
pager,
preferences = preferences,
item = item,
title = title,
pager = pager,
sortAndDirection = sortAndDirection!!,
modifier = Modifier.fillMaxSize(),
onClickItem = onClickItem,
@ -699,6 +712,7 @@ fun CollectionFolderGrid(
fun CollectionFolderGridContent(
preferences: UserPreferences,
item: BaseItem?,
title: String,
pager: List<BaseItem?>,
sortAndDirection: SortAndDirection,
onClickItem: (Int, BaseItem) -> Unit,
@ -722,7 +736,6 @@ fun CollectionFolderGridContent(
onFilterChange: (GetItemsFilter) -> Unit = {},
) {
val context = LocalContext.current
val title = item?.name ?: item?.data?.collectionType?.name ?: stringResource(R.string.collection)
var showHeader by rememberSaveable { mutableStateOf(true) }
var showViewOptions by rememberSaveable { mutableStateOf(false) }

View file

@ -15,6 +15,9 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.services.ImageUrlService
import com.github.damontecres.wholphin.services.NavigationManager
@ -39,6 +42,7 @@ import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.api.ItemFields
@ -55,9 +59,12 @@ class GenreViewModel
constructor(
private val api: ApiClient,
private val imageUrlService: ImageUrlService,
private val serverRepository: ServerRepository,
val navigationManager: NavigationManager,
) : ViewModel() {
private lateinit var itemId: UUID
val item = MutableLiveData<BaseItem?>(null)
val loading = MutableLiveData<LoadingState>(LoadingState.Pending)
val genres = MutableLiveData<List<Genre>>(listOf())
@ -65,8 +72,14 @@ class GenreViewModel
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 {
BaseItem(it, false)
}
this@GenreViewModel.item.setValueOnMain(item)
val request =
GetGenresRequest(
userId = serverRepository.currentUser.value?.id,
parentId = itemId,
fields = SlimItemFields,
)
@ -187,13 +200,23 @@ fun GenreCardGrid(
LoadingState.Success -> {
Box(modifier = modifier) {
LaunchedEffect(Unit) { gridFocusRequester.tryRequestFocus() }
val item by viewModel.item.observeAsState(null)
CardGrid(
pager = genres,
onClickItem = { _, genre ->
viewModel.navigationManager.navigateTo(
Destination.FilteredCollection(
itemId = itemId,
filter = GetItemsFilter(genres = listOf(genre.id)),
filter =
CollectionFolderFilter(
nameOverride =
listOfNotNull(
genre.name,
item?.title,
).joinToString(" "),
filter = GetItemsFilter(genres = listOf(genre.id)),
useSavedLibraryDisplayInfo = false,
),
recursive = true,
),
)

View file

@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ViewOptionsPoster
@ -28,7 +28,7 @@ fun CollectionFolderBoxSet(
item: BaseItem?,
recursive: Boolean,
modifier: Modifier = Modifier,
filter: GetItemsFilter = GetItemsFilter(),
filter: CollectionFolderFilter = CollectionFolderFilter(),
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
playEnabled: Boolean = false,
) {

View file

@ -11,7 +11,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.github.damontecres.wholphin.data.filter.DefaultFilterOptions
import com.github.damontecres.wholphin.data.filter.ItemFilterBy
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ViewOptionsPoster
@ -29,7 +29,7 @@ fun CollectionFolderGeneric(
recursive: Boolean,
playEnabled: Boolean,
modifier: Modifier = Modifier,
filter: GetItemsFilter = GetItemsFilter(),
filter: CollectionFolderFilter = CollectionFolderFilter(),
filterOptions: List<ItemFilterBy<*>> = DefaultFilterOptions,
sortOptions: List<ItemSortBy> = VideoSortOptions,
preferencesViewModel: PreferencesViewModel = hiltViewModel(),

View file

@ -29,7 +29,7 @@ import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.NavigationManager
@ -164,7 +164,7 @@ fun CollectionFolderLiveTv(
preferences = preferences,
onClickItem = onClickItem,
itemId = folders[folderIndex].id,
initialFilter = GetItemsFilter(),
initialFilter = CollectionFolderFilter(),
showTitle = false,
recursive = false,
sortOptions = VideoSortOptions,

View file

@ -23,6 +23,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
@ -115,8 +116,11 @@ fun CollectionFolderMovie(
},
itemId = destination.itemId,
initialFilter =
GetItemsFilter(
includeItemTypes = listOf(BaseItemKind.MOVIE),
CollectionFolderFilter(
filter =
GetItemsFilter(
includeItemTypes = listOf(BaseItemKind.MOVIE),
),
),
showTitle = false,
recursive = true,
@ -143,8 +147,11 @@ fun CollectionFolderMovie(
},
itemId = destination.itemId,
initialFilter =
GetItemsFilter(
includeItemTypes = listOf(BaseItemKind.BOX_SET),
CollectionFolderFilter(
filter =
GetItemsFilter(
includeItemTypes = listOf(BaseItemKind.BOX_SET),
),
),
showTitle = false,
recursive = true,

View file

@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ViewOptionsSquare
@ -25,7 +25,7 @@ fun CollectionFolderPlaylist(
item: BaseItem?,
recursive: Boolean,
modifier: Modifier = Modifier,
filter: GetItemsFilter = GetItemsFilter(),
filter: CollectionFolderFilter = CollectionFolderFilter(),
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
) {
var showHeader by remember { mutableStateOf(true) }

View file

@ -9,7 +9,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ViewOptionsPoster
@ -23,7 +23,7 @@ fun CollectionFolderRecordings(
itemId: UUID,
recursive: Boolean,
modifier: Modifier = Modifier,
filter: GetItemsFilter = GetItemsFilter(),
filter: CollectionFolderFilter = CollectionFolderFilter(),
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
) {
var showHeader by remember { mutableStateOf(true) }

View file

@ -24,6 +24,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
@ -115,8 +116,11 @@ fun CollectionFolderTv(
preferences = preferences,
itemId = destination.itemId,
initialFilter =
GetItemsFilter(
includeItemTypes = listOf(BaseItemKind.SERIES),
CollectionFolderFilter(
filter =
GetItemsFilter(
includeItemTypes = listOf(BaseItemKind.SERIES),
),
),
showTitle = false,
recursive = true,

View file

@ -25,6 +25,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.filter.DefaultForFavoritesFilterOptions
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.data.model.GetItemsFilterOverride
import com.github.damontecres.wholphin.preferences.UserPreferences
@ -117,9 +118,12 @@ fun FavoritesPage(
onClickItem = { _, item -> onClickItem.invoke(item) },
itemId = "${NavDrawerItem.Favorites.id}_movies",
initialFilter =
GetItemsFilter(
favorite = true,
includeItemTypes = listOf(BaseItemKind.MOVIE),
CollectionFolderFilter(
filter =
GetItemsFilter(
favorite = true,
includeItemTypes = listOf(BaseItemKind.MOVIE),
),
),
showTitle = false,
recursive = true,
@ -145,9 +149,12 @@ fun FavoritesPage(
onClickItem = { _, item -> onClickItem.invoke(item) },
itemId = "${NavDrawerItem.Favorites.id}_series",
initialFilter =
GetItemsFilter(
favorite = true,
includeItemTypes = listOf(BaseItemKind.SERIES),
CollectionFolderFilter(
filter =
GetItemsFilter(
favorite = true,
includeItemTypes = listOf(BaseItemKind.SERIES),
),
),
showTitle = false,
recursive = true,
@ -173,9 +180,12 @@ fun FavoritesPage(
onClickItem = { _, item -> onClickItem.invoke(item) },
itemId = "${NavDrawerItem.Favorites.id}_episodes",
initialFilter =
GetItemsFilter(
favorite = true,
includeItemTypes = listOf(BaseItemKind.EPISODE),
CollectionFolderFilter(
filter =
GetItemsFilter(
favorite = true,
includeItemTypes = listOf(BaseItemKind.EPISODE),
),
),
showTitle = false,
recursive = true,
@ -202,9 +212,12 @@ fun FavoritesPage(
onClickItem = { _, item -> onClickItem.invoke(item) },
itemId = "${NavDrawerItem.Favorites.id}_videos",
initialFilter =
GetItemsFilter(
favorite = true,
includeItemTypes = listOf(BaseItemKind.VIDEO),
CollectionFolderFilter(
filter =
GetItemsFilter(
favorite = true,
includeItemTypes = listOf(BaseItemKind.VIDEO),
),
),
showTitle = false,
recursive = true,
@ -230,9 +243,12 @@ fun FavoritesPage(
onClickItem = { _, item -> onClickItem.invoke(item) },
itemId = "${NavDrawerItem.Favorites.id}_playlists",
initialFilter =
GetItemsFilter(
favorite = true,
includeItemTypes = listOf(BaseItemKind.PLAYLIST),
CollectionFolderFilter(
filter =
GetItemsFilter(
favorite = true,
includeItemTypes = listOf(BaseItemKind.PLAYLIST),
),
),
showTitle = false,
recursive = true,
@ -258,9 +274,12 @@ fun FavoritesPage(
onClickItem = { _, item -> onClickItem.invoke(item) },
itemId = "${NavDrawerItem.Favorites.id}_people",
initialFilter =
GetItemsFilter(
favorite = true,
override = GetItemsFilterOverride.PERSON,
CollectionFolderFilter(
filter =
GetItemsFilter(
favorite = true,
override = GetItemsFilterOverride.PERSON,
),
),
initialSortAndDirection =
SortAndDirection(

View file

@ -5,6 +5,7 @@ package com.github.damontecres.wholphin.ui.nav
import androidx.annotation.StringRes
import androidx.navigation3.runtime.NavKey
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.data.model.ItemPlayback
import com.github.damontecres.wholphin.ui.data.SortAndDirection
@ -94,7 +95,7 @@ sealed class Destination(
@Serializable
data class FilteredCollection(
val itemId: UUID,
val filter: GetItemsFilter,
val filter: CollectionFolderFilter,
val recursive: Boolean,
) : Destination(false)