Add download queue features from J2K fork
This commit is contained in:
parent
3e5a48e5e4
commit
fb897e37d1
@ -5,6 +5,7 @@ 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
|
||||||
@ -19,7 +20,7 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
*
|
*
|
||||||
* @param context the application context.
|
* @param context the application context.
|
||||||
*/
|
*/
|
||||||
class DownloadManager(context: Context) {
|
class DownloadManager(private val context: Context) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The sources manager.
|
* The sources manager.
|
||||||
@ -92,6 +93,29 @@ 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>) {
|
||||||
|
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.
|
* Tells the downloader to enqueue the given list of chapters.
|
||||||
*
|
*
|
||||||
@ -157,6 +181,15 @@ class DownloadManager(context: Context) {
|
|||||||
return cache.getDownloadCount(manga)
|
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.
|
* Deletes the directories of a list of downloaded chapters.
|
||||||
*
|
*
|
||||||
|
@ -83,7 +83,8 @@ class Downloader(
|
|||||||
* Whether the downloader is running.
|
* Whether the downloader is running.
|
||||||
*/
|
*/
|
||||||
@Volatile
|
@Volatile
|
||||||
private var isRunning: Boolean = false
|
var isRunning: Boolean = false
|
||||||
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
launchNow {
|
launchNow {
|
||||||
|
@ -24,17 +24,30 @@ class Download(val source: HttpSource, val manga: Manga, val chapter: Chapter) {
|
|||||||
set(status) {
|
set(status) {
|
||||||
field = status
|
field = status
|
||||||
statusSubject?.onNext(this)
|
statusSubject?.onNext(this)
|
||||||
|
statusCallback?.invoke(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private var statusSubject: PublishSubject<Download>? = null
|
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>?) {
|
fun setStatusSubject(subject: PublishSubject<Download>?) {
|
||||||
statusSubject = subject
|
statusSubject = subject
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
fun setStatusCallback(f: ((Download) -> Unit)?) {
|
||||||
|
statusCallback = f
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
const val NOT_DOWNLOADED = 0
|
const val NOT_DOWNLOADED = 0
|
||||||
const val QUEUE = 1
|
const val QUEUE = 1
|
||||||
const val DOWNLOADING = 2
|
const val DOWNLOADING = 2
|
||||||
|
@ -12,16 +12,18 @@ import rx.subjects.PublishSubject
|
|||||||
class DownloadQueue(
|
class DownloadQueue(
|
||||||
private val store: DownloadStore,
|
private val store: DownloadStore,
|
||||||
private val queue: MutableList<Download> = CopyOnWriteArrayList<Download>()
|
private val queue: MutableList<Download> = CopyOnWriteArrayList<Download>()
|
||||||
) :
|
) : List<Download> by queue {
|
||||||
List<Download> by queue {
|
|
||||||
|
|
||||||
private val statusSubject = PublishSubject.create<Download>()
|
private val statusSubject = PublishSubject.create<Download>()
|
||||||
|
|
||||||
private val updatedRelay = PublishRelay.create<Unit>()
|
private val updatedRelay = PublishRelay.create<Unit>()
|
||||||
|
|
||||||
|
private val downloadListeners = mutableListOf<DownloadListener>()
|
||||||
|
|
||||||
fun addAll(downloads: List<Download>) {
|
fun addAll(downloads: List<Download>) {
|
||||||
downloads.forEach { download ->
|
downloads.forEach { download ->
|
||||||
download.setStatusSubject(statusSubject)
|
download.setStatusSubject(statusSubject)
|
||||||
|
download.setStatusCallback(::setPagesFor)
|
||||||
download.status = Download.QUEUE
|
download.status = Download.QUEUE
|
||||||
}
|
}
|
||||||
queue.addAll(downloads)
|
queue.addAll(downloads)
|
||||||
@ -33,6 +35,11 @@ class DownloadQueue(
|
|||||||
val removed = queue.remove(download)
|
val removed = queue.remove(download)
|
||||||
store.remove(download)
|
store.remove(download)
|
||||||
download.setStatusSubject(null)
|
download.setStatusSubject(null)
|
||||||
|
download.setStatusCallback(null)
|
||||||
|
if (download.status == Download.DOWNLOADING || download.status == Download.QUEUE) {
|
||||||
|
download.status = Download.NOT_DOWNLOADED
|
||||||
|
}
|
||||||
|
callListeners(download)
|
||||||
if (removed) {
|
if (removed) {
|
||||||
updatedRelay.call(Unit)
|
updatedRelay.call(Unit)
|
||||||
}
|
}
|
||||||
@ -55,6 +62,11 @@ class DownloadQueue(
|
|||||||
fun clear() {
|
fun clear() {
|
||||||
queue.forEach { download ->
|
queue.forEach { download ->
|
||||||
download.setStatusSubject(null)
|
download.setStatusSubject(null)
|
||||||
|
download.setStatusCallback(null)
|
||||||
|
if (download.status == Download.DOWNLOADING || download.status == Download.QUEUE) {
|
||||||
|
download.status = Download.NOT_DOWNLOADED
|
||||||
|
}
|
||||||
|
callListeners(download)
|
||||||
}
|
}
|
||||||
queue.clear()
|
queue.clear()
|
||||||
store.clear()
|
store.clear()
|
||||||
@ -70,6 +82,24 @@ class DownloadQueue(
|
|||||||
.startWith(Unit)
|
.startWith(Unit)
|
||||||
.map { this }
|
.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> {
|
fun getProgressObservable(): Observable<Download> {
|
||||||
return statusSubject.onBackpressureBuffer()
|
return statusSubject.onBackpressureBuffer()
|
||||||
.startWith(getActiveDownloads())
|
.startWith(getActiveDownloads())
|
||||||
@ -77,12 +107,14 @@ class DownloadQueue(
|
|||||||
if (download.status == Download.DOWNLOADING) {
|
if (download.status == Download.DOWNLOADING) {
|
||||||
val pageStatusSubject = PublishSubject.create<Int>()
|
val pageStatusSubject = PublishSubject.create<Int>()
|
||||||
setPagesSubject(download.pages, pageStatusSubject)
|
setPagesSubject(download.pages, pageStatusSubject)
|
||||||
|
callListeners(download)
|
||||||
return@flatMap pageStatusSubject
|
return@flatMap pageStatusSubject
|
||||||
.onBackpressureBuffer()
|
.onBackpressureBuffer()
|
||||||
.filter { it == Page.READY }
|
.filter { it == Page.READY }
|
||||||
.map { download }
|
.map { download }
|
||||||
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
|
} else if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) {
|
||||||
setPagesSubject(download.pages, null)
|
setPagesSubject(download.pages, null)
|
||||||
|
callListeners(download)
|
||||||
}
|
}
|
||||||
Observable.just(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) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
statusSubject?.onNext(value)
|
statusSubject?.onNext(value)
|
||||||
|
statusCallback?.invoke(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
@Volatile
|
@Volatile
|
||||||
var progress: Int = 0
|
var progress: Int = 0
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
statusCallback?.invoke(this)
|
||||||
|
}
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private var statusSubject: Subject<Int, Int>? = null
|
private var statusSubject: Subject<Int, Int>? = null
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private var statusCallback: ((Page) -> Unit)? = null
|
||||||
|
|
||||||
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||||
progress = if (contentLength > 0) {
|
progress = if (contentLength > 0) {
|
||||||
(100 * bytesRead / contentLength).toInt()
|
(100 * bytesRead / contentLength).toInt()
|
||||||
@ -41,6 +49,10 @@ open class Page(
|
|||||||
this.statusSubject = subject
|
this.statusSubject = subject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setStatusCallback(f: ((Page) -> Unit)?) {
|
||||||
|
statusCallback = f
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val QUEUE = 0
|
const val QUEUE = 0
|
||||||
const val LOAD_PAGE = 1
|
const val LOAD_PAGE = 1
|
||||||
|
@ -1,71 +1,26 @@
|
|||||||
package eu.kanade.tachiyomi.ui.download
|
package eu.kanade.tachiyomi.ui.download
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.MenuItem
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
|
||||||
import eu.kanade.tachiyomi.util.view.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 : RecyclerView.Adapter<DownloadHolder>() {
|
class DownloadAdapter(controller: DownloadController) : FlexibleAdapter<DownloadItem>(
|
||||||
|
null,
|
||||||
private var items = emptyList<Download>()
|
controller,
|
||||||
|
true
|
||||||
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 downloadItemListener: DownloadItemListener = controller
|
||||||
items = downloads
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
interface DownloadItemListener {
|
||||||
* Returns the number of downloads in the adapter
|
fun onItemReleased(position: Int)
|
||||||
*/
|
fun onMenuItemClick(position: Int, menuItem: MenuItem)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,8 @@ import rx.android.schedulers.AndroidSchedulers
|
|||||||
* 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.DownloadItemListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter containing the active downloads.
|
* Adapter containing the active downloads.
|
||||||
@ -64,14 +65,15 @@ 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 = LinearLayoutManager(view.context)
|
recycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
recycler.setHasFixedSize(true)
|
recycler.setHasFixedSize(true)
|
||||||
|
|
||||||
// Suscribe to changes
|
// Subscribe to changes
|
||||||
DownloadService.runningRelay
|
DownloadService.runningRelay
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribeUntilDestroy { onQueueStatusChange(it) }
|
.subscribeUntilDestroy { onQueueStatusChange(it) }
|
||||||
@ -99,14 +101,10 @@ class DownloadController : NucleusController<DownloadPresenter>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
// Set start button visibility.
|
|
||||||
menu.findItem(R.id.start_queue).isVisible = !isRunning && !presenter.downloadQueue.isEmpty()
|
menu.findItem(R.id.start_queue).isVisible = !isRunning && !presenter.downloadQueue.isEmpty()
|
||||||
|
|
||||||
// Set pause button visibility.
|
|
||||||
menu.findItem(R.id.pause_queue).isVisible = isRunning
|
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.clear_queue).isVisible = !presenter.downloadQueue.isEmpty()
|
||||||
|
menu.findItem(R.id.reorder).isVisible = !presenter.downloadQueue.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
@ -121,6 +119,16 @@ class DownloadController : NucleusController<DownloadPresenter>() {
|
|||||||
DownloadService.stop(context)
|
DownloadService.stop(context)
|
||||||
presenter.clearQueue()
|
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)
|
return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
@ -173,7 +181,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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -203,10 +211,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -214,7 +222,7 @@ class DownloadController : NucleusController<DownloadPresenter>() {
|
|||||||
*
|
*
|
||||||
* @param download the download whose progress has changed.
|
* @param download the download whose progress has changed.
|
||||||
*/
|
*/
|
||||||
fun onUpdateProgress(download: Download) {
|
private fun onUpdateProgress(download: Download) {
|
||||||
getHolder(download)?.notifyProgress()
|
getHolder(download)?.notifyProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +231,7 @@ class DownloadController : NucleusController<DownloadPresenter>() {
|
|||||||
*
|
*
|
||||||
* @param download the download whose page has been downloaded.
|
* @param download the download whose page has been downloaded.
|
||||||
*/
|
*/
|
||||||
fun onUpdateDownloadedPages(download: Download) {
|
private fun onUpdateDownloadedPages(download: Download) {
|
||||||
getHolder(download)?.notifyDownloadedPages()
|
getHolder(download)?.notifyDownloadedPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,4 +255,48 @@ class DownloadController : NucleusController<DownloadPresenter>() {
|
|||||||
empty_view?.hide()
|
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
|
package eu.kanade.tachiyomi.ui.download
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
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.chapter_title
|
import eu.kanade.tachiyomi.util.view.popupMenu
|
||||||
import kotlinx.android.synthetic.main.download_item.view.download_progress
|
import kotlinx.android.synthetic.main.download_item.chapter_title
|
||||||
import kotlinx.android.synthetic.main.download_item.view.download_progress_text
|
import kotlinx.android.synthetic.main.download_item.download_progress
|
||||||
import kotlinx.android.synthetic.main.download_item.view.manga_title
|
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.
|
* 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.
|
* @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(private val view: View, val adapter: DownloadAdapter) :
|
||||||
|
BaseFlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
setDragHandleView(reorder)
|
||||||
|
menu.setOnClickListener { it.post { showPopupMenu(it) } }
|
||||||
|
}
|
||||||
|
|
||||||
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_full_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()
|
||||||
}
|
}
|
||||||
@ -52,10 +60,10 @@ class DownloadHolder(private val view: View) : BaseViewHolder(view) {
|
|||||||
*/
|
*/
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,6 +71,22 @@ 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.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.Download
|
||||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import java.util.ArrayList
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -16,9 +15,6 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
*/
|
*/
|
||||||
class DownloadPresenter : BasePresenter<DownloadController>() {
|
class DownloadPresenter : BasePresenter<DownloadController>() {
|
||||||
|
|
||||||
/**
|
|
||||||
* Download manager.
|
|
||||||
*/
|
|
||||||
val downloadManager: DownloadManager by injectLazy()
|
val downloadManager: DownloadManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,7 +28,7 @@ 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)
|
||||||
}
|
}
|
||||||
@ -61,4 +57,12 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
|
|||||||
fun clearQueue() {
|
fun clearQueue() {
|
||||||
downloadManager.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.Color
|
||||||
import android.graphics.Point
|
import android.graphics.Point
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.MenuRes
|
||||||
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,6 +42,25 @@ inline fun View.snack(message: String, length: Int = Snackbar.LENGTH_LONG, f: Sn
|
|||||||
return snack
|
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() {
|
inline fun View.visible() {
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
9
app/src/main/res/drawable/ic_more_vert_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_more_vert_24dp.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
|
||||||
|
</vector>
|
@ -1,47 +1,89 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="@dimen/material_layout_keylines_screen_edge_margin"
|
android:paddingStart="0dp"
|
||||||
android:paddingTop="@dimen/material_component_lists_padding_above_list"
|
android:paddingTop="@dimen/material_component_lists_padding_above_list">
|
||||||
android:paddingEnd="@dimen/material_layout_keylines_screen_edge_margin">
|
|
||||||
|
|
||||||
<TextView
|
<ImageView
|
||||||
android:id="@+id/download_progress_text"
|
android:id="@+id/reorder"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="0dp"
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textAppearance="@style/TextAppearance.Regular.Caption.Hint"
|
|
||||||
tools:text="(0/10)" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/manga_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_toStartOf="@id/download_progress_text"
|
android:layout_gravity="start"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:tint="?android:attr/textColorPrimary"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_reorder_grey_24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/manga_full_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_toEndOf="@id/reorder"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textAppearance="@style/TextAppearance.Regular.Body1"
|
android:textAppearance="@style/TextAppearance.Regular.Body1"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/download_progress_text"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/reorder"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="Manga title" />
|
tools:text="Manga title" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/chapter_title"
|
android:id="@+id/chapter_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/manga_title"
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_toEndOf="@id/reorder"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textAppearance="@style/TextAppearance.Regular.Caption"
|
android:textAppearance="@style/TextAppearance.Regular.Caption"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/menu"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/manga_full_title"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/manga_full_title"
|
||||||
tools:text="Chapter Title" />
|
tools:text="Chapter Title" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/download_progress"
|
android:id="@+id/download_progress"
|
||||||
style="?android:attr/progressBarStyleHorizontal"
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/chapter_title" />
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/menu"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/reorder"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/chapter_title" />
|
||||||
|
|
||||||
</RelativeLayout>
|
<TextView
|
||||||
|
android:id="@+id/download_progress_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toEndOf="@id/manga_full_title"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAppearance="@style/TextAppearance.Regular.Caption.Hint"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/manga_full_title"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/menu"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/manga_full_title"
|
||||||
|
tools:text="(0/10)" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/menu"
|
||||||
|
android:layout_width="44dp"
|
||||||
|
android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
|
||||||
|
android:layout_toEndOf="@id/download_progress_text"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/action_menu"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingEnd="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_more_vert_24dp"
|
||||||
|
app:tint="?attr/colorOnBackground" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
android:id="@+id/start_queue"
|
android:id="@+id/start_queue"
|
||||||
android:icon="@drawable/ic_play_arrow_24dp"
|
android:icon="@drawable/ic_play_arrow_24dp"
|
||||||
android:title="@string/action_start"
|
android:title="@string/action_start"
|
||||||
android:visible="false"
|
|
||||||
app:iconTint="?attr/colorOnPrimary"
|
app:iconTint="?attr/colorOnPrimary"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
@ -14,14 +13,26 @@
|
|||||||
android:id="@+id/pause_queue"
|
android:id="@+id/pause_queue"
|
||||||
android:icon="@drawable/ic_pause_24dp"
|
android:icon="@drawable/ic_pause_24dp"
|
||||||
android:title="@string/action_pause"
|
android:title="@string/action_pause"
|
||||||
android:visible="false"
|
|
||||||
app:iconTint="?attr/colorOnPrimary"
|
app:iconTint="?attr/colorOnPrimary"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/reorder"
|
||||||
|
android:title="@string/action_reorganize_by"
|
||||||
|
app:showAsAction="never">
|
||||||
|
<menu>
|
||||||
|
<item
|
||||||
|
android:id="@+id/newest"
|
||||||
|
android:title="@string/action_newest" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/oldest"
|
||||||
|
android:title="@string/action_oldest" />
|
||||||
|
</menu>
|
||||||
|
</item>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/clear_queue"
|
android:id="@+id/clear_queue"
|
||||||
android:title="@string/action_cancel_all"
|
android:title="@string/action_cancel_all"
|
||||||
android:visible="false"
|
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
16
app/src/main/res/menu/download_single.xml
Normal file
16
app/src/main/res/menu/download_single.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/move_to_top"
|
||||||
|
android:title="@string/action_move_to_top" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/move_to_bottom"
|
||||||
|
android:title="@string/action_move_to_bottom" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/cancel_download"
|
||||||
|
android:title="@string/action_cancel" />
|
||||||
|
|
||||||
|
</menu>
|
@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<string name="action_settings">Settings</string>
|
<string name="action_settings">Settings</string>
|
||||||
|
<string name="action_menu">Menu</string>
|
||||||
<string name="action_filter">Filter</string>
|
<string name="action_filter">Filter</string>
|
||||||
<string name="action_filter_downloaded">Downloaded</string>
|
<string name="action_filter_downloaded">Downloaded</string>
|
||||||
<string name="action_filter_bookmarked">Bookmarked</string>
|
<string name="action_filter_bookmarked">Bookmarked</string>
|
||||||
@ -87,6 +88,11 @@
|
|||||||
<string name="action_cancel">Cancel</string>
|
<string name="action_cancel">Cancel</string>
|
||||||
<string name="action_cancel_all">Cancel all</string>
|
<string name="action_cancel_all">Cancel all</string>
|
||||||
<string name="action_sort">Sort</string>
|
<string name="action_sort">Sort</string>
|
||||||
|
<string name="action_reorganize_by">Reorder</string>
|
||||||
|
<string name="action_newest">Newest</string>
|
||||||
|
<string name="action_oldest">Oldest</string>
|
||||||
|
<string name="action_move_to_top">Move to top</string>
|
||||||
|
<string name="action_move_to_bottom">Move to bottom</string>
|
||||||
<string name="action_install">Install</string>
|
<string name="action_install">Install</string>
|
||||||
<string name="action_share">Share</string>
|
<string name="action_share">Share</string>
|
||||||
<string name="action_save">Save</string>
|
<string name="action_save">Save</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user