Custom download chapter popup now replaced with range select

Long pressing the badge also starts range select
This commit is contained in:
Jay 2020-03-11 23:16:44 -07:00
parent dad12ce216
commit b3cda93f3c
10 changed files with 146 additions and 148 deletions

View File

@ -21,7 +21,6 @@ import android.view.WindowManager
import android.webkit.WebView
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.core.view.GestureDetectorCompat
@ -392,7 +391,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
val duration = resources.getInteger(android.R.integer.config_mediumAnimTime) * scale
delay(duration.toLong())
delay(100)
window?.statusBarColor = ColorUtils.setAlphaComponent(getResourceColor(android.R.attr
if (Color.alpha(window?.statusBarColor ?: Color.BLACK) >= 255)
window?.statusBarColor = ColorUtils.setAlphaComponent(getResourceColor(android.R.attr
.colorBackground), 175)
}
super.onSupportActionModeFinished(mode)

View File

@ -27,6 +27,9 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
@ -51,6 +54,7 @@ import com.bumptech.glide.signature.ObjectKey
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
@ -68,6 +72,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.library.LibraryController
@ -76,7 +81,6 @@ import eu.kanade.tachiyomi.ui.main.SearchActivity
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterMatHolder
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter
import eu.kanade.tachiyomi.ui.manga.chapter.DownloadCustomChaptersDialog
import eu.kanade.tachiyomi.ui.manga.info.EditMangaDialog
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
@ -101,12 +105,12 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
class MangaDetailsController : BaseController,
open class MangaDetailsController : BaseController,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
ActionMode.Callback,
ChaptersAdapter.MangaHeaderInterface,
ChangeMangaCategoriesDialog.Listener,
DownloadCustomChaptersDialog.Listener,
NoToolbarElevationController {
constructor(manga: Manga?,
@ -145,11 +149,18 @@ class MangaDetailsController : BaseController,
val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false)
var coverDrawable:Drawable? = null
var trackingBottomSheet: TrackingBottomSheet? = null
var startingDLChapterPos:Int? = null
/**
* Adapter containing a list of chapters.
*/
private var adapter: ChaptersAdapter? = null
/**
* Action mode for selections.
*/
private var actionMode: ActionMode? = null
// Hold a reference to the current animator,
// so that it can be canceled mid-way.
private var currentAnimator: Animator? = null
@ -209,6 +220,13 @@ class MangaDetailsController : BaseController,
val atTop = !recycler.canScrollVertically(-1)
if ((!atTop && !toolbarIsColored) || (atTop && toolbarIsColored)) {
toolbarIsColored = !atTop
val isCurrentController =
router?.backstack?.lastOrNull()?.controller() == this@MangaDetailsController
if (isCurrentController) setTitle()
if (actionMode != null) {
(activity as MainActivity).toolbar.setBackgroundColor(Color.TRANSPARENT)
return
}
val color =
coverColor ?: activity!!.getResourceColor(android.R.attr.colorPrimary)
val colorFrom =
@ -230,9 +248,6 @@ class MangaDetailsController : BaseController,
activity?.window?.statusBarColor = (animator.animatedValue as Int)
}
colorAnimator?.start()
val isCurrentController =
router?.backstack?.lastOrNull()?.controller() == this@MangaDetailsController
if (isCurrentController) setTitle()
}
}
})
@ -310,11 +325,10 @@ class MangaDetailsController : BaseController,
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (type == ControllerChangeType.PUSH_ENTER || type == ControllerChangeType.POP_ENTER) {
if (type == ControllerChangeType.POP_ENTER)
return
setStatusBar()
(activity as MainActivity).appbar.setBackgroundColor(Color.TRANSPARENT)
(activity as MainActivity).toolbar.setBackgroundColor(Color.TRANSPARENT)
activity?.window?.statusBarColor = Color.TRANSPARENT
(activity as MainActivity).toolbar.setBackgroundColor(activity?.window?.statusBarColor
?: Color.TRANSPARENT)
}
else if (type == ControllerChangeType.PUSH_EXIT || type == ControllerChangeType.POP_EXIT) {
if (router.backstack.lastOrNull()?.controller() is DialogController)
@ -349,7 +363,6 @@ class MangaDetailsController : BaseController,
activity?.invalidateOptionsMenu()
}
fun updateChapters(chapters: List<ChapterItem>) {
swipe_refresh?.isRefreshing = presenter.isLoading
if (presenter.chapters.isEmpty() && fromCatalogue && !presenter.hasRequested) {
@ -365,6 +378,32 @@ class MangaDetailsController : BaseController,
override fun onItemClick(view: View?, position: Int): Boolean {
val chapter = adapter?.getItem(position)?.chapter ?: return false
if (chapter.isHeader) return false
if (actionMode != null) {
if (startingDLChapterPos == null) {
adapter?.addSelection(position)
(recycler.findViewHolderForAdapterPosition(position) as? BaseFlexibleViewHolder)
?.toggleActivation()
startingDLChapterPos = position
actionMode?.invalidate()
}
else {
val startingPosition = startingDLChapterPos ?: return false
var chapterList = listOf<ChapterItem>()
when {
startingPosition > position ->
chapterList = presenter.chapters.subList(position - 1, startingPosition)
startingPosition <= position ->
chapterList = presenter.chapters.subList(startingPosition - 1, position)
}
downloadChapters(chapterList)
adapter?.removeSelection(startingPosition)
(recycler.findViewHolderForAdapterPosition(startingPosition) as? BaseFlexibleViewHolder)
?.toggleActivation()
startingDLChapterPos = null
destroyActionModeIfNeeded()
}
return false
}
openChapter(chapter)
return false
}
@ -551,7 +590,7 @@ class MangaDetailsController : BaseController,
R.id.download_next_5 -> presenter.getUnreadChaptersSorted().take(5)
R.id.download_next_10 -> presenter.getUnreadChaptersSorted().take(10)
R.id.download_custom -> {
showCustomDownloadDialog()
createActionModeIfNeeded()
return
}
R.id.download_unread -> presenter.chapters.filter { !it.read }
@ -669,15 +708,9 @@ class MangaDetailsController : BaseController,
}
}
private fun showCustomDownloadDialog() {
DownloadCustomChaptersDialog(this, presenter.chapters.size).showDialog(router)
}
override fun downloadCustomChapters(amount: Int) {
val chaptersToDownload = presenter.getUnreadChaptersSorted().take(amount)
if (chaptersToDownload.isNotEmpty()) {
downloadChapters(chaptersToDownload)
}
override fun startDownloadRange(position: Int) {
createActionModeIfNeeded()
onItemClick(null, position)
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
@ -903,6 +936,61 @@ class MangaDetailsController : BaseController,
trackingBottomSheet?.onSearchResultsError(error)
}
/**
* Creates the action mode if it's not created already.
*/
private fun createActionModeIfNeeded() {
if (actionMode == null) {
actionMode = (activity as AppCompatActivity).startSupportActionMode(this)
(activity as MainActivity).toolbar.setBackgroundColor(Color.TRANSPARENT)
val view = activity?.window?.currentFocus ?: return
val imm = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
?: return
imm.hideSoftInputFromWindow(view.windowToken, 0)
if (adapter?.mode != SelectableAdapter.Mode.MULTI) {
adapter?.mode = SelectableAdapter.Mode.MULTI
}
}
}
/**
* Destroys the action mode.
*/
private fun destroyActionModeIfNeeded() {
actionMode?.finish()
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return true
}
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return true
}
override fun onDestroyActionMode(mode: ActionMode?) {
actionMode = null
setStatusBar()
startingDLChapterPos = null
adapter?.mode = SelectableAdapter.Mode.IDLE
adapter?.clearSelection()
return
}
private fun setStatusBar() {
activity?.window?.statusBarColor = if (toolbarIsColored) {
val translucentColor = ColorUtils.setAlphaComponent(coverColor ?: Color.TRANSPARENT, 175)
(activity as MainActivity).toolbar.setBackgroundColor(translucentColor)
translucentColor
} else Color.TRANSPARENT
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
mode?.title = view?.context?.getString(if (startingDLChapterPos == null)
R.string.select_start_chapter else R.string.select_end_chapter)
return false
}
override fun zoomImageFromThumb(thumbView: View) {
// If there's an animation in progress, cancel it immediately and proceed with this one.
currentAnimator?.cancel()
@ -1040,9 +1128,5 @@ class MangaDetailsController : BaseController,
const val FROM_CATALOGUE_EXTRA = "from_catalogue"
const val MANGA_EXTRA = "manga"
const val INFO_CONTROLLER = 0
const val CHAPTERS_CONTROLLER = 1
const val TRACK_CONTROLLER = 2
}
}

