diff --git a/app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt b/app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt new file mode 100644 index 000000000..6ac95a130 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/components/DownloadDropdownMenu.kt @@ -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() + }, + ) + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/components/MangaBottomActionMenu.kt b/app/src/main/java/eu/kanade/presentation/components/MangaBottomActionMenu.kt index a0ad1281a..22def9446 100644 --- a/app/src/main/java/eu/kanade/presentation/components/MangaBottomActionMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/components/MangaBottomActionMenu.kt @@ -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 @@ -37,8 +38,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector @@ -48,6 +51,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 kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -211,7 +215,7 @@ fun LibraryBottomActionMenu( onChangeCategoryClicked: (() -> Unit)?, onMarkAsReadClicked: (() -> Unit)?, onMarkAsUnreadClicked: (() -> Unit)?, - onDownloadClicked: (() -> Unit)?, + onDownloadClicked: ((DownloadAction) -> Unit)?, onDeleteClicked: (() -> Unit)?, ) { AnimatedVisibility( @@ -270,13 +274,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( diff --git a/app/src/main/java/eu/kanade/presentation/library/LibraryScreen.kt b/app/src/main/java/eu/kanade/presentation/library/LibraryScreen.kt index c55cfb7c0..c9efd2e7c 100644 --- a/app/src/main/java/eu/kanade/presentation/library/LibraryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/library/LibraryScreen.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt index 95b991485..138136eb7 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt @@ -27,6 +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.DownloadDropdownMenu import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.components.OverflowMenu import eu.kanade.presentation.manga.DownloadAction @@ -99,53 +100,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, + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index e943d7a6b..b30b87c14 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -15,6 +15,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 @@ -54,7 +56,7 @@ class LibraryController( onChangeCategoryClicked = ::showMangaCategoriesDialog, onMarkAsReadClicked = { markReadStatus(true) }, onMarkAsUnreadClicked = { markReadStatus(false) }, - onDownloadClicked = ::downloadUnreadChapters, + onDownloadClicked = ::runDownloadChapterAction, onDeleteClicked = ::showDeleteMangaDialog, onClickFilter = ::showSettingsSheet, onClickRefresh = { @@ -101,6 +103,16 @@ class LibraryController( }, ) } + is LibraryPresenter.Dialog.DownloadCustomAmount -> { + DownloadCustomAmountDialog( + maxAmount = dialog.max, + onDismissRequest = onDismissRequest, + onConfirm = { amount -> + presenter.downloadUnreadChapters(dialog.manga, amount) + presenter.clearSelection() + }, + ) + } null -> {} } @@ -218,9 +230,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() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 86ac68081..d712736d0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -19,6 +19,7 @@ import eu.kanade.domain.category.interactor.SetMangaCategories import eu.kanade.domain.category.model.Category import eu.kanade.domain.chapter.interactor.GetChapterByMangaId import eu.kanade.domain.chapter.interactor.SetReadStatus +import eu.kanade.domain.chapter.model.Chapter import eu.kanade.domain.chapter.model.toDbChapter import eu.kanade.domain.library.model.LibraryManga import eu.kanade.domain.library.model.LibrarySort @@ -39,11 +40,13 @@ 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.track.TrackManager import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource 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 @@ -401,18 +404,37 @@ 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 { + 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) { + fun downloadUnreadChapters(mangas: List, amount: Int?) { presenterScope.launchNonCancellable { mangas.forEach { manga -> - val chapters = getChapterByMangaId.await(manga.id) - .filter { !it.read } + val chapters = getNotDownloadedUnreadChapters(manga) + .let { if (amount != null) it.take(amount) else it } .map { it.toDbChapter() } - downloadManager.downloadChapters(manga, chapters) } } @@ -604,5 +626,6 @@ class LibraryPresenter( sealed class Dialog { data class ChangeCategory(val manga: List, val initialSelection: List>) : Dialog() data class DeleteManga(val manga: List) : Dialog() + data class DownloadCustomAmount(val manga: List, val max: Int) : Dialog() } }