Add different download options within the Library (#8267)
* feat: add download options to library * feat: use max instead of min * feat: remove download all option * feat: applied requested changes + rename some functions * feat: merge downloadAllUnreadChapters and downloadUnreadChapters into one function * Apply suggestions from code review Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> * feat: apply lint suggestions + fix code feat: apply lint suggestions + fix code * feat: revert onClickDownload back to onDownloadClicked Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> (cherry picked from commit 50b17d5d3416e5c44a4a98f93e9710769e74aa51) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
This commit is contained in:
parent
2f6bd9754d
commit
dd08da26e8
@ -0,0 +1,66 @@
|
||||
package eu.kanade.presentation.components
|
||||
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
@Composable
|
||||
fun DownloadDropdownMenu(
|
||||
expanded: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
onDownloadClicked: (DownloadAction) -> Unit,
|
||||
includeDownloadAllOption: Boolean = true,
|
||||
) {
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download_1)) },
|
||||
onClick = {
|
||||
onDownloadClicked(DownloadAction.NEXT_1_CHAPTER)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download_5)) },
|
||||
onClick = {
|
||||
onDownloadClicked(DownloadAction.NEXT_5_CHAPTERS)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download_10)) },
|
||||
onClick = {
|
||||
onDownloadClicked(DownloadAction.NEXT_10_CHAPTERS)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download_custom)) },
|
||||
onClick = {
|
||||
onDownloadClicked(DownloadAction.CUSTOM)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download_unread)) },
|
||||
onClick = {
|
||||
onDownloadClicked(DownloadAction.UNREAD_CHAPTERS)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
if (includeDownloadAllOption) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download_all)) },
|
||||
onClick = {
|
||||
onDownloadClicked(DownloadAction.ALL_CHAPTERS)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
@ -54,6 +55,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.isTabletUi
|
||||
import kotlinx.coroutines.Job
|
||||
@ -218,7 +220,7 @@ fun LibraryBottomActionMenu(
|
||||
onChangeCategoryClicked: (() -> Unit)?,
|
||||
onMarkAsReadClicked: (() -> Unit)?,
|
||||
onMarkAsUnreadClicked: (() -> Unit)?,
|
||||
onDownloadClicked: (() -> Unit)?,
|
||||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||
onDeleteClicked: (() -> Unit)?,
|
||||
// SY -->
|
||||
onClickCleanTitles: (() -> Unit)?,
|
||||
@ -270,13 +272,23 @@ fun LibraryBottomActionMenu(
|
||||
)
|
||||
}
|
||||
if (onDownloadClicked != null) {
|
||||
Button(
|
||||
title = stringResource(R.string.action_download),
|
||||
icon = Icons.Outlined.Download,
|
||||
toConfirm = confirm[3],
|
||||
onLongClick = { onLongClickItem(3) },
|
||||
onClick = onDownloadClicked,
|
||||
)
|
||||
Box {
|
||||
var downloadExpanded by remember { mutableStateOf(false) }
|
||||
this@Row.Button(
|
||||
title = stringResource(R.string.action_download),
|
||||
icon = Icons.Outlined.Download,
|
||||
toConfirm = confirm[3],
|
||||
onLongClick = { onLongClickItem(3) },
|
||||
onClick = { downloadExpanded = !downloadExpanded },
|
||||
)
|
||||
val onDismissRequest = { downloadExpanded = false }
|
||||
DownloadDropdownMenu(
|
||||
expanded = downloadExpanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onDownloadClicked = onDownloadClicked,
|
||||
includeDownloadAllOption = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (onDeleteClicked != null) {
|
||||
Button(
|
||||
|
@ -18,6 +18,7 @@ import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.components.Scaffold
|
||||
import eu.kanade.presentation.library.components.LibraryContent
|
||||
import eu.kanade.presentation.library.components.LibraryToolbar
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryPresenter
|
||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
||||
@ -30,7 +31,7 @@ fun LibraryScreen(
|
||||
onChangeCategoryClicked: () -> Unit,
|
||||
onMarkAsReadClicked: () -> Unit,
|
||||
onMarkAsUnreadClicked: () -> Unit,
|
||||
onDownloadClicked: () -> Unit,
|
||||
onDownloadClicked: (DownloadAction) -> Unit,
|
||||
onDeleteClicked: () -> Unit,
|
||||
onClickUnselectAll: () -> Unit,
|
||||
onClickSelectAll: () -> Unit,
|
||||
|
@ -27,7 +27,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.components.AppStateBanners
|
||||
import eu.kanade.presentation.components.DropdownMenu
|
||||
import eu.kanade.presentation.components.DownloadDropdownMenu
|
||||
import eu.kanade.presentation.components.OverflowMenu
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import eu.kanade.presentation.theme.active
|
||||
@ -105,53 +105,11 @@ fun MangaToolbar(
|
||||
)
|
||||
}
|
||||
val onDismissRequest = { onDownloadExpanded(false) }
|
||||
DropdownMenu(
|
||||
DownloadDropdownMenu(
|
||||
expanded = downloadExpanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download_1)) },
|
||||
onClick = {
|
||||
onClickDownload(DownloadAction.NEXT_1_CHAPTER)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download_5)) },
|
||||
onClick = {
|
||||
onClickDownload(DownloadAction.NEXT_5_CHAPTERS)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download_10)) },
|
||||
onClick = {
|
||||
onClickDownload(DownloadAction.NEXT_10_CHAPTERS)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download_custom)) },
|
||||
onClick = {
|
||||
onClickDownload(DownloadAction.CUSTOM)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download_unread)) },
|
||||
onClick = {
|
||||
onClickDownload(DownloadAction.UNREAD_CHAPTERS)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.download_all)) },
|
||||
onClick = {
|
||||
onClickDownload(DownloadAction.ALL_CHAPTERS)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
}
|
||||
onDownloadClicked = onClickDownload,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,8 @@ import eu.kanade.domain.manga.model.toDbManga
|
||||
import eu.kanade.presentation.components.ChangeCategoryDialog
|
||||
import eu.kanade.presentation.components.DeleteLibraryMangaDialog
|
||||
import eu.kanade.presentation.library.LibraryScreen
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||
@ -87,7 +89,7 @@ class LibraryController(
|
||||
onChangeCategoryClicked = ::showMangaCategoriesDialog,
|
||||
onMarkAsReadClicked = { markReadStatus(true) },
|
||||
onMarkAsUnreadClicked = { markReadStatus(false) },
|
||||
onDownloadClicked = ::downloadUnreadChapters,
|
||||
onDownloadClicked = ::runDownloadChapterAction,
|
||||
onDeleteClicked = ::showDeleteMangaDialog,
|
||||
onClickFilter = ::showSettingsSheet,
|
||||
onClickRefresh = {
|
||||
@ -184,6 +186,16 @@ class LibraryController(
|
||||
},
|
||||
)
|
||||
}
|
||||
is LibraryPresenter.Dialog.DownloadCustomAmount -> {
|
||||
DownloadCustomAmountDialog(
|
||||
maxAmount = dialog.max,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirm = { amount ->
|
||||
presenter.downloadUnreadChapters(dialog.manga, amount)
|
||||
presenter.clearSelection()
|
||||
},
|
||||
)
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
|
||||
@ -312,9 +324,22 @@ class LibraryController(
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadUnreadChapters() {
|
||||
val mangaList = presenter.selection.toList()
|
||||
presenter.downloadUnreadChapters(mangaList.map { it.manga })
|
||||
private fun runDownloadChapterAction(action: DownloadAction) {
|
||||
val mangas = presenter.selection.map { it.manga }.toList()
|
||||
when (action) {
|
||||
DownloadAction.NEXT_1_CHAPTER -> presenter.downloadUnreadChapters(mangas, 1)
|
||||
DownloadAction.NEXT_5_CHAPTERS -> presenter.downloadUnreadChapters(mangas, 5)
|
||||
DownloadAction.NEXT_10_CHAPTERS -> presenter.downloadUnreadChapters(mangas, 10)
|
||||
DownloadAction.UNREAD_CHAPTERS -> presenter.downloadUnreadChapters(mangas, null)
|
||||
DownloadAction.CUSTOM -> {
|
||||
presenter.dialog = LibraryPresenter.Dialog.DownloadCustomAmount(
|
||||
mangas,
|
||||
presenter.selection.maxOf { it.unreadCount }.toInt(),
|
||||
)
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
presenter.clearSelection()
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,7 @@ import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainManga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadCache
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.TrackStatus
|
||||
@ -64,6 +65,7 @@ import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
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.withIOContext
|
||||
@ -600,12 +602,32 @@ class LibraryPresenter(
|
||||
return mangaCategories.flatten().distinct().subtract(common)
|
||||
}
|
||||
|
||||
fun shouldDownloadChapter(manga: Manga, chapter: Chapter): Boolean {
|
||||
val activeDownload = downloadManager.queue.find { chapter.id == it.chapter.id }
|
||||
val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source)
|
||||
val state = when {
|
||||
activeDownload != null -> activeDownload.status
|
||||
downloaded -> Download.State.DOWNLOADED
|
||||
else -> Download.State.NOT_DOWNLOADED
|
||||
}
|
||||
return state == Download.State.NOT_DOWNLOADED
|
||||
}
|
||||
|
||||
suspend fun getNotDownloadedUnreadChapters(manga: Manga): List<Chapter> {
|
||||
return getChapterByMangaId.await(manga.id)
|
||||
.filter { chapter ->
|
||||
!chapter.read && shouldDownloadChapter(manga, chapter)
|
||||
}
|
||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues all unread chapters from the given list of manga.
|
||||
* Queues the amount specified of unread chapters from the list of mangas given.
|
||||
*
|
||||
* @param mangas the list of manga.
|
||||
* @param amount the amount to queue or null to queue all
|
||||
*/
|
||||
fun downloadUnreadChapters(mangas: List<Manga>) {
|
||||
fun downloadUnreadChapters(mangas: List<Manga>, amount: Int?) {
|
||||
presenterScope.launchNonCancellable {
|
||||
mangas.forEach { manga ->
|
||||
if (manga.source == MERGED_SOURCE_ID) {
|
||||
@ -614,6 +636,7 @@ class LibraryPresenter(
|
||||
mergedSource
|
||||
.getChapters(manga.id)
|
||||
.filter { !it.read }
|
||||
.let { if (amount != null) it.take(amount) else it }
|
||||
.groupBy { it.mangaId }
|
||||
.forEach ab@{ (mangaId, chapters) ->
|
||||
val mergedManga = mergedMangas.firstOrNull { it.id == mangaId } ?: return@ab
|
||||
@ -626,8 +649,8 @@ class LibraryPresenter(
|
||||
?.takeUnless { it.read }
|
||||
.let(::listOfNotNull)
|
||||
} else {
|
||||
/* SY <-- */ getChapterByMangaId.await(manga.id)
|
||||
.filter { !it.read }
|
||||
/* SY <-- */ getNotDownloadedUnreadChapters(manga)
|
||||
.let { if (amount != null) it.take(amount) else it }
|
||||
}
|
||||
|
||||
downloadManager.downloadChapters(manga, chapters.map { it.toDbChapter() })
|
||||
@ -1054,6 +1077,7 @@ class LibraryPresenter(
|
||||
sealed class Dialog {
|
||||
data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : Dialog()
|
||||
data class DeleteManga(val manga: List<Manga>) : Dialog()
|
||||
data class DownloadCustomAmount(val manga: List<Manga>, val max: Int) : Dialog()
|
||||
}
|
||||
|
||||
// SY -->
|
||||
|
Loading…
x
Reference in New Issue
Block a user