mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-29 21:37:56 +01:00 
			
		
		
		
	Observe tracker login state instead of fetching once (#987)
* Observe tracker login state instead of fetching once * Review changes
This commit is contained in:
		| @@ -9,6 +9,7 @@ import androidx.compose.foundation.verticalScroll | ||||
| import androidx.compose.material3.FilterChip | ||||
| import androidx.compose.material3.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.collectAsState | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.ui.Modifier | ||||
| @@ -125,7 +126,7 @@ private fun ColumnScope.FilterPage( | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     val trackers = remember { screenModel.trackers } | ||||
|     val trackers by screenModel.trackersFlow.collectAsState() | ||||
|     when (trackers.size) { | ||||
|         0 -> { | ||||
|             // No trackers | ||||
| @@ -158,15 +159,15 @@ private fun ColumnScope.SortPage( | ||||
|     category: Category?, | ||||
|     screenModel: LibrarySettingsScreenModel, | ||||
| ) { | ||||
|     val trackers by screenModel.trackersFlow.collectAsState() | ||||
|     val sortingMode = category.sort.type | ||||
|     val sortDescending = !category.sort.isAscending | ||||
|  | ||||
|     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) | ||||
|     } | ||||
|  | ||||
|     listOf( | ||||
|         MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical, | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.data.track.mangaupdates.MangaUpdates | ||||
| 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 { | ||||
|  | ||||
| @@ -32,5 +33,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 } | ||||
| } | ||||
|   | ||||
| @@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.combine | ||||
| import kotlinx.coroutines.flow.debounce | ||||
| import kotlinx.coroutines.flow.distinctUntilChanged | ||||
| 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 | ||||
| @@ -106,10 +107,10 @@ class LibraryScreenModel( | ||||
|                 getTracksPerManga.subscribe(), | ||||
|                 getTrackingFilterFlow(), | ||||
|                 downloadCache.changes, | ||||
|             ) { searchQuery, library, tracks, loggedInTrackers, _ -> | ||||
|             ) { searchQuery, library, tracks, trackingFiler, _ -> | ||||
|                 library | ||||
|                     .applyFilters(tracks, loggedInTrackers) | ||||
|                     .applySort(tracks) | ||||
|                     .applyFilters(tracks, trackingFiler) | ||||
|                     .applySort(tracks, trackingFiler.keys) | ||||
|                     .mapValues { (_, value) -> | ||||
|                         if (searchQuery != null) { | ||||
|                             // Filter query | ||||
| @@ -173,9 +174,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 | ||||
| @@ -187,10 +189,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() | ||||
|  | ||||
|         val filterFnDownloaded: (LibraryItem) -> Boolean = { | ||||
| @@ -254,9 +256,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> | ||||
|     ): LibraryMap { | ||||
|         val sortAlphabetically: (LibraryItem, LibraryItem) -> Int = { i1, i2 -> | ||||
|             i1.libraryManga.manga.title.lowercase().compareToWithCollator(i2.libraryManga.manga.title.lowercase()) | ||||
| @@ -264,7 +268,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 | ||||
| @@ -405,18 +409,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()) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,8 @@ import cafe.adriel.voyager.core.model.ScreenModel | ||||
| import cafe.adriel.voyager.core.model.screenModelScope | ||||
| 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 | ||||
| @@ -16,17 +18,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() | ||||
|         ) | ||||
|  | ||||
|     fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) { | ||||
|         preference(libraryPreferences).getAndSet { | ||||
|   | ||||
| @@ -138,7 +138,7 @@ class MangaScreen( | ||||
|                 ) | ||||
|             }.takeIf { isHttpSource }, | ||||
|             onTrackingClicked = { | ||||
|                 if (screenModel.loggedInTrackers.isEmpty()) { | ||||
|                 if (successState.loggedInTracker.isEmpty()) { | ||||
|                     navigator.push(SettingsScreen(SettingsScreen.Destination.Tracking)) | ||||
|                 } else { | ||||
|                     screenModel.showTrackDialog() | ||||
|   | ||||
| @@ -28,6 +28,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.network.HttpException | ||||
| import eu.kanade.tachiyomi.source.Source | ||||
| @@ -45,7 +46,6 @@ import kotlinx.coroutines.flow.collectLatest | ||||
| import kotlinx.coroutines.flow.combine | ||||
| import kotlinx.coroutines.flow.distinctUntilChanged | ||||
| import kotlinx.coroutines.flow.filter | ||||
| import kotlinx.coroutines.flow.map | ||||
| import kotlinx.coroutines.flow.update | ||||
| import kotlinx.coroutines.isActive | ||||
| import kotlinx.coroutines.launch | ||||
| @@ -117,8 +117,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 | ||||
|  | ||||
| @@ -194,6 +192,16 @@ class MangaScreenModel( | ||||
|                 } | ||||
|         } | ||||
|  | ||||
|         screenModelScope.launchIO { | ||||
|             trackerManager.loggedInTrackersFlow() | ||||
|                 .distinctUntilChanged() | ||||
|                 .collectLatest { trackers -> | ||||
|                     updateSuccessState { | ||||
|                         it.copy(loggedInTracker = trackers) | ||||
|                     } | ||||
|                 } | ||||
|         } | ||||
|  | ||||
|         observeDownloads() | ||||
|  | ||||
|         screenModelScope.launchIO { | ||||
| @@ -978,15 +986,16 @@ class MangaScreenModel( | ||||
|         val manga = successState?.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 } | ||||
|             } | ||||
|                 .distinctUntilChanged() | ||||
|                 .collectLatest { trackItems -> | ||||
|                     updateSuccessState { it.copy(trackItems = trackItems) } | ||||
| @@ -1057,6 +1066,7 @@ class MangaScreenModel( | ||||
|             val isRefreshingData: Boolean = false, | ||||
|             val dialog: Dialog? = null, | ||||
|             val hasPromptedToAddBefore: Boolean = false, | ||||
|             val loggedInTracker: List<Tracker> = emptyList(), | ||||
|         ) : State { | ||||
|             val processedChapters by lazy { | ||||
|                 chapters.applyFilters(manga).toList() | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -36,7 +36,7 @@ class StatsScreenModel( | ||||
|     private val trackerManager: TrackerManager = Injekt.get(), | ||||
| ) : StateScreenModel<StatsScreenState>(StatsScreenState.Loading) { | ||||
|  | ||||
|     private val loggedInTrackers by lazy { trackerManager.trackers.fastFilter { it.isLoggedIn } } | ||||
|     private val loggedInTrackers by lazy { trackerManager.loggedInTrackers() } | ||||
|  | ||||
|     init { | ||||
|         screenModelScope.launchIO { | ||||
|   | ||||
| @@ -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(), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user