From e5c830d08b84e6edba1e14c26240908ffd646b70 Mon Sep 17 00:00:00 2001
From: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
Date: Sat, 6 Jul 2024 07:25:33 +0600
Subject: [PATCH] Observe tracker login state instead of fetching once
 (mihonapp/mihon#987)

* Observe tracker login state instead of fetching once

* Review changes

(cherry picked from commit 2092c81bad59fd745a8514af320e534ecf40a5da)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
---
 .../library/LibrarySettingsDialog.kt          | 23 +++---
 .../more/settings/PreferenceItem.kt           | 20 +++---
 .../tachiyomi/data/track/BaseTracker.kt       | 11 +++
 .../eu/kanade/tachiyomi/data/track/Tracker.kt |  3 +
 .../tachiyomi/data/track/TrackerManager.kt    |  9 +++
 .../ui/library/LibraryScreenModel.kt          | 44 +++++++-----
 .../ui/library/LibrarySettingsScreenModel.kt  | 13 +++-
 .../kanade/tachiyomi/ui/manga/MangaScreen.kt  |  2 +-
 .../tachiyomi/ui/manga/MangaScreenModel.kt    | 72 ++++++++++++++-----
 .../ui/manga/track/TrackInfoDialog.kt         |  2 +-
 .../tachiyomi/ui/stats/StatsScreenModel.kt    |  2 +-
 .../main/java/eu/kanade/test/DummyTracker.kt  |  3 +
 12 files changed, 142 insertions(+), 62 deletions(-)

diff --git a/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt
index c87060d398..1cfdead822 100644
--- a/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt
+++ b/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt
@@ -92,6 +92,7 @@ fun LibrarySettingsDialog(
     }
 }
 
