Update item fields queried & expand usage of primary aspect ratio (#1852)

## Description
Slight optimization to remove "child count" for grid pages. Adds back
"primary image aspect ratio" for several queries where its necessary.

Also updates extras cards to use the "primary image aspect ratio"
instead of a hard coded value.

Dev note: some light refactoring is also in this PR including renaming
the item fields lists to better reflect the intended usage in code

### Related issues
Fixes #1843

### Testing
Emulator, I tested many pages and views, but probably need to do "real
world" testing to see if I missed anything

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-08-17 22:54:15 -04:00 • committed by GitHub
parent c4264a3263
commit b064af7c4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 105 additions and 83 deletions

View file

@ -27,6 +27,7 @@ sealed interface ExtrasItem {
val subtitle: String?
val isPlayed: Boolean
val playedPercentage: Double
val aspectRatio: Float?
/**
* Represents multiple extras of the same type
@ -39,6 +40,7 @@ sealed interface ExtrasItem {
override val title: String,
override val subtitle: String,
override val isPlayed: Boolean,
override val aspectRatio: Float?,
) : ExtrasItem {
override val destination: Destination =
Destination.ItemGrid(
@ -82,6 +84,8 @@ sealed interface ExtrasItem {
get() = item.played
override val playedPercentage
get() = item.data.userData?.playedPercentage ?: 0.0
override val aspectRatio: Float?
get() = item.aspectRatio
}
}

View file

@ -74,6 +74,7 @@ class ExtrasService
items.size,
items.size,
)
val itemForImage = items.random()
ExtrasItem.Group(
parentId = itemId,
type = type,
@ -83,9 +84,10 @@ class ExtrasService
isPlayed = items.all { it.played },
imageUrl =
imageUrlService.getItemImageUrl(
items.random(),
itemForImage,
ImageType.PRIMARY,
),
aspectRatio = itemForImage.aspectRatio,
)
} else {
null

View file

@ -15,7 +15,7 @@ import com.github.damontecres.wholphin.data.model.AudioItem
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.get
import com.github.damontecres.wholphin.services.hilt.DefaultCoroutineScope
import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.DetailItemFields
import com.github.damontecres.wholphin.ui.gt
import com.github.damontecres.wholphin.ui.main.settings.MoveDirection
import com.github.damontecres.wholphin.ui.onMain
@ -180,7 +180,7 @@ class MusicService
userId = serverRepository.currentUser?.id,
itemId = itemId,
limit = 200,
fields = DefaultItemFields,
fields = DetailItemFields,
).content.items
.map { BaseItem(it, false) }
setQueue(items, false)

View file

@ -8,7 +8,7 @@ import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.data.model.Playlist
import com.github.damontecres.wholphin.data.model.PlaylistInfo
import com.github.damontecres.wholphin.data.model.PlaylistItem
import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.DetailItemFields
import com.github.damontecres.wholphin.ui.components.baseItemKinds
import com.github.damontecres.wholphin.ui.data.SortAndDirection
import com.github.damontecres.wholphin.ui.gt
@ -65,7 +65,7 @@ class PlaylistCreator
GetEpisodesRequest(
seriesId = seriesId,
seasonId = seasonId,
fields = DefaultItemFields,
fields = DetailItemFields,
startItemId = episodeId,
sortBy = if (shuffled) ItemSortBy.RANDOM else null,
limit = Playlist.MAX_SIZE,
@ -94,7 +94,7 @@ class PlaylistCreator
GetItemsRequest(
userId = serverRepository.currentUser?.id,
parentId = playlistId,
fields = DefaultItemFields,
fields = DetailItemFields,
startIndex = startIndex,
limit = Playlist.MAX_SIZE,
sortBy = listOf(sortAndDirection.sort),
@ -135,7 +135,7 @@ class PlaylistCreator
excludeItemIds = listOf(item.id),
sortBy = sortAndDirection?.let { listOf(sortAndDirection.sort) },
sortOrder = sortAndDirection?.let { listOf(sortAndDirection.direction) },
fields = DefaultItemFields,
fields = DetailItemFields,
startIndex = startIndex,
limit = Playlist.MAX_SIZE,
excludeLocationTypes = listOf(LocationType.VIRTUAL),
@ -208,7 +208,7 @@ class PlaylistCreator
seasonId = item.id,
limit = Playlist.MAX_SIZE,
sortBy = ItemSortBy.RANDOM,
fields = DefaultItemFields,
fields = DetailItemFields,
).content.items
.convertAndAddParts()
.let {
@ -236,7 +236,7 @@ class PlaylistCreator
seriesId = item.id,
limit = Playlist.MAX_SIZE,
sortBy = ItemSortBy.RANDOM,
fields = DefaultItemFields,
fields = DetailItemFields,
).content.items
.convertAndAddParts()
.let {

View file

@ -45,11 +45,10 @@ object AppColors {
const val DEFAULT_PAGE_SIZE = 100
/**
* The default [ItemFields] to fetch for most queries
* The [ItemFields] to fetch for detailed paged and playback
*/
val DefaultItemFields =
val DetailItemFields =
listOf(
ItemFields.CHILD_COUNT,
ItemFields.OVERVIEW,
ItemFields.TRICKPLAY,
ItemFields.SORT_NAME,
@ -57,6 +56,7 @@ val DefaultItemFields =
ItemFields.MEDIA_SOURCES,
ItemFields.MEDIA_SOURCE_COUNT,
ItemFields.CAN_DELETE,
ItemFields.PRIMARY_IMAGE_ASPECT_RATIO,
)
/**
@ -64,13 +64,17 @@ val DefaultItemFields =
*/
val SlimItemFields =
listOf(
ItemFields.CHILD_COUNT,
ItemFields.OVERVIEW,
ItemFields.SORT_NAME,
ItemFields.MEDIA_SOURCE_COUNT,
ItemFields.CAN_DELETE,
)
/**
* ItemFields for displaying items in rows such as in a [com.github.damontecres.wholphin.ui.cards.ItemRow] with [com.github.damontecres.wholphin.ui.cards.SeasonCard]
*/
val ItemRowFields = SlimItemFields + ItemFields.PRIMARY_IMAGE_ASPECT_RATIO
val HomeItemFields =
listOf(
ItemFields.OVERVIEW,
@ -78,13 +82,13 @@ val HomeItemFields =
)
val PhotoItemFields =
DefaultItemFields +
DetailItemFields +
listOf(
ItemFields.WIDTH,
ItemFields.HEIGHT,
)
val ProgramItemFields = DefaultItemFields + listOf(ItemFields.CHANNEL_INFO)
val ProgramItemFields = DetailItemFields + listOf(ItemFields.CHANNEL_INFO)
object Cards {
const val HEIGHT_2X3_DP = 172

View file

@ -38,7 +38,7 @@ fun ExtrasRow(
unplayedItemCount = -1,
playedPercentage = item?.playedPercentage ?: 0.0,
numberOfVersions = -1,
aspectRatio = AspectRatios.FOUR_THREE, // TODO
aspectRatio = item?.aspectRatio ?: AspectRatios.WIDE,
)
},
modifier = modifier,

View file

@ -71,7 +71,6 @@ import com.github.damontecres.wholphin.util.GetArtistsHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.GetPersonsHandler
import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.RequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.successValue
import dagger.assisted.Assisted
@ -476,13 +475,16 @@ class CollectionFolderViewModel
when (filter.override) {
GetItemsFilterOverride.ARTIST -> {
GetArtistsHandler.countMatching(
createGetArtistsRequest(filter).copy(
enableImageTypes = null,
fields = null,
nameLessThan = letter.toString(),
limit = 0,
enableTotalRecordCount = true,
),
api = api,
request =
createGetArtistsRequest(filter).copy(
enableImageTypes = null,
fields = null,
nameLessThan = letter.toString(),
limit = 0,
enableTotalRecordCount = true,
enableUserData = false,
),
)
}
@ -494,30 +496,25 @@ class CollectionFolderViewModel
GetItemsFilterOverride.NONE -> {
GetItemsRequestHandler.countMatching(
createGetItemsRequest(
sortAndDirection = state.value.sortAndDirection,
recursive = recursive,
filter = filter,
).copy(
enableImageTypes = null,
fields = null,
nameLessThan = letter.toString(),
limit = 0,
enableTotalRecordCount = true,
),
api = api,
request =
createGetItemsRequest(
sortAndDirection = state.value.sortAndDirection,
recursive = recursive,
filter = filter,
).copy(
enableImageTypes = null,
fields = null,
nameLessThan = letter.toString(),
limit = 0,
enableTotalRecordCount = true,
enableUserData = false,
),
)
}
}
}
/**
* [request] must ask for the count, ie `limit = 0` and `enableTotalRecordCount = true`
*/
private suspend fun <T> RequestHandler<T>.countMatching(request: T): Int {
val result by execute(api, request)
return result.totalRecordCount
}
override fun setWatched(
position: Int,
itemId: UUID,

View file

@ -149,16 +149,17 @@ class GenreViewModel
suspend fun positionOfLetter(letter: Char): Int =
withContext(WholphinDispatchers.IO) {
val request =
GetGenresRequest(
parentId = itemId,
nameLessThan = letter.toString(),
limit = 0,
enableTotalRecordCount = true,
includeItemTypes = includeItemTypes,
)
val result by GetGenresRequestHandler.execute(api, request)
return@withContext result.totalRecordCount
GetGenresRequestHandler.countMatching(
api = api,
request =
GetGenresRequest(
parentId = itemId,
nameLessThan = letter.toString(),
limit = 0,
enableTotalRecordCount = true,
includeItemTypes = includeItemTypes,
),
)
}
}

View file

@ -120,17 +120,18 @@ class StudioViewModel
suspend fun positionOfLetter(letter: Char): Int =
withContext(WholphinDispatchers.IO) {
val request =
GetStudiosRequest(
userId = serverRepository.currentUser?.id,
parentId = itemId,
nameLessThan = letter.toString(),
limit = 0,
enableTotalRecordCount = true,
includeItemTypes = includeItemTypes,
)
val result by GetStudiosRequestHandler.execute(api, request)
return@withContext result.totalRecordCount
GetStudiosRequestHandler.countMatching(
api = api,
request =
GetStudiosRequest(
userId = serverRepository.currentUser?.id,
parentId = itemId,
nameLessThan = letter.toString(),
limit = 0,
enableTotalRecordCount = true,
includeItemTypes = includeItemTypes,
),
)
}
}

