revert: Revert flow usage commits

Too much headache...
This commit is contained in:
Ahmad Ansori Palembani 2024-12-17 08:18:01 +07:00
parent 4a0f578211
commit 4ffb0ad8ee
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
3 changed files with 170 additions and 187 deletions

View file

@ -182,37 +182,6 @@ var Manga.vibrantCoverColor: Int?
id?.let { MangaCoverMetadata.setVibrantColor(it, value) } id?.let { MangaCoverMetadata.setVibrantColor(it, value) }
} }
fun Manga.copyDomain(): Manga = MangaImpl().also { other ->
other.url = this.url
other.title = this.title
other.artist = this.artist
other.author = this.author
other.description = this.description
other.genre = this.genre
other.status = this.status
other.thumbnail_url = this.thumbnail_url
other.initialized = this.initialized
other.id = this.id
other.source = this.source
other.favorite = this.favorite
other.last_update = this.last_update
other.date_added = this.date_added
other.viewer_flags = this.viewer_flags
other.chapter_flags = this.chapter_flags
other.hide_title = this.hide_title
other.filtered_scanlators = this.filtered_scanlators
other.ogTitle = this.ogTitle
other.ogAuthor = this.ogAuthor
other.ogArtist = this.ogArtist
other.ogDesc = this.ogDesc
other.ogGenre = this.ogGenre
other.ogStatus = this.ogStatus
other.cover_last_modified = this.cover_last_modified
}
fun Manga.Companion.create(source: Long) = MangaImpl().apply { fun Manga.Companion.create(source: Long) = MangaImpl().apply {
this.source = source this.source = source
} }

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,11 +821,15 @@ class MangaDetailsController :
updateMenuVisibility(activityBinding?.toolbar?.menu) updateMenuVisibility(activityBinding?.toolbar?.menu)
} }
fun updateChapters() { fun updateChapters(chapters: List<ChapterItem>) {
view ?: return view ?: return
binding.swipeRefresh.isRefreshing = presenter.isLoading binding.swipeRefresh.isRefreshing = presenter.isLoading
adapter?.setChapters(presenter.chapters) if (presenter.chapters.isEmpty() && fromCatalogue && !presenter.hasRequested) {
launchUI { binding.swipeRefresh.isRefreshing = true }
presenter.fetchChaptersFromSource()
}
tabletAdapter?.notifyItemChanged(0) tabletAdapter?.notifyItemChanged(0)
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

@ -18,7 +18,6 @@ import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.bookmarkedFilter import eu.kanade.tachiyomi.data.database.models.bookmarkedFilter
import eu.kanade.tachiyomi.data.database.models.chapterOrder import eu.kanade.tachiyomi.data.database.models.chapterOrder
import eu.kanade.tachiyomi.data.database.models.copyDomain
import eu.kanade.tachiyomi.data.database.models.downloadedFilter import eu.kanade.tachiyomi.data.database.models.downloadedFilter
import eu.kanade.tachiyomi.data.database.models.prepareCoverUpdate import eu.kanade.tachiyomi.data.database.models.prepareCoverUpdate
import eu.kanade.tachiyomi.data.database.models.readFilter import eu.kanade.tachiyomi.data.database.models.readFilter
@ -35,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
@ -78,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
@ -133,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()
@ -161,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
@ -174,7 +160,7 @@ class MangaDetailsPresenter(
var allHistory: List<History> = emptyList() var allHistory: List<History> = emptyList()
private set private set
val headerItem: MangaHeaderItem get() = MangaHeaderItem(mangaId, view?.fromCatalogue == true) val headerItem: MangaHeaderItem by lazy { MangaHeaderItem(mangaId, view?.fromCatalogue == true)}
var tabletChapterHeaderItem: MangaHeaderItem? = null var tabletChapterHeaderItem: MangaHeaderItem? = null
get() { get() {
when (view?.isTablet) { when (view?.isTablet) {
@ -200,15 +186,7 @@ class MangaDetailsPresenter(
val controller = view ?: return val controller = view ?: return
isLockedFromSearch = controller.shouldLockIfNeeded && SecureActivityDelegate.shouldBeLocked() isLockedFromSearch = controller.shouldLockIfNeeded && SecureActivityDelegate.shouldBeLocked()
if (!::manga.isInitialized) runBlocking { refreshMangaFromDb() }
presenterScope.launchUI {
currentManga.collectLatest {
if (it == null) return@collectLatest
controller.updateHeader()
}
}
if (currentManga.value == null) runBlocking { refreshMangaFromDb() }
syncData() syncData()
presenterScope.launchUI { presenterScope.launchUI {
@ -226,19 +204,6 @@ class MangaDetailsPresenter(
presenterScope.launchIO { presenterScope.launchIO {
downloadManager.queueState.collectLatest(::onQueueUpdate) downloadManager.queueState.collectLatest(::onQueueUpdate)
} }
presenterScope.launchIO {
currentChapters.collectLatest { chapters ->
allChapters = if (!isScanlatorFiltered()) chapters else getChapter.awaitAll(mangaId, false).map { it.toModel() }
allChapterScanlators = allChapters.mapNotNull { it.chapter.scanlator }.toSet()
getHistory()
withUIContext {
controller.updateChapters()
}
}
}
runBlocking { runBlocking {
tracks = getTrack.awaitAllByMangaId(mangaId) tracks = getTrack.awaitAllByMangaId(mangaId)
@ -251,26 +216,25 @@ class MangaDetailsPresenter(
fun onCreateLate() { fun onCreateLate() {
val controller = view ?: return val controller = view ?: return
isLoading = true
controller.setRefresh(true) // FIXME: Use progress indicator instead
LibraryUpdateJob.updateFlow LibraryUpdateJob.updateFlow
.filter { it == mangaId } .filter { it == mangaId }
.onEach { onUpdateManga() } .onEach { onUpdateManga() }
.launchIn(presenterScope) .launchIn(presenterScope)
val updateMangaNeeded = currentManga.value?.initialized != true if (manga.isLocal()) {
val updateChaptersNeeded = runBlocking { setAndGetChapters() }.isEmpty() refreshAll()
} else if (!manga.initialized) {
isLoading = true
controller.setRefresh(true)
controller.updateHeader()
refreshAll()
} else {
runBlocking { getChapters() }
controller.updateChapters(this.chapters)
getHistory()
}
presenterScope.launch { presenterScope.launch {
isLoading = true
val tasks = listOf(
async { if (updateMangaNeeded) fetchMangaFromSource() },
async { if (updateChaptersNeeded) fetchChaptersFromSource(false) },
)
tasks.awaitAll()
isLoading = false
setTrackItems() setTrackItems()
} }
@ -279,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
@ -298,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().applyChapterFilters() } 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.applyChapterFilters() } 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() {
@ -323,8 +291,7 @@ class MangaDetailsPresenter(
* *
* @param chapters the list of chapter from the database. * @param chapters the list of chapter from the database.
*/ */
private fun setDownloadedChapters(queue: List<Download>) { private fun setDownloadedChapters(chapters: List<ChapterItem>, queue: List<Download>) {
currentChaptersInternal.update { chapters ->
for (chapter in chapters) { for (chapter in chapters) {
if (downloadManager.isChapterDownloaded(chapter, manga)) { if (downloadManager.isChapterDownloaded(chapter, manga)) {
chapter.status = Download.State.DOWNLOADED chapter.status = Download.State.DOWNLOADED
@ -333,8 +300,6 @@ class MangaDetailsPresenter(
?.status ?: Download.State.default ?.status ?: Download.State.default
} }
} }
chapters
}
} }
/** /**
@ -367,12 +332,12 @@ class MangaDetailsPresenter(
* @param chapterList the list of chapters from the database * @param chapterList the list of chapters from the database
* @return an observable of the list of chapters filtered and sorted. * @return an observable of the list of chapters filtered and sorted.
*/ */
private fun List<ChapterItem>.applyChapterFilters(): List<ChapterItem> { private fun applyChapterFilters(chapterList: List<ChapterItem>): List<ChapterItem> {
if (isLockedFromSearch) { if (isLockedFromSearch) {
return this return chapterList
} }
getScrollType(this) getScrollType(chapterList)
return chapterSort.getChaptersSorted(this) return chapterSort.getChaptersSorted(chapterList)
} }
fun getChapterUrl(chapter: Chapter): String? { fun getChapterUrl(chapter: Chapter): String? {
@ -429,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)
} }
@ -447,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)
@ -467,29 +432,34 @@ 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) {
)
tasks.awaitAll()
isLoading = false
}
}
private suspend fun fetchMangaFromSource() {
try { try {
val manga = manga.copyDomain() source.getChapterList(manga.copy())
val networkManga = source.getMangaDetails(manga.copy()) } catch (e: Exception) {
chapterError = e
emptyList()
}
}
val nManga = async(Dispatchers.IO) {
try {
source.getMangaDetails(manga.copy())
} catch (e: java.lang.Exception) {
mangaError = e
null
}
}
val networkManga = nManga.await()
if (networkManga != null) {
manga.prepareCoverUpdate(coverCache, networkManga, false) manga.prepareCoverUpdate(coverCache, networkManga, false)
manga.copyFrom(networkManga) manga.copyFrom(networkManga)
manga.initialized = true manga.initialized = true
updateManga.await(manga.toMangaUpdate()) updateManga.await(manga.toMangaUpdate())
setCurrentManga(manga) launchIO {
presenterScope.launchNonCancellableIO {
val request = val request =
ImageRequest.Builder(preferences.context).data(manga.cover()) ImageRequest.Builder(preferences.context).data(manga.cover())
.memoryCachePolicy(CachePolicy.DISABLED) .memoryCachePolicy(CachePolicy.DISABLED)
@ -497,53 +467,93 @@ class MangaDetailsPresenter(
.build() .build()
if (preferences.context.imageLoader.execute(request) is SuccessResult) { if (preferences.context.imageLoader.execute(request) is SuccessResult) {
withUIContext { withContext(Dispatchers.Main) {
view?.setPaletteColor() view?.setPaletteColor()
} }
} }
} }
} catch (e: Exception) {
if (e is HttpException && e.code == 103) return
withUIContext {
view?.showError(trimException(e))
} }
} val finChapters = chapters.await()
} if (finChapters.isNotEmpty()) {
val newChapters = withIOContext { syncChaptersWithSource(finChapters, manga, source) }
private suspend fun fetchChaptersFromSource(manualFetch: Boolean = true) { if (newChapters.first.isNotEmpty()) {
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 }
.map { it.toModel() },
)
} }
withUIContext {
view?.view?.context?.let { mangaShortcutManager.updateShortcuts(it) } view?.view?.context?.let { mangaShortcutManager.updateShortcuts(it) }
} }
} if (newChapters.second.isNotEmpty()) {
if (removed.isNotEmpty() && manualFetch) { val removedChaptersId = newChapters.second.map { it.id }
val removedChaptersId = removed.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()
}
isLoading = false
if (chapterError == null) {
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() getHistory()
} }
}
/**
* Requests an updated list of chapters from the source.
*/
fun fetchChaptersFromSource() {
hasRequested = true
isLoading = true
presenterScope.launch(Dispatchers.IO) {
val chapters = try {
source.getChapterList(manga.copy())
} catch (e: Exception) { } catch (e: Exception) {
withUIContext { withContext(Dispatchers.Main) { view?.showError(trimException(e)) }
return@launch
}
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)) view?.showError(trimException(e))
} }
} }
} }
}
private fun trimException(e: java.lang.Exception): String { private fun trimException(e: java.lang.Exception): String {
return ( return (
@ -569,7 +579,8 @@ class MangaDetailsPresenter(
it.toProgressUpdate() it.toProgressUpdate()
} }
updateChapter.awaitAll(updates) updateChapter.awaitAll(updates)
setCurrentChapters(getChapters()) getChapters()
withContext(Dispatchers.Main) { view?.updateChapters(chapters) }
} }
} }
@ -598,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) {
@ -729,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
@ -798,7 +811,7 @@ class MangaDetailsPresenter(
withUIContext { withUIContext {
view?.shareManga(uri.uri.toFile()) view?.shareManga(uri.uri.toFile())
} }
} catch (_: Exception) { } catch (_: java.lang.Exception) {
} }
} }
} }
@ -1140,15 +1153,15 @@ class MangaDetailsPresenter(
override fun onStatusChange(download: Download) { override fun onStatusChange(download: Download) {
super.onStatusChange(download) super.onStatusChange(download)
currentChaptersInternal.update { chapters ->
chapters.find { it.id == download.chapter.id }?.status = download.status chapters.find { it.id == download.chapter.id }?.status = download.status
chapters
}
onPageProgressUpdate(download) onPageProgressUpdate(download)
} }
private suspend fun onQueueUpdate(queue: List<Download>) = withIOContext { private suspend fun onQueueUpdate(queue: List<Download>) = withIOContext {
setDownloadedChapters(queue) getChapters(queue)
withUIContext {
view?.updateChapters(chapters)
}
} }
override fun onQueueUpdate(download: Download) { override fun onQueueUpdate(download: Download) {
@ -1160,10 +1173,7 @@ class MangaDetailsPresenter(
} }
override fun onPageProgressUpdate(download: Download) { override fun onPageProgressUpdate(download: Download) {
currentChaptersInternal.update { chapters ->
chapters.find { it.id == download.chapter.id }?.download = download chapters.find { it.id == download.chapter.id }?.download = download
chapters
}
view?.updateChapterDownload(download) view?.updateChapterDownload(download)
} }