From 98a4f6cccb85d39fb37c95aa99da71bb7abfd134 Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Wed, 28 Sep 2022 04:49:21 +0700 Subject: [PATCH] manga-refresh-state (#8090) --- .../manga/interactor/GetMangaWithChapters.kt | 4 + .../kanade/presentation/manga/MangaScreen.kt | 4 +- .../tachiyomi/ui/manga/MangaPresenter.kt | 130 ++++++++++-------- 3 files changed, 79 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/GetMangaWithChapters.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/GetMangaWithChapters.kt index 0386a11fcb..5189c42e06 100644 --- a/app/src/main/java/eu/kanade/domain/manga/interactor/GetMangaWithChapters.kt +++ b/app/src/main/java/eu/kanade/domain/manga/interactor/GetMangaWithChapters.kt @@ -24,4 +24,8 @@ class GetMangaWithChapters( suspend fun awaitManga(id: Long): Manga { return mangaRepository.getMangaById(id) } + + suspend fun awaitChapters(id: Long): List { + return chapterRepository.getChapterByMangaId(id) + } } diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt index 1be654d77d..57dba2adc8 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -295,7 +295,7 @@ private fun MangaScreenSmallImpl( val topPadding = contentPadding.calculateTopPadding() SwipeRefresh( - state = rememberSwipeRefreshState(state.isRefreshingInfo || state.isRefreshingChapter), + state = rememberSwipeRefreshState(state.isRefreshingData), onRefresh = onRefresh, swipeEnabled = !chapters.any { it.selected }, indicatorPadding = contentPadding, @@ -429,7 +429,7 @@ fun MangaScreenLargeImpl( val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues() val (topBarHeight, onTopBarHeightChanged) = remember { mutableStateOf(0) } SwipeRefresh( - state = rememberSwipeRefreshState(state.isRefreshingInfo || state.isRefreshingChapter), + state = rememberSwipeRefreshState(state.isRefreshingData), onRefresh = onRefresh, swipeEnabled = !chapters.any { it.selected }, indicatorPadding = PaddingValues( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index 91c48d9599..f8db3f2f05 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt @@ -47,6 +47,7 @@ import eu.kanade.tachiyomi.util.chapter.getChapterSort import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.toRelativeString +import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.preference.asHotFlow import eu.kanade.tachiyomi.util.removeCovers @@ -67,6 +68,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext @@ -84,7 +86,6 @@ class MangaPresenter( val mangaId: Long, val isFromSource: Boolean, private val basePreferences: BasePreferences = Injekt.get(), - private val uiPreferences: UiPreferences = Injekt.get(), private val downloadPreferences: DownloadPreferences = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(), private val trackManager: TrackManager = Injekt.get(), @@ -112,9 +113,6 @@ class MangaPresenter( private val successState: MangaScreenState.Success? get() = state.value as? MangaScreenState.Success - private var fetchMangaJob: Job? = null - private var fetchChaptersJob: Job? = null - private var observeDownloadsStatusJob: Job? = null private var observeDownloadsPageJob: Job? = null @@ -161,57 +159,30 @@ class MangaPresenter( override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) - presenterScope.launchIO { - val manga = getMangaAndChapters.awaitManga(mangaId) - - if (!manga.favorite) { - setMangaDefaultChapterFlags.await(manga) - } - - // Show what we have earlier. - // Defaults set by the block above won't apply until next update but it doesn't matter - // since we don't have any chapter yet. - _state.update { - MangaScreenState.Success( - manga = manga, - source = Injekt.get().getOrStub(manga.source), - isFromSource = isFromSource, - trackingAvailable = trackManager.hasLoggedServices(), - chapters = emptyList(), - isRefreshingChapter = true, - isIncognitoMode = incognitoMode, - isDownloadedOnlyMode = downloadedOnlyMode, - dialog = null, - ) - } + val toChapterItemsParams: List.(manga: DomainManga) -> List = { manga -> + val uiPreferences = Injekt.get() + toChapterItems( + context = view?.activity ?: Injekt.get(), + manga = manga, + dateRelativeTime = uiPreferences.relativeTime().get(), + dateFormat = UiPreferences.dateFormat(uiPreferences.dateFormat().get()), + ) + } + // For UI changes + presenterScope.launch { getMangaAndChapters.subscribe(mangaId) .distinctUntilChanged() .collectLatest { (manga, chapters) -> - val chapterItems = chapters.toChapterItems( - context = view?.activity ?: Injekt.get(), - manga = manga, - dateRelativeTime = uiPreferences.relativeTime().get(), - dateFormat = UiPreferences.dateFormat(uiPreferences.dateFormat().get()), - ) + val chapterItems = chapters.toChapterItemsParams(manga) updateSuccessState { it.copy( manga = manga, chapters = chapterItems, - isRefreshingChapter = false, - isRefreshingInfo = false, ) } - observeTrackers() - observeTrackingCount() observeDownloads() - - if (!manga.initialized) { - fetchAllFromSource(manualFetch = false) - } else if (chapterItems.isEmpty()) { - fetchChaptersFromSource() - } } } @@ -222,11 +193,63 @@ class MangaPresenter( basePreferences.downloadedOnly() .asHotFlow { downloadedOnlyMode = it } .launchIn(presenterScope) + + // This block runs once on create + presenterScope.launchIO { + val manga = getMangaAndChapters.awaitManga(mangaId) + val chapters = getMangaAndChapters.awaitChapters(mangaId) + .toChapterItemsParams(manga) + + if (!manga.favorite) { + setMangaDefaultChapterFlags.await(manga) + } + + val needRefreshInfo = !manga.initialized + val needRefreshChapter = chapters.isEmpty() + + // Show what we have earlier. + _state.update { + MangaScreenState.Success( + manga = manga, + source = Injekt.get().getOrStub(manga.source), + isFromSource = isFromSource, + trackingAvailable = trackManager.hasLoggedServices(), + chapters = chapters, + isRefreshingData = needRefreshInfo || needRefreshChapter, + isIncognitoMode = incognitoMode, + isDownloadedOnlyMode = downloadedOnlyMode, + dialog = null, + ) + } + + // Start observe tracking since it only needs mangaId + observeTrackers() + observeTrackingCount() + + // Fetch info-chapters when needed + if (presenterScope.isActive) { + val fetchFromSourceTasks = listOf( + async { if (needRefreshInfo) fetchMangaFromSource() }, + async { if (needRefreshChapter) fetchChaptersFromSource() }, + ) + fetchFromSourceTasks.awaitAll() + } + + // Initial loading finished + updateSuccessState { it.copy(isRefreshingData = false) } + } } fun fetchAllFromSource(manualFetch: Boolean = true) { - fetchMangaFromSource(manualFetch) - fetchChaptersFromSource(manualFetch) + presenterScope.launch { + updateSuccessState { it.copy(isRefreshingData = true) } + val fetchFromSourceTasks = listOf( + async { fetchMangaFromSource(manualFetch) }, + async { fetchChaptersFromSource(manualFetch) }, + ) + fetchFromSourceTasks.awaitAll() + updateSuccessState { it.copy(isRefreshingData = false) } + } } // Manga info - start @@ -234,10 +257,8 @@ class MangaPresenter( /** * Fetch manga information from source. */ - private fun fetchMangaFromSource(manualFetch: Boolean = false) { - if (fetchMangaJob?.isActive == true) return - fetchMangaJob = presenterScope.launchIO { - updateSuccessState { it.copy(isRefreshingInfo = true) } + private suspend fun fetchMangaFromSource(manualFetch: Boolean = false) { + withIOContext { try { successState?.let { val networkManga = it.source.getMangaDetails(it.manga.toSManga()) @@ -246,7 +267,6 @@ class MangaPresenter( } catch (e: Throwable) { withUIContext { view?.onFetchMangaInfoError(e) } } - updateSuccessState { it.copy(isRefreshingInfo = false) } } } @@ -534,10 +554,8 @@ class MangaPresenter( /** * Requests an updated list of chapters from the source. */ - private fun fetchChaptersFromSource(manualFetch: Boolean = false) { - if (fetchChaptersJob?.isActive == true) return - fetchChaptersJob = presenterScope.launchIO { - updateSuccessState { it.copy(isRefreshingChapter = true) } + private suspend fun fetchChaptersFromSource(manualFetch: Boolean = false) { + withIOContext { try { successState?.let { successState -> val chapters = successState.source.getChapterList(successState.manga.toSManga()) @@ -555,7 +573,6 @@ class MangaPresenter( } catch (e: Throwable) { withUIContext { view?.onFetchChaptersError(e) } } - updateSuccessState { it.copy(isRefreshingChapter = false) } } } @@ -1082,8 +1099,7 @@ sealed class MangaScreenState { val chapters: List, val trackingAvailable: Boolean = false, val trackingCount: Int = 0, - val isRefreshingInfo: Boolean = false, - val isRefreshingChapter: Boolean = false, + val isRefreshingData: Boolean = false, val isIncognitoMode: Boolean = false, val isDownloadedOnlyMode: Boolean = false, val dialog: MangaPresenter.Dialog? = null,