feat(reader): Download from reader chapter list (#1110)

* Download from reader

* Fix for localsource

* Fix actions for merged entries

* fix spotless

* refactor code

* catching Exception instead of Throwable

---------

Co-authored-by: Cuong-Tran <cuongtran.tm@gmail.com>
This commit is contained in:
kana-shii 2025-09-03 08:12:38 -03:00 committed by GitHub
parent 46f0e26dd3
commit 5280caa60d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 101 additions and 10 deletions

View file

@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.presentation.manga.components.ChapterDownloadAction
import eu.kanade.presentation.manga.components.MangaChapterListItem
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
@ -42,6 +43,9 @@ fun ChapterListDialog(
onClickChapter: (Chapter) -> Unit,
onBookmark: (Chapter) -> Unit,
dateRelativeTime: Boolean,
// KMK -->
onDownloadAction: ((Chapter, ChapterDownloadAction) -> Unit)? = null,
// KMK <--
) {
val manga by screenModel.mangaFlow.collectAsState()
val context = LocalContext.current
@ -106,14 +110,22 @@ fun ChapterListDialog(
read = chapterItem.chapter.read,
bookmark = chapterItem.chapter.bookmark,
selected = false,
downloadIndicatorEnabled = false,
// KMK -->
downloadIndicatorEnabled = onDownloadAction != null,
// KMK <--
downloadStateProvider = { downloadState },
downloadProgressProvider = { progress },
chapterSwipeStartAction = LibraryPreferences.ChapterSwipeAction.ToggleBookmark,
chapterSwipeEndAction = LibraryPreferences.ChapterSwipeAction.ToggleBookmark,
onLongClick = { /*TODO*/ },
onClick = { onClickChapter(chapterItem.chapter) },
onDownloadClick = null,
// KMK -->
onDownloadClick = if (onDownloadAction != null) {
{ action -> onDownloadAction(chapterItem.chapter, action) }
} else {
null
},
// KMK <--
onChapterSwipe = {
onBookmark(chapterItem.chapter)
},

View file

@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.DiskUtil.NOMEDIA_FILE
import eu.kanade.tachiyomi.util.storage.saveTo
import exh.source.MERGED_SOURCE_ID
import exh.source.isEhBasedSource
import exh.util.DataSaver
import exh.util.DataSaver.Companion.getImage
@ -281,6 +282,11 @@ class Downloader(
if (chapters.isEmpty()) return
val source = sourceManager.get(manga.source) as? HttpSource ?: return
// KMK -->
if (source.id == MERGED_SOURCE_ID) return
// KMK <--
val wasEmpty = queueState.value.isEmpty()
val chaptersToQueue = chapters.asSequence()
// Filter out those already downloaded.
@ -330,6 +336,10 @@ class Downloader(
* @param download the chapter to be downloaded.
*/
private suspend fun downloadChapter(download: Download) {
// KMK -->
if (download.source.id == MERGED_SOURCE_ID) return
// KMK <--
val mangaDir = provider.getMangaDir(/* SY --> */ download.manga.ogTitle /* SY <-- */, download.source).getOrElse { e ->
download.status = Download.State.ERROR
notifier.onError(e.message, download.chapter.name, download.manga.title, download.manga.id)

View file

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.reader
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.UiModeManager
import android.app.assist.AssistContent
import android.content.ClipData
@ -209,7 +208,7 @@ class ReaderActivity : BaseActivity() {
registerSecureActivity(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
overrideActivityTransition(
Activity.OVERRIDE_TRANSITION_OPEN,
OVERRIDE_TRANSITION_OPEN,
R.anim.shared_axis_x_push_enter,
R.anim.shared_axis_x_push_exit,
)
@ -355,7 +354,7 @@ class ReaderActivity : BaseActivity() {
super.finish()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
overrideActivityTransition(
Activity.OVERRIDE_TRANSITION_CLOSE,
OVERRIDE_TRANSITION_CLOSE,
R.anim.shared_axis_x_pop_enter,
R.anim.shared_axis_x_pop_exit,
)
@ -689,6 +688,11 @@ class ReaderActivity : BaseActivity() {
}.toImmutableList()
},
state.dateRelativeTime,
// KMK -->
onDownloadAction = { chapter, action ->
viewModel.handleDownloadAction(chapter, action)
},
// KMK <--
)
}
// SY -->
@ -1061,7 +1065,7 @@ class ReaderActivity : BaseActivity() {
try {
readingModeToast?.cancel()
readingModeToast = toast(ReadingMode.fromPreference(mode).stringRes)
} catch (e: ArrayIndexOutOfBoundsException) {
} catch (_: ArrayIndexOutOfBoundsException) {
logcat(LogPriority.ERROR) { "Unknown reading mode: $mode" }
}
}

View file

@ -18,6 +18,7 @@ import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.domain.track.interactor.TrackChapter
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.manga.components.ChapterDownloadAction
import eu.kanade.tachiyomi.data.database.models.toDomainChapter
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadProvider
@ -72,6 +73,7 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import logcat.LogPriority
import tachiyomi.core.common.preference.toggle
@ -169,6 +171,69 @@ class ReaderViewModel @JvmOverloads constructor(
field = value
}
// KMK -->
fun handleDownloadAction(chapter: Chapter, action: ChapterDownloadAction) {
when (action) {
ChapterDownloadAction.START -> downloadChapter(chapter)
ChapterDownloadAction.START_NOW -> downloadManager.startDownloadNow(chapter.id)
ChapterDownloadAction.CANCEL -> cancelDownload(chapter.id)
ChapterDownloadAction.DELETE -> deleteChapter(chapter)
}
}
/**
* @param chapter the chapter to download.
*/
private fun downloadChapter(chapter: Chapter) {
viewModelScope.launch {
val manga = manga?.let {
if (it.source == MERGED_SOURCE_ID) {
state.value.mergedManga?.get(chapter.mangaId) ?: return@launch
} else {
it
}
} ?: return@launch
downloadManager.downloadChapters(manga, listOf(chapter))
downloadManager.startDownloads()
}
}
private fun cancelDownload(chapterId: Long) {
viewModelScope.launch {
val activeDownload = downloadManager.getQueuedDownloadOrNull(chapterId) ?: return@launch
downloadManager.cancelQueuedDownloads(listOf(activeDownload))
// TODO: updateDownloadState(activeDownload.apply { status = Download.State.NOT_DOWNLOADED })
}
}
private fun deleteChapter(chapter: Chapter) {
viewModelScope.launchNonCancellable {
try {
val manga = if (manga?.source == MERGED_SOURCE_ID) {
state.value.mergedManga?.get(chapter.mangaId) ?: return@launchNonCancellable
} else {
manga ?: return@launchNonCancellable
}
val source = sourceManager.get(manga.source) ?: return@launchNonCancellable
downloadManager.deleteChapters(
listOf(chapter),
manga,
source,
ignoreCategoryExclusion = true,
)
// // KMK -->
// if (source.isLocal()) {
// // TODO: Refresh chapters state for Local source
// fetchChaptersFromSource()
// }
// // KMK <--
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
}
}
// KMK <--
/**
* The chapter loader for the loaded manga. It'll be null until [manga] is set.
*/
@ -662,7 +727,7 @@ class ReaderViewModel @JvmOverloads constructor(
* if setting is enabled and [currentChapter] is queued for download
*/
private fun cancelQueuedDownloads(currentChapter: ReaderChapter): Download? {
return downloadManager.getQueuedDownloadOrNull(currentChapter.chapter.id!!.toLong())?.also {
return downloadManager.getQueuedDownloadOrNull(currentChapter.chapter.id!!)?.also {
downloadManager.cancelQueuedDownloads(listOf(it))
}
}
@ -864,7 +929,7 @@ class ReaderViewModel @JvmOverloads constructor(
viewModelScope.launchNonCancellable {
updateChapter.await(
ChapterUpdate(
id = chapter.id!!.toLong(),
id = chapter.id!!,
bookmark = bookmarked,
),
)
@ -997,7 +1062,7 @@ class ReaderViewModel @JvmOverloads constructor(
val chapter = page.chapter.chapter
val filenameSuffix = " - ${page.number}"
return DiskUtil.buildValidFilename(
"${manga.title} - ${chapter.name}".takeBytes(DiskUtil.MAX_FILE_NAME_BYTES - filenameSuffix.byteSize()),
"${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize()),
) + filenameSuffix
}
@ -1293,7 +1358,7 @@ class ReaderViewModel @JvmOverloads constructor(
} else {
SetAsCoverResult.AddToLibraryFirst
}
} catch (e: Exception) {
} catch (_: Exception) {
SetAsCoverResult.Error
}
eventChannel.send(Event.SetCoverResult(result))