mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Tracker-related cleanup
This commit is contained in:
		@@ -1,7 +1,6 @@
 | 
			
		||||
package eu.kanade.domain
 | 
			
		||||
 | 
			
		||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
 | 
			
		||||
import eu.kanade.domain.chapter.interactor.SyncChapterProgressWithTrack
 | 
			
		||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
 | 
			
		||||
import eu.kanade.domain.download.interactor.DeleteDownload
 | 
			
		||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
 | 
			
		||||
@@ -16,7 +15,9 @@ import eu.kanade.domain.source.interactor.SetMigrateSorting
 | 
			
		||||
import eu.kanade.domain.source.interactor.ToggleLanguage
 | 
			
		||||
import eu.kanade.domain.source.interactor.ToggleSource
 | 
			
		||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
 | 
			
		||||
import eu.kanade.domain.track.interactor.AddTracks
 | 
			
		||||
import eu.kanade.domain.track.interactor.RefreshTracks
 | 
			
		||||
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
 | 
			
		||||
import eu.kanade.domain.track.interactor.TrackChapter
 | 
			
		||||
import tachiyomi.data.category.CategoryRepositoryImpl
 | 
			
		||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
 | 
			
		||||
@@ -114,11 +115,13 @@ class DomainModule : InjektModule {
 | 
			
		||||
 | 
			
		||||
        addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
 | 
			
		||||
        addFactory { TrackChapter(get(), get(), get(), get()) }
 | 
			
		||||
        addFactory { AddTracks(get(), get(), get()) }
 | 
			
		||||
        addFactory { RefreshTracks(get(), get(), get(), get()) }
 | 
			
		||||
        addFactory { DeleteTrack(get()) }
 | 
			
		||||
        addFactory { GetTracksPerManga(get()) }
 | 
			
		||||
        addFactory { GetTracks(get()) }
 | 
			
		||||
        addFactory { InsertTrack(get()) }
 | 
			
		||||
        addFactory { SyncChapterProgressWithTrack(get(), get(), get()) }
 | 
			
		||||
 | 
			
		||||
        addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
 | 
			
		||||
        addFactory { GetChapter(get()) }
 | 
			
		||||
@@ -127,7 +130,6 @@ class DomainModule : InjektModule {
 | 
			
		||||
        addFactory { SetReadStatus(get(), get(), get(), get()) }
 | 
			
		||||
        addFactory { ShouldUpdateDbChapter() }
 | 
			
		||||
        addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get()) }
 | 
			
		||||
        addFactory { SyncChapterProgressWithTrack(get(), get(), get()) }
 | 
			
		||||
 | 
			
		||||
        addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
 | 
			
		||||
        addFactory { GetHistory(get()) }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,47 @@
 | 
			
		||||
package eu.kanade.domain.track.interactor
 | 
			
		||||
 | 
			
		||||
import eu.kanade.domain.track.model.toDomainTrack
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.Tracker
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import logcat.LogPriority
 | 
			
		||||
import tachiyomi.core.util.lang.withNonCancellableContext
 | 
			
		||||
import tachiyomi.core.util.system.logcat
 | 
			
		||||
import tachiyomi.domain.manga.model.Manga
 | 
			
		||||
import tachiyomi.domain.track.interactor.GetTracks
 | 
			
		||||
import tachiyomi.domain.track.interactor.InsertTrack
 | 
			
		||||
 | 
			
		||||
