Mangadex support manga rating

This commit is contained in:
Jobobby04 2021-12-27 16:18:02 -05:00
parent 77f5acf2dd
commit d7856fe351
10 changed files with 98 additions and 27 deletions

View File

@ -26,6 +26,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.lang.runAsObservable import eu.kanade.tachiyomi.util.lang.runAsObservable
import exh.md.MangaDexFabHeaderAdapter import exh.md.MangaDexFabHeaderAdapter
import exh.md.dto.MangaDto import exh.md.dto.MangaDto
import exh.md.dto.StatisticsMangaDto
import exh.md.handlers.ApiMangaParser import exh.md.handlers.ApiMangaParser
import exh.md.handlers.BilibiliHandler import exh.md.handlers.BilibiliHandler
import exh.md.handlers.ComikeyHandler import exh.md.handlers.ComikeyHandler
@ -58,7 +59,7 @@ import kotlin.reflect.KClass
@Suppress("OverridingDeprecatedMember") @Suppress("OverridingDeprecatedMember")
class MangaDex(delegate: HttpSource, val context: Context) : class MangaDex(delegate: HttpSource, val context: Context) :
DelegatedHttpSource(delegate), DelegatedHttpSource(delegate),
MetadataSource<MangaDexSearchMetadata, Pair<MangaDto, List<String>>>, MetadataSource<MangaDexSearchMetadata, Triple<MangaDto, List<String>, StatisticsMangaDto>>,
UrlImportableSource, UrlImportableSource,
FollowsSource, FollowsSource,
LoginSource, LoginSource,
@ -206,8 +207,8 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return MangaDexDescriptionAdapter(controller) return MangaDexDescriptionAdapter(controller)
} }
override suspend fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Pair<MangaDto, List<String>>) { override suspend fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Triple<MangaDto, List<String>, StatisticsMangaDto>) {
apiMangaParser.parseIntoMetadata(metadata, input.first, input.second) apiMangaParser.parseIntoMetadata(metadata, input.first, input.second, input.third)
} }
// LoginSource methods // LoginSource methods
@ -265,11 +266,11 @@ class MangaDex(delegate: HttpSource, val context: Context) :
// Tracker methods // Tracker methods
/*suspend fun updateReadingProgress(track: Track): Boolean { /*suspend fun updateReadingProgress(track: Track): Boolean {
return followsHandler.updateReadingProgress(track) return followsHandler.updateReadingProgress(track)
} }*/
suspend fun updateRating(track: Track): Boolean { suspend fun updateRating(track: Track): Boolean {
return followsHandler.updateRating(track) return followsHandler.updateRating(track)
}*/ }
suspend fun getTrackingAndMangaInfo(track: Track): Pair<Track, MangaDexSearchMetadata?> { suspend fun getTrackingAndMangaInfo(track: Track): Pair<Track, MangaDexSearchMetadata?> {
return mangaHandler.getTrackingInfo(track) return mangaHandler.getTrackingInfo(track)

View File

@ -0,0 +1,18 @@
package exh.md.dto
import kotlinx.serialization.Serializable
@Serializable
data class StatisticsDto(
val statistics: Map<String, StatisticsMangaDto>
)
@Serializable
data class StatisticsMangaDto(
val rating: StatisticsMangaRatingDto
)
@Serializable
data class StatisticsMangaRatingDto(
val average: Double?
)

View File

@ -6,6 +6,7 @@ import exh.log.xLogE
import exh.md.dto.ChapterDataDto import exh.md.dto.ChapterDataDto
import exh.md.dto.ChapterDto import exh.md.dto.ChapterDto
import exh.md.dto.MangaDto import exh.md.dto.MangaDto
import exh.md.dto.StatisticsMangaDto
import exh.md.utils.MdConstants import exh.md.utils.MdConstants
import exh.md.utils.MdUtil import exh.md.utils.MdUtil
import exh.md.utils.asMdMap import exh.md.utils.asMdMap
@ -36,14 +37,20 @@ class ApiMangaParser(
}?.call() }?.call()
?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!") ?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!")
fun parseToManga(manga: MangaInfo, input: MangaDto, simpleChapters: List<String>, sourceId: Long): MangaInfo { fun parseToManga(
manga: MangaInfo,
sourceId: Long,
input: MangaDto,
simpleChapters: List<String>,
statistics: StatisticsMangaDto?
): MangaInfo {
val mangaId = db.getManga(manga.key, sourceId).executeAsBlocking()?.id val mangaId = db.getManga(manga.key, sourceId).executeAsBlocking()?.id
val metadata = if (mangaId != null) { val metadata = if (mangaId != null) {
val flatMetadata = db.getFlatMetadataForManga(mangaId).executeAsBlocking() val flatMetadata = db.getFlatMetadataForManga(mangaId).executeAsBlocking()
flatMetadata?.raise(metaClass) ?: newMetaInstance() flatMetadata?.raise(metaClass) ?: newMetaInstance()
} else newMetaInstance() } else newMetaInstance()
parseIntoMetadata(metadata, input, simpleChapters) parseIntoMetadata(metadata, input, simpleChapters, statistics)
if (mangaId != null) { if (mangaId != null) {
metadata.mangaId = mangaId metadata.mangaId = mangaId
db.insertFlatMetadata(metadata.flatten()) db.insertFlatMetadata(metadata.flatten())
@ -52,7 +59,12 @@ class ApiMangaParser(
return metadata.createMangaInfo(manga) return metadata.createMangaInfo(manga)
} }
fun parseIntoMetadata(metadata: MangaDexSearchMetadata, mangaDto: MangaDto, simpleChapters: List<String>) { fun parseIntoMetadata(
metadata: MangaDexSearchMetadata,
mangaDto: MangaDto,
simpleChapters: List<String>,
statistics: StatisticsMangaDto?
) {
with(metadata) { with(metadata) {
try { try {
val mangaAttributesDto = mangaDto.data.attributes val mangaAttributesDto = mangaDto.data.attributes
@ -83,12 +95,12 @@ class ApiMangaParser(
val lastChapter = mangaAttributesDto.lastChapter?.toFloatOrNull() val lastChapter = mangaAttributesDto.lastChapter?.toFloatOrNull()
lastChapterNumber = lastChapter?.floor() lastChapterNumber = lastChapter?.floor()
/*networkManga.rating?.let { statistics?.rating?.let {
manga.rating = it.bayesian ?: it.mean rating = it.average?.toFloat()
manga.users = it.users // manga.users = it.users
}*/ }
mangaAttributesDto.links?.asMdMap()?.let { links -> mangaAttributesDto.links?.asMdMap<String>()?.let { links ->
links["al"]?.let { anilistId = it } links["al"]?.let { anilistId = it }
links["kt"]?.let { kitsuId = it } links["kt"]?.let { kitsuId = it }
links["mal"]?.let { myAnimeListId = it } links["mal"]?.let { myAnimeListId = it }

View File

@ -15,7 +15,9 @@ import exh.md.utils.MdUtil
import exh.md.utils.mdListCall import exh.md.utils.mdListCall
import exh.metadata.metadata.MangaDexSearchMetadata import exh.metadata.metadata.MangaDexSearchMetadata
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import rx.Observable import rx.Observable
import tachiyomi.source.model.ChapterInfo import tachiyomi.source.model.ChapterInfo
import tachiyomi.source.model.MangaInfo import tachiyomi.source.model.MangaInfo
@ -27,9 +29,19 @@ class MangaHandler(
private val followsHandler: FollowsHandler private val followsHandler: FollowsHandler
) { ) {
suspend fun getMangaDetails(manga: MangaInfo, sourceId: Long): MangaInfo { suspend fun getMangaDetails(manga: MangaInfo, sourceId: Long): MangaInfo {
val response = withIOContext { service.viewManga(MdUtil.getMangaId(manga.key)) } return coroutineScope {
val simpleChapters = withIOContext { getSimpleChapters(manga) } val mangaId = MdUtil.getMangaId(manga.key)
return apiMangaParser.parseToManga(manga, response, simpleChapters, sourceId) val response = async(Dispatchers.IO) { service.viewManga(mangaId) }
val simpleChapters = async(Dispatchers.IO) { getSimpleChapters(manga) }
val statistics = async(Dispatchers.IO) { service.mangasRating(mangaId).statistics[mangaId] }
apiMangaParser.parseToManga(
manga,
sourceId,
response.await(),
simpleChapters.await(),
statistics.await()
)
}
} }
fun fetchMangaDetailsObservable(manga: SManga, sourceId: Long): Observable<SManga> { fun fetchMangaDetailsObservable(manga: SManga, sourceId: Long): Observable<SManga> {

View File

@ -13,6 +13,7 @@ import exh.md.dto.MangaDto
import exh.md.dto.MangaListDto import exh.md.dto.MangaListDto
import exh.md.dto.RelationListDto import exh.md.dto.RelationListDto
import exh.md.dto.ResultDto import exh.md.dto.ResultDto
import exh.md.dto.StatisticsDto
import exh.md.utils.MdApi import exh.md.utils.MdApi
import exh.md.utils.MdConstants import exh.md.utils.MdConstants
import exh.md.utils.MdUtil import exh.md.utils.MdUtil
@ -66,6 +67,25 @@ class MangaDexService(
).await().parseAs(MdUtil.jsonParser) ).await().parseAs(MdUtil.jsonParser)
} }
suspend fun mangasRating(
vararg ids: String
): StatisticsDto {
return client.newCall(
GET(
MdApi.statistics.toHttpUrl()
.newBuilder()
.apply {
ids.forEach { id ->
addQueryParameter("manga[]", id)
}
}
.build()
.toString(),
cache = CacheControl.FORCE_NETWORK
)
).await().parseAs(MdUtil.jsonParser)
}
suspend fun aggregateChapters( suspend fun aggregateChapters(
id: String, id: String,
translatedLanguage: String translatedLanguage: String

View File

@ -10,6 +10,7 @@ object MdApi {
const val chapter = "$baseUrl/chapter" const val chapter = "$baseUrl/chapter"
const val group = "$baseUrl/group" const val group = "$baseUrl/group"
const val author = "$baseUrl/author" const val author = "$baseUrl/author"
const val statistics = "$baseUrl/statistics/manga"
const val chapterImageServer = "$baseUrl/at-home/server" const val chapterImageServer = "$baseUrl/at-home/server"
const val userFollows = "$baseUrl/user/follows/manga" const val userFollows = "$baseUrl/user/follows/manga"
const val readingStatusForAllManga = "$baseUrl/manga/status" const val readingStatusForAllManga = "$baseUrl/manga/status"

View File

@ -3,9 +3,8 @@ package exh.md.utils
import exh.md.dto.ListCallDto import exh.md.dto.ListCallDto
import exh.util.under import exh.util.under
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
suspend fun <T> mdListCall(request: suspend (offset: Int) -> ListCallDto<T>): List<T> { suspend fun <T> mdListCall(request: suspend (offset: Int) -> ListCallDto<T>): List<T> {
val results = mutableListOf<T>() val results = mutableListOf<T>()
@ -20,8 +19,8 @@ suspend fun <T> mdListCall(request: suspend (offset: Int) -> ListCallDto<T>): Li
return results return results
} }
fun JsonElement.asMdMap(): Map<String, String> { fun <T> JsonElement.asMdMap(): Map<String, T> {
return runCatching { return runCatching {
jsonObject.map { it.key to it.value.jsonPrimitive.contentOrNull.orEmpty() }.toMap() MdUtil.jsonParser.decodeFromJsonElement<Map<String, T>>(jsonObject)
}.getOrElse { emptyMap() } }.getOrElse { emptyMap() }
} }

View File

@ -27,7 +27,7 @@ class MangaDexSearchMetadata : RaisedSearchMetadata() {
var langFlag: String? = null var langFlag: String? = null
var lastChapterNumber: Int? = null var lastChapterNumber: Int? = null
// var rating: String? = null var rating: Float? = null
// var users: String? = null // var users: String? = null
var anilistId: String? = null var anilistId: String? = null

View File

@ -1,5 +1,6 @@
package exh.ui.metadata.adapters package exh.ui.metadata.adapters
import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -10,9 +11,11 @@ import eu.kanade.tachiyomi.databinding.DescriptionAdapterMdBinding
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil.getRatingString
import exh.metadata.bindDrawable import exh.metadata.bindDrawable
import exh.metadata.metadata.MangaDexSearchMetadata import exh.metadata.metadata.MangaDexSearchMetadata
import exh.ui.metadata.MetadataViewController import exh.ui.metadata.MetadataViewController
import kotlin.math.round
class MangaDexDescriptionAdapter( class MangaDexDescriptionAdapter(
private val controller: MangaController private val controller: MangaController
@ -39,12 +42,12 @@ class MangaDexDescriptionAdapter(
if (meta == null || meta !is MangaDexSearchMetadata) return if (meta == null || meta !is MangaDexSearchMetadata) return
// todo // todo
/*val ratingFloat = meta.rating?.toFloatOrNull() val ratingFloat = meta.rating
binding.ratingBar.rating = ratingFloat?.div(2F) ?: 0F binding.ratingBar.rating = ratingFloat?.div(2F) ?: 0F
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
binding.rating.text = (round((meta.rating?.toFloatOrNull() ?: 0F) * 100.0) / 100.0).toString() + " - " + getRatingString(itemView.context, ratingFloat)*/ binding.rating.text = (round((ratingFloat ?: 0F) * 100.0) / 100.0).toString() + " - " + getRatingString(itemView.context, ratingFloat)
binding.rating.isVisible = false binding.rating.isVisible = ratingFloat != null
binding.ratingBar.isVisible = false binding.ratingBar.isVisible = ratingFloat != null
binding.moreInfo.bindDrawable(itemView.context, R.drawable.ic_info_24dp) binding.moreInfo.bindDrawable(itemView.context, R.drawable.ic_info_24dp)

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -11,9 +12,11 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:textAppearance="?attr/textAppearanceBodyMedium" android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/rating_bar" app:layout_constraintStart_toEndOf="@+id/rating_bar"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<me.zhanghai.android.materialratingbar.MaterialRatingBar <me.zhanghai.android.materialratingbar.MaterialRatingBar
@ -25,9 +28,11 @@
android:focusable="false" android:focusable="false"
android:isIndicator="true" android:isIndicator="true"
android:numStars="5" android:numStars="5"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<Button <Button
android:id="@+id/more_info" android:id="@+id/more_info"