Преглед на файлове

Decoupled Tracker Interface (#10042)

Split out Tracker to interface and created simple dummy instance for previews
Caleb Morris преди 1 година
родител
ревизия
3a35c13575

+ 169 - 0
app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt

@@ -0,0 +1,169 @@
+package eu.kanade.tachiyomi.data.track
+
+import android.app.Application
+import androidx.annotation.CallSuper
+import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
+import eu.kanade.domain.track.model.toDbTrack
+import eu.kanade.domain.track.model.toDomainTrack
+import eu.kanade.domain.track.service.TrackPreferences
+import eu.kanade.tachiyomi.data.database.models.Track
+import eu.kanade.tachiyomi.network.NetworkHelper
+import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
+import eu.kanade.tachiyomi.util.system.toast
+import logcat.LogPriority
+import okhttp3.OkHttpClient
+import tachiyomi.core.util.lang.withIOContext
+import tachiyomi.core.util.lang.withUIContext
+import tachiyomi.core.util.system.logcat
+import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
+import tachiyomi.domain.history.interactor.GetHistory
+import tachiyomi.domain.track.interactor.InsertTrack
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import uy.kohesive.injekt.injectLazy
+import java.time.ZoneOffset
+import tachiyomi.domain.track.model.Track as DomainTrack
+
+abstract class BaseTracker(
+    override val id: Long,
+    override val name: String,
+) : Tracker {
+
+    val trackPreferences: TrackPreferences by injectLazy()
+    val networkService: NetworkHelper by injectLazy()
+    private val insertTrack: InsertTrack by injectLazy()
+    private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack by injectLazy()
+
+    override val client: OkHttpClient
+        get() = networkService.client
+
+    // Application and remote support for reading dates
+    override val supportsReadingDates: Boolean = false
+
+    // TODO: Store all scores as 10 point in the future maybe?
+    override fun get10PointScore(track: DomainTrack): Double {
+        return track.score
+    }
+
+    override fun indexToScore(index: Int): Float {
+        return index.toFloat()
+    }
+
+    @CallSuper
+    override fun logout() {
+        trackPreferences.setCredentials(this, "", "")
+    }
+
+    override val isLoggedIn: Boolean
+        get() = getUsername().isNotEmpty() &&
+            getPassword().isNotEmpty()
+
+    override fun getUsername() = trackPreferences.trackUsername(this).get()
+
+    override fun getPassword() = trackPreferences.trackPassword(this).get()
+
+    override fun saveCredentials(username: String, password: String) {
+        trackPreferences.setCredentials(this, username, password)
+    }
+
+    // TODO: move this to an interactor, and update all trackers based on common data
+    override suspend fun register(item: Track, mangaId: Long) {
+        item.manga_id = mangaId
+        try {
+            withIOContext {
+                val allChapters = Injekt.get<GetChapterByMangaId>().await(mangaId)
+                val hasReadChapters = allChapters.any { it.read }
+                bind(item, hasReadChapters)
+
+                var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext
+
+                insertTrack.await(track)
+
+                // TODO: merge into [SyncChapterProgressWithTrack]?
+                // Update chapter progress if newer chapters marked read locally
+                if (hasReadChapters) {
+                    val latestLocalReadChapterNumber = allChapters
+                        .sortedBy { it.chapterNumber }
+                        .takeWhile { it.read }
+                        .lastOrNull()
+                        ?.chapterNumber ?: -1.0
+
+                    if (latestLocalReadChapterNumber > track.lastChapterRead) {
+                        track = track.copy(
+                            lastChapterRead = latestLocalReadChapterNumber,
+                        )
+                        setRemoteLastChapterRead(track.toDbTrack(), latestLocalReadChapterNumber.toInt())
+                    }
+
+                    if (track.startDate <= 0) {
+                        val firstReadChapterDate = Injekt.get<GetHistory>().await(mangaId)
+                            .sortedBy { it.readAt }
+                            .firstOrNull()
+                            ?.readAt
+
+                        firstReadChapterDate?.let {
+                            val startDate = firstReadChapterDate.time.convertEpochMillisZone(ZoneOffset.systemDefault(), ZoneOffset.UTC)
+                            track = track.copy(
+                                startDate = startDate,
+                            )
+                            setRemoteStartDate(track.toDbTrack(), startDate)
+                        }
+                    }
+                }
+
+                syncChapterProgressWithTrack.await(mangaId, track, this@BaseTracker)
+            }
+        } catch (e: Throwable) {
+            withUIContext { Injekt.get<Application>().toast(e.message) }
+        }
+    }
+
+    override suspend fun setRemoteStatus(track: Track, status: Int) {
+        track.status = status
+        if (track.status == getCompletionStatus() && track.total_chapters != 0) {
+            track.last_chapter_read = track.total_chapters.toFloat()
+        }
+        withIOContext { updateRemote(track) }
+    }
+
+    override suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int) {
+        if (track.last_chapter_read == 0f && track.last_chapter_read < chapterNumber && track.status != getRereadingStatus()) {
+            track.status = getReadingStatus()
+        }
+        track.last_chapter_read = chapterNumber.toFloat()
+        if (track.total_chapters != 0 && track.last_chapter_read.toInt() == track.total_chapters) {
+            track.status = getCompletionStatus()
+            track.finished_reading_date = System.currentTimeMillis()
+        }
+        withIOContext { updateRemote(track) }
+    }
+
+    override suspend fun setRemoteScore(track: Track, scoreString: String) {
+        track.score = indexToScore(getScoreList().indexOf(scoreString))
+        withIOContext { updateRemote(track) }
+    }
+
+    override suspend fun setRemoteStartDate(track: Track, epochMillis: Long) {
+        track.started_reading_date = epochMillis
+        withIOContext { updateRemote(track) }
+    }
+
+    override suspend fun setRemoteFinishDate(track: Track, epochMillis: Long) {
+        track.finished_reading_date = epochMillis
+        withIOContext { updateRemote(track) }
+    }
+
+    private suspend fun updateRemote(track: Track) {
+        withIOContext {
+            try {
+                update(track)
+                track.toDomainTrack(idRequired = false)?.let {
+                    insertTrack.await(it)
+                }
+            } catch (e: Exception) {
+                logcat(LogPriority.ERROR, e) { "Failed to update remote track data id=$id" }
+                withUIContext { Injekt.get<Application>().toast(e.message) }
+            }
+        }
+    }
+}

