Support RecommendsScreen even if MangaDex/Comick is not installed

This commit is contained in:
Cuong-Tran 2025-01-24 17:15:59 +07:00
parent 343cd3727c
commit c7c76d22dd
No known key found for this signature in database
GPG key ID: 733AA7624B9315C2
7 changed files with 235 additions and 39 deletions

View file

@ -2,24 +2,37 @@ package exh.md.similar
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.online.all.MangaDex
import exh.md.handlers.SimilarHandler
import exh.md.service.MangaDexService
import exh.md.service.SimilarService
import exh.md.utils.MdLang
import exh.recs.sources.RecommendationPagingSource
import exh.source.getMainSource
import exh.recs.sources.SourceCatalogue
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import tachiyomi.data.source.NoResultsException
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.sy.SYMR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/**
* MangaDexSimilarPagingSource inherited from the general Pager.
*/
class MangaDexSimilarPagingSource(
internal class MangaDexSimilarPagingSource(
manga: Manga,
private val mangaDex: MangaDex,
) : RecommendationPagingSource(mangaDex, manga) {
// KMK -->
private val sourceCatalogue: SourceCatalogue,
// KMK <--
) : RecommendationPagingSource(
// KMK -->
sourceCatalogue.source,
// KMK <--
manga,
) {
override val name: String
get() = "MangaDex"
@ -28,12 +41,40 @@ class MangaDexSimilarPagingSource(
get() = SYMR.strings.similar_titles
override val associatedSourceId: Long
get() = mangaDex.getMainSource().id
// KMK -->
get() = sourceCatalogue.sourceId
private val client by lazy { Injekt.get<NetworkHelper>().client }
private val mdLang by lazy {
sourceCatalogue.source?.lang?.let { lang ->
MdLang.fromExt(lang)
} ?: MdLang.ENGLISH
}
private val mangadexService by lazy {
MangaDexService(client)
}
private val similarService by lazy {
SimilarService(client)
}
private val similarHandler by lazy {
SimilarHandler(mdLang.lang, mangadexService, similarService)
}
// KMK <--
override suspend fun requestNextPage(currentPage: Int): MangasPage {
val mangasPage = coroutineScope {
val similarPageDef = async { mangaDex.getMangaSimilar(manga.toSManga()) }
val relatedPageDef = async { mangaDex.getMangaRelated(manga.toSManga()) }
val similarPageDef = async {
// KMK -->
similarHandler.getSimilar(manga.toSManga())
// KMK <--
}
val relatedPageDef = async {
// KMK -->
similarHandler.getRelated(manga.toSManga())
// KMK <--
}
val similarPage = similarPageDef.await()
val relatedPage = relatedPageDef.await()

View file

@ -1,13 +1,12 @@
package exh.recs
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
import exh.recs.sources.RecommendationPagingSource
import exh.recs.sources.SourceCatalogue
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.runBlocking
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -16,23 +15,19 @@ class BrowseRecommendsScreenModel(
sourceId: Long,
private val recommendationSourceName: String,
private val getManga: GetManga = Injekt.get(),
// KMK -->
sourceManager: SourceManager = Injekt.get(),
// KMK <--
) : BrowseSourceScreenModel(sourceId, null) {
val manga = runBlocking { getManga.await(mangaId) }!!
// KMK -->
val source_ = sourceManager.get(sourceId)
?.let { it as CatalogueSource }
private val sourceCatalogue = SourceCatalogue(sourceId)
// KMK <--
val recommendationSource: RecommendationPagingSource
get() = RecommendationPagingSource.createSources(
manga,
// KMK -->
source_,
sourceCatalogue,
// KMK <--
).first {
it::class.qualifiedName == recommendationSourceName

View file

@ -57,7 +57,7 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() {
val onClickItem = { manga: Manga ->
when (manga.source) {
-1L -> navigator.push(
RECOMMENDS_SOURCE -> navigator.push(
SourcesScreen(SourcesScreen.SmartSearchConfig(manga.ogTitle)),
)
else -> {
@ -75,7 +75,7 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() {
val onLongClickItem = { manga: Manga ->
when (manga.source) {
-1L -> WebViewActivity.newIntent(context, manga.url, title = manga.title).let(context::startActivity)
RECOMMENDS_SOURCE -> WebViewActivity.newIntent(context, manga.url, title = manga.title).let(context::startActivity)
else -> {
// KMK -->
scope.launchIO {

View file

@ -6,8 +6,8 @@ import androidx.compose.runtime.produceState
import cafe.adriel.voyager.core.model.StateScreenModel
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.presentation.util.ioCoroutineScope
import eu.kanade.tachiyomi.source.CatalogueSource
import exh.recs.sources.RecommendationPagingSource
import exh.recs.sources.SourceCatalogue
import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.mutate
import kotlinx.collections.immutable.persistentMapOf
@ -25,21 +25,18 @@ import kotlinx.coroutines.withContext
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
open class RecommendsScreenModel(
val mangaId: Long,
val sourceId: Long,
sourceManager: SourceManager = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
internal val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
) : StateScreenModel<RecommendsScreenModel.State>(State()) {
val source = sourceManager.get(sourceId)
// KMK -->
?.let { it as CatalogueSource }
// KMK -->
private val sourceCatalogue = SourceCatalogue(sourceId)
// KMK <--
private val coroutineDispatcher = Dispatchers.IO.limitedParallelism(5)
@ -56,7 +53,12 @@ open class RecommendsScreenModel(
ioCoroutineScope.launch {
val manga = getManga.await(mangaId)!!
mutableState.update { it.copy(manga = manga) }
val recommendationSources = RecommendationPagingSource.createSources(manga, source)
val recommendationSources = RecommendationPagingSource.createSources(
manga,
// KMK -->
sourceCatalogue,
// KMK <--
)
updateItems(
recommendationSources
@ -80,7 +82,7 @@ open class RecommendsScreenModel(
// KMK -->
// If the recommendation is associated with a source, resolve it
// Otherwise, skip this step. The user will be prompted to choose a source via SmartSearch
it.toDomainManga(recSourceId ?: -1)
it.toDomainManga(recSourceId ?: RECOMMENDS_SOURCE)
// KMK <--
}
@ -155,3 +157,5 @@ sealed interface RecommendationItemResult {
return !onlyShowHasResults || (this is Success && !this.isEmpty)
}
}
const val RECOMMENDS_SOURCE = -1L

View file

@ -25,10 +25,17 @@ import uy.kohesive.injekt.injectLazy
fun CatalogueSource.isComickSource() = name == "Comick"
class ComickPagingSource(
internal class ComickPagingSource(
manga: Manga,
private val comickSource: CatalogueSource,
) : RecommendationPagingSource(comickSource, manga) {
// KMK -->
private val sourceCatalogue: SourceCatalogue,
// KMK <--
) : RecommendationPagingSource(
// KMK -->
sourceCatalogue.source,
// KMK <--
manga,
) {
override val name: String
get() = "Comick"
@ -37,7 +44,9 @@ class ComickPagingSource(
get() = SYMR.strings.community_recommendations
override val associatedSourceId: Long
get() = comickSource.id
// KMK -->
get() = sourceCatalogue.sourceId
// KMK <--
private val client by lazy { Injekt.get<NetworkHelper>().client }
private val json by injectLazy<Json>()

View file

@ -6,10 +6,10 @@ import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.MangaDex
import exh.md.similar.MangaDexSimilarPagingSource
import exh.pref.DelegateSourcePreferences
import exh.source.getMainSource
import exh.source.COMICK_IDS
import exh.source.MANGADEX_IDS
import exh.source.isMdBasedSource
import kotlinx.serialization.json.Json
import logcat.LogPriority
@ -17,6 +17,7 @@ import tachiyomi.core.common.util.system.logcat
import tachiyomi.data.source.NoResultsException
import tachiyomi.data.source.SourcePagingSource
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracks
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -44,7 +45,15 @@ abstract class RecommendationPagingSource(
open val associatedSourceId: Long? = null
companion object {
fun createSources(manga: Manga, source: CatalogueSource?): List<RecommendationPagingSource> {
internal fun createSources(
manga: Manga,
// KMK -->
sourceCatalogue: SourceCatalogue,
// KMK <--
): List<RecommendationPagingSource> {
// KMK -->
val source = sourceCatalogue.source
// KMK <--
return buildList {
add(AniListPagingSource(manga, source))
add(MangaUpdatesCommunityPagingSource(manga, source))
@ -54,22 +63,36 @@ abstract class RecommendationPagingSource(
// Only include MangaDex if the delegate sources are enabled and the source is MD-based
if (
// KMK -->
source != null &&
sourceCatalogue.isMangaDexSource() ||
// KMK <--
source.isMdBasedSource() &&
source?.isMdBasedSource() /* KMK --> */ == true /* KMK <-- */ &&
Injekt.get<DelegateSourcePreferences>().delegateSources().get()
) {
add(MangaDexSimilarPagingSource(manga, source.getMainSource() as MangaDex))
add(
MangaDexSimilarPagingSource(
manga,
// KMK -->
sourceCatalogue,
// KMK <--
),
)
}
// Only include Comick if the source manga is from there
if (
// KMK -->
source != null &&
sourceCatalogue.isComickSource() ||
// KMK <--
source.isComickSource()
source?.isComickSource() /* KMK --> */ == true /* KMK <-- */
) {
add(ComickPagingSource(manga, source))
add(
ComickPagingSource(
manga,
// KMK -->
sourceCatalogue,
// KMK <--
),
)
}
}.sortedWith(compareBy({ it.name }, { it.category.resourceId }))
}
@ -123,3 +146,17 @@ abstract class TrackerRecommendationPagingSource(
return MangasPage(recs, false)
}
}
// KMK -->
internal class SourceCatalogue(
internal val sourceId: Long,
sourceManager: SourceManager = Injekt.get(),
) {
val source = sourceManager.get(sourceId)
?.let { it as CatalogueSource }
fun isComickSource(): Boolean = sourceId in COMICK_IDS
fun isMangaDexSource(): Boolean = sourceId in MANGADEX_IDS
}
// KMK <--

View file

@ -14,3 +14,113 @@ const val MERGED_SOURCE_ID = LEWD_SOURCE_SERIES + 69
const val NHENTAI_OLD_ID = 6907L
const val TSUMINO_OLD_ID = 6909L
const val HBROWSE_OLD_ID = 6912L
// KMK -->
val COMICK_IDS = setOf(
982606170401027267, // all
2971557565147974499, // en
8729626158695297897, // pt-BR
5846182885417171581, // ru
9126078936214680667, // fr
3182432228546767958, // es-419
7005108854993254607, // pl
7186425300860782365, // tr
8807318985460553537, // it
9052019484488287695, // es
5506707690027487154, // id
7838940669485160901, // hu
9191587139933034493, // vi
3140511316190656180, // zh-Hant
8266599095155001097, // ar
7552236568334706863, // de
1071494508319622063, // zh-Hans
2159382907508433047, // ca
8981320463367739957, // bg
4246541831082737053, // th
3146252372540608964, // fa
3505068018066717349, // uk
2147260678391898600, // mn
6676949771764486043, // ro
5354540502202034685, // he
4731643595200952045, // ms
8549617092958820123, // tl
8288710818308434509, // ja
5176570178081213805, // hi
9199495862098963317, // my
3493720175703105662, // ko
2651978322082769022, // cs
4153491877797434408, // pt
6104206360977276112, // nl
979314012722687145, // sv
3598159956413889411, // bn
5932005504194733317, // no
1792260331167396074, // lt
6190162673651111756, // el
571668187470919545, // sr
7137437402245830147, // da
)
val MANGADEX_IDS = setOf(
2499283573021220255, // en
4638673959522768501, // af
987803387023224960, // sq
3339599426223341161, // ar
9127464796236242233, // az
1553736397938752611, // eu
3601180820582582605, // be
4150470519566206911, // bn
5463447640980279236, // bg
1347402746269051958, // my
5860541308324630662, // ca
5148895169070562838, // zh-Hans
1493666528525752601, // zh-Hant
2072928884077964642, // cv
8548374501079910130, // hr
3578612018159256808, // cs
425785191804166217, // da
6750440049024086587, // nl
2819252027406613931, // eo
8773024089257004344, // et
8578871918181236609, // fil
8254121249433835847, // fi
4505830566611664829, // fr
8952567078513962586, // ka
5098537545549490547, // de
3260701926561129943, // el
1424273154577029558, // he
6840513937945146538, // hi
4284949320785450865, // hu
3686775439235747344, // ga
3807502156582598786, // id
1952071260038453057, // it
1573550798452063564, // jv
1411768577036936240, // ja
8884149958776527952, // kk
3285208643537017688, // ko
764587568075398530, // la
737986167355114438, // lt
1471784905273036181, // ms
5967745367608513818, // mn
954368055061084457, // ne
4872213291993424667, // no
3781216447842245147, // fa
8033579885162383068, // pl
2655149515337070132, // pt-BR
5189216366882819742, // pt
4774459486579224459, // ro
2098905203823335614, // ru
3507378005483435886, // sr
2875503798326600410, // sk
4938773340256184018, // es-419
6400665728063187402, // es
1145824452519314725, // sv
4273071356706429527, // ta
6886894829142702925, // te
1713554459881080228, // th
3846770256925560569, // tr
5779037855201976894, // uk
72800122520214493, // ur
2838715564514827672, // uz
9194073792736219759, // vi
)
// KMK <--