diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt index 7b4802589..285690183 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt @@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.mdlist.MdList import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.MetadataMangasPage import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga @@ -284,10 +285,14 @@ class MangaDex(delegate: HttpSource, val context: Context) : return mangaHandler.fetchRandomMangaId() } - suspend fun getMangaSimilar(manga: MangaInfo): MangasPage { + suspend fun getMangaSimilar(manga: MangaInfo): MetadataMangasPage { return similarHandler.getSimilar(manga) } + suspend fun getMangaRelated(manga: MangaInfo): MetadataMangasPage { + return similarHandler.getRelated(manga) + } + companion object { private const val dataSaverPref = "dataSaverV5" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceComfortableGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceComfortableGridHolder.kt index 801a4f788..e9d76ca5e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceComfortableGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceComfortableGridHolder.kt @@ -61,6 +61,10 @@ class SourceComfortableGridHolder(private val view: View, private val adapter: F binding.badges.localText.text = itemView.context.resources.getStringArray(R.array.md_follows_options).asList()[it] binding.badges.localText.isVisible = true } + metadata.relation?.let { + binding.badges.localText.setText(it.resId) + binding.badges.localText.isVisible = true + } } } // SY <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceCompactGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceCompactGridHolder.kt index ba629a433..7c4681f1b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceCompactGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceCompactGridHolder.kt @@ -58,6 +58,10 @@ open class SourceCompactGridHolder(private val view: View, private val adapter: binding.badges.localText.text = itemView.context.resources.getStringArray(R.array.md_follows_options).asList()[it] binding.badges.localText.isVisible = true } + metadata.relation?.let { + binding.badges.localText.setText(it.resId) + binding.badges.localText.isVisible = true + } } } // SY <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceListHolder.kt index 201e7618f..722a50587 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceListHolder.kt @@ -58,6 +58,10 @@ class SourceListHolder(private val view: View, adapter: FlexibleAdapter<*>) : binding.localText.text = itemView.context.resources.getStringArray(R.array.md_follows_options).asList()[it] binding.localText.isVisible = true } + metadata.relation?.let { + binding.localText.setText(it.resId) + binding.localText.isVisible = true + } } } // SY <-- diff --git a/app/src/main/java/exh/md/dto/SimilarDto.kt b/app/src/main/java/exh/md/dto/SimilarDto.kt index 4914d0639..db9985fed 100644 --- a/app/src/main/java/exh/md/dto/SimilarDto.kt +++ b/app/src/main/java/exh/md/dto/SimilarDto.kt @@ -18,3 +18,25 @@ data class SimilarMangaMatchListDto( val contentRating: String, val score: Double, ) + +@Serializable +data class RelationListDto( + val response: String, + val data: List, +) + +@Serializable +data class RelationDto( + val attributes: RelationAttributesDto, + val relationships: List, +) + +@Serializable +data class RelationMangaDto( + val id: String +) + +@Serializable +data class RelationAttributesDto( + val relation: String, +) diff --git a/app/src/main/java/exh/md/handlers/SimilarHandler.kt b/app/src/main/java/exh/md/handlers/SimilarHandler.kt index d8379bc7b..a0938d661 100644 --- a/app/src/main/java/exh/md/handlers/SimilarHandler.kt +++ b/app/src/main/java/exh/md/handlers/SimilarHandler.kt @@ -1,11 +1,15 @@ package exh.md.handlers -import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.MetadataMangasPage import eu.kanade.tachiyomi.source.model.toSManga +import eu.kanade.tachiyomi.util.lang.withIOContext +import exh.md.dto.RelationListDto import exh.md.dto.SimilarMangaDto import exh.md.service.MangaDexService import exh.md.service.SimilarService +import exh.md.utils.MangaDexRelation import exh.md.utils.MdUtil +import exh.metadata.metadata.MangaDexSearchMetadata import tachiyomi.source.model.MangaInfo class SimilarHandler( @@ -14,14 +18,14 @@ class SimilarHandler( private val similarService: SimilarService ) { - suspend fun getSimilar(manga: MangaInfo): MangasPage { - val similarDto = similarService.getSimilarManga(MdUtil.getMangaId(manga.key)) + suspend fun getSimilar(manga: MangaInfo): MetadataMangasPage { + val similarDto = withIOContext { similarService.getSimilarManga(MdUtil.getMangaId(manga.key)) } return similarDtoToMangaListPage(similarDto) } private suspend fun similarDtoToMangaListPage( similarMangaDto: SimilarMangaDto, - ): MangasPage { + ): MetadataMangasPage { val ids = similarMangaDto.matches.map { it.id } @@ -30,6 +34,34 @@ class SimilarHandler( MdUtil.createMangaEntry(it, lang).toSManga() } - return MangasPage(mangaList, false) + return MetadataMangasPage(mangaList, false, List(mangaList.size) { MangaDexSearchMetadata().also { it.relation = MangaDexRelation.SIMILAR } }) + } + + suspend fun getRelated(manga: MangaInfo): MetadataMangasPage { + val relatedListDto = withIOContext { service.relatedManga(MdUtil.getMangaId(manga.key)) } + return relatedDtoToMangaListPage(relatedListDto) + } + + private suspend fun relatedDtoToMangaListPage( + relatedListDto: RelationListDto, + ): MetadataMangasPage { + val ids = relatedListDto.data + .mapNotNull { it.relationships.firstOrNull() } + .map { it.id } + + val mangaList = service.viewMangas(ids).data.map { + MdUtil.createMangaEntry(it, lang).toSManga() + } + + return MetadataMangasPage( + mangas = mangaList, + hasNextPage = false, + mangasMetadata = mangaList.map { manga -> + MangaDexSearchMetadata().also { + it.relation = relatedListDto.data.firstOrNull { it.relationships.any { it.id == MdUtil.getMangaId(manga.url) } } + ?.attributes?.relation?.let(MangaDexRelation::fromDex) + } + } + ) } } diff --git a/app/src/main/java/exh/md/service/MangaDexService.kt b/app/src/main/java/exh/md/service/MangaDexService.kt index 304e4b1b9..f324f92a8 100644 --- a/app/src/main/java/exh/md/service/MangaDexService.kt +++ b/app/src/main/java/exh/md/service/MangaDexService.kt @@ -11,6 +11,7 @@ import exh.md.dto.ChapterDto import exh.md.dto.ChapterListDto import exh.md.dto.MangaDto import exh.md.dto.MangaListDto +import exh.md.dto.RelationListDto import exh.md.dto.ResultDto import exh.md.utils.MdApi import exh.md.utils.MdConstants @@ -145,6 +146,21 @@ class MangaDexService( ): AtHomeDto { return client.newCall(GET(atHomeRequestUrl, headers, CacheControl.FORCE_NETWORK)) .await() - .parseAs() + .parseAs(MdUtil.jsonParser) + } + + suspend fun relatedManga(id: String): RelationListDto { + return client.newCall( + GET( + MdApi.manga.toHttpUrl().newBuilder() + .apply { + addPathSegment(id) + addPathSegment("relation") + } + .build() + .toString(), + cache = CacheControl.FORCE_NETWORK + ) + ).await().parseAs(MdUtil.jsonParser) } } diff --git a/app/src/main/java/exh/md/similar/MangaDexSimilarPager.kt b/app/src/main/java/exh/md/similar/MangaDexSimilarPager.kt index 93fe6804b..cb7e74fd6 100644 --- a/app/src/main/java/exh/md/similar/MangaDexSimilarPager.kt +++ b/app/src/main/java/exh/md/similar/MangaDexSimilarPager.kt @@ -2,9 +2,12 @@ package exh.md.similar import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.toMangaInfo +import eu.kanade.tachiyomi.source.model.MetadataMangasPage import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.ui.browse.source.browse.NoResultsException import eu.kanade.tachiyomi.ui.browse.source.browse.Pager +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope /** * MangaDexSimilarPager inherited from the general Pager. @@ -12,7 +15,18 @@ import eu.kanade.tachiyomi.ui.browse.source.browse.Pager class MangaDexSimilarPager(val manga: Manga, val source: MangaDex) : Pager() { override suspend fun requestNextPage() { - val mangasPage = source.getMangaSimilar(manga.toMangaInfo()) + val mangasPage = coroutineScope { + val similarPageDef = async { source.getMangaSimilar(manga.toMangaInfo()) } + val relatedPageDef = async { source.getMangaRelated(manga.toMangaInfo()) } + val similarPage = similarPageDef.await() + val relatedPage = relatedPageDef.await() + + MetadataMangasPage( + relatedPage.mangas + similarPage.mangas, + false, + relatedPage.mangasMetadata + similarPage.mangasMetadata + ) + } if (mangasPage.mangas.isNotEmpty()) { onPageReceived(mangasPage) diff --git a/app/src/main/java/exh/md/utils/MangaDexRelation.kt b/app/src/main/java/exh/md/utils/MangaDexRelation.kt new file mode 100644 index 000000000..876d779a3 --- /dev/null +++ b/app/src/main/java/exh/md/utils/MangaDexRelation.kt @@ -0,0 +1,27 @@ +package exh.md.utils + +import androidx.annotation.StringRes +import eu.kanade.tachiyomi.R + +enum class MangaDexRelation(@StringRes val resId: Int, val mdString: String?) { + SIMILAR(R.string.relation_similar, null), + MONOCHROME(R.string.relation_monochrome, "monochrome"), + MAIN_STORY(R.string.relation_main_story, "main_story"), + ADAPTED_FROM(R.string.relation_adapted_from, "adapted_from"), + BASED_ON(R.string.relation_based_on, "based_on"), + PREQUEL(R.string.relation_prequel, "prequel"), + SIDE_STORY(R.string.relation_side_story, "side_story"), + DOUJINSHI(R.string.relation_doujinshi, "doujinshi"), + SAME_FRANCHISE(R.string.relation_same_franchise, "same_franchise"), + SHARED_UNIVERSE(R.string.relation_shared_universe, "shared_universe"), + SEQUEL(R.string.relation_sequel, "sequel"), + SPIN_OFF(R.string.relation_spin_off, "spin_off"), + ALTERNATE_STORY(R.string.relation_alternate_story, "alternate_story"), + PRESERIALIZATION(R.string.relation_preserialization, "preserialization"), + COLORED(R.string.relation_colored, "colored"), + SERIALIZATION(R.string.relation_serialization, "serialization"); + + companion object { + fun fromDex(mdString: String) = values().find { it.mdString == mdString } + } +} diff --git a/app/src/main/java/exh/metadata/metadata/MangaDexSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/MangaDexSearchMetadata.kt index 138f23121..5667ce5dd 100644 --- a/app/src/main/java/exh/metadata/metadata/MangaDexSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/MangaDexSearchMetadata.kt @@ -2,6 +2,7 @@ package exh.metadata.metadata import android.content.Context import eu.kanade.tachiyomi.R +import exh.md.utils.MangaDexRelation import exh.md.utils.MdUtil import exh.metadata.metadata.base.RaisedSearchMetadata import kotlinx.serialization.Serializable @@ -40,6 +41,7 @@ class MangaDexSearchMetadata : RaisedSearchMetadata() { // var missing_chapters: String? = null var followStatus: Int? = null + var relation: MangaDexRelation? = null // var maxChapterNumber: Int? = null diff --git a/app/src/main/res/values/strings_sy.xml b/app/src/main/res/values/strings_sy.xml index c2482348a..e42284405 100644 --- a/app/src/main/res/values/strings_sy.xml +++ b/app/src/main/res/values/strings_sy.xml @@ -669,6 +669,24 @@ Similar to %1$s No Similar Manga found + + + Similar + Monochrome + Main story + Adapted from + Based on + Prequel + Side story + Doujinshi + Same franchise + Shared universe + Sequel + Spin-off + Alternate story + Pre-serialization + Colored + Serialization