+ 43 - 163
app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt

@@ -1,201 +1,81 @@
 package eu.kanade.tachiyomi.data.track
 
-import android.app.Application
 import androidx.annotation.CallSuper
 import androidx.annotation.ColorInt
 import androidx.annotation.DrawableRes
 import androidx.annotation.StringRes
-import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
-import eu.kanade.domain.track.model.toDbTrack
-import eu.kanade.domain.track.model.toDomainTrack
-import eu.kanade.domain.track.service.TrackPreferences
 import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
-import eu.kanade.tachiyomi.network.NetworkHelper
-import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
-import eu.kanade.tachiyomi.util.system.toast
-import logcat.LogPriority
 import okhttp3.OkHttpClient
-import tachiyomi.core.util.lang.withIOContext
-import tachiyomi.core.util.lang.withUIContext
-import tachiyomi.core.util.system.logcat
-import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
-import tachiyomi.domain.history.interactor.GetHistory
-import tachiyomi.domain.track.interactor.InsertTrack
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-import uy.kohesive.injekt.injectLazy
-import java.time.ZoneOffset
-import tachiyomi.domain.track.model.Track as DomainTrack
-
-abstract class Tracker(val id: Long, val name: String) {
-
-    val trackPreferences: TrackPreferences by injectLazy()
-    val networkService: NetworkHelper by injectLazy()
-    private val insertTrack: InsertTrack by injectLazy()
-    private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack by injectLazy()
-
-    open val client: OkHttpClient
-        get() = networkService.client
 
-    // Application and remote support for reading dates
-    open val supportsReadingDates: Boolean = false
+interface Tracker {
 
-    @DrawableRes
-    abstract fun getLogo(): Int
+    val id: Long
+
+    val name: String
+
+    val client: OkHttpClient
+
+    // Application and remote support for reading dates
+    val supportsReadingDates: Boolean
 
     @ColorInt
-    abstract fun getLogoColor(): Int
+    fun getLogoColor(): Int
 
-    abstract fun getStatusList(): List<Int>
+    @DrawableRes
+    fun getLogo(): Int
+
+    fun getStatusList(): List<Int>
 
     @StringRes
-    abstract fun getStatus(status: Int): Int?
+    fun getStatus(status: Int): Int?
 
-    abstract fun getReadingStatus(): Int
+    fun getReadingStatus(): Int
 
-    abstract fun getRereadingStatus(): Int
+    fun getRereadingStatus(): Int
 
-    abstract fun getCompletionStatus(): Int
+    fun getCompletionStatus(): Int
 
-    abstract fun getScoreList(): List<String>
+    fun getScoreList(): List<String>
 
     // TODO: Store all scores as 10 point in the future maybe?
-    open fun get10PointScore(track: DomainTrack): Double {
-        return track.score
-    }
+    fun get10PointScore(track: tachiyomi.domain.track.model.Track): Double
 
-    open fun indexToScore(index: Int): Float {
-        return index.toFloat()
-    }
+    fun indexToScore(index: Int): Float
 
-    abstract fun displayScore(track: Track): String
+    fun displayScore(track: Track): String
 
-    abstract suspend fun update(track: Track, didReadChapter: Boolean = false): Track
+    suspend fun update(track: Track, didReadChapter: Boolean = false): Track
 
-    abstract suspend fun bind(track: Track, hasReadChapters: Boolean = false): Track
+    suspend fun bind(track: Track, hasReadChapters: Boolean = false): Track
 
-    abstract suspend fun search(query: String): List<TrackSearch>
+    suspend fun search(query: String): List<TrackSearch>
 
-    abstract suspend fun refresh(track: Track): Track
+    suspend fun refresh(track: Track): Track
 
-    abstract suspend fun login(username: String, password: String)
+    suspend fun login(username: String, password: String)
 
     @CallSuper
-    open fun logout() {
-        trackPreferences.setCredentials(this, "", "")
-    }
+    fun logout()
 
-    open val isLoggedIn: Boolean
-        get() = getUsername().isNotEmpty() &&
-            getPassword().isNotEmpty()
+    val isLoggedIn: Boolean
 
-    fun getUsername() = trackPreferences.trackUsername(this).get()
+    fun getUsername(): String
 
-    fun getPassword() = trackPreferences.trackPassword(this).get()
+    fun getPassword(): String
 
-    fun saveCredentials(username: String, password: String) {
-        trackPreferences.setCredentials(this, username, password)
-    }
+    fun saveCredentials(username: String, password: String)
 
     // TODO: move this to an interactor, and update all trackers based on common data
-    suspend fun register(item: Track, mangaId: Long) {
-        item.manga_id = mangaId
-        try {
-            withIOContext {
-                val allChapters = Injekt.get<GetChapterByMangaId>().await(mangaId)
-                val hasReadChapters = allChapters.any { it.read }
-                bind(item, hasReadChapters)
-
-                var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext
-
-                insertTrack.await(track)
-
-                // TODO: merge into [SyncChapterProgressWithTrack]?
-                // Update chapter progress if newer chapters marked read locally
-                if (hasReadChapters) {
-                    val latestLocalReadChapterNumber = allChapters
-                        .sortedBy { it.chapterNumber }
-                        .takeWhile { it.read }
-                        .lastOrNull()
-                        ?.chapterNumber ?: -1.0
-
-                    if (latestLocalReadChapterNumber > track.lastChapterRead) {
-                        track = track.copy(
-                            lastChapterRead = latestLocalReadChapterNumber,
-                        )
-                        setRemoteLastChapterRead(track.toDbTrack(), latestLocalReadChapterNumber.toInt())
-                    }
-
-                    if (track.startDate <= 0) {
-                        val firstReadChapterDate = Injekt.get<GetHistory>().await(mangaId)
-                            .sortedBy { it.readAt }
-                            .firstOrNull()
-                            ?.readAt
-
-                        firstReadChapterDate?.let {
-                            val startDate = firstReadChapterDate.time.convertEpochMillisZone(ZoneOffset.systemDefault(), ZoneOffset.UTC)
-                            track = track.copy(
-                                startDate = startDate,
-                            )
-                            setRemoteStartDate(track.toDbTrack(), startDate)
-                        }
-                    }
-                }
-
-                syncChapterProgressWithTrack.await(mangaId, track, this@Tracker)
-            }
-        } catch (e: Throwable) {
-            withUIContext { Injekt.get<Application>().toast(e.message) }
-        }
-    }
-
-    suspend fun setRemoteStatus(track: Track, status: Int) {
-        track.status = status
-        if (track.status == getCompletionStatus() && track.total_chapters != 0) {
-            track.last_chapter_read = track.total_chapters.toFloat()
-        }
-        withIOContext { updateRemote(track) }
-    }
-
-    suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int) {
-        if (track.last_chapter_read == 0f && track.last_chapter_read < chapterNumber && track.status != getRereadingStatus()) {
-            track.status = getReadingStatus()
-        }
-        track.last_chapter_read = chapterNumber.toFloat()
-        if (track.total_chapters != 0 && track.last_chapter_read.toInt() == track.total_chapters) {
-            track.status = getCompletionStatus()
-            track.finished_reading_date = System.currentTimeMillis()
-        }
-        withIOContext { updateRemote(track) }
-    }
-
-    suspend fun setRemoteScore(track: Track, scoreString: String) {
-        track.score = indexToScore(getScoreList().indexOf(scoreString))
-        withIOContext { updateRemote(track) }
-    }
-
-    suspend fun setRemoteStartDate(track: Track, epochMillis: Long) {
-        track.started_reading_date = epochMillis
-        withIOContext { updateRemote(track) }
-    }
-
-    suspend fun setRemoteFinishDate(track: Track, epochMillis: Long) {
-        track.finished_reading_date = epochMillis
-        withIOContext { updateRemote(track) }
-    }
-
-    private suspend fun updateRemote(track: Track) {
-        withIOContext {
-            try {
-                update(track)
-                track.toDomainTrack(idRequired = false)?.let {
-                    insertTrack.await(it)
-                }
-            } catch (e: Exception) {
-                logcat(LogPriority.ERROR, e) { "Failed to update remote track data id=$id" }
-                withUIContext { Injekt.get<Application>().toast(e.message) }
-            }
-        }
-    }
+    suspend fun register(item: Track, mangaId: Long)
+
+    suspend fun setRemoteStatus(track: Track, status: Int)
+
+    suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int)
+
+    suspend fun setRemoteScore(track: Track, scoreString: String)
+
+    suspend fun setRemoteStartDate(track: Track, epochMillis: Long)
+
+    suspend fun setRemoteFinishDate(track: Track, epochMillis: Long)
 }

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt

