Fixes missing parental ratings & focus loss when changing filters (#1559)

## Description
- Ensures all parent ratings are shown in the filter drop down
- Fixes focus loss after filtering library and there are no results

### Related issues
Fixes #1535 (along w/ #1529)
Fixes #1534

### Testing
Emulator w/ a UK movie library

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-06-10 19:29:49 -04:00 • committed by GitHub
parent 01bd3249fa
commit f7f0ea9cca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 33 additions and 11 deletions

View file

@ -124,9 +124,9 @@ class FilterOptionCache
parentId = parentId, parentId = parentId,
userId = userId, userId = userId,
).content.officialRatings ).content.officialRatings
?.mapNotNull { r -> ?.map { r ->
val value = ratings[r] val value = ratings[r] ?: 0
value?.let { FilterValueOption(r, value) } FilterValueOption(r, value)
}?.sortedBy { it.value as Int } }?.sortedBy { it.value as Int }
.orEmpty() .orEmpty()
} }

View file

@ -21,6 +21,8 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -270,6 +272,8 @@ fun CollectionFolderHeader(
currentFilter: GetItemsFilter = GetItemsFilter(), currentFilter: GetItemsFilter = GetItemsFilter(),
filterOptions: List<ItemFilterBy<*>> = listOf(), filterOptions: List<ItemFilterBy<*>> = listOf(),
onFilterChange: (GetItemsFilter) -> Unit = {}, onFilterChange: (GetItemsFilter) -> Unit = {},
onShowFilterDropdown: ((Boolean) -> Unit)? = null,
filterButtonFocusRequester: FocusRequester = remember { FocusRequester() },
) { ) {
AnimatedVisibility( AnimatedVisibility(
showHeader, showHeader,
@ -292,13 +296,14 @@ fun CollectionFolderHeader(
modifier = modifier =
Modifier Modifier
.padding(start = 16.dp, end = endPadding) .padding(start = 16.dp, end = endPadding)
.focusRestorer()
.fillMaxWidth(), .fillMaxWidth(),
) { ) {
if (sortOptions.isNotEmpty() || filterOptions.isNotEmpty()) { if (sortOptions.isNotEmpty() || filterOptions.isNotEmpty()) {
Row( Row(
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier, modifier = Modifier.focusRestorer(),
) { ) {
if (sortOptions.isNotEmpty()) { if (sortOptions.isNotEmpty()) {
SortByButton( SortByButton(
@ -314,7 +319,8 @@ fun CollectionFolderHeader(
current = currentFilter, current = currentFilter,
onFilterChange = onFilterChange, onFilterChange = onFilterChange,
getPossibleValues = getPossibleFilterValues, getPossibleValues = getPossibleFilterValues,
modifier = Modifier, modifier = Modifier.focusRequester(filterButtonFocusRequester),
onShow = onShowFilterDropdown,
) )
} }
ExpandableFaButton( ExpandableFaButton(
@ -329,7 +335,7 @@ fun CollectionFolderHeader(
Row( Row(
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier, modifier = Modifier.focusRestorer(),
) { ) {
ExpandablePlayButton( ExpandablePlayButton(
title = R.string.play, title = R.string.play,

View file

@ -659,6 +659,9 @@ fun CollectionFolderView(
val contextMenu = rememberContextMenu(preferences, viewModel) val contextMenu = rememberContextMenu(preferences, viewModel)
var showViewOptions by rememberSaveable { mutableStateOf(false) } var showViewOptions by rememberSaveable { mutableStateOf(false) }
var filterDropdownShowing by remember { mutableStateOf(false) }
val headerRowFocusRequester = remember { FocusRequester() }
val filterButtonFocusRequester = remember { FocusRequester() }
val gridActions = val gridActions =
remember(actions) { remember(actions) {
@ -741,7 +744,6 @@ fun CollectionFolderView(
?: item?.data?.collectionType?.name ?: item?.data?.collectionType?.name
?: stringResource(R.string.collection) ?: stringResource(R.string.collection)
Column(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) {
val headerRowFocusRequester = remember { FocusRequester() }
LifecycleResumeEffect(itemId) { LifecycleResumeEffect(itemId) {
viewModel.onResumePage() viewModel.onResumePage()
@ -780,6 +782,8 @@ fun CollectionFolderView(
onClickPlayAll = gridActions.onClickPlayAll!!, onClickPlayAll = gridActions.onClickPlayAll!!,
onClickShowViewOptions = { showViewOptions = true }, onClickShowViewOptions = { showViewOptions = true },
modifier = Modifier.focusRequester(headerRowFocusRequester), modifier = Modifier.focusRequester(headerRowFocusRequester),
onShowFilterDropdown = { filterDropdownShowing = it },
filterButtonFocusRequester = filterButtonFocusRequester,
) )
when (val pager = state.items) { when (val pager = state.items) {
@ -798,9 +802,14 @@ fun CollectionFolderView(
is DataLoadingState.Success<List<BaseItem?>> -> { is DataLoadingState.Success<List<BaseItem?>> -> {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (pager.data.isNotEmpty()) { val focusRequester =
gridFocusRequester.tryRequestFocus() when {
} contextMenu.isShowing -> null
filterDropdownShowing -> filterButtonFocusRequester
pager.data.isNotEmpty() -> gridFocusRequester
else -> focusRequesterOnEmpty ?: headerRowFocusRequester
}
focusRequester?.tryRequestFocus()
} }
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize()) {
if (state.viewOptions.type == ViewOptionsType.GRID) { if (state.viewOptions.type == ViewOptionsType.GRID) {

View file

@ -36,6 +36,8 @@ class ContextMenuUtils(
) { ) {
private var showContextMenu by mutableStateOf<Pair<Int, BaseItem>?>(null) private var showContextMenu by mutableStateOf<Pair<Int, BaseItem>?>(null)
val isShowing: Boolean get() = showContextMenu != null
/** /**
* Show the context menu, typically from a long click * Show the context menu, typically from a long click
*/ */

View file

@ -71,6 +71,7 @@ fun FilterByButton(
onFilterChange: (GetItemsFilter) -> Unit, onFilterChange: (GetItemsFilter) -> Unit,
getPossibleValues: suspend (ItemFilterBy<*>) -> List<FilterValueOption>, getPossibleValues: suspend (ItemFilterBy<*>) -> List<FilterValueOption>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onShow: ((Boolean) -> Unit)? = null,
) { ) {
var dropDown by remember { mutableStateOf(false) } var dropDown by remember { mutableStateOf(false) }
var nestedDropDown by remember { mutableStateOf<ItemFilterBy<*>?>(null) } var nestedDropDown by remember { mutableStateOf<ItemFilterBy<*>?>(null) }
@ -79,7 +80,10 @@ fun FilterByButton(
Box(modifier = modifier) { Box(modifier = modifier) {
ExpandableFilterButton( ExpandableFilterButton(
filterCount = filterCount, filterCount = filterCount,
onClick = { dropDown = true }, onClick = {
onShow?.invoke(true)
dropDown = true
},
modifier = Modifier, modifier = Modifier,
) )
@ -87,6 +91,7 @@ fun FilterByButton(
expanded = dropDown, expanded = dropDown,
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp), containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
onDismissRequest = { onDismissRequest = {
onShow?.invoke(false)
dropDown = false dropDown = false
nestedDropDown = null nestedDropDown = null
}, },