feat(tracker): add support for EnhancedTracker to merged sources (#932)

* fix: add support for `EnhancedTracker` to merged sources

* ref: readability

* fix: use only sources specific to the merged title

* fix: don't match using name

* formatting

* don't block, suspend

* Move merged source out of EnhancedTracker

* Extract Injekt

* Return first accepted merged manga

* Move getMergedSources to SourceManager and update usages accordingly

* simplify code

---------

Co-authored-by: Cuong-Tran <cuongtran.tm@gmail.com>
Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com>
This commit is contained in:
David Brochero 2026-02-02 02:17:23 -03:00 committed by GitHub
parent 8f57f4a5a0
commit c7e0585c6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 76 additions and 14 deletions

View file

@ -18,6 +18,15 @@ interface EnhancedTracker {
return source::class.qualifiedName in getAcceptedSources()
}
// KMK -->
/**
* This tracker will only work with the sources that are accepted by this filter function.
*/
fun accept(sources: List<Source>): Boolean {
return sources.any { accept(it) }
}
// KMK <--
/**
* Fully qualified source classes that this tracker is compatible with.
*/

View file

@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import tachiyomi.domain.manga.interactor.GetMergedReferencesById
import tachiyomi.domain.source.model.StubSource
import tachiyomi.domain.source.repository.StubSourceRepository
import tachiyomi.domain.source.service.SourceManager
@ -76,6 +77,9 @@ class AndroidSourceManager(
private val exhPreferences: ExhPreferences by injectLazy()
private val sourcePreferences: SourcePreferences by injectLazy()
// SY <--
// KMK -->
private val getMergedReferencesById: GetMergedReferencesById by injectLazy()
// KMK <--
init {
scope.launch {
@ -238,6 +242,14 @@ class AndroidSourceManager(
}
// SY <--
// KMK -->
override suspend fun getMergedSources(mangaId: Long): List<Source> {
val sources = getMergedReferencesById.await(mangaId)
return sources.distinctBy { it.mangaSourceId }
.map { getOrStub(it.mangaSourceId) }
}
// KMK <--
private fun registerStubSource(source: StubSource) {
scope.launch {
val dbSource = sourceRepository.getStubSource(source.id)

View file

@ -1823,7 +1823,16 @@ class MangaScreenModel(
trackerManager.loggedInTrackersFlow(),
) { mangaTracks, loggedInTrackers ->
// Show only if the service supports this manga's source
val supportedTrackers = loggedInTrackers.filter { (it as? EnhancedTracker)?.accept(source!!) ?: true }
// KMK -->
val supportedTrackers = source?.let { source ->
val sources = if (source is MergedSource) {
state.mergedData?.sources ?: emptyList()
} else {
listOf(source)
}
loggedInTrackers.filter { (it as? EnhancedTracker)?.accept(sources) ?: true }
} ?: loggedInTrackers.filterNot { it is EnhancedTracker }
// KMK <--
val supportedTrackerIds = supportedTrackers.map { it.id }.toHashSet()
val supportedTrackerTracks = mangaTracks.filter { it.trackerId in supportedTrackerIds }
supportedTrackerTracks to supportedTrackers

View file

@ -60,12 +60,14 @@ import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
import eu.kanade.tachiyomi.util.lang.toLocalDate
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast
import exh.metadata.metadata.base.TrackerIdMetadata
import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.catch
@ -83,6 +85,8 @@ import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.manga.interactor.GetFlatMetadataById
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMergedReferencesById
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.DeleteTrack
import tachiyomi.domain.track.interactor.GetTracks
@ -94,6 +98,7 @@ import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneOffset
@ -230,10 +235,14 @@ data class TrackInfoDialogHomeScreen(
private val trackPreferences: TrackPreferences = Injekt.get(),
// SY <--
// KMK -->
val sourceManager: SourceManager = Injekt.get<SourceManager>(),
val getFlatMetadataById: GetFlatMetadataById = Injekt.get<GetFlatMetadataById>(),
private val sourceManager: SourceManager = Injekt.get(),
// KMK <--
) : StateScreenModel<Model.State>(State()) {
// KMK -->
private val getFlatMetadataById: GetFlatMetadataById by injectLazy()
private val getMangaById: GetManga by injectLazy()
private val getMergedReferencesById: GetMergedReferencesById by injectLazy()
// KMK <--
init {
screenModelScope.launch {
@ -249,10 +258,25 @@ data class TrackInfoDialogHomeScreen(
}
}
// KMK -->
private suspend fun getMangaForTracking(item: TrackItem): Manga? {
if (sourceId != MERGED_SOURCE_ID) {
return getMangaById.await(mangaId)
}
item.tracker as EnhancedTracker
val references = getMergedReferencesById.await(mangaId)
return references.distinctBy { it.mangaSourceId }.firstNotNullOfOrNull { ref ->
sourceManager.get(ref.mangaSourceId)
?.takeIf(item.tracker::accept)
?.let { ref.mangaId?.let { mangaId -> getMangaById.await(mangaId) } }
}
}
// KMK <--
fun registerEnhancedTracking(item: TrackItem) {
item.tracker as EnhancedTracker
screenModelScope.launchNonCancellable {
val manga = Injekt.get<GetManga>().await(mangaId) ?: return@launchNonCancellable
val manga = getMangaForTracking(item) ?: return@launchNonCancellable
try {
val matchResult = item.tracker.match(manga) ?: throw Exception()
item.tracker.register(matchResult, mangaId)
@ -361,14 +385,23 @@ data class TrackInfoDialogHomeScreen(
}
}
private fun List<Track>.mapToTrackItem(): List<TrackItem> {
val loggedInTrackers = Injekt.get<TrackerManager>().loggedInTrackers()
val source = Injekt.get<SourceManager>().getOrStub(sourceId)
private suspend fun List<Track>.mapToTrackItem(): List<TrackItem> {
val loggedInTrackers = trackerManager.loggedInTrackers()
val source = sourceManager.getOrStub(sourceId)
return loggedInTrackers
// Map to TrackItem
.map { service -> TrackItem(find { it.trackerId == service.id }, service) }
// Show only if the service supports this manga's source
.filter { (it.tracker as? EnhancedTracker)?.accept(source) ?: true }
// KMK -->
.let { trackers ->
val sources = if (source is MergedSource) {
sourceManager.getMergedSources(mangaId)
} else {
listOf(source)
}
trackers.filter { (it.tracker as? EnhancedTracker)?.accept(sources) ?: true }
}
// KMK <--
}
@Immutable

View file

@ -39,7 +39,6 @@ import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMergedReferencesById
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
@ -57,9 +56,6 @@ class MigrationListScreenModel(
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
private val migrateManga: MigrateMangaUseCase = Injekt.get(),
// SY -->
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
// SY <--
) : StateScreenModel<MigrationListScreenModel.State>(State()) {
private val smartSearchEngine = SmartSourceSearchEngine(extraSearchQuery)
@ -97,8 +93,7 @@ class MigrationListScreenModel(
source = sourceManager.getOrStub(manga.source).getNameForMangaInfo(
// KMK -->
if (manga.source == MERGED_SOURCE_ID) {
getMergedReferencesById.await(manga.id)
.map { sourceManager.getOrStub(it.mangaSourceId) }
sourceManager.getMergedSources(manga.id)
} else {
null
},

View file

@ -27,5 +27,9 @@ interface SourceManager {
fun getVisibleCatalogueSources(): List<CatalogueSource>
// SY <--
// KMK -->
suspend fun getMergedSources(mangaId: Long): List<Source>
// KMK <--
fun getStubSources(): List<StubSource>
}