@@ -4,15 +4,15 @@ 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.BaseTracker
 import eu.kanade.tachiyomi.data.track.DeletableTracker
-import eu.kanade.tachiyomi.data.track.Tracker
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 import uy.kohesive.injekt.injectLazy
 import tachiyomi.domain.track.model.Track as DomainTrack
 
-class Anilist(id: Long) : Tracker(id, "AniList"), DeletableTracker {
+class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
 
     companion object {
         const val READING = 1

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt

@@ -4,13 +4,13 @@ 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.Tracker
+import eu.kanade.tachiyomi.data.track.BaseTracker
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 import uy.kohesive.injekt.injectLazy
 
-class Bangumi(id: Long) : Tracker(id, "Bangumi") {
+class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
 
     private val json: Json by injectLazy()
 

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt

@@ -4,8 +4,8 @@ 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.BaseTracker
 import eu.kanade.tachiyomi.data.track.EnhancedTracker
-import eu.kanade.tachiyomi.data.track.Tracker
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import eu.kanade.tachiyomi.source.ConfigurableSource
 import eu.kanade.tachiyomi.source.Source
@@ -16,7 +16,7 @@ import uy.kohesive.injekt.injectLazy
 import java.security.MessageDigest
 import tachiyomi.domain.track.model.Track as DomainTrack
 
-class Kavita(id: Long) : Tracker(id, "Kavita"), EnhancedTracker {
+class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
 
     companion object {
         const val UNREAD = 1

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt

@@ -4,15 +4,15 @@ 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.BaseTracker
 import eu.kanade.tachiyomi.data.track.DeletableTracker
-import eu.kanade.tachiyomi.data.track.Tracker
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 import uy.kohesive.injekt.injectLazy
 import java.text.DecimalFormat
 
-class Kitsu(id: Long) : Tracker(id, "Kitsu"), DeletableTracker {
+class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
 
     companion object {
         const val READING = 1

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt

@@ -4,8 +4,8 @@ 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.BaseTracker
 import eu.kanade.tachiyomi.data.track.EnhancedTracker
-import eu.kanade.tachiyomi.data.track.Tracker
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import eu.kanade.tachiyomi.source.Source
 import okhttp3.Dns
@@ -13,7 +13,7 @@ import okhttp3.OkHttpClient
 import tachiyomi.domain.manga.model.Manga
 import tachiyomi.domain.track.model.Track as DomainTrack
 
-class Komga(id: Long) : Tracker(id, "Komga"), EnhancedTracker {
+class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
 
     companion object {
         const val UNREAD = 1

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt

@@ -4,13 +4,13 @@ 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.BaseTracker
 import eu.kanade.tachiyomi.data.track.DeletableTracker
-import eu.kanade.tachiyomi.data.track.Tracker
 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(id: Long) : Tracker(id, "MangaUpdates"), DeletableTracker {
+class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker {
 
     companion object {
         const val READING_LIST = 0

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt

@@ -4,14 +4,14 @@ 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.BaseTracker
 import eu.kanade.tachiyomi.data.track.DeletableTracker
-import eu.kanade.tachiyomi.data.track.Tracker
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 import uy.kohesive.injekt.injectLazy
 
-class MyAnimeList(id: Long) : Tracker(id, "MyAnimeList"), DeletableTracker {
+class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
 
     companion object {
         const val READING = 1

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt

@@ -4,14 +4,14 @@ 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.BaseTracker
 import eu.kanade.tachiyomi.data.track.DeletableTracker
-import eu.kanade.tachiyomi.data.track.Tracker
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 import uy.kohesive.injekt.injectLazy
 
-class Shikimori(id: Long) : Tracker(id, "Shikimori"), DeletableTracker {
+class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
 
     companion object {
         const val READING = 1

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt

@@ -4,14 +4,14 @@ 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.BaseTracker
 import eu.kanade.tachiyomi.data.track.EnhancedTracker
-import eu.kanade.tachiyomi.data.track.Tracker
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import eu.kanade.tachiyomi.source.Source
 import tachiyomi.domain.manga.model.Manga as DomainManga
 import tachiyomi.domain.track.model.Track as DomainTrack
 
-class Suwayomi(id: Long) : Tracker(id, "Suwayomi"), EnhancedTracker {
+class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker {
 
     val api by lazy { SuwayomiApi(id) }