Fixes favorites page not loading (#1462)

## Description
- Fixes a bug introduced in #1453 which broke the favorite page
- Follow up to #1428 to refactor the collection folder code to hoist
state up another level
- Fixes resuming playback from series details play button

### Related issues
Fixes #1313
Fixes #1461
Follow up to #1453 & #1428 

### Testing
Emulator

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-05-25 20:05:44 -04:00 committed by GitHub
parent 510160aceb
commit 23856ffcf8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 294 additions and 396 deletions

View file

@ -16,21 +16,13 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.NonRestartableComposable import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
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.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
@ -44,17 +36,13 @@ import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.AspectRatios import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus
import com.github.damontecres.wholphin.ui.cards.GridCard import com.github.damontecres.wholphin.ui.cards.GridCard
import com.github.damontecres.wholphin.ui.data.SortAndDirection import com.github.damontecres.wholphin.ui.data.SortAndDirection
import com.github.damontecres.wholphin.ui.detail.CardGrid import com.github.damontecres.wholphin.ui.detail.CardGrid
import com.github.damontecres.wholphin.ui.detail.GridItemDetails import com.github.damontecres.wholphin.ui.detail.GridItemDetails
import com.github.damontecres.wholphin.ui.main.HomePageHeader import com.github.damontecres.wholphin.ui.main.HomePageHeader
import com.github.damontecres.wholphin.ui.playback.scale import com.github.damontecres.wholphin.ui.playback.scale
import com.github.damontecres.wholphin.ui.rememberInt
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.ui.util.ScrollToTopBringIntoViewSpec import com.github.damontecres.wholphin.ui.util.ScrollToTopBringIntoViewSpec
import com.github.damontecres.wholphin.util.DataLoadingState
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.CollectionType import org.jellyfin.sdk.model.api.CollectionType
import org.jellyfin.sdk.model.api.ItemSortBy import org.jellyfin.sdk.model.api.ItemSortBy
@ -64,76 +52,25 @@ import kotlin.time.Duration
@Composable @Composable
fun CollectionFolderGrid( fun CollectionFolderGrid(
preferences: UserPreferences, preferences: UserPreferences,
item: BaseItem?, collectionType: CollectionType?,
title: String, focusedItem: BaseItem?,
items: DataLoadingState<List<BaseItem?>>, items: List<BaseItem?>,
sortAndDirection: SortAndDirection, sortAndDirection: SortAndDirection,
onClickItem: (Int, BaseItem) -> Unit, onClickItem: (Int, BaseItem) -> Unit,
onLongClickItem: (Int, BaseItem) -> Unit, onLongClickItem: (Int, BaseItem) -> Unit,
onSortChange: (SortAndDirection) -> Unit,
letterPosition: suspend (Char) -> Int, letterPosition: suspend (Char) -> Int,
sortOptions: List<ItemSortBy>,
playEnabled: Boolean,
getPossibleFilterValues: suspend (ItemFilterBy<*>) -> List<FilterValueOption>,
viewOptions: ViewOptions, viewOptions: ViewOptions,
onClickPlayAll: (shuffle: Boolean) -> Unit,
onClickPlay: (Int, BaseItem) -> Unit, onClickPlay: (Int, BaseItem) -> Unit,
onChangeBackdrop: (BaseItem) -> Unit,
onClickShowViewOptions: () -> Unit,
initialPosition: Int, initialPosition: Int,
gridFocusRequester: FocusRequester,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
showTitle: Boolean = true,
positionCallback: ((columns: Int, position: Int) -> Unit)? = null, positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
currentFilter: GetItemsFilter = GetItemsFilter(),
filterOptions: List<ItemFilterBy<*>> = listOf(),
onFilterChange: (GetItemsFilter) -> Unit = {},
focusRequesterOnEmpty: FocusRequester? = null,
) { ) {
val context = LocalContext.current
val pager = (items as? DataLoadingState.Success)?.data
var showHeader by rememberSaveable { mutableStateOf(true) }
val headerRowFocusRequester = remember { FocusRequester() }
val gridFocusRequester = remember { FocusRequester() }
if (pager?.isNotEmpty() == true) {
RequestOrRestoreFocus(gridFocusRequester)
} else {
LaunchedEffect(Unit) {
(focusRequesterOnEmpty ?: headerRowFocusRequester).tryRequestFocus()
}
}
var backdropImageUrl by remember { mutableStateOf<String?>(null) }
var position by rememberInt(initialPosition)
val focusedItem = pager?.getOrNull(position)
if (viewOptions.showBackdrop) {
LaunchedEffect(focusedItem) {
focusedItem?.let(onChangeBackdrop)
}
}
Box(modifier = modifier) { Box(modifier = modifier) {
Column( Column(
verticalArrangement = Arrangement.spacedBy(0.dp), verticalArrangement = Arrangement.spacedBy(0.dp),
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
) { ) {
CollectionFolderHeader(
showHeader = showHeader || items !is DataLoadingState.Success,
showTitle = showTitle,
playEnabled = playEnabled && pager?.isNotEmpty() == true,
title = title,
sortAndDirection = sortAndDirection,
onSortChange = onSortChange,
sortOptions = sortOptions,
getPossibleFilterValues = getPossibleFilterValues,
onClickShowViewOptions = onClickShowViewOptions,
onClickPlayAll = onClickPlayAll,
currentFilter = currentFilter,
filterOptions = filterOptions,
onFilterChange = onFilterChange,
modifier = Modifier.focusRequester(headerRowFocusRequester),
)
val defaultBringIntoViewSpec = LocalBringIntoViewSpec.current val defaultBringIntoViewSpec = LocalBringIntoViewSpec.current
val density = LocalDensity.current val density = LocalDensity.current
AnimatedVisibility(viewOptions.showDetails) { AnimatedVisibility(viewOptions.showDetails) {
@ -147,21 +84,8 @@ fun CollectionFolderGrid(
.padding(HeaderUtils.padding), .padding(HeaderUtils.padding),
) )
} }
when (val state = items) {
DataLoadingState.Pending,
DataLoadingState.Loading,
-> {
// This shouldn't happen, so just show placeholder
Text(stringResource(R.string.loading))
}
is DataLoadingState.Error -> {
ErrorMessage(state.message, state.exception)
}
is DataLoadingState.Success<List<BaseItem?>> -> {
CardGrid( CardGrid(
pager = state.data, pager = items,
onClickItem = onClickItem, onClickItem = onClickItem,
onLongClickItem = onLongClickItem, onLongClickItem = onLongClickItem,
onClickPlay = onClickPlay, onClickPlay = onClickPlay,
@ -171,11 +95,7 @@ fun CollectionFolderGrid(
showLetterButtons = sortAndDirection.sort == ItemSortBy.SORT_NAME, showLetterButtons = sortAndDirection.sort == ItemSortBy.SORT_NAME,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
initialPosition = initialPosition, initialPosition = initialPosition,
positionCallback = { columns, newPosition -> positionCallback = positionCallback,
showHeader = newPosition < columns
position = newPosition
positionCallback?.invoke(columns, newPosition)
},
cardContent = { (item, index, onClick, onLongClick, widthPx, mod) -> cardContent = { (item, index, onClick, onLongClick, widthPx, mod) ->
GridCard( GridCard(
item = item, item = item,
@ -204,8 +124,6 @@ fun CollectionFolderGrid(
} }
} }
} }
}
}
data class PositionItem( data class PositionItem(
val position: Int, val position: Int,

View file

@ -28,7 +28,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -51,10 +50,7 @@ import coil3.compose.useExistingImageAsPlaceholder
import coil3.request.ImageRequest import coil3.request.ImageRequest
import coil3.request.crossfade import coil3.request.crossfade
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.filter.FilterValueOption
import com.github.damontecres.wholphin.data.filter.ItemFilterBy
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.LocalImageUrlService import com.github.damontecres.wholphin.ui.LocalImageUrlService
import com.github.damontecres.wholphin.ui.cards.FavoriteIndicator import com.github.damontecres.wholphin.ui.cards.FavoriteIndicator
@ -67,7 +63,6 @@ import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.rememberInt import com.github.damontecres.wholphin.ui.rememberInt
import com.github.damontecres.wholphin.ui.roundMinutes import com.github.damontecres.wholphin.ui.roundMinutes
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.DataLoadingState
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
@ -79,60 +74,30 @@ import org.jellyfin.sdk.model.extensions.ticks
@Composable @Composable
fun CollectionFolderList( fun CollectionFolderList(
preferences: UserPreferences, preferences: UserPreferences,
item: BaseItem?, collectionType: CollectionType?,
title: String, focusedItem: BaseItem?,
items: DataLoadingState<List<BaseItem?>>, items: List<BaseItem?>,
sortAndDirection: SortAndDirection, sortAndDirection: SortAndDirection,
onClickItem: (Int, BaseItem) -> Unit, onClickItem: (Int, BaseItem) -> Unit,
onLongClickItem: (Int, BaseItem) -> Unit, onLongClickItem: (Int, BaseItem) -> Unit,
onSortChange: (SortAndDirection) -> Unit,
letterPosition: suspend (Char) -> Int, letterPosition: suspend (Char) -> Int,
sortOptions: List<ItemSortBy>,
playEnabled: Boolean,
getPossibleFilterValues: suspend (ItemFilterBy<*>) -> List<FilterValueOption>,
viewOptions: ViewOptions, viewOptions: ViewOptions,
onClickPlayAll: (shuffle: Boolean) -> Unit,
onClickPlay: (Int, BaseItem) -> Unit, onClickPlay: (Int, BaseItem) -> Unit,
onChangeBackdrop: (BaseItem) -> Unit,
onClickShowViewOptions: () -> Unit,
initialPosition: Int, initialPosition: Int,
gridFocusRequester: FocusRequester,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
showTitle: Boolean = true,
positionCallback: ((columns: Int, position: Int) -> Unit)? = null, positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
currentFilter: GetItemsFilter = GetItemsFilter(),
filterOptions: List<ItemFilterBy<*>> = listOf(),
onFilterChange: (GetItemsFilter) -> Unit = {},
focusRequesterOnEmpty: FocusRequester? = null,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val pager = (items as? DataLoadingState.Success)?.data
var showHeader by rememberSaveable { mutableStateOf(true) }
val headerRowFocusRequester = remember { FocusRequester() }
val showLetterButtons = sortAndDirection.sort == ItemSortBy.SORT_NAME
val gridFocusRequester = remember { FocusRequester() }
if (pager?.isNotEmpty() == true) {
LaunchedEffect(viewOptions.type) {
gridFocusRequester.tryRequestFocus()
}
} else {
LaunchedEffect(Unit) {
(focusRequesterOnEmpty ?: headerRowFocusRequester).tryRequestFocus()
}
}
val listState = rememberLazyListState() val listState = rememberLazyListState()
var contentHashFocus by remember { mutableStateOf(false) } var contentHashFocus by remember { mutableStateOf(false) }
var position by rememberInt(initialPosition) var position by rememberInt(initialPosition)
val positionFocusRequester = remember { FocusRequester() } val positionFocusRequester = remember { FocusRequester() }
val focusedItem = pager?.getOrNull(position) val showLetterButtons = sortAndDirection.sort == ItemSortBy.SORT_NAME
if (viewOptions.showBackdrop) {
LaunchedEffect(focusedItem) { var showHeader by remember { mutableStateOf(true) }
focusedItem?.let(onChangeBackdrop)
}
}
fun jumpTo(newPosition: Int) { fun jumpTo(newPosition: Int) {
scope.launch { scope.launch {
@ -147,36 +112,6 @@ fun CollectionFolderList(
} }
Column(modifier = modifier) { Column(modifier = modifier) {
CollectionFolderHeader(
showHeader = showHeader || items !is DataLoadingState.Success,
showTitle = showTitle,
playEnabled = playEnabled && pager?.isNotEmpty() == true,
title = title,
sortAndDirection = sortAndDirection,
onSortChange = onSortChange,
sortOptions = sortOptions,
getPossibleFilterValues = getPossibleFilterValues,
onClickShowViewOptions = onClickShowViewOptions,
onClickPlayAll = onClickPlayAll,
currentFilter = currentFilter,
filterOptions = filterOptions,
onFilterChange = onFilterChange,
modifier = Modifier.focusRequester(headerRowFocusRequester),
)
when (val state = items) {
DataLoadingState.Pending,
DataLoadingState.Loading,
-> {
// This shouldn't happen, so just show placeholder
Text(stringResource(R.string.loading))
}
is DataLoadingState.Error -> {
ErrorMessage(state.message, state.exception)
}
is DataLoadingState.Success<List<BaseItem?>> -> {
val pager = state.data
val topPadding by animateDpAsState( val topPadding by animateDpAsState(
targetValue = targetValue =
when { when {
@ -214,9 +149,9 @@ fun CollectionFolderList(
) )
} }
CollectionFolderContent( CollectionFolderContent(
items = state.data, items = items,
viewOptions = viewOptions, viewOptions = viewOptions,
collectionType = item?.data?.collectionType, collectionType = collectionType,
listState = listState, listState = listState,
position = position, position = position,
positionFocusRequester = positionFocusRequester, positionFocusRequester = positionFocusRequester,
@ -249,7 +184,7 @@ fun CollectionFolderList(
} }
?: letters[0] ?: letters[0]
} }
if (showLetterButtons && pager.isNotEmpty()) { if (showLetterButtons && items.isNotEmpty()) {
AlphabetButtons( AlphabetButtons(
letters = letters, letters = letters,
currentLetter = currentLetter, currentLetter = currentLetter,
@ -268,8 +203,6 @@ fun CollectionFolderList(
} }
} }
} }
}
}
@OptIn(ExperimentalCoilApi::class) @OptIn(ExperimentalCoilApi::class)
@Composable @Composable

View file

@ -4,11 +4,13 @@ import android.content.Context
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -18,6 +20,7 @@ import androidx.compose.runtime.setValue
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.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
@ -60,8 +63,10 @@ import com.github.damontecres.wholphin.ui.equalsNotNull
import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.rememberInt
import com.github.damontecres.wholphin.ui.showToast import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.ui.toServerString import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.ui.util.FilterUtils import com.github.damontecres.wholphin.ui.util.FilterUtils
import com.github.damontecres.wholphin.util.ApiRequestPager import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.DataLoadingState
@ -149,10 +154,12 @@ class CollectionFolderViewModel
viewModelScope.launchIO { viewModelScope.launchIO {
try { try {
val item = val item =
itemId.toUUIDOrNull()?.let {
api.userLibraryApi api.userLibraryApi
.getItem(itemId.toUUID()) .getItem(it)
.content .content
.let(::BaseItem) .let(::BaseItem)
}
val libraryDisplayInfo = val libraryDisplayInfo =
serverRepository.currentUser?.let { user -> serverRepository.currentUser?.let { user ->
@ -184,7 +191,11 @@ class CollectionFolderViewModel
// onResumePage() // onResumePage()
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.e(ex, "Error during init") Timber.e(ex, "Error during init")
_state.update { it.copy(item = DataLoadingState.Error(ex)) } _state.update {
it.copy(
item = DataLoadingState.Error(ex),
)
}
} }
} }
mediaManagementService.deletedItemFlow mediaManagementService.deletedItemFlow
@ -313,7 +324,12 @@ class CollectionFolderViewModel
sortAndDirection, sortAndDirection,
filter, filter,
) )
_state.update { it.copy(items = DataLoadingState.Error(ex)) } _state.update {
it.copy(
items = DataLoadingState.Error(ex),
backgroundLoading = LoadingState.Pending,
)
}
} }
} }
@ -516,7 +532,7 @@ class CollectionFolderViewModel
} }
data class CollectionFolderState( data class CollectionFolderState(
val item: DataLoadingState<BaseItem> = DataLoadingState.Loading, val item: DataLoadingState<BaseItem?> = DataLoadingState.Loading,
val items: DataLoadingState<List<BaseItem?>> = DataLoadingState.Loading, val items: DataLoadingState<List<BaseItem?>> = DataLoadingState.Loading,
val backgroundLoading: LoadingState = LoadingState.Loading, val backgroundLoading: LoadingState = LoadingState.Loading,
val sortAndDirection: SortAndDirection = SortAndDirection.DEFAULT, val sortAndDirection: SortAndDirection = SortAndDirection.DEFAULT,
@ -738,24 +754,27 @@ fun CollectionFolderView(
}, },
) )
} }
Box(modifier = modifier) {
when (val st = state.item) { when (val st = state.item) {
DataLoadingState.Loading, DataLoadingState.Loading,
DataLoadingState.Pending, DataLoadingState.Pending,
-> { -> {
LoadingPage(modifier) LoadingPage(Modifier.fillMaxSize())
} }
is DataLoadingState.Error, is DataLoadingState.Error -> {
is DataLoadingState.Success<*>, ErrorMessage(st, Modifier.fillMaxSize())
-> { }
is DataLoadingState.Success<BaseItem?> -> {
val item = st.successValue val item = st.successValue
val title = val title =
initialFilter.nameOverride initialFilter.nameOverride
?: item?.name ?: item?.name
?: item?.data?.collectionType?.name ?: item?.data?.collectionType?.name
?: stringResource(R.string.collection) ?: stringResource(R.string.collection)
Box(modifier = modifier) { Column(modifier = Modifier.fillMaxSize()) {
val headerRowFocusRequester = remember { FocusRequester() }
LifecycleResumeEffect(itemId) { LifecycleResumeEffect(itemId) {
viewModel.onResumePage() viewModel.onResumePage()
@ -763,22 +782,27 @@ fun CollectionFolderView(
viewModel.release() viewModel.release()
} }
} }
if (state.viewOptions.type == ViewOptionsType.GRID) { var showHeader by rememberSaveable { mutableStateOf(true) }
CollectionFolderGrid( val gridFocusRequester = remember { FocusRequester() }
preferences = preferences, val pager = remember(state.items) { state.items.successValue }
initialPosition = viewModel.position, var position by rememberInt(viewModel.position)
item = item,
val focusedItem = pager?.getOrNull(position)
if (state.viewOptions.showBackdrop) {
LaunchedEffect(focusedItem) {
focusedItem?.let(viewModel::updateBackdrop)
}
}
CollectionFolderHeader(
showHeader = showHeader || state.items !is DataLoadingState.Success,
showTitle = showTitle,
playEnabled = playEnabled && state.items.successValue?.isNotEmpty() == true,
title = title, title = title,
items = state.items,
sortAndDirection = state.sortAndDirection, sortAndDirection = state.sortAndDirection,
modifier = Modifier.fillMaxSize(),
focusRequesterOnEmpty = focusRequesterOnEmpty,
onClickItem = gridActions.onClickItem,
onLongClickItem = gridActions.onLongClickItem!!,
onSortChange = { onSortChange = {
viewModel.onSortChange(it, recursive, state.filter) viewModel.onSortChange(it, recursive, state.filter)
}, },
filterOptions = filterOptions, sortOptions = sortOptions,
currentFilter = state.filter, currentFilter = state.filter,
onFilterChange = { onFilterChange = {
viewModel.onFilterChange(it, recursive) viewModel.onFilterChange(it, recursive)
@ -786,60 +810,79 @@ fun CollectionFolderView(
getPossibleFilterValues = { getPossibleFilterValues = {
viewModel.getFilterOptionValues(it) viewModel.getFilterOptionValues(it)
}, },
showTitle = showTitle, filterOptions = filterOptions,
sortOptions = sortOptions, onClickPlayAll = gridActions.onClickPlayAll!!,
positionCallback = { columns, position -> onClickShowViewOptions = { showViewOptions = true },
viewModel.position = position modifier = Modifier.focusRequester(headerRowFocusRequester),
positionCallback?.invoke(columns, position) )
when (val pager = state.items) {
is DataLoadingState.Error -> {
LaunchedEffect(Unit) {
(focusRequesterOnEmpty ?: headerRowFocusRequester).tryRequestFocus()
}
ErrorMessage(pager, Modifier.fillMaxSize())
}
DataLoadingState.Loading,
DataLoadingState.Pending,
-> {
LoadingPage(Modifier.fillMaxSize())
}
is DataLoadingState.Success<List<BaseItem?>> -> {
LaunchedEffect(Unit) {
if (pager.data.isNotEmpty()) {
gridFocusRequester.tryRequestFocus()
}
}
Box(Modifier.fillMaxSize()) {
if (state.viewOptions.type == ViewOptionsType.GRID) {
CollectionFolderGrid(
preferences = preferences,
collectionType = item?.data?.collectionType,
initialPosition = viewModel.position,
items = pager.data,
sortAndDirection = state.sortAndDirection,
modifier = Modifier.fillMaxSize(),
gridFocusRequester = gridFocusRequester,
onClickItem = gridActions.onClickItem,
onLongClickItem = gridActions.onLongClickItem!!,
positionCallback = { columns, pos ->
viewModel.position = pos
showHeader = pos < columns
position = pos
positionCallback?.invoke(columns, pos)
}, },
letterPosition = { viewModel.positionOfLetter(it) ?: -1 }, letterPosition = { viewModel.positionOfLetter(it) ?: -1 },
viewOptions = state.viewOptions, viewOptions = state.viewOptions,
onChangeBackdrop = viewModel::updateBackdrop,
playEnabled = playEnabled,
onClickPlay = gridActions.onClickPlayRemoteButton!!, onClickPlay = gridActions.onClickPlayRemoteButton!!,
onClickPlayAll = gridActions.onClickPlayAll!!, focusedItem = focusedItem,
onClickShowViewOptions = { showViewOptions = true },
) )
} else { } else {
CollectionFolderList( CollectionFolderList(
preferences = preferences, preferences = preferences,
collectionType = item?.data?.collectionType,
initialPosition = viewModel.position, initialPosition = viewModel.position,
item = item, items = pager.data,
title = title,
items = state.items,
sortAndDirection = state.sortAndDirection, sortAndDirection = state.sortAndDirection,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
focusRequesterOnEmpty = focusRequesterOnEmpty, gridFocusRequester = gridFocusRequester,
onClickItem = gridActions.onClickItem, onClickItem = gridActions.onClickItem,
onLongClickItem = gridActions.onLongClickItem!!, onLongClickItem = gridActions.onLongClickItem!!,
onSortChange = { positionCallback = { columns, pos ->
viewModel.onSortChange(it, recursive, state.filter) viewModel.position = pos
}, showHeader = pos < columns
filterOptions = filterOptions, position = pos
currentFilter = state.filter, positionCallback?.invoke(columns, pos)
onFilterChange = {
viewModel.onFilterChange(it, recursive)
},
getPossibleFilterValues = {
viewModel.getFilterOptionValues(it)
},
showTitle = showTitle,
sortOptions = sortOptions,
positionCallback = { columns, position ->
viewModel.position = position
positionCallback?.invoke(columns, position)
}, },
letterPosition = { viewModel.positionOfLetter(it) ?: -1 }, letterPosition = { viewModel.positionOfLetter(it) ?: -1 },
viewOptions = state.viewOptions, viewOptions = state.viewOptions,
onChangeBackdrop = viewModel::updateBackdrop,
playEnabled = playEnabled,
onClickPlay = gridActions.onClickPlayRemoteButton!!, onClickPlay = gridActions.onClickPlayRemoteButton!!,
onClickPlayAll = gridActions.onClickPlayAll!!, focusedItem = focusedItem,
onClickShowViewOptions = { showViewOptions = true },
) )
} }
androidx.compose.animation.AnimatedVisibility(
AnimatedVisibility(
state.backgroundLoading == LoadingState.Loading, state.backgroundLoading == LoadingState.Loading,
modifier = modifier =
Modifier Modifier
@ -858,6 +901,10 @@ fun CollectionFolderView(
} }
} }
} }
}
}
}
}
overviewDialog?.let { info -> overviewDialog?.let { info ->
ItemDetailsDialog( ItemDetailsDialog(
info = info, info = info,

View file

@ -492,7 +492,7 @@ class SeriesViewModel
.firstOrNull() .firstOrNull()
if (nextUp != null) { if (nextUp != null) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
navigateTo(Destination.Playback(nextUp.id, 0L)) navigateTo(Destination.Playback(BaseItem(nextUp)))
} }
} else { } else {
showToast( showToast(