Albirew/tachiyomi
Archivé
1
0
Bifurcation 0

Add download queue features from J2K fork

Cette révision appartient à :
arkon 2020-02-23 12:42:10 -05:00
Parent 3e5a48e5e4
révision fb897e37d1
16 fichiers modifiés avec 439 ajouts et 127 suppressions

Voir le fichier

@ -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.
*

Voir le fichier

@ -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 {

Voir le fichier

@ -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

Voir le fichier

@ -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)
}
}

Voir le fichier

@ -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

Voir le fichier

@ -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)
}
}

Voir le fichier

@ -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)
}
}
}
}

Voir le fichier

@ -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
})
}
}

Voir le fichier

@ -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()
}
}

Voir le fichier

@ -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)
}
}

Voir le fichier

@ -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
}

Voir le fichier

@ -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>

Voir le fichier

@ -1,47 +1,89 @@
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/material_layout_keylines_screen_edge_margin"
android:paddingTop="@dimen/material_component_lists_padding_above_list"
android:paddingEnd="@dimen/material_layout_keylines_screen_edge_margin">
android:paddingStart="0dp"
android:paddingTop="@dimen/material_component_lists_padding_above_list">
<TextView
android:id="@+id/download_progress_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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"
<ImageView
android:id="@+id/reorder"
android:layout_width="@dimen/material_component_lists_single_line_with_avatar_height"
android:layout_height="0dp"
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:maxLines="1"
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" />
<TextView
android:id="@+id/chapter_title"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/manga_title"
android:layout_marginTop="4dp"
android:layout_toEndOf="@id/reorder"
android:ellipsize="end"
android:maxLines="1"
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" />
<ProgressBar
android:id="@+id/download_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_width="0dp"
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>

Voir le fichier

@ -6,7 +6,6 @@
android:id="@+id/start_queue"
android:icon="@drawable/ic_play_arrow_24dp"
android:title="@string/action_start"
android:visible="false"
app:iconTint="?attr/colorOnPrimary"
app:showAsAction="ifRoom" />
@ -14,14 +13,26 @@
android:id="@+id/pause_queue"
android:icon="@drawable/ic_pause_24dp"
android:title="@string/action_pause"
android:visible="false"
app:iconTint="?attr/colorOnPrimary"
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
android:id="@+id/clear_queue"
android:title="@string/action_cancel_all"
android:visible="false"
app:showAsAction="never" />
</menu>

Voir le fichier

@ -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>

Voir le fichier

@ -29,6 +29,7 @@
<!-- Actions -->
<string name="action_settings">Settings</string>
<string name="action_menu">Menu</string>
<string name="action_filter">Filter</string>
<string name="action_filter_downloaded">Downloaded</string>
<string name="action_filter_bookmarked">Bookmarked</string>
@ -87,6 +88,11 @@
<string name="action_cancel">Cancel</string>
<string name="action_cancel_all">Cancel all</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_share">Share</string>
<string name="action_save">Save</string>