Tracker-related cleanup

This commit is contained in:
arkon 2023-09-25 23:22:16 -04:00
parent 86edce0d87
commit c8e226acb2
54 changed files with 417 additions and 395 deletions

View File

@ -1,7 +1,6 @@
package eu.kanade.domain package eu.kanade.domain
import eu.kanade.domain.chapter.interactor.SetReadStatus 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.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.download.interactor.DeleteDownload import eu.kanade.domain.download.interactor.DeleteDownload
import eu.kanade.domain.extension.interactor.GetExtensionLanguages 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.ToggleLanguage
import eu.kanade.domain.source.interactor.ToggleSource import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.domain.source.interactor.ToggleSourcePin 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.RefreshTracks
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
import eu.kanade.domain.track.interactor.TrackChapter import eu.kanade.domain.track.interactor.TrackChapter
import tachiyomi.data.category.CategoryRepositoryImpl import tachiyomi.data.category.CategoryRepositoryImpl
import tachiyomi.data.chapter.ChapterRepositoryImpl import tachiyomi.data.chapter.ChapterRepositoryImpl
@ -114,11 +115,13 @@ class DomainModule : InjektModule {
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) } addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
addFactory { TrackChapter(get(), get(), get(), get()) } addFactory { TrackChapter(get(), get(), get(), get()) }
addFactory { AddTracks(get(), get(), get()) }
addFactory { RefreshTracks(get(), get(), get(), get()) } addFactory { RefreshTracks(get(), get(), get(), get()) }
addFactory { DeleteTrack(get()) } addFactory { DeleteTrack(get()) }
addFactory { GetTracksPerManga(get()) } addFactory { GetTracksPerManga(get()) }
addFactory { GetTracks(get()) } addFactory { GetTracks(get()) }
addFactory { InsertTrack(get()) } addFactory { InsertTrack(get()) }
addFactory { SyncChapterProgressWithTrack(get(), get(), get()) }
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) } addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
addFactory { GetChapter(get()) } addFactory { GetChapter(get()) }
@ -127,7 +130,6 @@ class DomainModule : InjektModule {
addFactory { SetReadStatus(get(), get(), get(), get()) } addFactory { SetReadStatus(get(), get(), get(), get()) }
addFactory { ShouldUpdateDbChapter() } addFactory { ShouldUpdateDbChapter() }
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get()) } addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get()) }
addFactory { SyncChapterProgressWithTrack(get(), get(), get()) }
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) } addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
addFactory { GetHistory(get()) } addFactory { GetHistory(get()) }

View File

@ -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" }
}
}
}
}
}

View File

