Fix the alphabet buttons scroll bar on the Artists tab (#1775)

## Description
positionOfLetter counted through /Items no matter which endpoint the
grid was actually paging. A music library's Artists tab pages /Artists,
so the index it returned had counted every album and song before the
letter - an order of magnitude more rows than the artist grid holds -
and every letter jumped to the last row.

Count through the same endpoint createPager builds its pager from:
/Artists for the ARTIST override, /Items for NONE. The artist request is
now built once in createGetArtistsRequest and shared by both, so the two
cannot drift apart.

GetPersonsRequest has neither nameLessThan nor startIndex, so /Persons
cannot be counted up to a letter at all; the PERSON override returns
null, which the call sites already map to the "no jump" sentinel. That
path is unreachable today - the only PERSON tab sorts by
ItemSortBy.DEFAULT, so showLetterButtons is never true there and
AlphabetButtons is never composed.

### Related issues
https://github.com/damontecres/Wholphin/issues/1774

### Testing
Select a music library -> Go to Artist tab -> Select a letter on the
right side alphabet scroll bar

## AI or LLM usage
I used Cursor and Claude Code for planning, implementation and code
review.

---------

Co-authored-by: Damontecres <damontecres@gmail.com>
This commit is contained in:
Stefan Bullones 2026-08-03 15:54:03 +02:00 • committed by GitHub
parent 6ad96865e7
commit d7d63f52c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 259 additions and 33 deletions

View file

@ -71,6 +71,7 @@ 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
@ -378,26 +379,30 @@ class CollectionFolderViewModel
}
GetItemsFilterOverride.ARTIST -> {
val item = state.value.item.successValue
val request =
filter.applyTo(
GetArtistsRequest(
parentId = item?.id,
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
),
)
val newPager =
ApiRequestPager(
api,
request,
GetArtistsHandler,
viewModelScope,
useSeriesForPrimary = useSeriesForPrimary,
)
newPager
ApiRequestPager(
api,
createGetArtistsRequest(filter),
GetArtistsHandler,
viewModelScope,
useSeriesForPrimary = useSeriesForPrimary,
)
}
}
/**
* Shared with [positionOfLetter] so that the alphabet jump counts the same artists that are
* actually paged through
*/
private fun createGetArtistsRequest(filter: GetItemsFilter): GetArtistsRequest {
val item = state.value.item.successValue
return filter.applyTo(
GetArtistsRequest(
parentId = item?.id,
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
),
)
}
private fun createGetItemsRequest(
sortAndDirection: SortAndDirection,
recursive: Boolean,
@ -460,26 +465,57 @@ class CollectionFolderViewModel
filterOption,
)
/**
* The count must come from the same endpoint that [createPager] pages through, otherwise
* the index refers to a different result set and lands past the end of the list.
*/
suspend fun positionOfLetter(letter: Char): Int? =
withContext(WholphinDispatchers.IO) {
val sort = state.value.sortAndDirection
val filter = state.value.filter
val request =
createGetItemsRequest(
sortAndDirection = sort,
recursive = recursive,
filter = filter,
).copy(
enableImageTypes = null,
fields = null,
nameLessThan = letter.toString(),
limit = 0,
enableTotalRecordCount = true,
)
val result by GetItemsRequestHandler.execute(api, request)
result.totalRecordCount
when (filter.override) {
GetItemsFilterOverride.ARTIST -> {
GetArtistsHandler.countMatching(
createGetArtistsRequest(filter).copy(
enableImageTypes = null,
nameLessThan = letter.toString(),
limit = 0,
enableTotalRecordCount = true,
),
)
}
// GetPersonsRequest has no nameLessThan or startIndex, so /Persons cannot be
// counted up to a letter at all
GetItemsFilterOverride.PERSON -> {
null
}
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,
),
)
}
}
}
/**
* [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,