mirror of
https://git.mihon.tech/mihonapp/mihon
synced 2024-11-23 13:45:43 +03:00
Add MangaUpdates as a tracker (#7170)
* Add MangaUpdates as a tracker - jobobby04 co-authored for suggestion in BackupTracking.kt Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com> * Changes from code review Co-authored-by: arkon <arkon@users.noreply.github.com> Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com> Co-authored-by: arkon <arkon@users.noreply.github.com>
This commit is contained in:
parent
9b0d85bf6c
commit
0c631a4990
29 changed files with 513 additions and 24 deletions
|
@ -12,7 +12,7 @@ data class BackupTracking(
|
||||||
@ProtoNumber(1) var syncId: Int,
|
@ProtoNumber(1) var syncId: Int,
|
||||||
// LibraryId is not null in 1.x
|
// LibraryId is not null in 1.x
|
||||||
@ProtoNumber(2) var libraryId: Long,
|
@ProtoNumber(2) var libraryId: Long,
|
||||||
@ProtoNumber(3) var mediaId: Int = 0,
|
@Deprecated("Use mediaId instead", level = DeprecationLevel.WARNING) @ProtoNumber(3) var mediaIdInt: Int = 0,
|
||||||
// trackingUrl is called mediaUrl in 1.x
|
// trackingUrl is called mediaUrl in 1.x
|
||||||
@ProtoNumber(4) var trackingUrl: String = "",
|
@ProtoNumber(4) var trackingUrl: String = "",
|
||||||
@ProtoNumber(5) var title: String = "",
|
@ProtoNumber(5) var title: String = "",
|
||||||
|
@ -25,11 +25,17 @@ data class BackupTracking(
|
||||||
@ProtoNumber(10) var startedReadingDate: Long = 0,
|
@ProtoNumber(10) var startedReadingDate: Long = 0,
|
||||||
// finishedReadingDate is called endReadTime in 1.x
|
// finishedReadingDate is called endReadTime in 1.x
|
||||||
@ProtoNumber(11) var finishedReadingDate: Long = 0,
|
@ProtoNumber(11) var finishedReadingDate: Long = 0,
|
||||||
|
@ProtoNumber(100) var mediaId: Long = 0,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun getTrackingImpl(): TrackImpl {
|
fun getTrackingImpl(): TrackImpl {
|
||||||
return TrackImpl().apply {
|
return TrackImpl().apply {
|
||||||
sync_id = this@BackupTracking.syncId
|
sync_id = this@BackupTracking.syncId
|
||||||
media_id = this@BackupTracking.mediaId
|
media_id = if (this@BackupTracking.mediaIdInt != 0) {
|
||||||
|
this@BackupTracking.mediaIdInt.toLong()
|
||||||
|
} else {
|
||||||
|
this@BackupTracking.mediaId
|
||||||
|
}
|
||||||
library_id = this@BackupTracking.libraryId
|
library_id = this@BackupTracking.libraryId
|
||||||
title = this@BackupTracking.title
|
title = this@BackupTracking.title
|
||||||
last_chapter_read = this@BackupTracking.lastChapterRead
|
last_chapter_read = this@BackupTracking.lastChapterRead
|
||||||
|
|
|
@ -45,7 +45,7 @@ open class TrackBaseSerializer<T : Track> : KSerializer<T> {
|
||||||
val jsonObject = decoder.decodeJsonElement().jsonObject
|
val jsonObject = decoder.decodeJsonElement().jsonObject
|
||||||
title = jsonObject[TITLE]!!.jsonPrimitive.content
|
title = jsonObject[TITLE]!!.jsonPrimitive.content
|
||||||
sync_id = jsonObject[SYNC]!!.jsonPrimitive.int
|
sync_id = jsonObject[SYNC]!!.jsonPrimitive.int
|
||||||
media_id = jsonObject[MEDIA]!!.jsonPrimitive.int
|
media_id = jsonObject[MEDIA]!!.jsonPrimitive.long
|
||||||
library_id = jsonObject[LIBRARY]!!.jsonPrimitive.long
|
library_id = jsonObject[LIBRARY]!!.jsonPrimitive.long
|
||||||
last_chapter_read = jsonObject[LAST_READ]!!.jsonPrimitive.float
|
last_chapter_read = jsonObject[LAST_READ]!!.jsonPrimitive.float
|
||||||
tracking_url = jsonObject[TRACKING_URL]!!.jsonPrimitive.content
|
tracking_url = jsonObject[TRACKING_URL]!!.jsonPrimitive.content
|
||||||
|
|
|
@ -68,7 +68,7 @@ class TrackGetResolver : DefaultGetResolver<Track>() {
|
||||||
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
|
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
|
||||||
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
|
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
|
||||||
sync_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_SYNC_ID))
|
sync_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_SYNC_ID))
|
||||||
media_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_MEDIA_ID))
|
media_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MEDIA_ID))
|
||||||
library_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LIBRARY_ID))
|
library_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LIBRARY_ID))
|
||||||
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE))
|
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE))
|
||||||
last_chapter_read = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_LAST_CHAPTER_READ))
|
last_chapter_read = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_LAST_CHAPTER_READ))
|
||||||
|
|
|
@ -10,7 +10,7 @@ interface Track : Serializable {
|
||||||
|
|
||||||
var sync_id: Int
|
var sync_id: Int
|
||||||
|
|
||||||
var media_id: Int
|
var media_id: Long
|
||||||
|
|
||||||
var library_id: Long?
|
var library_id: Long?
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ class TrackImpl : Track {
|
||||||
|
|
||||||
override var sync_id: Int = 0
|
override var sync_id: Int = 0
|
||||||
|
|
||||||
override var media_id: Int = 0
|
override var media_id: Long = 0
|
||||||
|
|
||||||
override var library_id: Long? = null
|
override var library_id: Long? = null
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class TrackImpl : Track {
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = (manga_id xor manga_id.ushr(32)).toInt()
|
var result = (manga_id xor manga_id.ushr(32)).toInt()
|
||||||
result = 31 * result + sync_id
|
result = 31 * result + sync_id
|
||||||
result = 31 * result + media_id
|
result = 31 * result + media_id.toInt()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||||
import eu.kanade.tachiyomi.data.track.bangumi.Bangumi
|
import eu.kanade.tachiyomi.data.track.bangumi.Bangumi
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.Kitsu
|
import eu.kanade.tachiyomi.data.track.kitsu.Kitsu
|
||||||
import eu.kanade.tachiyomi.data.track.komga.Komga
|
import eu.kanade.tachiyomi.data.track.komga.Komga
|
||||||
|
import eu.kanade.tachiyomi.data.track.mangaupdates.MangaUpdates
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
|
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
|
import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ class TrackManager(context: Context) {
|
||||||
const val SHIKIMORI = 4
|
const val SHIKIMORI = 4
|
||||||
const val BANGUMI = 5
|
const val BANGUMI = 5
|
||||||
const val KOMGA = 6
|
const val KOMGA = 6
|
||||||
|
const val MANGA_UPDATES = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
val myAnimeList = MyAnimeList(context, MYANIMELIST)
|
val myAnimeList = MyAnimeList(context, MYANIMELIST)
|
||||||
|
@ -31,7 +33,9 @@ class TrackManager(context: Context) {
|
||||||
|
|
||||||
val komga = Komga(context, KOMGA)
|
val komga = Komga(context, KOMGA)
|
||||||
|
|
||||||
val services = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga)
|
val mangaUpdates = MangaUpdates(context, MANGA_UPDATES)
|
||||||
|
|
||||||
|
val services = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga, mangaUpdates)
|
||||||
|
|
||||||
fun getService(id: Int) = services.find { it.id == id }
|
fun getService(id: Int) = services.find { it.id == id }
|
||||||
|
|
||||||
|
|
|
@ -268,7 +268,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
|
|
||||||
private fun jsonToALManga(struct: JsonObject): ALManga {
|
private fun jsonToALManga(struct: JsonObject): ALManga {
|
||||||
return ALManga(
|
return ALManga(
|
||||||
struct["id"]!!.jsonPrimitive.int,
|
struct["id"]!!.jsonPrimitive.long,
|
||||||
struct["title"]!!.jsonObject["userPreferred"]!!.jsonPrimitive.content,
|
struct["title"]!!.jsonObject["userPreferred"]!!.jsonPrimitive.content,
|
||||||
struct["coverImage"]!!.jsonObject["large"]!!.jsonPrimitive.content,
|
struct["coverImage"]!!.jsonObject["large"]!!.jsonPrimitive.content,
|
||||||
struct["description"]!!.jsonPrimitive.contentOrNull,
|
struct["description"]!!.jsonPrimitive.contentOrNull,
|
||||||
|
@ -329,7 +329,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
private const val baseUrl = "https://anilist.co/api/v2/"
|
private const val baseUrl = "https://anilist.co/api/v2/"
|
||||||
private const val baseMangaUrl = "https://anilist.co/manga/"
|
private const val baseMangaUrl = "https://anilist.co/manga/"
|
||||||
|
|
||||||
fun mangaUrl(mediaId: Int): String {
|
fun mangaUrl(mediaId: Long): String {
|
||||||
return baseMangaUrl + mediaId
|
return baseMangaUrl + mediaId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
data class ALManga(
|
data class ALManga(
|
||||||
val media_id: Int,
|
val media_id: Long,
|
||||||
val title_user_pref: String,
|
val title_user_pref: String,
|
||||||
val image_url_lge: String,
|
val image_url_lge: String,
|
||||||
val description: String?,
|
val description: String?,
|
||||||
|
|
|
@ -18,6 +18,7 @@ import kotlinx.serialization.json.int
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import kotlinx.serialization.json.long
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
@ -106,7 +107,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
return TrackSearch.create(TrackManager.BANGUMI).apply {
|
return TrackSearch.create(TrackManager.BANGUMI).apply {
|
||||||
media_id = obj["id"]!!.jsonPrimitive.int
|
media_id = obj["id"]!!.jsonPrimitive.long
|
||||||
title = obj["name_cn"]!!.jsonPrimitive.content
|
title = obj["name_cn"]!!.jsonPrimitive.content
|
||||||
cover_url = coverUrl
|
cover_url = coverUrl
|
||||||
summary = obj["name"]!!.jsonPrimitive.content
|
summary = obj["name"]!!.jsonPrimitive.content
|
||||||
|
|
|
@ -11,10 +11,10 @@ import eu.kanade.tachiyomi.network.parseAs
|
||||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.int
|
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import kotlinx.serialization.json.long
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import kotlinx.serialization.json.putJsonObject
|
import kotlinx.serialization.json.putJsonObject
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
|
@ -70,7 +70,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
||||||
.await()
|
.await()
|
||||||
.parseAs<JsonObject>()
|
.parseAs<JsonObject>()
|
||||||
.let {
|
.let {
|
||||||
track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.int
|
track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.long
|
||||||
track
|
track
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
||||||
private const val algoliaFilter =
|
private const val algoliaFilter =
|
||||||
"&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D"
|
"&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D"
|
||||||
|
|
||||||
fun mangaUrl(remoteId: Int): String {
|
fun mangaUrl(remoteId: Long): String {
|
||||||
return baseMangaUrl + remoteId
|
return baseMangaUrl + remoteId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,13 @@ import kotlinx.serialization.json.int
|
||||||
import kotlinx.serialization.json.intOrNull
|
import kotlinx.serialization.json.intOrNull
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import kotlinx.serialization.json.long
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class KitsuSearchManga(obj: JsonObject) {
|
class KitsuSearchManga(obj: JsonObject) {
|
||||||
val id = obj["id"]!!.jsonPrimitive.int
|
val id = obj["id"]!!.jsonPrimitive.long
|
||||||
private val canonicalTitle = obj["canonicalTitle"]!!.jsonPrimitive.content
|
private val canonicalTitle = obj["canonicalTitle"]!!.jsonPrimitive.content
|
||||||
private val chapterCount = obj["chapterCount"]?.jsonPrimitive?.intOrNull
|
private val chapterCount = obj["chapterCount"]?.jsonPrimitive?.intOrNull
|
||||||
val subType = obj["subtype"]?.jsonPrimitive?.contentOrNull
|
val subType = obj["subtype"]?.jsonPrimitive?.contentOrNull
|
||||||
|
@ -60,7 +61,7 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
|
||||||
private val startDate = manga["attributes"]!!.jsonObject["startDate"]?.jsonPrimitive?.contentOrNull.orEmpty()
|
private val startDate = manga["attributes"]!!.jsonObject["startDate"]?.jsonPrimitive?.contentOrNull.orEmpty()
|
||||||
private val startedAt = obj["attributes"]!!.jsonObject["startedAt"]?.jsonPrimitive?.contentOrNull
|
private val startedAt = obj["attributes"]!!.jsonObject["startedAt"]?.jsonPrimitive?.contentOrNull
|
||||||
private val finishedAt = obj["attributes"]!!.jsonObject["finishedAt"]?.jsonPrimitive?.contentOrNull
|
private val finishedAt = obj["attributes"]!!.jsonObject["finishedAt"]?.jsonPrimitive?.contentOrNull
|
||||||
private val libraryId = obj["id"]!!.jsonPrimitive.int
|
private val libraryId = obj["id"]!!.jsonPrimitive.long
|
||||||
val status = obj["attributes"]!!.jsonObject["status"]!!.jsonPrimitive.content
|
val status = obj["attributes"]!!.jsonObject["status"]!!.jsonPrimitive.content
|
||||||
private val ratingTwenty = obj["attributes"]!!.jsonObject["ratingTwenty"]?.jsonPrimitive?.contentOrNull
|
private val ratingTwenty = obj["attributes"]!!.jsonObject["ratingTwenty"]?.jsonPrimitive?.contentOrNull
|
||||||
val progress = obj["attributes"]!!.jsonObject["progress"]!!.jsonPrimitive.int
|
val progress = obj["attributes"]!!.jsonObject["progress"]!!.jsonPrimitive.int
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.mangaupdates
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
|
||||||
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
|
||||||
|
class MangaUpdates(private val context: Context, id: Int) : TrackService(id) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val READING_LIST = 0
|
||||||
|
const val WISH_LIST = 1
|
||||||
|
const val COMPLETE_LIST = 2
|
||||||
|
const val UNFINISHED_LIST = 3
|
||||||
|
const val ON_HOLD_LIST = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
private val interceptor by lazy { MangaUpdatesInterceptor(this) }
|
||||||
|
|
||||||
|
private val api by lazy { MangaUpdatesApi(interceptor, client) }
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
override fun nameRes(): Int = R.string.tracker_manga_updates
|
||||||
|
|
||||||
|
override fun getLogo(): Int = R.drawable.ic_manga_updates
|
||||||
|
|
||||||
|
override fun getLogoColor(): Int = Color.rgb(146, 160, 173)
|
||||||
|
|
||||||
|
override fun getStatusList(): List<Int> {
|
||||||
|
return listOf(READING_LIST, COMPLETE_LIST, ON_HOLD_LIST, UNFINISHED_LIST, WISH_LIST)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStatus(status: Int): String = with(context) {
|
||||||
|
when (status) {
|
||||||
|
READING_LIST -> getString(R.string.reading_list)
|
||||||
|
WISH_LIST -> getString(R.string.wish_list)
|
||||||
|
COMPLETE_LIST -> getString(R.string.complete_list)
|
||||||
|
ON_HOLD_LIST -> getString(R.string.on_hold_list)
|
||||||
|
UNFINISHED_LIST -> getString(R.string.unfinished_list)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getReadingStatus(): Int = READING_LIST
|
||||||
|
|
||||||
|
override fun getRereadingStatus(): Int = -1
|
||||||
|
|
||||||
|
override fun getCompletionStatus(): Int = COMPLETE_LIST
|
||||||
|
|
||||||
|
override fun getScoreList(): List<String> = (0..10).map(Int::toString)
|
||||||
|
|
||||||
|
override fun displayScore(track: Track): String = track.score.toInt().toString()
|
||||||
|
|
||||||
|
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||||
|
api.updateSeriesListItem(track)
|
||||||
|
return track
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
||||||
|
return try {
|
||||||
|
val (series, rating) = api.getSeriesListItem(track)
|
||||||
|
series.copyTo(track)
|
||||||
|
rating?.copyTo(track) ?: track
|
||||||
|
} catch (e: Exception) {
|
||||||
|
api.addSeriesToList(track, hasReadChapters)
|
||||||
|
track
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<TrackSearch> {
|
||||||
|
return api.search(query)
|
||||||
|
.map {
|
||||||
|
it.toTrackSearch(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun refresh(track: Track): Track {
|
||||||
|
val (series, rating) = api.getSeriesListItem(track)
|
||||||
|
series.copyTo(track)
|
||||||
|
return rating?.copyTo(track) ?: track
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun login(username: String, password: String) {
|
||||||
|
val authenticated = api.authenticate(username, password) ?: throw Throwable("Unable to login")
|
||||||
|
saveCredentials(authenticated.uid.toString(), authenticated.sessionToken)
|
||||||
|
interceptor.newAuth(authenticated.sessionToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restoreSession(): String? {
|
||||||
|
return preferences.trackPassword(this)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.mangaupdates
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.mangaupdates.MangaUpdates.Companion.READING_LIST
|
||||||
|
import eu.kanade.tachiyomi.data.track.mangaupdates.MangaUpdates.Companion.WISH_LIST
|
||||||
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.Context
|
||||||
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.ListItem
|
||||||
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.Rating
|
||||||
|
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.Record
|
||||||
|
import eu.kanade.tachiyomi.network.DELETE
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import eu.kanade.tachiyomi.network.PUT
|
||||||
|
import eu.kanade.tachiyomi.network.await
|
||||||
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.addJsonObject
|
||||||
|
import kotlinx.serialization.json.buildJsonArray
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
import kotlinx.serialization.json.putJsonObject
|
||||||
|
import logcat.LogPriority
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class MangaUpdatesApi(
|
||||||
|
interceptor: MangaUpdatesInterceptor,
|
||||||
|
private val client: OkHttpClient,
|
||||||
|
) {
|
||||||
|
private val baseUrl = "https://api.mangaupdates.com"
|
||||||
|
private val contentType = "application/vnd.api+json".toMediaType()
|
||||||
|
|
||||||
|
private val json by injectLazy<Json>()
|
||||||
|
|
||||||
|
private val authClient by lazy {
|
||||||
|
client.newBuilder()
|
||||||
|
.addInterceptor(interceptor)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeriesListItem(track: Track): Pair<ListItem, Rating?> {
|
||||||
|
val listItem =
|
||||||
|
authClient.newCall(
|
||||||
|
GET(
|
||||||
|
url = "$baseUrl/v1/lists/series/${track.media_id}",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await()
|
||||||
|
.parseAs<ListItem>()
|
||||||
|
|
||||||
|
val rating = getSeriesRating(track)
|
||||||
|
|
||||||
|
return listItem to rating
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addSeriesToList(track: Track, hasReadChapters: Boolean) {
|
||||||
|
val status = if (hasReadChapters) READING_LIST else WISH_LIST
|
||||||
|
val body = buildJsonArray {
|
||||||
|
addJsonObject {
|
||||||
|
putJsonObject("series") {
|
||||||
|
put("id", track.media_id)
|
||||||
|
}
|
||||||
|
put("list_id", status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authClient.newCall(
|
||||||
|
POST(
|
||||||
|
url = "$baseUrl/v1/lists/series",
|
||||||
|
body = body.toString().toRequestBody(contentType),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await()
|
||||||
|
.let {
|
||||||
|
if (it.code == 200) {
|
||||||
|
track.status = status
|
||||||
|
track.last_chapter_read = 1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateSeriesListItem(track: Track) {
|
||||||
|
val body = buildJsonArray {
|
||||||
|
addJsonObject {
|
||||||
|
putJsonObject("series") {
|
||||||
|
put("id", track.media_id)
|
||||||
|
}
|
||||||
|
put("list_id", track.status)
|
||||||
|
putJsonObject("status") {
|
||||||
|
put("chapter", track.last_chapter_read.toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authClient.newCall(
|
||||||
|
POST(
|
||||||
|
url = "$baseUrl/v1/lists/series/update",
|
||||||
|
body = body.toString().toRequestBody(contentType),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await()
|
||||||
|
|
||||||
|
updateSeriesRating(track)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeriesRating(track: Track): Rating? {
|
||||||
|
return try {
|
||||||
|
authClient.newCall(
|
||||||
|
GET(
|
||||||
|
url = "$baseUrl/v1/series/${track.media_id}/rating",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await()
|
||||||
|
.parseAs<Rating>()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateSeriesRating(track: Track) {
|
||||||
|
if (track.score != 0f) {
|
||||||
|
val body = buildJsonObject {
|
||||||
|
put("rating", track.score.toInt())
|
||||||
|
}
|
||||||
|
authClient.newCall(
|
||||||
|
PUT(
|
||||||
|
url = "$baseUrl/v1/series/${track.media_id}/rating",
|
||||||
|
body = body.toString().toRequestBody(contentType),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await()
|
||||||
|
} else {
|
||||||
|
authClient.newCall(
|
||||||
|
DELETE(
|
||||||
|
url = "$baseUrl/v1/series/${track.media_id}/rating",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun search(query: String): List<Record> {
|
||||||
|
val body = buildJsonObject {
|
||||||
|
put("search", query)
|
||||||
|
}
|
||||||
|
return client.newCall(
|
||||||
|
POST(
|
||||||
|
url = "$baseUrl/v1/series/search",
|
||||||
|
body = body.toString().toRequestBody(contentType),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await()
|
||||||
|
.parseAs<JsonObject>()
|
||||||
|
.let { obj ->
|
||||||
|
obj["results"]?.jsonArray?.map { element ->
|
||||||
|
json.decodeFromJsonElement<Record>(element.jsonObject["record"]!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun authenticate(username: String, password: String): Context? {
|
||||||
|
val body = buildJsonObject {
|
||||||
|
put("username", username)
|
||||||
|
put("password", password)
|
||||||
|
}
|
||||||
|
return client.newCall(
|
||||||
|
PUT(
|
||||||
|
url = "$baseUrl/v1/account/login",
|
||||||
|
body = body.toString().toRequestBody(contentType),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await()
|
||||||
|
.parseAs<JsonObject>()
|
||||||
|
.let { obj ->
|
||||||
|
try {
|
||||||
|
json.decodeFromJsonElement<Context>(obj["context"]!!)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.mangaupdates
|
||||||
|
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class MangaUpdatesInterceptor(
|
||||||
|
mangaUpdates: MangaUpdates,
|
||||||
|
) : Interceptor {
|
||||||
|
|
||||||
|
private var token: String? = mangaUpdates.restoreSession()
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val originalRequest = chain.request()
|
||||||
|
|
||||||
|
val token = token ?: throw IOException("Not authenticated with MangaUpdates")
|
||||||
|
|
||||||
|
// Add the authorization header to the original request.
|
||||||
|
val authRequest = originalRequest.newBuilder()
|
||||||
|
.addHeader("Authorization", "Bearer $token")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return chain.proceed(authRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newAuth(token: String?) {
|
||||||
|
this.token = token
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Context(
|
||||||
|
@SerialName("session_token")
|
||||||
|
val sessionToken: String,
|
||||||
|
val uid: Long,
|
||||||
|
)
|
|
@ -0,0 +1,10 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Image(
|
||||||
|
val url: Url? = null,
|
||||||
|
val height: Int? = null,
|
||||||
|
val width: Int? = null,
|
||||||
|
)
|
|
@ -0,0 +1,22 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.mangaupdates.MangaUpdates.Companion.READING_LIST
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ListItem(
|
||||||
|
val series: Series? = null,
|
||||||
|
@SerialName("list_id")
|
||||||
|
val listId: Int? = null,
|
||||||
|
val status: Status? = null,
|
||||||
|
val priority: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun ListItem.copyTo(track: Track): Track {
|
||||||
|
return track.apply {
|
||||||
|
this.status = listId ?: READING_LIST
|
||||||
|
this.last_chapter_read = this@copyTo.status?.chapter?.toFloat() ?: 0f
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Rating(
|
||||||
|
val rating: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Rating.copyTo(track: Track): Track {
|
||||||
|
return track.apply {
|
||||||
|
this.score = rating?.toFloat() ?: 0f
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Record(
|
||||||
|
@SerialName("series_id")
|
||||||
|
val seriesId: Long? = null,
|
||||||
|
val title: String? = null,
|
||||||
|
val url: String? = null,
|
||||||
|
val description: String? = null,
|
||||||
|
val image: Image? = null,
|
||||||
|
val type: String? = null,
|
||||||
|
val year: String? = null,
|
||||||
|
@SerialName("bayesian_rating")
|
||||||
|
val bayesianRating: Double? = null,
|
||||||
|
@SerialName("rating_votes")
|
||||||
|
val ratingVotes: Int? = null,
|
||||||
|
@SerialName("latest_chapter")
|
||||||
|
val latestChapter: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Record.toTrackSearch(id: Int): TrackSearch {
|
||||||
|
return TrackSearch.create(id).apply {
|
||||||
|
media_id = this@toTrackSearch.seriesId ?: 0L
|
||||||
|
title = this@toTrackSearch.title ?: ""
|
||||||
|
total_chapters = 0
|
||||||
|
cover_url = this@toTrackSearch.image?.url?.original ?: ""
|
||||||
|
summary = this@toTrackSearch.description ?: ""
|
||||||
|
tracking_url = this@toTrackSearch.url ?: ""
|
||||||
|
publishing_status = ""
|
||||||
|
publishing_type = this@toTrackSearch.type.toString()
|
||||||
|
start_date = this@toTrackSearch.year.toString()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Series(
|
||||||
|
val id: Long? = null,
|
||||||
|
val title: String? = null,
|
||||||
|
)
|
|
@ -0,0 +1,9 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Status(
|
||||||
|
val volume: Int? = null,
|
||||||
|
val chapter: Int? = null,
|
||||||
|
)
|
|
@ -0,0 +1,9 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.mangaupdates.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Url(
|
||||||
|
val original: String? = null,
|
||||||
|
val thumb: String? = null,
|
||||||
|
)
|
|
@ -10,7 +10,7 @@ class TrackSearch : Track {
|
||||||
|
|
||||||
override var sync_id: Int = 0
|
override var sync_id: Int = 0
|
||||||
|
|
||||||
override var media_id: Int = 0
|
override var media_id: Long = 0
|
||||||
|
|
||||||
override var library_id: Long? = null
|
override var library_id: Long? = null
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class TrackSearch : Track {
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = (manga_id xor manga_id.ushr(32)).toInt()
|
var result = (manga_id xor manga_id.ushr(32)).toInt()
|
||||||
result = 31 * result + sync_id
|
result = 31 * result + sync_id
|
||||||
result = 31 * result + media_id
|
result = 31 * result + media_id.toInt()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import kotlinx.serialization.json.int
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import kotlinx.serialization.json.long
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
@ -94,7 +95,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
||||||
.let {
|
.let {
|
||||||
val obj = it.jsonObject
|
val obj = it.jsonObject
|
||||||
TrackSearch.create(TrackManager.MYANIMELIST).apply {
|
TrackSearch.create(TrackManager.MYANIMELIST).apply {
|
||||||
media_id = obj["id"]!!.jsonPrimitive.int
|
media_id = obj["id"]!!.jsonPrimitive.long
|
||||||
title = obj["title"]!!.jsonPrimitive.content
|
title = obj["title"]!!.jsonPrimitive.content
|
||||||
summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""
|
summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""
|
||||||
total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
|
total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
|
||||||
|
@ -251,7 +252,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
||||||
.appendQueryParameter("response_type", "code")
|
.appendQueryParameter("response_type", "code")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun mangaUrl(id: Int): Uri = "$baseApiUrl/manga".toUri().buildUpon()
|
fun mangaUrl(id: Long): Uri = "$baseApiUrl/manga".toUri().buildUpon()
|
||||||
.appendPath(id.toString())
|
.appendPath(id.toString())
|
||||||
.appendPath("my_list_status")
|
.appendPath("my_list_status")
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -19,6 +19,7 @@ import kotlinx.serialization.json.float
|
||||||
import kotlinx.serialization.json.int
|
import kotlinx.serialization.json.int
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import kotlinx.serialization.json.long
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import kotlinx.serialization.json.putJsonObject
|
import kotlinx.serialization.json.putJsonObject
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
|
@ -73,7 +74,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
||||||
|
|
||||||
private fun jsonToSearch(obj: JsonObject): TrackSearch {
|
private fun jsonToSearch(obj: JsonObject): TrackSearch {
|
||||||
return TrackSearch.create(TrackManager.SHIKIMORI).apply {
|
return TrackSearch.create(TrackManager.SHIKIMORI).apply {
|
||||||
media_id = obj["id"]!!.jsonPrimitive.int
|
media_id = obj["id"]!!.jsonPrimitive.long
|
||||||
title = obj["name"]!!.jsonPrimitive.content
|
title = obj["name"]!!.jsonPrimitive.content
|
||||||
total_chapters = obj["chapters"]!!.jsonPrimitive.int
|
total_chapters = obj["chapters"]!!.jsonPrimitive.int
|
||||||
cover_url = baseUrl + obj["image"]!!.jsonObject["preview"]!!.jsonPrimitive.content
|
cover_url = baseUrl + obj["image"]!!.jsonObject["preview"]!!.jsonPrimitive.content
|
||||||
|
@ -88,7 +89,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
||||||
private fun jsonToTrack(obj: JsonObject, mangas: JsonObject): Track {
|
private fun jsonToTrack(obj: JsonObject, mangas: JsonObject): Track {
|
||||||
return Track.create(TrackManager.SHIKIMORI).apply {
|
return Track.create(TrackManager.SHIKIMORI).apply {
|
||||||
title = mangas["name"]!!.jsonPrimitive.content
|
title = mangas["name"]!!.jsonPrimitive.content
|
||||||
media_id = obj["id"]!!.jsonPrimitive.int
|
media_id = obj["id"]!!.jsonPrimitive.long
|
||||||
total_chapters = mangas["chapters"]!!.jsonPrimitive.int
|
total_chapters = mangas["chapters"]!!.jsonPrimitive.int
|
||||||
last_chapter_read = obj["chapters"]!!.jsonPrimitive.float
|
last_chapter_read = obj["chapters"]!!.jsonPrimitive.float
|
||||||
score = (obj["score"]!!.jsonPrimitive.int).toFloat()
|
score = (obj["score"]!!.jsonPrimitive.int).toFloat()
|
||||||
|
|
|
@ -36,3 +36,31 @@ fun POST(
|
||||||
.cacheControl(cache)
|
.cacheControl(cache)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun PUT(
|
||||||
|
url: String,
|
||||||
|
headers: Headers = DEFAULT_HEADERS,
|
||||||
|
body: RequestBody = DEFAULT_BODY,
|
||||||
|
cache: CacheControl = DEFAULT_CACHE_CONTROL,
|
||||||
|
): Request {
|
||||||
|
return Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.put(body)
|
||||||
|
.headers(headers)
|
||||||
|
.cacheControl(cache)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DELETE(
|
||||||
|
url: String,
|
||||||
|
headers: Headers = DEFAULT_HEADERS,
|
||||||
|
body: RequestBody = DEFAULT_BODY,
|
||||||
|
cache: CacheControl = DEFAULT_CACHE_CONTROL,
|
||||||
|
): Request {
|
||||||
|
return Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.delete(body)
|
||||||
|
.headers(headers)
|
||||||
|
.cacheControl(cache)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
|
@ -63,13 +63,17 @@ class SettingsTrackingController :
|
||||||
dialog.targetController = this@SettingsTrackingController
|
dialog.targetController = this@SettingsTrackingController
|
||||||
dialog.showDialog(router)
|
dialog.showDialog(router)
|
||||||
}
|
}
|
||||||
|
trackPreference(trackManager.mangaUpdates) {
|
||||||
|
val dialog = TrackLoginDialog(trackManager.mangaUpdates, R.string.username)
|
||||||
|
dialog.targetController = this@SettingsTrackingController
|
||||||
|
dialog.showDialog(router)
|
||||||
|
}
|
||||||
trackPreference(trackManager.shikimori) {
|
trackPreference(trackManager.shikimori) {
|
||||||
activity?.openInBrowser(ShikimoriApi.authUrl(), forceDefaultBrowser = true)
|
activity?.openInBrowser(ShikimoriApi.authUrl(), forceDefaultBrowser = true)
|
||||||
}
|
}
|
||||||
trackPreference(trackManager.bangumi) {
|
trackPreference(trackManager.bangumi) {
|
||||||
activity?.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true)
|
activity?.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
infoPreference(R.string.tracking_info)
|
infoPreference(R.string.tracking_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
app/src/main/res/drawable-nodpi/ic_manga_updates.webp
Normal file
BIN
app/src/main/res/drawable-nodpi/ic_manga_updates.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -643,6 +643,7 @@
|
||||||
<string name="tracker_komga_warning">This tracker is only compatible with the Komga source.</string>
|
<string name="tracker_komga_warning">This tracker is only compatible with the Komga source.</string>
|
||||||
<string name="tracker_bangumi" translatable="false">Bangumi</string>
|
<string name="tracker_bangumi" translatable="false">Bangumi</string>
|
||||||
<string name="tracker_shikimori" translatable="false">Shikimori</string>
|
<string name="tracker_shikimori" translatable="false">Shikimori</string>
|
||||||
|
<string name="tracker_manga_updates" translatable="false">MangaUpdates</string>
|
||||||
<string name="manga_tracking_tab">Tracking</string>
|
<string name="manga_tracking_tab">Tracking</string>
|
||||||
<plurals name="num_trackers">
|
<plurals name="num_trackers">
|
||||||
<item quantity="one">%d tracker</item>
|
<item quantity="one">%d tracker</item>
|
||||||
|
@ -657,6 +658,11 @@
|
||||||
<string name="paused">Paused</string>
|
<string name="paused">Paused</string>
|
||||||
<string name="plan_to_read">Plan to read</string>
|
<string name="plan_to_read">Plan to read</string>
|
||||||
<string name="repeating">Rereading</string>
|
<string name="repeating">Rereading</string>
|
||||||
|
<string name="reading_list">Reading List</string>
|
||||||
|
<string name="wish_list">Wish List</string>
|
||||||
|
<string name="complete_list">Complete List</string>
|
||||||
|
<string name="on_hold_list">On Hold List</string>
|
||||||
|
<string name="unfinished_list">Unfinished List</string>
|
||||||
<string name="score">Score</string>
|
<string name="score">Score</string>
|
||||||
<string name="title">Title</string>
|
<string name="title">Title</string>
|
||||||
<string name="status">Status</string>
|
<string name="status">Status</string>
|
||||||
|
|
Loading…
Reference in a new issue