Browse Source

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 <[email protected]>

* feat: apply lint suggestions + fix code

feat: apply lint suggestions + fix code

* feat: revert onClickDownload back to onDownloadClicked

Co-authored-by: AntsyLich <[email protected]>
Swords 2 years ago
parent
commit
50b17d5d34

+ 66 - 0
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()
+                },
+            )
+        }
+    }
+}

+ 22 - 8
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) }
+                        [email protected](
+                            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(

+ 2 - 1
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,

+ 4 - 45
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,
+                            )
                         }
                     }
 

+ 29 - 4
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()
     }
 

+ 28 - 5
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<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 ->
-                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<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()
     }
 }