mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-25 10:37:51 +02:00
Add download queue features from J2K fork
This commit is contained in:
@ -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
|
||||
@ -19,7 +20,7 @@ import uy.kohesive.injekt.injectLazy
|
||||
*
|
||||
* @param context the application context.
|
||||
*/
|
||||
class DownloadManager(context: Context) {
|
||||
class DownloadManager(private val context: Context) {
|
||||
|
||||
/**
|
||||
* The sources manager.
|
||||
@ -92,6 +93,29 @@ class DownloadManager(context: Context) {
|
||||
downloader.clearQueue(isNotification)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders the download queue.
|
||||
*
|
||||
* @param downloads value to set the download queue to
|
||||
*/
|
||||
fun reorderQueue(downloads: List<Download>) {
|
||||
val wasRunning = downloader.isRunning
|
||||
|
||||
if (downloads.isEmpty()) {
|
||||
DownloadService.stop(context)
|
||||
downloader.queue.clear()
|
||||
return
|
||||
}
|
||||
|
||||
downloader.pause()
|
||||
downloader.queue.clear()
|
||||
downloader.queue.addAll(downloads)
|
||||
|
||||
if (wasRunning) {
|
||||
downloader.start()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the downloader to enqueue the given list of chapters.
|
||||
*
|
||||
@ -157,6 +181,15 @@ class DownloadManager(context: Context) {
|
||||
return cache.getDownloadCount(manga)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls delete chapter, which deletes a temp download.
|
||||
*
|
||||
* @param download the download to cancel.
|
||||
*/
|
||||
fun deletePendingDownload(download: Download) {
|
||||
deleteChapters(listOf(download.chapter), download.manga, download.source)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the directories of a list of downloaded chapters.
|
||||
*
|
||||
|
@ -83,7 +83,8 @@ class Downloader(
|
||||
* Whether the downloader is running.
|
||||
*/
|
||||
@Volatile
|
||||
private var isRunning: Boolean = false
|
||||
var isRunning: Boolean = false
|
||||
private set
|
||||
|
||||
init {
|
||||
launchNow {
|
||||
|
@ -24,17 +24,30 @@ class Download(val source: HttpSource, val manga: Manga, val chapter: Chapter) {
|
||||
set(status) {
|
||||
field = status
|
||||
statusSubject?.onNext(this)
|
||||
statusCallback?.invoke(this)
|
||||
}
|
||||
|
||||
@Transient
|
||||
private var statusSubject: PublishSubject<Download>? = null
|
||||
|
||||
@Transient
|
||||
private var statusCallback: ((Download) -> Unit)? = null
|
||||
|
||||
val progress: Int
|
||||
get() {
|
||||
val pages = pages ?: return 0
|
||||
return pages.map(Page::progress).average().toInt()
|
||||
}
|
||||
|
||||
fun setStatusSubject(subject: PublishSubject<Download>?) {
|
||||
statusSubject = subject
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun setStatusCallback(f: ((Download) -> Unit)?) {
|
||||
statusCallback = f
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NOT_DOWNLOADED = 0
|
||||
const val QUEUE = 1
|
||||
const val DOWNLOADING = 2
|
||||
|
@ -12,16 +12,18 @@ import rx.subjects.PublishSubject
|
||||
class DownloadQueue(
|
||||
private val store: DownloadStore,
|
||||
private val queue: MutableList<Download> = CopyOnWriteArrayList<Download>()
|
||||
) :
|
||||
List<Download> by queue {
|
||||
) : List<Download> by queue {
|
||||
|
||||
private val statusSubject = PublishSubject.create<Download>()
|
||||
|
||||
private val updatedRelay = PublishRelay.create<Unit>()
|
||||
|
||||
private val downloadListeners = mutableListOf<DownloadListener>()
|
||||
|
||||
fun addAll(downloads: List<Download>) {
|
||||
downloads.forEach { download ->
|
||||
download.setStatusSubject(statusSubject)
|
||||
download.setStatusCallback(::setPagesFor)
|
||||
download.status = Download.QUEUE
|
||||
}
|
||||
queue.addAll(downloads)
|
||||
@ -33,6 +35,11 @@ class DownloadQueue(
|
||||
val removed = queue.remove(download)
|
||||
store.remove(download)
|
||||
download.setStatusSubject(null)
|
||||
download.setStatusCallback(null)
|
||||
if (download.status == Download.DOWNLOADING || download.status == Download.QUEUE) {
|
||||
download.status = Download.NOT_DOWNLOADED
|
||||
}
|
||||
callListeners(download)
|
||||
if (removed) {
|
||||
updatedRelay.call(Unit)
|
||||
}
|
||||
@ -55,6 +62,11 @@ class DownloadQueue(
|
||||
fun clear() {
|
||||
queue.forEach { download ->
|
||||
download.setStatusSubject(null)
|
||||
download.setStatusCallback(null)
|
||||
if (download.status == Download.DOWNLOADING || download.status == Download.QUEUE) {
|
||||
download.status = Download.NOT_DOWNLOADED
|
||||
}
|
||||
callListeners(download)
|
||||
}
|
||||
queue.clear()
|
||||
store.clear()
|
||||
@ -70,6 +82,24 @@ class DownloadQueue(
|
||||
.startWith(Unit)
|
||||
.map { this }
|
||||
|
||||
private fun setPagesFor(download: Download) {
|
||||
if (download.status == Download.DOWNLOADING) {
|
||||
download.pages?.forEach { page ->
|
||||
page.setStatusCallback {
|
||||
callListeners(download)
|
||||
}
|
||||
}
|
||||
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
|
||||
setPagesSubject(download.pages, null)
|
||||
}
|
||||
|
||||
callListeners(download)
|
||||
}
|
||||
|
||||
private fun callListeners(download: Download) {
|
||||
downloadListeners.forEach { it.updateDownload(download) }
|
||||
}
|
||||
|
||||
fun getProgressObservable(): Observable<Download> {
|
||||
return statusSubject.onBackpressureBuffer()
|
||||
.startWith(getActiveDownloads())
|
||||
@ -77,12 +107,14 @@ class DownloadQueue(
|
||||
if (download.status == Download.DOWNLOADING) {
|
||||
val pageStatusSubject = PublishSubject.create<Int>()
|
||||
setPagesSubject(download.pages, pageStatusSubject)
|
||||
callListeners(download)
|
||||
return@flatMap pageStatusSubject
|
||||
.onBackpressureBuffer()
|
||||
.filter { it == Page.READY }
|
||||
.map { download }
|
||||
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
|
||||
setPagesSubject(download.pages, null)
|
||||
callListeners(download)
|
||||
}
|
||||
Observable.just(download)
|
||||
}
|
||||
@ -96,4 +128,16 @@ class DownloadQueue(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addListener(listener: DownloadListener) {
|
||||
downloadListeners.add(listener)
|
||||
}
|
||||
|
||||
fun removeListener(listener: DownloadListener) {
|
||||
downloadListeners.remove(listener)
|
||||
}
|
||||
|
||||
interface DownloadListener {
|
||||
fun updateDownload(download: Download)
|
||||
}
|
||||
}
|
||||
|
@ -20,15 +20,23 @@ open class Page(
|
||||
set(value) {
|
||||
field = value
|
||||
statusSubject?.onNext(value)
|
||||
statusCallback?.invoke(this)
|
||||
}
|
||||
|
||||
@Transient
|
||||
@Volatile
|
||||
var progress: Int = 0
|
||||
set(value) {
|
||||
field = value
|
||||
statusCallback?.invoke(this)
|
||||
}
|
||||
|
||||
@Transient
|
||||
private var statusSubject: Subject<Int, Int>? = null
|
||||
|
||||
@Transient
|
||||
private var statusCallback: ((Page) -> Unit)? = null
|
||||
|
||||
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||
progress = if (contentLength > 0) {
|
||||
(100 * bytesRead / contentLength).toInt()
|
||||
@ -41,6 +49,10 @@ open class Page(
|
||||
this.statusSubject = subject
|
||||
}
|
||||
|
||||
fun setStatusCallback(f: ((Page) -> Unit)?) {
|
||||
statusCallback = f
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val QUEUE = 0
|
||||
const val LOAD_PAGE = 1
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,17 @@ package eu.kanade.tachiyomi.util.view
|
||||
import android.graphics.Color
|
||||
import android.graphics.Point
|
||||
import android.graphics.Typeface
|
||||
import android.view.Gravity
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.MenuRes
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import eu.kanade.tachiyomi.R
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
@ -36,6 +42,25 @@ inline fun View.snack(message: String, length: Int = Snackbar.LENGTH_LONG, f: Sn
|
||||
return snack
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a popup menu on top of this view.
|
||||
*
|
||||
* @param menuRes menu items to inflate the menu with.
|
||||
* @param initMenu function to execute when the menu after is inflated.
|
||||
* @param onMenuItemClick function to execute when a menu item is clicked.
|
||||
*/
|
||||
fun View.popupMenu(@MenuRes menuRes: Int, initMenu: (Menu.() -> Unit)? = null, onMenuItemClick: MenuItem.() -> Boolean) {
|
||||
val popup = PopupMenu(context, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
|
||||
popup.menuInflater.inflate(menuRes, popup.menu)
|
||||
|
||||
if (initMenu != null) {
|
||||
popup.menu.initMenu()
|
||||
}
|
||||
popup.setOnMenuItemClickListener { it.onMenuItemClick() }
|
||||
|
||||
popup.show()
|
||||
}
|
||||
|
||||
inline fun View.visible() {
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
|
Reference in New Issue
Block a user