diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 9be9f02456..066d8706fb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index 1e7ba08d0d..a2dade8ec1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -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) { 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() + 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 } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt index 65cf86aae4..e855a62933 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt @@ -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 { - 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() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt index 0b7b43b242..130894fbf4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt @@ -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>): MangaChapterHolder { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterMatHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterMatHolder.kt index 6c194b1a2b..b4c8fad61e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterMatHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterMatHolder.kt @@ -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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt index 906391f69e..a317758899 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt @@ -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(null, controller, true) { @@ -26,8 +25,7 @@ class ChaptersAdapter( var items: List = 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) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DownloadCustomChaptersDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DownloadCustomChaptersDialog.kt deleted file mode 100644 index 13e2e80f7d..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DownloadCustomChaptersDialog.kt +++ /dev/null @@ -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 : 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" - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/chapters_mat_item.xml b/app/src/main/res/layout/chapters_mat_item.xml index f7bf6893e7..f5a19bab93 100644 --- a/app/src/main/res/layout/chapters_mat_item.xml +++ b/app/src/main/res/layout/chapters_mat_item.xml @@ -2,13 +2,14 @@ - - + android:title="@string/download_first" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b58c10bc90..4f45598dcc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -524,6 +524,8 @@ Mark all chapters as read? Remove from library All caught up + Select starting chapter + Select ending chapter Start reading @@ -549,11 +551,12 @@ Download Download custom amount Next chapter + First unread chapter Next 5 chapters Next 10 chapters - Custom - All - Unread + Custom range + All chapters + All unread chapters Are you sure you want to delete selected chapters? Migrate %1$d%2$s manga? @@ -743,5 +746,4 @@ Sort by chapter number Newest to oldest Oldest to newest -