manga-refresh-state (#8090)

This commit is contained in:
Ivan Iskandar 2022-09-28 04:49:21 +07:00 committed by GitHub
parent 633bd6eb46
commit 98a4f6cccb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 79 additions and 59 deletions

View file

@ -24,4 +24,8 @@ class GetMangaWithChapters(
suspend fun awaitManga(id: Long): Manga { suspend fun awaitManga(id: Long): Manga {
return mangaRepository.getMangaById(id) return mangaRepository.getMangaById(id)
} }
suspend fun awaitChapters(id: Long): List<Chapter> {
return chapterRepository.getChapterByMangaId(id)
}
} }

View file

@ -295,7 +295,7 @@ private fun MangaScreenSmallImpl(
val topPadding = contentPadding.calculateTopPadding() val topPadding = contentPadding.calculateTopPadding()
SwipeRefresh( SwipeRefresh(
state = rememberSwipeRefreshState(state.isRefreshingInfo || state.isRefreshingChapter), state = rememberSwipeRefreshState(state.isRefreshingData),
onRefresh = onRefresh, onRefresh = onRefresh,
swipeEnabled = !chapters.any { it.selected }, swipeEnabled = !chapters.any { it.selected },
indicatorPadding = contentPadding, indicatorPadding = contentPadding,
@ -429,7 +429,7 @@ fun MangaScreenLargeImpl(
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues() val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
val (topBarHeight, onTopBarHeightChanged) = remember { mutableStateOf(0) } val (topBarHeight, onTopBarHeightChanged) = remember { mutableStateOf(0) }
SwipeRefresh( SwipeRefresh(
state = rememberSwipeRefreshState(state.isRefreshingInfo || state.isRefreshingChapter), state = rememberSwipeRefreshState(state.isRefreshingData),
onRefresh = onRefresh, onRefresh = onRefresh,
swipeEnabled = !chapters.any { it.selected }, swipeEnabled = !chapters.any { it.selected },
indicatorPadding = PaddingValues( indicatorPadding = PaddingValues(

View file

@ -47,6 +47,7 @@ import eu.kanade.tachiyomi.util.chapter.getChapterSort
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.launchNonCancellable
import eu.kanade.tachiyomi.util.lang.toRelativeString 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.lang.withUIContext
import eu.kanade.tachiyomi.util.preference.asHotFlow import eu.kanade.tachiyomi.util.preference.asHotFlow
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
@ -67,6 +68,7 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -84,7 +86,6 @@ class MangaPresenter(
val mangaId: Long, val mangaId: Long,
val isFromSource: Boolean, val isFromSource: Boolean,
private val basePreferences: BasePreferences = Injekt.get(), private val basePreferences: BasePreferences = Injekt.get(),
private val uiPreferences: UiPreferences = Injekt.get(),
private val downloadPreferences: DownloadPreferences = Injekt.get(), private val downloadPreferences: DownloadPreferences = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(),
private val trackManager: TrackManager = Injekt.get(), private val trackManager: TrackManager = Injekt.get(),
@ -112,9 +113,6 @@ class MangaPresenter(
private val successState: MangaScreenState.Success? private val successState: MangaScreenState.Success?
get() = state.value as? 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 observeDownloadsStatusJob: Job? = null
private var observeDownloadsPageJob: Job? = null private var observeDownloadsPageJob: Job? = null
@ -161,57 +159,30 @@ class MangaPresenter(
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
presenterScope.launchIO { val toChapterItemsParams: List<DomainChapter>.(manga: DomainManga) -> List<ChapterItem> = { manga ->
val manga = getMangaAndChapters.awaitManga(mangaId) val uiPreferences = Injekt.get<UiPreferences>()
toChapterItems(
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<SourceManager>().getOrStub(manga.source),
isFromSource = isFromSource,
trackingAvailable = trackManager.hasLoggedServices(),
chapters = emptyList(),
isRefreshingChapter = true,
isIncognitoMode = incognitoMode,
isDownloadedOnlyMode = downloadedOnlyMode,
dialog = null,
)
}
getMangaAndChapters.subscribe(mangaId)
.distinctUntilChanged()
.collectLatest { (manga, chapters) ->
val chapterItems = chapters.toChapterItems(
context = view?.activity ?: Injekt.get<Application>(), context = view?.activity ?: Injekt.get<Application>(),
manga = manga, manga = manga,
dateRelativeTime = uiPreferences.relativeTime().get(), dateRelativeTime = uiPreferences.relativeTime().get(),
dateFormat = UiPreferences.dateFormat(uiPreferences.dateFormat().get()), dateFormat = UiPreferences.dateFormat(uiPreferences.dateFormat().get()),
) )
}
// For UI changes
presenterScope.launch {
getMangaAndChapters.subscribe(mangaId)
.distinctUntilChanged()
.collectLatest { (manga, chapters) ->
val chapterItems = chapters.toChapterItemsParams(manga)
updateSuccessState { updateSuccessState {
it.copy( it.copy(
manga = manga, manga = manga,
chapters = chapterItems, chapters = chapterItems,
isRefreshingChapter = false,
isRefreshingInfo = false,
) )
} }
observeTrackers()
observeTrackingCount()
observeDownloads() observeDownloads()
if (!manga.initialized) {
fetchAllFromSource(manualFetch = false)
} else if (chapterItems.isEmpty()) {
fetchChaptersFromSource()
}
} }
} }
@ -222,11 +193,63 @@ class MangaPresenter(
basePreferences.downloadedOnly() basePreferences.downloadedOnly()
.asHotFlow { downloadedOnlyMode = it } .asHotFlow { downloadedOnlyMode = it }
.launchIn(presenterScope) .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<SourceManager>().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) { fun fetchAllFromSource(manualFetch: Boolean = true) {
fetchMangaFromSource(manualFetch) presenterScope.launch {
fetchChaptersFromSource(manualFetch) updateSuccessState { it.copy(isRefreshingData = true) }
val fetchFromSourceTasks = listOf(
async { fetchMangaFromSource(manualFetch) },
async { fetchChaptersFromSource(manualFetch) },
)
fetchFromSourceTasks.awaitAll()
updateSuccessState { it.copy(isRefreshingData = false) }
}
} }
// Manga info - start // Manga info - start
@ -234,10 +257,8 @@ class MangaPresenter(
/** /**
* Fetch manga information from source. * Fetch manga information from source.
*/ */
private fun fetchMangaFromSource(manualFetch: Boolean = false) { private suspend fun fetchMangaFromSource(manualFetch: Boolean = false) {
if (fetchMangaJob?.isActive == true) return withIOContext {
fetchMangaJob = presenterScope.launchIO {
updateSuccessState { it.copy(isRefreshingInfo = true) }
try { try {
successState?.let { successState?.let {
val networkManga = it.source.getMangaDetails(it.manga.toSManga()) val networkManga = it.source.getMangaDetails(it.manga.toSManga())
@ -246,7 +267,6 @@ class MangaPresenter(
} catch (e: Throwable) { } catch (e: Throwable) {
withUIContext { view?.onFetchMangaInfoError(e) } withUIContext { view?.onFetchMangaInfoError(e) }
} }
updateSuccessState { it.copy(isRefreshingInfo = false) }
} }
} }
@ -534,10 +554,8 @@ class MangaPresenter(
/** /**
* Requests an updated list of chapters from the source. * Requests an updated list of chapters from the source.
*/ */
private fun fetchChaptersFromSource(manualFetch: Boolean = false) { private suspend fun fetchChaptersFromSource(manualFetch: Boolean = false) {
if (fetchChaptersJob?.isActive == true) return withIOContext {
fetchChaptersJob = presenterScope.launchIO {
updateSuccessState { it.copy(isRefreshingChapter = true) }
try { try {
successState?.let { successState -> successState?.let { successState ->
val chapters = successState.source.getChapterList(successState.manga.toSManga()) val chapters = successState.source.getChapterList(successState.manga.toSManga())
@ -555,7 +573,6 @@ class MangaPresenter(
} catch (e: Throwable) { } catch (e: Throwable) {
withUIContext { view?.onFetchChaptersError(e) } withUIContext { view?.onFetchChaptersError(e) }
} }
updateSuccessState { it.copy(isRefreshingChapter = false) }
} }
} }
@ -1082,8 +1099,7 @@ sealed class MangaScreenState {
val chapters: List<ChapterItem>, val chapters: List<ChapterItem>,
val trackingAvailable: Boolean = false, val trackingAvailable: Boolean = false,
val trackingCount: Int = 0, val trackingCount: Int = 0,
val isRefreshingInfo: Boolean = false, val isRefreshingData: Boolean = false,
val isRefreshingChapter: Boolean = false,
val isIncognitoMode: Boolean = false, val isIncognitoMode: Boolean = false,
val isDownloadedOnlyMode: Boolean = false, val isDownloadedOnlyMode: Boolean = false,
val dialog: MangaPresenter.Dialog? = null, val dialog: MangaPresenter.Dialog? = null,