mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
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:
parent
510160aceb
commit
23856ffcf8
4 changed files with 294 additions and 396 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue