diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index 4e23599d0..0a5af76a7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -5,6 +5,7 @@ import com.hippo.unifile.UniFile import com.jakewharton.rxrelay.BehaviorRelay import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.DownloadQueue import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager @@ -92,6 +93,18 @@ class DownloadManager(context: Context) { downloader.clearQueue(isNotification) } + /** + * Reorders the download queue. + * + * @param downloads value to set the download queue to + */ + fun reorderQueue(downloads: List) { + downloader.pause() + downloader.queue.clear() + downloader.queue.addAll(downloads) + downloader.start() + } + /** * Tells the downloader to enqueue the given list of chapters. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 6c86c1d72..b5a43e168 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -95,7 +95,7 @@ class Downloader( fun start(): Boolean { if (isRunning || queue.isEmpty()) return false - + notifier.paused = false if (!subscriptions.hasSubscriptions()) initializeSubscriptions() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt index 795b85f13..76bc02a33 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt @@ -1,72 +1,24 @@ package eu.kanade.tachiyomi.ui.download -import android.support.v7.widget.RecyclerView -import android.view.ViewGroup -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.download.model.Download -import eu.kanade.tachiyomi.util.inflate +import eu.davidea.flexibleadapter.FlexibleAdapter /** * Adapter storing a list of downloads. * * @param context the context of the fragment containing this adapter. */ -class DownloadAdapter : RecyclerView.Adapter() { - - private var items = emptyList() - - init { - setHasStableIds(true) - } +class DownloadAdapter(controller: DownloadController) : FlexibleAdapter(null, controller, + true) { /** - * Sets a list of downloads in the adapter. - * - * @param downloads the list to set. + * Listener called when an item of the list is released. */ - fun setItems(downloads: List) { - items = downloads - notifyDataSetChanged() - } + val onItemReleaseListener: OnItemReleaseListener = controller - /** - * Returns the number of downloads in the adapter - */ - override fun getItemCount(): Int { - return items.size + interface OnItemReleaseListener { + /** + * Called when an item of the list is released. + */ + fun onItemReleased(position: Int) } - - /** - * Returns the identifier for a download. - * - * @param position the position in the adapter. - * @return an identifier for the item. - */ - override fun getItemId(position: Int): Long { - return items[position].chapter.id!! - } - - /** - * Creates a new view holder. - * - * @param parent the parent view. - * @param viewType the type of the holder. - * @return a new view holder for a manga. - */ - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadHolder { - val view = parent.inflate(R.layout.download_item) - return DownloadHolder(view) - } - - /** - * Binds a holder with a new position. - * - * @param holder the holder to bind. - * @param position the position to bind. - */ - override fun onBindViewHolder(holder: DownloadHolder, position: Int) { - val download = items[position] - holder.onSetValues(download) - } - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt index 9252374f1..eeedd17cc 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt @@ -18,7 +18,8 @@ import java.util.concurrent.TimeUnit * Controller that shows the currently active downloads. * Uses R.layout.fragment_download_queue. */ -class DownloadController : NucleusController() { +class DownloadController : NucleusController(), + DownloadAdapter.OnItemReleaseListener { /** * Adapter containing the active downloads. @@ -58,8 +59,9 @@ class DownloadController : NucleusController() { setInformationView() // Initialize adapter. - adapter = DownloadAdapter() + adapter = DownloadAdapter(this@DownloadController) recycler.adapter = adapter + adapter?.isHandleDragEnabled = true // Set the layout manager for the recycler and fixed size. recycler.layoutManager = LinearLayoutManager(view.context) @@ -168,7 +170,7 @@ class DownloadController : NucleusController() { // Avoid leaking subscriptions progressSubscriptions.remove(download)?.unsubscribe() - progressSubscriptions.put(download, subscription) + progressSubscriptions[download] = subscription } /** @@ -198,10 +200,10 @@ class DownloadController : NucleusController() { * * @param downloads the downloads from the queue. */ - fun onNextDownloads(downloads: List) { + fun onNextDownloads(downloads: List) { activity?.invalidateOptionsMenu() setInformationView() - adapter?.setItems(downloads) + adapter?.updateDataSet(downloads) } /** @@ -244,4 +246,15 @@ class DownloadController : NucleusController() { } } + /** + * Called when an item is released from a drag. + * + * @param position The position of the released item. + */ + override fun onItemReleased(position: Int) { + val adapter = adapter ?: return + val downloads = (0 until adapter.itemCount).mapNotNull { adapter.getItem(it)?.download } + presenter.reorder(downloads) + } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt index fefc804a0..57966518d 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt @@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.download import android.view.View import eu.kanade.tachiyomi.data.download.model.Download -import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder -import kotlinx.android.synthetic.main.download_item.view.* +import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder +import kotlinx.android.synthetic.main.download_item.* /** * Class used to hold the data of a download. @@ -12,33 +12,36 @@ import kotlinx.android.synthetic.main.download_item.view.* * @param view the inflated view for this holder. * @constructor creates a new download holder. */ -class DownloadHolder(private val view: View) : BaseViewHolder(view) { +class DownloadHolder(view: View, val adapter: DownloadAdapter) : BaseFlexibleViewHolder(view, adapter) { + + init { + setDragHandleView(reorder) + } private lateinit var download: Download /** - * Method called from [DownloadAdapter.onBindViewHolder]. It updates the data for this - * holder with the given download. + * Binds this holder with the given category * - * @param download the download to bind. + * @param category The category to bind */ - fun onSetValues(download: Download) { + fun bind(download: Download) { this.download = download // Update the chapter name. - view.chapter_title.text = download.chapter.name + chapter_title.text = download.chapter.name // Update the manga title - view.manga_title.text = download.manga.title + manga_title.text = download.manga.title // Update the progress bar and the number of downloaded pages val pages = download.pages if (pages == null) { - view.download_progress.progress = 0 - view.download_progress.max = 1 - view.download_progress_text.text = "" + download_progress.progress = 0 + download_progress.max = 1 + download_progress_text.text = "" } else { - view.download_progress.max = pages.size * 100 + download_progress.max = pages.size * 100 notifyProgress() notifyDownloadedPages() } @@ -49,10 +52,10 @@ class DownloadHolder(private val view: View) : BaseViewHolder(view) { */ fun notifyProgress() { val pages = download.pages ?: return - if (view.download_progress.max == 1) { - view.download_progress.max = pages.size * 100 + if (download_progress.max == 1) { + download_progress.max = pages.size * 100 } - view.download_progress.progress = download.totalProgress + download_progress.progress = download.totalProgress } /** @@ -60,7 +63,12 @@ class DownloadHolder(private val view: View) : BaseViewHolder(view) { */ fun notifyDownloadedPages() { val pages = download.pages ?: return - view.download_progress_text.text = "${download.downloadedImages}/${pages.size}" + download_progress_text.text = "${download.downloadedImages}/${pages.size}" + } + + override fun onItemReleased(position: Int) { + super.onItemReleased(position) + adapter.onItemReleaseListener.onItemReleased(position) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadItem.kt new file mode 100644 index 000000000..48cc31b9c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadItem.kt @@ -0,0 +1,68 @@ +package eu.kanade.tachiyomi.ui.download + +import android.view.View +import android.support.v7.widget.RecyclerView +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.download.model.Download + +class DownloadItem(val download: Download) : AbstractFlexibleItem() { + + /** + * Whether this item is currently selected. + */ + var isSelected = false + + /** + * Returns the layout resource for this item. + */ + override fun getLayoutRes(): Int { + return R.layout.download_item + } + + /** + * Returns a new view holder for this item. + * + * @param view The view of this item. + * @param adapter The adapter of this item. + */ + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): DownloadHolder { + return DownloadHolder(view, adapter as DownloadAdapter) + } + + /** + * Binds the given view holder with this item. + * + * @param adapter The adapter of this item. + * @param holder The holder to bind. + * @param position The position of this item in the adapter. + * @param payloads List of partial changes. + */ + override fun bindViewHolder(adapter: FlexibleAdapter>, + holder: DownloadHolder, position: Int, payloads: MutableList) { + holder.bind(download) + } + + /** + * Returns true if this item is draggable. + */ + override fun isDraggable(): Boolean { + return true + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other is DownloadItem) { + return download.chapter.id == other.download.chapter.id + } + return false + } + + override fun hashCode(): Int { + return download.chapter.id!!.toInt() + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt index 0577176e9..abf4b92a1 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt @@ -32,10 +32,10 @@ class DownloadPresenter : BasePresenter() { downloadQueue.getUpdatedObservable() .observeOn(AndroidSchedulers.mainThread()) - .map { ArrayList(it) } - .subscribeLatestCache(DownloadController::onNextDownloads, { _, error -> + .map { it.map(::DownloadItem) } + .subscribeLatestCache(DownloadController::onNextDownloads) { _, error -> Timber.e(error) - }) + } } fun getDownloadStatusObservable(): Observable { @@ -62,4 +62,7 @@ class DownloadPresenter : BasePresenter() { downloadManager.clearQueue() } -} \ No newline at end of file + fun reorder(downloads: List) { + downloadManager.reorderQueue(downloads) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt index 1f7a43d28..726796dea 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt @@ -65,6 +65,8 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att */ private var subscriptions = CompositeSubscription() + private var lastClickPosition = -1 + // EXH --> private var initialLoadHandle: LoadingHandle? = null lateinit var scope: CoroutineScope @@ -233,6 +235,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att } is LibrarySelectionEvent.Unselected -> { findAndToggleSelection(event.manga) + if (adapter.indexOf(event.manga) != -1) lastClickPosition = -1 if (controller.selectedMangas.isEmpty()) { adapter.mode = SelectableAdapter.Mode.SINGLE } @@ -240,6 +243,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att is LibrarySelectionEvent.Cleared -> { adapter.mode = SelectableAdapter.Mode.SINGLE adapter.clearSelection() + lastClickPosition = -1 } } } @@ -267,6 +271,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att // If the action mode is created and the position is valid, toggle the selection. val item = adapter.getItem(position) ?: return false if (adapter.mode == SelectableAdapter.Mode.MULTI) { + lastClickPosition = position toggleSelection(position) return true } else { @@ -282,7 +287,16 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att */ override fun onItemLongClick(position: Int) { controller.createActionModeIfNeeded() - toggleSelection(position) + when { + lastClickPosition == -1 -> setSelection(position) + lastClickPosition > position -> for (i in position until lastClickPosition) + setSelection(i) + lastClickPosition < position -> for (i in lastClickPosition + 1..position) + setSelection(i) + else -> setSelection(position) + } + lastClickPosition = position + } /** @@ -305,5 +319,16 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att controller.setSelection(item.manga, !adapter.isSelected(position)) controller.invalidateActionMode() } + /** + * Tells the presenter to set the selection for the given position. + * + * @param position the position to toggle. + */ + private fun setSelection(position: Int) { + val item = adapter.getItem(position) ?: return + + controller.setSelection(item.manga, true) + controller.invalidateActionMode() + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index d9fa12e0a..da94cc547 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -58,7 +58,42 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference */ override fun filter(constraint: String): Boolean { return manga.title.contains(constraint, true) || - (manga.author?.contains(constraint, true) ?: false) + (manga.author?.contains(constraint, true) ?: false) || + if (constraint.contains(" ") || constraint.contains("\"")) { + val genres = manga.genre?.split(", ")?.map { + it.drop(it.indexOfFirst{it==':'}+1).toLowerCase().trim() //tachiEH tag namespaces + } + var clean_constraint = "" + var ignorespace = false + for (i in constraint.trim().toLowerCase()) { + if (i==' ') { + if (!ignorespace) { + clean_constraint = clean_constraint + "," + } else { + clean_constraint = clean_constraint + " " + } + } else if (i=='"') { + ignorespace = !ignorespace + } else { + clean_constraint = clean_constraint + Character.toString(i) + } + } + clean_constraint.split(",").all { containsGenre(it.trim(), genres) } + } + else containsGenre(constraint, manga.genre?.split(", ")?.map { + it.drop(it.indexOfFirst{it==':'}+1).toLowerCase().trim() //tachiEH tag namespaces + }) + } + + private fun containsGenre(tag: String, genres: List?): Boolean { + return if (tag.startsWith("-")) + genres?.find { + it.trim().toLowerCase() == tag.substringAfter("-").toLowerCase() + } == null + else + genres?.find { + it.trim().toLowerCase() == tag.toLowerCase() + } != null } override fun equals(other: Any?): Boolean { @@ -71,4 +106,4 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference override fun hashCode(): Int { return manga.id!!.hashCode() } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt index 785e1b5aa..e65eb9910 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt @@ -60,6 +60,8 @@ class ChaptersController : NucleusController(), */ private val selectedItems = mutableSetOf() + private var lastClickPosition = -1 + init { setHasOptionsMenu(true) setOptionsMenuHidden(true) @@ -275,6 +277,7 @@ class ChaptersController : NucleusController(), val adapter = adapter ?: return false val item = adapter.getItem(position) ?: return false if (actionMode != null && adapter.mode == SelectableAdapter.Mode.MULTI) { + lastClickPosition = position toggleSelection(position) return true } else { @@ -285,7 +288,16 @@ class ChaptersController : NucleusController(), override fun onItemLongClick(position: Int) { createActionModeIfNeeded() - toggleSelection(position) + when { + lastClickPosition == -1 -> setSelection(position) + lastClickPosition > position -> for (i in position until lastClickPosition) + setSelection(i) + lastClickPosition < position -> for (i in lastClickPosition + 1..position) + setSelection(i) + else -> setSelection(position) + } + lastClickPosition = position + adapter?.notifyDataSetChanged() } // SELECTIONS & ACTION MODE @@ -294,6 +306,7 @@ class ChaptersController : NucleusController(), val adapter = adapter ?: return val item = adapter.getItem(position) ?: return adapter.toggleSelection(position) + adapter.notifyDataSetChanged() if (adapter.isSelected(position)) { selectedItems.add(item) } else { @@ -302,6 +315,16 @@ class ChaptersController : NucleusController(), actionMode?.invalidate() } + private fun setSelection(position: Int) { + val adapter = adapter ?: return + val item = adapter.getItem(position) ?: return + if (!adapter.isSelected(position)) { + adapter.toggleSelection(position) + selectedItems.add(item) + actionMode?.invalidate() + } + } + private fun getSelectedChapters(): List { val adapter = adapter ?: return emptyList() return adapter.selectedPositions.mapNotNull { adapter.getItem(it) } @@ -314,6 +337,7 @@ class ChaptersController : NucleusController(), } private fun destroyActionModeIfNeeded() { + lastClickPosition = -1 actionMode?.finish() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt index 95d3408d2..a93077d9b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt @@ -102,7 +102,10 @@ class WebtoonViewer(val activity: ReaderActivity) : BaseViewer { }) recycler.tapListener = { event -> val positionX = event.rawX + val positionY = event.rawY when { + positionY < recycler.height * 0.25 -> if (config.tappingEnabled) scrollUp() + positionY > recycler.height * 0.75 -> if (config.tappingEnabled) scrollDown() positionX < recycler.width * 0.33 -> if (config.tappingEnabled) scrollUp() positionX > recycler.width * 0.66 -> if (config.tappingEnabled) scrollDown() else -> activity.toggleMenu() diff --git a/app/src/main/res/layout/download_item.xml b/app/src/main/res/layout/download_item.xml index 2008002f8..15666e31a 100755 --- a/app/src/main/res/layout/download_item.xml +++ b/app/src/main/res/layout/download_item.xml @@ -2,9 +2,9 @@ @@ -25,6 +25,7 @@ android:layout_alignParentLeft="true" android:maxLines="1" android:ellipsize="end" + android:layout_marginStart="@dimen/material_component_lists_single_line_with_avatar_height" android:textAppearance="@style/TextAppearance.Regular.Body1" tools:text="Manga title"/> @@ -36,6 +37,7 @@ android:maxLines="1" android:ellipsize="end" tools:text="Chapter Title" + android:layout_marginStart="@dimen/material_component_lists_single_line_with_avatar_height" android:textAppearance="@style/TextAppearance.Regular.Caption"/> - \ No newline at end of file + + +