+@Suppress("UnusedReceiverParameter")
 @Composable
 private fun ColumnScope.FilterPage(
     screenModel: LibrarySettingsScreenModel,
@@ -155,7 +156,7 @@ private fun ColumnScope.FilterPage(
     )
     // SY <--
 
-    val trackers = remember { screenModel.trackers }
+    val trackers by screenModel.trackersFlow.collectAsState()
     when (trackers.size) {
         0 -> {
             // No trackers
@@ -183,11 +184,13 @@ private fun ColumnScope.FilterPage(
     }
 }
 
+@Suppress("UnusedReceiverParameter")
 @Composable
 private fun ColumnScope.SortPage(
     category: Category?,
     screenModel: LibrarySettingsScreenModel,
 ) {
+    val trackers by screenModel.trackersFlow.collectAsState()
     // SY -->
     val globalSortMode by screenModel.libraryPreferences.sortingMode().collectAsState()
     val sortingMode = if (screenModel.grouping == LibraryGroup.BY_DEFAULT) {
@@ -206,12 +209,11 @@ private fun ColumnScope.SortPage(
     }.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary().get().isNotEmpty())
     // SY <--
 
-    val trackerSortOption =
-        if (screenModel.trackers.isEmpty()) {
-            emptyList()
-        } else {
-            listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
-        }
+    val trackerSortOption = if (trackers.isEmpty()) {
+        emptyList()
+    } else {
+        listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
+    }
 
     listOfNotNull(
         MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical,
@@ -256,6 +258,7 @@ private val displayModes = listOf(
     MR.strings.action_display_list to LibraryDisplayMode.List,
 )
 
+@Suppress("UnusedReceiverParameter")
 @Composable
 private fun ColumnScope.DisplayPage(
     screenModel: LibrarySettingsScreenModel,
@@ -341,17 +344,19 @@ private fun groupTypeDrawableRes(type: Int): Int {
     }
 }
 
+@Suppress("UnusedReceiverParameter")
 @Composable
 private fun ColumnScope.GroupPage(
     screenModel: LibrarySettingsScreenModel,
     hasCategories: Boolean,
 ) {
-    val groups = remember(hasCategories, screenModel.trackers) {
+    val trackers by screenModel.trackersFlow.collectAsState()
+    val groups = remember(hasCategories, trackers) {
         buildList {
             add(LibraryGroup.BY_DEFAULT)
             add(LibraryGroup.BY_SOURCE)
             add(LibraryGroup.BY_STATUS)
-            if (screenModel.trackers.isNotEmpty()) {
+            if (trackers.isNotEmpty()) {
                 add(LibraryGroup.BY_TRACK_STATUS)
             }
             if (hasCategories) {
diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt
index b22e69323a..5a3c9d53ba 100644
--- a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt
+++ b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt
@@ -7,12 +7,12 @@ import androidx.compose.animation.fadeOut
 import androidx.compose.animation.shrinkVertically
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.structuralEqualityPolicy
 import androidx.compose.ui.unit.dp
-import eu.kanade.domain.track.service.TrackPreferences
 import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
 import eu.kanade.presentation.more.settings.widget.InfoWidget
 import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
@@ -23,8 +23,6 @@ import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
 import kotlinx.coroutines.launch
 import tachiyomi.presentation.core.components.SliderItem
 import tachiyomi.presentation.core.util.collectAsState
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
 
 val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
 val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp }
@@ -156,16 +154,14 @@ internal fun PreferenceItem(
                 )
             }
             is Preference.PreferenceItem.TrackerPreference -> {
-                val uName by Injekt.get<TrackPreferences>()
-                    .trackUsername(item.tracker)
-                    .collectAsState()
-                item.tracker.run {
-                    TrackingPreferenceWidget(
-                        tracker = this,
-                        checked = uName.isNotEmpty(),
-                        onClick = { if (isLoggedIn) item.logout() else item.login() },
-                    )
+                val isLoggedIn by item.tracker.let { tracker ->
+                    tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn)
                 }
+                TrackingPreferenceWidget(
+                    tracker = item.tracker,
+                    checked = isLoggedIn,
+                    onClick = { if (isLoggedIn) item.logout() else item.login() },
+                )
             }
             is Preference.PreferenceItem.InfoPreference -> {
                 InfoWidget(text = item.title)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt
index 8f88f1051f..33caf65554 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt
@@ -8,6 +8,8 @@ import eu.kanade.domain.track.service.TrackPreferences
 import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.network.NetworkHelper
 import eu.kanade.tachiyomi.util.system.toast
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import logcat.LogPriority
 import okhttp3.OkHttpClient
 import tachiyomi.core.common.util.lang.withIOContext
@@ -53,6 +55,15 @@ abstract class BaseTracker(
         get() = getUsername().isNotEmpty() &&
             getPassword().isNotEmpty()
 
+    override val isLoggedInFlow: Flow<Boolean> by lazy {
+        combine(
+            trackPreferences.trackUsername(this).changes(),
+            trackPreferences.trackPassword(this).changes(),
+        ) { username, password ->
+            username.isNotEmpty() && password.isNotEmpty()
+        }
+    }
+
     override fun getUsername() = trackPreferences.trackUsername(this).get()
 
     override fun getPassword() = trackPreferences.trackPassword(this).get()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt
index 06ca272c4e..a908cdd9de 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt
@@ -7,6 +7,7 @@ import dev.icerock.moko.resources.StringResource
 import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import kotlinx.collections.immutable.ImmutableList
+import kotlinx.coroutines.flow.Flow
 import okhttp3.OkHttpClient
 import tachiyomi.domain.track.model.Track as DomainTrack
 
@@ -61,6 +62,8 @@ interface Tracker {
 
     val isLoggedIn: Boolean
 
+    val isLoggedInFlow: Flow<Boolean>
+
     fun getUsername(): String
 
     fun getPassword(): String
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt
index 813f565284..9a45b37c58 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt
@@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.track.mdlist.MdList
 import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
 import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
 import eu.kanade.tachiyomi.data.track.suwayomi.Suwayomi
+import kotlinx.coroutines.flow.combine
 
 class TrackerManager {
 
@@ -40,5 +41,13 @@ class TrackerManager {
 
     fun loggedInTrackers() = trackers.filter { it.isLoggedIn }
 
+    fun loggedInTrackersFlow() = combine(trackers.map { it.isLoggedInFlow }) {
+        it.mapIndexedNotNull { index, isLoggedIn ->
+            if (isLoggedIn) trackers[index] else null
+        }
+    }
+
     fun get(id: Long) = trackers.find { it.id == id }
+
+    fun getAll(ids: Set<Long>) = trackers.filter { it.id in ids }
 }
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
index 1eb906e88c..ce29fa5b83 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
@@ -68,6 +68,7 @@ import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
@@ -177,18 +178,24 @@ class LibraryScreenModel(
                     ::Pair,
                 ),
                 // SY <--
-            ) { searchQuery, library, tracks, (loggedInTrackers, _), (groupType, sort) ->
+            ) { searchQuery, library, tracks, (trackingFiler, _), (groupType, sort) ->
                 library
                     // SY -->
                     .applyGrouping(groupType)
                     // SY <--
-                    .applyFilters(tracks, loggedInTrackers)
-                    .applySort(tracks, /* SY --> */sort.takeIf { groupType != LibraryGroup.BY_DEFAULT } /* SY <-- */)
+                    .applyFilters(tracks, trackingFiler)
+                    .applySort(
+                        tracks,
+                        trackingFiler.keys,
+                        // SY -->
+                        sort.takeIf { groupType != LibraryGroup.BY_DEFAULT },
+                        // SY <--
+                    )
                     .mapValues { (_, value) ->
                         if (searchQuery != null) {
                             // Filter query
                             // SY -->
-                            filterLibrary(value, searchQuery, loggedInTrackers)
+                            filterLibrary(value, searchQuery, trackingFiler)
                             // SY <--
                         } else {
                             // Don't do anything
@@ -277,9 +284,10 @@ class LibraryScreenModel(
     /**
      * Applies library filters to the given map of manga.
      */
+    @Suppress("LongMethod", "CyclomaticComplexMethod")
     private suspend fun LibraryMap.applyFilters(
         trackMap: Map<Long, List<Track>>,
-        loggedInTrackers: Map<Long, TriState>,
+        trackingFiler: Map<Long, TriState>,
     ): LibraryMap {
         val prefs = getLibraryItemPreferencesFlow().first()
         val downloadedOnly = prefs.globalFilterDownloaded
@@ -291,10 +299,10 @@ class LibraryScreenModel(
         val filterCompleted = prefs.filterCompleted
         val filterIntervalCustom = prefs.filterIntervalCustom
 
-        val isNotLoggedInAnyTrack = loggedInTrackers.isEmpty()
+        val isNotLoggedInAnyTrack = trackingFiler.isEmpty()
 
-        val excludedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null }
-        val includedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null }
+        val excludedTracks = trackingFiler.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null }
+        val includedTracks = trackingFiler.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null }
         val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty()
 
         // SY -->
@@ -371,9 +379,11 @@ class LibraryScreenModel(
     /**
      * Applies library sorting to the given map of manga.
      */
+    @Suppress("LongMethod", "CyclomaticComplexMethod")
     private fun LibraryMap.applySort(
         // Map<MangaId, List<Track>>
         trackMap: Map<Long, List<Track>>,
+        loggedInTrackerIds: Set<Long>,
         /* SY --> */
         groupSort: LibrarySort? = null, /* SY <-- */
     ): LibraryMap {
@@ -397,7 +407,7 @@ class LibraryScreenModel(
 
         val defaultTrackerScoreSortValue = -1.0
         val trackerScores by lazy {
-            val trackerMap = trackerManager.loggedInTrackers().associateBy { e -> e.id }
+            val trackerMap = trackerManager.getAll(loggedInTrackerIds).associateBy { e -> e.id }
             trackMap.mapValues { entry ->
                 when {
                     entry.value.isEmpty() -> null
@@ -596,18 +606,17 @@ class LibraryScreenModel(
      * @return map of track id with the filter value
      */
     private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
-        val loggedInTrackers = trackerManager.loggedInTrackers()
-        return if (loggedInTrackers.isNotEmpty()) {
-            val prefFlows = loggedInTrackers
-                .map { libraryPreferences.filterTracking(it.id.toInt()).changes() }
-                .toTypedArray()
-            combine(*prefFlows) {
+        return trackerManager.loggedInTrackersFlow().flatMapLatest { loggedInTrackers ->
+            if (loggedInTrackers.isEmpty()) return@flatMapLatest flowOf(emptyMap())
+
+            val prefFlows = loggedInTrackers.map { tracker ->
+                libraryPreferences.filterTracking(tracker.id.toInt()).changes()
+            }
+            combine(prefFlows) {
                 loggedInTrackers
                     .mapIndexed { index, tracker -> tracker.id to it[index] }
                     .toMap()
             }
-        } else {
-            flowOf(emptyMap())
         }
     }
 
@@ -878,7 +887,6 @@ class LibraryScreenModel(
     }
 
     // SY -->
-
     fun getCategoryName(
         context: Context,
         category: Category?,
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt
index 55497e05f3..652ec1d506 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt
@@ -6,6 +6,8 @@ import cafe.adriel.voyager.core.model.screenModelScope
 import eu.kanade.core.preference.asState
 import eu.kanade.domain.base.BasePreferences
 import eu.kanade.tachiyomi.data.track.TrackerManager
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
 import tachiyomi.core.common.preference.Preference
 import tachiyomi.core.common.preference.TriState
 import tachiyomi.core.common.preference.getAndSet
@@ -18,17 +20,22 @@ import tachiyomi.domain.library.model.LibrarySort
 import tachiyomi.domain.library.service.LibraryPreferences
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import kotlin.time.Duration.Companion.seconds
 
 class LibrarySettingsScreenModel(
     val preferences: BasePreferences = Injekt.get(),
     val libraryPreferences: LibraryPreferences = Injekt.get(),
     private val setDisplayMode: SetDisplayMode = Injekt.get(),
     private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
-    private val trackerManager: TrackerManager = Injekt.get(),
+    trackerManager: TrackerManager = Injekt.get(),
 ) : ScreenModel {
 
-    val trackers
-        get() = trackerManager.trackers.filter { it.isLoggedIn }
+    val trackersFlow = trackerManager.loggedInTrackersFlow()
+        .stateIn(
+            scope = screenModelScope,
+            started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds),
+            initialValue = trackerManager.loggedInTrackers()
+        )
 
     // SY -->
     val grouping by libraryPreferences.groupLibraryBy().asState(screenModelScope)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt
index 005e01a186..57e67fd037 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt
@@ -290,7 +290,7 @@ class MangaScreen(
                 )
             }.takeIf { isHttpSource },
             onTrackingClicked = {
-                if (screenModel.loggedInTrackers.isEmpty()) {
+                if (successState.loggedInTracker.isEmpty()) {
                     navigator.push(SettingsScreen(SettingsScreen.Destination.Tracking))
                 } else {
                     screenModel.showTrackDialog()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
index f128d95e72..c88d102e35 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
@@ -49,6 +49,7 @@ import eu.kanade.tachiyomi.data.download.DownloadCache
 import eu.kanade.tachiyomi.data.download.DownloadManager
 import eu.kanade.tachiyomi.data.download.model.Download
 import eu.kanade.tachiyomi.data.track.EnhancedTracker
+import eu.kanade.tachiyomi.data.track.Tracker
 import eu.kanade.tachiyomi.data.track.TrackerManager
 import eu.kanade.tachiyomi.data.track.mdlist.MdList
 import eu.kanade.tachiyomi.network.HttpException
@@ -166,8 +167,10 @@ import androidx.compose.runtime.State as RuntimeState
 class MangaScreenModel(
     val context: Context,
     val mangaId: Long,
+    // SY -->
     private val isFromSource: Boolean,
     val smartSearched: Boolean,
+    // SY <--
     private val downloadPreferences: DownloadPreferences = Injekt.get(),
     private val libraryPreferences: LibraryPreferences = Injekt.get(),
     readerPreferences: ReaderPreferences = Injekt.get(),
@@ -219,8 +222,6 @@ class MangaScreenModel(
     private val successState: State.Success?
         get() = state.value as? State.Success
 
-    val loggedInTrackers by lazy { trackerManager.trackers.filter { it.isLoggedIn } }
-
     // KMK -->
     val useNewSourceNavigation by uiPreferences.useNewSourceNavigation().asState(screenModelScope)
     val themeCoverBased = uiPreferences.themeCoverBased().get()
@@ -246,7 +247,8 @@ class MangaScreenModel(
 
     private val skipFiltered by readerPreferences.skipFiltered().asState(screenModelScope)
 
-    val isUpdateIntervalEnabled = LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateMangaRestrictions().get()
+    val isUpdateIntervalEnabled =
+        LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateMangaRestrictions().get()
 
     private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list
     private val selectedChapterIds: HashSet<Long> = HashSet()
@@ -259,6 +261,7 @@ class MangaScreenModel(
     data class EXHRedirect(val mangaId: Long)
     // EXH <--
 
+    // SY -->
     private data class CombineState(
         val manga: Manga,
         val chapters: List<Chapter>,
@@ -269,6 +272,7 @@ class MangaScreenModel(
         constructor(pair: Pair<Manga, List<Chapter>>, flatMetadata: FlatMetadata?) :
             this(pair.first, pair.second, flatMetadata)
     }
+    // SY <--
 
     /**
      * Helper function to update the UI state only if it's currently in success state
@@ -284,8 +288,7 @@ class MangaScreenModel(
 
     init {
         screenModelScope.launchIO {
-            getMangaAndChapters.subscribe(mangaId, applyScanlatorFilter = true)
-                .distinctUntilChanged()
+            getMangaAndChapters.subscribe(mangaId, applyScanlatorFilter = true).distinctUntilChanged()
                 // SY -->
                 .combine(
                     getMergedChaptersByMangaId.subscribe(mangaId, true, applyScanlatorFilter = true)
@@ -397,6 +400,16 @@ class MangaScreenModel(
                 }
         }
 
+        screenModelScope.launchIO {
+            trackerManager.loggedInTrackersFlow()
+                .distinctUntilChanged()
+                .collectLatest { trackers ->
+                    updateSuccessState {
+                        it.copy(loggedInTracker = trackers)
+                    }
+                }
+        }
+
         observeDownloads()
 
         screenModelScope.launchIO {
@@ -425,7 +438,9 @@ class MangaScreenModel(
 
             // Show what we have earlier
             mutableState.update {
+                // SY -->
                 val source = sourceManager.getOrStub(manga.source)
+                // SY <--
                 State.Success(
                     manga = manga,
                     source = source,
@@ -1099,7 +1114,9 @@ class MangaScreenModel(
 
     private fun List<Chapter>.toChapterListItems(
         manga: Manga,
+        // SY -->
         mergedData: MergedMangaData?,
+        // SY <--
     ): List<ChapterList.Item> {
         val isLocal = manga.isLocal()
         // SY -->
@@ -1175,7 +1192,9 @@ class MangaScreenModel(
         val state = successState ?: return
         try {
             withIOContext {
+                // SY -->
                 if (state.source !is MergedSource) {
+                    // SY <--
                     val chapters = state.source.getChapterList(state.manga.toSManga())
 
                     val newChapters = syncChaptersWithSource.await(
@@ -1188,9 +1207,11 @@ class MangaScreenModel(
                     if (manualFetch) {
                         downloadNewChapters(newChapters)
                     }
+                    // SY -->
                 } else {
                     state.source.fetchChaptersForMergedManga(state.manga, manualFetch)
                 }
+                // SY <--
             }
         } catch (e: Throwable) {
             val message = if (e is NoChaptersException) {
@@ -1471,6 +1492,7 @@ class MangaScreenModel(
      * @param chapters the list of chapters to download.
      */
     private fun downloadChapters(chapters: List<Chapter>) {
+        // SY -->
         val state = successState ?: return
         if (state.source is MergedSource) {
             chapters.groupBy { it.mangaId }.forEach { map ->
@@ -1478,7 +1500,7 @@ class MangaScreenModel(
                 downloadManager.downloadChapters(manga, map.value)
             }
         } else {
-            /* SY <-- */
+            // SY <--
             val manga = state.manga
             downloadManager.downloadChapters(manga, chapters)
         }
@@ -1524,7 +1546,15 @@ class MangaScreenModel(
         screenModelScope.launchNonCancellable {
             val manga = successState?.manga ?: return@launchNonCancellable
             val categories = getCategories.await(manga.id).map { it.id }
-            if (chapters.isEmpty() || !manga.shouldDownloadNewChapters(categories, downloadPreferences) || manga.isEhBasedManga()) return@launchNonCancellable
+            if (
+                chapters.isEmpty() ||
+                !manga.shouldDownloadNewChapters(categories, downloadPreferences) ||
+                // EXH -->
+                manga.isEhBasedManga()
+                // EXH <--
+            ) {
+                return@launchNonCancellable
+            }
             downloadChapters(chapters)
         }
     }
@@ -1721,15 +1751,16 @@ class MangaScreenModel(
         val manga = state?.manga ?: return
 
         screenModelScope.launchIO {
-            getTracks.subscribe(manga.id)
-                .catch { logcat(LogPriority.ERROR, it) }
-                .map { tracks ->
-                    loggedInTrackers
-                        // Map to TrackItem
-                        .map { service -> TrackItem(tracks.find { it.trackerId == service.id }, service) }
-                        // Show only if the service supports this manga's source
-                        .filter { (it.tracker as? EnhancedTracker)?.accept(source!!) ?: true }
-                }
+            combine(
+                getTracks.subscribe(manga.id).catch { logcat(LogPriority.ERROR, it) },
+                trackerManager.loggedInTrackersFlow(),
+            ) { mangaTracks, loggedInTrackers ->
+                loggedInTrackers
+                    // Map to TrackItem
+                    .map { service -> TrackItem(mangaTracks.find { it.trackerId == service.id }, service) }
+                    // Show only if the service supports this manga's source
+                    .filter { (it.tracker as? EnhancedTracker)?.accept(source!!) ?: true }
+            }
                 // SY -->
                 .map { trackItems ->
                     if (manga.source in mangaDexSourceIds || state.mergedData?.manga?.values.orEmpty().any {
@@ -1869,6 +1900,8 @@ class MangaScreenModel(
             val isRefreshingData: Boolean = false,
             val dialog: MangaScreenModel.Dialog? = null,
             val hasPromptedToAddBefore: Boolean = false,
+            val loggedInTracker: List<Tracker> = emptyList(),
+
             // SY -->
             val meta: RaisedSearchMetadata?,
             val mergedData: MergedMangaData?,
@@ -1950,7 +1983,10 @@ class MangaScreenModel(
 
             val trackingCount: Int
                 get() = trackItems.count {
-                    it.track != null && ((it.tracker is MdList && it.track.status != FollowStatus.UNFOLLOWED.long) || it.tracker !is MdList)
+                    it.track != null && (
+                        it.tracker !is MdList ||
+                            it.track.status != FollowStatus.UNFOLLOWED.long
+                        )
                 }
 
             /**
@@ -1972,11 +2008,13 @@ class MangaScreenModel(
     }
 }
 
+// SY -->
 data class MergedMangaData(
     val references: List<MergedMangaReference>,
     val manga: Map<Long, Manga>,
     val sources: List<Source>,
 )
+// SY <--
 
 @Immutable
 sealed class ChapterList {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt
index 1ba697f245..4c66940c97 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt
@@ -239,7 +239,7 @@ data class TrackInfoDialogHomeScreen(
         }
 
         private fun List<Track>.mapToTrackItem(): List<TrackItem> {
-            val loggedInTrackers = Injekt.get<TrackerManager>().trackers.filter { it.isLoggedIn }
+            val loggedInTrackers = Injekt.get<TrackerManager>().loggedInTrackers()
             val source = Injekt.get<SourceManager>().getOrStub(sourceId)
             return loggedInTrackers
                 // Map to TrackItem
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt
index 66d7354f59..db44cd6728 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt
@@ -43,7 +43,7 @@ class StatsScreenModel(
     // SY <--
 ) : StateScreenModel<StatsScreenState>(StatsScreenState.Loading) {
 
-    private val loggedInTrackers by lazy { trackerManager.trackers.fastFilter { it.isLoggedIn } }
+    private val loggedInTrackers by lazy { trackerManager.loggedInTrackers() }
 
     // SY -->
     private val _allRead = MutableStateFlow(false)
diff --git a/app/src/main/java/eu/kanade/test/DummyTracker.kt b/app/src/main/java/eu/kanade/test/DummyTracker.kt
index 68e0c2f78a..415425e2cf 100644
--- a/app/src/main/java/eu/kanade/test/DummyTracker.kt
+++ b/app/src/main/java/eu/kanade/test/DummyTracker.kt
@@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.data.track.Tracker
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.toImmutableList
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
 import okhttp3.OkHttpClient
 import tachiyomi.domain.track.model.Track
 import tachiyomi.i18n.MR
@@ -16,6 +18,7 @@ data class DummyTracker(
     override val name: String,
     override val supportsReadingDates: Boolean = false,
     override val isLoggedIn: Boolean = false,
+    override val isLoggedInFlow: Flow<Boolean> = flowOf(false),
     val valLogoColor: Int = Color.rgb(18, 25, 35),
     val valLogo: Int = R.drawable.ic_tracker_anilist,
     val valStatuses: List<Long> = (1L..6L).toList(),