mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01: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