MangaPresenter: Fix state updates when opening a new manga entry (#7379)

(cherry picked from commit 0e0c1dcdc5f42b0a63d5b865c9aba29db4ab18a6)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
This commit is contained in:
Ivan Iskandar 2022-06-26 20:45:06 +07:00 committed by Jobobby04
parent a3dd6b523a
commit 3fa1c24f8d

View File

@ -32,7 +32,6 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.saver.ImageSaver
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
@ -84,6 +83,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import logcat.LogPriority import logcat.LogPriority
@ -152,8 +152,6 @@ class MangaPresenter(
private val loggedServices by lazy { trackManager.services.filter { it.isLogged } } private val loggedServices by lazy { trackManager.services.filter { it.isLogged } }
private val imageSaver: ImageSaver by injectLazy()
private var trackSubscription: Subscription? = null private var trackSubscription: Subscription? = null
private var searchTrackerJob: Job? = null private var searchTrackerJob: Job? = null
private var refreshTrackersJob: Job? = null private var refreshTrackersJob: Job? = null
@ -194,6 +192,13 @@ class MangaPresenter(
this(pair.first, pair.second, flatMetadata) this(pair.first, pair.second, flatMetadata)
} }
/**
* Helper function to update the UI state only if it's currently in success state
*/
private fun updateSuccessState(func: (MangaScreenState.Success) -> MangaScreenState.Success) {
_state.update { if (it is MangaScreenState.Success) func(it) else it }
}
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
@ -253,49 +258,50 @@ class MangaPresenter(
// SY <-- // SY <--
.collectLatest { (manga, chapters, flatMetadata, mergedData) -> .collectLatest { (manga, chapters, flatMetadata, mergedData) ->
val chapterItems = chapters.toChapterItems(manga, mergedData) val chapterItems = chapters.toChapterItems(manga, mergedData)
val currentState = _state.value _state.update { currentState ->
_state.value = when (currentState) { when (currentState) {
// Initialize success state // Initialize success state
MangaScreenState.Loading -> { MangaScreenState.Loading -> {
val source = Injekt.get<SourceManager>().getOrStub(manga.source) val source = Injekt.get<SourceManager>().getOrStub(manga.source)
MangaScreenState.Success( MangaScreenState.Success(
manga = manga, manga = manga,
source = source, source = source,
dateRelativeTime = if (source.isEhBasedSource()) 0 else preferences.relativeTime().get(), dateRelativeTime = if (source.isEhBasedSource()) 0 else preferences.relativeTime().get(),
dateFormat = if (source.isEhBasedSource()) { dateFormat = if (source.isEhBasedSource()) {
MetadataUtil.EX_DATE_FORMAT MetadataUtil.EX_DATE_FORMAT
} else { } else {
preferences.dateFormat() preferences.dateFormat()
}, },
isFromSource = isFromSource, isFromSource = isFromSource,
trackingAvailable = trackManager.hasLoggedServices(), trackingAvailable = trackManager.hasLoggedServices(),
chapters = chapterItems, chapters = chapterItems,
meta = raiseMetadata(flatMetadata, source), meta = raiseMetadata(flatMetadata, source),
mergedData = mergedData, mergedData = mergedData,
showRecommendationsInOverflow = preferences.recommendsInOverflow().get(), showRecommendationsInOverflow = preferences.recommendsInOverflow().get(),
showMergeWithAnother = smartSearched, showMergeWithAnother = smartSearched,
).also { ).also {
getTrackingObservable(manga) getTrackingObservable(manga)
.subscribeLatestCache( .subscribeLatestCache(
{ _, count -> { _, count ->
successState?.let { successState?.let {
_state.value = it.copy(trackingCount = count) _state.value = it.copy(trackingCount = count)
} }
}, },
{ _, error -> logcat(LogPriority.ERROR, error) }, { _, error -> logcat(LogPriority.ERROR, error) },
) )
}
} }
}
// Update state // Update state
is MangaScreenState.Success -> currentState.copy( is MangaScreenState.Success -> currentState.copy(
manga = manga, manga = manga,
chapters = chapterItems, chapters = chapterItems,
// SY --> // SY -->
meta = raiseMetadata(flatMetadata, currentState.source), meta = raiseMetadata(flatMetadata, currentState.source),
mergedData = mergedData, mergedData = mergedData,
// SY <-- // SY <--
) )
}
} }
fetchTrackers() fetchTrackers()
@ -309,17 +315,13 @@ class MangaPresenter(
preferences.incognitoMode() preferences.incognitoMode()
.asImmediateFlow { incognito -> .asImmediateFlow { incognito ->
successState?.let { updateSuccessState { it.copy(isIncognitoMode = incognito) }
_state.value = it.copy(isIncognitoMode = incognito)
}
} }
.launchIn(presenterScope) .launchIn(presenterScope)
preferences.downloadedOnly() preferences.downloadedOnly()
.asImmediateFlow { downloadedOnly -> .asImmediateFlow { downloadedOnly ->
successState?.let { updateSuccessState { it.copy(isDownloadedOnlyMode = downloadedOnly) }
_state.value = it.copy(isDownloadedOnlyMode = downloadedOnly)
}
} }
.launchIn(presenterScope) .launchIn(presenterScope)
} }
@ -353,17 +355,18 @@ class MangaPresenter(
*/ */
private fun fetchMangaFromSource(manualFetch: Boolean = false) { private fun fetchMangaFromSource(manualFetch: Boolean = false) {
if (fetchMangaJob?.isActive == true) return if (fetchMangaJob?.isActive == true) return
val successState = successState ?: return
fetchMangaJob = presenterScope.launchIO { fetchMangaJob = presenterScope.launchIO {
_state.value = successState.copy(isRefreshingInfo = true) updateSuccessState { it.copy(isRefreshingInfo = true) }
try { try {
val networkManga = successState.source.getMangaDetails(successState.manga.toMangaInfo()) successState?.let {
updateManga.awaitUpdateFromSource(successState.manga, networkManga, manualFetch) val networkManga = it.source.getMangaDetails(it.manga.toMangaInfo())
updateManga.awaitUpdateFromSource(it.manga, networkManga, manualFetch)
}
} catch (e: Throwable) { } catch (e: Throwable) {
this@MangaPresenter.xLogE("Error getting manga details", e) this@MangaPresenter.xLogE("Error getting manga details", e)
withUIContext { view?.onFetchMangaInfoError(e) } withUIContext { view?.onFetchMangaInfoError(e) }
} }
_state.value = successState.copy(isRefreshingInfo = false) updateSuccessState { it.copy(isRefreshingInfo = false) }
} }
} }
@ -417,7 +420,9 @@ class MangaPresenter(
manga = manga.copy() manga = manga.copy()
} }
_state.value = state.copy(manga = manga) updateSuccessState { successState ->
successState.copy(manga = manga)
}
} }
suspend fun smartSearchMerge(context: Context, manga: DomainManga, originalMangaId: Long): Manga { suspend fun smartSearchMerge(context: Context, manga: DomainManga, originalMangaId: Long): Manga {
@ -750,15 +755,16 @@ class MangaPresenter(
} }
private fun updateDownloadState(download: Download) { private fun updateDownloadState(download: Download) {
val successState = successState ?: return updateSuccessState { successState ->
val modifiedIndex = successState.chapters.indexOfFirst { it.chapter.id == download.chapter.id } val modifiedIndex = successState.chapters.indexOfFirst { it.chapter.id == download.chapter.id }
if (modifiedIndex >= 0) { if (modifiedIndex < 0) return@updateSuccessState successState
val newChapters = successState.chapters.toMutableList().apply { val newChapters = successState.chapters.toMutableList().apply {
val item = removeAt(modifiedIndex) val item = removeAt(modifiedIndex)
.copy(downloadState = download.status, downloadProgress = download.progress) .copy(downloadState = download.status, downloadProgress = download.progress)
add(modifiedIndex, item) add(modifiedIndex, item)
} }
_state.value = successState.copy(chapters = newChapters) successState.copy(chapters = newChapters)
} }
} }
@ -794,32 +800,33 @@ class MangaPresenter(
*/ */
private fun fetchChaptersFromSource(manualFetch: Boolean = false) { private fun fetchChaptersFromSource(manualFetch: Boolean = false) {
if (fetchChaptersJob?.isActive == true) return if (fetchChaptersJob?.isActive == true) return
val successState = successState ?: return
fetchChaptersJob = presenterScope.launchIO { fetchChaptersJob = presenterScope.launchIO {
_state.value = successState.copy(isRefreshingChapter = true) updateSuccessState { it.copy(isRefreshingChapter = true) }
try { try {
if (successState.source !is MergedSource) { successState?.let { successState ->
val chapters = successState.source.getChapterList(successState.manga.toMangaInfo()) if (successState.source !is MergedSource) {
.map { it.toSChapter() } val chapters = successState.source.getChapterList(successState.manga.toMangaInfo())
.map { it.toSChapter() }
val (newChapters, _) = syncChaptersWithSource.await( val (newChapters, _) = syncChaptersWithSource.await(
chapters, chapters,
successState.manga, successState.manga,
successState.source, successState.source,
) )
if (manualFetch) { if (manualFetch) {
val dbChapters = newChapters.map { it.toDbChapter() } val dbChapters = newChapters.map { it.toDbChapter() }
downloadNewChapters(dbChapters) downloadNewChapters(dbChapters)
}
} else {
successState.source.fetchChaptersForMergedManga(successState.manga, manualFetch, true, dedupe)
Unit
} }
} else {
successState.source.fetchChaptersForMergedManga(successState.manga, manualFetch, true, dedupe)
} }
} catch (e: Throwable) { } catch (e: Throwable) {
withUIContext { view?.onFetchChaptersError(e) } withUIContext { view?.onFetchChaptersError(e) }
} }
_state.value = successState.copy(isRefreshingChapter = false) updateSuccessState { it.copy(isRefreshingChapter = false) }
} }
} }
@ -926,26 +933,27 @@ class MangaPresenter(
* @param chapters the list of chapters to delete. * @param chapters the list of chapters to delete.
*/ */
fun deleteChapters(chapters: List<DomainChapter>) { fun deleteChapters(chapters: List<DomainChapter>) {
val successState = successState ?: return
launchIO { launchIO {
val chapters2 = chapters.map { it.toDbChapter() } val chapters2 = chapters.map { it.toDbChapter() }
try { try {
val deletedIds = downloadManager updateSuccessState { successState ->
.deleteChapters(chapters2, successState.manga.toDbManga(), successState.source) val deletedIds = downloadManager
.map { it.id } .deleteChapters(chapters2, successState.manga.toDbManga(), successState.source)
val deletedChapters = successState.chapters.filter { deletedIds.contains(it.chapter.id) } .map { it.id }
if (deletedChapters.isEmpty()) return@launchIO val deletedChapters = successState.chapters.filter { deletedIds.contains(it.chapter.id) }
if (deletedChapters.isEmpty()) return@updateSuccessState successState
// TODO: Don't do this fake status update // TODO: Don't do this fake status update
val newChapters = successState.chapters.toMutableList().apply { val newChapters = successState.chapters.toMutableList().apply {
deletedChapters.forEach { deletedChapters.forEach {
val index = indexOf(it) val index = indexOf(it)
val toAdd = removeAt(index) val toAdd = removeAt(index)
.copy(downloadState = Download.State.NOT_DOWNLOADED, downloadProgress = 0) .copy(downloadState = Download.State.NOT_DOWNLOADED, downloadProgress = 0)
add(index, toAdd) add(index, toAdd)
}
} }
successState.copy(chapters = newChapters)
} }
_state.value = successState.copy(chapters = newChapters)
} catch (e: Throwable) { } catch (e: Throwable) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
} }