diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt index ce8db419..3fa8ffa5 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt @@ -89,10 +89,10 @@ import com.github.damontecres.wholphin.ui.setValueOnMain import com.github.damontecres.wholphin.ui.toServerString import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.util.ApiRequestPager +import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetPersonsHandler -import com.github.damontecres.wholphin.util.LoadingExceptionHandler import com.github.damontecres.wholphin.util.LoadingState import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -151,9 +151,8 @@ class CollectionFolderViewModel ): CollectionFolderViewModel } - val loading = MutableLiveData(LoadingState.Loading) + val loading = MutableLiveData>>(DataLoadingState.Loading) val backgroundLoading = MutableLiveData(LoadingState.Loading) - val pager = MutableLiveData>(listOf()) val sortAndDirection = MutableLiveData() val filter = MutableLiveData(GetItemsFilter()) val viewOptions = MutableLiveData() @@ -165,40 +164,40 @@ class CollectionFolderViewModel } init { - viewModelScope.launch( - LoadingExceptionHandler( - loading, - context.getString(R.string.error_loading_collection, itemId), - ) + Dispatchers.IO, - ) { + viewModelScope.launchIO { super.itemId = itemId - itemId.toUUIDOrNull()?.let { - fetchItem(it) + try { + itemId.toUUIDOrNull()?.let { + fetchItem(it) + } + + val libraryDisplayInfo = + serverRepository.currentUser.value?.let { user -> + libraryDisplayInfoDao.getItem(user, itemId) + } + this@CollectionFolderViewModel.viewOptions.setValueOnMain( + libraryDisplayInfo?.viewOptions ?: defaultViewOptions, + ) + + val sortAndDirection = + if (collectionFilter.useSavedLibraryDisplayInfo) { + libraryDisplayInfo?.sortAndDirection + } else { + null + } ?: initialSortAndDirection ?: SortAndDirection.DEFAULT + + val filterToUse = + if (collectionFilter.useSavedLibraryDisplayInfo && libraryDisplayInfo?.filter != null) { + collectionFilter.filter.merge(libraryDisplayInfo.filter) + } else { + collectionFilter.filter + } + + loadResults(true, sortAndDirection, recursive, filterToUse, useSeriesForPrimary) + } catch (ex: Exception) { + Timber.e(ex, "Error during init") + loading.setValueOnMain(DataLoadingState.Error(ex)) } - - val libraryDisplayInfo = - serverRepository.currentUser.value?.let { user -> - libraryDisplayInfoDao.getItem(user, itemId) - } - this@CollectionFolderViewModel.viewOptions.setValueOnMain( - libraryDisplayInfo?.viewOptions ?: defaultViewOptions, - ) - - val sortAndDirection = - if (collectionFilter.useSavedLibraryDisplayInfo) { - libraryDisplayInfo?.sortAndDirection - } else { - null - } ?: initialSortAndDirection ?: SortAndDirection.DEFAULT - - val filterToUse = - if (collectionFilter.useSavedLibraryDisplayInfo && libraryDisplayInfo?.filter != null) { - collectionFilter.filter.merge(libraryDisplayInfo.filter) - } else { - collectionFilter.filter - } - - loadResults(true, sortAndDirection, recursive, filterToUse, useSeriesForPrimary) } } @@ -269,8 +268,7 @@ class CollectionFolderViewModel viewModelScope.launch(Dispatchers.IO) { withContext(Dispatchers.Main) { if (resetState) { - pager.value = listOf() - loading.value = LoadingState.Loading + loading.value = DataLoadingState.Loading } backgroundLoading.value = LoadingState.Loading this@CollectionFolderViewModel.sortAndDirection.value = sortAndDirection @@ -281,8 +279,7 @@ class CollectionFolderViewModel createPager(sortAndDirection, recursive, filter, useSeriesForPrimary).init() if (newPager.isNotEmpty()) newPager.getBlocking(0) withContext(Dispatchers.Main) { - pager.value = newPager - loading.value = LoadingState.Success + loading.value = DataLoadingState.Success(newPager) backgroundLoading.value = LoadingState.Success } } catch (ex: Exception) { @@ -293,8 +290,7 @@ class CollectionFolderViewModel filter, ) withContext(Dispatchers.Main) { - loading.value = LoadingState.Error(ex) - pager.value = listOf() + loading.value = DataLoadingState.Error(ex) } } } @@ -498,7 +494,9 @@ class CollectionFolderViewModel played: Boolean, ) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { favoriteWatchManager.setWatched(itemId, played) - (pager.value as? ApiRequestPager<*>)?.refreshItem(position, itemId) + (loading.value as? DataLoadingState.Success)?.let { + (it.data as? ApiRequestPager<*>)?.refreshItem(position, itemId) + } } fun setFavorite( @@ -507,7 +505,9 @@ class CollectionFolderViewModel favorite: Boolean, ) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { favoriteWatchManager.setFavorite(itemId, favorite) - (pager.value as? ApiRequestPager<*>)?.refreshItem(position, itemId) + (loading.value as? DataLoadingState.Success)?.let { + (it.data as? ApiRequestPager<*>)?.refreshItem(position, itemId) + } } fun updateBackdrop(item: BaseItem) { @@ -598,7 +598,6 @@ fun CollectionFolderGrid( val loading by viewModel.loading.observeAsState(LoadingState.Loading) val backgroundLoading by viewModel.backgroundLoading.observeAsState(LoadingState.Loading) val item by viewModel.item.observeAsState() - val pager by viewModel.pager.observeAsState() val viewOptions by viewModel.viewOptions.observeAsState(defaultViewOptions) var moreDialog by remember { mutableStateOf>(Optional.absent()) } @@ -606,95 +605,91 @@ fun CollectionFolderGrid( val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending) when (val state = loading) { - is LoadingState.Error -> { - ErrorMessage(state) - } - - LoadingState.Loading, - LoadingState.Pending, + DataLoadingState.Loading, + DataLoadingState.Pending, -> { LoadingPage() } - LoadingState.Success -> { - pager?.let { pager -> - val title = - initialFilter.nameOverride - ?: item?.name - ?: item?.data?.collectionType?.name - ?: stringResource(R.string.collection) - Box(modifier = modifier) { - CollectionFolderGridContent( - preferences = preferences, - initialPosition = viewModel.position, - item = item, - title = title, - pager = pager, - sortAndDirection = sortAndDirection!!, - modifier = Modifier.fillMaxSize(), - focusRequesterOnEmpty = focusRequesterOnEmpty, - onClickItem = onClickItem, - onLongClickItem = { position, item -> - moreDialog.makePresent(PositionItem(position, item)) - }, - onSortChange = { - viewModel.onSortChange(it, recursive, filter) - }, - filterOptions = filterOptions, - currentFilter = filter, - 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 }, - viewOptions = viewOptions, - defaultViewOptions = defaultViewOptions, - onSaveViewOptions = { viewModel.saveViewOptions(it) }, - onChangeBackdrop = viewModel::updateBackdrop, - playEnabled = playEnabled, - onClickPlay = { _, item -> - viewModel.navigationManager.navigateTo(Destination.Playback(item)) - }, - onClickPlayAll = { shuffle -> - itemId.toUUIDOrNull()?.let { - viewModel.navigationManager.navigateTo( - Destination.PlaybackList( - itemId = it, - startIndex = 0, - shuffle = shuffle, - recursive = recursive, - sortAndDirection = sortAndDirection, - filter = filter, - ), - ) - } - }, - ) + is DataLoadingState.Error, + is DataLoadingState.Success<*>, + -> { + val title = + initialFilter.nameOverride + ?: item?.name + ?: item?.data?.collectionType?.name + ?: stringResource(R.string.collection) + Box(modifier = modifier) { + CollectionFolderGridContent( + preferences = preferences, + initialPosition = viewModel.position, + item = item, + title = title, + loadingState = state as DataLoadingState>, + sortAndDirection = sortAndDirection!!, + modifier = Modifier.fillMaxSize(), + focusRequesterOnEmpty = focusRequesterOnEmpty, + onClickItem = onClickItem, + onLongClickItem = { position, item -> + moreDialog.makePresent(PositionItem(position, item)) + }, + onSortChange = { + viewModel.onSortChange(it, recursive, filter) + }, + filterOptions = filterOptions, + currentFilter = filter, + 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 }, + viewOptions = viewOptions, + defaultViewOptions = defaultViewOptions, + onSaveViewOptions = { viewModel.saveViewOptions(it) }, + onChangeBackdrop = viewModel::updateBackdrop, + playEnabled = playEnabled, + onClickPlay = { _, item -> + viewModel.navigationManager.navigateTo(Destination.Playback(item)) + }, + onClickPlayAll = { shuffle -> + itemId.toUUIDOrNull()?.let { + viewModel.navigationManager.navigateTo( + Destination.PlaybackList( + itemId = it, + startIndex = 0, + shuffle = shuffle, + recursive = recursive, + sortAndDirection = sortAndDirection, + filter = filter, + ), + ) + } + }, + ) - AnimatedVisibility( - backgroundLoading == LoadingState.Loading, - modifier = - Modifier - .align(Alignment.Center) - .padding(16.dp), - ) { - CircularProgress( - Modifier - .background( - MaterialTheme.colorScheme.background.copy(alpha = .25f), - shape = CircleShape, - ).size(64.dp) - .padding(4.dp), - ) - } + AnimatedVisibility( + backgroundLoading == LoadingState.Loading, + modifier = + Modifier + .align(Alignment.Center) + .padding(16.dp), + ) { + CircularProgress( + Modifier + .background( + MaterialTheme.colorScheme.background.copy(alpha = .25f), + shape = CircleShape, + ).size(64.dp) + .padding(4.dp), + ) } } } @@ -755,7 +750,7 @@ fun CollectionFolderGridContent( preferences: UserPreferences, item: BaseItem?, title: String, - pager: List, + loadingState: DataLoadingState>, sortAndDirection: SortAndDirection, onClickItem: (Int, BaseItem) -> Unit, onLongClickItem: (Int, BaseItem) -> Unit, @@ -781,13 +776,14 @@ fun CollectionFolderGridContent( ) { val context = LocalContext.current + val pager = (loadingState as? DataLoadingState.Success)?.data var showHeader by rememberSaveable { mutableStateOf(true) } var showViewOptions by rememberSaveable { mutableStateOf(false) } var viewOptions by remember { mutableStateOf(viewOptions) } val headerRowFocusRequester = remember { FocusRequester() } val gridFocusRequester = remember { FocusRequester() } - if (pager.isNotEmpty()) { + if (pager?.isNotEmpty() == true) { RequestOrRestoreFocus(gridFocusRequester) } else { LaunchedEffect(Unit) { @@ -797,7 +793,7 @@ fun CollectionFolderGridContent( var backdropImageUrl by remember { mutableStateOf(null) } var position by rememberInt(initialPosition) - val focusedItem = pager.getOrNull(position) + val focusedItem = pager?.getOrNull(position) if (viewOptions.showDetails) { LaunchedEffect(focusedItem) { focusedItem?.let(onChangeBackdrop) @@ -810,7 +806,7 @@ fun CollectionFolderGridContent( modifier = Modifier.fillMaxSize(), ) { AnimatedVisibility( - showHeader, + showHeader || loadingState !is DataLoadingState.Success, enter = slideInVertically() + fadeIn(), exit = slideOutVertically() + fadeOut(), ) { @@ -871,7 +867,7 @@ fun CollectionFolderGridContent( ) } } - if (playEnabled) { + if (playEnabled && pager?.isNotEmpty() == true) { Row( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, @@ -903,47 +899,62 @@ fun CollectionFolderGridContent( .padding(16.dp), ) } - CardGrid( - pager = pager, - onClickItem = onClickItem, - onLongClickItem = onLongClickItem, - onClickPlay = onClickPlay, - letterPosition = letterPosition, - gridFocusRequester = gridFocusRequester, - showJumpButtons = false, // TODO add preference - showLetterButtons = sortAndDirection.sort == ItemSortBy.SORT_NAME, - modifier = Modifier.fillMaxSize(), - initialPosition = initialPosition, - positionCallback = { columns, newPosition -> - showHeader = newPosition < columns - position = newPosition - positionCallback?.invoke(columns, newPosition) - }, - cardContent = { item, onClick, onLongClick, mod -> - GridCard( - item = item, - onClick = onClick, - onLongClick = onLongClick, - imageContentScale = viewOptions.contentScale.scale, - imageAspectRatio = viewOptions.aspectRatio.ratio, - imageType = viewOptions.imageType, - showTitle = viewOptions.showTitles, - modifier = mod, + when (val state = loadingState) { + DataLoadingState.Pending, + DataLoadingState.Loading, + -> { + // This shouldn't happen, so just show placeholder + Text("Loading") + } + + is DataLoadingState.Error -> { + ErrorMessage(state.message, state.exception) + } + + is DataLoadingState.Success> -> { + CardGrid( + pager = state.data, + onClickItem = onClickItem, + onLongClickItem = onLongClickItem, + onClickPlay = onClickPlay, + letterPosition = letterPosition, + gridFocusRequester = gridFocusRequester, + showJumpButtons = false, // TODO add preference + showLetterButtons = sortAndDirection.sort == ItemSortBy.SORT_NAME, + modifier = Modifier.fillMaxSize(), + initialPosition = initialPosition, + positionCallback = { columns, newPosition -> + showHeader = newPosition < columns + position = newPosition + positionCallback?.invoke(columns, newPosition) + }, + cardContent = { item, onClick, onLongClick, mod -> + GridCard( + item = item, + onClick = onClick, + onLongClick = onLongClick, + imageContentScale = viewOptions.contentScale.scale, + imageAspectRatio = viewOptions.aspectRatio.ratio, + imageType = viewOptions.imageType, + showTitle = viewOptions.showTitles, + modifier = mod, + ) + }, + columns = viewOptions.columns, + spacing = viewOptions.spacing.dp, ) - }, - columns = viewOptions.columns, - spacing = viewOptions.spacing.dp, - ) - AnimatedVisibility(showViewOptions) { - ViewOptionsDialog( - viewOptions = viewOptions, - defaultViewOptions = defaultViewOptions, - onDismissRequest = { - showViewOptions = false - onSaveViewOptions.invoke(viewOptions) - }, - onViewOptionsChange = { viewOptions = it }, - ) + AnimatedVisibility(showViewOptions) { + ViewOptionsDialog( + viewOptions = viewOptions, + defaultViewOptions = defaultViewOptions, + onDismissRequest = { + showViewOptions = false + onSaveViewOptions.invoke(viewOptions) + }, + onViewOptionsChange = { viewOptions = it }, + ) + } + } } } }