cherrypick drag and drop sorting

This commit is contained in:
Jay 2020-01-05 23:04:29 -08:00 committed by Jobobby04
parent 58cce53746
commit 41c99c33a6
16 changed files with 187 additions and 13 deletions

View File

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

View File

@ -12,6 +12,8 @@ interface Category : Serializable {
var flags: Int
var mangaOrder:List<Long>
val nameLower: String
get() = name.toLowerCase()

View File

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

View File

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

View File

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

View File

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

View File

@ -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.
*/

View File

@ -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) {
/**

View File

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

View File

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

View File

@ -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) {
/**

View File

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

View File

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

View File

@ -11,4 +11,5 @@ object LibrarySort {
@Deprecated("Removed in favor of searching by source")
const val SOURCE = 5
}
const val DRAG_AND_DROP = 6
}

View File

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

View File

@ -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 &amp; Drop</string>
<string name="action_search">Search</string>
<string name="action_global_search">Global search</string>
<string name="action_select_all">Select all</string>