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:
parent
46f0e26dd3
commit
5280caa60d
4 changed files with 101 additions and 10 deletions
|
|
@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.AdaptiveSheet
|
import eu.kanade.presentation.components.AdaptiveSheet
|
||||||
|
import eu.kanade.presentation.manga.components.ChapterDownloadAction
|
||||||
import eu.kanade.presentation.manga.components.MangaChapterListItem
|
import eu.kanade.presentation.manga.components.MangaChapterListItem
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
|
|
@ -42,6 +43,9 @@ fun ChapterListDialog(
|
||||||
onClickChapter: (Chapter) -> Unit,
|
onClickChapter: (Chapter) -> Unit,
|
||||||
onBookmark: (Chapter) -> Unit,
|
onBookmark: (Chapter) -> Unit,
|
||||||
dateRelativeTime: Boolean,
|
dateRelativeTime: Boolean,
|
||||||
|
// KMK -->
|
||||||
|
onDownloadAction: ((Chapter, ChapterDownloadAction) -> Unit)? = null,
|
||||||
|
// KMK <--
|
||||||
) {
|
) {
|
||||||
val manga by screenModel.mangaFlow.collectAsState()
|
val manga by screenModel.mangaFlow.collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
@ -106,14 +110,22 @@ fun ChapterListDialog(
|
||||||
read = chapterItem.chapter.read,
|
read = chapterItem.chapter.read,
|
||||||
bookmark = chapterItem.chapter.bookmark,
|
bookmark = chapterItem.chapter.bookmark,
|
||||||
selected = false,
|
selected = false,
|
||||||
downloadIndicatorEnabled = false,
|
// KMK -->
|
||||||
|
downloadIndicatorEnabled = onDownloadAction != null,
|
||||||
|
// KMK <--
|
||||||
downloadStateProvider = { downloadState },
|
downloadStateProvider = { downloadState },
|
||||||
downloadProgressProvider = { progress },
|
downloadProgressProvider = { progress },
|
||||||
chapterSwipeStartAction = LibraryPreferences.ChapterSwipeAction.ToggleBookmark,
|
chapterSwipeStartAction = LibraryPreferences.ChapterSwipeAction.ToggleBookmark,
|
||||||
chapterSwipeEndAction = LibraryPreferences.ChapterSwipeAction.ToggleBookmark,
|
chapterSwipeEndAction = LibraryPreferences.ChapterSwipeAction.ToggleBookmark,
|
||||||
onLongClick = { /*TODO*/ },
|
onLongClick = { /*TODO*/ },
|
||||||
onClick = { onClickChapter(chapterItem.chapter) },
|
onClick = { onClickChapter(chapterItem.chapter) },
|
||||||
onDownloadClick = null,
|
// KMK -->
|
||||||
|
onDownloadClick = if (onDownloadAction != null) {
|
||||||
|
{ action -> onDownloadAction(chapterItem.chapter, action) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
|
// KMK <--
|
||||||
onChapterSwipe = {
|
onChapterSwipe = {
|
||||||
onBookmark(chapterItem.chapter)
|
onBookmark(chapterItem.chapter)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil.NOMEDIA_FILE
|
import eu.kanade.tachiyomi.util.storage.DiskUtil.NOMEDIA_FILE
|
||||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||||
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import exh.source.isEhBasedSource
|
import exh.source.isEhBasedSource
|
||||||
import exh.util.DataSaver
|
import exh.util.DataSaver
|
||||||
import exh.util.DataSaver.Companion.getImage
|
import exh.util.DataSaver.Companion.getImage
|
||||||
|
|
@ -281,6 +282,11 @@ class Downloader(
|
||||||
if (chapters.isEmpty()) return
|
if (chapters.isEmpty()) return
|
||||||
|
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource ?: 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 wasEmpty = queueState.value.isEmpty()
|
||||||
val chaptersToQueue = chapters.asSequence()
|
val chaptersToQueue = chapters.asSequence()
|
||||||
// Filter out those already downloaded.
|
// Filter out those already downloaded.
|
||||||
|
|
@ -330,6 +336,10 @@ class Downloader(
|
||||||
* @param download the chapter to be downloaded.
|
* @param download the chapter to be downloaded.
|
||||||
*/
|
*/
|
||||||
private suspend fun downloadChapter(download: Download) {
|
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 ->
|
val mangaDir = provider.getMangaDir(/* SY --> */ download.manga.ogTitle /* SY <-- */, download.source).getOrElse { e ->
|
||||||
download.status = Download.State.ERROR
|
download.status = Download.State.ERROR
|
||||||
notifier.onError(e.message, download.chapter.name, download.manga.title, download.manga.id)
|
notifier.onError(e.message, download.chapter.name, download.manga.title, download.manga.id)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.reader
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
|
||||||
import android.app.UiModeManager
|
import android.app.UiModeManager
|
||||||
import android.app.assist.AssistContent
|
import android.app.assist.AssistContent
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
|
|
@ -209,7 +208,7 @@ class ReaderActivity : BaseActivity() {
|
||||||
registerSecureActivity(this)
|
registerSecureActivity(this)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
overrideActivityTransition(
|
overrideActivityTransition(
|
||||||
Activity.OVERRIDE_TRANSITION_OPEN,
|
OVERRIDE_TRANSITION_OPEN,
|
||||||
R.anim.shared_axis_x_push_enter,
|
R.anim.shared_axis_x_push_enter,
|
||||||
R.anim.shared_axis_x_push_exit,
|
R.anim.shared_axis_x_push_exit,
|
||||||
)
|
)
|
||||||
|
|
@ -355,7 +354,7 @@ class ReaderActivity : BaseActivity() {
|
||||||
super.finish()
|
super.finish()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
overrideActivityTransition(
|
overrideActivityTransition(
|
||||||
Activity.OVERRIDE_TRANSITION_CLOSE,
|
OVERRIDE_TRANSITION_CLOSE,
|
||||||
R.anim.shared_axis_x_pop_enter,
|
R.anim.shared_axis_x_pop_enter,
|
||||||
R.anim.shared_axis_x_pop_exit,
|
R.anim.shared_axis_x_pop_exit,
|
||||||
)
|
)
|
||||||
|
|
@ -689,6 +688,11 @@ class ReaderActivity : BaseActivity() {
|
||||||
}.toImmutableList()
|
}.toImmutableList()
|
||||||
},
|
},
|
||||||
state.dateRelativeTime,
|
state.dateRelativeTime,
|
||||||
|
// KMK -->
|
||||||
|
onDownloadAction = { chapter, action ->
|
||||||
|
viewModel.handleDownloadAction(chapter, action)
|
||||||
|
},
|
||||||
|
// KMK <--
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|
@ -1061,7 +1065,7 @@ class ReaderActivity : BaseActivity() {
|
||||||
try {
|
try {
|
||||||
readingModeToast?.cancel()
|
readingModeToast?.cancel()
|
||||||
readingModeToast = toast(ReadingMode.fromPreference(mode).stringRes)
|
readingModeToast = toast(ReadingMode.fromPreference(mode).stringRes)
|
||||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
} catch (_: ArrayIndexOutOfBoundsException) {
|
||||||
logcat(LogPriority.ERROR) { "Unknown reading mode: $mode" }
|
logcat(LogPriority.ERROR) { "Unknown reading mode: $mode" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import eu.kanade.domain.sync.SyncPreferences
|
||||||
import eu.kanade.domain.track.interactor.TrackChapter
|
import eu.kanade.domain.track.interactor.TrackChapter
|
||||||
import eu.kanade.domain.track.service.TrackPreferences
|
import eu.kanade.domain.track.service.TrackPreferences
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
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.database.models.toDomainChapter
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
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.onEach
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.common.preference.toggle
|
import tachiyomi.core.common.preference.toggle
|
||||||
|
|
@ -169,6 +171,69 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||||
field = value
|
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.
|
* 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
|
* if setting is enabled and [currentChapter] is queued for download
|
||||||
*/
|
*/
|
||||||
private fun cancelQueuedDownloads(currentChapter: ReaderChapter): 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))
|
downloadManager.cancelQueuedDownloads(listOf(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -864,7 +929,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||||
viewModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
updateChapter.await(
|
updateChapter.await(
|
||||||
ChapterUpdate(
|
ChapterUpdate(
|
||||||
id = chapter.id!!.toLong(),
|
id = chapter.id!!,
|
||||||
bookmark = bookmarked,
|
bookmark = bookmarked,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -997,7 +1062,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||||
val chapter = page.chapter.chapter
|
val chapter = page.chapter.chapter
|
||||||
val filenameSuffix = " - ${page.number}"
|
val filenameSuffix = " - ${page.number}"
|
||||||
return DiskUtil.buildValidFilename(
|
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
|
) + filenameSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1293,7 +1358,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||||
} else {
|
} else {
|
||||||
SetAsCoverResult.AddToLibraryFirst
|
SetAsCoverResult.AddToLibraryFirst
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
SetAsCoverResult.Error
|
SetAsCoverResult.Error
|
||||||
}
|
}
|
||||||
eventChannel.send(Event.SetCoverResult(result))
|
eventChannel.send(Event.SetCoverResult(result))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue