Download options dialog
This commit is contained in:
@@ -8,10 +8,14 @@ import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
|
||||
@JvmName("mangaIds")
|
||||
fun Collection<Manga>.ids() = mapToSet { it.id }
|
||||
|
||||
fun Collection<Manga>.distinctById() = distinctBy { it.id }
|
||||
|
||||
@JvmName("chaptersIds")
|
||||
fun Collection<MangaChapter>.ids() = mapToSet { it.id }
|
||||
|
||||
fun Collection<ChapterListItem>.countChaptersByBranch(): Int {
|
||||
if (size <= 1) {
|
||||
return size
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.google.android.material.shape.ShapeAppearanceModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ViewTwoLinesItemBinding
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@@ -35,6 +36,18 @@ class TwoLinesItemView @JvmOverloads constructor(
|
||||
|
||||
private val binding = ViewTwoLinesItemBinding.inflate(LayoutInflater.from(context), this)
|
||||
|
||||
var title: CharSequence?
|
||||
get() = binding.title.text
|
||||
set(value) {
|
||||
binding.title.text = value
|
||||
}
|
||||
|
||||
var subtitle: CharSequence?
|
||||
get() = binding.subtitle.textAndVisible
|
||||
set(value) {
|
||||
binding.subtitle.textAndVisible = value
|
||||
}
|
||||
|
||||
init {
|
||||
var textColors: ColorStateList? = null
|
||||
context.withStyledAttributes(
|
||||
@@ -76,8 +89,7 @@ class TwoLinesItemView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
fun setIconResource(@DrawableRes resId: Int) {
|
||||
val icon = if (resId != 0) ContextCompat.getDrawable(context, resId) else null
|
||||
binding.icon.setImageDrawable(icon)
|
||||
binding.icon.setImageResource(resId)
|
||||
}
|
||||
|
||||
private fun createShapeDrawable(ta: TypedArray): InsetDrawable {
|
||||
|
||||
@@ -30,6 +30,7 @@ class ChaptersBottomSheetMediator(
|
||||
}
|
||||
|
||||
override fun onActionModeStarted(mode: ActionMode) {
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
lock()
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
||||
import org.koitharu.kotatsu.databinding.FragmentChaptersBinding
|
||||
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
|
||||
@@ -66,6 +67,9 @@ class ChaptersFragment :
|
||||
viewModel.isChaptersEmpty.observe(viewLifecycleOwner) {
|
||||
binding.textViewHolder.isVisible = it
|
||||
}
|
||||
viewModel.onSelectChapter.observeEvent(viewLifecycleOwner) {
|
||||
selectionController?.onItemLongClick(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
||||
@@ -16,12 +16,11 @@ import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ShareHelper
|
||||
import org.koitharu.kotatsu.details.ui.model.MangaBranch
|
||||
import org.koitharu.kotatsu.download.ui.dialog.DownloadOption
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
|
||||
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
|
||||
|
||||
@@ -30,7 +29,7 @@ class DetailsMenuProvider(
|
||||
private val viewModel: DetailsViewModel,
|
||||
private val snackbarHost: View,
|
||||
private val appShortcutManager: AppShortcutManager,
|
||||
) : MenuProvider {
|
||||
) : MenuProvider, OnListItemClickListener<DownloadOption> {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.opt_details, menu)
|
||||
@@ -44,7 +43,7 @@ class DetailsMenuProvider(
|
||||
menu.findItem(R.id.action_shortcut).isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(activity)
|
||||
menu.findItem(R.id.action_scrobbling).isVisible = viewModel.isScrobblingAvailable
|
||||
menu.findItem(R.id.action_favourite).setIcon(
|
||||
if (viewModel.favouriteCategories.value == true) R.drawable.ic_heart else R.drawable.ic_heart_outline,
|
||||
if (viewModel.favouriteCategories.value) R.drawable.ic_heart else R.drawable.ic_heart_outline,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -80,15 +79,7 @@ class DetailsMenuProvider(
|
||||
}
|
||||
|
||||
R.id.action_save -> {
|
||||
viewModel.manga.value?.let {
|
||||
val chaptersCount = it.chapters?.size ?: 0
|
||||
val branches = viewModel.branches.value.orEmpty()
|
||||
if (chaptersCount > 5 || branches.size > 1) {
|
||||
showSaveConfirmation(it, chaptersCount, branches)
|
||||
} else {
|
||||
viewModel.download(null)
|
||||
}
|
||||
}
|
||||
DownloadDialogHelper(snackbarHost, viewModel).show(this)
|
||||
}
|
||||
|
||||
R.id.action_browser -> {
|
||||
@@ -125,35 +116,16 @@ class DetailsMenuProvider(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun showSaveConfirmation(manga: Manga, chaptersCount: Int, branches: List<MangaBranch>) {
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.save_manga)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
if (branches.size > 1) {
|
||||
val items = Array(branches.size) { i -> branches[i].name.orEmpty() }
|
||||
val currentBranch = branches.indexOfFirst { it.isSelected }
|
||||
val checkedIndices = BooleanArray(branches.size) { i -> i == currentBranch }
|
||||
dialogBuilder.setMultiChoiceItems(items, checkedIndices) { _, i, checked ->
|
||||
checkedIndices[i] = checked
|
||||
}.setPositiveButton(R.string.save) { _, _ ->
|
||||
val selectedBranches = branches.mapIndexedNotNullTo(HashSet()) { i, b ->
|
||||
if (checkedIndices[i]) b.name else null
|
||||
}
|
||||
val chaptersIds = manga.chapters?.mapNotNullToSet { c ->
|
||||
if (c.branch in selectedBranches) c.id else null
|
||||
}
|
||||
viewModel.download(chaptersIds)
|
||||
}
|
||||
} else {
|
||||
dialogBuilder.setMessage(
|
||||
activity.getString(
|
||||
R.string.large_manga_save_confirm,
|
||||
activity.resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount),
|
||||
),
|
||||
).setPositiveButton(R.string.save) { _, _ ->
|
||||
viewModel.download(null)
|
||||
override fun onItemClick(item: DownloadOption, view: View) {
|
||||
val chaptersIds: Set<Long>? = when (item) {
|
||||
is DownloadOption.WholeManga -> null
|
||||
is DownloadOption.SelectionHint -> {
|
||||
viewModel.startChaptersSelection()
|
||||
return
|
||||
}
|
||||
|
||||
else -> item.chaptersIds
|
||||
}
|
||||
dialogBuilder.show()
|
||||
viewModel.download(chaptersIds)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ class DetailsViewModel @Inject constructor(
|
||||
|
||||
val onShowToast = MutableEventFlow<Int>()
|
||||
val onShowTip = MutableEventFlow<Unit>()
|
||||
val onSelectChapter = MutableEventFlow<Long>()
|
||||
val onDownloadStarted = MutableEventFlow<Unit>()
|
||||
|
||||
val manga = doubleManga.map { it?.any }
|
||||
@@ -290,6 +291,14 @@ class DetailsViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun startChaptersSelection() {
|
||||
val chapters = chapters.value
|
||||
val chapter = chapters.find {
|
||||
it.isUnread && !it.isDownloaded
|
||||
} ?: chapters.firstOrNull() ?: return
|
||||
onSelectChapter.call(chapter.chapter.id)
|
||||
}
|
||||
|
||||
fun onButtonTipClosed() {
|
||||
settings.closeTip(DetailsActivity.TIP_BUTTON)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.view.View
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.ids
|
||||
import org.koitharu.kotatsu.core.ui.dialog.RecyclerViewAlertDialog
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.download.ui.dialog.DownloadOption
|
||||
import org.koitharu.kotatsu.download.ui.dialog.downloadOptionAD
|
||||
|
||||
class DownloadDialogHelper(
|
||||
private val host: View,
|
||||
private val viewModel: DetailsViewModel,
|
||||
) {
|
||||
|
||||
fun show(callback: OnListItemClickListener<DownloadOption>) {
|
||||
val branch = viewModel.selectedBranchValue
|
||||
val allChapters = viewModel.manga.value?.chapters ?: return
|
||||
val branchChapters = viewModel.manga.value?.getChapters(branch).orEmpty()
|
||||
val history = viewModel.history.value
|
||||
|
||||
val options = buildList {
|
||||
add(DownloadOption.WholeManga(allChapters.ids()))
|
||||
if (branch != null && branchChapters.isNotEmpty()) {
|
||||
add(DownloadOption.AllChapters(branch, branchChapters.ids()))
|
||||
}
|
||||
|
||||
if (history != null) {
|
||||
val unreadChapters = branchChapters.takeLastWhile { it.id != history.chapterId }
|
||||
if (unreadChapters.isNotEmpty() && unreadChapters.size < branchChapters.size) {
|
||||
add(DownloadOption.AllUnreadChapters(unreadChapters.ids(), branch))
|
||||
if (unreadChapters.size > 5) {
|
||||
add(DownloadOption.NextUnreadChapters(unreadChapters.take(5).ids()))
|
||||
if (unreadChapters.size > 10) {
|
||||
add(DownloadOption.NextUnreadChapters(unreadChapters.take(10).ids()))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (branchChapters.size > 5) {
|
||||
add(DownloadOption.FirstChapters(branchChapters.take(5).ids()))
|
||||
if (branchChapters.size > 10) {
|
||||
add(DownloadOption.FirstChapters(branchChapters.take(10).ids()))
|
||||
}
|
||||
}
|
||||
}
|
||||
add(DownloadOption.SelectionHint())
|
||||
}
|
||||
var dialog: DialogInterface? = null
|
||||
val listener = OnListItemClickListener<DownloadOption> { item, _ ->
|
||||
callback.onItemClick(item, host)
|
||||
dialog?.dismiss()
|
||||
}
|
||||
dialog = RecyclerViewAlertDialog.Builder<DownloadOption>(host.context)
|
||||
.addAdapterDelegate(downloadOptionAD(listener))
|
||||
.setCancelable(true)
|
||||
.setTitle(R.string.download)
|
||||
.setNegativeButton(android.R.string.cancel)
|
||||
.setItems(options)
|
||||
.create()
|
||||
.also { it.show() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.koitharu.kotatsu.download.ui.dialog
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.DrawableRes
|
||||
import org.koitharu.kotatsu.R
|
||||
import java.util.Locale
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
sealed interface DownloadOption {
|
||||
|
||||
val chaptersIds: Set<Long>
|
||||
|
||||
@get:DrawableRes
|
||||
val iconResId: Int
|
||||
|
||||
val chaptersCount: Int
|
||||
get() = chaptersIds.size
|
||||
|
||||
fun getLabel(resources: Resources): CharSequence
|
||||
|
||||
class AllChapters(
|
||||
val branch: String,
|
||||
override val chaptersIds: Set<Long>,
|
||||
) : DownloadOption {
|
||||
|
||||
override val iconResId = R.drawable.ic_select_group
|
||||
|
||||
override fun getLabel(resources: Resources): CharSequence {
|
||||
return resources.getString(R.string.download_option_all_chapters, branch)
|
||||
}
|
||||
}
|
||||
|
||||
class WholeManga(
|
||||
override val chaptersIds: Set<Long>,
|
||||
) : DownloadOption {
|
||||
|
||||
override val iconResId = materialR.drawable.abc_ic_menu_selectall_mtrl_alpha
|
||||
|
||||
override fun getLabel(resources: Resources): CharSequence {
|
||||
return resources.getString(R.string.download_option_whole_manga)
|
||||
}
|
||||
}
|
||||
|
||||
class FirstChapters(
|
||||
override val chaptersIds: Set<Long>,
|
||||
) : DownloadOption {
|
||||
|
||||
override val iconResId = R.drawable.ic_list_start
|
||||
|
||||
override fun getLabel(resources: Resources): CharSequence {
|
||||
return resources.getString(
|
||||
R.string.download_option_first_n_chapters,
|
||||
resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount)
|
||||
.lowercase(Locale.getDefault()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class AllUnreadChapters(
|
||||
override val chaptersIds: Set<Long>,
|
||||
val branch: String?,
|
||||
) : DownloadOption {
|
||||
|
||||
override val iconResId = R.drawable.ic_list_end
|
||||
|
||||
override fun getLabel(resources: Resources): CharSequence {
|
||||
return if (branch == null) {
|
||||
resources.getString(R.string.download_option_all_unread)
|
||||
} else {
|
||||
resources.getString(R.string.download_option_all_unread_b, branch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NextUnreadChapters(
|
||||
override val chaptersIds: Set<Long>,
|
||||
) : DownloadOption {
|
||||
|
||||
override val iconResId = R.drawable.ic_list_next
|
||||
|
||||
override fun getLabel(resources: Resources): CharSequence {
|
||||
return resources.getString(
|
||||
R.string.download_option_next_unread_n_chapters,
|
||||
resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount)
|
||||
.lowercase(Locale.getDefault()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionHint : DownloadOption {
|
||||
|
||||
override val chaptersIds: Set<Long> = emptySet()
|
||||
override val iconResId = R.drawable.ic_tap
|
||||
|
||||
override fun getLabel(resources: Resources): CharSequence {
|
||||
return resources.getString(R.string.download_option_manual_selection)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.koitharu.kotatsu.download.ui.dialog
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.databinding.ItemDownloadOptionBinding
|
||||
|
||||
fun downloadOptionAD(
|
||||
onClickListener: OnListItemClickListener<DownloadOption>,
|
||||
) = adapterDelegateViewBinding<DownloadOption, DownloadOption, ItemDownloadOptionBinding>(
|
||||
{ layoutInflater, parent -> ItemDownloadOptionBinding.inflate(layoutInflater, parent, false) },
|
||||
) {
|
||||
|
||||
binding.root.setOnClickListener { v -> onClickListener.onItemClick(item, v) }
|
||||
|
||||
bind {
|
||||
with(binding.root) {
|
||||
title = item.getLabel(resources)
|
||||
subtitle = if (item.chaptersCount == 0) null else resources.getQuantityString(
|
||||
R.plurals.chapters,
|
||||
item.chaptersCount,
|
||||
item.chaptersCount,
|
||||
)
|
||||
setIconResource(item.iconResId)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user