Avoid crashing if opening browse with unavailable source

(cherry picked from commit 0ef7650c1a0ae7c4c6e17e458695191ce78944cb)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt
#	app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt
This commit is contained in:
arkon 2023-02-15 22:47:47 -05:00 committed by Jobobby04
parent 288fe0d888
commit ab19d5c62a
6 changed files with 91 additions and 40 deletions

View File

@ -1,6 +1,7 @@
package eu.kanade.presentation.browse package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.HelpOutline import androidx.compose.material.icons.outlined.HelpOutline
@ -11,6 +12,7 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.paging.LoadState import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
@ -19,12 +21,15 @@ import eu.kanade.presentation.browse.components.BrowseSourceComfortableGrid
import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid import eu.kanade.presentation.browse.components.BrowseSourceCompactGrid
import eu.kanade.presentation.browse.components.BrowseSourceEHentaiList import eu.kanade.presentation.browse.components.BrowseSourceEHentaiList
import eu.kanade.presentation.browse.components.BrowseSourceList import eu.kanade.presentation.browse.components.BrowseSourceList
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.EmptyScreenAction import eu.kanade.presentation.components.EmptyScreenAction
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.source.isEhBasedSource import exh.source.isEhBasedSource
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -33,7 +38,7 @@ import tachiyomi.domain.manga.model.Manga
@Composable @Composable
fun BrowseSourceContent( fun BrowseSourceContent(
source: CatalogueSource?, source: Source?,
mangaList: LazyPagingItems<StateFlow</* SY --> */Pair<Manga, RaisedSearchMetadata?>/* SY <-- */>>, mangaList: LazyPagingItems<StateFlow</* SY --> */Pair<Manga, RaisedSearchMetadata?>/* SY <-- */>>,
columns: GridCells, columns: GridCells,
// SY --> // SY -->
@ -169,3 +174,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,9 +20,9 @@ import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.components.RadioMenuItem import eu.kanade.presentation.components.RadioMenuItem
import eu.kanade.presentation.components.SearchToolbar import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import exh.source.anyIs import exh.source.anyIs
import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.LibraryDisplayMode
@ -30,7 +30,7 @@ import tachiyomi.domain.library.model.LibraryDisplayMode
fun BrowseSourceToolbar( fun BrowseSourceToolbar(
searchQuery: String?, searchQuery: String?,
onSearchQueryChange: (String?) -> Unit, onSearchQueryChange: (String?) -> Unit,
source: CatalogueSource?, source: Source?,
displayMode: LibraryDisplayMode?, displayMode: LibraryDisplayMode?,
onDisplayModeChange: (LibraryDisplayMode) -> Unit, onDisplayModeChange: (LibraryDisplayMode) -> Unit,
navigateUp: () -> Unit, navigateUp: () -> Unit,

View File

@ -248,7 +248,7 @@ class SourceManager(
} }
} }
inner class SourceNotInstalledException(val sourceString: String) : inner class SourceNotInstalledException(sourceString: String) :
Exception(context.getString(R.string.source_not_installed, sourceString)) Exception(context.getString(R.string.source_not_installed, sourceString))
// SY --> // SY -->

View File

@ -39,6 +39,7 @@ import cafe.adriel.voyager.core.screen.uniqueScreenKey
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.BrowseSourceContent 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.BrowseSourceToolbar
import eu.kanade.presentation.browse.components.FailedToLoadSavedSearchDialog import eu.kanade.presentation.browse.components.FailedToLoadSavedSearchDialog
import eu.kanade.presentation.browse.components.RemoveMangaDialog import eu.kanade.presentation.browse.components.RemoveMangaDialog
@ -51,7 +52,9 @@ import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.util.AssistContentScreen import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.padding import eu.kanade.presentation.util.padding
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen
@ -83,12 +86,6 @@ data class BrowseSourceScreen(
@Composable @Composable
override fun Content() { 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 { val screenModel = rememberScreenModel {
BrowseSourceScreenModel( BrowseSourceScreenModel(
sourceId = sourceId, sourceId = sourceId,
@ -101,8 +98,7 @@ data class BrowseSourceScreen(
} }
val state by screenModel.state.collectAsState() val state by screenModel.state.collectAsState()
val snackbarHostState = remember { SnackbarHostState() } val navigator = LocalNavigator.currentOrThrow
val navigateUp: () -> Unit = { val navigateUp: () -> Unit = {
when { when {
!state.isUserQuery && state.toolbarQuery != null -> screenModel.setToolbarQuery(null) !state.isUserQuery && state.toolbarQuery != null -> screenModel.setToolbarQuery(null)
@ -110,8 +106,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 onWebViewClick = f@{
val source = screenModel.source as? HttpSource ?: return@f val source = screenModel.source as? HttpSource ?: return@f
navigator.push( navigator.push(
@ -169,7 +178,7 @@ data class BrowseSourceScreen(
Text(text = stringResource(R.string.popular)) Text(text = stringResource(R.string.popular))
}, },
) )
if (screenModel.source.supportsLatest) { if ((screenModel.source as CatalogueSource).supportsLatest) {
FilterChip( FilterChip(
selected = state.listing == Listing.Latest, selected = state.listing == Listing.Latest,
onClick = { onClick = {

View File

@ -143,7 +143,7 @@ open class BrowseSourceScreenModel(
var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope) var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
val source = sourceManager.get(sourceId) as CatalogueSource val source = sourceManager.getOrStub(sourceId)
// SY --> // SY -->
val ehentaiBrowseDisplayMode by unsortedPreferences.enhancedEHentaiView().asState(coroutineScope) val ehentaiBrowseDisplayMode by unsortedPreferences.enhancedEHentaiView().asState(coroutineScope)
@ -152,20 +152,22 @@ open class BrowseSourceScreenModel(
// SY <-- // SY <--
init { init {
mutableState.update { if (source is CatalogueSource) {
var query: String? = null mutableState.update {
var listing = it.listing var query: String? = null
var listing = it.listing
if (listing is Listing.Search) { if (listing is Listing.Search) {
query = listing.query query = listing.query
listing = Listing.Search(query, source.getFilterList()) listing = Listing.Search(query, source.getFilterList())
}
it.copy(
listing = listing,
filters = source.getFilterList(),
toolbarQuery = query,
)
} }
it.copy(
listing = listing,
filters = source.getFilterList(),
toolbarQuery = query,
)
} }
// SY --> // SY -->
@ -185,14 +187,16 @@ open class BrowseSourceScreenModel(
} }
} }
getExhSavedSearch.subscribe(source.id, source::getFilterList) if (source is CatalogueSource) {
.onEach { savedSearches -> getExhSavedSearch.subscribe(source.id, source::getFilterList)
mutableState.update { it.copy(savedSearches = savedSearches) } .onEach { savedSearches ->
withUIContext { mutableState.update { it.copy(savedSearches = savedSearches) }
filterSheet?.setSavedSearches(savedSearches) withUIContext {
filterSheet?.setSavedSearches(savedSearches)
}
} }
} .launchIn(coroutineScope)
.launchIn(coroutineScope) }
// SY <-- // SY <--
} }
@ -252,6 +256,8 @@ open class BrowseSourceScreenModel(
// SY <-- // SY <--
fun resetFilters() { fun resetFilters() {
if (source !is CatalogueSource) return
mutableState.update { it.copy(filters = source.getFilterList()) } mutableState.update { it.copy(filters = source.getFilterList()) }
} }
@ -260,6 +266,7 @@ open class BrowseSourceScreenModel(
} }
fun search(query: String? = null, filters: FilterList? = null) { fun search(query: String? = null, filters: FilterList? = null) {
if (source !is CatalogueSource) return
// SY --> // SY -->
if (filters != null && filters !== state.value.filters) { if (filters != null && filters !== state.value.filters) {
mutableState.update { state -> state.copy(filters = filters) } mutableState.update { state -> state.copy(filters = filters) }
@ -280,6 +287,8 @@ open class BrowseSourceScreenModel(
} }
fun searchGenre(genreName: String) { fun searchGenre(genreName: String) {
if (source !is CatalogueSource) return
val defaultFilters = source.getFilterList() val defaultFilters = source.getFilterList()
var genreExists = false var genreExists = false
@ -467,6 +476,7 @@ open class BrowseSourceScreenModel(
} }
open fun initFilterSheet(context: Context, navigator: Navigator) { open fun initFilterSheet(context: Context, navigator: Navigator) {
source as? CatalogueSource ?: return
val state = state.value val state = state.value
/*if (state.filters.isEmpty()) { /*if (state.filters.isEmpty()) {
return return
@ -575,6 +585,7 @@ open class BrowseSourceScreenModel(
fun saveSearch( fun saveSearch(
name: String, name: String,
) { ) {
if (source !is CatalogueSource) return
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
val query = state.value.listing.query val query = state.value.listing.query
val filterList = state.value.listing.filters.ifEmpty { source.getFilterList() } val filterList = state.value.listing.filters.ifEmpty { source.getFilterList() }
@ -596,11 +607,15 @@ open class BrowseSourceScreenModel(
} }
} }
suspend fun loadSearch(searchId: Long) = suspend fun loadSearch(searchId: Long): EXHSavedSearch? {
getExhSavedSearch.awaitOne(searchId, source::getFilterList) if (source !is CatalogueSource) return null
return getExhSavedSearch.awaitOne(searchId, source::getFilterList)
}
suspend fun loadSearches() = suspend fun loadSearches(): List<EXHSavedSearch> {
getExhSavedSearch.await(source.id, source::getFilterList) if (source !is CatalogueSource) return emptyList()
return getExhSavedSearch.await(source.id, source::getFilterList)
}
// EXH <-- // EXH <--
} }

View File

@ -2,6 +2,7 @@ package exh.recs
import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.source.model.SourcePagingSourceType import eu.kanade.domain.source.model.SourcePagingSourceType
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -17,6 +18,6 @@ class RecommendsScreenModel(
val manga = runBlocking { getManga.await(mangaId) }!! val manga = runBlocking { getManga.await(mangaId) }!!
override fun createSourcePagingSource(query: String, filters: FilterList): SourcePagingSourceType { override fun createSourcePagingSource(query: String, filters: FilterList): SourcePagingSourceType {
return RecommendsPagingSource(source, manga) return RecommendsPagingSource(source as CatalogueSource, manga)
} }
} }