cherrypick drag and drop sorting
This commit is contained in:
parent
58cce53746
commit
41c99c33a6
@ -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<Category>() {
|
||||
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<Category>() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,8 @@ interface Category : Serializable {
|
||||
|
||||
var flags: Int
|
||||
|
||||
var mangaOrder:List<Long>
|
||||
|
||||
val nameLower: String
|
||||
get() = name.toLowerCase()
|
||||
|
||||
|
@ -10,6 +10,8 @@ class CategoryImpl : Category {
|
||||
|
||||
override var flags: Int = 0
|
||||
|
||||
override var mangaOrder: List<Long> = emptyList()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || javaClass != other.javaClass) return false
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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<LibraryItem> = emptyList()
|
||||
|
||||
val onItemReleaseListener: CategoryAdapter.OnItemReleaseListener = view
|
||||
|
||||
/**
|
||||
* Sets a list of manga in the adapter.
|
||||
*
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -106,6 +106,11 @@ class LibraryController(
|
||||
*/
|
||||
val selectInverseRelay: PublishRelay<Int> = PublishRelay.create()
|
||||
|
||||
/**
|
||||
* Relay to notify the library's viewpager to reotagnize all
|
||||
*/
|
||||
val reorganizeRelay: PublishRelay<Pair<Int, Int>> = 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.
|
||||
*/
|
||||
|
@ -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<IFlexible<RecyclerView.ViewHolder>>
|
||||
) : LibraryHolder(view, adapter) {
|
||||
|
||||
/**
|
||||
|
@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
|
||||
abstract class LibraryHolder(
|
||||
view: View,
|
||||
adapter: FlexibleAdapter<*>
|
||||
val adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
|
||||
) : 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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<IFlexible<RecyclerView.ViewHolder>>
|
||||
) : LibraryHolder(view, adapter) {
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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,10 +147,13 @@ class LibrarySettingsSheet(
|
||||
(it as Item.MultiStateGroup).state =
|
||||
Item.MultiSort.SORT_NONE
|
||||
}
|
||||
if (item == dragAndDrop)
|
||||
item.state = SORT_ASC
|
||||
else
|
||||
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
|
||||
SORT_NONE -> SORT_ASC
|
||||
SORT_ASC -> SORT_DESC
|
||||
SORT_DESC -> SORT_ASC
|
||||
else -> throw Exception("Unknown state")
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
)
|
||||
|
@ -11,4 +11,5 @@ object LibrarySort {
|
||||
|
||||
@Deprecated("Removed in favor of searching by source")
|
||||
const val SOURCE = 5
|
||||
const val DRAG_AND_DROP = 6
|
||||
}
|
@ -41,4 +41,24 @@
|
||||
android:title="@string/label_migration"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_reorganize"
|
||||
android:title="@string/label_reorganize_by"
|
||||
app:showAsAction="never">
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/action_alpha_asc"
|
||||
android:title="@string/action_sort_alpha"/>
|
||||
<item
|
||||
android:id="@+id/action_alpha_dsc"
|
||||
android:title="@string/label_alpha_reverse"/>
|
||||
<item
|
||||
android:id="@+id/action_update_asc"
|
||||
android:title="@string/action_sort_last_updated"/>
|
||||
<item
|
||||
android:id="@+id/action_update_dsc"
|
||||
android:title="@string/action_sort_first_updated"/>
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
</menu>
|
||||
|
@ -21,6 +21,10 @@
|
||||
<string name="label_categories">Categories</string>
|
||||
<string name="label_backup">Backup</string>
|
||||
<string name="label_migration">Source migration</string>
|
||||
<string name="label_reorganize_by">Re-order</string>
|
||||
<string name="label_alpha_reverse">Alphabetically (descending)</string>
|
||||
<string name="label_hide_title">Hide title</string>
|
||||
<string name="label_show_title">Show title</string>
|
||||
<string name="label_extensions">Extensions</string>
|
||||
<string name="label_extension_info">Extension info</string>
|
||||
<string name="label_help">Help</string>
|
||||
@ -42,6 +46,7 @@
|
||||
<string name="action_sort_last_read">Last read</string>
|
||||
<string name="action_sort_last_checked">Last checked</string>
|
||||
<string name="action_sort_latest_chapter">Latest chapter</string>
|
||||
<string name="action_sort_drag_and_drop">Drag & Drop</string>
|
||||
<string name="action_search">Search</string>
|
||||
<string name="action_global_search">Global search</string>
|
||||
<string name="action_select_all">Select all</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user