浏览代码

Grouped chapter download list by source (#5575)

Ivan Iskandar 3 年之前
父节点
当前提交
9106fc5b94

+ 7 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt

@@ -2,13 +2,14 @@ package eu.kanade.tachiyomi.ui.download
 
 import android.view.MenuItem
 import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
 
 /**
  * Adapter storing a list of downloads.
  *
  * @param context the context of the fragment containing this adapter.
  */
-class DownloadAdapter(controller: DownloadController) : FlexibleAdapter<DownloadItem>(
+class DownloadAdapter(controller: DownloadController) : FlexibleAdapter<AbstractFlexibleItem<*>>(
     null,
     controller,
     true
@@ -19,6 +20,11 @@ class DownloadAdapter(controller: DownloadController) : FlexibleAdapter<Download
      */
     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 {
         fun onItemReleased(position: Int)
         fun onMenuItemClick(position: Int, menuItem: MenuItem)

+ 46 - 39
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt

@@ -166,13 +166,17 @@ class DownloadController :
 
     private fun <R : Comparable<R>> reorderQueue(selector: (DownloadItem) -> R, reverse: Boolean = false) {
         val adapter = adapter ?: return
-        val items = adapter.currentItems.sortedBy(selector).toMutableList()
-        if (reverse) {
-            items.reverse()
+        val newDownloads = mutableListOf<Download>()
+        adapter.headerItems.forEach { headerItem ->
+            headerItem as DownloadHeaderItem
+            headerItem.subItems = headerItem.subItems.sortedBy(selector).toMutableList().apply {
+                if (reverse) {
+                    reverse()
+                }
+            }
+            newDownloads.addAll(headerItem.subItems.map { it.download })
         }
-        adapter.updateDataSet(items)
-        val downloads = items.mapNotNull { it.download }
-        presenter.reorder(downloads)
+        presenter.reorder(newDownloads)
     }
 
     /**
@@ -254,7 +258,7 @@ class DownloadController :
      *
      * @param downloads the downloads from the queue.
      */
-    fun onNextDownloads(downloads: List<DownloadItem>) {
+    fun onNextDownloads(downloads: List<DownloadHeaderItem>) {
         activity?.invalidateOptionsMenu()
         setInformationView()
         adapter?.updateDataSet(downloads)
@@ -327,7 +331,11 @@ class DownloadController :
      */
     override fun onItemReleased(position: Int) {
         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)
     }
 
@@ -338,38 +346,37 @@ class DownloadController :
      * @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 download = adapter?.getItem(position) ?: return
-                val items = adapter?.currentItems?.toMutableList() ?: return
-                items.remove(download)
-                if (menuItem.itemId == R.id.move_to_top) {
-                    items.add(0, download)
-                } else {
-                    items.add(download)
+        val item = adapter?.getItem(position) ?: return
+        if (item is DownloadItem) {
+            when (menuItem.itemId) {
+                R.id.move_to_top, R.id.move_to_bottom -> {
+                    val headerItems = adapter?.headerItems ?: return
+                    val newDownloads = mutableListOf<Download>()
+                    headerItems.forEach { headerItem ->
+                        headerItem as DownloadHeaderItem
+                        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)
                 }
-
-                val adapter = adapter ?: return
-                adapter.updateDataSet(items)
-                val downloads = adapter.currentItems.mapNotNull { it?.download }
-                presenter.reorder(downloads)
-            }
-            R.id.cancel_download -> {
-                val download = adapter?.getItem(position)?.download ?: return
-                presenter.cancelDownload(download)
-
-                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)
+                R.id.cancel_download -> {
+                    presenter.cancelDownload(item.download)
+                }
+                R.id.cancel_series -> {
+                    val allDownloadsForSeries = adapter?.currentItems
+                        ?.filterIsInstance<DownloadItem>()
+                        ?.filter { item.download.manga.id == it.download.manga.id }
+                        ?.map(DownloadItem::download)
+                    if (!allDownloadsForSeries.isNullOrEmpty()) {
+                        presenter.cancelDownloads(allDownloadsForSeries)
+                    }
                 }
             }
         }

+ 35 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHeaderHolder.kt

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

+ 52 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHeaderItem.kt

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

+ 0 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt

@@ -40,9 +40,6 @@ class DownloadHolder(private val view: View, val adapter: DownloadAdapter) :
         // Update the 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
         val pages = download.pages
         if (pages == null) {

+ 5 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadItem.kt

@@ -3,12 +3,15 @@ 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.AbstractSectionableItem
 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>() {
+class DownloadItem(
+    val download: Download,
+    header: DownloadHeaderItem
+) : AbstractSectionableItem<DownloadHolder, DownloadHeaderItem>(header) {
 
     override fun getLayoutRes(): Int {
         return R.layout.download_item

+ 9 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt

@@ -29,7 +29,15 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
 
         downloadQueue.getUpdatedObservable()
             .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 ->
                 logcat(LogPriority.ERROR, error)
             }

+ 42 - 0
app/src/main/res/layout/download_header.xml

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

+ 3 - 18
app/src/main/res/layout/download_item.xml

@@ -5,14 +5,14 @@
     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">
 
     <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content">
+        android:layout_height="wrap_content"
+        android:paddingVertical="4dp">
 
         <ImageView
             android:id="@+id/reorder"
@@ -48,13 +48,12 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginTop="2dp"
-            android:layout_marginEnd="8dp"
             android:layout_toEndOf="@id/reorder"
             android:ellipsize="end"
             android:maxLines="1"
             android:textAppearance="?attr/textAppearanceBody2"
             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_constraintTop_toBottomOf="@+id/manga_full_title"
             tools:text="Chapter Title" />
@@ -84,20 +83,6 @@
             app:layout_constraintTop_toTopOf="@+id/manga_full_title"
             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
             android:id="@+id/menu"
             android:layout_width="wrap_content"