From 41c99c33a6a2b89f03312604a76296d1d47b7573 Mon Sep 17 00:00:00 2001 From: Jay Date: Sun, 5 Jan 2020 23:04:29 -0800 Subject: [PATCH] cherrypick drag and drop sorting --- .../database/mappers/CategoryTypeMapping.kt | 7 ++ .../data/database/models/Category.kt | 2 + .../data/database/models/CategoryImpl.kt | 2 + .../data/database/tables/CategoryTable.kt | 9 ++- .../ui/library/LibraryCategoryAdapter.kt | 6 ++ .../ui/library/LibraryCategoryView.kt | 75 ++++++++++++++++++- .../tachiyomi/ui/library/LibraryController.kt | 19 +++++ .../tachiyomi/ui/library/LibraryGridHolder.kt | 2 +- .../tachiyomi/ui/library/LibraryHolder.kt | 14 +++- .../tachiyomi/ui/library/LibraryItem.kt | 7 ++ .../tachiyomi/ui/library/LibraryListHolder.kt | 2 +- .../tachiyomi/ui/library/LibraryPresenter.kt | 9 +++ .../ui/library/LibrarySettingsSheet.kt | 18 +++-- .../tachiyomi/ui/library/LibrarySort.kt | 3 +- app/src/main/res/menu/library.xml | 20 +++++ app/src/main/res/values/strings.xml | 5 ++ 16 files changed, 187 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt index 4cf2d1eea..11c467c9d 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt @@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.CategoryImpl import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_FLAGS import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ID +import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_MANGA_ORDER import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_NAME import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE @@ -40,6 +41,9 @@ class CategoryPutResolver : DefaultPutResolver() { put(COL_NAME, obj.name) put(COL_ORDER, obj.order) put(COL_FLAGS, obj.flags) + val orderString = obj.mangaOrder.joinToString("/") + put(COL_MANGA_ORDER, orderString) + } } @@ -50,6 +54,9 @@ class CategoryGetResolver : DefaultGetResolver() { name = cursor.getString(cursor.getColumnIndex(COL_NAME)) order = cursor.getInt(cursor.getColumnIndex(COL_ORDER)) flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS)) + + val orderString = cursor.getString(cursor.getColumnIndex(COL_MANGA_ORDER)) + mangaOrder = orderString?.split("/")?.mapNotNull { it.toLongOrNull() } ?: emptyList() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt index 3657caa72..374183a75 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt @@ -12,6 +12,8 @@ interface Category : Serializable { var flags: Int + var mangaOrder:List + val nameLower: String get() = name.toLowerCase() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt index 3e59a674e..39191799d 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt @@ -10,6 +10,8 @@ class CategoryImpl : Category { override var flags: Int = 0 + override var mangaOrder: List = emptyList() + override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || javaClass != other.javaClass) return false diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt index 76ffd7187..17a6e75c4 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt @@ -12,12 +12,19 @@ object CategoryTable { const val COL_FLAGS = "flags" + const val COL_MANGA_ORDER = "manga_order" + val createTableQuery: String get() = """CREATE TABLE $TABLE( $COL_ID INTEGER NOT NULL PRIMARY KEY, $COL_NAME TEXT NOT NULL, $COL_ORDER INTEGER NOT NULL, - $COL_FLAGS INTEGER NOT NULL + $COL_FLAGS INTEGER NOT NULL, + $COL_MANGA_ORDER TEXT NOT NULL )""" + + + val addMangaOrder: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_MANGA_ORDER TEXT" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index 353d50dc1..cb9e0ba98 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -16,6 +16,10 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.toList import timber.log.Timber import uy.kohesive.injekt.injectLazy +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.SelectableAdapter +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.ui.category.CategoryAdapter /** * Adapter storing a list of manga in a certain category. @@ -40,6 +44,8 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) : */ private var mangas: List = emptyList() + val onItemReleaseListener: CategoryAdapter.OnItemReleaseListener = view + /** * Sets a list of manga in the adapter. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt index f07e09027..4b81b29eb 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt @@ -9,6 +9,7 @@ import androidx.recyclerview.widget.RecyclerView import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.SelectableAdapter import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.library.LibraryUpdateService @@ -35,7 +36,8 @@ import uy.kohesive.injekt.injectLazy class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs), FlexibleAdapter.OnItemClickListener, - FlexibleAdapter.OnItemLongClickListener { + FlexibleAdapter.OnItemLongClickListener, + FlexibleAdapter.OnItemMoveListener, { private val scope = CoroutineScope(Job() + Dispatchers.Main) @@ -129,6 +131,8 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att } else { SelectableAdapter.Mode.SINGLE } + val sortingMode = preferences.librarySortingMode().getOrDefault() + adapter.isLongPressDragEnabled = sortingMode == LibrarySort.DRAG_AND_DROP // EXH --> scope = newScope() @@ -190,6 +194,27 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att } controller.invalidateActionMode() } + + subscriptions += controller.reorganizeRelay + .subscribe { + if (it.first == category.id) { + var items = when (it.second) { + 1, 2 -> adapter.currentItems.sortedBy { + if (preferences.removeArticles().getOrDefault()) + it.manga.title.removeArticles() + else + it.manga.title + } + 3, 4 -> adapter.currentItems.sortedBy { it.manga.last_update } + else -> adapter.currentItems.sortedBy { it.manga.title } + } + if (it.second % 2 == 0) + items = items.reversed() + adapter.setItems(items) + adapter.notifyDataSetChanged() + onItemReleased(0) + } + } } fun onRecycle() { @@ -214,8 +239,18 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att */ suspend fun onNextLibraryManga(cScope: CoroutineScope, event: LibraryMangaEvent) { // Get the manga list for this category. - val mangaForCategory = event.getMangaForCategory(category).orEmpty() + + val sortingMode = preferences.librarySortingMode().getOrDefault() + adapter.isLongPressDragEnabled = sortingMode == LibrarySort.DRAG_AND_DROP + var mangaForCategory = event.getMangaForCategory(category).orEmpty() + if (sortingMode == LibrarySort.DRAG_AND_DROP) { + if (category.name == "Default") + category.mangaOrder = preferences.defaultMangaOrder().getOrDefault().split("/") + .mapNotNull { it.toLongOrNull() } + mangaForCategory = mangaForCategory.sortedBy { category.mangaOrder.indexOf(it.manga + .id) } + } // Update the category with its manga. // EXH --> adapter.setItems(cScope, mangaForCategory) @@ -243,6 +278,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att is LibrarySelectionEvent.Selected -> { if (adapter.mode != SelectableAdapter.Mode.MULTI) { adapter.mode = SelectableAdapter.Mode.MULTI + adapter.isLongPressDragEnabled = false } findAndToggleSelection(event.manga) } @@ -251,12 +287,16 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att if (adapter.indexOf(event.manga) != -1) lastClickPosition = -1 if (controller.selectedMangas.isEmpty()) { adapter.mode = SelectableAdapter.Mode.SINGLE + adapter.isLongPressDragEnabled = preferences.librarySortingMode() + .getOrDefault() == LibrarySort.DRAG_AND_DROP } } is LibrarySelectionEvent.Cleared -> { adapter.mode = SelectableAdapter.Mode.SINGLE adapter.clearSelection() lastClickPosition = -1 + adapter.isLongPressDragEnabled = preferences.librarySortingMode() + .getOrDefault() == LibrarySort.DRAG_AND_DROP } } } @@ -300,6 +340,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att */ override fun onItemLongClick(position: Int) { controller.createActionModeIfNeeded() + adapter.isLongPressDragEnabled = false when { lastClickPosition == -1 -> setSelection(position) lastClickPosition > position -> @@ -313,6 +354,36 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att lastClickPosition = position } + override fun onItemMove(fromPosition: Int, toPosition: Int) { + + } + + override fun onItemReleased(position: Int) { + if (adapter.selectedItemCount == 0) { + val mangaIds = adapter.currentItems.mapNotNull { it.manga.id } + category.mangaOrder = mangaIds + val db: DatabaseHelper by injectLazy() + if (category.name == "Default") + preferences.defaultMangaOrder().set(mangaIds.joinToString("/")) + else + db.insertCategory(category).asRxObservable().subscribe() + } + } + + override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean { + if (adapter.selectedItemCount > 1) + return false + if (adapter.isSelected(fromPosition)) + toggleSelection(fromPosition) + return true + } + + override fun onActionStateChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + val position = viewHolder?.adapterPosition ?: return + if (actionState == 2) + onItemLongClick(position) + } + /** * Opens a manga. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index a9966aac1..af5e15dc7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -106,6 +106,11 @@ class LibraryController( */ val selectInverseRelay: PublishRelay = PublishRelay.create() + /** + * Relay to notify the library's viewpager to reotagnize all + */ + val reorganizeRelay: PublishRelay> = PublishRelay.create() + /** * Number of manga per row in grid mode. */ @@ -302,6 +307,7 @@ class LibraryController( * Called when the sorting mode is changed. */ private fun onSortChanged() { + activity?.invalidateOptionsMenu() presenter.requestSortUpdate() } @@ -343,6 +349,9 @@ class LibraryController( override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.library, menu) + val reorganizeItem = menu.findItem(R.id.action_reorganize) + reorganizeItem.isVisible = preferences.librarySortingMode().getOrDefault() == LibrarySort.DRAG_AND_DROP + val searchItem = menu.findItem(R.id.action_search) val searchView = searchItem.actionView as SearchView searchView.maxWidth = Int.MAX_VALUE @@ -409,11 +418,21 @@ class LibraryController( presenter.favoritesSync.runSync() } // <-- EXH + R.id.action_alpha_asc -> reOrder(1) + R.id.action_alpha_dsc -> reOrder(2) + R.id.action_update_asc -> reOrder(3) + R.id.action_update_dsc -> reOrder(4) } return super.onOptionsItemSelected(item) } + private fun reOrder(type: Int) { + adapter?.categories?.getOrNull(library_pager.currentItem)?.id?.let { + reorganizeRelay.call(it to type) + } + } + /** * Invalidates the action mode, forcing it to refresh its content. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt index 5c1bf711a..516d31317 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt @@ -24,7 +24,7 @@ import kotlinx.android.synthetic.main.source_grid_item.unread_text */ class LibraryGridHolder( private val view: View, - private val adapter: FlexibleAdapter<*> + adapter: FlexibleAdapter> ) : LibraryHolder(view, adapter) { /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt index b5715de23..ce30395de 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt @@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder abstract class LibraryHolder( view: View, - adapter: FlexibleAdapter<*> + val adapter: FlexibleAdapter> ) : BaseFlexibleViewHolder(view, adapter) { /** @@ -23,4 +23,16 @@ abstract class LibraryHolder( * @param item the manga item to bind. */ abstract fun onSetValues(item: LibraryItem) + + + /** + * Called when an item is released. + * + * @param position The position of the released item. + */ + override fun onItemReleased(position: Int) { + super.onItemReleased(position) + (adapter as? LibraryCategoryAdapter)?.onItemReleaseListener?.onItemReleased(position) + } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index 1d13c50a6..14c6bfc36 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -59,6 +59,13 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference holder.onSetValues(this) } + /** + * Returns true if this item is draggable. + */ + override fun isDraggable(): Boolean { + return true + } + /** * Filters a manga depending on a query. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt index 9fcb3a346..f5fc5bb20 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt @@ -25,7 +25,7 @@ import kotlinx.android.synthetic.main.source_list_item.unread_text class LibraryListHolder( private val view: View, - private val adapter: FlexibleAdapter<*> + adapter: FlexibleAdapter> ) : LibraryHolder(view, adapter) { /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 910d5683f..e0afff2a3 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -214,6 +214,9 @@ class LibraryPresenter( ?: latestChapterManga.size manga1latestChapter.compareTo(manga2latestChapter) } + LibrarySort.DRAG_AND_DROP -> { + 0 + } else -> throw Exception("Unknown sorting mode") } } @@ -227,6 +230,12 @@ class LibraryPresenter( return map.mapValues { entry -> entry.value.sortedWith(comparator) } } + private fun sortAlphabetical(i1: LibraryItem, i2: LibraryItem): Int { + return if (preferences.removeArticles().getOrDefault()) + i1.manga.title.removeArticles().compareTo(i2.manga.title.removeArticles(), true) + else i1.manga.title.compareTo(i2.manga.title, true) + } + /** * Get the categories and all its manga from the database. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt index c93f8cce8..1ee5ba083 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt @@ -110,10 +110,11 @@ class LibrarySettingsSheet( private val lastChecked = Item.MultiSort(R.string.action_sort_last_checked, this) private val unread = Item.MultiSort(R.string.action_filter_unread, this) private val latestChapter = Item.MultiSort(R.string.action_sort_latest_chapter, this) + private val dragAndDrop = Item.MultiSort(R.string.action_sort_drag_and_drop, this) override val header = null override val items = - listOf(alphabetically, lastRead, lastChecked, unread, total, latestChapter) + listOf(alphabetically, lastRead, lastChecked, unread, total, latestChapter, dragAndDrop) override val footer = null override fun initModels() { @@ -135,6 +136,7 @@ class LibrarySettingsSheet( total.state = if (sorting == LibrarySort.TOTAL) order else Item.MultiSort.SORT_NONE latestChapter.state = if (sorting == LibrarySort.LATEST_CHAPTER) order else Item.MultiSort.SORT_NONE + dragAndDrop.state = if (sorting == LibrarySort.DRAG_AND_DROP) order else SORT_NONE } override fun onItemClicked(item: Item) { @@ -145,11 +147,14 @@ class LibrarySettingsSheet( (it as Item.MultiStateGroup).state = Item.MultiSort.SORT_NONE } - item.state = when (prevState) { - Item.MultiSort.SORT_NONE -> Item.MultiSort.SORT_ASC - Item.MultiSort.SORT_ASC -> Item.MultiSort.SORT_DESC - Item.MultiSort.SORT_DESC -> Item.MultiSort.SORT_ASC - else -> throw Exception("Unknown state") + if (item == dragAndDrop) + item.state = SORT_ASC + else + item.state = when (prevState) { + SORT_NONE -> SORT_ASC + SORT_ASC -> SORT_DESC + SORT_DESC -> SORT_ASC + else -> throw Exception("Unknown state") } preferences.librarySortingMode().set( @@ -160,6 +165,7 @@ class LibrarySettingsSheet( unread -> LibrarySort.UNREAD total -> LibrarySort.TOTAL latestChapter -> LibrarySort.LATEST_CHAPTER + dragAndDrop -> LibrarySort.DRAG_AND_DROP else -> throw Exception("Unknown sorting") } ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt index 5dfdff91f..e27dd901f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt @@ -11,4 +11,5 @@ object LibrarySort { @Deprecated("Removed in favor of searching by source") const val SOURCE = 5 -} + const val DRAG_AND_DROP = 6 +} \ No newline at end of file diff --git a/app/src/main/res/menu/library.xml b/app/src/main/res/menu/library.xml index bb7241382..1b9353a4a 100755 --- a/app/src/main/res/menu/library.xml +++ b/app/src/main/res/menu/library.xml @@ -41,4 +41,24 @@ android:title="@string/label_migration" app:showAsAction="never"/> + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8efc0dbf3..5745297fd 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -21,6 +21,10 @@ Categories Backup Source migration + Re-order + Alphabetically (descending) + Hide title + Show title Extensions Extension info Help @@ -42,6 +46,7 @@ Last read Last checked Latest chapter + Drag & Drop Search Global search Select all