Reorder downloads + Fixes to extensions autochecker

This commit is contained in:
Jay 2019-12-22 12:13:22 -08:00
parent 65ca7ab476
commit 53f7b3a762
9 changed files with 180 additions and 178 deletions

View File

@ -5,13 +5,11 @@ import com.hippo.unifile.UniFile
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga 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.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Page 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 rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -95,6 +93,19 @@ class DownloadManager(context: Context) {
downloader.clearQueue(isNotification) downloader.clearQueue(isNotification)
} }
/**
* Reorders the download queue.
*
* @param downloads value to set the download queue to
*/
fun reorderQueue(downloads: List<Download>) {
downloader.pause()
downloader.queue.clear()
downloader.queue.addAll(downloads)
downloader.start()
}
/** /**
* Tells the downloader to enqueue the given list of chapters. * Tells the downloader to enqueue the given list of chapters.
* *

View File

@ -13,7 +13,12 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList 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 kotlinx.coroutines.async
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
@ -94,7 +99,7 @@ class Downloader(
fun start(): Boolean { fun start(): Boolean {
if (isRunning || queue.isEmpty()) if (isRunning || queue.isEmpty())
return false return false
notifier.paused = false
if (!subscriptions.hasSubscriptions()) if (!subscriptions.hasSubscriptions())
initializeSubscriptions() initializeSubscriptions()
@ -184,7 +189,6 @@ class Downloader(
if (isRunning) return if (isRunning) return
isRunning = true isRunning = true
runningRelay.call(true) runningRelay.call(true)
subscriptions.clear() subscriptions.clear()
subscriptions += downloadsRelay.concatMapIterable { it } subscriptions += downloadsRelay.concatMapIterable { it }

View File

@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.notification import eu.kanade.tachiyomi.util.notification
import rx.Observable import rx.Observable
import rx.Subscription
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -19,47 +20,16 @@ import java.util.concurrent.TimeUnit
class ExtensionUpdateJob : Job() { class ExtensionUpdateJob : Job() {
var subscription:Subscription? = null
override fun onRunJob(params: Params): Result { override fun onRunJob(params: Params): Result {
val extensionManager: ExtensionManager = Injekt.get() val extensionManager: ExtensionManager = Injekt.get()
extensionManager.findAvailableExtensions() 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 { subscription?.unsubscribe()
notify(Notifications.ID_UPDATES_TO_EXTS,
context.notification(Notifications.CHANNEL_UPDATES_TO_EXTS) { // Update favorite manga. Destroy service when completed or in case of an error.
setContentTitle( subscription = Observable.defer {
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 {
extensionManager.getInstalledExtensionsObservable().map { list -> extensionManager.getInstalledExtensionsObservable().map { list ->
val pendingUpdates = list.filter { it.hasUpdate } val pendingUpdates = list.filter { it.hasUpdate }
if (pendingUpdates.isNotEmpty()) { if (pendingUpdates.isNotEmpty()) {
@ -89,8 +59,9 @@ class ExtensionUpdateJob : Job() {
}) })
} }
} }
subscription?.unsubscribe()
Result.SUCCESS Result.SUCCESS
}.onErrorReturn { Result.FAILURE } }
}.subscribeOn(Schedulers.io()) }.subscribeOn(Schedulers.io())
.subscribe({ .subscribe({
}, { }, {
@ -100,51 +71,6 @@ class ExtensionUpdateJob : Job() {
return Result.SUCCESS 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 { companion object {
const val TAG = "ExtensionUpdate" const val TAG = "ExtensionUpdate"

View File

@ -1,72 +1,24 @@
package eu.kanade.tachiyomi.ui.download package eu.kanade.tachiyomi.ui.download
import androidx.recyclerview.widget.RecyclerView import eu.davidea.flexibleadapter.FlexibleAdapter
import android.view.ViewGroup
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.util.inflate
/** /**
* Adapter storing a list of downloads. * Adapter storing a list of downloads.
* *
* @param context the context of the fragment containing this adapter. * @param context the context of the fragment containing this adapter.
*/ */
class DownloadAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<DownloadHolder>() { class DownloadAdapter(controller: DownloadController) : FlexibleAdapter<DownloadItem>(null, controller,
true) {
private var items = emptyList<Download>()
init {
setHasStableIds(true)
}
/** /**
* Sets a list of downloads in the adapter. * Listener called when an item of the list is released.
*
* @param downloads the list to set.
*/ */
fun setItems(downloads: List<Download>) { val onItemReleaseListener: OnItemReleaseListener = controller
items = downloads
notifyDataSetChanged()
}
interface OnItemReleaseListener {
/** /**
* Returns the number of downloads in the adapter * Called when an item of the list is released.
*/ */
override fun getItemCount(): Int { fun onItemReleased(position: 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)
}
} }

View File

@ -1,7 +1,12 @@
package eu.kanade.tachiyomi.ui.download 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 androidx.recyclerview.widget.LinearLayoutManager
import android.view.*
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
@ -12,14 +17,15 @@ import kotlinx.android.synthetic.main.download_controller.*
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import java.util.* import java.util.HashMap
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/** /**
* Controller that shows the currently active downloads. * Controller that shows the currently active downloads.
* Uses R.layout.fragment_download_queue. * Uses R.layout.fragment_download_queue.
*/ */
class DownloadController : NucleusController<DownloadPresenter>() { class DownloadController : NucleusController<DownloadPresenter>(),
DownloadAdapter.OnItemReleaseListener {
/** /**
* Adapter containing the active downloads. * Adapter containing the active downloads.
@ -59,11 +65,12 @@ class DownloadController : NucleusController<DownloadPresenter>() {
setInformationView() setInformationView()
// Initialize adapter. // Initialize adapter.
adapter = DownloadAdapter() adapter = DownloadAdapter(this@DownloadController)
recycler.adapter = adapter recycler.adapter = adapter
adapter?.isHandleDragEnabled = true
// Set the layout manager for the recycler and fixed size. // 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.setHasFixedSize(true)
recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
@ -170,7 +177,7 @@ class DownloadController : NucleusController<DownloadPresenter>() {
// Avoid leaking subscriptions // Avoid leaking subscriptions
progressSubscriptions.remove(download)?.unsubscribe() progressSubscriptions.remove(download)?.unsubscribe()
progressSubscriptions.put(download, subscription) progressSubscriptions[download] = subscription
} }
/** /**
@ -200,10 +207,10 @@ class DownloadController : NucleusController<DownloadPresenter>() {
* *
* @param downloads the downloads from the queue. * @param downloads the downloads from the queue.
*/ */
fun onNextDownloads(downloads: List<Download>) { fun onNextDownloads(downloads: List<DownloadItem>) {
activity?.invalidateOptionsMenu() activity?.invalidateOptionsMenu()
setInformationView() setInformationView()
adapter?.setItems(downloads) adapter?.updateDataSet(downloads)
} }
/** /**
@ -246,4 +253,15 @@ class DownloadController : NucleusController<DownloadPresenter>() {
} }
} }
/**
* 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)
}
} }

View File

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.download
import android.view.View import android.view.View
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import kotlinx.android.synthetic.main.download_item.view.* import kotlinx.android.synthetic.main.download_item.*
/** /**
* Class used to hold the data of a download. * 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. * @param view the inflated view for this holder.
* @constructor creates a new download 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 private lateinit var download: Download
/** /**
* Method called from [DownloadAdapter.onBindViewHolder]. It updates the data for this * Binds this holder with the given category.
* holder with the given download.
* *
* @param download the download to bind. * @param category The category to bind.
*/ */
fun onSetValues(download: Download) { fun bind(download: Download) {
this.download = download this.download = download
// Update the chapter name. // Update the chapter name.
view.chapter_title.text = download.chapter.name chapter_title.text = download.chapter.name
// Update the manga title // 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 // Update the progress bar and the number of downloaded pages
val pages = download.pages val pages = download.pages
if (pages == null) { if (pages == null) {
view.download_progress.progress = 0 download_progress.progress = 0
view.download_progress.max = 1 download_progress.max = 1
view.download_progress_text.text = "" download_progress_text.text = ""
} else { } else {
view.download_progress.max = pages.size * 100 download_progress.max = pages.size * 100
notifyProgress() notifyProgress()
notifyDownloadedPages() notifyDownloadedPages()
} }
} }
/** /**
* Updates the progress bar of the download. * Updates the progress bar of the download.
*/ */
fun notifyProgress() { fun notifyProgress() {
val pages = download.pages ?: return val pages = download.pages ?: return
if (view.download_progress.max == 1) { if (download_progress.max == 1) {
view.download_progress.max = pages.size * 100 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() { fun notifyDownloadedPages() {
val pages = download.pages ?: return 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)
} }
} }

View File

@ -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<DownloadHolder>() {
/**
* 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<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

@ -9,7 +9,6 @@ import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.*
/** /**
* Presenter of [DownloadController]. * Presenter of [DownloadController].
@ -32,10 +31,10 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
downloadQueue.getUpdatedObservable() downloadQueue.getUpdatedObservable()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.map { ArrayList(it) } .map { it.map(::DownloadItem) }
.subscribeLatestCache(DownloadController::onNextDownloads, { _, error -> .subscribeLatestCache(DownloadController::onNextDownloads) { _, error ->
Timber.e(error) Timber.e(error)
}) }
} }
fun getDownloadStatusObservable(): Observable<Download> { fun getDownloadStatusObservable(): Observable<Download> {
@ -62,4 +61,8 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
downloadManager.clearQueue() downloadManager.clearQueue()
} }
fun reorder(downloads: List<Download>) {
downloadManager.reorderQueue(downloads)
}
} }

View File

@ -2,9 +2,9 @@
<RelativeLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="@dimen/material_layout_keylines_screen_edge_margin"
android:paddingRight="@dimen/material_layout_keylines_screen_edge_margin" android:paddingRight="@dimen/material_layout_keylines_screen_edge_margin"
android:paddingTop="@dimen/material_component_lists_padding_above_list"> android:paddingTop="@dimen/material_component_lists_padding_above_list">
@ -25,6 +25,7 @@
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
android:layout_marginStart="@dimen/material_component_lists_single_line_with_avatar_height"
android:textAppearance="@style/TextAppearance.Regular.Body1" android:textAppearance="@style/TextAppearance.Regular.Body1"
tools:text="Manga title"/> tools:text="Manga title"/>
@ -36,6 +37,7 @@
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
tools:text="Chapter Title" tools:text="Chapter Title"
android:layout_marginStart="@dimen/material_component_lists_single_line_with_avatar_height"
android:textAppearance="@style/TextAppearance.Regular.Caption"/> android:textAppearance="@style/TextAppearance.Regular.Caption"/>
<ProgressBar <ProgressBar
@ -43,6 +45,16 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/chapter_title" android:layout_below="@id/chapter_title"
android:layout_marginStart="@dimen/material_component_lists_single_line_with_avatar_height"
style="?android:attr/progressBarStyleHorizontal"/> style="?android:attr/progressBarStyleHorizontal"/>
<ImageView
android:id="@+id/reorder"
android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height"
android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
android:layout_gravity="start"
android:scaleType="center"
android:tint="?android:attr/textColorPrimary"
app:srcCompat="@drawable/ic_reorder_grey_24dp" />
</RelativeLayout> </RelativeLayout>