|
@@ -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,72 +159,97 @@ class MangaPresenter(
|
|
|
override fun onCreate(savedState: Bundle?) {
|
|
|
super.onCreate(savedState)
|
|
|
|
|
|
+ val toChapterItemsParams: List<DomainChapter>.(manga: DomainManga) -> List<ChapterItem> = { manga ->
|
|
|
+ val uiPreferences = Injekt.get<UiPreferences>()
|
|
|
+ toChapterItems(
|
|
|
+ context = view?.activity ?: Injekt.get<Application>(),
|
|
|
+ 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.toChapterItemsParams(manga)
|
|
|
+ updateSuccessState {
|
|
|
+ it.copy(
|
|
|
+ manga = manga,
|
|
|
+ chapters = chapterItems,
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ observeDownloads()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ basePreferences.incognitoMode()
|
|
|
+ .asHotFlow { incognitoMode = it }
|
|
|
+ .launchIn(presenterScope)
|
|
|
+
|
|
|
+ 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.
|
|
|
- // 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,
|
|
|
+ chapters = chapters,
|
|
|
+ isRefreshingData = needRefreshInfo || needRefreshChapter,
|
|
|
isIncognitoMode = incognitoMode,
|
|
|
isDownloadedOnlyMode = downloadedOnlyMode,
|
|
|
dialog = null,
|
|
|
)
|
|
|
}
|
|
|
|
|
|
- getMangaAndChapters.subscribe(mangaId)
|
|
|
- .distinctUntilChanged()
|
|
|
- .collectLatest { (manga, chapters) ->
|
|
|
- val chapterItems = chapters.toChapterItems(
|
|
|
- context = view?.activity ?: Injekt.get<Application>(),
|
|
|
- manga = manga,
|
|
|
- dateRelativeTime = uiPreferences.relativeTime().get(),
|
|
|
- dateFormat = UiPreferences.dateFormat(uiPreferences.dateFormat().get()),
|
|
|
- )
|
|
|
- updateSuccessState {
|
|
|
- it.copy(
|
|
|
- manga = manga,
|
|
|
- chapters = chapterItems,
|
|
|
- isRefreshingChapter = false,
|
|
|
- isRefreshingInfo = false,
|
|
|
- )
|
|
|
- }
|
|
|
+ // Start observe tracking since it only needs mangaId
|
|
|
+ observeTrackers()
|
|
|
+ observeTrackingCount()
|
|
|
|
|
|
- observeTrackers()
|
|
|
- observeTrackingCount()
|
|
|
- observeDownloads()
|
|
|
+ // Fetch info-chapters when needed
|
|
|
+ if (presenterScope.isActive) {
|
|
|
+ val fetchFromSourceTasks = listOf(
|
|
|
+ async { if (needRefreshInfo) fetchMangaFromSource() },
|
|
|
+ async { if (needRefreshChapter) fetchChaptersFromSource() },
|
|
|
+ )
|
|
|
+ fetchFromSourceTasks.awaitAll()
|
|
|
+ }
|
|
|
|
|
|
- if (!manga.initialized) {
|
|
|
- fetchAllFromSource(manualFetch = false)
|
|
|
- } else if (chapterItems.isEmpty()) {
|
|
|
- fetchChaptersFromSource()
|
|
|
- }
|
|
|
- }
|
|
|
+ // Initial loading finished
|
|
|
+ updateSuccessState { it.copy(isRefreshingData = false) }
|
|
|
}
|
|
|
-
|
|
|
- basePreferences.incognitoMode()
|
|
|
- .asHotFlow { incognitoMode = it }
|
|
|
- .launchIn(presenterScope)
|
|
|
-
|
|
|
- basePreferences.downloadedOnly()
|
|
|
- .asHotFlow { downloadedOnlyMode = it }
|
|
|
- .launchIn(presenterScope)
|
|
|
}
|
|
|
|
|
|
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<ChapterItem>,
|
|
|
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,
|