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.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(

View File

@ -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,

View File

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

View File

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

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.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 -->