mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-22 18:11:50 +01:00
Grouped chapter download list by source (#5575)
This commit is contained in:
parent
918502742d
commit
9106fc5b94
@ -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)
|
||||||
|
@ -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>()
|
||||||
if (reverse) {
|
adapter.headerItems.forEach { headerItem ->
|
||||||
items.reverse()
|
headerItem as DownloadHeaderItem
|
||||||
|
headerItem.subItems = headerItem.subItems.sortedBy(selector).toMutableList().apply {
|
||||||
|
if (reverse) {
|
||||||
|
reverse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newDownloads.addAll(headerItem.subItems.map { it.download })
|
||||||
}
|
}
|
||||||
adapter.updateDataSet(items)
|
presenter.reorder(newDownloads)
|
||||||
val downloads = items.mapNotNull { it.download }
|
|
||||||
presenter.reorder(downloads)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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,38 +346,37 @@ 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) {
|
||||||
when (menuItem.itemId) {
|
val item = adapter?.getItem(position) ?: return
|
||||||
R.id.move_to_top, R.id.move_to_bottom -> {
|
if (item is DownloadItem) {
|
||||||
val download = adapter?.getItem(position) ?: return
|
when (menuItem.itemId) {
|
||||||
val items = adapter?.currentItems?.toMutableList() ?: return
|
R.id.move_to_top, R.id.move_to_bottom -> {
|
||||||
items.remove(download)
|
val headerItems = adapter?.headerItems ?: return
|
||||||
if (menuItem.itemId == R.id.move_to_top) {
|
val newDownloads = mutableListOf<Download>()
|
||||||
items.add(0, download)
|
headerItems.forEach { headerItem ->
|
||||||
} else {
|
headerItem as DownloadHeaderItem
|
||||||
items.add(download)
|
if (headerItem == item.header) {
|
||||||
|
headerItem.removeSubItem(item)
|
||||||
|
if (menuItem.itemId == R.id.move_to_top) {
|
||||||
|
headerItem.addSubItem(0, item)
|
||||||
|
} else {
|
||||||
|
headerItem.addSubItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newDownloads.addAll(headerItem.subItems.map { it.download })
|
||||||
|
}
|
||||||
|
presenter.reorder(newDownloads)
|
||||||
}
|
}
|
||||||
|
R.id.cancel_download -> {
|
||||||
val adapter = adapter ?: return
|
presenter.cancelDownload(item.download)
|
||||||
adapter.updateDataSet(items)
|
}
|
||||||
val downloads = adapter.currentItems.mapNotNull { it?.download }
|
R.id.cancel_series -> {
|
||||||
presenter.reorder(downloads)
|
val allDownloadsForSeries = adapter?.currentItems
|
||||||
}
|
?.filterIsInstance<DownloadItem>()
|
||||||
R.id.cancel_download -> {
|
?.filter { item.download.manga.id == it.download.manga.id }
|
||||||
val download = adapter?.getItem(position)?.download ?: return
|
?.map(DownloadItem::download)
|
||||||
presenter.cancelDownload(download)
|
if (!allDownloadsForSeries.isNullOrEmpty()) {
|
||||||
|
presenter.cancelDownloads(allDownloadsForSeries)
|
||||||
val adapter = adapter ?: return
|
}
|
||||||
adapter.removeItem(position)
|
|
||||||
val downloads = adapter.currentItems.mapNotNull { it?.download }
|
|
||||||
presenter.reorder(downloads)
|
|
||||||
}
|
|
||||||
R.id.cancel_series -> {
|
|
||||||
val download = adapter?.getItem(position)?.download ?: return
|
|
||||||
val allDownloadsForSeries = adapter?.currentItems
|
|
||||||
?.filter { download.manga.id == it.download.manga.id }
|
|
||||||
?.map(DownloadItem::download)
|
|
||||||
if (!allDownloadsForSeries.isNullOrEmpty()) {
|
|
||||||
presenter.cancelDownloads(allDownloadsForSeries)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
42
app/src/main/res/layout/download_header.xml
Normal file
42
app/src/main/res/layout/download_header.xml
Normal 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>
|
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user