mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	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>
This commit is contained in:
		@@ -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
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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,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,
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user