Observe tracker login state instead of fetching once (#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
This commit is contained in:
AntsyLich 2024-07-06 07:25:33 +06:00 committed by Jobobby04
parent 3db4bccebc
commit 2d12c670db
12 changed files with 100 additions and 55 deletions

View File

@ -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,8 +207,8 @@ private fun ColumnScope.SortPage(
}.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary().get().isNotEmpty())
// SY <--
val trackerSortOption =
if (screenModel.trackers.isEmpty()) {
val trackerSortOption = if (trackers.isEmpty()) {
emptyList()
} else {
listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
@ -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) {

View File

@ -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,17 +154,15 @@ internal fun PreferenceItem(
)
}
is Preference.PreferenceItem.TrackerPreference -> {
val uName by Injekt.get<TrackPreferences>()
.trackUsername(item.tracker)
.collectAsState()
item.tracker.run {
val isLoggedIn by item.tracker.let { tracker ->
tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn)
}
TrackingPreferenceWidget(
tracker = this,
checked = uName.isNotEmpty(),
tracker = item.tracker,
checked = isLoggedIn,
onClick = { if (isLoggedIn) item.logout() else item.login() },
)
}
}
is Preference.PreferenceItem.InfoPreference -> {
InfoWidget(text = item.title)
}

View File

@ -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()

View File

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

View File

@ -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 get(id: Long) = trackers.find { it.id == id }
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 }
}

View File

@ -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<Long, List<Track>>,
loggedInTrackers: Map<Long, TriState>,
trackingFiler: Map<Long, TriState>,
): 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<MangaId, List<Track>>
trackMap: Map<Long, List<Track>>,
loggedInTrackerIds: Set<Long>,
/* 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<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())
}
}

View File

@ -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)

View File

@ -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()

View File

@ -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,12 +1499,13 @@ class MangaScreenModel(
val manga = state?.manga ?: return
screenModelScope.launchIO {
getTracks.subscribe(manga.id)
.catch { logcat(LogPriority.ERROR, it) }
.map { tracks ->
combine(
getTracks.subscribe(manga.id).catch { logcat(LogPriority.ERROR, it) },
trackerManager.loggedInTrackersFlow(),
) { mangaTracks, loggedInTrackers ->
loggedInTrackers
// Map to TrackItem
.map { service -> TrackItem(tracks.find { it.trackerId == service.id }, service) }
.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 }
}
@ -1637,6 +1647,7 @@ 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?,

View File

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

View File

@ -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)

View File

@ -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(),