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

This commit is contained in:
Ivan Iskandar 2022-06-26 20:45:06 +07:00 committed by GitHub
parent c9770eea2f
commit 0e0c1dcdc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -24,7 +24,6 @@ import eu.kanade.tachiyomi.data.database.models.toDomainChapter
import eu.kanade.tachiyomi.data.download.DownloadManager 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.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
@ -52,6 +51,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import logcat.LogPriority import logcat.LogPriority
import rx.Observable import rx.Observable
@ -60,7 +60,6 @@ import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.DateFormat import java.text.DateFormat
import eu.kanade.domain.chapter.model.Chapter as DomainChapter import eu.kanade.domain.chapter.model.Chapter as DomainChapter
import eu.kanade.domain.manga.model.Manga as DomainManga import eu.kanade.domain.manga.model.Manga as DomainManga
@ -108,8 +107,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
@ -126,6 +123,13 @@ class MangaPresenter(
val processedChapters: Sequence<ChapterItem>? val processedChapters: Sequence<ChapterItem>?
get() = successState?.processedChapters get() = successState?.processedChapters
/**
* 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)
@ -139,27 +143,28 @@ class MangaPresenter(
getMangaAndChapters.subscribe(mangaId) getMangaAndChapters.subscribe(mangaId)
.collectLatest { (manga, chapters) -> .collectLatest { (manga, chapters) ->
val chapterItems = chapters.toChapterItems(manga) val chapterItems = chapters.toChapterItems(manga)
val currentState = _state.value _state.update { currentState ->
_state.value = when (currentState) { when (currentState) {
// Initialize success state // Initialize success state
MangaScreenState.Loading -> MangaScreenState.Success( MangaScreenState.Loading -> MangaScreenState.Success(
manga = manga, manga = manga,
source = Injekt.get<SourceManager>().getOrStub(manga.source), source = Injekt.get<SourceManager>().getOrStub(manga.source),
dateRelativeTime = preferences.relativeTime().get(), dateRelativeTime = preferences.relativeTime().get(),
dateFormat = preferences.dateFormat(), dateFormat = preferences.dateFormat(),
isFromSource = isFromSource, isFromSource = isFromSource,
trackingAvailable = trackManager.hasLoggedServices(), trackingAvailable = trackManager.hasLoggedServices(),
chapters = chapterItems, chapters = chapterItems,
).also { ).also {
getTrackingObservable(manga) getTrackingObservable(manga)
.subscribeLatestCache( .subscribeLatestCache(
{ _, count -> successState?.let { _state.value = it.copy(trackingCount = count) } }, { _, count -> updateSuccessState { it.copy(trackingCount = count) } },
{ _, error -> logcat(LogPriority.ERROR, error) }, { _, error -> logcat(LogPriority.ERROR, error) },
) )
} }
// Update state // Update state
is MangaScreenState.Success -> currentState.copy(manga = manga, chapters = chapterItems) is MangaScreenState.Success -> currentState.copy(manga = manga, chapters = chapterItems)
}
} }
fetchTrackers() fetchTrackers()
@ -173,17 +178,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)
} }
@ -213,16 +214,17 @@ 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) {
withUIContext { view?.onFetchMangaInfoError(e) } withUIContext { view?.onFetchMangaInfoError(e) }
} }
_state.value = successState.copy(isRefreshingInfo = false) updateSuccessState { it.copy(isRefreshingInfo = false) }
} }
} }
@ -402,15 +404,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)
} }
} }
@ -436,28 +439,28 @@ 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 {
val chapters = successState.source.getChapterList(successState.manga.toMangaInfo()) successState?.let { successState ->
.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)
}
} }
} 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) }
} }
} }
@ -551,26 +554,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)
} }