class AddTracks(
 | 
			
		||||
    private val getTracks: GetTracks,
 | 
			
		||||
    private val insertTrack: InsertTrack,
 | 
			
		||||
    private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    suspend fun bindEnhancedTracks(manga: Manga, source: Source) {
 | 
			
		||||
        withNonCancellableContext {
 | 
			
		||||
            getTracks.await(manga.id)
 | 
			
		||||
                .filterIsInstance<EnhancedTracker>()
 | 
			
		||||
                .filter { it.accept(source) }
 | 
			
		||||
                .forEach { service ->
 | 
			
		||||
                    try {
 | 
			
		||||
                        service.match(manga)?.let { track ->
 | 
			
		||||
                            track.manga_id = manga.id
 | 
			
		||||
                            (service as Tracker).bind(track)
 | 
			
		||||
                            insertTrack.await(track.toDomainTrack()!!)
 | 
			
		||||
 | 
			
		||||
                            syncChapterProgressWithTrack.await(
 | 
			
		||||
                                manga.id,
 | 
			
		||||
                                track.toDomainTrack()!!,
 | 
			
		||||
                                service,
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                    } catch (e: Exception) {
 | 
			
		||||
                        logcat(
 | 
			
		||||
                            LogPriority.WARN,
 | 
			
		||||
                            e,
 | 
			
		||||
                        ) { "Could not match manga: ${manga.title} with service $service" }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
package eu.kanade.domain.track.interactor
 | 
			
		||||
 | 
			
		||||
import eu.kanade.domain.chapter.interactor.SyncChapterProgressWithTrack
 | 
			
		||||
import eu.kanade.domain.track.model.toDbTrack
 | 
			
		||||
import eu.kanade.domain.track.model.toDomainTrack
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.Tracker
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import kotlinx.coroutines.async
 | 
			
		||||
import kotlinx.coroutines.awaitAll
 | 
			
		||||
import kotlinx.coroutines.supervisorScope
 | 
			
		||||
@@ -13,7 +12,7 @@ import tachiyomi.domain.track.interactor.InsertTrack
 | 
			
		||||
 | 
			
		||||
class RefreshTracks(
 | 
			
		||||
    private val getTracks: GetTracks,
 | 
			
		||||
    private val trackManager: TrackManager,
 | 
			
		||||
    private val trackerManager: TrackerManager,
 | 
			
		||||
    private val insertTrack: InsertTrack,
 | 
			
		||||
    private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
 | 
			
		||||
) {
 | 
			
		||||
@@ -23,18 +22,17 @@ class RefreshTracks(
 | 
			
		||||
     *
 | 
			
		||||
     * @return Failed updates.
 | 
			
		||||
     */
 | 
			
		||||
    suspend fun await(mangaId: Long): List<Pair<TrackService?, Throwable>> {
 | 
			
		||||
    suspend fun await(mangaId: Long): List<Pair<Tracker?, Throwable>> {
 | 
			
		||||
        return supervisorScope {
 | 
			
		||||
            return@supervisorScope getTracks.await(mangaId)
 | 
			
		||||
                .map { track ->
 | 
			
		||||
                .map { it to trackerManager.get(it.syncId) }
 | 
			
		||||
                .filter { (_, service) -> service?.isLoggedIn == true }
 | 
			
		||||
                .map { (track, service) ->
 | 
			
		||||
                    async {
 | 
			
		||||
                        val service = trackManager.getService(track.syncId)
 | 
			
		||||
                        return@async try {
 | 
			
		||||
                            if (service?.isLoggedIn == true) {
 | 
			
		||||
                                val updatedTrack = service.refresh(track.toDbTrack())
 | 
			
		||||
                                insertTrack.await(updatedTrack.toDomainTrack()!!)
 | 
			
		||||
                                syncChapterProgressWithTrack.await(mangaId, track, service)
 | 
			
		||||
                            }
 | 
			
		||||
                            val updatedTrack = service!!.refresh(track.toDbTrack())
 | 
			
		||||
                            insertTrack.await(updatedTrack.toDomainTrack()!!)
 | 
			
		||||
                            syncChapterProgressWithTrack.await(mangaId, track, service)
 | 
			
		||||
                            null
 | 
			
		||||
                        } catch (e: Throwable) {
 | 
			
		||||
                            service to e
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
package eu.kanade.domain.chapter.interactor
 | 
			
		||||
package eu.kanade.domain.track.interactor
 | 
			
		||||
 | 
			
		||||
import eu.kanade.domain.track.model.toDbTrack
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.Tracker
 | 
			
		||||
import logcat.LogPriority
 | 
			
		||||
import tachiyomi.core.util.system.logcat
 | 
			
		||||
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
 | 
			
		||||
@@ -20,9 +20,9 @@ class SyncChapterProgressWithTrack(
 | 
			
		||||
    suspend fun await(
 | 
			
		||||
        mangaId: Long,
 | 
			
		||||
        remoteTrack: Track,
 | 
			
		||||
        service: TrackService,
 | 
			
		||||
        tracker: Tracker,
 | 
			
		||||
    ) {
 | 
			
		||||
        if (service !is EnhancedTrackService) {
 | 
			
		||||
        if (tracker !is EnhancedTracker) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -39,7 +39,7 @@ class SyncChapterProgressWithTrack(
 | 
			
		||||
        val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            service.update(updatedTrack.toDbTrack())
 | 
			
		||||
            tracker.update(updatedTrack.toDbTrack())
 | 
			
		||||
            updateChapter.awaitAll(chapterUpdates)
 | 
			
		||||
            insertTrack.await(updatedTrack)
 | 
			
		||||
        } catch (e: Throwable) {
 | 
			
		||||
@@ -4,30 +4,29 @@ import android.content.Context
 | 
			
		||||
import eu.kanade.domain.track.model.toDbTrack
 | 
			
		||||
import eu.kanade.domain.track.service.DelayedTrackingUpdateJob
 | 
			
		||||
import eu.kanade.domain.track.store.DelayedTrackingStore
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import kotlinx.coroutines.async
 | 
			
		||||
import kotlinx.coroutines.awaitAll
 | 
			
		||||
import kotlinx.coroutines.coroutineScope
 | 
			
		||||
import logcat.LogPriority
 | 
			
		||||
import tachiyomi.core.util.lang.launchNonCancellable
 | 
			
		||||
import tachiyomi.core.util.lang.withNonCancellableContext
 | 
			
		||||
import tachiyomi.core.util.system.logcat
 | 
			
		||||
import tachiyomi.domain.track.interactor.GetTracks
 | 
			
		||||
import tachiyomi.domain.track.interactor.InsertTrack
 | 
			
		||||
 | 
			
		||||
class TrackChapter(
 | 
			
		||||
    private val getTracks: GetTracks,
 | 
			
		||||
    private val trackManager: TrackManager,
 | 
			
		||||
    private val trackerManager: TrackerManager,
 | 
			
		||||
    private val insertTrack: InsertTrack,
 | 
			
		||||
    private val delayedTrackingStore: DelayedTrackingStore,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) = coroutineScope {
 | 
			
		||||
        launchNonCancellable {
 | 
			
		||||
    suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) {
 | 
			
		||||
        withNonCancellableContext {
 | 
			
		||||
            val tracks = getTracks.await(mangaId)
 | 
			
		||||
            if (tracks.isEmpty()) return@launchNonCancellable
 | 
			
		||||
            if (tracks.isEmpty()) return@withNonCancellableContext
 | 
			
		||||
 | 
			
		||||
            tracks.mapNotNull { track ->
 | 
			
		||||
                val service = trackManager.getService(track.syncId)
 | 
			
		||||
                val service = trackerManager.get(track.syncId)
 | 
			
		||||
                if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) {
 | 
			
		||||
                    return@mapNotNull null
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import androidx.work.OneTimeWorkRequestBuilder
 | 
			
		||||
import androidx.work.WorkerParameters
 | 
			
		||||
import eu.kanade.domain.track.model.toDbTrack
 | 
			
		||||
import eu.kanade.domain.track.store.DelayedTrackingStore
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.workManager
 | 
			
		||||
import logcat.LogPriority
 | 
			
		||||
import tachiyomi.core.util.lang.withIOContext
 | 
			
		||||
@@ -33,7 +33,7 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
 | 
			
		||||
        val getTracks = Injekt.get<GetTracks>()
 | 
			
		||||
        val insertTrack = Injekt.get<InsertTrack>()
 | 
			
		||||
 | 
			
		||||
        val trackManager = Injekt.get<TrackManager>()
 | 
			
		||||
        val trackerManager = Injekt.get<TrackerManager>()
 | 
			
		||||
        val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
 | 
			
		||||
 | 
			
		||||
        withIOContext {
 | 
			
		||||
@@ -47,7 +47,7 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
 | 
			
		||||
                }
 | 
			
		||||
                .forEach { track ->
 | 
			
		||||
                    try {
 | 
			
		||||
                        val service = trackManager.getService(track.syncId)
 | 
			
		||||
                        val service = trackerManager.get(track.syncId)
 | 
			
		||||
                        if (service != null && service.isLoggedIn) {
 | 
			
		||||
                            logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" }
 | 
			
		||||
                            service.update(track.toDbTrack(), true)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package eu.kanade.domain.track.service
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.Tracker
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
 | 
			
		||||
import tachiyomi.core.preference.PreferenceStore
 | 
			
		||||
 | 
			
		||||
@@ -8,16 +8,16 @@ class TrackPreferences(
 | 
			
		||||
    private val preferenceStore: PreferenceStore,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    fun trackUsername(sync: TrackService) = preferenceStore.getString(trackUsername(sync.id), "")
 | 
			
		||||
    fun trackUsername(sync: Tracker) = preferenceStore.getString(trackUsername(sync.id), "")
 | 
			
		||||
 | 
			
		||||
    fun trackPassword(sync: TrackService) = preferenceStore.getString(trackPassword(sync.id), "")
 | 
			
		||||
    fun trackPassword(sync: Tracker) = preferenceStore.getString(trackPassword(sync.id), "")
 | 
			
		||||
 | 
			
		||||
    fun setTrackCredentials(sync: TrackService, username: String, password: String) {
 | 
			
		||||
    fun setCredentials(sync: Tracker, username: String, password: String) {
 | 
			
		||||
        trackUsername(sync).set(username)
 | 
			
		||||
        trackPassword(sync).set(password)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun trackToken(sync: TrackService) = preferenceStore.getString(trackToken(sync.id), "")
 | 
			
		||||
    fun trackToken(sync: Tracker) = preferenceStore.getString(trackToken(sync.id), "")
 | 
			
		||||
 | 
			
		||||
    fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -108,13 +108,13 @@ private fun ColumnScope.FilterPage(
 | 
			
		||||
        onClick = { screenModel.toggleFilter(LibraryPreferences::filterCompleted) },
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    val trackServices = remember { screenModel.trackServices }
 | 
			
		||||
    when (trackServices.size) {
 | 
			
		||||
    val trackers = remember { screenModel.trackers }
 | 
			
		||||
    when (trackers.size) {
 | 
			
		||||
        0 -> {
 | 
			
		||||
            // No trackers
 | 
			
		||||
        }
 | 
			
		||||
        1 -> {
 | 
			
		||||
            val service = trackServices[0]
 | 
			
		||||
            val service = trackers[0]
 | 
			
		||||
            val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
 | 
			
		||||
            TriStateItem(
 | 
			
		||||
                label = stringResource(R.string.action_filter_tracked),
 | 
			
		||||
@@ -124,7 +124,7 @@ private fun ColumnScope.FilterPage(
 | 
			
		||||
        }
 | 
			
		||||
        else -> {
 | 
			
		||||
            HeadingItem(R.string.action_filter_tracked)
 | 
			
		||||
            trackServices.map { service ->
 | 
			
		||||
            trackers.map { service ->
 | 
			
		||||
                val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
 | 
			
		||||
                TriStateItem(
 | 
			
		||||
                    label = service.name,
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.ui.graphics.vector.ImageVector
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.Tracker
 | 
			
		||||
import tachiyomi.core.preference.Preference as PreferenceData
 | 
			
		||||
 | 
			
		||||
sealed class Preference {
 | 
			
		||||
@@ -132,10 +132,10 @@ sealed class Preference {
 | 
			
		||||
        ) : PreferenceItem<String>()
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * A [PreferenceItem] for individual tracking service.
 | 
			
		||||
         * A [PreferenceItem] for individual tracker.
 | 
			
		||||
         */
 | 
			
		||||
        data class TrackingPreference(
 | 
			
		||||
            val service: TrackService,
 | 
			
		||||
        data class TrackerPreference(
 | 
			
		||||
            val tracker: Tracker,
 | 
			
		||||
            override val title: String,
 | 
			
		||||
            val login: () -> Unit,
 | 
			
		||||
            val logout: () -> Unit,
 | 
			
		||||
 
 | 
			
		||||
@@ -156,13 +156,13 @@ internal fun PreferenceItem(
 | 
			
		||||
                    },
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            is Preference.PreferenceItem.TrackingPreference -> {
 | 
			
		||||
            is Preference.PreferenceItem.TrackerPreference -> {
 | 
			
		||||
                val uName by Injekt.get<PreferenceStore>()
 | 
			
		||||
                    .getString(TrackPreferences.trackUsername(item.service.id))
 | 
			
		||||
                    .getString(TrackPreferences.trackUsername(item.tracker.id))
 | 
			
		||||
                    .collectAsState()
 | 
			
		||||
                item.service.run {
 | 
			
		||||
                item.tracker.run {
 | 
			
		||||
                    TrackingPreferenceWidget(
 | 
			
		||||
                        service = this,
 | 
			
		||||
                        tracker = this,
 | 
			
		||||
                        checked = uName.isNotEmpty(),
 | 
			
		||||
                        onClick = { if (isLoggedIn) item.logout() else item.login() },
 | 
			
		||||
                    )
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.DownloadCache
 | 
			
		||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.network.NetworkHelper
 | 
			
		||||
import eu.kanade.tachiyomi.network.NetworkPreferences
 | 
			
		||||
import eu.kanade.tachiyomi.network.PREF_DOH_360
 | 
			
		||||
@@ -328,7 +328,7 @@ object SettingsAdvancedScreen : SearchableSettings {
 | 
			
		||||
    private fun getLibraryGroup(): Preference.PreferenceGroup {
 | 
			
		||||
        val scope = rememberCoroutineScope()
 | 
			
		||||
        val context = LocalContext.current
 | 
			
		||||
        val trackManager = remember { Injekt.get<TrackManager>() }
 | 
			
		||||
        val trackerManager = remember { Injekt.get<TrackerManager>() }
 | 
			
		||||
 | 
			
		||||
        return Preference.PreferenceGroup(
 | 
			
		||||
            title = stringResource(R.string.label_library),
 | 
			
		||||
@@ -340,7 +340,7 @@ object SettingsAdvancedScreen : SearchableSettings {
 | 
			
		||||
                Preference.PreferenceItem.TextPreference(
 | 
			
		||||
                    title = stringResource(R.string.pref_refresh_library_tracking),
 | 
			
		||||
                    subtitle = stringResource(R.string.pref_refresh_library_tracking_summary),
 | 
			
		||||
                    enabled = trackManager.hasLoggedServices(),
 | 
			
		||||
                    enabled = trackerManager.hasLoggedIn(),
 | 
			
		||||
                    onClick = { LibraryUpdateJob.startNow(context, target = LibraryUpdateJob.Target.TRACKING) },
 | 
			
		||||
                ),
 | 
			
		||||
                Preference.PreferenceItem.TextPreference(
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import eu.kanade.presentation.more.settings.Preference
 | 
			
		||||
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlinx.coroutines.runBlocking
 | 
			
		||||
@@ -199,7 +199,7 @@ object SettingsLibraryScreen : SearchableSettings {
 | 
			
		||||
                ),
 | 
			
		||||
                Preference.PreferenceItem.SwitchPreference(
 | 
			
		||||
                    pref = libraryPreferences.autoUpdateTrackers(),
 | 
			
		||||
                    enabled = Injekt.get<TrackManager>().hasLoggedServices(),
 | 
			
		||||
                    enabled = Injekt.get<TrackerManager>().hasLoggedIn(),
 | 
			
		||||
                    title = stringResource(R.string.pref_library_update_refresh_trackers),
 | 
			
		||||
                    subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary),
 | 
			
		||||
                ),
 | 
			
		||||
 
 | 
			
		||||
@@ -44,9 +44,9 @@ import androidx.compose.ui.unit.dp
 | 
			
		||||
import eu.kanade.domain.track.service.TrackPreferences
 | 
			
		||||
import eu.kanade.presentation.more.settings.Preference
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.Tracker
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi
 | 
			
		||||
@@ -82,7 +82,7 @@ object SettingsTrackingScreen : SearchableSettings {
 | 
			
		||||
    override fun getPreferences(): List<Preference> {
 | 
			
		||||
        val context = LocalContext.current
 | 
			
		||||
        val trackPreferences = remember { Injekt.get<TrackPreferences>() }
 | 
			
		||||
        val trackManager = remember { Injekt.get<TrackManager>() }
 | 
			
		||||
        val trackerManager = remember { Injekt.get<TrackerManager>() }
 | 
			
		||||
        val sourceManager = remember { Injekt.get<SourceManager>() }
 | 
			
		||||
 | 
			
		||||
        var dialog by remember { mutableStateOf<Any?>(null) }
 | 
			
		||||
@@ -90,24 +90,24 @@ object SettingsTrackingScreen : SearchableSettings {
 | 
			
		||||
            when (this) {
 | 
			
		||||
                is LoginDialog -> {
 | 
			
		||||
                    TrackingLoginDialog(
 | 
			
		||||
                        service = service,
 | 
			
		||||
                        tracker = tracker,
 | 
			
		||||
                        uNameStringRes = uNameStringRes,
 | 
			
		||||
                        onDismissRequest = { dialog = null },
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                is LogoutDialog -> {
 | 
			
		||||
                    TrackingLogoutDialog(
 | 
			
		||||
                        service = service,
 | 
			
		||||
                        tracker = tracker,
 | 
			
		||||
                        onDismissRequest = { dialog = null },
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val enhancedTrackers = trackManager.services
 | 
			
		||||
            .filter { it is EnhancedTrackService }
 | 
			
		||||
        val enhancedTrackers = trackerManager.trackers
 | 
			
		||||
            .filter { it is EnhancedTracker }
 | 
			
		||||
            .partition { service ->
 | 
			
		||||
                val acceptedSources = (service as EnhancedTrackService).getAcceptedSources()
 | 
			
		||||
                val acceptedSources = (service as EnhancedTracker).getAcceptedSources()
 | 
			
		||||
                sourceManager.getCatalogueSources().any { it::class.qualifiedName in acceptedSources }
 | 
			
		||||
            }
 | 
			
		||||
        var enhancedTrackerInfo = stringResource(R.string.enhanced_tracking_info)
 | 
			
		||||
@@ -127,41 +127,41 @@ object SettingsTrackingScreen : SearchableSettings {
 | 
			
		||||
            Preference.PreferenceGroup(
 | 
			
		||||
                title = stringResource(R.string.services),
 | 
			
		||||
                preferenceItems = listOf(
 | 
			
		||||
                    Preference.PreferenceItem.TrackingPreference(
 | 
			
		||||
                        title = trackManager.myAnimeList.name,
 | 
			
		||||
                        service = trackManager.myAnimeList,
 | 
			
		||||
                    Preference.PreferenceItem.TrackerPreference(
 | 
			
		||||
                        title = trackerManager.myAnimeList.name,
 | 
			
		||||
                        tracker = trackerManager.myAnimeList,
 | 
			
		||||
                        login = { context.openInBrowser(MyAnimeListApi.authUrl(), forceDefaultBrowser = true) },
 | 
			
		||||
                        logout = { dialog = LogoutDialog(trackManager.myAnimeList) },
 | 
			
		||||
                        logout = { dialog = LogoutDialog(trackerManager.myAnimeList) },
 | 
			
		||||
                    ),
 | 
			
		||||
                    Preference.PreferenceItem.TrackingPreference(
 | 
			
		||||
                        title = trackManager.aniList.name,
 | 
			
		||||
                        service = trackManager.aniList,
 | 
			
		||||
                    Preference.PreferenceItem.TrackerPreference(
 | 
			
		||||
                        title = trackerManager.aniList.name,
 | 
			
		||||
                        tracker = trackerManager.aniList,
 | 
			
		||||
                        login = { context.openInBrowser(AnilistApi.authUrl(), forceDefaultBrowser = true) },
 | 
			
		||||
                        logout = { dialog = LogoutDialog(trackManager.aniList) },
 | 
			
		||||
                        logout = { dialog = LogoutDialog(trackerManager.aniList) },
 | 
			
		||||
                    ),
 | 
			
		||||
                    Preference.PreferenceItem.TrackingPreference(
 | 
			
		||||
                        title = trackManager.kitsu.name,
 | 
			
		||||
                        service = trackManager.kitsu,
 | 
			
		||||
                        login = { dialog = LoginDialog(trackManager.kitsu, R.string.email) },
 | 
			
		||||
                        logout = { dialog = LogoutDialog(trackManager.kitsu) },
 | 
			
		||||
                    Preference.PreferenceItem.TrackerPreference(
 | 
			
		||||
                        title = trackerManager.kitsu.name,
 | 
			
		||||
                        tracker = trackerManager.kitsu,
 | 
			
		||||
                        login = { dialog = LoginDialog(trackerManager.kitsu, R.string.email) },
 | 
			
		||||
                        logout = { dialog = LogoutDialog(trackerManager.kitsu) },
 | 
			
		||||
                    ),
 | 
			
		||||
                    Preference.PreferenceItem.TrackingPreference(
 | 
			
		||||
                        title = trackManager.mangaUpdates.name,
 | 
			
		||||
                        service = trackManager.mangaUpdates,
 | 
			
		||||
                        login = { dialog = LoginDialog(trackManager.mangaUpdates, R.string.username) },
 | 
			
		||||
                        logout = { dialog = LogoutDialog(trackManager.mangaUpdates) },
 | 
			
		||||
                    Preference.PreferenceItem.TrackerPreference(
 | 
			
		||||
                        title = trackerManager.mangaUpdates.name,
 | 
			
		||||
                        tracker = trackerManager.mangaUpdates,
 | 
			
		||||
                        login = { dialog = LoginDialog(trackerManager.mangaUpdates, R.string.username) },
 | 
			
		||||
                        logout = { dialog = LogoutDialog(trackerManager.mangaUpdates) },
 | 
			
		||||
                    ),
 | 
			
		||||
                    Preference.PreferenceItem.TrackingPreference(
 | 
			
		||||
                        title = trackManager.shikimori.name,
 | 
			
		||||
                        service = trackManager.shikimori,
 | 
			
		||||
                    Preference.PreferenceItem.TrackerPreference(
 | 
			
		||||
                        title = trackerManager.shikimori.name,
 | 
			
		||||
                        tracker = trackerManager.shikimori,
 | 
			
		||||
                        login = { context.openInBrowser(ShikimoriApi.authUrl(), forceDefaultBrowser = true) },
 | 
			
		||||
                        logout = { dialog = LogoutDialog(trackManager.shikimori) },
 | 
			
		||||
                        logout = { dialog = LogoutDialog(trackerManager.shikimori) },
 | 
			
		||||
                    ),
 | 
			
		||||
                    Preference.PreferenceItem.TrackingPreference(
 | 
			
		||||
                        title = trackManager.bangumi.name,
 | 
			
		||||
                        service = trackManager.bangumi,
 | 
			
		||||
                    Preference.PreferenceItem.TrackerPreference(
 | 
			
		||||
                        title = trackerManager.bangumi.name,
 | 
			
		||||
                        tracker = trackerManager.bangumi,
 | 
			
		||||
                        login = { context.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true) },
 | 
			
		||||
                        logout = { dialog = LogoutDialog(trackManager.bangumi) },
 | 
			
		||||
                        logout = { dialog = LogoutDialog(trackerManager.bangumi) },
 | 
			
		||||
                    ),
 | 
			
		||||
                    Preference.PreferenceItem.InfoPreference(stringResource(R.string.tracking_info)),
 | 
			
		||||
                ),
 | 
			
		||||
@@ -170,10 +170,10 @@ object SettingsTrackingScreen : SearchableSettings {
 | 
			
		||||
                title = stringResource(R.string.enhanced_services),
 | 
			
		||||
                preferenceItems = enhancedTrackers.first
 | 
			
		||||
                    .map { service ->
 | 
			
		||||
                        Preference.PreferenceItem.TrackingPreference(
 | 
			
		||||
                        Preference.PreferenceItem.TrackerPreference(
 | 
			
		||||
                            title = service.name,
 | 
			
		||||
                            service = service,
 | 
			
		||||
                            login = { (service as EnhancedTrackService).loginNoop() },
 | 
			
		||||
                            tracker = service,
 | 
			
		||||
                            login = { (service as EnhancedTracker).loginNoop() },
 | 
			
		||||
                            logout = service::logout,
 | 
			
		||||
                        )
 | 
			
		||||
                    } + listOf(Preference.PreferenceItem.InfoPreference(enhancedTrackerInfo)),
 | 
			
		||||
@@ -183,15 +183,15 @@ object SettingsTrackingScreen : SearchableSettings {
 | 
			
		||||
 | 
			
		||||
    @Composable
 | 
			
		||||
    private fun TrackingLoginDialog(
 | 
			
		||||
        service: TrackService,
 | 
			
		||||
        tracker: Tracker,
 | 
			
		||||
        @StringRes uNameStringRes: Int,
 | 
			
		||||
        onDismissRequest: () -> Unit,
 | 
			
		||||
    ) {
 | 
			
		||||
        val context = LocalContext.current
 | 
			
		||||
        val scope = rememberCoroutineScope()
 | 
			
		||||
 | 
			
		||||
        var username by remember { mutableStateOf(TextFieldValue(service.getUsername())) }
 | 
			
		||||
        var password by remember { mutableStateOf(TextFieldValue(service.getPassword())) }
 | 
			
		||||
        var username by remember { mutableStateOf(TextFieldValue(tracker.getUsername())) }
 | 
			
		||||
        var password by remember { mutableStateOf(TextFieldValue(tracker.getPassword())) }
 | 
			
		||||
        var processing by remember { mutableStateOf(false) }
 | 
			
		||||
        var inputError by remember { mutableStateOf(false) }
 | 
			
		||||
 | 
			
		||||
@@ -200,7 +200,7 @@ object SettingsTrackingScreen : SearchableSettings {
 | 
			
		||||
            title = {
 | 
			
		||||
                Row(verticalAlignment = Alignment.CenterVertically) {
 | 
			
		||||
                    Text(
 | 
			
		||||
                        text = stringResource(R.string.login_title, service.name),
 | 
			
		||||
                        text = stringResource(R.string.login_title, tracker.name),
 | 
			
		||||
                        modifier = Modifier.weight(1f),
 | 
			
		||||
                    )
 | 
			
		||||
                    IconButton(onClick = onDismissRequest) {
 | 
			
		||||
@@ -264,7 +264,7 @@ object SettingsTrackingScreen : SearchableSettings {
 | 
			
		||||
                            processing = true
 | 
			
		||||
                            val result = checkLogin(
 | 
			
		||||
                                context = context,
 | 
			
		||||
                                service = service,
 | 
			
		||||
                                tracker = tracker,
 | 
			
		||||
                                username = username.text,
 | 
			
		||||
                                password = password.text,
 | 
			
		||||
                            )
 | 
			
		||||
@@ -283,16 +283,16 @@ object SettingsTrackingScreen : SearchableSettings {
 | 
			
		||||
 | 
			
		||||
    private suspend fun checkLogin(
 | 
			
		||||
        context: Context,
 | 
			
		||||
        service: TrackService,
 | 
			
		||||
        tracker: Tracker,
 | 
			
		||||
        username: String,
 | 
			
		||||
        password: String,
 | 
			
		||||
    ): Boolean {
 | 
			
		||||
        return try {
 | 
			
		||||
            service.login(username, password)
 | 
			
		||||
            tracker.login(username, password)
 | 
			
		||||
            withUIContext { context.toast(R.string.login_success) }
 | 
			
		||||
            true
 | 
			
		||||
        } catch (e: Throwable) {
 | 
			
		||||
            service.logout()
 | 
			
		||||
            tracker.logout()
 | 
			
		||||
            withUIContext { context.toast(e.message.toString()) }
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
@@ -300,7 +300,7 @@ object SettingsTrackingScreen : SearchableSettings {
 | 
			
		||||
 | 
			
		||||
    @Composable
 | 
			
		||||
    private fun TrackingLogoutDialog(
 | 
			
		||||
        service: TrackService,
 | 
			
		||||
        tracker: Tracker,
 | 
			
		||||
        onDismissRequest: () -> Unit,
 | 
			
		||||
    ) {
 | 
			
		||||
        val context = LocalContext.current
 | 
			
		||||
@@ -308,7 +308,7 @@ object SettingsTrackingScreen : SearchableSettings {
 | 
			
		||||
            onDismissRequest = onDismissRequest,
 | 
			
		||||
            title = {
 | 
			
		||||
                Text(
 | 
			
		||||
                    text = stringResource(R.string.logout_title, service.name),
 | 
			
		||||
                    text = stringResource(R.string.logout_title, tracker.name),
 | 
			
		||||
                    textAlign = TextAlign.Center,
 | 
			
		||||
                    modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
                )
 | 
			
		||||
@@ -324,7 +324,7 @@ object SettingsTrackingScreen : SearchableSettings {
 | 
			
		||||
                    Button(
 | 
			
		||||
                        modifier = Modifier.weight(1f),
 | 
			
		||||
                        onClick = {
 | 
			
		||||
                            service.logout()
 | 
			
		||||
                            tracker.logout()
 | 
			
		||||
                            onDismissRequest()
 | 
			
		||||
                            context.toast(R.string.logout_success)
 | 
			
		||||
                        },
 | 
			
		||||
@@ -342,10 +342,10 @@ object SettingsTrackingScreen : SearchableSettings {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private data class LoginDialog(
 | 
			
		||||
    val service: TrackService,
 | 
			
		||||
    val tracker: Tracker,
 | 
			
		||||
    @StringRes val uNameStringRes: Int,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
private data class LogoutDialog(
 | 
			
		||||
    val service: TrackService,
 | 
			
		||||
    val tracker: Tracker,
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -20,12 +20,12 @@ import androidx.compose.ui.unit.dp
 | 
			
		||||
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
 | 
			
		||||
import eu.kanade.presentation.track.components.TrackLogoIcon
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.Tracker
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun TrackingPreferenceWidget(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    service: TrackService,
 | 
			
		||||
    tracker: Tracker,
 | 
			
		||||
    checked: Boolean,
 | 
			
		||||
    onClick: (() -> Unit)? = null,
 | 
			
		||||
) {
 | 
			
		||||
@@ -38,9 +38,9 @@ fun TrackingPreferenceWidget(
 | 
			
		||||
                .padding(horizontal = PrefsHorizontalPadding, vertical = 8.dp),
 | 
			
		||||
            verticalAlignment = Alignment.CenterVertically,
 | 
			
		||||
        ) {
 | 
			
		||||
            TrackLogoIcon(service)
 | 
			
		||||
            TrackLogoIcon(tracker)
 | 
			
		||||
            Text(
 | 
			
		||||
                text = service.name,
 | 
			
		||||
                text = tracker.name,
 | 
			
		||||
                modifier = Modifier
 | 
			
		||||
                    .weight(1f)
 | 
			
		||||
                    .padding(horizontal = 16.dp),
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@ import eu.kanade.domain.track.model.toDbTrack
 | 
			
		||||
import eu.kanade.presentation.components.DropdownMenu
 | 
			
		||||
import eu.kanade.presentation.track.components.TrackLogoIcon
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.Tracker
 | 
			
		||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
 | 
			
		||||
import java.text.DateFormat
 | 
			
		||||
@@ -80,12 +80,12 @@ fun TrackInfoDialogHome(
 | 
			
		||||
    ) {
 | 
			
		||||
        trackItems.forEach { item ->
 | 
			
		||||
            if (item.track != null) {
 | 
			
		||||
                val supportsScoring = item.service.getScoreList().isNotEmpty()
 | 
			
		||||
                val supportsReadingDates = item.service.supportsReadingDates
 | 
			
		||||
                val supportsScoring = item.tracker.getScoreList().isNotEmpty()
 | 
			
		||||
                val supportsReadingDates = item.tracker.supportsReadingDates
 | 
			
		||||
                TrackInfoItem(
 | 
			
		||||
                    title = item.track.title,
 | 
			
		||||
                    service = item.service,
 | 
			
		||||
                    status = item.service.getStatus(item.track.status.toInt()),
 | 
			
		||||
                    tracker = item.tracker,
 | 
			
		||||
                    status = item.tracker.getStatus(item.track.status.toInt()),
 | 
			
		||||
                    onStatusClick = { onStatusClick(item) },
 | 
			
		||||
                    chapters = "${item.track.lastChapterRead.toInt()}".let {
 | 
			
		||||
                        val totalChapters = item.track.totalChapters
 | 
			
		||||
@@ -97,7 +97,7 @@ fun TrackInfoDialogHome(
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    onChaptersClick = { onChapterClick(item) },
 | 
			
		||||
                    score = item.service.displayScore(item.track.toDbTrack())
 | 
			
		||||
                    score = item.tracker.displayScore(item.track.toDbTrack())
 | 
			
		||||
                        .takeIf { supportsScoring && item.track.score != 0.0 },
 | 
			
		||||
                    onScoreClick = { onScoreClick(item) }
 | 
			
		||||
                        .takeIf { supportsScoring },
 | 
			
		||||
@@ -115,7 +115,7 @@ fun TrackInfoDialogHome(
 | 
			
		||||
                )
 | 
			
		||||
            } else {
 | 
			
		||||
                TrackInfoItemEmpty(
 | 
			
		||||
                    service = item.service,
 | 
			
		||||
                    tracker = item.tracker,
 | 
			
		||||
                    onNewSearch = { onNewSearch(item) },
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
@@ -126,7 +126,7 @@ fun TrackInfoDialogHome(
 | 
			
		||||
@Composable
 | 
			
		||||
private fun TrackInfoItem(
 | 
			
		||||
    title: String,
 | 
			
		||||
    service: TrackService,
 | 
			
		||||
    tracker: Tracker,
 | 
			
		||||
    @StringRes status: Int?,
 | 
			
		||||
    onStatusClick: () -> Unit,
 | 
			
		||||
    chapters: String,
 | 
			
		||||
@@ -147,7 +147,7 @@ private fun TrackInfoItem(
 | 
			
		||||
            verticalAlignment = Alignment.CenterVertically,
 | 
			
		||||
        ) {
 | 
			
		||||
            TrackLogoIcon(
 | 
			
		||||
                service = service,
 | 
			
		||||
                tracker = tracker,
 | 
			
		||||
                onClick = onOpenInBrowser,
 | 
			
		||||
            )
 | 
			
		||||
            Box(
 | 
			
		||||
@@ -260,13 +260,13 @@ private fun TrackDetailsItem(
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun TrackInfoItemEmpty(
 | 
			
		||||
    service: TrackService,
 | 
			
		||||
    tracker: Tracker,
 | 
			
		||||
    onNewSearch: () -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    Row(
 | 
			
		||||
        verticalAlignment = Alignment.CenterVertically,
 | 
			
		||||
    ) {
 | 
			
		||||
        TrackLogoIcon(service)
 | 
			
		||||
        TrackLogoIcon(tracker)
 | 
			
		||||
        TextButton(
 | 
			
		||||
            onClick = onNewSearch,
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,7 @@ import tachiyomi.presentation.core.util.runOnEnterKeyPressed
 | 
			
		||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun TrackServiceSearch(
 | 
			
		||||
fun TrackerSearch(
 | 
			
		||||
    query: TextFieldValue,
 | 
			
		||||
    onQueryChange: (TextFieldValue) -> Unit,
 | 
			
		||||
    onDispatchQuery: () -> Unit,
 | 
			
		||||
@@ -12,12 +12,12 @@ import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.res.painterResource
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.Tracker
 | 
			
		||||
import tachiyomi.presentation.core.util.clickableNoIndication
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun TrackLogoIcon(
 | 
			
		||||
    service: TrackService,
 | 
			
		||||
    tracker: Tracker,
 | 
			
		||||
    onClick: (() -> Unit)? = null,
 | 
			
		||||
) {
 | 
			
		||||
    val modifier = if (onClick != null) {
 | 
			
		||||
@@ -29,13 +29,13 @@ fun TrackLogoIcon(
 | 
			
		||||
    Box(
 | 
			
		||||
        modifier = modifier
 | 
			
		||||
            .size(48.dp)
 | 
			
		||||
            .background(color = Color(service.getLogoColor()), shape = MaterialTheme.shapes.medium)
 | 
			
		||||
            .background(color = Color(tracker.getLogoColor()), shape = MaterialTheme.shapes.medium)
 | 
			
		||||
            .padding(4.dp),
 | 
			
		||||
        contentAlignment = Alignment.Center,
 | 
			
		||||
    ) {
 | 
			
		||||
        Image(
 | 
			
		||||
            painter = painterResource(service.getLogo()),
 | 
			
		||||
            contentDescription = service.name,
 | 
			
		||||
            painter = painterResource(tracker.getLogo()),
 | 
			
		||||
            contentDescription = tracker.name,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ import eu.kanade.tachiyomi.data.download.DownloadCache
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.DownloadManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.DownloadProvider
 | 
			
		||||
import eu.kanade.tachiyomi.data.saver.ImageSaver
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.extension.ExtensionManager
 | 
			
		||||
import eu.kanade.tachiyomi.network.JavaScriptEngine
 | 
			
		||||
import eu.kanade.tachiyomi.network.NetworkHelper
 | 
			
		||||
@@ -132,7 +132,7 @@ class AppModule(val app: Application) : InjektModule {
 | 
			
		||||
        addSingletonFactory { DownloadManager(app) }
 | 
			
		||||
        addSingletonFactory { DownloadCache(app) }
 | 
			
		||||
 | 
			
		||||
        addSingletonFactory { TrackManager(app) }
 | 
			
		||||
        addSingletonFactory { TrackerManager() }
 | 
			
		||||
        addSingletonFactory { DelayedTrackingStore(app) }
 | 
			
		||||
 | 
			
		||||
        addSingletonFactory { ImageSaver(app) }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import eu.kanade.domain.ui.UiPreferences
 | 
			
		||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
 | 
			
		||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.network.NetworkPreferences
 | 
			
		||||
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
 | 
			
		||||
@@ -47,7 +47,7 @@ object Migrations {
 | 
			
		||||
        libraryPreferences: LibraryPreferences,
 | 
			
		||||
        readerPreferences: ReaderPreferences,
 | 
			
		||||
        backupPreferences: BackupPreferences,
 | 
			
		||||
        trackManager: TrackManager,
 | 
			
		||||
        trackerManager: TrackerManager,
 | 
			
		||||
    ): Boolean {
 | 
			
		||||
        val lastVersionCode = preferenceStore.getInt("last_version_code", 0)
 | 
			
		||||
        val oldVersion = lastVersionCode.get()
 | 
			
		||||
@@ -135,8 +135,8 @@ object Migrations {
 | 
			
		||||
                // Force MAL log out due to login flow change
 | 
			
		||||
                // v52: switched from scraping to WebView
 | 
			
		||||
                // v53: switched from WebView to OAuth
 | 
			
		||||
                if (trackManager.myAnimeList.isLoggedIn) {
 | 
			
		||||
                    trackManager.myAnimeList.logout()
 | 
			
		||||
                if (trackerManager.myAnimeList.isLoggedIn) {
 | 
			
		||||
                    trackerManager.myAnimeList.logout()
 | 
			
		||||
                    context.toast(R.string.myanimelist_relogin)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -342,7 +342,7 @@ object Migrations {
 | 
			
		||||
                    "pref_filter_library_started",
 | 
			
		||||
                    "pref_filter_library_bookmarked",
 | 
			
		||||
                    "pref_filter_library_completed",
 | 
			
		||||
                ) + trackManager.services.map { "pref_filter_library_tracked_${it.id}" }
 | 
			
		||||
                ) + trackerManager.trackers.map { "pref_filter_library_tracked_${it.id}" }
 | 
			
		||||
 | 
			
		||||
                prefKeys.forEach { key ->
 | 
			
		||||
                    val pref = preferenceStore.getInt(key, 0)
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.backup
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.util.BackupUtil
 | 
			
		||||
import tachiyomi.domain.source.service.SourceManager
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
@@ -11,7 +11,7 @@ import uy.kohesive.injekt.api.get
 | 
			
		||||
 | 
			
		||||
class BackupFileValidator(
 | 
			
		||||
    private val sourceManager: SourceManager = Injekt.get(),
 | 
			
		||||
    private val trackManager: TrackManager = Injekt.get(),
 | 
			
		||||
    private val trackerManager: TrackerManager = Injekt.get(),
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -50,7 +50,7 @@ class BackupFileValidator(
 | 
			
		||||
            .map { it.syncId }
 | 
			
		||||
            .distinct()
 | 
			
		||||
        val missingTrackers = trackers
 | 
			
		||||
            .mapNotNull { trackManager.getService(it.toLong()) }
 | 
			
		||||
            .mapNotNull { trackerManager.get(it.toLong()) }
 | 
			
		||||
            .filter { !it.isLoggedIn }
 | 
			
		||||
            .map { it.name }
 | 
			
		||||
            .sorted()
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.data.track
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * For track services api that support deleting a manga entry for a user's list
 | 
			
		||||
 * Tracker that support deleting am entry from a user's list.
 | 
			
		||||
 */
 | 
			
		||||
interface DeletableTrackService {
 | 
			
		||||
interface DeletableTracker {
 | 
			
		||||
 | 
			
		||||
    suspend fun delete(track: Track): Track
 | 
			
		||||
}
 | 
			
		||||
@@ -6,31 +6,32 @@ import tachiyomi.domain.manga.model.Manga
 | 
			
		||||
import tachiyomi.domain.track.model.Track
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An Enhanced Track Service will never prompt the user to match a manga with the remote.
 | 
			
		||||
 * It is expected that such Track Service can only work with specific sources and unique IDs.
 | 
			
		||||
 * A tracker that will never prompt the user to manually bind an entry.
 | 
			
		||||
 * It is expected that such tracker can only work with specific sources and unique IDs.
 | 
			
		||||
 */
 | 
			
		||||
interface EnhancedTrackService {
 | 
			
		||||
interface EnhancedTracker {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This TrackService will only work with the sources that are accepted by this filter function.
 | 
			
		||||
     * This tracker will only work with the sources that are accepted by this filter function.
 | 
			
		||||
     */
 | 
			
		||||
    fun accept(source: Source): Boolean {
 | 
			
		||||
        return source::class.qualifiedName in getAcceptedSources()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fully qualified source classes that this track service is compatible with.
 | 
			
		||||
     * Fully qualified source classes that this tracker is compatible with.
 | 
			
		||||
     */
 | 
			
		||||
    fun getAcceptedSources(): List<String>
 | 
			
		||||
 | 
			
		||||
    fun loginNoop()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * match is similar to TrackService.search, but only return zero or one match.
 | 
			
		||||
     * Similar to [Tracker].search, but only returns zero or one match.
 | 
			
		||||
     */
 | 
			
		||||
    suspend fun match(manga: Manga): TrackSearch?
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks whether the provided source/track/manga triplet is from this TrackService
 | 
			
		||||
     * Checks whether the provided source/track/manga triplet is from this [Tracker]
 | 
			
		||||
     */
 | 
			
		||||
    fun isTrackFrom(track: Track, manga: Manga, source: Source?): Boolean
 | 
			
		||||
 | 
			
		||||
@@ -5,7 +5,7 @@ import androidx.annotation.CallSuper
 | 
			
		||||
import androidx.annotation.ColorInt
 | 
			
		||||
import androidx.annotation.DrawableRes
 | 
			
		||||
import androidx.annotation.StringRes
 | 
			
		||||
import eu.kanade.domain.chapter.interactor.SyncChapterProgressWithTrack
 | 
			
		||||
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
 | 
			
		||||
@@ -28,7 +28,7 @@ import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.time.ZoneOffset
 | 
			
		||||
import tachiyomi.domain.track.model.Track as DomainTrack
 | 
			
		||||
 | 
			
		||||
abstract class TrackService(val id: Long, val name: String) {
 | 
			
		||||
abstract class Tracker(val id: Long, val name: String) {
 | 
			
		||||
 | 
			
		||||
    val trackPreferences: TrackPreferences by injectLazy()
 | 
			
		||||
    val networkService: NetworkHelper by injectLazy()
 | 
			
		||||
@@ -83,7 +83,7 @@ abstract class TrackService(val id: Long, val name: String) {
 | 
			
		||||
 | 
			
		||||
    @CallSuper
 | 
			
		||||
    open fun logout() {
 | 
			
		||||
        trackPreferences.setTrackCredentials(this, "", "")
 | 
			
		||||
        trackPreferences.setCredentials(this, "", "")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open val isLoggedIn: Boolean
 | 
			
		||||
@@ -95,7 +95,7 @@ abstract class TrackService(val id: Long, val name: String) {
 | 
			
		||||
    fun getPassword() = trackPreferences.trackPassword(this).get()
 | 
			
		||||
 | 
			
		||||
    fun saveCredentials(username: String, password: String) {
 | 
			
		||||
        trackPreferences.setTrackCredentials(this, username, password)
 | 
			
		||||
        trackPreferences.setCredentials(this, username, password)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: move this to an interactor, and update all trackers based on common data
 | 
			
		||||
@@ -111,7 +111,7 @@ abstract class TrackService(val id: Long, val name: String) {
 | 
			
		||||
 | 
			
		||||
                insertTrack.await(track)
 | 
			
		||||
 | 
			
		||||
                // TODO: merge into SyncChaptersWithTrackServiceTwoWay?
 | 
			
		||||
                // TODO: merge into [SyncChapterProgressWithTrack]?
 | 
			
		||||
                // Update chapter progress if newer chapters marked read locally
 | 
			
		||||
                if (hasReadChapters) {
 | 
			
		||||
                    val latestLocalReadChapterNumber = allChapters
 | 
			
		||||
@@ -143,7 +143,7 @@ abstract class TrackService(val id: Long, val name: String) {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                syncChapterProgressWithTrack.await(mangaId, track, this@TrackService)
 | 
			
		||||
                syncChapterProgressWithTrack.await(mangaId, track, this@Tracker)
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: Throwable) {
 | 
			
		||||
            withUIContext { Injekt.get<Application>().toast(e.message) }
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.track
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.bangumi.Bangumi
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.kavita.Kavita
 | 
			
		||||
@@ -11,33 +10,27 @@ import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.suwayomi.Suwayomi
 | 
			
		||||
 | 
			
		||||
class TrackManager(context: Context) {
 | 
			
		||||
class TrackerManager {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val MYANIMELIST = 1L
 | 
			
		||||
        const val ANILIST = 2L
 | 
			
		||||
        const val KITSU = 3L
 | 
			
		||||
        const val SHIKIMORI = 4L
 | 
			
		||||
        const val BANGUMI = 5L
 | 
			
		||||
        const val KOMGA = 6L
 | 
			
		||||
        const val MANGA_UPDATES = 7L
 | 
			
		||||
        const val KAVITA = 8L
 | 
			
		||||
        const val SUWAYOMI = 9L
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val myAnimeList = MyAnimeList(MYANIMELIST)
 | 
			
		||||
    val myAnimeList = MyAnimeList(1L)
 | 
			
		||||
    val aniList = Anilist(ANILIST)
 | 
			
		||||
    val kitsu = Kitsu(KITSU)
 | 
			
		||||
    val shikimori = Shikimori(SHIKIMORI)
 | 
			
		||||
    val bangumi = Bangumi(BANGUMI)
 | 
			
		||||
    val komga = Komga(KOMGA)
 | 
			
		||||
    val mangaUpdates = MangaUpdates(MANGA_UPDATES)
 | 
			
		||||
    val kavita = Kavita(context, KAVITA)
 | 
			
		||||
    val suwayomi = Suwayomi(SUWAYOMI)
 | 
			
		||||
    val shikimori = Shikimori(4L)
 | 
			
		||||
    val bangumi = Bangumi(5L)
 | 
			
		||||
    val komga = Komga(6L)
 | 
			
		||||
    val mangaUpdates = MangaUpdates(7L)
 | 
			
		||||
    val kavita = Kavita(KAVITA)
 | 
			
		||||
    val suwayomi = Suwayomi(9L)
 | 
			
		||||
 | 
			
		||||
    val services = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga, mangaUpdates, kavita, suwayomi)
 | 
			
		||||
    val trackers = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga, mangaUpdates, kavita, suwayomi)
 | 
			
		||||
 | 
			
		||||
    fun getService(id: Long) = services.find { it.id == id }
 | 
			
		||||
    fun get(id: Long) = trackers.find { it.id == id }
 | 
			
		||||
 | 
			
		||||
    fun hasLoggedServices() = services.any { it.isLoggedIn }
 | 
			
		||||
    fun hasLoggedIn() = trackers.any { it.isLoggedIn }
 | 
			
		||||
}
 | 
			
		||||
@@ -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.DeletableTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
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) : TrackService(id, "AniList"), DeletableTrackService {
 | 
			
		||||
class Anilist(id: Long) : Tracker(id, "AniList"), DeletableTracker {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val READING = 1
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.track.anilist
 | 
			
		||||
 | 
			
		||||
import eu.kanade.domain.track.service.TrackPreferences
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
@@ -20,7 +20,7 @@ data class ALManga(
 | 
			
		||||
    val total_chapters: Int,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    fun toTrack() = TrackSearch.create(TrackManager.ANILIST).apply {
 | 
			
		||||
    fun toTrack() = TrackSearch.create(TrackerManager.ANILIST).apply {
 | 
			
		||||
        media_id = this@ALManga.media_id
 | 
			
		||||
        title = title_user_pref
 | 
			
		||||
        total_chapters = this@ALManga.total_chapters
 | 
			
		||||
@@ -50,7 +50,7 @@ data class ALUserManga(
 | 
			
		||||
    val manga: ALManga,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    fun toTrack() = Track.create(TrackManager.ANILIST).apply {
 | 
			
		||||
    fun toTrack() = Track.create(TrackerManager.ANILIST).apply {
 | 
			
		||||
        media_id = manga.media_id
 | 
			
		||||
        title = manga.title_user_pref
 | 
			
		||||
        status = toTrackStatus()
 | 
			
		||||
@@ -62,7 +62,7 @@ data class ALUserManga(
 | 
			
		||||
        total_chapters = manga.total_chapters
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun toTrackStatus() = when (list_status) {
 | 
			
		||||
    private fun toTrackStatus() = when (list_status) {
 | 
			
		||||
        "CURRENT" -> Anilist.READING
 | 
			
		||||
        "COMPLETED" -> Anilist.COMPLETED
 | 
			
		||||
        "PAUSED" -> Anilist.ON_HOLD
 | 
			
		||||
 
 | 
			
		||||
@@ -4,19 +4,19 @@ 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.Tracker
 | 
			
		||||
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) : TrackService(id, "Bangumi") {
 | 
			
		||||
class Bangumi(id: Long) : Tracker(id, "Bangumi") {
 | 
			
		||||
 | 
			
		||||
    private val json: Json by injectLazy()
 | 
			
		||||
 | 
			
		||||
    private val interceptor by lazy { BangumiInterceptor(this) }
 | 
			
		||||
 | 
			
		||||
    private val api by lazy { BangumiApi(client, interceptor) }
 | 
			
		||||
    private val api by lazy { BangumiApi(id, client, interceptor) }
 | 
			
		||||
 | 
			
		||||
    override fun getScoreList(): List<String> {
 | 
			
		||||
        return IntRange(0, 10).map(Int::toString)
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.track.bangumi
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import androidx.core.net.toUri
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.network.POST
 | 
			
		||||
@@ -26,7 +25,11 @@ import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.net.URLEncoder
 | 
			
		||||
import java.nio.charset.StandardCharsets
 | 
			
		||||
 | 
			
		||||
class BangumiApi(private val client: OkHttpClient, interceptor: BangumiInterceptor) {
 | 
			
		||||
class BangumiApi(
 | 
			
		||||
    private val trackId: Long,
 | 
			
		||||
    private val client: OkHttpClient,
 | 
			
		||||
    interceptor: BangumiInterceptor,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    private val json: Json by injectLazy()
 | 
			
		||||
 | 
			
		||||
@@ -105,7 +108,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
 | 
			
		||||
        } else {
 | 
			
		||||
            0
 | 
			
		||||
        }
 | 
			
		||||
        return TrackSearch.create(TrackManager.BANGUMI).apply {
 | 
			
		||||
        return TrackSearch.create(trackId).apply {
 | 
			
		||||
            media_id = obj["id"]!!.jsonPrimitive.long
 | 
			
		||||
            title = obj["name_cn"]!!.jsonPrimitive.content
 | 
			
		||||
            cover_url = coverUrl
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,21 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.track.kavita
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.SharedPreferences
 | 
			
		||||
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.EnhancedTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
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
 | 
			
		||||
import tachiyomi.domain.manga.model.Manga
 | 
			
		||||
import tachiyomi.domain.source.service.SourceManager
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.security.MessageDigest
 | 
			
		||||
import tachiyomi.domain.track.model.Track as DomainTrack
 | 
			
		||||
 | 
			
		||||
class Kavita(private val context: Context, id: Long) : TrackService(id, "Kavita"), EnhancedTrackService {
 | 
			
		||||
class Kavita(id: Long) : Tracker(id, "Kavita"), EnhancedTracker {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val UNREAD = 1
 | 
			
		||||
@@ -27,6 +28,8 @@ class Kavita(private val context: Context, id: Long) : TrackService(id, "Kavita"
 | 
			
		||||
    private val interceptor by lazy { KavitaInterceptor(this) }
 | 
			
		||||
    val api by lazy { KavitaApi(client, interceptor) }
 | 
			
		||||
 | 
			
		||||
    private val sourceManager: SourceManager by injectLazy()
 | 
			
		||||
 | 
			
		||||
    override fun getLogo(): Int = R.drawable.ic_tracker_kavita
 | 
			
		||||
 | 
			
		||||
    override fun getLogoColor() = Color.rgb(74, 198, 148)
 | 
			
		||||
@@ -83,7 +86,7 @@ class Kavita(private val context: Context, id: Long) : TrackService(id, "Kavita"
 | 
			
		||||
        saveCredentials("user", "pass")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TrackService.isLogged works by checking that credentials are saved.
 | 
			
		||||
    // [Tracker].isLogged works by checking that credentials are saved.
 | 
			
		||||
    // By saving dummy, unused credentials, we can activate the tracker simply by login/logout
 | 
			
		||||
    override fun loginNoop() {
 | 
			
		||||
        saveCredentials("user", "pass")
 | 
			
		||||
@@ -110,28 +113,29 @@ class Kavita(private val context: Context, id: Long) : TrackService(id, "Kavita"
 | 
			
		||||
 | 
			
		||||
    fun loadOAuth() {
 | 
			
		||||
        val oauth = OAuth()
 | 
			
		||||
        for (sourceId in 1..3) {
 | 
			
		||||
            val authentication = oauth.authentications[sourceId - 1]
 | 
			
		||||
            val sourceSuffixID by lazy {
 | 
			
		||||
                val key = "kavita_$sourceId/all/1" // Hardcoded versionID to 1
 | 
			
		||||
        for (id in 1..3) {
 | 
			
		||||
            val authentication = oauth.authentications[id - 1]
 | 
			
		||||
            val sourceId by lazy {
 | 
			
		||||
                val key = "kavita_$id/all/1" // Hardcoded versionID to 1
 | 
			
		||||
                val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
 | 
			
		||||
                (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }
 | 
			
		||||
                    .reduce(Long::or) and Long.MAX_VALUE
 | 
			
		||||
            }
 | 
			
		||||
            val preferences: SharedPreferences by lazy {
 | 
			
		||||
                context.getSharedPreferences("source_$sourceSuffixID", Context.MODE_PRIVATE)
 | 
			
		||||
            }
 | 
			
		||||
            val prefApiUrl = preferences.getString("APIURL", "")!!
 | 
			
		||||
            if (prefApiUrl.isEmpty()) {
 | 
			
		||||
            val preferences = (sourceManager.get(sourceId) as ConfigurableSource).getPreferences()
 | 
			
		||||
 | 
			
		||||
            val prefApiUrl = preferences.getString("APIURL", "")
 | 
			
		||||
            val prefApiKey = preferences.getString("APIKEY", "")
 | 
			
		||||
            if (prefApiUrl.isNullOrEmpty() || prefApiKey.isNullOrEmpty()) {
 | 
			
		||||
                // Source not configured. Skip
 | 
			
		||||
                continue
 | 
			
		||||
            }
 | 
			
		||||
            val prefApiKey = preferences.getString("APIKEY", "")!!
 | 
			
		||||
 | 
			
		||||
            val token = api.getNewToken(apiUrl = prefApiUrl, apiKey = prefApiKey)
 | 
			
		||||
            if (token.isNullOrEmpty()) {
 | 
			
		||||
                // Source is not accessible. Skip
 | 
			
		||||
                continue
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            authentication.apiUrl = prefApiUrl
 | 
			
		||||
            authentication.jwtToken = token.toString()
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.track.kavita
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
 | 
			
		||||
@@ -22,7 +22,7 @@ data class SeriesDto(
 | 
			
		||||
    val libraryId: Int,
 | 
			
		||||
    val libraryName: String? = "",
 | 
			
		||||
) {
 | 
			
		||||
    fun toTrack(): TrackSearch = TrackSearch.create(TrackManager.KAVITA).also {
 | 
			
		||||
    fun toTrack(): TrackSearch = TrackSearch.create(TrackerManager.KAVITA).also {
 | 
			
		||||
        it.title = name
 | 
			
		||||
        it.summary = ""
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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.DeletableTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
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) : TrackService(id, "Kitsu"), DeletableTrackService {
 | 
			
		||||
class Kitsu(id: Long) : Tracker(id, "Kitsu"), DeletableTracker {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val READING = 1
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.track.kitsu
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.CallSuper
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import kotlinx.serialization.json.JsonObject
 | 
			
		||||
@@ -35,7 +35,7 @@ class KitsuSearchManga(obj: JsonObject) {
 | 
			
		||||
    private val endDate = obj["endDate"]?.jsonPrimitive?.contentOrNull
 | 
			
		||||
 | 
			
		||||
    @CallSuper
 | 
			
		||||
    fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply {
 | 
			
		||||
    fun toTrack() = TrackSearch.create(TrackerManager.KITSU).apply {
 | 
			
		||||
        media_id = this@KitsuSearchManga.id
 | 
			
		||||
        title = canonicalTitle
 | 
			
		||||
        total_chapters = chapterCount ?: 0
 | 
			
		||||
@@ -67,7 +67,7 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
 | 
			
		||||
    private val ratingTwenty = obj["attributes"]!!.jsonObject["ratingTwenty"]?.jsonPrimitive?.contentOrNull
 | 
			
		||||
    val progress = obj["attributes"]!!.jsonObject["progress"]!!.jsonPrimitive.int
 | 
			
		||||
 | 
			
		||||
    fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply {
 | 
			
		||||
    fun toTrack() = TrackSearch.create(TrackerManager.KITSU).apply {
 | 
			
		||||
        media_id = libraryId
 | 
			
		||||
        title = canonicalTitle
 | 
			
		||||
        total_chapters = chapterCount ?: 0
 | 
			
		||||
 
 | 
			
		||||
@@ -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.EnhancedTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
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) : TrackService(id, "Komga"), EnhancedTrackService {
 | 
			
		||||
class Komga(id: Long) : Tracker(id, "Komga"), EnhancedTracker {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val UNREAD = 1
 | 
			
		||||
@@ -26,7 +26,7 @@ class Komga(id: Long) : TrackService(id, "Komga"), EnhancedTrackService {
 | 
			
		||||
            .dns(Dns.SYSTEM) // don't use DNS over HTTPS as it breaks IP addressing
 | 
			
		||||
            .build()
 | 
			
		||||
 | 
			
		||||
    val api by lazy { KomgaApi(client) }
 | 
			
		||||
    val api by lazy { KomgaApi(id, client) }
 | 
			
		||||
 | 
			
		||||
    override fun getLogo() = R.drawable.ic_tracker_komga
 | 
			
		||||
 | 
			
		||||
@@ -85,7 +85,7 @@ class Komga(id: Long) : TrackService(id, "Komga"), EnhancedTrackService {
 | 
			
		||||
        saveCredentials("user", "pass")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TrackService.isLogged works by checking that credentials are saved.
 | 
			
		||||
    // [Tracker].isLogged works by checking that credentials are saved.
 | 
			
		||||
    // By saving dummy, unused credentials, we can activate the tracker simply by login/logout
 | 
			
		||||
    override fun loginNoop() {
 | 
			
		||||
        saveCredentials("user", "pass")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.track.komga
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.network.awaitSuccess
 | 
			
		||||
@@ -19,7 +18,10 @@ import uy.kohesive.injekt.injectLazy
 | 
			
		||||
 | 
			
		||||
private const val READLIST_API = "/api/v1/readlists"
 | 
			
		||||
 | 
			
		||||
class KomgaApi(private val client: OkHttpClient) {
 | 
			
		||||
class KomgaApi(
 | 
			
		||||
    private val trackId: Long,
 | 
			
		||||
    private val client: OkHttpClient,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    private val json: Json by injectLazy()
 | 
			
		||||
 | 
			
		||||
@@ -85,13 +87,13 @@ class KomgaApi(private val client: OkHttpClient) {
 | 
			
		||||
        return getTrackSearch(track.tracking_url)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun SeriesDto.toTrack(): TrackSearch = TrackSearch.create(TrackManager.KOMGA).also {
 | 
			
		||||
    private fun SeriesDto.toTrack(): TrackSearch = TrackSearch.create(trackId).also {
 | 
			
		||||
        it.title = metadata.title
 | 
			
		||||
        it.summary = metadata.summary
 | 
			
		||||
        it.publishing_status = metadata.status
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun ReadListDto.toTrack(): TrackSearch = TrackSearch.create(TrackManager.KOMGA).also {
 | 
			
		||||
    private fun ReadListDto.toTrack(): TrackSearch = TrackSearch.create(trackId).also {
 | 
			
		||||
        it.title = name
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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.DeletableTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
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) : TrackService(id, "MangaUpdates"), DeletableTrackService {
 | 
			
		||||
class MangaUpdates(id: Long) : Tracker(id, "MangaUpdates"), DeletableTracker {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val READING_LIST = 0
 | 
			
		||||
 
 | 
			
		||||
@@ -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.DeletableTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
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) : TrackService(id, "MyAnimeList"), DeletableTrackService {
 | 
			
		||||
class MyAnimeList(id: Long) : Tracker(id, "MyAnimeList"), DeletableTracker {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val READING = 1
 | 
			
		||||
@@ -28,7 +28,7 @@ class MyAnimeList(id: Long) : TrackService(id, "MyAnimeList"), DeletableTrackSer
 | 
			
		||||
    private val json: Json by injectLazy()
 | 
			
		||||
 | 
			
		||||
    private val interceptor by lazy { MyAnimeListInterceptor(this, getPassword()) }
 | 
			
		||||
    private val api by lazy { MyAnimeListApi(client, interceptor) }
 | 
			
		||||
    private val api by lazy { MyAnimeListApi(id, client, interceptor) }
 | 
			
		||||
 | 
			
		||||
    override val supportsReadingDates: Boolean = true
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.track.myanimelist
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import androidx.core.net.toUri
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.network.POST
 | 
			
		||||
@@ -32,7 +31,11 @@ import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
 | 
			
		||||
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
 | 
			
		||||
class MyAnimeListApi(
 | 
			
		||||
    private val trackId: Long,
 | 
			
		||||
    private val client: OkHttpClient,
 | 
			
		||||
    interceptor: MyAnimeListInterceptor,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    private val json: Json by injectLazy()
 | 
			
		||||
 | 
			
		||||
@@ -106,7 +109,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
 | 
			
		||||
                    .parseAs<JsonObject>()
 | 
			
		||||
                    .let {
 | 
			
		||||
                        val obj = it.jsonObject
 | 
			
		||||
                        TrackSearch.create(TrackManager.MYANIMELIST).apply {
 | 
			
		||||
                        TrackSearch.create(trackId).apply {
 | 
			
		||||
                            media_id = obj["id"]!!.jsonPrimitive.long
 | 
			
		||||
                            title = obj["title"]!!.jsonPrimitive.content
 | 
			
		||||
                            summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""
 | 
			
		||||
 
 | 
			
		||||
@@ -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.DeletableTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
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) : TrackService(id, "Shikimori"), DeletableTrackService {
 | 
			
		||||
class Shikimori(id: Long) : Tracker(id, "Shikimori"), DeletableTracker {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val READING = 1
 | 
			
		||||
@@ -26,7 +26,7 @@ class Shikimori(id: Long) : TrackService(id, "Shikimori"), DeletableTrackService
 | 
			
		||||
 | 
			
		||||
    private val interceptor by lazy { ShikimoriInterceptor(this) }
 | 
			
		||||
 | 
			
		||||
    private val api by lazy { ShikimoriApi(client, interceptor) }
 | 
			
		||||
    private val api by lazy { ShikimoriApi(id, client, interceptor) }
 | 
			
		||||
 | 
			
		||||
    override fun getScoreList(): List<String> {
 | 
			
		||||
        return IntRange(0, 10).map(Int::toString)
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.data.track.shikimori
 | 
			
		||||
 | 
			
		||||
import androidx.core.net.toUri
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
 | 
			
		||||
import eu.kanade.tachiyomi.network.DELETE
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
@@ -28,7 +27,11 @@ import okhttp3.RequestBody.Companion.toRequestBody
 | 
			
		||||
import tachiyomi.core.util.lang.withIOContext
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
 | 
			
		||||
class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) {
 | 
			
		||||
class ShikimoriApi(
 | 
			
		||||
    private val trackId: Long,
 | 
			
		||||
    private val client: OkHttpClient,
 | 
			
		||||
    interceptor: ShikimoriInterceptor,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    private val json: Json by injectLazy()
 | 
			
		||||
 | 
			
		||||
@@ -96,7 +99,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun jsonToSearch(obj: JsonObject): TrackSearch {
 | 
			
		||||
        return TrackSearch.create(TrackManager.SHIKIMORI).apply {
 | 
			
		||||
        return TrackSearch.create(trackId).apply {
 | 
			
		||||
            media_id = obj["id"]!!.jsonPrimitive.long
 | 
			
		||||
            title = obj["name"]!!.jsonPrimitive.content
 | 
			
		||||
            total_chapters = obj["chapters"]!!.jsonPrimitive.int
 | 
			
		||||
@@ -110,7 +113,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun jsonToTrack(obj: JsonObject, mangas: JsonObject): Track {
 | 
			
		||||
        return Track.create(TrackManager.SHIKIMORI).apply {
 | 
			
		||||
        return Track.create(trackId).apply {
 | 
			
		||||
            title = mangas["name"]!!.jsonPrimitive.content
 | 
			
		||||
            media_id = obj["id"]!!.jsonPrimitive.long
 | 
			
		||||
            total_chapters = mangas["chapters"]!!.jsonPrimitive.int
 | 
			
		||||
 
 | 
			
		||||
@@ -4,16 +4,16 @@ 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.EnhancedTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
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) : TrackService(id, "Suwayomi"), EnhancedTrackService {
 | 
			
		||||
class Suwayomi(id: Long) : Tracker(id, "Suwayomi"), EnhancedTracker {
 | 
			
		||||
 | 
			
		||||
    val api by lazy { TachideskApi() }
 | 
			
		||||
    val api by lazy { SuwayomiApi(id) }
 | 
			
		||||
 | 
			
		||||
    override fun getLogo() = R.drawable.ic_tracker_suwayomi
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ import android.app.Application
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.SharedPreferences
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.network.NetworkHelper
 | 
			
		||||
@@ -24,7 +23,7 @@ import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.nio.charset.Charset
 | 
			
		||||
import java.security.MessageDigest
 | 
			
		||||
 | 
			
		||||
class TachideskApi {
 | 
			
		||||
class SuwayomiApi(private val trackId: Long) {
 | 
			
		||||
 | 
			
		||||
    private val network: NetworkHelper by injectLazy()
 | 
			
		||||
    private val json: Json by injectLazy()
 | 
			
		||||
@@ -62,7 +61,7 @@ class TachideskApi {
 | 
			
		||||
                .parseAs<MangaDataClass>()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        TrackSearch.create(TrackManager.SUWAYOMI).apply {
 | 
			
		||||
        TrackSearch.create(trackId).apply {
 | 
			
		||||
            title = manga.title
 | 
			
		||||
            cover_url = "$url/thumbnail"
 | 
			
		||||
            summary = manga.description.orEmpty()
 | 
			
		||||
@@ -101,26 +100,24 @@ class TachideskApi {
 | 
			
		||||
        return getTrackSearch(track.tracking_url)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val tachideskExtensionId by lazy {
 | 
			
		||||
    private val sourceId by lazy {
 | 
			
		||||
        val key = "tachidesk/en/1"
 | 
			
		||||
        val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
 | 
			
		||||
        (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val preferences: SharedPreferences by lazy {
 | 
			
		||||
        Injekt.get<Application>().getSharedPreferences("source_$tachideskExtensionId", Context.MODE_PRIVATE)
 | 
			
		||||
        Injekt.get<Application>().getSharedPreferences("source_$sourceId", Context.MODE_PRIVATE)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getPrefBaseUrl(): String = preferences.getString(ADDRESS_TITLE, ADDRESS_DEFAULT)!!
 | 
			
		||||
    private fun getPrefBaseLogin(): String = preferences.getString(LOGIN_TITLE, LOGIN_DEFAULT)!!
 | 
			
		||||
    private fun getPrefBasePassword(): String = preferences.getString(PASSWORD_TITLE, PASSWORD_DEFAULT)!!
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val ADDRESS_TITLE = "Server URL Address"
 | 
			
		||||
        private const val ADDRESS_DEFAULT = ""
 | 
			
		||||
        private const val LOGIN_TITLE = "Login (Basic Auth)"
 | 
			
		||||
        private const val LOGIN_DEFAULT = ""
 | 
			
		||||
        private const val PASSWORD_TITLE = "Password (Basic Auth)"
 | 
			
		||||
        private const val PASSWORD_DEFAULT = ""
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private const val ADDRESS_TITLE = "Server URL Address"
 | 
			
		||||
private const val ADDRESS_DEFAULT = ""
 | 
			
		||||
private const val LOGIN_TITLE = "Login (Basic Auth)"
 | 
			
		||||
private const val LOGIN_DEFAULT = ""
 | 
			
		||||
private const val PASSWORD_TITLE = "Password (Basic Auth)"
 | 
			
		||||
private const val PASSWORD_DEFAULT = ""
 | 
			
		||||
@@ -35,8 +35,8 @@ import eu.kanade.domain.manga.model.toSManga
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.cache.CoverCache
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.DownloadManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SChapter
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags
 | 
			
		||||
@@ -177,7 +177,7 @@ internal class MigrateDialogScreenModel(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val enhancedServices by lazy {
 | 
			
		||||
        Injekt.get<TrackManager>().services.filterIsInstance<EnhancedTrackService>()
 | 
			
		||||
        Injekt.get<TrackerManager>().trackers.filterIsInstance<EnhancedTracker>()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun migrateManga(
 | 
			
		||||
 
 | 
			
		||||
@@ -15,16 +15,12 @@ import cafe.adriel.voyager.core.model.StateScreenModel
 | 
			
		||||
import cafe.adriel.voyager.core.model.coroutineScope
 | 
			
		||||
import eu.kanade.core.preference.asState
 | 
			
		||||
import eu.kanade.domain.base.BasePreferences
 | 
			
		||||
import eu.kanade.domain.chapter.interactor.SyncChapterProgressWithTrack
 | 
			
		||||
import eu.kanade.domain.manga.interactor.UpdateManga
 | 
			
		||||
import eu.kanade.domain.manga.model.toDomainManga
 | 
			
		||||
import eu.kanade.domain.source.service.SourcePreferences
 | 
			
		||||
import eu.kanade.domain.track.model.toDomainTrack
 | 
			
		||||
import eu.kanade.domain.track.interactor.AddTracks
 | 
			
		||||
import eu.kanade.presentation.util.ioCoroutineScope
 | 
			
		||||
import eu.kanade.tachiyomi.data.cache.CoverCache
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.FilterList
 | 
			
		||||
import eu.kanade.tachiyomi.util.removeCovers
 | 
			
		||||
@@ -37,11 +33,9 @@ import kotlinx.coroutines.flow.map
 | 
			
		||||
import kotlinx.coroutines.flow.stateIn
 | 
			
		||||
import kotlinx.coroutines.flow.update
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import logcat.LogPriority
 | 
			
		||||
import tachiyomi.core.preference.CheckboxState
 | 
			
		||||
import tachiyomi.core.preference.mapAsCheckboxState
 | 
			
		||||
import tachiyomi.core.util.lang.launchIO
 | 
			
		||||
import tachiyomi.core.util.system.logcat
 | 
			
		||||
import tachiyomi.domain.category.interactor.GetCategories
 | 
			
		||||
import tachiyomi.domain.category.interactor.SetMangaCategories
 | 
			
		||||
import tachiyomi.domain.category.model.Category
 | 
			
		||||
@@ -54,7 +48,6 @@ import tachiyomi.domain.manga.model.Manga
 | 
			
		||||
import tachiyomi.domain.manga.model.toMangaUpdate
 | 
			
		||||
import tachiyomi.domain.source.interactor.GetRemoteManga
 | 
			
		||||
import tachiyomi.domain.source.service.SourceManager
 | 
			
		||||
import tachiyomi.domain.track.interactor.InsertTrack
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.util.Date
 | 
			
		||||
@@ -76,12 +69,9 @@ class BrowseSourceScreenModel(
 | 
			
		||||
    private val getManga: GetManga = Injekt.get(),
 | 
			
		||||
    private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
 | 
			
		||||
    private val updateManga: UpdateManga = Injekt.get(),
 | 
			
		||||
    private val insertTrack: InsertTrack = Injekt.get(),
 | 
			
		||||
    private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack = Injekt.get(),
 | 
			
		||||
    private val addTracks: AddTracks = Injekt.get(),
 | 
			
		||||
) : StateScreenModel<BrowseSourceScreenModel.State>(State(Listing.valueOf(listingQuery))) {
 | 
			
		||||
 | 
			
		||||
    private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLoggedIn } }
 | 
			
		||||
 | 
			
		||||
    var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
 | 
			
		||||
 | 
			
		||||
    val source = sourceManager.getOrStub(sourceId)
 | 
			
		||||
@@ -243,8 +233,7 @@ class BrowseSourceScreenModel(
 | 
			
		||||
                new = new.removeCovers(coverCache)
 | 
			
		||||
            } else {
 | 
			
		||||
                setMangaDefaultChapterFlags.await(manga)
 | 
			
		||||
 | 
			
		||||
                autoAddTrack(manga)
 | 
			
		||||
                addTracks.bindEnhancedTracks(manga, source)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            updateManga.await(new.toMangaUpdate())
 | 
			
		||||
@@ -281,25 +270,6 @@ class BrowseSourceScreenModel(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun autoAddTrack(manga: Manga) {
 | 
			
		||||
        loggedServices
 | 
			
		||||
            .filterIsInstance<EnhancedTrackService>()
 | 
			
		||||
            .filter { it.accept(source) }
 | 
			
		||||
            .forEach { service ->
 | 
			
		||||
                try {
 | 
			
		||||
                    service.match(manga)?.let { track ->
 | 
			
		||||
                        track.manga_id = manga.id
 | 
			
		||||
                        (service as TrackService).bind(track)
 | 
			
		||||
                        insertTrack.await(track.toDomainTrack()!!)
 | 
			
		||||
 | 
			
		||||
                        syncChapterProgressWithTrack.await(manga.id, track.toDomainTrack()!!, service)
 | 
			
		||||
                    }
 | 
			
		||||
                } catch (e: Exception) {
 | 
			
		||||
                    logcat(LogPriority.WARN, e) { "Could not match manga: ${manga.title} with service $service" }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get user categories.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import eu.kanade.presentation.manga.DownloadAction
 | 
			
		||||
import eu.kanade.tachiyomi.data.cache.CoverCache
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.DownloadCache
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.DownloadManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.HttpSource
 | 
			
		||||
import eu.kanade.tachiyomi.util.chapter.getNextUnread
 | 
			
		||||
@@ -88,7 +88,7 @@ class LibraryScreenModel(
 | 
			
		||||
    private val sourceManager: SourceManager = Injekt.get(),
 | 
			
		||||
    private val downloadManager: DownloadManager = Injekt.get(),
 | 
			
		||||
    private val downloadCache: DownloadCache = Injekt.get(),
 | 
			
		||||
    private val trackManager: TrackManager = Injekt.get(),
 | 
			
		||||
    private val trackerManager: TrackerManager = Injekt.get(),
 | 
			
		||||
) : StateScreenModel<LibraryScreenModel.State>(State()) {
 | 
			
		||||
 | 
			
		||||
    var activeCategoryIndex: Int by libraryPreferences.lastUsedCategory().asState(coroutineScope)
 | 
			
		||||
@@ -101,9 +101,9 @@ class LibraryScreenModel(
 | 
			
		||||
                getTracksPerManga.subscribe(),
 | 
			
		||||
                getTrackingFilterFlow(),
 | 
			
		||||
                downloadCache.changes,
 | 
			
		||||
            ) { searchQuery, library, tracks, loggedInTrackServices, _ ->
 | 
			
		||||
            ) { searchQuery, library, tracks, loggedInTrackers, _ ->
 | 
			
		||||
                library
 | 
			
		||||
                    .applyFilters(tracks, loggedInTrackServices)
 | 
			
		||||
                    .applyFilters(tracks, loggedInTrackers)
 | 
			
		||||
                    .applySort()
 | 
			
		||||
                    .mapValues { (_, value) ->
 | 
			
		||||
                        if (searchQuery != null) {
 | 
			
		||||
@@ -169,7 +169,7 @@ class LibraryScreenModel(
 | 
			
		||||
     */
 | 
			
		||||
    private suspend fun LibraryMap.applyFilters(
 | 
			
		||||
        trackMap: Map<Long, List<Long>>,
 | 
			
		||||
        loggedInTrackServices: Map<Long, TriState>,
 | 
			
		||||
        loggedInTrackers: Map<Long, TriState>,
 | 
			
		||||
    ): LibraryMap {
 | 
			
		||||
        val prefs = getLibraryItemPreferencesFlow().first()
 | 
			
		||||
        val downloadedOnly = prefs.globalFilterDownloaded
 | 
			
		||||
@@ -180,10 +180,10 @@ class LibraryScreenModel(
 | 
			
		||||
        val filterBookmarked = prefs.filterBookmarked
 | 
			
		||||
        val filterCompleted = prefs.filterCompleted
 | 
			
		||||
 | 
			
		||||
        val isNotLoggedInAnyTrack = loggedInTrackServices.isEmpty()
 | 
			
		||||
        val isNotLoggedInAnyTrack = loggedInTrackers.isEmpty()
 | 
			
		||||
 | 
			
		||||
        val excludedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null }
 | 
			
		||||
        val includedTracks = loggedInTrackServices.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null }
 | 
			
		||||
        val excludedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null }
 | 
			
		||||
        val includedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null }
 | 
			
		||||
        val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty()
 | 
			
		||||
 | 
			
		||||
        val filterFnDownloaded: (LibraryItem) -> Boolean = {
 | 
			
		||||
@@ -366,14 +366,14 @@ class LibraryScreenModel(
 | 
			
		||||
     * @return map of track id with the filter value
 | 
			
		||||
     */
 | 
			
		||||
    private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
 | 
			
		||||
        val loggedServices = trackManager.services.filter { it.isLoggedIn }
 | 
			
		||||
        return if (loggedServices.isNotEmpty()) {
 | 
			
		||||
            val prefFlows = loggedServices
 | 
			
		||||
        val loggedInTrackers = trackerManager.trackers.filter { it.isLoggedIn }
 | 
			
		||||
        return if (loggedInTrackers.isNotEmpty()) {
 | 
			
		||||
            val prefFlows = loggedInTrackers
 | 
			
		||||
                .map { libraryPreferences.filterTracking(it.id.toInt()).changes() }
 | 
			
		||||
                .toTypedArray()
 | 
			
		||||
            combine(*prefFlows) {
 | 
			
		||||
                loggedServices
 | 
			
		||||
                    .mapIndexed { index, trackService -> trackService.id to it[index] }
 | 
			
		||||
                loggedInTrackers
 | 
			
		||||
                    .mapIndexed { index, tracker -> tracker.id to it[index] }
 | 
			
		||||
                    .toMap()
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.library
 | 
			
		||||
import cafe.adriel.voyager.core.model.ScreenModel
 | 
			
		||||
import cafe.adriel.voyager.core.model.coroutineScope
 | 
			
		||||
import eu.kanade.domain.base.BasePreferences
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import tachiyomi.core.preference.Preference
 | 
			
		||||
import tachiyomi.core.preference.TriState
 | 
			
		||||
import tachiyomi.core.preference.getAndSet
 | 
			
		||||
@@ -22,11 +22,11 @@ class LibrarySettingsScreenModel(
 | 
			
		||||
    val libraryPreferences: LibraryPreferences = Injekt.get(),
 | 
			
		||||
    private val setDisplayMode: SetDisplayMode = Injekt.get(),
 | 
			
		||||
    private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
 | 
			
		||||
    private val trackManager: TrackManager = Injekt.get(),
 | 
			
		||||
    private val trackerManager: TrackerManager = Injekt.get(),
 | 
			
		||||
) : ScreenModel {
 | 
			
		||||
 | 
			
		||||
    val trackServices
 | 
			
		||||
        get() = trackManager.services.filter { it.isLoggedIn }
 | 
			
		||||
    val trackers
 | 
			
		||||
        get() = trackerManager.trackers.filter { it.isLoggedIn }
 | 
			
		||||
 | 
			
		||||
    fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) {
 | 
			
		||||
        preference(libraryPreferences).getAndSet {
 | 
			
		||||
 
 | 
			
		||||
@@ -138,7 +138,7 @@ class MainActivity : BaseActivity() {
 | 
			
		||||
                libraryPreferences = libraryPreferences,
 | 
			
		||||
                readerPreferences = Injekt.get(),
 | 
			
		||||
                backupPreferences = Injekt.get(),
 | 
			
		||||
                trackManager = Injekt.get(),
 | 
			
		||||
                trackerManager = Injekt.get(),
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            false
 | 
			
		||||
 
 | 
			
		||||
@@ -23,9 +23,9 @@ import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.DownloadCache
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.DownloadManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.model.Download
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.Tracker
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.network.HttpException
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
 | 
			
		||||
@@ -84,7 +84,7 @@ class MangaScreenModel(
 | 
			
		||||
    private val libraryPreferences: LibraryPreferences = Injekt.get(),
 | 
			
		||||
    readerPreferences: ReaderPreferences = Injekt.get(),
 | 
			
		||||
    uiPreferences: UiPreferences = Injekt.get(),
 | 
			
		||||
    private val trackManager: TrackManager = Injekt.get(),
 | 
			
		||||
    private val trackerManager: TrackerManager = Injekt.get(),
 | 
			
		||||
    private val downloadManager: DownloadManager = Injekt.get(),
 | 
			
		||||
    private val downloadCache: DownloadCache = Injekt.get(),
 | 
			
		||||
    private val getMangaAndChapters: GetMangaWithChapters = Injekt.get(),
 | 
			
		||||
@@ -105,7 +105,7 @@ class MangaScreenModel(
 | 
			
		||||
    private val successState: State.Success?
 | 
			
		||||
        get() = state.value as? State.Success
 | 
			
		||||
 | 
			
		||||
    private val loggedServices by lazy { trackManager.services.filter { it.isLoggedIn } }
 | 
			
		||||
    private val loggedInTrackers by lazy { trackerManager.trackers.filter { it.isLoggedIn } }
 | 
			
		||||
 | 
			
		||||
    val manga: Manga?
 | 
			
		||||
        get() = successState?.manga
 | 
			
		||||
@@ -317,14 +317,14 @@ class MangaScreenModel(
 | 
			
		||||
                // Finally match with enhanced tracking when available
 | 
			
		||||
                val source = state.source
 | 
			
		||||
                state.trackItems
 | 
			
		||||
                    .map { it.service }
 | 
			
		||||
                    .filterIsInstance<EnhancedTrackService>()
 | 
			
		||||
                    .map { it.tracker }
 | 
			
		||||
                    .filterIsInstance<EnhancedTracker>()
 | 
			
		||||
                    .filter { it.accept(source) }
 | 
			
		||||
                    .forEach { service ->
 | 
			
		||||
                        launchIO {
 | 
			
		||||
                            try {
 | 
			
		||||
                                service.match(manga)?.let { track ->
 | 
			
		||||
                                    (service as TrackService).register(track, mangaId)
 | 
			
		||||
                                    (service as Tracker).register(track, mangaId)
 | 
			
		||||
                                }
 | 
			
		||||
                            } catch (e: Exception) {
 | 
			
		||||
                                logcat(LogPriority.WARN, e) {
 | 
			
		||||
@@ -949,11 +949,11 @@ class MangaScreenModel(
 | 
			
		||||
            getTracks.subscribe(manga.id)
 | 
			
		||||
                .catch { logcat(LogPriority.ERROR, it) }
 | 
			
		||||
                .map { tracks ->
 | 
			
		||||
                    loggedServices
 | 
			
		||||
                    loggedInTrackers
 | 
			
		||||
                        // Map to TrackItem
 | 
			
		||||
                        .map { service -> TrackItem(tracks.find { it.syncId == service.id }, service) }
 | 
			
		||||
                        // Show only if the service supports this manga's source
 | 
			
		||||
                        .filter { (it.service as? EnhancedTrackService)?.accept(source!!) ?: true }
 | 
			
		||||
                        .filter { (it.tracker as? EnhancedTracker)?.accept(source!!) ?: true }
 | 
			
		||||
                }
 | 
			
		||||
                .distinctUntilChanged()
 | 
			
		||||
                .collectLatest { trackItems ->
 | 
			
		||||
 
 | 
			
		||||
@@ -46,14 +46,14 @@ import eu.kanade.presentation.track.TrackChapterSelector
 | 
			
		||||
import eu.kanade.presentation.track.TrackDateSelector
 | 
			
		||||
import eu.kanade.presentation.track.TrackInfoDialogHome
 | 
			
		||||
import eu.kanade.presentation.track.TrackScoreSelector
 | 
			
		||||
import eu.kanade.presentation.track.TrackServiceSearch
 | 
			
		||||
import eu.kanade.presentation.track.TrackStatusSelector
 | 
			
		||||
import eu.kanade.presentation.track.TrackerSearch
 | 
			
		||||
import eu.kanade.presentation.util.Screen
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.DeletableTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.Tracker
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
 | 
			
		||||
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.openInBrowser
 | 
			
		||||
@@ -105,7 +105,7 @@ data class TrackInfoDialogHomeScreen(
 | 
			
		||||
                navigator.push(
 | 
			
		||||
                    TrackStatusSelectorScreen(
 | 
			
		||||
                        track = it.track!!,
 | 
			
		||||
                        serviceId = it.service.id,
 | 
			
		||||
                        serviceId = it.tracker.id,
 | 
			
		||||
                    ),
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
@@ -113,7 +113,7 @@ data class TrackInfoDialogHomeScreen(
 | 
			
		||||
                navigator.push(
 | 
			
		||||
                    TrackChapterSelectorScreen(
 | 
			
		||||
                        track = it.track!!,
 | 
			
		||||
                        serviceId = it.service.id,
 | 
			
		||||
                        serviceId = it.tracker.id,
 | 
			
		||||
                    ),
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
@@ -121,7 +121,7 @@ data class TrackInfoDialogHomeScreen(
 | 
			
		||||
                navigator.push(
 | 
			
		||||
                    TrackScoreSelectorScreen(
 | 
			
		||||
                        track = it.track!!,
 | 
			
		||||
                        serviceId = it.service.id,
 | 
			
		||||
                        serviceId = it.tracker.id,
 | 
			
		||||
                    ),
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
@@ -129,7 +129,7 @@ data class TrackInfoDialogHomeScreen(
 | 
			
		||||
                navigator.push(
 | 
			
		||||
                    TrackDateSelectorScreen(
 | 
			
		||||
                        track = it.track!!,
 | 
			
		||||
                        serviceId = it.service.id,
 | 
			
		||||
                        serviceId = it.tracker.id,
 | 
			
		||||
                        start = true,
 | 
			
		||||
                    ),
 | 
			
		||||
                )
 | 
			
		||||
@@ -138,21 +138,21 @@ data class TrackInfoDialogHomeScreen(
 | 
			
		||||
                navigator.push(
 | 
			
		||||
                    TrackDateSelectorScreen(
 | 
			
		||||
                        track = it.track!!,
 | 
			
		||||
                        serviceId = it.service.id,
 | 
			
		||||
                        serviceId = it.tracker.id,
 | 
			
		||||
                        start = false,
 | 
			
		||||
                    ),
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
            onNewSearch = {
 | 
			
		||||
                if (it.service is EnhancedTrackService) {
 | 
			
		||||
                if (it.tracker is EnhancedTracker) {
 | 
			
		||||
                    sm.registerEnhancedTracking(it)
 | 
			
		||||
                } else {
 | 
			
		||||
                    navigator.push(
 | 
			
		||||
                        TrackServiceSearchScreen(
 | 
			
		||||
                        TrackerSearchScreen(
 | 
			
		||||
                            mangaId = mangaId,
 | 
			
		||||
                            initialQuery = it.track?.title ?: mangaTitle,
 | 
			
		||||
                            currentUrl = it.track?.remoteUrl,
 | 
			
		||||
                            serviceId = it.service.id,
 | 
			
		||||
                            serviceId = it.tracker.id,
 | 
			
		||||
                        ),
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
@@ -160,10 +160,10 @@ data class TrackInfoDialogHomeScreen(
 | 
			
		||||
            onOpenInBrowser = { openTrackerInBrowser(context, it) },
 | 
			
		||||
            onRemoved = {
 | 
			
		||||
                navigator.push(
 | 
			
		||||
                    TrackServiceRemoveScreen(
 | 
			
		||||
                    TrackerRemoveScreen(
 | 
			
		||||
                        mangaId = mangaId,
 | 
			
		||||
                        track = it.track!!,
 | 
			
		||||
                        serviceId = it.service.id,
 | 
			
		||||
                        serviceId = it.tracker.id,
 | 
			
		||||
                    ),
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
@@ -201,12 +201,12 @@ data class TrackInfoDialogHomeScreen(
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun registerEnhancedTracking(item: TrackItem) {
 | 
			
		||||
            item.service as EnhancedTrackService
 | 
			
		||||
            item.tracker as EnhancedTracker
 | 
			
		||||
            coroutineScope.launchNonCancellable {
 | 
			
		||||
                val manga = Injekt.get<GetManga>().await(mangaId) ?: return@launchNonCancellable
 | 
			
		||||
                try {
 | 
			
		||||
                    val matchResult = item.service.match(manga) ?: throw Exception()
 | 
			
		||||
                    item.service.register(matchResult, mangaId)
 | 
			
		||||
                    val matchResult = item.tracker.match(manga) ?: throw Exception()
 | 
			
		||||
                    item.tracker.register(matchResult, mangaId)
 | 
			
		||||
                } catch (e: Exception) {
 | 
			
		||||
                    withUIContext { Injekt.get<Application>().toast(R.string.error_no_match) }
 | 
			
		||||
                }
 | 
			
		||||
@@ -236,13 +236,13 @@ data class TrackInfoDialogHomeScreen(
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private fun List<Track>.mapToTrackItem(): List<TrackItem> {
 | 
			
		||||
            val loggedServices = Injekt.get<TrackManager>().services.filter { it.isLoggedIn }
 | 
			
		||||
            val loggedInTrackers = Injekt.get<TrackerManager>().trackers.filter { it.isLoggedIn }
 | 
			
		||||
            val source = Injekt.get<SourceManager>().getOrStub(sourceId)
 | 
			
		||||
            return loggedServices
 | 
			
		||||
            return loggedInTrackers
 | 
			
		||||
                // Map to TrackItem
 | 
			
		||||
                .map { service -> TrackItem(find { it.syncId == service.id }, service) }
 | 
			
		||||
                // Show only if the service supports this manga's source
 | 
			
		||||
                .filter { (it.service as? EnhancedTrackService)?.accept(source) ?: true }
 | 
			
		||||
                .filter { (it.tracker as? EnhancedTracker)?.accept(source) ?: true }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Immutable
 | 
			
		||||
@@ -263,7 +263,7 @@ private data class TrackStatusSelectorScreen(
 | 
			
		||||
        val sm = rememberScreenModel {
 | 
			
		||||
            Model(
 | 
			
		||||
                track = track,
 | 
			
		||||
                service = Injekt.get<TrackManager>().getService(serviceId)!!,
 | 
			
		||||
                tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        val state by sm.state.collectAsState()
 | 
			
		||||
@@ -281,11 +281,11 @@ private data class TrackStatusSelectorScreen(
 | 
			
		||||
 | 
			
		||||
    private class Model(
 | 
			
		||||
        private val track: Track,
 | 
			
		||||
        private val service: TrackService,
 | 
			
		||||
        private val tracker: Tracker,
 | 
			
		||||
    ) : StateScreenModel<Model.State>(State(track.status.toInt())) {
 | 
			
		||||
 | 
			
		||||
        fun getSelections(): Map<Int, Int?> {
 | 
			
		||||
            return service.getStatusList().associateWith { service.getStatus(it) }
 | 
			
		||||
            return tracker.getStatusList().associateWith { tracker.getStatus(it) }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun setSelection(selection: Int) {
 | 
			
		||||
@@ -294,7 +294,7 @@ private data class TrackStatusSelectorScreen(
 | 
			
		||||
 | 
			
		||||
        fun setStatus() {
 | 
			
		||||
            coroutineScope.launchNonCancellable {
 | 
			
		||||
                service.setRemoteStatus(track.toDbTrack(), state.value.selection)
 | 
			
		||||
                tracker.setRemoteStatus(track.toDbTrack(), state.value.selection)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -316,7 +316,7 @@ private data class TrackChapterSelectorScreen(
 | 
			
		||||
        val sm = rememberScreenModel {
 | 
			
		||||
            Model(
 | 
			
		||||
                track = track,
 | 
			
		||||
                service = Injekt.get<TrackManager>().getService(serviceId)!!,
 | 
			
		||||
                tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        val state by sm.state.collectAsState()
 | 
			
		||||
@@ -335,7 +335,7 @@ private data class TrackChapterSelectorScreen(
 | 
			
		||||
 | 
			
		||||
    private class Model(
 | 
			
		||||
        private val track: Track,
 | 
			
		||||
        private val service: TrackService,
 | 
			
		||||
        private val tracker: Tracker,
 | 
			
		||||
    ) : StateScreenModel<Model.State>(State(track.lastChapterRead.toInt())) {
 | 
			
		||||
 | 
			
		||||
        fun getRange(): Iterable<Int> {
 | 
			
		||||
@@ -353,7 +353,7 @@ private data class TrackChapterSelectorScreen(
 | 
			
		||||
 | 
			
		||||
        fun setChapter() {
 | 
			
		||||
            coroutineScope.launchNonCancellable {
 | 
			
		||||
                service.setRemoteLastChapterRead(track.toDbTrack(), state.value.selection)
 | 
			
		||||
                tracker.setRemoteLastChapterRead(track.toDbTrack(), state.value.selection)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -375,7 +375,7 @@ private data class TrackScoreSelectorScreen(
 | 
			
		||||
        val sm = rememberScreenModel {
 | 
			
		||||
            Model(
 | 
			
		||||
                track = track,
 | 
			
		||||
                service = Injekt.get<TrackManager>().getService(serviceId)!!,
 | 
			
		||||
                tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        val state by sm.state.collectAsState()
 | 
			
		||||
@@ -394,11 +394,11 @@ private data class TrackScoreSelectorScreen(
 | 
			
		||||
 | 
			
		||||
    private class Model(
 | 
			
		||||
        private val track: Track,
 | 
			
		||||
        private val service: TrackService,
 | 
			
		||||
    ) : StateScreenModel<Model.State>(State(service.displayScore(track.toDbTrack()))) {
 | 
			
		||||
        private val tracker: Tracker,
 | 
			
		||||
    ) : StateScreenModel<Model.State>(State(tracker.displayScore(track.toDbTrack()))) {
 | 
			
		||||
 | 
			
		||||
        fun getSelections(): List<String> {
 | 
			
		||||
            return service.getScoreList()
 | 
			
		||||
            return tracker.getScoreList()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun setSelection(selection: String) {
 | 
			
		||||
@@ -407,7 +407,7 @@ private data class TrackScoreSelectorScreen(
 | 
			
		||||
 | 
			
		||||
        fun setScore() {
 | 
			
		||||
            coroutineScope.launchNonCancellable {
 | 
			
		||||
                service.setRemoteScore(track.toDbTrack(), state.value.selection)
 | 
			
		||||
                tracker.setRemoteScore(track.toDbTrack(), state.value.selection)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -486,7 +486,7 @@ private data class TrackDateSelectorScreen(
 | 
			
		||||
        val sm = rememberScreenModel {
 | 
			
		||||
            Model(
 | 
			
		||||
                track = track,
 | 
			
		||||
                service = Injekt.get<TrackManager>().getService(serviceId)!!,
 | 
			
		||||
                tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
 | 
			
		||||
                start = start,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
@@ -515,7 +515,7 @@ private data class TrackDateSelectorScreen(
 | 
			
		||||
 | 
			
		||||
    private class Model(
 | 
			
		||||
        private val track: Track,
 | 
			
		||||
        private val service: TrackService,
 | 
			
		||||
        private val tracker: Tracker,
 | 
			
		||||
        private val start: Boolean,
 | 
			
		||||
    ) : ScreenModel {
 | 
			
		||||
 | 
			
		||||
@@ -534,15 +534,15 @@ private data class TrackDateSelectorScreen(
 | 
			
		||||
            val localMillis = millis.convertEpochMillisZone(ZoneOffset.UTC, ZoneOffset.systemDefault())
 | 
			
		||||
            coroutineScope.launchNonCancellable {
 | 
			
		||||
                if (start) {
 | 
			
		||||
                    service.setRemoteStartDate(track.toDbTrack(), localMillis)
 | 
			
		||||
                    tracker.setRemoteStartDate(track.toDbTrack(), localMillis)
 | 
			
		||||
                } else {
 | 
			
		||||
                    service.setRemoteFinishDate(track.toDbTrack(), localMillis)
 | 
			
		||||
                    tracker.setRemoteFinishDate(track.toDbTrack(), localMillis)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun confirmRemoveDate(navigator: Navigator) {
 | 
			
		||||
            navigator.push(TrackDateRemoverScreen(track, service.id, start))
 | 
			
		||||
            navigator.push(TrackDateRemoverScreen(track, tracker.id, start))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -559,7 +559,7 @@ private data class TrackDateRemoverScreen(
 | 
			
		||||
        val sm = rememberScreenModel {
 | 
			
		||||
            Model(
 | 
			
		||||
                track = track,
 | 
			
		||||
                service = Injekt.get<TrackManager>().getService(serviceId)!!,
 | 
			
		||||
                tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
 | 
			
		||||
                start = start,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
@@ -614,25 +614,25 @@ private data class TrackDateRemoverScreen(
 | 
			
		||||
 | 
			
		||||
    private class Model(
 | 
			
		||||
        private val track: Track,
 | 
			
		||||
        private val service: TrackService,
 | 
			
		||||
        private val tracker: Tracker,
 | 
			
		||||
        private val start: Boolean,
 | 
			
		||||
    ) : ScreenModel {
 | 
			
		||||
 | 
			
		||||
        fun getServiceName() = service.name
 | 
			
		||||
        fun getServiceName() = tracker.name
 | 
			
		||||
 | 
			
		||||
        fun removeDate() {
 | 
			
		||||
            coroutineScope.launchNonCancellable {
 | 
			
		||||
                if (start) {
 | 
			
		||||
                    service.setRemoteStartDate(track.toDbTrack(), 0)
 | 
			
		||||
                    tracker.setRemoteStartDate(track.toDbTrack(), 0)
 | 
			
		||||
                } else {
 | 
			
		||||
                    service.setRemoteFinishDate(track.toDbTrack(), 0)
 | 
			
		||||
                    tracker.setRemoteFinishDate(track.toDbTrack(), 0)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
data class TrackServiceSearchScreen(
 | 
			
		||||
data class TrackerSearchScreen(
 | 
			
		||||
    private val mangaId: Long,
 | 
			
		||||
    private val initialQuery: String,
 | 
			
		||||
    private val currentUrl: String?,
 | 
			
		||||
@@ -647,14 +647,14 @@ data class TrackServiceSearchScreen(
 | 
			
		||||
                mangaId = mangaId,
 | 
			
		||||
                currentUrl = currentUrl,
 | 
			
		||||
                initialQuery = initialQuery,
 | 
			
		||||
                service = Injekt.get<TrackManager>().getService(serviceId)!!,
 | 
			
		||||
                tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val state by sm.state.collectAsState()
 | 
			
		||||
 | 
			
		||||
        var textFieldValue by remember { mutableStateOf(TextFieldValue(initialQuery)) }
 | 
			
		||||
        TrackServiceSearch(
 | 
			
		||||
        TrackerSearch(
 | 
			
		||||
            query = textFieldValue,
 | 
			
		||||
            onQueryChange = { textFieldValue = it },
 | 
			
		||||
            onDispatchQuery = { sm.trackingSearch(textFieldValue.text) },
 | 
			
		||||
@@ -673,7 +673,7 @@ data class TrackServiceSearchScreen(
 | 
			
		||||
        private val mangaId: Long,
 | 
			
		||||
        private val currentUrl: String? = null,
 | 
			
		||||
        initialQuery: String,
 | 
			
		||||
        private val service: TrackService,
 | 
			
		||||
        private val tracker: Tracker,
 | 
			
		||||
    ) : StateScreenModel<Model.State>(State()) {
 | 
			
		||||
 | 
			
		||||
        init {
 | 
			
		||||
@@ -690,7 +690,7 @@ data class TrackServiceSearchScreen(
 | 
			
		||||
 | 
			
		||||
                val result = withIOContext {
 | 
			
		||||
                    try {
 | 
			
		||||
                        val results = service.search(query)
 | 
			
		||||
                        val results = tracker.search(query)
 | 
			
		||||
                        Result.success(results)
 | 
			
		||||
                    } catch (e: Throwable) {
 | 
			
		||||
                        Result.failure(e)
 | 
			
		||||
@@ -706,7 +706,7 @@ data class TrackServiceSearchScreen(
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun registerTracking(item: TrackSearch) {
 | 
			
		||||
            coroutineScope.launchNonCancellable { service.register(item, mangaId) }
 | 
			
		||||
            coroutineScope.launchNonCancellable { tracker.register(item, mangaId) }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun updateSelection(selected: TrackSearch) {
 | 
			
		||||
@@ -721,7 +721,7 @@ data class TrackServiceSearchScreen(
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private data class TrackServiceRemoveScreen(
 | 
			
		||||
private data class TrackerRemoveScreen(
 | 
			
		||||
    private val mangaId: Long,
 | 
			
		||||
    private val track: Track,
 | 
			
		||||
    private val serviceId: Long,
 | 
			
		||||
@@ -734,10 +734,10 @@ private data class TrackServiceRemoveScreen(
 | 
			
		||||
            Model(
 | 
			
		||||
                mangaId = mangaId,
 | 
			
		||||
                track = track,
 | 
			
		||||
                service = Injekt.get<TrackManager>().getService(serviceId)!!,
 | 
			
		||||
                tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        val serviceName = sm.getServiceName()
 | 
			
		||||
        val serviceName = sm.getName()
 | 
			
		||||
        var removeRemoteTrack by remember { mutableStateOf(false) }
 | 
			
		||||
        AlertDialogContent(
 | 
			
		||||
            modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars),
 | 
			
		||||
@@ -758,7 +758,7 @@ private data class TrackServiceRemoveScreen(
 | 
			
		||||
                    Text(
 | 
			
		||||
                        text = stringResource(R.string.track_delete_text, serviceName),
 | 
			
		||||
                    )
 | 
			
		||||
                    if (sm.isServiceDeletable()) {
 | 
			
		||||
                    if (sm.isDeletable()) {
 | 
			
		||||
                        Row(verticalAlignment = Alignment.CenterVertically) {
 | 
			
		||||
                            Checkbox(checked = removeRemoteTrack, onCheckedChange = { removeRemoteTrack = it })
 | 
			
		||||
                            Text(text = stringResource(R.string.track_delete_remote_text, serviceName))
 | 
			
		||||
@@ -798,17 +798,17 @@ private data class TrackServiceRemoveScreen(
 | 
			
		||||
    private class Model(
 | 
			
		||||
        private val mangaId: Long,
 | 
			
		||||
        private val track: Track,
 | 
			
		||||
        private val service: TrackService,
 | 
			
		||||
        private val tracker: Tracker,
 | 
			
		||||
        private val deleteTrack: DeleteTrack = Injekt.get(),
 | 
			
		||||
    ) : ScreenModel {
 | 
			
		||||
 | 
			
		||||
        fun getServiceName() = service.name
 | 
			
		||||
        fun getName() = tracker.name
 | 
			
		||||
 | 
			
		||||
        fun isServiceDeletable() = service is DeletableTrackService
 | 
			
		||||
        fun isDeletable() = tracker is DeletableTracker
 | 
			
		||||
 | 
			
		||||
        fun deleteMangaFromService() {
 | 
			
		||||
            coroutineScope.launchNonCancellable {
 | 
			
		||||
                (service as DeletableTrackService).delete(track.toDbTrack())
 | 
			
		||||
                (tracker as DeletableTracker).delete(track.toDbTrack())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.manga.track
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.Tracker
 | 
			
		||||
import tachiyomi.domain.track.model.Track
 | 
			
		||||
 | 
			
		||||
data class TrackItem(val track: Track?, val service: TrackService)
 | 
			
		||||
data class TrackItem(val track: Track?, val tracker: Tracker)
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.setting.track
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
 | 
			
		||||
import eu.kanade.tachiyomi.ui.main.MainActivity
 | 
			
		||||
import eu.kanade.tachiyomi.util.view.setComposeContent
 | 
			
		||||
@@ -12,7 +12,7 @@ import uy.kohesive.injekt.injectLazy
 | 
			
		||||
 | 
			
		||||
abstract class BaseOAuthLoginActivity : BaseActivity() {
 | 
			
		||||
 | 
			
		||||
    internal val trackManager: TrackManager by injectLazy()
 | 
			
		||||
    internal val trackerManager: TrackerManager by injectLazy()
 | 
			
		||||
 | 
			
		||||
    abstract fun handleResult(data: Uri?)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,11 +20,11 @@ class TrackLoginActivity : BaseOAuthLoginActivity() {
 | 
			
		||||
        val matchResult = regex.find(data.fragment.toString())
 | 
			
		||||
        if (matchResult?.groups?.get(1) != null) {
 | 
			
		||||
            lifecycleScope.launchIO {
 | 
			
		||||
                trackManager.aniList.login(matchResult.groups[1]!!.value)
 | 
			
		||||
                trackerManager.aniList.login(matchResult.groups[1]!!.value)
 | 
			
		||||
                returnToSettings()
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            trackManager.aniList.logout()
 | 
			
		||||
            trackerManager.aniList.logout()
 | 
			
		||||
            returnToSettings()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -33,11 +33,11 @@ class TrackLoginActivity : BaseOAuthLoginActivity() {
 | 
			
		||||
        val code = data.getQueryParameter("code")
 | 
			
		||||
        if (code != null) {
 | 
			
		||||
            lifecycleScope.launchIO {
 | 
			
		||||
                trackManager.bangumi.login(code)
 | 
			
		||||
                trackerManager.bangumi.login(code)
 | 
			
		||||
                returnToSettings()
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            trackManager.bangumi.logout()
 | 
			
		||||
            trackerManager.bangumi.logout()
 | 
			
		||||
            returnToSettings()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -46,11 +46,11 @@ class TrackLoginActivity : BaseOAuthLoginActivity() {
 | 
			
		||||
        val code = data.getQueryParameter("code")
 | 
			
		||||
        if (code != null) {
 | 
			
		||||
            lifecycleScope.launchIO {
 | 
			
		||||
                trackManager.myAnimeList.login(code)
 | 
			
		||||
                trackerManager.myAnimeList.login(code)
 | 
			
		||||
                returnToSettings()
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            trackManager.myAnimeList.logout()
 | 
			
		||||
            trackerManager.myAnimeList.logout()
 | 
			
		||||
            returnToSettings()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -59,11 +59,11 @@ class TrackLoginActivity : BaseOAuthLoginActivity() {
 | 
			
		||||
        val code = data.getQueryParameter("code")
 | 
			
		||||
        if (code != null) {
 | 
			
		||||
            lifecycleScope.launchIO {
 | 
			
		||||
                trackManager.shikimori.login(code)
 | 
			
		||||
                trackerManager.shikimori.login(code)
 | 
			
		||||
                returnToSettings()
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            trackManager.shikimori.logout()
 | 
			
		||||
            trackerManager.shikimori.logout()
 | 
			
		||||
            returnToSettings()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import eu.kanade.core.util.fastMapNotNull
 | 
			
		||||
import eu.kanade.presentation.more.stats.StatsScreenState
 | 
			
		||||
import eu.kanade.presentation.more.stats.data.StatsData
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.DownloadManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackerManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import kotlinx.coroutines.flow.update
 | 
			
		||||
import tachiyomi.core.util.lang.launchIO
 | 
			
		||||
@@ -33,10 +33,10 @@ class StatsScreenModel(
 | 
			
		||||
    private val getTotalReadDuration: GetTotalReadDuration = Injekt.get(),
 | 
			
		||||
    private val getTracks: GetTracks = Injekt.get(),
 | 
			
		||||
    private val preferences: LibraryPreferences = Injekt.get(),
 | 
			
		||||
    private val trackManager: TrackManager = Injekt.get(),
 | 
			
		||||
    private val trackerManager: TrackerManager = Injekt.get(),
 | 
			
		||||
) : StateScreenModel<StatsScreenState>(StatsScreenState.Loading) {
 | 
			
		||||
 | 
			
		||||
    private val loggedServices by lazy { trackManager.services.fastFilter { it.isLoggedIn } }
 | 
			
		||||
    private val loggedInTrackers by lazy { trackerManager.trackers.fastFilter { it.isLoggedIn } }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        coroutineScope.launchIO {
 | 
			
		||||
@@ -72,7 +72,7 @@ class StatsScreenModel(
 | 
			
		||||
            val trackersStatData = StatsData.Trackers(
 | 
			
		||||
                trackedTitleCount = mangaTrackMap.count { it.value.isNotEmpty() },
 | 
			
		||||
                meanScore = meanScore,
 | 
			
		||||
                trackerCount = loggedServices.size,
 | 
			
		||||
                trackerCount = loggedInTrackers.size,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            mutableState.update {
 | 
			
		||||
@@ -115,10 +115,10 @@ class StatsScreenModel(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun getMangaTrackMap(libraryManga: List<LibraryManga>): Map<Long, List<Track>> {
 | 
			
		||||
        val loggedServicesIds = loggedServices.map { it.id }.toHashSet()
 | 
			
		||||
        val loggedInTrackerIds = loggedInTrackers.map { it.id }.toHashSet()
 | 
			
		||||
        return libraryManga.associate { manga ->
 | 
			
		||||
            val tracks = getTracks.await(manga.id)
 | 
			
		||||
                .fastFilter { it.syncId in loggedServicesIds }
 | 
			
		||||
                .fastFilter { it.syncId in loggedInTrackerIds }
 | 
			
		||||
 | 
			
		||||
            manga.id to tracks
 | 
			
		||||
        }
 | 
			
		||||
@@ -144,7 +144,7 @@ class StatsScreenModel(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun get10PointScore(track: Track): Double {
 | 
			
		||||
        val service = trackManager.getService(track.syncId)!!
 | 
			
		||||
        val service = trackerManager.get(track.syncId)!!
 | 
			
		||||
        return service.get10PointScore(track)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user