View file

@ -70,7 +70,7 @@ import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.MusicService
import com.github.damontecres.wholphin.services.MusicServiceState
import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.cards.ItemCardImage
import com.github.damontecres.wholphin.ui.components.BasicDialog
import com.github.damontecres.wholphin.ui.components.ContextMenu
@ -225,7 +225,7 @@ class PlaylistViewModel
GetItemsRequest(
parentId = itemId,
userId = user.id,
fields = DefaultItemFields,
fields = SlimItemFields,
sortBy = listOf(sortAndDirection.sort),
sortOrder = listOf(sortAndDirection.direction),
),

View file

@ -26,7 +26,7 @@ import com.github.damontecres.wholphin.services.ThemeSongPlayer
import com.github.damontecres.wholphin.services.TrailerService
import com.github.damontecres.wholphin.services.UserPreferencesService
import com.github.damontecres.wholphin.services.deleteItem
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.ItemRowFields
import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.letNotEmpty
@ -168,7 +168,7 @@ class MovieViewModel
GetSimilarItemsRequest(
userId = serverRepository.currentUser?.id,
itemId = itemId,
fields = SlimItemFields,
fields = ItemRowFields,
limit = 25,
),
).content.items

View file

@ -58,7 +58,7 @@ import com.github.damontecres.wholphin.services.MusicService
import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.UserPreferencesService
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.ItemRowFields
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
import com.github.damontecres.wholphin.ui.cards.ExtrasRow
@ -211,7 +211,7 @@ class AlbumViewModel
userId = serverRepository.currentUser?.id,
albumIds = listOf(itemId),
parentId = null,
fields = DefaultItemFields,
fields = ItemRowFields,
recursive = true,
includeItemTypes = listOf(BaseItemKind.MUSIC_VIDEO),
)
@ -563,7 +563,7 @@ fun AlbumDetailsPage(
item = item,
onClick = onClick,
onLongClick = onLongClick,
aspectRatio = AspectRatios.WIDE,
aspectRatio = item?.aspectRatio ?: AspectRatios.WIDE,
played = item?.played ?: false,
playPercent = item?.data?.userData?.playedPercentage ?: 0.0,
favorite = item?.favorite ?: false,

View file

@ -54,7 +54,7 @@ import com.github.damontecres.wholphin.services.MusicService
import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.UserPreferencesService
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.ItemRowFields
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
import com.github.damontecres.wholphin.ui.cards.ItemRow
@ -157,7 +157,7 @@ class ArtistViewModel
GetItemsRequest(
albumArtistIds = listOf(itemId),
recursive = true,
fields = DefaultItemFields,
fields = SlimItemFields,
includeItemTypes = listOf(BaseItemKind.MUSIC_ALBUM),
sortBy =
listOf(
@ -174,7 +174,7 @@ class ArtistViewModel
GetItemsRequest(
contributingArtistIds = listOf(itemId),
recursive = true,
fields = DefaultItemFields,
fields = SlimItemFields,
includeItemTypes = listOf(BaseItemKind.MUSIC_ALBUM),
sortBy =
listOf(
@ -208,7 +208,7 @@ class ArtistViewModel
val request =
GetItemsRequest(
artistIds = listOf(itemId),
fields = DefaultItemFields,
fields = SlimItemFields,
recursive = true,
includeItemTypes = listOf(BaseItemKind.AUDIO),
minCommunityRating = 1.0,
@ -266,7 +266,7 @@ class ArtistViewModel
userId = serverRepository.currentUser?.id,
artistIds = listOf(itemId),
parentId = null,
fields = DefaultItemFields,
fields = ItemRowFields,
recursive = true,
includeItemTypes = listOf(BaseItemKind.MUSIC_VIDEO),
)
@ -671,7 +671,7 @@ fun ArtistDetailsPage(
item = item,
onClick = onClick,
onLongClick = onLongClick,
aspectRatio = AspectRatios.WIDE,
aspectRatio = item?.aspectRatio ?: AspectRatios.WIDE,
played = item?.played ?: false,
playPercent = item?.data?.userData?.playedPercentage ?: 0.0,
favorite = item?.favorite ?: false,

View file

@ -2,7 +2,7 @@ package com.github.damontecres.wholphin.ui.detail.music
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.DetailItemFields
import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import org.jellyfin.sdk.api.client.ApiClient
@ -19,7 +19,7 @@ suspend fun ViewModel.getPagerForAlbum(
GetItemsRequest(
parentId = albumId,
includeItemTypes = listOf(BaseItemKind.AUDIO),
fields = DefaultItemFields,
fields = DetailItemFields,
sortBy =
listOf(
ItemSortBy.PARENT_INDEX_NUMBER,
@ -39,7 +39,7 @@ suspend fun ViewModel.getPagerForArtist(
artistIds = listOf(artistId),
recursive = true,
includeItemTypes = listOf(BaseItemKind.AUDIO),
fields = DefaultItemFields,
fields = DetailItemFields,
// TODO better sort
sortBy =
listOf(
@ -60,7 +60,7 @@ suspend fun ViewModel.getPagerForPlaylist(
parentId = playlistId,
recursive = true,
includeItemTypes = listOf(BaseItemKind.AUDIO),
fields = DefaultItemFields,
fields = DetailItemFields,
sortBy =
listOf(
ItemSortBy.DEFAULT,

View file

@ -27,7 +27,7 @@ import com.github.damontecres.wholphin.services.ThemeSongPlayer
import com.github.damontecres.wholphin.services.TrailerService
import com.github.damontecres.wholphin.services.UserPreferencesService
import com.github.damontecres.wholphin.services.deleteItem
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.ItemRowFields
import com.github.damontecres.wholphin.ui.equalsNotNull
import com.github.damontecres.wholphin.ui.gt
import com.github.damontecres.wholphin.ui.launchDefault
@ -232,7 +232,7 @@ class SeriesViewModel
GetSimilarItemsRequest(
userId = serverRepository.currentUser?.id,
itemId = seriesId,
fields = SlimItemFields,
fields = ItemRowFields,
limit = 25,
),
).content.items

View file

@ -131,6 +131,19 @@ sealed interface RequestHandler<T> {
api: ApiClient,
request: T,
): Response<BaseItemDtoQueryResult>
/**
* Count the total number of possible results for the request.
*
* [request] should be efficient and use `limit = 0` and `enableTotalRecordCount = true`
*/
suspend fun countMatching(
api: ApiClient,
request: T,
): Int {
val result by execute(api, request)
return result.totalRecordCount
}
}
@Serializable