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 c87060d39..df755d550 100644 --- a/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt @@ -155,7 +155,7 @@ private fun ColumnScope.FilterPage( ) // SY <-- - val trackers = remember { screenModel.trackers } + val trackers by screenModel.trackersFlow.collectAsState() when (trackers.size) { 0 -> { // No trackers @@ -188,6 +188,7 @@ 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 +207,12 @@ 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, @@ -346,12 +347,13 @@ 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 b22e69323..5a3c9d53b 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() - .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 8f88f1051..33caf6555 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 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 06644e932..fd3a9f45e 100755 --- 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 + 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 813f56528..9a45b37c5 100755 --- 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) = 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 1eb906e88..98c2b1685 100755 --- 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,18 @@ 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 +278,10 @@ class LibraryScreenModel( /** * Applies library filters to the given map of manga. */ + @Suppress("LongMethod", "CyclomaticComplexMethod") private suspend fun LibraryMap.applyFilters( trackMap: Map>, - loggedInTrackers: Map, + trackingFiler: Map, ): LibraryMap { val prefs = getLibraryItemPreferencesFlow().first() val downloadedOnly = prefs.globalFilterDownloaded @@ -291,10 +293,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 +373,11 @@ class LibraryScreenModel( /** * Applies library sorting to the given map of manga. */ + @Suppress("LongMethod", "CyclomaticComplexMethod") private fun LibraryMap.applySort( // Map> trackMap: Map>, + loggedInTrackerIds: Set, /* SY --> */ groupSort: LibrarySort? = null, /* SY <-- */ ): LibraryMap { @@ -397,7 +401,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 +600,17 @@ class LibraryScreenModel( * @return map of track id with the filter value */ private fun getTrackingFilterFlow(): Flow> { - 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()) } } 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 55497e05f..652ec1d50 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 9f2a8af83..823a52a2b 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 @@ -186,7 +186,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 206862c84..655b3003d 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 @@ -34,6 +34,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 @@ -187,8 +188,6 @@ class MangaScreenModel( private val successState: State.Success? get() = state.value as? State.Success - val loggedInTrackers by lazy { trackerManager.trackers.filter { it.isLoggedIn } } - val manga: Manga? get() = successState?.manga @@ -362,6 +361,16 @@ class MangaScreenModel( } } + screenModelScope.launchIO { + trackerManager.loggedInTrackersFlow() + .distinctUntilChanged() + .collectLatest { trackers -> + updateSuccessState { + it.copy(loggedInTracker = trackers) + } + } + } + observeDownloads() screenModelScope.launchIO { @@ -1490,15 +1499,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 { @@ -1637,6 +1647,7 @@ class MangaScreenModel( val isRefreshingData: Boolean = false, val dialog: MangaScreenModel.Dialog? = null, val hasPromptedToAddBefore: Boolean = false, + val loggedInTracker: List = emptyList(), // SY --> val meta: RaisedSearchMetadata?, val mergedData: MergedMangaData?, 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 1ba697f24..4c66940c9 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.mapToTrackItem(): List { - val loggedInTrackers = Injekt.get().trackers.filter { it.isLoggedIn } + val loggedInTrackers = Injekt.get().loggedInTrackers() val source = Injekt.get().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 66d7354f5..db44cd672 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.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 56092b440..4c77e660d 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 = flowOf(false), val valLogoColor: Int = Color.rgb(18, 25, 35), val valLogo: Int = R.drawable.ic_tracker_anilist, val valStatuses: List = (1L..6L).toList(),