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 f490956eec..d3a449b493 100644 --- 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,13 +5,11 @@ 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 import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.util.launchNow -import eu.kanade.tachiyomi.util.launchUI -import kotlinx.coroutines.delay import rx.Observable import uy.kohesive.injekt.injectLazy @@ -95,6 +93,19 @@ 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 a68d60c1f5..bdccdb9670 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -13,7 +13,12 @@ import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList -import eu.kanade.tachiyomi.util.* +import eu.kanade.tachiyomi.util.ImageUtil +import eu.kanade.tachiyomi.util.RetryWithDelay +import eu.kanade.tachiyomi.util.launchNow +import eu.kanade.tachiyomi.util.launchUI +import eu.kanade.tachiyomi.util.plusAssign +import eu.kanade.tachiyomi.util.saveTo import kotlinx.coroutines.async import okhttp3.Response import rx.Observable @@ -94,7 +99,7 @@ class Downloader( fun start(): Boolean { if (isRunning || queue.isEmpty()) return false - + notifier.paused = false if (!subscriptions.hasSubscriptions()) initializeSubscriptions() @@ -184,7 +189,6 @@ class Downloader( if (isRunning) return isRunning = true runningRelay.call(true) - subscriptions.clear() subscriptions += downloadsRelay.concatMapIterable { it } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt index 9f506fea53..7225330b23 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.notification import rx.Observable +import rx.Subscription import rx.schedulers.Schedulers import timber.log.Timber import uy.kohesive.injekt.Injekt @@ -19,47 +20,16 @@ import java.util.concurrent.TimeUnit class ExtensionUpdateJob : Job() { + var subscription:Subscription? = null + override fun onRunJob(params: Params): Result { val extensionManager: ExtensionManager = Injekt.get() extensionManager.findAvailableExtensions() - /*return extensionManager.getInstalledExtensionsObservable() - .map { list -> - val pendingUpdates = list.filter { it.hasUpdate } - if (pendingUpdates.isNotEmpty()) { - val names = pendingUpdates.map { it.name } - NotificationManagerCompat.from(context).apply { - notify(Notifications.ID_UPDATES_TO_EXTS, - context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) { - setContentTitle( - context.getString( - R.string.update_check_notification_ext_updates, names.size - ) - ) - val extNames = if (names.size > 5) { - "${names.take(4).joinToString(", ")}, " + context.getString( - R.string.notification_and_n_more, (names.size - 4) - ) - } else names.joinToString(", ") - setContentText(extNames) - setSmallIcon(R.drawable.ic_extension_update) - color = ContextCompat.getColor(context, R.color.colorAccentLight) - setContentIntent( - NotificationReceiver.openExtensionsPendingActivity( - context - ) - ) - setAutoCancel(true) - }) - } - } - Result.SUCCESS - } - .onErrorReturn { Result.FAILURE } - // Sadly, the task needs to be synchronous. - .toBlocking() - .single()*/ - Observable.defer { + subscription?.unsubscribe() + + // Update favorite manga. Destroy service when completed or in case of an error. + subscription = Observable.defer { extensionManager.getInstalledExtensionsObservable().map { list -> val pendingUpdates = list.filter { it.hasUpdate } if (pendingUpdates.isNotEmpty()) { @@ -89,8 +59,9 @@ class ExtensionUpdateJob : Job() { }) } } + subscription?.unsubscribe() Result.SUCCESS - }.onErrorReturn { Result.FAILURE } + } }.subscribeOn(Schedulers.io()) .subscribe({ }, { @@ -100,51 +71,6 @@ class ExtensionUpdateJob : Job() { return Result.SUCCESS } - /*fun runStuff(context: Context) { - val extensionManager: ExtensionManager = Injekt.get() - extensionManager.findAvailableExtensions() - Observable.defer { - extensionManager.getInstalledExtensionsObservable().map { list -> - val pendingUpdates = list.filter { it.hasUpdate } - if (pendingUpdates.isNotEmpty()) { - val names = pendingUpdates.map { it.name } - NotificationManagerCompat.from(context).apply { - notify(Notifications.ID_UPDATES_TO_EXTS, - context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) { - setContentTitle( - context.getString( - R.string.update_check_notification_ext_updates, names.size - ) - ) - val extNames = if (names.size > 5) { - "${names.take(4).joinToString(", ")}, " + context.getString( - R.string.notification_and_n_more, (names.size - 4) - ) - } else names.joinToString(", ") - setContentText(extNames) - setSmallIcon(R.drawable.ic_extension_update) - color = ContextCompat.getColor(context, R.color.colorAccentLight) - setContentIntent( - NotificationReceiver.openExtensionsPendingActivity( - context - ) - ) - setAutoCancel(true) - }) - } - } - Result.SUCCESS - }.onErrorReturn { Result.FAILURE } - }.subscribeOn(Schedulers.io()) - .subscribe({ - }, { - Timber.e(it) - }, { - }) - }*/ - - - companion object { const val TAG = "ExtensionUpdate" 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 6cf6c1bebc..76bc02a33f 100644 --- 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 androidx.recyclerview.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 : androidx.recyclerview.widget.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 2430892b7f..e3b4099bdd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt @@ -1,7 +1,12 @@ package eu.kanade.tachiyomi.ui.download +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager -import android.view.* import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download @@ -12,14 +17,15 @@ import kotlinx.android.synthetic.main.download_controller.* import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers -import java.util.* +import java.util.HashMap 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. @@ -59,11 +65,12 @@ 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 = androidx.recyclerview.widget.LinearLayoutManager(view.context) + recycler.layoutManager = LinearLayoutManager(view.context) recycler.setHasFixedSize(true) recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) @@ -170,7 +177,7 @@ class DownloadController : NucleusController() { // Avoid leaking subscriptions progressSubscriptions.remove(download)?.unsubscribe() - progressSubscriptions.put(download, subscription) + progressSubscriptions[download] = subscription } /** @@ -200,10 +207,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) } /** @@ -246,4 +253,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 fefc804a05..8428a8b30f 100644 --- 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,47 +12,50 @@ 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() } } + /** * Updates the progress bar of the download. */ 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 0000000000..5627ea127f --- /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 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() { + + /** + * 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() + } + +} \ No newline at end of file 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 0577176e95..a0b28d0807 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt @@ -9,7 +9,6 @@ import rx.Observable import rx.android.schedulers.AndroidSchedulers import timber.log.Timber import uy.kohesive.injekt.injectLazy -import java.util.* /** * Presenter of [DownloadController]. @@ -32,10 +31,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 +61,8 @@ class DownloadPresenter : BasePresenter() { downloadManager.clearQueue() } + fun reorder(downloads: List) { + downloadManager.reorderQueue(downloads) + } + } \ No newline at end of file diff --git a/app/src/main/res/layout/download_item.xml b/app/src/main/res/layout/download_item.xml index 2008002f86..ac4f28f530 100644 --- 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