Avoid crashing if opening browse with unavailable source

This commit is contained in:
arkon 2023-02-15 22:47:47 -05:00
parent 4635e58405
commit 0ef7650c1a
5 changed files with 71 additions and 28 deletions

View file

@ -1,6 +1,7 @@
package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.HelpOutline
@ -11,6 +12,7 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
@ -18,19 +20,22 @@ import eu.kanade.data.source.NoResultsException
import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
import eu.kanade.presentation.browse.components.BrowseSourceList
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.EmptyScreenAction
import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import kotlinx.coroutines.flow.StateFlow
import tachiyomi.domain.library.model.LibraryDisplayMode
import tachiyomi.domain.manga.model.Manga
@Composable
fun BrowseSourceContent(
source: CatalogueSource?,
source: Source?,
mangaList: LazyPagingItems<StateFlow<Manga>>,
columns: GridCells,
displayMode: LibraryDisplayMode,
@ -139,3 +144,24 @@ fun BrowseSourceContent(
}
}
}
@Composable
fun MissingSourceScreen(
source: SourceManager.StubSource,
navigateUp: () -> Unit,
) {
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = source.name,
navigateUp = navigateUp,
scrollBehavior = scrollBehavior,
)
},
) { paddingValues ->
EmptyScreen(
message = source.getSourceNotInstalledException().message!!,
modifier = Modifier.padding(paddingValues),
)
}
}

View file

@ -20,15 +20,15 @@ import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.components.RadioMenuItem
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import tachiyomi.domain.library.model.LibraryDisplayMode
@Composable
fun BrowseSourceToolbar(
searchQuery: String?,
onSearchQueryChange: (String?) -> Unit,
source: CatalogueSource?,
source: Source?,
displayMode: LibraryDisplayMode,
onDisplayModeChange: (LibraryDisplayMode) -> Unit,
navigateUp: () -> Unit,

View file

@ -152,6 +152,6 @@ class SourceManager(
}
}
inner class SourceNotInstalledException(val sourceString: String) :
inner class SourceNotInstalledException(sourceString: String) :
Exception(context.getString(R.string.source_not_installed, sourceString))
}

View file

@ -39,6 +39,7 @@ import cafe.adriel.voyager.core.screen.uniqueScreenKey
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.BrowseSourceContent
import eu.kanade.presentation.browse.MissingSourceScreen
import eu.kanade.presentation.browse.components.BrowseSourceToolbar
import eu.kanade.presentation.browse.components.RemoveMangaDialog
import eu.kanade.presentation.components.ChangeCategoryDialog
@ -48,7 +49,9 @@ import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.padding
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
import eu.kanade.tachiyomi.ui.category.CategoryScreen
@ -73,17 +76,10 @@ data class BrowseSourceScreen(
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val scope = rememberCoroutineScope()
val context = LocalContext.current
val haptic = LocalHapticFeedback.current
val uriHandler = LocalUriHandler.current
val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId, listingQuery) }
val state by screenModel.state.collectAsState()
val snackbarHostState = remember { SnackbarHostState() }
val navigator = LocalNavigator.currentOrThrow
val navigateUp: () -> Unit = {
when {
!state.isUserQuery && state.toolbarQuery != null -> screenModel.setToolbarQuery(null)
@ -91,8 +87,21 @@ data class BrowseSourceScreen(
}
}
val onHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) }
if (screenModel.source is SourceManager.StubSource) {
MissingSourceScreen(
source = screenModel.source,
navigateUp = navigateUp,
)
return
}
val scope = rememberCoroutineScope()
val context = LocalContext.current
val haptic = LocalHapticFeedback.current
val uriHandler = LocalUriHandler.current
val snackbarHostState = remember { SnackbarHostState() }
val onHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) }
val onWebViewClick = f@{
val source = screenModel.source as? HttpSource ?: return@f
navigator.push(
@ -147,7 +156,7 @@ data class BrowseSourceScreen(
Text(text = stringResource(R.string.popular))
},
)
if (screenModel.source.supportsLatest) {
if ((screenModel.source as CatalogueSource).supportsLatest) {
FilterChip(
selected = state.listing == Listing.Latest,
onClick = {

View file

@ -103,23 +103,25 @@ class BrowseSourceScreenModel(
var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
val source = sourceManager.get(sourceId) as CatalogueSource
val source = sourceManager.getOrStub(sourceId)
init {
mutableState.update {
var query: String? = null
var listing = it.listing
if (source is CatalogueSource) {
mutableState.update {
var query: String? = null
var listing = it.listing
if (listing is Listing.Search) {
query = listing.query
listing = Listing.Search(query, source.getFilterList())
if (listing is Listing.Search) {
query = listing.query
listing = Listing.Search(query, source.getFilterList())
}
it.copy(
listing = listing,
filters = source.getFilterList(),
toolbarQuery = query,
)
}
it.copy(
listing = listing,
filters = source.getFilterList(),
toolbarQuery = query,
)
}
}
@ -164,6 +166,8 @@ class BrowseSourceScreenModel(
}
fun resetFilters() {
if (source !is CatalogueSource) return
mutableState.update { it.copy(filters = source.getFilterList()) }
}
@ -172,6 +176,8 @@ class BrowseSourceScreenModel(
}
fun search(query: String? = null, filters: FilterList? = null) {
if (source !is CatalogueSource) return
val input = state.value.listing as? Listing.Search
?: Listing.Search(query = null, filters = source.getFilterList())
@ -187,6 +193,8 @@ class BrowseSourceScreenModel(
}
fun searchGenre(genreName: String) {
if (source !is CatalogueSource) return
val defaultFilters = source.getFilterList()
var genreExists = false