@ -1,10 +1,9 @@
package eu.kanade.domain.track.interactor 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.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackerManager
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
@ -13,7 +12,7 @@ import tachiyomi.domain.track.interactor.InsertTrack
class RefreshTracks( class RefreshTracks(
private val getTracks: GetTracks, private val getTracks: GetTracks,
private val trackManager: TrackManager, private val trackerManager: TrackerManager,
private val insertTrack: InsertTrack, private val insertTrack: InsertTrack,
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack, private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
) { ) {
@ -23,18 +22,17 @@ class RefreshTracks(
* *
* @return Failed updates. * @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 {
return@supervisorScope getTracks.await(mangaId) return@supervisorScope getTracks.await(mangaId)
.map { track -> .map { it to trackerManager.get(it.syncId) }
.filter { (_, service) -> service?.isLoggedIn == true }
.map { (track, service) ->
async { async {
val service = trackManager.getService(track.syncId)
return@async try { return@async try {
if (service?.isLoggedIn == true) { val updatedTrack = service!!.refresh(track.toDbTrack())
val updatedTrack = service.refresh(track.toDbTrack())
insertTrack.await(updatedTrack.toDomainTrack()!!) insertTrack.await(updatedTrack.toDomainTrack()!!)
syncChapterProgressWithTrack.await(mangaId, track, service) syncChapterProgressWithTrack.await(mangaId, track, service)
}
null null
} catch (e: Throwable) { } catch (e: Throwable) {
service to e service to e

View File

@ -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.domain.track.model.toDbTrack
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.Tracker
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
@ -20,9 +20,9 @@ class SyncChapterProgressWithTrack(
suspend fun await( suspend fun await(
mangaId: Long, mangaId: Long,
remoteTrack: Track, remoteTrack: Track,
service: TrackService, tracker: Tracker,
) { ) {
if (service !is EnhancedTrackService) { if (tracker !is EnhancedTracker) {
return return
} }
@ -39,7 +39,7 @@ class SyncChapterProgressWithTrack(
val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble()) val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
try { try {
service.update(updatedTrack.toDbTrack()) tracker.update(updatedTrack.toDbTrack())
updateChapter.awaitAll(chapterUpdates) updateChapter.awaitAll(chapterUpdates)
insertTrack.await(updatedTrack) insertTrack.await(updatedTrack)
} catch (e: Throwable) { } catch (e: Throwable) {

View File

@ -4,30 +4,29 @@ import android.content.Context
import eu.kanade.domain.track.model.toDbTrack import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.service.DelayedTrackingUpdateJob import eu.kanade.domain.track.service.DelayedTrackingUpdateJob
import eu.kanade.domain.track.store.DelayedTrackingStore 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.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.track.interactor.GetTracks import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack import tachiyomi.domain.track.interactor.InsertTrack
class TrackChapter( class TrackChapter(
private val getTracks: GetTracks, private val getTracks: GetTracks,
private val trackManager: TrackManager, private val trackerManager: TrackerManager,
private val insertTrack: InsertTrack, private val insertTrack: InsertTrack,
private val delayedTrackingStore: DelayedTrackingStore, private val delayedTrackingStore: DelayedTrackingStore,
) { ) {
suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) = coroutineScope { suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) {
launchNonCancellable { withNonCancellableContext {
val tracks = getTracks.await(mangaId) val tracks = getTracks.await(mangaId)
if (tracks.isEmpty()) return@launchNonCancellable if (tracks.isEmpty()) return@withNonCancellableContext
tracks.mapNotNull { track -> tracks.mapNotNull { track ->
val service = trackManager.getService(track.syncId) val service = trackerManager.get(track.syncId)
if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) { if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) {
return@mapNotNull null return@mapNotNull null
} }

View File

@ -10,7 +10,7 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import eu.kanade.domain.track.model.toDbTrack import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.store.DelayedTrackingStore 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 eu.kanade.tachiyomi.util.system.workManager
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
@ -33,7 +33,7 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
val getTracks = Injekt.get<GetTracks>() val getTracks = Injekt.get<GetTracks>()
val insertTrack = Injekt.get<InsertTrack>() val insertTrack = Injekt.get<InsertTrack>()
val trackManager = Injekt.get<TrackManager>() val trackerManager = Injekt.get<TrackerManager>()
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>() val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
withIOContext { withIOContext {
@ -47,7 +47,7 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
} }
.forEach { track -> .forEach { track ->
try { try {
val service = trackManager.getService(track.syncId) val service = trackerManager.get(track.syncId)
if (service != null && service.isLoggedIn) { if (service != null && service.isLoggedIn) {
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" } logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" }
service.update(track.toDbTrack(), true) service.update(track.toDbTrack(), true)

View File

@ -1,6 +1,6 @@
package eu.kanade.domain.track.service 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 eu.kanade.tachiyomi.data.track.anilist.Anilist
import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.PreferenceStore
@ -8,16 +8,16 @@ class TrackPreferences(
private val preferenceStore: PreferenceStore, 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) trackUsername(sync).set(username)
trackPassword(sync).set(password) 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) fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)

View File

@ -108,13 +108,13 @@ private fun ColumnScope.FilterPage(
onClick = { screenModel.toggleFilter(LibraryPreferences::filterCompleted) }, onClick = { screenModel.toggleFilter(LibraryPreferences::filterCompleted) },
) )
val trackServices = remember { screenModel.trackServices } val trackers = remember { screenModel.trackers }
when (trackServices.size) { when (trackers.size) {
0 -> { 0 -> {
// No trackers // No trackers
} }
1 -> { 1 -> {
val service = trackServices[0] val service = trackers[0]
val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState() val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
TriStateItem( TriStateItem(
label = stringResource(R.string.action_filter_tracked), label = stringResource(R.string.action_filter_tracked),
@ -124,7 +124,7 @@ private fun ColumnScope.FilterPage(
} }
else -> { else -> {
HeadingItem(R.string.action_filter_tracked) HeadingItem(R.string.action_filter_tracked)
trackServices.map { service -> trackers.map { service ->
val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState() val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
TriStateItem( TriStateItem(
label = service.name, label = service.name,

View File

@ -5,7 +5,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import eu.kanade.tachiyomi.R 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 import tachiyomi.core.preference.Preference as PreferenceData
sealed class Preference { sealed class Preference {
@ -132,10 +132,10 @@ sealed class Preference {
) : PreferenceItem<String>() ) : PreferenceItem<String>()
/** /**
* A [PreferenceItem] for individual tracking service. * A [PreferenceItem] for individual tracker.
*/ */
data class TrackingPreference( data class TrackerPreference(
val service: TrackService, val tracker: Tracker,
override val title: String, override val title: String,
val login: () -> Unit, val login: () -> Unit,
val logout: () -> Unit, val logout: () -> Unit,

View File

@ -156,13 +156,13 @@ internal fun PreferenceItem(
}, },
) )
} }
is Preference.PreferenceItem.TrackingPreference -> { is Preference.PreferenceItem.TrackerPreference -> {
val uName by Injekt.get<PreferenceStore>() val uName by Injekt.get<PreferenceStore>()
.getString(TrackPreferences.trackUsername(item.service.id)) .getString(TrackPreferences.trackUsername(item.tracker.id))
.collectAsState() .collectAsState()
item.service.run { item.tracker.run {
TrackingPreferenceWidget( TrackingPreferenceWidget(
service = this, tracker = this,
checked = uName.isNotEmpty(), checked = uName.isNotEmpty(),
onClick = { if (isLoggedIn) item.logout() else item.login() }, onClick = { if (isLoggedIn) item.logout() else item.login() },
) )

View File

@ -34,7 +34,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob 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.NetworkHelper
import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.network.PREF_DOH_360 import eu.kanade.tachiyomi.network.PREF_DOH_360
@ -328,7 +328,7 @@ object SettingsAdvancedScreen : SearchableSettings {
private fun getLibraryGroup(): Preference.PreferenceGroup { private fun getLibraryGroup(): Preference.PreferenceGroup {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val context = LocalContext.current val context = LocalContext.current
val trackManager = remember { Injekt.get<TrackManager>() } val trackerManager = remember { Injekt.get<TrackerManager>() }
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(R.string.label_library), title = stringResource(R.string.label_library),
@ -340,7 +340,7 @@ object SettingsAdvancedScreen : SearchableSettings {
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.pref_refresh_library_tracking), title = stringResource(R.string.pref_refresh_library_tracking),
subtitle = stringResource(R.string.pref_refresh_library_tracking_summary), subtitle = stringResource(R.string.pref_refresh_library_tracking_summary),
enabled = trackManager.hasLoggedServices(), enabled = trackerManager.hasLoggedIn(),
onClick = { LibraryUpdateJob.startNow(context, target = LibraryUpdateJob.Target.TRACKING) }, onClick = { LibraryUpdateJob.startNow(context, target = LibraryUpdateJob.Target.TRACKING) },
), ),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(

View File

@ -23,7 +23,7 @@ import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.widget.TriStateListDialog import eu.kanade.presentation.more.settings.widget.TriStateListDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob 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 eu.kanade.tachiyomi.ui.category.CategoryScreen
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -199,7 +199,7 @@ object SettingsLibraryScreen : SearchableSettings {
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
pref = libraryPreferences.autoUpdateTrackers(), pref = libraryPreferences.autoUpdateTrackers(),
enabled = Injekt.get<TrackManager>().hasLoggedServices(), enabled = Injekt.get<TrackerManager>().hasLoggedIn(),
title = stringResource(R.string.pref_library_update_refresh_trackers), title = stringResource(R.string.pref_library_update_refresh_trackers),
subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary), subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary),
), ),

View File

@ -44,9 +44,9 @@ import androidx.compose.ui.unit.dp
import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.data.track.anilist.AnilistApi import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi
@ -82,7 +82,7 @@ object SettingsTrackingScreen : SearchableSettings {
override fun getPreferences(): List<Preference> { override fun getPreferences(): List<Preference> {
val context = LocalContext.current val context = LocalContext.current
val trackPreferences = remember { Injekt.get<TrackPreferences>() } val trackPreferences = remember { Injekt.get<TrackPreferences>() }
val trackManager = remember { Injekt.get<TrackManager>() } val trackerManager = remember { Injekt.get<TrackerManager>() }
val sourceManager = remember { Injekt.get<SourceManager>() } val sourceManager = remember { Injekt.get<SourceManager>() }
var dialog by remember { mutableStateOf<Any?>(null) } var dialog by remember { mutableStateOf<Any?>(null) }
@ -90,24 +90,24 @@ object SettingsTrackingScreen : SearchableSettings {
when (this) { when (this) {
is LoginDialog -> { is LoginDialog -> {
TrackingLoginDialog( TrackingLoginDialog(
service = service, tracker = tracker,
uNameStringRes = uNameStringRes, uNameStringRes = uNameStringRes,
onDismissRequest = { dialog = null }, onDismissRequest = { dialog = null },
) )
} }
is LogoutDialog -> { is LogoutDialog -> {
TrackingLogoutDialog( TrackingLogoutDialog(
service = service, tracker = tracker,
onDismissRequest = { dialog = null }, onDismissRequest = { dialog = null },
) )
} }
} }
} }
val enhancedTrackers = trackManager.services val enhancedTrackers = trackerManager.trackers
.filter { it is EnhancedTrackService } .filter { it is EnhancedTracker }
.partition { service -> .partition { service ->
val acceptedSources = (service as EnhancedTrackService).getAcceptedSources() val acceptedSources = (service as EnhancedTracker).getAcceptedSources()
sourceManager.getCatalogueSources().any { it::class.qualifiedName in acceptedSources } sourceManager.getCatalogueSources().any { it::class.qualifiedName in acceptedSources }
} }
var enhancedTrackerInfo = stringResource(R.string.enhanced_tracking_info) var enhancedTrackerInfo = stringResource(R.string.enhanced_tracking_info)
@ -127,41 +127,41 @@ object SettingsTrackingScreen : SearchableSettings {
Preference.PreferenceGroup( Preference.PreferenceGroup(
title = stringResource(R.string.services), title = stringResource(R.string.services),
preferenceItems = listOf( preferenceItems = listOf(
Preference.PreferenceItem.TrackingPreference( Preference.PreferenceItem.TrackerPreference(
title = trackManager.myAnimeList.name, title = trackerManager.myAnimeList.name,
service = trackManager.myAnimeList, tracker = trackerManager.myAnimeList,
login = { context.openInBrowser(MyAnimeListApi.authUrl(), forceDefaultBrowser = true) }, login = { context.openInBrowser(MyAnimeListApi.authUrl(), forceDefaultBrowser = true) },
logout = { dialog = LogoutDialog(trackManager.myAnimeList) }, logout = { dialog = LogoutDialog(trackerManager.myAnimeList) },
), ),
Preference.PreferenceItem.TrackingPreference( Preference.PreferenceItem.TrackerPreference(
title = trackManager.aniList.name, title = trackerManager.aniList.name,
service = trackManager.aniList, tracker = trackerManager.aniList,
login = { context.openInBrowser(AnilistApi.authUrl(), forceDefaultBrowser = true) }, login = { context.openInBrowser(AnilistApi.authUrl(), forceDefaultBrowser = true) },
logout = { dialog = LogoutDialog(trackManager.aniList) }, logout = { dialog = LogoutDialog(trackerManager.aniList) },
), ),
Preference.PreferenceItem.TrackingPreference( Preference.PreferenceItem.TrackerPreference(
title = trackManager.kitsu.name, title = trackerManager.kitsu.name,
service = trackManager.kitsu, tracker = trackerManager.kitsu,
login = { dialog = LoginDialog(trackManager.kitsu, R.string.email) }, login = { dialog = LoginDialog(trackerManager.kitsu, R.string.email) },
logout = { dialog = LogoutDialog(trackManager.kitsu) }, logout = { dialog = LogoutDialog(trackerManager.kitsu) },
), ),
Preference.PreferenceItem.TrackingPreference( Preference.PreferenceItem.TrackerPreference(
title = trackManager.mangaUpdates.name, title = trackerManager.mangaUpdates.name,
service = trackManager.mangaUpdates, tracker = trackerManager.mangaUpdates,
login = { dialog = LoginDialog(trackManager.mangaUpdates, R.string.username) }, login = { dialog = LoginDialog(trackerManager.mangaUpdates, R.string.username) },
logout = { dialog = LogoutDialog(trackManager.mangaUpdates) }, logout = { dialog = LogoutDialog(trackerManager.mangaUpdates) },
), ),
Preference.PreferenceItem.TrackingPreference( Preference.PreferenceItem.TrackerPreference(
title = trackManager.shikimori.name, title = trackerManager.shikimori.name,
service = trackManager.shikimori, tracker = trackerManager.shikimori,
login = { context.openInBrowser(ShikimoriApi.authUrl(), forceDefaultBrowser = true) }, login = { context.openInBrowser(ShikimoriApi.authUrl(), forceDefaultBrowser = true) },
logout = { dialog = LogoutDialog(trackManager.shikimori) }, logout = { dialog = LogoutDialog(trackerManager.shikimori) },
), ),
Preference.PreferenceItem.TrackingPreference( Preference.PreferenceItem.TrackerPreference(
title = trackManager.bangumi.name, title = trackerManager.bangumi.name,
service = trackManager.bangumi, tracker = trackerManager.bangumi,
login = { context.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true) }, 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)), Preference.PreferenceItem.InfoPreference(stringResource(R.string.tracking_info)),
), ),
@ -170,10 +170,10 @@ object SettingsTrackingScreen : SearchableSettings {
title = stringResource(R.string.enhanced_services), title = stringResource(R.string.enhanced_services),
preferenceItems = enhancedTrackers.first preferenceItems = enhancedTrackers.first
.map { service -> .map { service ->
Preference.PreferenceItem.TrackingPreference( Preference.PreferenceItem.TrackerPreference(
title = service.name, title = service.name,
service = service, tracker = service,
login = { (service as EnhancedTrackService).loginNoop() }, login = { (service as EnhancedTracker).loginNoop() },
logout = service::logout, logout = service::logout,
) )
} + listOf(Preference.PreferenceItem.InfoPreference(enhancedTrackerInfo)), } + listOf(Preference.PreferenceItem.InfoPreference(enhancedTrackerInfo)),
@ -183,15 +183,15 @@ object SettingsTrackingScreen : SearchableSettings {
@Composable @Composable
private fun TrackingLoginDialog( private fun TrackingLoginDialog(
service: TrackService, tracker: Tracker,
@StringRes uNameStringRes: Int, @StringRes uNameStringRes: Int,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var username by remember { mutableStateOf(TextFieldValue(service.getUsername())) } var username by remember { mutableStateOf(TextFieldValue(tracker.getUsername())) }
var password by remember { mutableStateOf(TextFieldValue(service.getPassword())) } var password by remember { mutableStateOf(TextFieldValue(tracker.getPassword())) }
var processing by remember { mutableStateOf(false) } var processing by remember { mutableStateOf(false) }
var inputError by remember { mutableStateOf(false) } var inputError by remember { mutableStateOf(false) }
@ -200,7 +200,7 @@ object SettingsTrackingScreen : SearchableSettings {
title = { title = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Text( Text(
text = stringResource(R.string.login_title, service.name), text = stringResource(R.string.login_title, tracker.name),
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) )
IconButton(onClick = onDismissRequest) { IconButton(onClick = onDismissRequest) {
@ -264,7 +264,7 @@ object SettingsTrackingScreen : SearchableSettings {
processing = true processing = true
val result = checkLogin( val result = checkLogin(
context = context, context = context,
service = service, tracker = tracker,
username = username.text, username = username.text,
password = password.text, password = password.text,
) )
@ -283,16 +283,16 @@ object SettingsTrackingScreen : SearchableSettings {
private suspend fun checkLogin( private suspend fun checkLogin(
context: Context, context: Context,
service: TrackService, tracker: Tracker,
username: String, username: String,
password: String, password: String,
): Boolean { ): Boolean {
return try { return try {
service.login(username, password) tracker.login(username, password)
withUIContext { context.toast(R.string.login_success) } withUIContext { context.toast(R.string.login_success) }
true true
} catch (e: Throwable) { } catch (e: Throwable) {
service.logout() tracker.logout()
withUIContext { context.toast(e.message.toString()) } withUIContext { context.toast(e.message.toString()) }
false false
} }
@ -300,7 +300,7 @@ object SettingsTrackingScreen : SearchableSettings {
@Composable @Composable
private fun TrackingLogoutDialog( private fun TrackingLogoutDialog(
service: TrackService, tracker: Tracker,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
@ -308,7 +308,7 @@ object SettingsTrackingScreen : SearchableSettings {
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
title = { title = {
Text( Text(
text = stringResource(R.string.logout_title, service.name), text = stringResource(R.string.logout_title, tracker.name),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
@ -324,7 +324,7 @@ object SettingsTrackingScreen : SearchableSettings {
Button( Button(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
onClick = { onClick = {
service.logout() tracker.logout()
onDismissRequest() onDismissRequest()
context.toast(R.string.logout_success) context.toast(R.string.logout_success)
}, },
@ -342,10 +342,10 @@ object SettingsTrackingScreen : SearchableSettings {
} }
private data class LoginDialog( private data class LoginDialog(
val service: TrackService, val tracker: Tracker,
@StringRes val uNameStringRes: Int, @StringRes val uNameStringRes: Int,
) )
private data class LogoutDialog( private data class LogoutDialog(
val service: TrackService, val tracker: Tracker,
) )

View File

@ -20,12 +20,12 @@ import androidx.compose.ui.unit.dp
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
import eu.kanade.presentation.track.components.TrackLogoIcon import eu.kanade.presentation.track.components.TrackLogoIcon
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.Tracker
@Composable @Composable
fun TrackingPreferenceWidget( fun TrackingPreferenceWidget(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
service: TrackService, tracker: Tracker,
checked: Boolean, checked: Boolean,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
) { ) {
@ -38,9 +38,9 @@ fun TrackingPreferenceWidget(
.padding(horizontal = PrefsHorizontalPadding, vertical = 8.dp), .padding(horizontal = PrefsHorizontalPadding, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
TrackLogoIcon(service) TrackLogoIcon(tracker)
Text( Text(
text = service.name, text = tracker.name,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),

View File

@ -49,7 +49,7 @@ import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.track.components.TrackLogoIcon import eu.kanade.presentation.track.components.TrackLogoIcon
import eu.kanade.tachiyomi.R 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.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import java.text.DateFormat import java.text.DateFormat
@ -80,12 +80,12 @@ fun TrackInfoDialogHome(
) { ) {
trackItems.forEach { item -> trackItems.forEach { item ->
if (item.track != null) { if (item.track != null) {
val supportsScoring = item.service.getScoreList().isNotEmpty() val supportsScoring = item.tracker.getScoreList().isNotEmpty()
val supportsReadingDates = item.service.supportsReadingDates val supportsReadingDates = item.tracker.supportsReadingDates
TrackInfoItem( TrackInfoItem(
title = item.track.title, title = item.track.title,
service = item.service, tracker = item.tracker,
status = item.service.getStatus(item.track.status.toInt()), status = item.tracker.getStatus(item.track.status.toInt()),
onStatusClick = { onStatusClick(item) }, onStatusClick = { onStatusClick(item) },
chapters = "${item.track.lastChapterRead.toInt()}".let { chapters = "${item.track.lastChapterRead.toInt()}".let {
val totalChapters = item.track.totalChapters val totalChapters = item.track.totalChapters
@ -97,7 +97,7 @@ fun TrackInfoDialogHome(
} }
}, },
onChaptersClick = { onChapterClick(item) }, onChaptersClick = { onChapterClick(item) },
score = item.service.displayScore(item.track.toDbTrack()) score = item.tracker.displayScore(item.track.toDbTrack())
.takeIf { supportsScoring && item.track.score != 0.0 }, .takeIf { supportsScoring && item.track.score != 0.0 },
onScoreClick = { onScoreClick(item) } onScoreClick = { onScoreClick(item) }
.takeIf { supportsScoring }, .takeIf { supportsScoring },
@ -115,7 +115,7 @@ fun TrackInfoDialogHome(
) )
} else { } else {
TrackInfoItemEmpty( TrackInfoItemEmpty(
service = item.service, tracker = item.tracker,
onNewSearch = { onNewSearch(item) }, onNewSearch = { onNewSearch(item) },
) )
} }
@ -126,7 +126,7 @@ fun TrackInfoDialogHome(
@Composable @Composable
private fun TrackInfoItem( private fun TrackInfoItem(
title: String, title: String,
service: TrackService, tracker: Tracker,
@StringRes status: Int?, @StringRes status: Int?,
onStatusClick: () -> Unit, onStatusClick: () -> Unit,
chapters: String, chapters: String,
@ -147,7 +147,7 @@ private fun TrackInfoItem(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
TrackLogoIcon( TrackLogoIcon(
service = service, tracker = tracker,
onClick = onOpenInBrowser, onClick = onOpenInBrowser,
) )
Box( Box(
@ -260,13 +260,13 @@ private fun TrackDetailsItem(
@Composable @Composable
private fun TrackInfoItemEmpty( private fun TrackInfoItemEmpty(
service: TrackService, tracker: Tracker,
onNewSearch: () -> Unit, onNewSearch: () -> Unit,
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
TrackLogoIcon(service) TrackLogoIcon(tracker)
TextButton( TextButton(
onClick = onNewSearch, onClick = onNewSearch,
modifier = Modifier modifier = Modifier

View File

@ -70,7 +70,7 @@ import tachiyomi.presentation.core.util.runOnEnterKeyPressed
import tachiyomi.presentation.core.util.secondaryItemAlpha import tachiyomi.presentation.core.util.secondaryItemAlpha
@Composable @Composable
fun TrackServiceSearch( fun TrackerSearch(
query: TextFieldValue, query: TextFieldValue,
onQueryChange: (TextFieldValue) -> Unit, onQueryChange: (TextFieldValue) -> Unit,
onDispatchQuery: () -> Unit, onDispatchQuery: () -> Unit,

View File

@ -12,12 +12,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp 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 import tachiyomi.presentation.core.util.clickableNoIndication
@Composable @Composable
fun TrackLogoIcon( fun TrackLogoIcon(
service: TrackService, tracker: Tracker,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
) { ) {
val modifier = if (onClick != null) { val modifier = if (onClick != null) {
@ -29,13 +29,13 @@ fun TrackLogoIcon(
Box( Box(
modifier = modifier modifier = modifier
.size(48.dp) .size(48.dp)
.background(color = Color(service.getLogoColor()), shape = MaterialTheme.shapes.medium) .background(color = Color(tracker.getLogoColor()), shape = MaterialTheme.shapes.medium)
.padding(4.dp), .padding(4.dp),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
Image( Image(
painter = painterResource(service.getLogo()), painter = painterResource(tracker.getLogo()),
contentDescription = service.name, contentDescription = tracker.name,
) )
} }
} }

View File

@ -19,7 +19,7 @@ import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.data.download.DownloadProvider
import eu.kanade.tachiyomi.data.saver.ImageSaver 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.extension.ExtensionManager
import eu.kanade.tachiyomi.network.JavaScriptEngine import eu.kanade.tachiyomi.network.JavaScriptEngine
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
@ -132,7 +132,7 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { DownloadManager(app) } addSingletonFactory { DownloadManager(app) }
addSingletonFactory { DownloadCache(app) } addSingletonFactory { DownloadCache(app) }
addSingletonFactory { TrackManager(app) } addSingletonFactory { TrackerManager() }
addSingletonFactory { DelayedTrackingStore(app) } addSingletonFactory { DelayedTrackingStore(app) }
addSingletonFactory { ImageSaver(app) } addSingletonFactory { ImageSaver(app) }

View File

@ -9,7 +9,7 @@ import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.core.security.SecurityPreferences import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.data.backup.BackupCreateJob import eu.kanade.tachiyomi.data.backup.BackupCreateJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob 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.NetworkPreferences
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
@ -47,7 +47,7 @@ object Migrations {
libraryPreferences: LibraryPreferences, libraryPreferences: LibraryPreferences,
readerPreferences: ReaderPreferences, readerPreferences: ReaderPreferences,
backupPreferences: BackupPreferences, backupPreferences: BackupPreferences,
trackManager: TrackManager, trackerManager: TrackerManager,
): Boolean { ): Boolean {
val lastVersionCode = preferenceStore.getInt("last_version_code", 0) val lastVersionCode = preferenceStore.getInt("last_version_code", 0)
val oldVersion = lastVersionCode.get() val oldVersion = lastVersionCode.get()
@ -135,8 +135,8 @@ object Migrations {
// Force MAL log out due to login flow change // Force MAL log out due to login flow change
// v52: switched from scraping to WebView // v52: switched from scraping to WebView
// v53: switched from WebView to OAuth // v53: switched from WebView to OAuth
if (trackManager.myAnimeList.isLoggedIn) { if (trackerManager.myAnimeList.isLoggedIn) {
trackManager.myAnimeList.logout() trackerManager.myAnimeList.logout()
context.toast(R.string.myanimelist_relogin) context.toast(R.string.myanimelist_relogin)
} }
} }
@ -342,7 +342,7 @@ object Migrations {
"pref_filter_library_started", "pref_filter_library_started",
"pref_filter_library_bookmarked", "pref_filter_library_bookmarked",
"pref_filter_library_completed", "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 -> prefKeys.forEach { key ->
val pref = preferenceStore.getInt(key, 0) val pref = preferenceStore.getInt(key, 0)

View File

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.backup
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.R 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 eu.kanade.tachiyomi.util.BackupUtil
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -11,7 +11,7 @@ import uy.kohesive.injekt.api.get
class BackupFileValidator( class BackupFileValidator(
private val sourceManager: SourceManager = Injekt.get(), 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 } .map { it.syncId }
.distinct() .distinct()
val missingTrackers = trackers val missingTrackers = trackers
.mapNotNull { trackManager.getService(it.toLong()) } .mapNotNull { trackerManager.get(it.toLong()) }
.filter { !it.isLoggedIn } .filter { !it.isLoggedIn }
.map { it.name } .map { it.name }
.sorted() .sorted()

View File

@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.data.track
import eu.kanade.tachiyomi.data.database.models.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 suspend fun delete(track: Track): Track
} }

View File

@ -6,31 +6,32 @@ import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.model.Track import tachiyomi.domain.track.model.Track
/** /**
* An Enhanced Track Service will never prompt the user to match a manga with the remote. * A tracker that will never prompt the user to manually bind an entry.
* It is expected that such Track Service can only work with specific sources and unique IDs. * 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 { fun accept(source: Source): Boolean {
return source::class.qualifiedName in getAcceptedSources() 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 getAcceptedSources(): List<String>
fun loginNoop() 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? 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 fun isTrackFrom(track: Track, manga: Manga, source: Source?): Boolean

View File

@ -5,7 +5,7 @@ import androidx.annotation.CallSuper
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes 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.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.track.service.TrackPreferences
@ -28,7 +28,7 @@ import uy.kohesive.injekt.injectLazy
import java.time.ZoneOffset import java.time.ZoneOffset
import tachiyomi.domain.track.model.Track as DomainTrack 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 trackPreferences: TrackPreferences by injectLazy()
val networkService: NetworkHelper by injectLazy() val networkService: NetworkHelper by injectLazy()
@ -83,7 +83,7 @@ abstract class TrackService(val id: Long, val name: String) {
@CallSuper @CallSuper
open fun logout() { open fun logout() {
trackPreferences.setTrackCredentials(this, "", "") trackPreferences.setCredentials(this, "", "")
} }
open val isLoggedIn: Boolean open val isLoggedIn: Boolean
@ -95,7 +95,7 @@ abstract class TrackService(val id: Long, val name: String) {
fun getPassword() = trackPreferences.trackPassword(this).get() fun getPassword() = trackPreferences.trackPassword(this).get()
fun saveCredentials(username: String, password: String) { 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 // 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) insertTrack.await(track)
// TODO: merge into SyncChaptersWithTrackServiceTwoWay? // TODO: merge into [SyncChapterProgressWithTrack]?
// Update chapter progress if newer chapters marked read locally // Update chapter progress if newer chapters marked read locally
if (hasReadChapters) { if (hasReadChapters) {
val latestLocalReadChapterNumber = allChapters 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) { } catch (e: Throwable) {
withUIContext { Injekt.get<Application>().toast(e.message) } withUIContext { Injekt.get<Application>().toast(e.message) }

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track package eu.kanade.tachiyomi.data.track
import android.content.Context
import eu.kanade.tachiyomi.data.track.anilist.Anilist import eu.kanade.tachiyomi.data.track.anilist.Anilist
import eu.kanade.tachiyomi.data.track.bangumi.Bangumi import eu.kanade.tachiyomi.data.track.bangumi.Bangumi
import eu.kanade.tachiyomi.data.track.kavita.Kavita 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.shikimori.Shikimori
import eu.kanade.tachiyomi.data.track.suwayomi.Suwayomi import eu.kanade.tachiyomi.data.track.suwayomi.Suwayomi
class TrackManager(context: Context) { class TrackerManager {
companion object { companion object {
const val MYANIMELIST = 1L
const val ANILIST = 2L const val ANILIST = 2L
const val KITSU = 3L 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 KAVITA = 8L
const val SUWAYOMI = 9L
} }
val myAnimeList = MyAnimeList(MYANIMELIST) val myAnimeList = MyAnimeList(1L)
val aniList = Anilist(ANILIST) val aniList = Anilist(ANILIST)
val kitsu = Kitsu(KITSU) val kitsu = Kitsu(KITSU)
val shikimori = Shikimori(SHIKIMORI) val shikimori = Shikimori(4L)
val bangumi = Bangumi(BANGUMI) val bangumi = Bangumi(5L)
val komga = Komga(KOMGA) val komga = Komga(6L)
val mangaUpdates = MangaUpdates(MANGA_UPDATES) val mangaUpdates = MangaUpdates(7L)
val kavita = Kavita(context, KAVITA) val kavita = Kavita(KAVITA)
val suwayomi = Suwayomi(SUWAYOMI) 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 }
} }

View File

@ -4,15 +4,15 @@ import android.graphics.Color
import androidx.annotation.StringRes import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.DeletableTrackService import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import tachiyomi.domain.track.model.Track as DomainTrack 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 { companion object {
const val READING = 1 const val READING = 1

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.track.anilist
import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.data.database.models.Track 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 eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -20,7 +20,7 @@ data class ALManga(
val total_chapters: Int, val total_chapters: Int,
) { ) {
fun toTrack() = TrackSearch.create(TrackManager.ANILIST).apply { fun toTrack() = TrackSearch.create(TrackerManager.ANILIST).apply {
media_id = this@ALManga.media_id media_id = this@ALManga.media_id
title = title_user_pref title = title_user_pref
total_chapters = this@ALManga.total_chapters total_chapters = this@ALManga.total_chapters
@ -50,7 +50,7 @@ data class ALUserManga(
val manga: ALManga, val manga: ALManga,
) { ) {
fun toTrack() = Track.create(TrackManager.ANILIST).apply { fun toTrack() = Track.create(TrackerManager.ANILIST).apply {
media_id = manga.media_id media_id = manga.media_id
title = manga.title_user_pref title = manga.title_user_pref
status = toTrackStatus() status = toTrackStatus()
@ -62,7 +62,7 @@ data class ALUserManga(
total_chapters = manga.total_chapters total_chapters = manga.total_chapters
} }
fun toTrackStatus() = when (list_status) { private fun toTrackStatus() = when (list_status) {
"CURRENT" -> Anilist.READING "CURRENT" -> Anilist.READING
"COMPLETED" -> Anilist.COMPLETED "COMPLETED" -> Anilist.COMPLETED
"PAUSED" -> Anilist.ON_HOLD "PAUSED" -> Anilist.ON_HOLD

View File

@ -4,19 +4,19 @@ import android.graphics.Color
import androidx.annotation.StringRes import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track 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 eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy 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 json: Json by injectLazy()
private val interceptor by lazy { BangumiInterceptor(this) } 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> { override fun getScoreList(): List<String> {
return IntRange(0, 10).map(Int::toString) return IntRange(0, 10).map(Int::toString)

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.track.bangumi
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track 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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
@ -26,7 +25,11 @@ import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder import java.net.URLEncoder
import java.nio.charset.StandardCharsets 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() private val json: Json by injectLazy()
@ -105,7 +108,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
} else { } else {
0 0
} }
return TrackSearch.create(TrackManager.BANGUMI).apply { return TrackSearch.create(trackId).apply {
media_id = obj["id"]!!.jsonPrimitive.long media_id = obj["id"]!!.jsonPrimitive.long
title = obj["name_cn"]!!.jsonPrimitive.content title = obj["name_cn"]!!.jsonPrimitive.content
cover_url = coverUrl cover_url = coverUrl

View File

@ -1,20 +1,21 @@
package eu.kanade.tachiyomi.data.track.kavita package eu.kanade.tachiyomi.data.track.kavita
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Color import android.graphics.Color
import androidx.annotation.StringRes import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.injectLazy
import java.security.MessageDigest import java.security.MessageDigest
import tachiyomi.domain.track.model.Track as DomainTrack 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 { companion object {
const val UNREAD = 1 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) } private val interceptor by lazy { KavitaInterceptor(this) }
val api by lazy { KavitaApi(client, interceptor) } val api by lazy { KavitaApi(client, interceptor) }
private val sourceManager: SourceManager by injectLazy()
override fun getLogo(): Int = R.drawable.ic_tracker_kavita override fun getLogo(): Int = R.drawable.ic_tracker_kavita
override fun getLogoColor() = Color.rgb(74, 198, 148) 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") 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 // By saving dummy, unused credentials, we can activate the tracker simply by login/logout
override fun loginNoop() { override fun loginNoop() {
saveCredentials("user", "pass") saveCredentials("user", "pass")
@ -110,28 +113,29 @@ class Kavita(private val context: Context, id: Long) : TrackService(id, "Kavita"
fun loadOAuth() { fun loadOAuth() {
val oauth = OAuth() val oauth = OAuth()
for (sourceId in 1..3) { for (id in 1..3) {
val authentication = oauth.authentications[sourceId - 1] val authentication = oauth.authentications[id - 1]
val sourceSuffixID by lazy { val sourceId by lazy {
val key = "kavita_$sourceId/all/1" // Hardcoded versionID to 1 val key = "kavita_$id/all/1" // Hardcoded versionID to 1
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) } (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }
.reduce(Long::or) and Long.MAX_VALUE .reduce(Long::or) and Long.MAX_VALUE
} }
val preferences: SharedPreferences by lazy { val preferences = (sourceManager.get(sourceId) as ConfigurableSource).getPreferences()
context.getSharedPreferences("source_$sourceSuffixID", Context.MODE_PRIVATE)
} val prefApiUrl = preferences.getString("APIURL", "")
val prefApiUrl = preferences.getString("APIURL", "")!! val prefApiKey = preferences.getString("APIKEY", "")
if (prefApiUrl.isEmpty()) { if (prefApiUrl.isNullOrEmpty() || prefApiKey.isNullOrEmpty()) {
// Source not configured. Skip // Source not configured. Skip
continue continue
} }
val prefApiKey = preferences.getString("APIKEY", "")!!
val token = api.getNewToken(apiUrl = prefApiUrl, apiKey = prefApiKey) val token = api.getNewToken(apiUrl = prefApiUrl, apiKey = prefApiKey)
if (token.isNullOrEmpty()) { if (token.isNullOrEmpty()) {
// Source is not accessible. Skip // Source is not accessible. Skip
continue continue
} }
authentication.apiUrl = prefApiUrl authentication.apiUrl = prefApiUrl
authentication.jwtToken = token.toString() authentication.jwtToken = token.toString()
} }

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.data.track.kavita 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 eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -22,7 +22,7 @@ data class SeriesDto(
val libraryId: Int, val libraryId: Int,
val libraryName: String? = "", val libraryName: String? = "",
) { ) {
fun toTrack(): TrackSearch = TrackSearch.create(TrackManager.KAVITA).also { fun toTrack(): TrackSearch = TrackSearch.create(TrackerManager.KAVITA).also {
it.title = name it.title = name
it.summary = "" it.summary = ""
} }

View File

@ -4,15 +4,15 @@ import android.graphics.Color
import androidx.annotation.StringRes import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.DeletableTrackService import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.DecimalFormat import java.text.DecimalFormat
class Kitsu(id: Long) : TrackService(id, "Kitsu"), DeletableTrackService { class Kitsu(id: Long) : Tracker(id, "Kitsu"), DeletableTracker {
companion object { companion object {
const val READING = 1 const val READING = 1

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.track.kitsu
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import eu.kanade.tachiyomi.data.database.models.Track 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 eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
@ -35,7 +35,7 @@ class KitsuSearchManga(obj: JsonObject) {
private val endDate = obj["endDate"]?.jsonPrimitive?.contentOrNull private val endDate = obj["endDate"]?.jsonPrimitive?.contentOrNull
@CallSuper @CallSuper
fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply { fun toTrack() = TrackSearch.create(TrackerManager.KITSU).apply {
media_id = this@KitsuSearchManga.id media_id = this@KitsuSearchManga.id
title = canonicalTitle title = canonicalTitle
total_chapters = chapterCount ?: 0 total_chapters = chapterCount ?: 0
@ -67,7 +67,7 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
private val ratingTwenty = obj["attributes"]!!.jsonObject["ratingTwenty"]?.jsonPrimitive?.contentOrNull private val ratingTwenty = obj["attributes"]!!.jsonObject["ratingTwenty"]?.jsonPrimitive?.contentOrNull
val progress = obj["attributes"]!!.jsonObject["progress"]!!.jsonPrimitive.int val progress = obj["attributes"]!!.jsonObject["progress"]!!.jsonPrimitive.int
fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply { fun toTrack() = TrackSearch.create(TrackerManager.KITSU).apply {
media_id = libraryId media_id = libraryId
title = canonicalTitle title = canonicalTitle
total_chapters = chapterCount ?: 0 total_chapters = chapterCount ?: 0

View File

@ -4,8 +4,8 @@ import android.graphics.Color
import androidx.annotation.StringRes import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import okhttp3.Dns import okhttp3.Dns
@ -13,7 +13,7 @@ import okhttp3.OkHttpClient
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.model.Track as DomainTrack 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 { companion object {
const val UNREAD = 1 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 .dns(Dns.SYSTEM) // don't use DNS over HTTPS as it breaks IP addressing
.build() .build()
val api by lazy { KomgaApi(client) } val api by lazy { KomgaApi(id, client) }
override fun getLogo() = R.drawable.ic_tracker_komga override fun getLogo() = R.drawable.ic_tracker_komga
@ -85,7 +85,7 @@ class Komga(id: Long) : TrackService(id, "Komga"), EnhancedTrackService {
saveCredentials("user", "pass") 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 // By saving dummy, unused credentials, we can activate the tracker simply by login/logout
override fun loginNoop() { override fun loginNoop() {
saveCredentials("user", "pass") saveCredentials("user", "pass")

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.data.track.komga package eu.kanade.tachiyomi.data.track.komga
import eu.kanade.tachiyomi.data.database.models.Track 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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.awaitSuccess
@ -19,7 +18,10 @@ import uy.kohesive.injekt.injectLazy
private const val READLIST_API = "/api/v1/readlists" 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() private val json: Json by injectLazy()
@ -85,13 +87,13 @@ class KomgaApi(private val client: OkHttpClient) {
return getTrackSearch(track.tracking_url) 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.title = metadata.title
it.summary = metadata.summary it.summary = metadata.summary
it.publishing_status = metadata.status 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 it.title = name
} }
} }

View File

@ -4,13 +4,13 @@ import android.graphics.Color
import androidx.annotation.StringRes import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.DeletableTrackService import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
import eu.kanade.tachiyomi.data.track.model.TrackSearch 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 { companion object {
const val READING_LIST = 0 const val READING_LIST = 0

View File

@ -4,14 +4,14 @@ import android.graphics.Color
import androidx.annotation.StringRes import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.DeletableTrackService import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class MyAnimeList(id: Long) : TrackService(id, "MyAnimeList"), DeletableTrackService { class MyAnimeList(id: Long) : Tracker(id, "MyAnimeList"), DeletableTracker {
companion object { companion object {
const val READING = 1 const val READING = 1
@ -28,7 +28,7 @@ class MyAnimeList(id: Long) : TrackService(id, "MyAnimeList"), DeletableTrackSer
private val json: Json by injectLazy() private val json: Json by injectLazy()
private val interceptor by lazy { MyAnimeListInterceptor(this, getPassword()) } 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 override val supportsReadingDates: Boolean = true

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.track.myanimelist
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track 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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
@ -32,7 +31,11 @@ import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale 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() private val json: Json by injectLazy()
@ -106,7 +109,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.parseAs<JsonObject>() .parseAs<JsonObject>()
.let { .let {
val obj = it.jsonObject val obj = it.jsonObject
TrackSearch.create(TrackManager.MYANIMELIST).apply { TrackSearch.create(trackId).apply {
media_id = obj["id"]!!.jsonPrimitive.long media_id = obj["id"]!!.jsonPrimitive.long
title = obj["title"]!!.jsonPrimitive.content title = obj["title"]!!.jsonPrimitive.content
summary = obj["synopsis"]?.jsonPrimitive?.content ?: "" summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""

View File

@ -4,14 +4,14 @@ import android.graphics.Color
import androidx.annotation.StringRes import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.DeletableTrackService import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class Shikimori(id: Long) : TrackService(id, "Shikimori"), DeletableTrackService { class Shikimori(id: Long) : Tracker(id, "Shikimori"), DeletableTracker {
companion object { companion object {
const val READING = 1 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 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> { override fun getScoreList(): List<String> {
return IntRange(0, 10).map(Int::toString) return IntRange(0, 10).map(Int::toString)

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.data.track.shikimori
import androidx.core.net.toUri import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track 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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.DELETE import eu.kanade.tachiyomi.network.DELETE
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
@ -28,7 +27,11 @@ import okhttp3.RequestBody.Companion.toRequestBody
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import uy.kohesive.injekt.injectLazy 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() private val json: Json by injectLazy()
@ -96,7 +99,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
} }
private fun jsonToSearch(obj: JsonObject): TrackSearch { private fun jsonToSearch(obj: JsonObject): TrackSearch {
return TrackSearch.create(TrackManager.SHIKIMORI).apply { return TrackSearch.create(trackId).apply {
media_id = obj["id"]!!.jsonPrimitive.long media_id = obj["id"]!!.jsonPrimitive.long
title = obj["name"]!!.jsonPrimitive.content title = obj["name"]!!.jsonPrimitive.content
total_chapters = obj["chapters"]!!.jsonPrimitive.int total_chapters = obj["chapters"]!!.jsonPrimitive.int
@ -110,7 +113,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
} }
private fun jsonToTrack(obj: JsonObject, mangas: JsonObject): Track { private fun jsonToTrack(obj: JsonObject, mangas: JsonObject): Track {
return Track.create(TrackManager.SHIKIMORI).apply { return Track.create(trackId).apply {
title = mangas["name"]!!.jsonPrimitive.content title = mangas["name"]!!.jsonPrimitive.content
media_id = obj["id"]!!.jsonPrimitive.long media_id = obj["id"]!!.jsonPrimitive.long
total_chapters = mangas["chapters"]!!.jsonPrimitive.int total_chapters = mangas["chapters"]!!.jsonPrimitive.int

View File

@ -4,16 +4,16 @@ import android.graphics.Color
import androidx.annotation.StringRes import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import tachiyomi.domain.manga.model.Manga as DomainManga import tachiyomi.domain.manga.model.Manga as DomainManga
import tachiyomi.domain.track.model.Track as DomainTrack 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 override fun getLogo() = R.drawable.ic_tracker_suwayomi

View File

@ -4,7 +4,6 @@ import android.app.Application
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import eu.kanade.tachiyomi.data.database.models.Track 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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
@ -24,7 +23,7 @@ import uy.kohesive.injekt.injectLazy
import java.nio.charset.Charset import java.nio.charset.Charset
import java.security.MessageDigest import java.security.MessageDigest
class TachideskApi { class SuwayomiApi(private val trackId: Long) {
private val network: NetworkHelper by injectLazy() private val network: NetworkHelper by injectLazy()
private val json: Json by injectLazy() private val json: Json by injectLazy()
@ -62,7 +61,7 @@ class TachideskApi {
.parseAs<MangaDataClass>() .parseAs<MangaDataClass>()
} }
TrackSearch.create(TrackManager.SUWAYOMI).apply { TrackSearch.create(trackId).apply {
title = manga.title title = manga.title
cover_url = "$url/thumbnail" cover_url = "$url/thumbnail"
summary = manga.description.orEmpty() summary = manga.description.orEmpty()
@ -101,26 +100,24 @@ class TachideskApi {
return getTrackSearch(track.tracking_url) return getTrackSearch(track.tracking_url)
} }
private val tachideskExtensionId by lazy { private val sourceId by lazy {
val key = "tachidesk/en/1" val key = "tachidesk/en/1"
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) 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 (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 { 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 getPrefBaseUrl(): String = preferences.getString(ADDRESS_TITLE, ADDRESS_DEFAULT)!!
private fun getPrefBaseLogin(): String = preferences.getString(LOGIN_TITLE, LOGIN_DEFAULT)!! private fun getPrefBaseLogin(): String = preferences.getString(LOGIN_TITLE, LOGIN_DEFAULT)!!
private fun getPrefBasePassword(): String = preferences.getString(PASSWORD_TITLE, PASSWORD_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 = ""

View File

@ -35,8 +35,8 @@ import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags
@ -177,7 +177,7 @@ internal class MigrateDialogScreenModel(
} }
private val enhancedServices by lazy { private val enhancedServices by lazy {
Injekt.get<TrackManager>().services.filterIsInstance<EnhancedTrackService>() Injekt.get<TrackerManager>().trackers.filterIsInstance<EnhancedTracker>()
} }
suspend fun migrateManga( suspend fun migrateManga(

View File

@ -15,16 +15,12 @@ import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.core.preference.asState import eu.kanade.core.preference.asState
import eu.kanade.domain.base.BasePreferences 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.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDomainManga import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.source.service.SourcePreferences 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.presentation.util.ioCoroutineScope
import eu.kanade.tachiyomi.data.cache.CoverCache 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.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
@ -37,11 +33,9 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.preference.CheckboxState import tachiyomi.core.preference.CheckboxState
import tachiyomi.core.preference.mapAsCheckboxState import tachiyomi.core.preference.mapAsCheckboxState
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.SetMangaCategories import tachiyomi.domain.category.interactor.SetMangaCategories
import tachiyomi.domain.category.model.Category 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.manga.model.toMangaUpdate
import tachiyomi.domain.source.interactor.GetRemoteManga import tachiyomi.domain.source.interactor.GetRemoteManga
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.InsertTrack
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.util.Date
@ -76,12 +69,9 @@ class BrowseSourceScreenModel(
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(), private val addTracks: AddTracks = Injekt.get(),
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack = Injekt.get(),
) : StateScreenModel<BrowseSourceScreenModel.State>(State(Listing.valueOf(listingQuery))) { ) : 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) var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
val source = sourceManager.getOrStub(sourceId) val source = sourceManager.getOrStub(sourceId)
@ -243,8 +233,7 @@ class BrowseSourceScreenModel(
new = new.removeCovers(coverCache) new = new.removeCovers(coverCache)
} else { } else {
setMangaDefaultChapterFlags.await(manga) setMangaDefaultChapterFlags.await(manga)
addTracks.bindEnhancedTracks(manga, source)
autoAddTrack(manga)
} }
updateManga.await(new.toMangaUpdate()) 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. * Get user categories.
* *

View File

@ -23,7 +23,7 @@ import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.download.DownloadManager 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.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.chapter.getNextUnread import eu.kanade.tachiyomi.util.chapter.getNextUnread
@ -88,7 +88,7 @@ class LibraryScreenModel(
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(),
private val downloadCache: DownloadCache = Injekt.get(), private val downloadCache: DownloadCache = Injekt.get(),
private val trackManager: TrackManager = Injekt.get(), private val trackerManager: TrackerManager = Injekt.get(),
) : StateScreenModel<LibraryScreenModel.State>(State()) { ) : StateScreenModel<LibraryScreenModel.State>(State()) {
var activeCategoryIndex: Int by libraryPreferences.lastUsedCategory().asState(coroutineScope) var activeCategoryIndex: Int by libraryPreferences.lastUsedCategory().asState(coroutineScope)
@ -101,9 +101,9 @@ class LibraryScreenModel(
getTracksPerManga.subscribe(), getTracksPerManga.subscribe(),
getTrackingFilterFlow(), getTrackingFilterFlow(),
downloadCache.changes, downloadCache.changes,
) { searchQuery, library, tracks, loggedInTrackServices, _ -> ) { searchQuery, library, tracks, loggedInTrackers, _ ->
library library
.applyFilters(tracks, loggedInTrackServices) .applyFilters(tracks, loggedInTrackers)
.applySort() .applySort()
.mapValues { (_, value) -> .mapValues { (_, value) ->
if (searchQuery != null) { if (searchQuery != null) {
@ -169,7 +169,7 @@ class LibraryScreenModel(
*/ */
private suspend fun LibraryMap.applyFilters( private suspend fun LibraryMap.applyFilters(
trackMap: Map<Long, List<Long>>, trackMap: Map<Long, List<Long>>,
loggedInTrackServices: Map<Long, TriState>, loggedInTrackers: Map<Long, TriState>,
): LibraryMap { ): LibraryMap {
val prefs = getLibraryItemPreferencesFlow().first() val prefs = getLibraryItemPreferencesFlow().first()
val downloadedOnly = prefs.globalFilterDownloaded val downloadedOnly = prefs.globalFilterDownloaded
@ -180,10 +180,10 @@ class LibraryScreenModel(
val filterBookmarked = prefs.filterBookmarked val filterBookmarked = prefs.filterBookmarked
val filterCompleted = prefs.filterCompleted 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 excludedTracks = loggedInTrackers.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 includedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null }
val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty() val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty()
val filterFnDownloaded: (LibraryItem) -> Boolean = { val filterFnDownloaded: (LibraryItem) -> Boolean = {
@ -366,14 +366,14 @@ class LibraryScreenModel(
* @return map of track id with the filter value * @return map of track id with the filter value
*/ */
private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> { private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
val loggedServices = trackManager.services.filter { it.isLoggedIn } val loggedInTrackers = trackerManager.trackers.filter { it.isLoggedIn }
return if (loggedServices.isNotEmpty()) { return if (loggedInTrackers.isNotEmpty()) {
val prefFlows = loggedServices val prefFlows = loggedInTrackers
.map { libraryPreferences.filterTracking(it.id.toInt()).changes() } .map { libraryPreferences.filterTracking(it.id.toInt()).changes() }
.toTypedArray() .toTypedArray()
combine(*prefFlows) { combine(*prefFlows) {
loggedServices loggedInTrackers
.mapIndexed { index, trackService -> trackService.id to it[index] } .mapIndexed { index, tracker -> tracker.id to it[index] }
.toMap() .toMap()
} }
} else { } else {

View File

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.library
import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.domain.base.BasePreferences 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.Preference
import tachiyomi.core.preference.TriState import tachiyomi.core.preference.TriState
import tachiyomi.core.preference.getAndSet import tachiyomi.core.preference.getAndSet
@ -22,11 +22,11 @@ class LibrarySettingsScreenModel(
val libraryPreferences: LibraryPreferences = Injekt.get(), val libraryPreferences: LibraryPreferences = Injekt.get(),
private val setDisplayMode: SetDisplayMode = Injekt.get(), private val setDisplayMode: SetDisplayMode = Injekt.get(),
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(), private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
private val trackManager: TrackManager = Injekt.get(), private val trackerManager: TrackerManager = Injekt.get(),
) : ScreenModel { ) : ScreenModel {
val trackServices val trackers
get() = trackManager.services.filter { it.isLoggedIn } get() = trackerManager.trackers.filter { it.isLoggedIn }
fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) { fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) {
preference(libraryPreferences).getAndSet { preference(libraryPreferences).getAndSet {

View File

@ -138,7 +138,7 @@ class MainActivity : BaseActivity() {
libraryPreferences = libraryPreferences, libraryPreferences = libraryPreferences,
readerPreferences = Injekt.get(), readerPreferences = Injekt.get(),
backupPreferences = Injekt.get(), backupPreferences = Injekt.get(),
trackManager = Injekt.get(), trackerManager = Injekt.get(),
) )
} else { } else {
false false

View File

@ -23,9 +23,9 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.network.HttpException import eu.kanade.tachiyomi.network.HttpException
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.ui.manga.track.TrackItem
@ -84,7 +84,7 @@ class MangaScreenModel(
private val libraryPreferences: LibraryPreferences = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(),
readerPreferences: ReaderPreferences = Injekt.get(), readerPreferences: ReaderPreferences = Injekt.get(),
uiPreferences: UiPreferences = 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 downloadManager: DownloadManager = Injekt.get(),
private val downloadCache: DownloadCache = Injekt.get(), private val downloadCache: DownloadCache = Injekt.get(),
private val getMangaAndChapters: GetMangaWithChapters = Injekt.get(), private val getMangaAndChapters: GetMangaWithChapters = Injekt.get(),
@ -105,7 +105,7 @@ class MangaScreenModel(
private val successState: State.Success? private val successState: State.Success?
get() = state.value as? 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? val manga: Manga?
get() = successState?.manga get() = successState?.manga
@ -317,14 +317,14 @@ class MangaScreenModel(
// Finally match with enhanced tracking when available // Finally match with enhanced tracking when available
val source = state.source val source = state.source
state.trackItems state.trackItems
.map { it.service } .map { it.tracker }
.filterIsInstance<EnhancedTrackService>() .filterIsInstance<EnhancedTracker>()
.filter { it.accept(source) } .filter { it.accept(source) }
.forEach { service -> .forEach { service ->
launchIO { launchIO {
try { try {
service.match(manga)?.let { track -> service.match(manga)?.let { track ->
(service as TrackService).register(track, mangaId) (service as Tracker).register(track, mangaId)
} }
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.WARN, e) { logcat(LogPriority.WARN, e) {
@ -949,11 +949,11 @@ class MangaScreenModel(
getTracks.subscribe(manga.id) getTracks.subscribe(manga.id)
.catch { logcat(LogPriority.ERROR, it) } .catch { logcat(LogPriority.ERROR, it) }
.map { tracks -> .map { tracks ->
loggedServices loggedInTrackers
// Map to TrackItem // Map to TrackItem
.map { service -> TrackItem(tracks.find { it.syncId == service.id }, service) } .map { service -> TrackItem(tracks.find { it.syncId == service.id }, service) }
// Show only if the service supports this manga's source // 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() .distinctUntilChanged()
.collectLatest { trackItems -> .collectLatest { trackItems ->

View File

@ -46,14 +46,14 @@ import eu.kanade.presentation.track.TrackChapterSelector
import eu.kanade.presentation.track.TrackDateSelector import eu.kanade.presentation.track.TrackDateSelector
import eu.kanade.presentation.track.TrackInfoDialogHome import eu.kanade.presentation.track.TrackInfoDialogHome
import eu.kanade.presentation.track.TrackScoreSelector import eu.kanade.presentation.track.TrackScoreSelector
import eu.kanade.presentation.track.TrackServiceSearch
import eu.kanade.presentation.track.TrackStatusSelector import eu.kanade.presentation.track.TrackStatusSelector
import eu.kanade.presentation.track.TrackerSearch
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.DeletableTrackService import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.openInBrowser
@ -105,7 +105,7 @@ data class TrackInfoDialogHomeScreen(
navigator.push( navigator.push(
TrackStatusSelectorScreen( TrackStatusSelectorScreen(
track = it.track!!, track = it.track!!,
serviceId = it.service.id, serviceId = it.tracker.id,
), ),
) )
}, },
@ -113,7 +113,7 @@ data class TrackInfoDialogHomeScreen(
navigator.push( navigator.push(
TrackChapterSelectorScreen( TrackChapterSelectorScreen(
track = it.track!!, track = it.track!!,
serviceId = it.service.id, serviceId = it.tracker.id,
), ),
) )
}, },
@ -121,7 +121,7 @@ data class TrackInfoDialogHomeScreen(
navigator.push( navigator.push(
TrackScoreSelectorScreen( TrackScoreSelectorScreen(
track = it.track!!, track = it.track!!,
serviceId = it.service.id, serviceId = it.tracker.id,
), ),
) )
}, },
@ -129,7 +129,7 @@ data class TrackInfoDialogHomeScreen(
navigator.push( navigator.push(
TrackDateSelectorScreen( TrackDateSelectorScreen(
track = it.track!!, track = it.track!!,
serviceId = it.service.id, serviceId = it.tracker.id,
start = true, start = true,
), ),
) )
@ -138,21 +138,21 @@ data class TrackInfoDialogHomeScreen(
navigator.push( navigator.push(
TrackDateSelectorScreen( TrackDateSelectorScreen(
track = it.track!!, track = it.track!!,
serviceId = it.service.id, serviceId = it.tracker.id,
start = false, start = false,
), ),
) )
}, },
onNewSearch = { onNewSearch = {
if (it.service is EnhancedTrackService) { if (it.tracker is EnhancedTracker) {
sm.registerEnhancedTracking(it) sm.registerEnhancedTracking(it)
} else { } else {
navigator.push( navigator.push(
TrackServiceSearchScreen( TrackerSearchScreen(
mangaId = mangaId, mangaId = mangaId,
initialQuery = it.track?.title ?: mangaTitle, initialQuery = it.track?.title ?: mangaTitle,
currentUrl = it.track?.remoteUrl, currentUrl = it.track?.remoteUrl,
serviceId = it.service.id, serviceId = it.tracker.id,
), ),
) )
} }
@ -160,10 +160,10 @@ data class TrackInfoDialogHomeScreen(
onOpenInBrowser = { openTrackerInBrowser(context, it) }, onOpenInBrowser = { openTrackerInBrowser(context, it) },
onRemoved = { onRemoved = {
navigator.push( navigator.push(
TrackServiceRemoveScreen( TrackerRemoveScreen(
mangaId = mangaId, mangaId = mangaId,
track = it.track!!, track = it.track!!,
serviceId = it.service.id, serviceId = it.tracker.id,
), ),
) )
}, },
@ -201,12 +201,12 @@ data class TrackInfoDialogHomeScreen(
} }
fun registerEnhancedTracking(item: TrackItem) { fun registerEnhancedTracking(item: TrackItem) {
item.service as EnhancedTrackService item.tracker as EnhancedTracker
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
val manga = Injekt.get<GetManga>().await(mangaId) ?: return@launchNonCancellable val manga = Injekt.get<GetManga>().await(mangaId) ?: return@launchNonCancellable
try { try {
val matchResult = item.service.match(manga) ?: throw Exception() val matchResult = item.tracker.match(manga) ?: throw Exception()
item.service.register(matchResult, mangaId) item.tracker.register(matchResult, mangaId)
} catch (e: Exception) { } catch (e: Exception) {
withUIContext { Injekt.get<Application>().toast(R.string.error_no_match) } withUIContext { Injekt.get<Application>().toast(R.string.error_no_match) }
} }
@ -236,13 +236,13 @@ data class TrackInfoDialogHomeScreen(
} }
private fun List<Track>.mapToTrackItem(): List<TrackItem> { 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) val source = Injekt.get<SourceManager>().getOrStub(sourceId)
return loggedServices return loggedInTrackers
// Map to TrackItem // Map to TrackItem
.map { service -> TrackItem(find { it.syncId == service.id }, service) } .map { service -> TrackItem(find { it.syncId == service.id }, service) }
// Show only if the service supports this manga's source // 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 @Immutable
@ -263,7 +263,7 @@ private data class TrackStatusSelectorScreen(
val sm = rememberScreenModel { val sm = rememberScreenModel {
Model( Model(
track = track, track = track,
service = Injekt.get<TrackManager>().getService(serviceId)!!, tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
) )
} }
val state by sm.state.collectAsState() val state by sm.state.collectAsState()
@ -281,11 +281,11 @@ private data class TrackStatusSelectorScreen(
private class Model( private class Model(
private val track: Track, private val track: Track,
private val service: TrackService, private val tracker: Tracker,
) : StateScreenModel<Model.State>(State(track.status.toInt())) { ) : StateScreenModel<Model.State>(State(track.status.toInt())) {
fun getSelections(): Map<Int, Int?> { fun getSelections(): Map<Int, Int?> {
return service.getStatusList().associateWith { service.getStatus(it) } return tracker.getStatusList().associateWith { tracker.getStatus(it) }
} }
fun setSelection(selection: Int) { fun setSelection(selection: Int) {
@ -294,7 +294,7 @@ private data class TrackStatusSelectorScreen(
fun setStatus() { fun setStatus() {
coroutineScope.launchNonCancellable { 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 { val sm = rememberScreenModel {
Model( Model(
track = track, track = track,
service = Injekt.get<TrackManager>().getService(serviceId)!!, tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
) )
} }
val state by sm.state.collectAsState() val state by sm.state.collectAsState()
@ -335,7 +335,7 @@ private data class TrackChapterSelectorScreen(
private class Model( private class Model(
private val track: Track, private val track: Track,
private val service: TrackService, private val tracker: Tracker,
) : StateScreenModel<Model.State>(State(track.lastChapterRead.toInt())) { ) : StateScreenModel<Model.State>(State(track.lastChapterRead.toInt())) {
fun getRange(): Iterable<Int> { fun getRange(): Iterable<Int> {
@ -353,7 +353,7 @@ private data class TrackChapterSelectorScreen(
fun setChapter() { fun setChapter() {
coroutineScope.launchNonCancellable { 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 { val sm = rememberScreenModel {
Model( Model(
track = track, track = track,
service = Injekt.get<TrackManager>().getService(serviceId)!!, tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
) )
} }
val state by sm.state.collectAsState() val state by sm.state.collectAsState()
@ -394,11 +394,11 @@ private data class TrackScoreSelectorScreen(
private class Model( private class Model(
private val track: Track, private val track: Track,
private val service: TrackService, private val tracker: Tracker,
) : StateScreenModel<Model.State>(State(service.displayScore(track.toDbTrack()))) { ) : StateScreenModel<Model.State>(State(tracker.displayScore(track.toDbTrack()))) {
fun getSelections(): List<String> { fun getSelections(): List<String> {
return service.getScoreList() return tracker.getScoreList()
} }
fun setSelection(selection: String) { fun setSelection(selection: String) {
@ -407,7 +407,7 @@ private data class TrackScoreSelectorScreen(
fun setScore() { fun setScore() {
coroutineScope.launchNonCancellable { 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 { val sm = rememberScreenModel {
Model( Model(
track = track, track = track,
service = Injekt.get<TrackManager>().getService(serviceId)!!, tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
start = start, start = start,
) )
} }
@ -515,7 +515,7 @@ private data class TrackDateSelectorScreen(
private class Model( private class Model(
private val track: Track, private val track: Track,
private val service: TrackService, private val tracker: Tracker,
private val start: Boolean, private val start: Boolean,
) : ScreenModel { ) : ScreenModel {
@ -534,15 +534,15 @@ private data class TrackDateSelectorScreen(
val localMillis = millis.convertEpochMillisZone(ZoneOffset.UTC, ZoneOffset.systemDefault()) val localMillis = millis.convertEpochMillisZone(ZoneOffset.UTC, ZoneOffset.systemDefault())
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
if (start) { if (start) {
service.setRemoteStartDate(track.toDbTrack(), localMillis) tracker.setRemoteStartDate(track.toDbTrack(), localMillis)
} else { } else {
service.setRemoteFinishDate(track.toDbTrack(), localMillis) tracker.setRemoteFinishDate(track.toDbTrack(), localMillis)
} }
} }
} }
fun confirmRemoveDate(navigator: Navigator) { 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 { val sm = rememberScreenModel {
Model( Model(
track = track, track = track,
service = Injekt.get<TrackManager>().getService(serviceId)!!, tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
start = start, start = start,
) )
} }
@ -614,25 +614,25 @@ private data class TrackDateRemoverScreen(
private class Model( private class Model(
private val track: Track, private val track: Track,
private val service: TrackService, private val tracker: Tracker,
private val start: Boolean, private val start: Boolean,
) : ScreenModel { ) : ScreenModel {
fun getServiceName() = service.name fun getServiceName() = tracker.name
fun removeDate() { fun removeDate() {
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
if (start) { if (start) {
service.setRemoteStartDate(track.toDbTrack(), 0) tracker.setRemoteStartDate(track.toDbTrack(), 0)
} else { } else {
service.setRemoteFinishDate(track.toDbTrack(), 0) tracker.setRemoteFinishDate(track.toDbTrack(), 0)
} }
} }
} }
} }
} }
data class TrackServiceSearchScreen( data class TrackerSearchScreen(
private val mangaId: Long, private val mangaId: Long,
private val initialQuery: String, private val initialQuery: String,
private val currentUrl: String?, private val currentUrl: String?,
@ -647,14 +647,14 @@ data class TrackServiceSearchScreen(
mangaId = mangaId, mangaId = mangaId,
currentUrl = currentUrl, currentUrl = currentUrl,
initialQuery = initialQuery, initialQuery = initialQuery,
service = Injekt.get<TrackManager>().getService(serviceId)!!, tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
) )
} }
val state by sm.state.collectAsState() val state by sm.state.collectAsState()
var textFieldValue by remember { mutableStateOf(TextFieldValue(initialQuery)) } var textFieldValue by remember { mutableStateOf(TextFieldValue(initialQuery)) }
TrackServiceSearch( TrackerSearch(
query = textFieldValue, query = textFieldValue,
onQueryChange = { textFieldValue = it }, onQueryChange = { textFieldValue = it },
onDispatchQuery = { sm.trackingSearch(textFieldValue.text) }, onDispatchQuery = { sm.trackingSearch(textFieldValue.text) },
@ -673,7 +673,7 @@ data class TrackServiceSearchScreen(
private val mangaId: Long, private val mangaId: Long,
private val currentUrl: String? = null, private val currentUrl: String? = null,
initialQuery: String, initialQuery: String,
private val service: TrackService, private val tracker: Tracker,
) : StateScreenModel<Model.State>(State()) { ) : StateScreenModel<Model.State>(State()) {
init { init {
@ -690,7 +690,7 @@ data class TrackServiceSearchScreen(
val result = withIOContext { val result = withIOContext {
try { try {
val results = service.search(query) val results = tracker.search(query)
Result.success(results) Result.success(results)
} catch (e: Throwable) { } catch (e: Throwable) {
Result.failure(e) Result.failure(e)
@ -706,7 +706,7 @@ data class TrackServiceSearchScreen(
} }
fun registerTracking(item: TrackSearch) { fun registerTracking(item: TrackSearch) {
coroutineScope.launchNonCancellable { service.register(item, mangaId) } coroutineScope.launchNonCancellable { tracker.register(item, mangaId) }
} }
fun updateSelection(selected: TrackSearch) { 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 mangaId: Long,
private val track: Track, private val track: Track,
private val serviceId: Long, private val serviceId: Long,
@ -734,10 +734,10 @@ private data class TrackServiceRemoveScreen(
Model( Model(
mangaId = mangaId, mangaId = mangaId,
track = track, 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) } var removeRemoteTrack by remember { mutableStateOf(false) }
AlertDialogContent( AlertDialogContent(
modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars), modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars),
@ -758,7 +758,7 @@ private data class TrackServiceRemoveScreen(
Text( Text(
text = stringResource(R.string.track_delete_text, serviceName), text = stringResource(R.string.track_delete_text, serviceName),
) )
if (sm.isServiceDeletable()) { if (sm.isDeletable()) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = removeRemoteTrack, onCheckedChange = { removeRemoteTrack = it }) Checkbox(checked = removeRemoteTrack, onCheckedChange = { removeRemoteTrack = it })
Text(text = stringResource(R.string.track_delete_remote_text, serviceName)) Text(text = stringResource(R.string.track_delete_remote_text, serviceName))
@ -798,17 +798,17 @@ private data class TrackServiceRemoveScreen(
private class Model( private class Model(
private val mangaId: Long, private val mangaId: Long,
private val track: Track, private val track: Track,
private val service: TrackService, private val tracker: Tracker,
private val deleteTrack: DeleteTrack = Injekt.get(), private val deleteTrack: DeleteTrack = Injekt.get(),
) : ScreenModel { ) : ScreenModel {
fun getServiceName() = service.name fun getName() = tracker.name
fun isServiceDeletable() = service is DeletableTrackService fun isDeletable() = tracker is DeletableTracker
fun deleteMangaFromService() { fun deleteMangaFromService() {
coroutineScope.launchNonCancellable { coroutineScope.launchNonCancellable {
(service as DeletableTrackService).delete(track.toDbTrack()) (tracker as DeletableTracker).delete(track.toDbTrack())
} }
} }

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.ui.manga.track 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 import tachiyomi.domain.track.model.Track
data class TrackItem(val track: Track?, val service: TrackService) data class TrackItem(val track: Track?, val tracker: Tracker)

View File

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.setting.track
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle 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.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.view.setComposeContent import eu.kanade.tachiyomi.util.view.setComposeContent
@ -12,7 +12,7 @@ import uy.kohesive.injekt.injectLazy
abstract class BaseOAuthLoginActivity : BaseActivity() { abstract class BaseOAuthLoginActivity : BaseActivity() {
internal val trackManager: TrackManager by injectLazy() internal val trackerManager: TrackerManager by injectLazy()
abstract fun handleResult(data: Uri?) abstract fun handleResult(data: Uri?)

View File

@ -20,11 +20,11 @@ class TrackLoginActivity : BaseOAuthLoginActivity() {
val matchResult = regex.find(data.fragment.toString()) val matchResult = regex.find(data.fragment.toString())
if (matchResult?.groups?.get(1) != null) { if (matchResult?.groups?.get(1) != null) {
lifecycleScope.launchIO { lifecycleScope.launchIO {
trackManager.aniList.login(matchResult.groups[1]!!.value) trackerManager.aniList.login(matchResult.groups[1]!!.value)
returnToSettings() returnToSettings()
} }
} else { } else {
trackManager.aniList.logout() trackerManager.aniList.logout()
returnToSettings() returnToSettings()
} }
} }
@ -33,11 +33,11 @@ class TrackLoginActivity : BaseOAuthLoginActivity() {
val code = data.getQueryParameter("code") val code = data.getQueryParameter("code")
if (code != null) { if (code != null) {
lifecycleScope.launchIO { lifecycleScope.launchIO {
trackManager.bangumi.login(code) trackerManager.bangumi.login(code)
returnToSettings() returnToSettings()
} }
} else { } else {
trackManager.bangumi.logout() trackerManager.bangumi.logout()
returnToSettings() returnToSettings()
} }
} }
@ -46,11 +46,11 @@ class TrackLoginActivity : BaseOAuthLoginActivity() {
val code = data.getQueryParameter("code") val code = data.getQueryParameter("code")
if (code != null) { if (code != null) {
lifecycleScope.launchIO { lifecycleScope.launchIO {
trackManager.myAnimeList.login(code) trackerManager.myAnimeList.login(code)
returnToSettings() returnToSettings()
} }
} else { } else {
trackManager.myAnimeList.logout() trackerManager.myAnimeList.logout()
returnToSettings() returnToSettings()
} }
} }
@ -59,11 +59,11 @@ class TrackLoginActivity : BaseOAuthLoginActivity() {
val code = data.getQueryParameter("code") val code = data.getQueryParameter("code")
if (code != null) { if (code != null) {
lifecycleScope.launchIO { lifecycleScope.launchIO {
trackManager.shikimori.login(code) trackerManager.shikimori.login(code)
returnToSettings() returnToSettings()
} }
} else { } else {
trackManager.shikimori.logout() trackerManager.shikimori.logout()
returnToSettings() returnToSettings()
} }
} }

View File

@ -10,7 +10,7 @@ import eu.kanade.core.util.fastMapNotNull
import eu.kanade.presentation.more.stats.StatsScreenState import eu.kanade.presentation.more.stats.StatsScreenState
import eu.kanade.presentation.more.stats.data.StatsData import eu.kanade.presentation.more.stats.data.StatsData
import eu.kanade.tachiyomi.data.download.DownloadManager 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.model.SManga
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
@ -33,10 +33,10 @@ class StatsScreenModel(
private val getTotalReadDuration: GetTotalReadDuration = Injekt.get(), private val getTotalReadDuration: GetTotalReadDuration = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(), private val getTracks: GetTracks = Injekt.get(),
private val preferences: LibraryPreferences = Injekt.get(), private val preferences: LibraryPreferences = Injekt.get(),
private val trackManager: TrackManager = Injekt.get(), private val trackerManager: TrackerManager = Injekt.get(),
) : StateScreenModel<StatsScreenState>(StatsScreenState.Loading) { ) : 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 { init {
coroutineScope.launchIO { coroutineScope.launchIO {
@ -72,7 +72,7 @@ class StatsScreenModel(
val trackersStatData = StatsData.Trackers( val trackersStatData = StatsData.Trackers(
trackedTitleCount = mangaTrackMap.count { it.value.isNotEmpty() }, trackedTitleCount = mangaTrackMap.count { it.value.isNotEmpty() },
meanScore = meanScore, meanScore = meanScore,
trackerCount = loggedServices.size, trackerCount = loggedInTrackers.size,
) )
mutableState.update { mutableState.update {
@ -115,10 +115,10 @@ class StatsScreenModel(
} }
private suspend fun getMangaTrackMap(libraryManga: List<LibraryManga>): Map<Long, List<Track>> { 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 -> return libraryManga.associate { manga ->
val tracks = getTracks.await(manga.id) val tracks = getTracks.await(manga.id)
.fastFilter { it.syncId in loggedServicesIds } .fastFilter { it.syncId in loggedInTrackerIds }
manga.id to tracks manga.id to tracks
} }
@ -144,7 +144,7 @@ class StatsScreenModel(
} }
private fun get10PointScore(track: Track): Double { private fun get10PointScore(track: Track): Double {
val service = trackManager.getService(track.syncId)!! val service = trackerManager.get(track.syncId)!!
return service.get10PointScore(track) return service.get10PointScore(track)
} }
} }

View File

@ -459,13 +459,13 @@
<!-- Tracking section --> <!-- Tracking section -->
<string name="tracking_guide">Tracking guide</string> <string name="tracking_guide">Tracking guide</string>
<string name="pref_auto_update_manga_sync">Update progress after reading</string> <string name="pref_auto_update_manga_sync">Update progress after reading</string>
<string name="services">Services</string> <string name="services">Trackers</string>
<string name="tracking_info">One-way sync to update the chapter progress in tracking services. Set up tracking for individual entries from their tracking button.</string> <string name="tracking_info">One-way sync to update the chapter progress in external tracker services. Set up tracking for individual entries from their tracking button.</string>
<string name="enhanced_services">Enhanced services</string> <string name="enhanced_services">Enhanced trackers</string>
<string name="enhanced_services_not_installed">Available but source not installed: %s</string> <string name="enhanced_services_not_installed">Available but source not installed: %s</string>
<string name="enhanced_tracking_info">Services that provide enhanced features for specific sources. Entries are automatically tracked when added to your library.</string> <string name="enhanced_tracking_info">Provides enhanced features for specific sources. Entries are automatically tracked when added to your library.</string>
<string name="action_track">Track</string> <string name="action_track">Track</string>
<string name="track_activity_name">Tracking login</string> <string name="track_activity_name">Tracker login</string>
<!-- Browse section --> <!-- Browse section -->
<string name="pref_hide_in_library_items">Hide entries already in library</string> <string name="pref_hide_in_library_items">Hide entries already in library</string>