Fix Related Mangas (#29)
* using mangaDetailsRequest for relatedMangaListRequest * rethrow exception if extension failed to get related mangas * improve coroutine * More like these * Fix missing unique mess up LazyRow/LazyGrid order
This commit is contained in:
parent
ce7177efc6
commit
6876d82982
11 changed files with 105 additions and 53 deletions
|
|
@ -154,9 +154,10 @@ fun RelatedMangaTitle(
|
|||
}
|
||||
|
||||
fun LazyGridScope.header(
|
||||
key: Any? = null,
|
||||
content: @Composable LazyGridItemScope.() -> Unit,
|
||||
) {
|
||||
item(span = { GridItemSpan(this.maxLineSpan) }, content = content)
|
||||
item(key = key, span = { GridItemSpan(this.maxLineSpan) }, content = content)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
|||
import eu.kanade.tachiyomi.ui.manga.RelatedManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.sy.SYMR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.plus
|
||||
|
|
@ -40,10 +41,10 @@ fun RelatedMangasComfortableGrid(
|
|||
verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer),
|
||||
horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer),
|
||||
) {
|
||||
relatedMangas.forEach {
|
||||
val isLoading = it is RelatedManga.Loading
|
||||
relatedMangas.forEach { related ->
|
||||
val isLoading = related is RelatedManga.Loading
|
||||
if (isLoading) {
|
||||
header {
|
||||
header(key = "$related#header") {
|
||||
RelatedMangaTitle(
|
||||
title = stringResource(MR.strings.loading),
|
||||
subtitle = null,
|
||||
|
|
@ -52,19 +53,27 @@ fun RelatedMangasComfortableGrid(
|
|||
modifier = Modifier.background(MaterialTheme.colorScheme.background),
|
||||
)
|
||||
}
|
||||
header { RelatedMangasLoadingItem() }
|
||||
header(key = "$related#content") { RelatedMangasLoadingItem() }
|
||||
} else {
|
||||
val relatedManga = it as RelatedManga.Success
|
||||
header {
|
||||
val relatedManga = related as RelatedManga.Success
|
||||
header(key = "${related.keyword}#header") {
|
||||
RelatedMangaTitle(
|
||||
title = relatedManga.keyword,
|
||||
title = if (relatedManga.keyword.isNotBlank()) {
|
||||
stringResource(SYMR.strings.related_mangas_more)
|
||||
} else {
|
||||
stringResource(SYMR.strings.pref_source_related_mangas)
|
||||
},
|
||||
subtitle = null,
|
||||
onClick = { onKeywordClick(relatedManga.keyword) },
|
||||
onLongClick = { onKeywordLongClick(relatedManga.keyword) },
|
||||
onClick = {
|
||||
if (relatedManga.keyword.isNotBlank()) onKeywordClick(relatedManga.keyword)
|
||||
},
|
||||
onLongClick = {
|
||||
if (relatedManga.keyword.isNotBlank()) onKeywordLongClick(relatedManga.keyword)
|
||||
},
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.background),
|
||||
)
|
||||
}
|
||||
items(count = relatedManga.mangaList.size) { index ->
|
||||
items(key = { relatedManga.mangaList[it].id }, count = relatedManga.mangaList.size) { index ->
|
||||
val manga by getManga(relatedManga.mangaList[index])
|
||||
BrowseSourceComfortableGridItem(
|
||||
manga = manga,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import eu.kanade.presentation.library.components.CommonMangaItemDefaults
|
|||
import eu.kanade.tachiyomi.ui.manga.RelatedManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.sy.SYMR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.plus
|
||||
|
|
@ -40,10 +41,10 @@ fun RelatedMangasCompactGrid(
|
|||
verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer),
|
||||
horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer),
|
||||
) {
|
||||
relatedMangas.forEach {
|
||||
val isLoading = it is RelatedManga.Loading
|
||||
relatedMangas.forEach { related ->
|
||||
val isLoading = related is RelatedManga.Loading
|
||||
if (isLoading) {
|
||||
header {
|
||||
header(key = "$related#header") {
|
||||
RelatedMangaTitle(
|
||||
title = stringResource(MR.strings.loading),
|
||||
subtitle = null,
|
||||
|
|
@ -52,19 +53,27 @@ fun RelatedMangasCompactGrid(
|
|||
modifier = Modifier.background(MaterialTheme.colorScheme.background),
|
||||
)
|
||||
}
|
||||
header { RelatedMangasLoadingItem() }
|
||||
header(key = "$related#content") { RelatedMangasLoadingItem() }
|
||||
} else {
|
||||
val relatedManga = it as RelatedManga.Success
|
||||
header {
|
||||
val relatedManga = related as RelatedManga.Success
|
||||
header(key = "${related.keyword}#header") {
|
||||
RelatedMangaTitle(
|
||||
title = relatedManga.keyword,
|
||||
title = if (relatedManga.keyword.isNotBlank()) {
|
||||
stringResource(SYMR.strings.related_mangas_more)
|
||||
} else {
|
||||
stringResource(SYMR.strings.pref_source_related_mangas)
|
||||
},
|
||||
subtitle = null,
|
||||
onClick = { onKeywordClick(relatedManga.keyword) },
|
||||
onLongClick = { onKeywordLongClick(relatedManga.keyword) },
|
||||
onClick = {
|
||||
if (relatedManga.keyword.isNotBlank()) onKeywordClick(relatedManga.keyword)
|
||||
},
|
||||
onLongClick = {
|
||||
if (relatedManga.keyword.isNotBlank()) onKeywordLongClick(relatedManga.keyword)
|
||||
},
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.background),
|
||||
)
|
||||
}
|
||||
items(count = relatedManga.mangaList.size) { index ->
|
||||
items(key = { relatedManga.mangaList[it].id }, count = relatedManga.mangaList.size) { index ->
|
||||
val manga by getManga(relatedManga.mangaList[index])
|
||||
BrowseSourceCompactGridItem(
|
||||
manga = manga,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import eu.kanade.presentation.browse.RelatedMangasLoadingItem
|
|||
import eu.kanade.tachiyomi.ui.manga.RelatedManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.sy.SYMR
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
|
|
@ -35,10 +36,10 @@ fun RelatedMangasList(
|
|||
),
|
||||
contentPadding = PaddingValues(MaterialTheme.padding.small),
|
||||
) {
|
||||
relatedMangas.forEach {
|
||||
val isLoading = it is RelatedManga.Loading
|
||||
relatedMangas.forEach { related ->
|
||||
val isLoading = related is RelatedManga.Loading
|
||||
if (isLoading) {
|
||||
stickyHeader {
|
||||
stickyHeader(key = "$related#header") {
|
||||
RelatedMangaTitle(
|
||||
title = stringResource(MR.strings.loading),
|
||||
subtitle = null,
|
||||
|
|
@ -47,19 +48,27 @@ fun RelatedMangasList(
|
|||
modifier = Modifier.background(MaterialTheme.colorScheme.background),
|
||||
)
|
||||
}
|
||||
item { RelatedMangasLoadingItem() }
|
||||
item(key = "$related#content") { RelatedMangasLoadingItem() }
|
||||
} else {
|
||||
val relatedManga = it as RelatedManga.Success
|
||||
stickyHeader {
|
||||
val relatedManga = related as RelatedManga.Success
|
||||
stickyHeader(key = "${related.keyword}#header") {
|
||||
RelatedMangaTitle(
|
||||
title = relatedManga.keyword,
|
||||
title = if (relatedManga.keyword.isNotBlank()) {
|
||||
stringResource(SYMR.strings.related_mangas_more)
|
||||
} else {
|
||||
stringResource(SYMR.strings.pref_source_related_mangas)
|
||||
},
|
||||
subtitle = null,
|
||||
onClick = { onKeywordClick(relatedManga.keyword) },
|
||||
onLongClick = { onKeywordLongClick(relatedManga.keyword) },
|
||||
onClick = {
|
||||
if (relatedManga.keyword.isNotBlank()) onKeywordClick(relatedManga.keyword)
|
||||
},
|
||||
onLongClick = {
|
||||
if (relatedManga.keyword.isNotBlank()) onKeywordLongClick(relatedManga.keyword)
|
||||
},
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.background),
|
||||
)
|
||||
}
|
||||
items(count = relatedManga.mangaList.size) { index ->
|
||||
items(key = { relatedManga.mangaList[it].id }, count = relatedManga.mangaList.size) { index ->
|
||||
val manga by getManga(relatedManga.mangaList[index])
|
||||
BrowseSourceListItem(
|
||||
manga = manga,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ fun RelatedMangaCardRow(
|
|||
contentPadding = PaddingValues(MaterialTheme.padding.small),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
||||
) {
|
||||
items(mangas) {
|
||||
items(mangas, key = { it.id }) {
|
||||
val manga by getManga(it)
|
||||
MangaItem(
|
||||
title = manga.title,
|
||||
|
|
|
|||
|
|
@ -452,10 +452,9 @@ class MangaScreenModel(
|
|||
if (screenModelScope.isActive) {
|
||||
// KMK -->
|
||||
launch {
|
||||
launch {
|
||||
fetchRelatedMangasFromSource()
|
||||
}.join()
|
||||
updateSuccessState { it.copy(isFetchingRelatedMangas = false) }
|
||||
fetchRelatedMangasFromSource(onFinish = {
|
||||
updateSuccessState { it.copy(isFetchingRelatedMangas = false) }
|
||||
})
|
||||
}
|
||||
// KMK <--
|
||||
val fetchFromSourceTasks = listOf(
|
||||
|
|
@ -1142,13 +1141,21 @@ class MangaScreenModel(
|
|||
/**
|
||||
* Requests an list of related mangas from the source.
|
||||
*/
|
||||
private suspend fun fetchRelatedMangasFromSource() {
|
||||
private suspend fun fetchRelatedMangasFromSource(onFinish: () -> Unit) {
|
||||
fun exceptionHandler(e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
val message = with(context) { e.formattedMessage }
|
||||
|
||||
screenModelScope.launch {
|
||||
snackbarHostState.showSnackbar(message = message)
|
||||
}
|
||||
}
|
||||
val state = successState ?: return
|
||||
val relatedMangasEnabled = Injekt.get<SourcePreferences>().relatedMangas().get()
|
||||
try {
|
||||
if (state.source !is StubSource) {
|
||||
if (relatedMangasEnabled) {
|
||||
state.source.getRelatedMangaList(state.manga.toSManga()) { pair, _ ->
|
||||
state.source.getRelatedMangaList(state.manga.toSManga(), { e -> exceptionHandler(e) }) { pair, _ ->
|
||||
/* Push found related mangas into collection */
|
||||
val relatedManga = RelatedManga.Success.fromPair(pair) { mangaList ->
|
||||
mangaList.map {
|
||||
|
|
@ -1167,13 +1174,10 @@ class MangaScreenModel(
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
val message = with(context) { e.formattedMessage }
|
||||
|
||||
screenModelScope.launch {
|
||||
snackbarHostState.showSnackbar(message = message)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
exceptionHandler(e)
|
||||
} finally {
|
||||
onFinish()
|
||||
}
|
||||
}
|
||||
// KMK <--
|
||||
|
|
@ -1793,6 +1797,10 @@ class MangaScreenModel(
|
|||
// KMK <--
|
||||
) : State {
|
||||
// KMK ->>
|
||||
/**
|
||||
* a value of null will be treated as still loading, so if all searching were failed and won't update
|
||||
* 'relatedMangaCollection` then we should return empty list
|
||||
*/
|
||||
val relatedMangasSorted = relatedMangaCollection
|
||||
?.sorted(manga)
|
||||
?.removeDuplicates(manga)
|
||||
|
|
@ -1929,7 +1937,7 @@ sealed interface RelatedManga {
|
|||
}
|
||||
|
||||
companion object {
|
||||
fun List<RelatedManga>.sorted(manga: Manga): List<RelatedManga> {
|
||||
internal fun List<RelatedManga>.sorted(manga: Manga): List<RelatedManga> {
|
||||
val success = filterIsInstance<Success>()
|
||||
val loading = filterIsInstance<Loading>()
|
||||
val title = manga.title.lowercase()
|
||||
|
|
@ -1943,7 +1951,7 @@ sealed interface RelatedManga {
|
|||
loading
|
||||
}
|
||||
|
||||
fun List<RelatedManga>.removeDuplicates(manga: Manga): List<RelatedManga> {
|
||||
internal fun List<RelatedManga>.removeDuplicates(manga: Manga): List<RelatedManga> {
|
||||
val mangaUrls = HashSet<String>().apply { add(manga.url) }
|
||||
|
||||
return map { relatedManga ->
|
||||
|
|
@ -1960,7 +1968,7 @@ sealed interface RelatedManga {
|
|||
}
|
||||
}
|
||||
|
||||
fun List<RelatedManga>.isLoading(isFetchingRelatedMangas: Boolean): List<RelatedManga> {
|
||||
internal fun List<RelatedManga>.isLoading(isFetchingRelatedMangas: Boolean): List<RelatedManga> {
|
||||
return if (isFetchingRelatedMangas) {
|
||||
this + listOf(Loading)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class StubSource(
|
|||
// KMK -->
|
||||
override suspend fun getRelatedMangaList(
|
||||
manga: SManga,
|
||||
exceptionHandler: (Throwable) -> Unit,
|
||||
pushResults: suspend (relatedManga: Pair<String, List<SManga>>, completed: Boolean) -> Unit,
|
||||
) = throw SourceNotInstalledException()
|
||||
// KMK <--
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@
|
|||
<string name="pref_local_source_hidden_folders_summery">Allow local source to read hidden folders</string>
|
||||
<string name="pref_source_related_mangas">Related titles</string>
|
||||
<string name="pref_source_related_mangas_summary">Show related titles while viewing entry</string>
|
||||
<string name="related_mangas_more">More like these...</string>
|
||||
|
||||
<!-- Backup settings -->
|
||||
<string name="custom_entry_info">Custom entry info</string>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
package eu.kanade.tachiyomi.source
|
||||
|
||||
import dev.icerock.moko.graphics.BuildConfig
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import logcat.LogPriority
|
||||
import rx.Observable
|
||||
import tachiyomi.core.common.util.lang.awaitSingle
|
||||
|
|
@ -95,12 +98,14 @@ interface CatalogueSource : Source {
|
|||
*/
|
||||
override suspend fun getRelatedMangaList(
|
||||
manga: SManga,
|
||||
exceptionHandler: (Throwable) -> Unit,
|
||||
pushResults: suspend (relatedManga: Pair<String, List<SManga>>, completed: Boolean) -> Unit,
|
||||
) {
|
||||
val handler = CoroutineExceptionHandler { _, e -> exceptionHandler(e) }
|
||||
if (!disableRelatedMangas) {
|
||||
coroutineScope {
|
||||
if (supportsRelatedMangas) launch { getRelatedMangaListByExtension(manga, pushResults) }
|
||||
if (!disableRelatedMangasBySearch) launch { getRelatedMangaListBySearch(manga, pushResults) }
|
||||
supervisorScope {
|
||||
if (supportsRelatedMangas) launch(handler) { getRelatedMangaListByExtension(manga, pushResults) }
|
||||
if (!disableRelatedMangasBySearch) launch(handler) { getRelatedMangaListBySearch(manga, pushResults) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -118,7 +123,15 @@ interface CatalogueSource : Source {
|
|||
runCatching { fetchRelatedMangaList(manga) }
|
||||
.onSuccess { if (it.isNotEmpty()) pushResults(Pair("", it), false) }
|
||||
.onFailure { e ->
|
||||
logcat(LogPriority.ERROR, e) { "## getRelatedMangaListByExtension: $e" }
|
||||
@Suppress("KotlinConstantConditions")
|
||||
if (BuildConfig.BUILD_TYPE == "release") {
|
||||
logcat(LogPriority.ERROR, e) { "## getRelatedMangaListByExtension: $e" }
|
||||
} else {
|
||||
throw UnsupportedOperationException(
|
||||
"Extension doesn't support site's related entries," +
|
||||
" please report an issue to Komikku."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ interface Source {
|
|||
*/
|
||||
suspend fun getRelatedMangaList(
|
||||
manga: SManga,
|
||||
exceptionHandler: (Throwable) -> Unit,
|
||||
pushResults: suspend (relatedManga: Pair<String, List<SManga>>, completed: Boolean) -> Unit,
|
||||
): Unit = throw IllegalStateException("Not used")
|
||||
// KMK <--
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ abstract class HttpSource : CatalogueSource {
|
|||
* @param manga the manga to look for related mangas.
|
||||
*/
|
||||
protected open fun relatedMangaListRequest(manga: SManga): Request {
|
||||
return GET(baseUrl + manga.url, headers)
|
||||
return mangaDetailsRequest(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue