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:
parent
288fe0d888
commit
ab19d5c62a
@ -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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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 -->
|
||||||
|
@ -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 = {
|
||||||
|
@ -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 <--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user