Add download queue features from J2K fork

This commit is contained in:
arkon
2020-02-23 12:42:10 -05:00
parent 3e5a48e5e4
commit fb897e37d1
16 changed files with 439 additions and 127 deletions

View File

@@ -1,71 +1,26 @@
package eu.kanade.tachiyomi.ui.download
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.util.view.inflate
import android.view.MenuItem
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<DownloadHolder>() {
private var items = emptyList<Download>()
init {
setHasStableIds(true)
}
class DownloadAdapter(controller: DownloadController) : FlexibleAdapter<DownloadItem>(
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<Download>) {
items = downloads
notifyDataSetChanged()
}
val downloadItemListener: DownloadItemListener = controller
/**
* Returns the number of downloads in the adapter
*/
override fun getItemCount(): Int {
return items.size
}
/**
* 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)
interface DownloadItemListener {
fun onItemReleased(position: Int)
fun onMenuItemClick(position: Int, menuItem: MenuItem)
}
}

View File

@@ -24,7 +24,8 @@ import rx.android.schedulers.AndroidSchedulers
* Controller that shows the currently active downloads.
* Uses R.layout.fragment_download_queue.
*/
class DownloadController : NucleusController<DownloadPresenter>() {
class DownloadController : NucleusController<DownloadPresenter>(),
DownloadAdapter.DownloadItemListener {
/**
* Adapter containing the active downloads.
@@ -64,14 +65,15 @@ class DownloadController : NucleusController<DownloadPresenter>() {
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)
recycler.setHasFixedSize(true)
// Suscribe to changes
// Subscribe to changes
DownloadService.runningRelay
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy { onQueueStatusChange(it) }
@@ -99,14 +101,10 @@ class DownloadController : NucleusController<DownloadPresenter>() {
}
override fun onPrepareOptionsMenu(menu: Menu) {
// Set start button visibility.
menu.findItem(R.id.start_queue).isVisible = !isRunning && !presenter.downloadQueue.isEmpty()
// Set pause button visibility.
menu.findItem(R.id.pause_queue).isVisible = isRunning
// Set clear button visibility.
menu.findItem(R.id.clear_queue).isVisible = !presenter.downloadQueue.isEmpty()
menu.findItem(R.id.reorder).isVisible = !presenter.downloadQueue.isEmpty()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -121,6 +119,16 @@ class DownloadController : NucleusController<DownloadPresenter>() {
DownloadService.stop(context)
presenter.clearQueue()
}
R.id.newest, R.id.oldest -> {
val adapter = adapter ?: return false
val items = adapter.currentItems.sortedBy { it.download.chapter.date_upload }
.toMutableList()
if (item.itemId == R.id.newest)
items.reverse()
adapter.updateDataSet(items)
val downloads = items.mapNotNull { it.download }
presenter.reorder(downloads)
}
}
return super.onOptionsItemSelected(item)
}
@@ -173,7 +181,7 @@ class DownloadController : NucleusController<DownloadPresenter>() {
// Avoid leaking subscriptions
progressSubscriptions.remove(download)?.unsubscribe()
progressSubscriptions.put(download, subscription)
progressSubscriptions[download] = subscription
}
/**
@@ -203,10 +211,10 @@ class DownloadController : NucleusController<DownloadPresenter>() {
*
* @param downloads the downloads from the queue.
*/
fun onNextDownloads(downloads: List<Download>) {
fun onNextDownloads(downloads: List<DownloadItem>) {
activity?.invalidateOptionsMenu()
setInformationView()
adapter?.setItems(downloads)
adapter?.updateDataSet(downloads)
}
/**
@@ -214,7 +222,7 @@ class DownloadController : NucleusController<DownloadPresenter>() {
*
* @param download the download whose progress has changed.
*/
fun onUpdateProgress(download: Download) {
private fun onUpdateProgress(download: Download) {
getHolder(download)?.notifyProgress()
}
@@ -223,7 +231,7 @@ class DownloadController : NucleusController<DownloadPresenter>() {
*
* @param download the download whose page has been downloaded.
*/
fun onUpdateDownloadedPages(download: Download) {
private fun onUpdateDownloadedPages(download: Download) {
getHolder(download)?.notifyDownloadedPages()
}
@@ -247,4 +255,48 @@ class DownloadController : NucleusController<DownloadPresenter>() {
empty_view?.hide()
}
}
/**
* 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)
}
/**
* Called when the menu item of a download is pressed
*
* @param position The position of the item
* @param menuItem The menu Item pressed
*/
override fun onMenuItemClick(position: Int, menuItem: MenuItem) {
when (menuItem.itemId) {
R.id.move_to_top, R.id.move_to_bottom -> {
val items = adapter?.currentItems?.toMutableList() ?: return
val item = items[position]
items.remove(item)
if (menuItem.itemId == R.id.move_to_top)
items.add(0, item)
else
items.add(item)
adapter?.updateDataSet(items)
val downloads = items.mapNotNull { it.download }
presenter.reorder(downloads)
}
R.id.cancel_download -> {
val download = adapter?.getItem(position)?.download ?: return
presenter.cancelDownload(download)
adapter?.removeItem(position)
val adapter = adapter ?: return
val downloads =
(0 until adapter.itemCount).mapNotNull { adapter.getItem(it)?.download }
presenter.reorder(downloads)
}
}
}
}

View File

@@ -1,12 +1,16 @@
package eu.kanade.tachiyomi.ui.download
import android.view.View
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder
import kotlinx.android.synthetic.main.download_item.view.chapter_title
import kotlinx.android.synthetic.main.download_item.view.download_progress
import kotlinx.android.synthetic.main.download_item.view.download_progress_text
import kotlinx.android.synthetic.main.download_item.view.manga_title
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.view.popupMenu
import kotlinx.android.synthetic.main.download_item.chapter_title
import kotlinx.android.synthetic.main.download_item.download_progress
import kotlinx.android.synthetic.main.download_item.download_progress_text
import kotlinx.android.synthetic.main.download_item.manga_full_title
import kotlinx.android.synthetic.main.download_item.menu
import kotlinx.android.synthetic.main.download_item.reorder
/**
* Class used to hold the data of a download.
@@ -15,33 +19,37 @@ import kotlinx.android.synthetic.main.download_item.view.manga_title
* @param view the inflated view for this holder.
* @constructor creates a new download holder.
*/
class DownloadHolder(private val view: View) : BaseViewHolder(view) {
class DownloadHolder(private val view: View, val adapter: DownloadAdapter) :
BaseFlexibleViewHolder(view, adapter) {
init {
setDragHandleView(reorder)
menu.setOnClickListener { it.post { showPopupMenu(it) } }
}
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_full_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()
}
@@ -52,10 +60,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
}
/**
@@ -63,6 +71,22 @@ 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.downloadItemListener.onItemReleased(position)
}
private fun showPopupMenu(view: View) {
view.popupMenu(R.menu.download_single, {
findItem(R.id.move_to_top).isVisible = adapterPosition != 0
findItem(R.id.move_to_bottom).isVisible =
adapterPosition != adapter.itemCount - 1
}, {
adapter.downloadItemListener.onMenuItemClick(adapterPosition, this)
true
})
}
}

View File

@@ -0,0 +1,65 @@
package eu.kanade.tachiyomi.ui.download
import android.view.View
import androidx.recyclerview.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<DownloadHolder>() {
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<IFlexible<RecyclerView.ViewHolder>>
): 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<IFlexible<RecyclerView.ViewHolder>>,
holder: DownloadHolder,
position: Int,
payloads: MutableList<Any>
) {
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()
}
}

View File

@@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import java.util.ArrayList
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import timber.log.Timber
@@ -16,9 +15,6 @@ import uy.kohesive.injekt.injectLazy
*/
class DownloadPresenter : BasePresenter<DownloadController>() {
/**
* Download manager.
*/
val downloadManager: DownloadManager by injectLazy()
/**
@@ -32,7 +28,7 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
downloadQueue.getUpdatedObservable()
.observeOn(AndroidSchedulers.mainThread())
.map { ArrayList(it) }
.map { it.map(::DownloadItem) }
.subscribeLatestCache(DownloadController::onNextDownloads) { _, error ->
Timber.e(error)
}
@@ -61,4 +57,12 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
fun clearQueue() {
downloadManager.clearQueue()
}
fun reorder(downloads: List<Download>) {
downloadManager.reorderQueue(downloads)
}
fun cancelDownload(download: Download) {
downloadManager.deletePendingDownload(download)
}
}