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:
Swords 2022-10-31 13:27:48 +11:00 committed by Jobobby04
parent 2f6bd9754d
commit dd08da26e8
6 changed files with 149 additions and 63 deletions

View File

@ -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()
},
)
}
}
}

View File

@ -9,6 +9,7 @@ import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope 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.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.isTabletUi import eu.kanade.tachiyomi.util.system.isTabletUi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -218,7 +220,7 @@ fun LibraryBottomActionMenu(
onChangeCategoryClicked: (() -> Unit)?, onChangeCategoryClicked: (() -> Unit)?,
onMarkAsReadClicked: (() -> Unit)?, onMarkAsReadClicked: (() -> Unit)?,
onMarkAsUnreadClicked: (() -> Unit)?, onMarkAsUnreadClicked: (() -> Unit)?,
onDownloadClicked: (() -> Unit)?, onDownloadClicked: ((DownloadAction) -> Unit)?,
onDeleteClicked: (() -> Unit)?, onDeleteClicked: (() -> Unit)?,
// SY --> // SY -->
onClickCleanTitles: (() -> Unit)?, onClickCleanTitles: (() -> Unit)?,
@ -270,13 +272,23 @@ fun LibraryBottomActionMenu(
) )
} }
if (onDownloadClicked != null) { if (onDownloadClicked != null) {
Button( Box {
title = stringResource(R.string.action_download), var downloadExpanded by remember { mutableStateOf(false) }
icon = Icons.Outlined.Download, this@Row.Button(
toConfirm = confirm[3], title = stringResource(R.string.action_download),
onLongClick = { onLongClickItem(3) }, icon = Icons.Outlined.Download,
onClick = onDownloadClicked, 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) { if (onDeleteClicked != null) {
Button( Button(

View File

@ -18,6 +18,7 @@ import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.library.components.LibraryContent import eu.kanade.presentation.library.components.LibraryContent
import eu.kanade.presentation.library.components.LibraryToolbar import eu.kanade.presentation.library.components.LibraryToolbar
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.library.LibraryPresenter import eu.kanade.tachiyomi.ui.library.LibraryPresenter
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
@ -30,7 +31,7 @@ fun LibraryScreen(
onChangeCategoryClicked: () -> Unit, onChangeCategoryClicked: () -> Unit,
onMarkAsReadClicked: () -> Unit, onMarkAsReadClicked: () -> Unit,
onMarkAsUnreadClicked: () -> Unit, onMarkAsUnreadClicked: () -> Unit,
onDownloadClicked: () -> Unit, onDownloadClicked: (DownloadAction) -> Unit,
onDeleteClicked: () -> Unit, onDeleteClicked: () -> Unit,
onClickUnselectAll: () -> Unit, onClickUnselectAll: () -> Unit,
onClickSelectAll: () -> Unit, onClickSelectAll: () -> Unit,

View File

@ -27,7 +27,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.AppStateBanners 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.components.OverflowMenu
import eu.kanade.presentation.manga.DownloadAction import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.presentation.theme.active import eu.kanade.presentation.theme.active
@ -105,53 +105,11 @@ fun MangaToolbar(
) )
} }
val onDismissRequest = { onDownloadExpanded(false) } val onDismissRequest = { onDownloadExpanded(false) }
DropdownMenu( DownloadDropdownMenu(
expanded = downloadExpanded, expanded = downloadExpanded,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) { onDownloadClicked = onClickDownload,
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()
},
)
}
} }
} }

View File

@ -20,6 +20,8 @@ import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.presentation.components.ChangeCategoryDialog import eu.kanade.presentation.components.ChangeCategoryDialog
import eu.kanade.presentation.components.DeleteLibraryMangaDialog import eu.kanade.presentation.components.DeleteLibraryMangaDialog
import eu.kanade.presentation.library.LibraryScreen 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.R
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
@ -87,7 +89,7 @@ class LibraryController(
onChangeCategoryClicked = ::showMangaCategoriesDialog, onChangeCategoryClicked = ::showMangaCategoriesDialog,
onMarkAsReadClicked = { markReadStatus(true) }, onMarkAsReadClicked = { markReadStatus(true) },
onMarkAsUnreadClicked = { markReadStatus(false) }, onMarkAsUnreadClicked = { markReadStatus(false) },
onDownloadClicked = ::downloadUnreadChapters, onDownloadClicked = ::runDownloadChapterAction,
onDeleteClicked = ::showDeleteMangaDialog, onDeleteClicked = ::showDeleteMangaDialog,
onClickFilter = ::showSettingsSheet, onClickFilter = ::showSettingsSheet,
onClickRefresh = { 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 -> {} null -> {}
} }
@ -312,9 +324,22 @@ class LibraryController(
} }
} }
private fun downloadUnreadChapters() { private fun runDownloadChapterAction(action: DownloadAction) {
val mangaList = presenter.selection.toList() val mangas = presenter.selection.map { it.manga }.toList()
presenter.downloadUnreadChapters(mangaList.map { it.manga }) 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() presenter.clearSelection()
} }

View File

@ -54,6 +54,7 @@ import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.toDomainManga import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadCache
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.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackStatus 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.HttpSource
import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter 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.launchIO
import eu.kanade.tachiyomi.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.launchNonCancellable
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
@ -600,12 +602,32 @@ class LibraryPresenter(
return mangaCategories.flatten().distinct().subtract(common) 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 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 { presenterScope.launchNonCancellable {
mangas.forEach { manga -> mangas.forEach { manga ->
if (manga.source == MERGED_SOURCE_ID) { if (manga.source == MERGED_SOURCE_ID) {
@ -614,6 +636,7 @@ class LibraryPresenter(
mergedSource mergedSource
.getChapters(manga.id) .getChapters(manga.id)
.filter { !it.read } .filter { !it.read }
.let { if (amount != null) it.take(amount) else it }
.groupBy { it.mangaId } .groupBy { it.mangaId }
.forEach ab@{ (mangaId, chapters) -> .forEach ab@{ (mangaId, chapters) ->
val mergedManga = mergedMangas.firstOrNull { it.id == mangaId } ?: return@ab val mergedManga = mergedMangas.firstOrNull { it.id == mangaId } ?: return@ab
@ -626,8 +649,8 @@ class LibraryPresenter(
?.takeUnless { it.read } ?.takeUnless { it.read }
.let(::listOfNotNull) .let(::listOfNotNull)
} else { } else {
/* SY <-- */ getChapterByMangaId.await(manga.id) /* SY <-- */ getNotDownloadedUnreadChapters(manga)
.filter { !it.read } .let { if (amount != null) it.take(amount) else it }
} }
downloadManager.downloadChapters(manga, chapters.map { it.toDbChapter() }) downloadManager.downloadChapters(manga, chapters.map { it.toDbChapter() })
@ -1054,6 +1077,7 @@ class LibraryPresenter(
sealed class Dialog { sealed class Dialog {
data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : Dialog() data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : Dialog()
data class DeleteManga(val manga: List<Manga>) : Dialog() data class DeleteManga(val manga: List<Manga>) : Dialog()
data class DownloadCustomAmount(val manga: List<Manga>, val max: Int) : Dialog()
} }
// SY --> // SY -->