View File

@ -32,12 +32,10 @@ class MangaHeaderHolder(
startExpanded: Boolean
) : MangaChapterHolder(view, adapter) {
init {
start_reading_button.setOnClickListener { adapter.coverListener?.readNextChapter() }
start_reading_button.setOnClickListener { adapter.coverListener.readNextChapter() }
top_view.updateLayoutParams<ConstraintLayout.LayoutParams> {
height = adapter.coverListener?.topCoverHeight() ?: 0
height = adapter.coverListener.topCoverHeight()
}
more_button.setOnClickListener { expandDesc() }
manga_summary.setOnClickListener { expandDesc() }
@ -48,30 +46,30 @@ class MangaHeaderHolder(
more_button_group.visible()
}
manga_genres_tags.setOnTagClickListener {
adapter.coverListener?.tagClicked(it)
adapter.coverListener.tagClicked(it)
}
filter_button.setOnClickListener { adapter.coverListener?.showChapterFilter() }
filters_text.setOnClickListener { adapter.coverListener?.showChapterFilter() }
chapters_title.setOnClickListener { adapter.coverListener?.showChapterFilter() }
webview_button.setOnClickListener { adapter.coverListener?.openInWebView() }
share_button.setOnClickListener { adapter.coverListener?.prepareToShareManga() }
filter_button.setOnClickListener { adapter.coverListener.showChapterFilter() }
filters_text.setOnClickListener { adapter.coverListener.showChapterFilter() }
chapters_title.setOnClickListener { adapter.coverListener.showChapterFilter() }
webview_button.setOnClickListener { adapter.coverListener.openInWebView() }
share_button.setOnClickListener { adapter.coverListener.prepareToShareManga() }
favorite_button.setOnClickListener {
adapter.coverListener?.favoriteManga(false)
adapter.coverListener.favoriteManga(false)
}
favorite_button.setOnLongClickListener {
adapter.coverListener?.favoriteManga(true)
adapter.coverListener.favoriteManga(true)
true
}
manga_full_title.setOnLongClickListener {
adapter.coverListener?.copyToClipboard(manga_full_title.text.toString(), R.string.manga_info_full_title_label)
adapter.coverListener.copyToClipboard(manga_full_title.text.toString(), R.string.manga_info_full_title_label)
true
}
manga_author.setOnLongClickListener {
adapter.coverListener?.copyToClipboard(manga_author.text.toString(), R.string.manga_info_author_label)
adapter.coverListener.copyToClipboard(manga_author.text.toString(), R.string.manga_info_author_label)
true
}
manga_cover.setOnClickListener { adapter.coverListener?.zoomImageFromThumb(cover_card) }
track_button.setOnClickListener { adapter.coverListener?.showTrackingSheet() }
manga_cover.setOnClickListener { adapter.coverListener.zoomImageFromThumb(cover_card) }
track_button.setOnClickListener { adapter.coverListener.showTrackingSheet() }
if (startExpanded)
expandDesc()
}

View File

@ -41,7 +41,7 @@ class ChapterItem(val chapter: Chapter, val manga: Manga) :
}
override fun isSelectable(): Boolean {
return chapter.isHeader
return !chapter.isHeader
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MangaChapterHolder {

View File

@ -20,17 +20,17 @@ class ChapterMatHolder(
) : MangaChapterHolder(view, adapter) {
init {
// We need to post a Runnable to show the popup to make sure that the PopupMenu is
// correctly positioned. The reason being that the view may change position before the
// PopupMenu is shown.
//chapter_menu.setOnClickListener { it.post { showPopupMenu(it) } }
download_button.setOnClickListener { downloadOrRemoveMenu() }
download_button.setOnLongClickListener {
adapter.coverListener.startDownloadRange(adapterPosition)
true
}
}
private fun downloadOrRemoveMenu() {
val chapter = adapter.getItem(adapterPosition) ?: return
if (chapter.status == Download.NOT_DOWNLOADED || chapter.status == Download.ERROR) {
adapter.coverListener?.downloadChapter(adapterPosition)
adapter.coverListener.downloadChapter(adapterPosition)
} else {
download_button.post {
// Create a PopupMenu, giving it the clicked view for an anchor
@ -46,7 +46,7 @@ class ChapterMatHolder(
// Set a listener so we are notified if a menu item is clicked
popup.setOnMenuItemClickListener { _ ->
adapter.coverListener?.downloadChapter(adapterPosition)
adapter.coverListener.downloadChapter(adapterPosition)
true
}

View File

@ -1,14 +1,13 @@
package eu.kanade.tachiyomi.ui.manga.chapter
import android.content.Context
import android.view.MenuItem
import android.view.View
import androidx.fragment.app.FragmentActivity
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.manga.MangaDetailsPresenter
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.system.getResourceColor
@ -18,7 +17,7 @@ import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
class ChaptersAdapter(
val controller: BaseController,
val controller: MangaDetailsController,
context: Context
) : FlexibleAdapter<ChapterItem>(null, controller, true) {
@ -26,8 +25,7 @@ class ChaptersAdapter(
var items: List<ChapterItem> = emptyList()
val menuItemListener: OnMenuItemClickListener? = controller as? OnMenuItemClickListener
val coverListener: MangaHeaderInterface? = controller as? MangaHeaderInterface
val coverListener: MangaHeaderInterface = controller
val readColor = context.getResourceColor(android.R.attr.textColorHint)
@ -54,10 +52,6 @@ class ChaptersAdapter(
SecureActivityDelegate.promptLockIfNeeded(activity)
}
interface OnMenuItemClickListener {
fun onMenuItemClick(position: Int, item: MenuItem)
}
interface MangaHeaderInterface {
fun coverColor(): Int?
fun mangaPresenter(): MangaDetailsPresenter
@ -72,5 +66,6 @@ class ChaptersAdapter(
fun copyToClipboard(content: String, label: Int)
fun zoomImageFromThumb(thumbView: View)
fun showTrackingSheet()
fun startDownloadRange(position: Int)
}
}

View File

@ -1,76 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.chapter
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.bluelinelabs.conductor.Controller
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.widget.DialogCustomDownloadView
/**
* Dialog used to let user select amount of chapters to download.
*/
class DownloadCustomChaptersDialog<T> : DialogController
where T : Controller, T : DownloadCustomChaptersDialog.Listener {
/**
* Maximum number of chapters to download in download chooser.
*/
private val maxChapters: Int
/**
* Initialize dialog.
* @param maxChapters maximal number of chapters that user can download.
*/
constructor(target: T, maxChapters: Int) : super(Bundle().apply {
// Add maximum number of chapters to download value to bundle.
putInt(KEY_ITEM_MAX, maxChapters)
}) {
targetController = target
this.maxChapters = maxChapters
}
/**
* Restore dialog.
* @param bundle bundle containing data from state restore.
*/
@Suppress("unused")
constructor(bundle: Bundle) : super(bundle) {
// Get maximum chapters to download from bundle
val maxChapters = bundle.getInt(KEY_ITEM_MAX, 0)
this.maxChapters = maxChapters
}
/**
* Called when dialog is being created.
*/
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val activity = activity!!
// Initialize view that lets user select number of chapters to download.
val view = DialogCustomDownloadView(activity).apply {
setMinMax(0, maxChapters)
}
// Build dialog.
// when positive dialog is pressed call custom listener.
return MaterialDialog(activity)
.title(R.string.custom_download)
.customView(view = view, scrollable = true)
.positiveButton(android.R.string.ok) {
(targetController as? Listener)?.downloadCustomChapters(view.amount)
}
.negativeButton(android.R.string.cancel)
}
interface Listener {
fun downloadCustomChapters(amount: Int)
}
private companion object {
// Key to retrieve max chapters from bundle on process death.
const val KEY_ITEM_MAX = "DownloadCustomChaptersDialog.int.maxChapters"
}
}

View File

@ -2,13 +2,14 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/chapter_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="?attr/selectable_list_drawable">
<TextView
android:id="@+id/chapter_title"
style="@style/TextAppearance.Regular.Body1"
style="@style/TextAppearance.MaterialComponents.Body2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@ -23,7 +24,7 @@
<TextView
android:id="@+id/chapter_scanlator"
style="@style/TextAppearance.Regular.Caption.Hint"
style="@style/TextAppearance.MaterialComponents.Caption"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"

View File

@ -16,13 +16,7 @@
<menu>
<item
android:id="@+id/download_next"
android:title="@string/download_1" />
<item
android:id="@+id/download_next_5"
android:title="@string/download_5" />
<item
android:id="@+id/download_next_10"
android:title="@string/download_10" />
android:title="@string/download_first" />
<item
android:id="@+id/download_custom"
android:title="@string/download_custom" />

View File

@ -524,6 +524,8 @@
<string name="mark_all_as_read_message">Mark all chapters as read?</string>
<string name="remove_from_library">Remove from library</string>
<string name="all_caught_up">All caught up</string>
<string name="select_start_chapter">Select starting chapter</string>
<string name="select_end_chapter">Select ending chapter</string>
<!-- Manga chapters fragment -->
<string name="start_reading">Start reading</string>
@ -549,11 +551,12 @@
<string name="manga_download">Download</string>
<string name="custom_download">Download custom amount</string>
<string name="download_1">Next chapter</string>
<string name="download_first">First unread chapter</string>
<string name="download_5">Next 5 chapters</string>
<string name="download_10">Next 10 chapters</string>
<string name="download_custom">Custom</string>
<string name="download_all">All</string>
<string name="download_unread">Unread</string>
<string name="download_custom">Custom range</string>
<string name="download_all">All chapters</string>
<string name="download_unread">All unread chapters</string>
<string name="confirm_delete_chapters">Are you sure you want to delete selected chapters?</string>
<plurals name="confirm_migration">
<item quantity="one">Migrate %1$d%2$s manga?</item>
@ -743,5 +746,4 @@
<string name="sort_by_chapter_number">Sort by chapter number</string>
<string name="newest_first">Newest to oldest</string>
<string name="oldest_first">Oldest to newest</string>
</resources>