Grouped chapter download list by source (#5575)

This commit is contained in:
Ivan Iskandar 2021-10-09 22:41:45 +07:00 committed by GitHub
parent 918502742d
commit 9106fc5b94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 199 additions and 64 deletions

View File

@ -2,13 +2,14 @@ package eu.kanade.tachiyomi.ui.download
import android.view.MenuItem import android.view.MenuItem
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
/** /**
* 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(controller: DownloadController) : FlexibleAdapter<DownloadItem>( class DownloadAdapter(controller: DownloadController) : FlexibleAdapter<AbstractFlexibleItem<*>>(
null, null,
controller, controller,
true true
@ -19,6 +20,11 @@ class DownloadAdapter(controller: DownloadController) : FlexibleAdapter<Download
*/ */
val downloadItemListener: DownloadItemListener = controller val downloadItemListener: DownloadItemListener = controller
override fun shouldMove(fromPosition: Int, toPosition: Int): Boolean {
// Don't let sub-items changing group
return getHeaderOf(getItem(fromPosition)) == getHeaderOf(getItem(toPosition))
}
interface DownloadItemListener { interface DownloadItemListener {
fun onItemReleased(position: Int) fun onItemReleased(position: Int)
fun onMenuItemClick(position: Int, menuItem: MenuItem) fun onMenuItemClick(position: Int, menuItem: MenuItem)

View File

@ -166,13 +166,17 @@ class DownloadController :
private fun <R : Comparable<R>> reorderQueue(selector: (DownloadItem) -> R, reverse: Boolean = false) { private fun <R : Comparable<R>> reorderQueue(selector: (DownloadItem) -> R, reverse: Boolean = false) {
val adapter = adapter ?: return val adapter = adapter ?: return
val items = adapter.currentItems.sortedBy(selector).toMutableList() val newDownloads = mutableListOf<Download>()
adapter.headerItems.forEach { headerItem ->
headerItem as DownloadHeaderItem
headerItem.subItems = headerItem.subItems.sortedBy(selector).toMutableList().apply {
if (reverse) { if (reverse) {
items.reverse() reverse()
} }
adapter.updateDataSet(items) }
val downloads = items.mapNotNull { it.download } newDownloads.addAll(headerItem.subItems.map { it.download })
presenter.reorder(downloads) }
presenter.reorder(newDownloads)
} }
/** /**
@ -254,7 +258,7 @@ class DownloadController :
* *
* @param downloads the downloads from the queue. * @param downloads the downloads from the queue.
*/ */
fun onNextDownloads(downloads: List<DownloadItem>) { fun onNextDownloads(downloads: List<DownloadHeaderItem>) {
activity?.invalidateOptionsMenu() activity?.invalidateOptionsMenu()
setInformationView() setInformationView()
adapter?.updateDataSet(downloads) adapter?.updateDataSet(downloads)
@ -327,7 +331,11 @@ class DownloadController :
*/ */
override fun onItemReleased(position: Int) { override fun onItemReleased(position: Int) {
val adapter = adapter ?: return val adapter = adapter ?: return
val downloads = (0 until adapter.itemCount).mapNotNull { adapter.getItem(it)?.download } val downloads = adapter.headerItems.flatMap { header ->
adapter.getSectionItems(header).map { item ->
(item as DownloadItem).download
}
}
presenter.reorder(downloads) presenter.reorder(downloads)
} }
@ -338,35 +346,33 @@ class DownloadController :
* @param menuItem The menu Item pressed * @param menuItem The menu Item pressed
*/ */
override fun onMenuItemClick(position: Int, menuItem: MenuItem) { override fun onMenuItemClick(position: Int, menuItem: MenuItem) {
val item = adapter?.getItem(position) ?: return
if (item is DownloadItem) {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.move_to_top, R.id.move_to_bottom -> { R.id.move_to_top, R.id.move_to_bottom -> {
val download = adapter?.getItem(position) ?: return val headerItems = adapter?.headerItems ?: return
val items = adapter?.currentItems?.toMutableList() ?: return val newDownloads = mutableListOf<Download>()
items.remove(download) headerItems.forEach { headerItem ->
headerItem as DownloadHeaderItem
if (headerItem == item.header) {
headerItem.removeSubItem(item)
if (menuItem.itemId == R.id.move_to_top) { if (menuItem.itemId == R.id.move_to_top) {
items.add(0, download) headerItem.addSubItem(0, item)
} else { } else {
items.add(download) headerItem.addSubItem(item)
} }
}
val adapter = adapter ?: return newDownloads.addAll(headerItem.subItems.map { it.download })
adapter.updateDataSet(items) }
val downloads = adapter.currentItems.mapNotNull { it?.download } presenter.reorder(newDownloads)
presenter.reorder(downloads)
} }
R.id.cancel_download -> { R.id.cancel_download -> {
val download = adapter?.getItem(position)?.download ?: return presenter.cancelDownload(item.download)
presenter.cancelDownload(download)
val adapter = adapter ?: return
adapter.removeItem(position)
val downloads = adapter.currentItems.mapNotNull { it?.download }
presenter.reorder(downloads)
} }
R.id.cancel_series -> { R.id.cancel_series -> {
val download = adapter?.getItem(position)?.download ?: return
val allDownloadsForSeries = adapter?.currentItems val allDownloadsForSeries = adapter?.currentItems
?.filter { download.manga.id == it.download.manga.id } ?.filterIsInstance<DownloadItem>()
?.filter { item.download.manga.id == it.download.manga.id }
?.map(DownloadItem::download) ?.map(DownloadItem::download)
if (!allDownloadsForSeries.isNullOrEmpty()) { if (!allDownloadsForSeries.isNullOrEmpty()) {
presenter.cancelDownloads(allDownloadsForSeries) presenter.cancelDownloads(allDownloadsForSeries)
@ -374,6 +380,7 @@ class DownloadController :
} }
} }
} }
}
private fun updateTitle(queueSize: Int = 0) { private fun updateTitle(queueSize: Int = 0) {
val defaultTitle = getTitle() val defaultTitle = getTitle()

View File

@ -0,0 +1,35 @@
package eu.kanade.tachiyomi.ui.download
import android.annotation.SuppressLint
import android.view.View
import androidx.recyclerview.widget.ItemTouchHelper
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.viewholders.ExpandableViewHolder
import eu.kanade.tachiyomi.databinding.DownloadHeaderBinding
class DownloadHeaderHolder(view: View, adapter: FlexibleAdapter<*>) : ExpandableViewHolder(view, adapter) {
private val binding = DownloadHeaderBinding.bind(view)
@SuppressLint("SetTextI18n")
fun bind(item: DownloadHeaderItem) {
setDragHandleView(binding.reorder)
binding.title.text = "${item.name} (${item.size})"
}
override fun onActionStateChanged(position: Int, actionState: Int) {
super.onActionStateChanged(position, actionState)
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
binding.container.isDragged = true
mAdapter.collapseAll()
}
}
override fun onItemReleased(position: Int) {
super.onItemReleased(position)
binding.container.isDragged = false
mAdapter as DownloadAdapter
mAdapter.expandAll()
mAdapter.downloadItemListener.onItemReleased(position)
}
}

View File

@ -0,0 +1,52 @@
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.AbstractExpandableHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
data class DownloadHeaderItem(
val name: String,
val size: Int
) : AbstractExpandableHeaderItem<DownloadHeaderHolder, DownloadItem>() {
override fun getLayoutRes(): Int {
return R.layout.download_header
}
override fun createViewHolder(
view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
): DownloadHeaderHolder {
return DownloadHeaderHolder(view, adapter)
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: DownloadHeaderHolder,
position: Int,
payloads: List<Any?>?
) {
holder.bind(this)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is DownloadHeaderItem) {
return name == other.name
}
return false
}
override fun hashCode(): Int {
return name.hashCode()
}
init {
isHidden = false
isExpanded = true
isSelectable = false
}
}

View File

@ -40,9 +40,6 @@ class DownloadHolder(private val view: View, val adapter: DownloadAdapter) :
// Update the manga title // Update the manga title
binding.mangaFullTitle.text = download.manga.title binding.mangaFullTitle.text = download.manga.title
// Update the manga source
binding.mangaSource.text = download.source.name
// 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 File

@ -3,12 +3,15 @@ package eu.kanade.tachiyomi.ui.download
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
class DownloadItem(val download: Download) : AbstractFlexibleItem<DownloadHolder>() { class DownloadItem(
val download: Download,
header: DownloadHeaderItem
) : AbstractSectionableItem<DownloadHolder, DownloadHeaderItem>(header) {
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return R.layout.download_item return R.layout.download_item

View File

@ -29,7 +29,15 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
downloadQueue.getUpdatedObservable() downloadQueue.getUpdatedObservable()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.map { it.map(::DownloadItem) } .map { downloads ->
downloads
.groupBy { it.source }
.map { entry ->
DownloadHeaderItem(entry.key.name, entry.value.size).apply {
addSubItems(0, entry.value.map { DownloadItem(it, this) })
}
}
}
.subscribeLatestCache(DownloadController::onNextDownloads) { _, error -> .subscribeLatestCache(DownloadController::onNextDownloads) { _, error ->
logcat(LogPriority.ERROR, error) logcat(LogPriority.ERROR, error)
} }

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:cardBackgroundColor="?android:attr/colorBackground"
app:cardElevation="0dp"
app:cardForegroundColor="@color/draggable_card_foreground">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
style="@style/TextAppearance.Tachiyomi.SectionHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:layout_weight="1"
android:layout_gravity="center_vertical"
tools:text="Title" />
<ImageView
android:id="@+id/reorder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:paddingHorizontal="10dp"
android:paddingVertical="8dp"
android:scaleType="center"
app:srcCompat="@drawable/ic_drag_handle_24dp"
app:tint="?android:attr/textColorHint"
tools:ignore="ContentDescription" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -5,14 +5,14 @@
android:id="@+id/container" android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:cardBackgroundColor="?android:attr/colorBackground" app:cardBackgroundColor="?android:attr/colorBackground"
app:cardElevation="0dp" app:cardElevation="0dp"
app:cardForegroundColor="@color/draggable_card_foreground"> app:cardForegroundColor="@color/draggable_card_foreground">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:paddingVertical="4dp">
<ImageView <ImageView
android:id="@+id/reorder" android:id="@+id/reorder"
@ -48,13 +48,12 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:layout_marginEnd="8dp"
android:layout_toEndOf="@id/reorder" android:layout_toEndOf="@id/reorder"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textAppearance="?attr/textAppearanceBody2" android:textAppearance="?attr/textAppearanceBody2"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/manga_source" app:layout_constraintEnd_toStartOf="@+id/menu"
app:layout_constraintStart_toStartOf="@+id/manga_full_title" app:layout_constraintStart_toStartOf="@+id/manga_full_title"
app:layout_constraintTop_toBottomOf="@+id/manga_full_title" app:layout_constraintTop_toBottomOf="@+id/manga_full_title"
tools:text="Chapter Title" /> tools:text="Chapter Title" />
@ -84,20 +83,6 @@
app:layout_constraintTop_toTopOf="@+id/manga_full_title" app:layout_constraintTop_toTopOf="@+id/manga_full_title"
tools:text="0/10" /> tools:text="0/10" />
<TextView
android:id="@+id/manga_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/chapter_title"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:textColorSecondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/chapter_title"
app:layout_constraintEnd_toStartOf="@+id/menu"
app:layout_constraintTop_toTopOf="@+id/chapter_title"
tools:text="Manga Source" />
<ImageButton <ImageButton
android:id="@+id/menu" android:id="@+id/menu"
android:layout_width="wrap_content" android:layout_width="wrap_content"