revert: "refactor(manga): Slowly using flow"

I'll just redo this in the morning
This commit is contained in:
Ahmad Ansori Palembani 2024-12-16 20:55:15 +07:00
parent 2ef1195a90
commit f8d74a6b2f
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
3 changed files with 134 additions and 145 deletions

View file

@ -194,7 +194,7 @@ class MangaDetailsController :
} }
} }
private val manga: Manga? get() = presenter.currentManga.value private val manga: Manga? get() = if (presenter.isMangaLateInitInitialized()) presenter.manga else null
private var colorAnimator: ValueAnimator? = null private var colorAnimator: ValueAnimator? = null
override val presenter: MangaDetailsPresenter override val presenter: MangaDetailsPresenter
private var coverColor: Int? = null private var coverColor: Int? = null
@ -674,7 +674,7 @@ class MangaDetailsController :
returningFromReader = false returningFromReader = false
runBlocking { runBlocking {
val itemAnimator = binding.recycler.itemAnimator val itemAnimator = binding.recycler.itemAnimator
val chapters = withTimeoutOrNull(1000) { presenter.setAndGetChapters() } ?: return@runBlocking val chapters = withTimeoutOrNull(1000) { presenter.getChaptersNow() } ?: return@runBlocking
binding.recycler.itemAnimator = null binding.recycler.itemAnimator = null
tabletAdapter?.notifyItemChanged(0) tabletAdapter?.notifyItemChanged(0)
adapter?.setChapters(chapters) adapter?.setChapters(chapters)
@ -821,15 +821,15 @@ class MangaDetailsController :
updateMenuVisibility(activityBinding?.toolbar?.menu) updateMenuVisibility(activityBinding?.toolbar?.menu)
} }
fun updateChapters(fetchFromSource: Boolean = false) { fun updateChapters(chapters: List<ChapterItem>) {
view ?: return view ?: return
binding.swipeRefresh.isRefreshing = presenter.isLoading binding.swipeRefresh.isRefreshing = presenter.isLoading
if (presenter.chapters.isEmpty() && fromCatalogue && !presenter.hasRequested && fetchFromSource) { if (presenter.chapters.isEmpty() && fromCatalogue && !presenter.hasRequested) {
launchUI { binding.swipeRefresh.isRefreshing = true } launchUI { binding.swipeRefresh.isRefreshing = true }
presenter.refreshChapters() presenter.fetchChaptersFromSource()
} }
tabletAdapter?.notifyItemChanged(0) tabletAdapter?.notifyItemChanged(0)
adapter?.setChapters(presenter.chapters) adapter?.setChapters(chapters)
addMangaHeader() addMangaHeader()
colorToolbar(binding.recycler.canScrollVertically(-1)) colorToolbar(binding.recycler.canScrollVertically(-1))
updateMenuVisibility(activityBinding?.toolbar?.menu) updateMenuVisibility(activityBinding?.toolbar?.menu)

View file

@ -34,7 +34,6 @@ 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
import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.network.HttpException
import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
@ -77,15 +76,11 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
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.flow.updateAndGet
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -132,15 +127,11 @@ class MangaDetailsPresenter(
private val networkPreferences: NetworkPreferences by injectLazy() private val networkPreferences: NetworkPreferences by injectLazy()
private val currentMangaInternal = MutableStateFlow<Manga?>(null) // private val currentMangaInternal: MutableStateFlow<Manga?> = MutableStateFlow(null)
val currentManga = currentMangaInternal.asStateFlow() // val currentManga get() = currentMangaInternal.asStateFlow()
/** lateinit var manga: Manga
* Unsafe, call only after currentManga is no longer null fun isMangaLateInitInitialized() = ::manga.isInitialized
*/
var manga: Manga
get() = currentManga.value!!
set(value) { currentMangaInternal.value = value }
private val customMangaManager: CustomMangaManager by injectLazy() private val customMangaManager: CustomMangaManager by injectLazy()
private val mangaShortcutManager: MangaShortcutManager by injectLazy() private val mangaShortcutManager: MangaShortcutManager by injectLazy()
@ -160,12 +151,8 @@ class MangaDetailsPresenter(
var trackList: List<TrackItem> = emptyList() var trackList: List<TrackItem> = emptyList()
private val currentChaptersInternal = MutableStateFlow<List<ChapterItem>>(emptyList()) var chapters: List<ChapterItem> = emptyList()
val currentChapters = currentChaptersInternal.asStateFlow() private set
var chapters: List<ChapterItem>
get() = currentChapters.value
private set(value) { currentChaptersInternal.value = value }
var allChapters: List<ChapterItem> = emptyList() var allChapters: List<ChapterItem> = emptyList()
private set private set
@ -199,7 +186,7 @@ class MangaDetailsPresenter(
val controller = view ?: return val controller = view ?: return
isLockedFromSearch = controller.shouldLockIfNeeded && SecureActivityDelegate.shouldBeLocked() isLockedFromSearch = controller.shouldLockIfNeeded && SecureActivityDelegate.shouldBeLocked()
if (currentManga.value == null) runBlocking { refreshMangaFromDb() } if (!::manga.isInitialized) runBlocking { refreshMangaFromDb() }
syncData() syncData()
presenterScope.launchUI { presenterScope.launchUI {
@ -217,22 +204,6 @@ class MangaDetailsPresenter(
presenterScope.launchIO { presenterScope.launchIO {
downloadManager.queueState.collectLatest(::onQueueUpdate) downloadManager.queueState.collectLatest(::onQueueUpdate)
} }
presenterScope.launchUI {
currentManga.collectLatest {
if (it == null) return@collectLatest
}
}
presenterScope.launchIO {
currentChapters.collectLatest { chapters ->
allChapters = if (!isScanlatorFiltered()) chapters else getChapter.awaitAll(mangaId, false).map { it.toModel() }
allChapterScanlators = allChapters.mapNotNull { it.chapter.scanlator }.toSet()
withUIContext {
controller.updateChapters(allChapters.isEmpty())
}
}
}
runBlocking { runBlocking {
tracks = getTrack.awaitAllByMangaId(mangaId) tracks = getTrack.awaitAllByMangaId(mangaId)
@ -258,7 +229,8 @@ class MangaDetailsPresenter(
controller.updateHeader() controller.updateHeader()
refreshAll() refreshAll()
} else { } else {
runBlocking { chapters = getChapters() } runBlocking { getChapters() }
controller.updateChapters(this.chapters)
getHistory() getHistory()
} }
@ -271,14 +243,16 @@ class MangaDetailsPresenter(
fun fetchChapters(andTracking: Boolean = true) { fun fetchChapters(andTracking: Boolean = true) {
presenterScope.launch { presenterScope.launch {
setCurrentChapters(getChapters()) getChapters()
if (andTracking) fetchTracks() if (andTracking) fetchTracks()
withContext(Dispatchers.Main) { view?.updateChapters(chapters) }
getHistory() getHistory()
} }
} }
fun setCurrentManga(manga: Manga?) { fun setCurrentManga(manga: Manga?) {
currentMangaInternal.update { manga } // currentMangaInternal.update { manga }
this.manga = manga!!
} }
// TODO: Use flow to "sync" data instead // TODO: Use flow to "sync" data instead
@ -290,18 +264,20 @@ class MangaDetailsPresenter(
} }
} }
// TODO: Use getChapter.subscribe() flow instead suspend fun getChaptersNow(): List<ChapterItem> {
suspend fun setAndGetChapters(): List<ChapterItem> { getChapters()
return currentChaptersInternal.updateAndGet { getChapters() } return chapters
} }
// TODO: Use getChapter.subscribe() flow instead private suspend fun getChapters(queue: List<Download> = downloadManager.queueState.value) {
private fun setCurrentChapters(chapters: List<ChapterItem>) { val chapters = getChapter.awaitAll(mangaId, isScanlatorFiltered()).map { it.toModel() }
currentChaptersInternal.update { chapters } allChapters = if (!isScanlatorFiltered()) chapters else getChapter.awaitAll(mangaId, false).map { it.toModel() }
}
private suspend fun getChapters(): List<ChapterItem> { // Find downloaded chapters
return getChapter.awaitAll(mangaId, isScanlatorFiltered()).map { it.toModel() } setDownloadedChapters(chapters, queue)
allChapterScanlators = allChapters.mapNotNull { it.chapter.scanlator }.toSet()
this.chapters = applyChapterFilters(chapters)
} }
private fun getHistory() { private fun getHistory() {
@ -418,7 +394,7 @@ class MangaDetailsPresenter(
download = null download = null
} }
view?.updateChapters() view?.updateChapters(this.chapters)
downloadManager.deleteChapters(listOf(chapter), manga, source, true) downloadManager.deleteChapters(listOf(chapter), manga, source, true)
} }
@ -436,7 +412,7 @@ class MangaDetailsPresenter(
} }
} }
if (update) view?.updateChapters() if (update) view?.updateChapters(this.chapters)
if (isEverything) { if (isEverything) {
downloadManager.deleteManga(manga, source) downloadManager.deleteManga(manga, source)
@ -456,93 +432,126 @@ class MangaDetailsPresenter(
if (view?.isNotOnline() == true && !manga.isLocal()) return if (view?.isNotOnline() == true && !manga.isLocal()) return
presenterScope.launch { presenterScope.launch {
isLoading = true isLoading = true
val tasks = listOf( var mangaError: java.lang.Exception? = null
async { fetchMangaFromSource() }, var chapterError: java.lang.Exception? = null
async { fetchChaptersFromSource() }, val chapters = async(Dispatchers.IO) {
) try {
tasks.awaitAll() source.getChapterList(manga.copy())
isLoading = false } catch (e: Exception) {
} chapterError = e
} emptyList()
}
}
val nManga = async(Dispatchers.IO) {
try {
source.getMangaDetails(manga.copy())
} catch (e: java.lang.Exception) {
mangaError = e
null
}
}
private suspend fun fetchMangaFromSource() { val networkManga = nManga.await()
try { if (networkManga != null) {
val manga = manga.copy() manga.prepareCoverUpdate(coverCache, networkManga, false)
val networkManga = source.getMangaDetails(manga) manga.copyFrom(networkManga)
manga.initialized = true
manga.prepareCoverUpdate(coverCache, networkManga, false) updateManga.await(manga.toMangaUpdate())
manga.copyFrom(networkManga)
manga.initialized = true
updateManga.await(manga.toMangaUpdate()) launchIO {
val request =
ImageRequest.Builder(preferences.context).data(manga.cover())
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.WRITE_ONLY)
.build()
setCurrentManga(manga) if (preferences.context.imageLoader.execute(request) is SuccessResult) {
withContext(Dispatchers.Main) {
presenterScope.launchNonCancellableIO { view?.setPaletteColor()
val request = }
ImageRequest.Builder(preferences.context).data(manga.cover())
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.WRITE_ONLY)
.build()
if (preferences.context.imageLoader.execute(request) is SuccessResult) {
withContext(Dispatchers.Main) {
view?.setPaletteColor()
} }
} }
} }
} catch (e: Exception) { val finChapters = chapters.await()
if (e is HttpException && e.code == 103) return if (finChapters.isNotEmpty()) {
val newChapters = withIOContext { syncChaptersWithSource(finChapters, manga, source) }
withUIContext { if (newChapters.first.isNotEmpty()) {
view?.showError(trimException(e))
}
}
}
private suspend fun fetchChaptersFromSource(manualFetch: Boolean = true) {
try {
withIOContext {
val chapters = source.getChapterList(manga.copy())
val (added, removed) = syncChaptersWithSource(chapters, manga, source)
if (added.isNotEmpty() && manualFetch) {
if (manga.shouldDownloadNewChapters(preferences)) { if (manga.shouldDownloadNewChapters(preferences)) {
downloadChapters(added.sortedBy { it.chapter_number }.map { it.toModel() }) downloadChapters(
} newChapters.first.sortedBy { it.chapter_number }
withUIContext { .map { it.toModel() },
view?.view?.context?.let { mangaShortcutManager.updateShortcuts(it) } )
} }
view?.view?.context?.let { mangaShortcutManager.updateShortcuts(it) }
} }
if (removed.isNotEmpty() && manualFetch) { if (newChapters.second.isNotEmpty()) {
val removedChaptersId = removed.map { it.id } val removedChaptersId = newChapters.second.map { it.id }
val removedChapters = this@MangaDetailsPresenter.chapters.filter { val removedChapters = this@MangaDetailsPresenter.chapters.filter {
it.id in removedChaptersId && it.isDownloaded it.id in removedChaptersId && it.isDownloaded
} }
if (removedChapters.isNotEmpty()) { if (removedChapters.isNotEmpty()) {
withUIContext { withContext(Dispatchers.Main) {
view?.showChaptersRemovedPopup(removedChapters) view?.showChaptersRemovedPopup(
removedChapters,
)
} }
} }
} }
setCurrentChapters(getChapters()) getChapters()
getHistory()
} }
} catch (e: Exception) { isLoading = false
withUIContext { if (chapterError == null) {
view?.showError(trimException(e)) withContext(Dispatchers.Main) {
view?.updateChapters(this@MangaDetailsPresenter.chapters)
}
} }
if (chapterError != null) {
withContext(Dispatchers.Main) {
view?.showError(
trimException(chapterError!!),
)
}
return@launch
} else if (mangaError != null) {
withContext(Dispatchers.Main) {
view?.showError(
trimException(mangaError!!),
)
}
}
getHistory()
} }
} }
/** /**
* Requests an updated list of chapters from the source. * Requests an updated list of chapters from the source.
*/ */
fun refreshChapters() { fun fetchChaptersFromSource() {
presenterScope.launchUI { hasRequested = true
hasRequested = true isLoading = true
isLoading = true
fetchChaptersFromSource(true) presenterScope.launch(Dispatchers.IO) {
val chapters = try {
source.getChapterList(manga.copy())
} catch (e: Exception) {
withContext(Dispatchers.Main) { view?.showError(trimException(e)) }
return@launch
}
isLoading = false isLoading = false
try {
syncChaptersWithSource(chapters, manga, source)
getChapters()
withContext(Dispatchers.Main) {
view?.updateChapters(this@MangaDetailsPresenter.chapters)
}
getHistory()
} catch (e: java.lang.Exception) {
withContext(Dispatchers.Main) {
view?.showError(trimException(e))
}
}
} }
} }
@ -570,7 +579,8 @@ class MangaDetailsPresenter(
it.toProgressUpdate() it.toProgressUpdate()
} }
updateChapter.awaitAll(updates) updateChapter.awaitAll(updates)
setCurrentChapters(getChapters()) getChapters()
withContext(Dispatchers.Main) { view?.updateChapters(chapters) }
} }
} }
@ -599,7 +609,8 @@ class MangaDetailsPresenter(
if (read && deleteNow && preferences.removeAfterMarkedAsRead().get()) { if (read && deleteNow && preferences.removeAfterMarkedAsRead().get()) {
deleteChapters(selectedChapters, false) deleteChapters(selectedChapters, false)
} }
setCurrentChapters(getChapters()) getChapters()
withContext(Dispatchers.Main) { view?.updateChapters(chapters) }
if (read && deleteNow) { if (read && deleteNow) {
val latestReadChapter = selectedChapters.maxByOrNull { it.chapter_number.toInt() }?.chapter val latestReadChapter = selectedChapters.maxByOrNull { it.chapter_number.toInt() }?.chapter
updateTrackChapterMarkedAsRead(preferences, latestReadChapter, manga.id) { updateTrackChapterMarkedAsRead(preferences, latestReadChapter, manga.id) {
@ -730,7 +741,8 @@ class MangaDetailsPresenter(
private suspend fun asyncUpdateMangaAndChapters(justChapters: Boolean = false) { private suspend fun asyncUpdateMangaAndChapters(justChapters: Boolean = false) {
if (!justChapters) updateManga.await(MangaUpdate(manga.id!!, chapterFlags = manga.chapter_flags)) if (!justChapters) updateManga.await(MangaUpdate(manga.id!!, chapterFlags = manga.chapter_flags))
setCurrentChapters(getChapters()) getChapters()
withUIContext { view?.updateChapters(chapters) }
} }
private fun isScanlatorFiltered() = manga.filtered_scanlators?.isNotEmpty() == true private fun isScanlatorFiltered() = manga.filtered_scanlators?.isNotEmpty() == true
@ -1146,9 +1158,9 @@ class MangaDetailsPresenter(
} }
private suspend fun onQueueUpdate(queue: List<Download>) = withIOContext { private suspend fun onQueueUpdate(queue: List<Download>) = withIOContext {
setDownloadedChapters(chapters, queue) getChapters(queue)
withUIContext { withUIContext {
view?.updateChapters() view?.updateChapters(chapters)
} }
} }

View file

@ -35,29 +35,6 @@ interface Manga : SManga {
var cover_last_modified: Long var cover_last_modified: Long
override fun copy(): Manga {
return (super.copy() as Manga).also {
it.id = this.id
it.source = this.source
it.favorite = this.favorite
it.last_update = this.last_update
it.date_added = this.date_added
it.viewer_flags = this.viewer_flags
it.chapter_flags = this.chapter_flags
it.hide_title = this.hide_title
it.filtered_scanlators = this.filtered_scanlators
it.ogTitle = this.ogTitle
it.ogAuthor = this.ogAuthor
it.ogArtist = this.ogArtist
it.ogDesc = this.ogDesc
it.ogGenre = this.ogGenre
it.ogStatus = this.ogStatus
it.cover_last_modified = this.cover_last_modified
}
}
@Deprecated("Use ogTitle directly instead", ReplaceWith("ogTitle")) @Deprecated("Use ogTitle directly instead", ReplaceWith("ogTitle"))
val originalTitle: String val originalTitle: String
get() = ogTitle get() = ogTitle