mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 06:17:57 +01:00 
			
		
		
		
	Grouped chapter download list by source (#5575)
This commit is contained in:
		| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -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 | ||||
|         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) { | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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
									
								
								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: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" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user