Drag & Drop Sorting in Library

This commit is contained in:
Jay 2020-01-05 23:04:29 -08:00
parent 5261864aba
commit b872ab837a
19 changed files with 194 additions and 20 deletions

View File

@ -18,7 +18,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
/** /**
* Version of the database. * Version of the database.
*/ */
const val DATABASE_VERSION = 9 const val DATABASE_VERSION = 10
} }
override fun onCreate(db: SupportSQLiteDatabase) = with(db) { override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
@ -70,6 +70,9 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
if (oldVersion < 9) { if (oldVersion < 9) {
db.execSQL(MangaTable.addHideTitle) db.execSQL(MangaTable.addHideTitle)
} }
if (oldVersion < 10) {
db.execSQL(CategoryTable.addMangaOrder)
}
} }
override fun onConfigure(db: SupportSQLiteDatabase) { override fun onConfigure(db: SupportSQLiteDatabase) {

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.models.CategoryImpl
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_FLAGS 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_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_NAME
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
@ -40,6 +41,9 @@ class CategoryPutResolver : DefaultPutResolver<Category>() {
put(COL_NAME, obj.name) put(COL_NAME, obj.name)
put(COL_ORDER, obj.order) put(COL_ORDER, obj.order)
put(COL_FLAGS, obj.flags) 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)) name = cursor.getString(cursor.getColumnIndex(COL_NAME))
order = cursor.getInt(cursor.getColumnIndex(COL_ORDER)) order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))
flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS)) 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 flags: Int
var mangaOrder:List<Long>
val nameLower: String val nameLower: String
get() = name.toLowerCase() get() = name.toLowerCase()

View File

@ -10,6 +10,8 @@ class CategoryImpl : Category {
override var flags: Int = 0 override var flags: Int = 0
override var mangaOrder: List<Long> = emptyList()
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other == null || javaClass != other.javaClass) return false if (other == null || javaClass != other.javaClass) return false

View File

@ -12,12 +12,18 @@ object CategoryTable {
const val COL_FLAGS = "flags" const val COL_FLAGS = "flags"
const val COL_MANGA_ORDER = "manga_order"
val createTableQuery: String val createTableQuery: String
get() = """CREATE TABLE $TABLE( get() = """CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY, $COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_NAME TEXT NOT NULL, $COL_NAME TEXT NOT NULL,
$COL_ORDER INTEGER 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

@ -7,6 +7,7 @@ import androidx.preference.PreferenceManager
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import com.f2prateek.rx.preferences.RxSharedPreferences import com.f2prateek.rx.preferences.RxSharedPreferences
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import java.io.File import java.io.File
@ -198,6 +199,8 @@ class PreferencesHelper(val context: Context) {
fun skipPreMigration() = rxPrefs.getBoolean(Keys.skipPreMigration, false) fun skipPreMigration() = rxPrefs.getBoolean(Keys.skipPreMigration, false)
fun defaultMangaOrder() = rxPrefs.getString("default_manga_order", "")
fun upgradeFilters() { fun upgradeFilters() {
val filterDl = rxPrefs.getBoolean(Keys.filterDownloaded, false).getOrDefault() val filterDl = rxPrefs.getBoolean(Keys.filterDownloaded, false).getOrDefault()
val filterUn = rxPrefs.getBoolean(Keys.filterUnread, false).getOrDefault() val filterUn = rxPrefs.getBoolean(Keys.filterUnread, false).getOrDefault()

View File

@ -3,7 +3,9 @@ package eu.kanade.tachiyomi.ui.library
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.SelectableAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.ui.category.CategoryAdapter
/** /**
* Adapter storing a list of manga in a certain category. * Adapter storing a list of manga in a certain category.
@ -18,6 +20,8 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
*/ */
private var mangas: List<LibraryItem> = emptyList() private var mangas: List<LibraryItem> = emptyList()
val onItemReleaseListener: CategoryAdapter.OnItemReleaseListener = view
/** /**
* Sets a list of manga in the adapter. * Sets a list of manga in the adapter.
* *

View File

@ -7,15 +7,16 @@ import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
import com.google.android.material.snackbar.Snackbar
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter import eu.davidea.flexibleadapter.SelectableAdapter
import eu.kanade.tachiyomi.R 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.Category
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.category.CategoryAdapter
import eu.kanade.tachiyomi.util.* import eu.kanade.tachiyomi.util.*
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.android.synthetic.main.library_category.view.* import kotlinx.android.synthetic.main.library_category.view.*
@ -28,7 +29,9 @@ import uy.kohesive.injekt.injectLazy
class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs), FrameLayout(context, attrs),
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener { FlexibleAdapter.OnItemLongClickListener,
FlexibleAdapter.OnItemMoveListener,
CategoryAdapter.OnItemReleaseListener {
/** /**
* Preferences. * Preferences.
@ -117,6 +120,8 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
} else { } else {
SelectableAdapter.Mode.SINGLE SelectableAdapter.Mode.SINGLE
} }
val sortingMode = preferences.librarySortingMode().getOrDefault()
adapter.isLongPressDragEnabled = sortingMode == LibrarySort.DRAG_AND_DROP
subscriptions += controller.searchRelay subscriptions += controller.searchRelay
.doOnNext { adapter.setFilter(it) } .doOnNext { adapter.setFilter(it) }
@ -138,6 +143,27 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
controller.invalidateActionMode() 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() { fun onRecycle() {
@ -158,8 +184,18 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
*/ */
fun onNextLibraryManga(event: LibraryMangaEvent) { fun onNextLibraryManga(event: LibraryMangaEvent) {
// Get the manga list for this category. // 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. // Update the category with its manga.
adapter.setItems(mangaForCategory) adapter.setItems(mangaForCategory)
@ -185,6 +221,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
is LibrarySelectionEvent.Selected -> { is LibrarySelectionEvent.Selected -> {
if (adapter.mode != SelectableAdapter.Mode.MULTI) { if (adapter.mode != SelectableAdapter.Mode.MULTI) {
adapter.mode = SelectableAdapter.Mode.MULTI adapter.mode = SelectableAdapter.Mode.MULTI
adapter.isLongPressDragEnabled = false
} }
findAndToggleSelection(event.manga) findAndToggleSelection(event.manga)
} }
@ -193,12 +230,16 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
if (adapter.indexOf(event.manga) != -1) lastClickPosition = -1 if (adapter.indexOf(event.manga) != -1) lastClickPosition = -1
if (controller.selectedMangas.isEmpty()) { if (controller.selectedMangas.isEmpty()) {
adapter.mode = SelectableAdapter.Mode.SINGLE adapter.mode = SelectableAdapter.Mode.SINGLE
adapter.isLongPressDragEnabled = preferences.librarySortingMode()
.getOrDefault() == LibrarySort.DRAG_AND_DROP
} }
} }
is LibrarySelectionEvent.Cleared -> { is LibrarySelectionEvent.Cleared -> {
adapter.mode = SelectableAdapter.Mode.SINGLE adapter.mode = SelectableAdapter.Mode.SINGLE
adapter.clearSelection() adapter.clearSelection()
lastClickPosition = -1 lastClickPosition = -1
adapter.isLongPressDragEnabled = preferences.librarySortingMode()
.getOrDefault() == LibrarySort.DRAG_AND_DROP
} }
} }
} }
@ -249,6 +290,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
*/ */
override fun onItemLongClick(position: Int) { override fun onItemLongClick(position: Int) {
controller.createActionModeIfNeeded() controller.createActionModeIfNeeded()
adapter.isLongPressDragEnabled = false
when { when {
lastClickPosition == -1 -> setSelection(position) lastClickPosition == -1 -> setSelection(position)
lastClickPosition > position -> for (i in position until lastClickPosition) lastClickPosition > position -> for (i in position until lastClickPosition)
@ -260,6 +302,36 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
lastClickPosition = position 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. * Opens a manga.
* *

View File

@ -26,6 +26,7 @@ import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.jakewharton.rxbinding.support.v4.view.pageSelections import com.jakewharton.rxbinding.support.v4.view.pageSelections
import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges
import com.jakewharton.rxbinding.view.visible
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
import com.jakewharton.rxrelay.PublishRelay import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -120,6 +121,11 @@ class LibraryController(
*/ */
val selectAllRelay: PublishRelay<Int> = PublishRelay.create() val selectAllRelay: 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. * Number of manga per row in grid mode.
*/ */
@ -328,6 +334,7 @@ class LibraryController(
* Called when the sorting mode is changed. * Called when the sorting mode is changed.
*/ */
private fun onSortChanged() { private fun onSortChanged() {
activity?.invalidateOptionsMenu()
presenter.requestSortUpdate() presenter.requestSortUpdate()
} }
@ -364,6 +371,9 @@ class LibraryController(
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.library, menu) 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 searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView val searchView = searchItem.actionView as SearchView
@ -417,12 +427,22 @@ class LibraryController(
R.id.action_source_migration -> { R.id.action_source_migration -> {
router.pushController(MigrationController().withFadeTransaction()) router.pushController(MigrationController().withFadeTransaction())
} }
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)
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
} }
return true return true
} }
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. * Invalidates the action mode, forcing it to refresh its content.
*/ */

View File

@ -20,7 +20,7 @@ import eu.davidea.flexibleadapter.items.IFlexible
*/ */
class LibraryGridHolder( class LibraryGridHolder(
private val view: View, private val view: View,
private val adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>> adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
) : LibraryHolder(view, adapter) { ) : LibraryHolder(view, adapter) {

View File

@ -15,7 +15,7 @@ import eu.davidea.flexibleadapter.items.IFlexible
abstract class LibraryHolder( abstract class LibraryHolder(
view: View, view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>> val adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
) : BaseFlexibleViewHolder(view, adapter) { ) : BaseFlexibleViewHolder(view, adapter) {
/** /**
@ -26,4 +26,15 @@ abstract class LibraryHolder(
*/ */
abstract fun onSetValues(item: LibraryItem) 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

@ -51,6 +51,13 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
holder.onSetValues(this) holder.onSetValues(this)
} }
/**
* Returns true if this item is draggable.
*/
override fun isDraggable(): Boolean {
return true
}
/** /**
* Filters a manga depending on a query. * Filters a manga depending on a query.
* *

View File

@ -21,7 +21,7 @@ import eu.davidea.flexibleadapter.items.IFlexible
class LibraryListHolder( class LibraryListHolder(
private val view: View, private val view: View,
private val adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>> adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
) : LibraryHolder(view, adapter) { ) : LibraryHolder(view, adapter) {
/** /**

View File

@ -131,7 +131,10 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
private val source = Item.MultiSort(R.string.manga_info_source_label, this) private val source = Item.MultiSort(R.string.manga_info_source_label, this)
override val items = listOf(alphabetically, lastRead, lastUpdated, unread, total, source) private val dragAndDrop = Item.MultiSort(R.string.action_sort_drag_and_drop, this)
override val items = listOf(alphabetically, lastRead, lastUpdated, unread, total, source,
dragAndDrop)
override val header = Item.Header(R.string.action_sort) override val header = Item.Header(R.string.action_sort)
@ -148,6 +151,7 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
unread.state = if (sorting == LibrarySort.UNREAD) order else SORT_NONE unread.state = if (sorting == LibrarySort.UNREAD) order else SORT_NONE
total.state = if (sorting == LibrarySort.TOTAL) order else SORT_NONE total.state = if (sorting == LibrarySort.TOTAL) order else SORT_NONE
source.state = if (sorting == LibrarySort.SOURCE) order else SORT_NONE source.state = if (sorting == LibrarySort.SOURCE) order else SORT_NONE
dragAndDrop.state = if (sorting == LibrarySort.DRAG_AND_DROP) order else SORT_NONE
} }
override fun onItemClicked(item: Item) { override fun onItemClicked(item: Item) {
@ -155,12 +159,15 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
val prevState = item.state val prevState = item.state
item.group.items.forEach { (it as Item.MultiStateGroup).state = SORT_NONE } item.group.items.forEach { (it as Item.MultiStateGroup).state = SORT_NONE }
item.state = when (prevState) { if (item == dragAndDrop)
SORT_NONE -> SORT_ASC item.state = SORT_ASC
SORT_ASC -> SORT_DESC else
SORT_DESC -> SORT_ASC item.state = when (prevState) {
else -> throw Exception("Unknown state") SORT_NONE -> SORT_ASC
} SORT_ASC -> SORT_DESC
SORT_DESC -> SORT_ASC
else -> throw Exception("Unknown state")
}
preferences.librarySortingMode().set(when (item) { preferences.librarySortingMode().set(when (item) {
alphabetically -> LibrarySort.ALPHA alphabetically -> LibrarySort.ALPHA
@ -169,9 +176,10 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
unread -> LibrarySort.UNREAD unread -> LibrarySort.UNREAD
total -> LibrarySort.TOTAL total -> LibrarySort.TOTAL
source -> LibrarySort.SOURCE source -> LibrarySort.SOURCE
dragAndDrop -> LibrarySort.DRAG_AND_DROP
else -> throw Exception("Unknown sorting") else -> throw Exception("Unknown sorting")
}) })
preferences.librarySortingAscending().set(if (item.state == SORT_ASC) true else false) preferences.librarySortingAscending().set(item.state == SORT_ASC)
item.group.items.forEach { adapter.notifyItemChanged(it) } item.group.items.forEach { adapter.notifyItemChanged(it) }
} }

View File

@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.migration.MigrationFlags import eu.kanade.tachiyomi.ui.migration.MigrationFlags
import eu.kanade.tachiyomi.util.combineLatest import eu.kanade.tachiyomi.util.combineLatest
import eu.kanade.tachiyomi.util.isNullOrUnsubscribed import eu.kanade.tachiyomi.util.isNullOrUnsubscribed
import eu.kanade.tachiyomi.util.removeArticles
import eu.kanade.tachiyomi.util.syncChaptersWithSource import eu.kanade.tachiyomi.util.syncChaptersWithSource
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_EXCLUDE import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_EXCLUDE
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_IGNORE import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_IGNORE
@ -210,6 +211,9 @@ class LibraryPresenter(
val mangaCompare = source1Name.compareTo(source2Name) val mangaCompare = source1Name.compareTo(source2Name)
if (mangaCompare == 0) sortAlphabetical(i1, i2) else mangaCompare if (mangaCompare == 0) sortAlphabetical(i1, i2) else mangaCompare
} }
LibrarySort.DRAG_AND_DROP -> {
0
}
else -> throw Exception("Unknown sorting mode") else -> throw Exception("Unknown sorting mode")
} }
} }
@ -228,10 +232,6 @@ class LibraryPresenter(
else i1.manga.title.compareTo(i2.manga.title, true) else i1.manga.title.compareTo(i2.manga.title, true)
} }
private fun String.removeArticles(): String {
return this.replace(Regex("^(an|a|the) ", RegexOption.IGNORE_CASE), "")
}
/** /**
* Get the categories and all its manga from the database. * Get the categories and all its manga from the database.
* *

View File

@ -8,4 +8,5 @@ object LibrarySort {
const val UNREAD = 3 const val UNREAD = 3
const val TOTAL = 4 const val TOTAL = 4
const val SOURCE = 5 const val SOURCE = 5
const val DRAG_AND_DROP = 6
} }

View File

@ -14,6 +14,10 @@ fun String.chop(count: Int, replacement: String = "..."): String {
} }
fun String.removeArticles(): String {
return this.replace(Regex("^(an|a|the) ", RegexOption.IGNORE_CASE), "")
}
/** /**
* Replaces the given string to have at most [count] characters using [replacement] near the center. * Replaces the given string to have at most [count] characters using [replacement] near the center.
* If [replacement] is longer than [count] an exception will be thrown when `length > count`. * If [replacement] is longer than [count] an exception will be thrown when `length > count`.

View File

@ -32,4 +32,24 @@
android:title="@string/label_migration" android:title="@string/label_migration"
app:showAsAction="never"/> 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> </menu>

View File

@ -22,6 +22,8 @@
<string name="label_selected">Selected: %1$d</string> <string name="label_selected">Selected: %1$d</string>
<string name="label_backup">Backup</string> <string name="label_backup">Backup</string>
<string name="label_migration">Source migration</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_hide_title">Hide title</string>
<string name="label_show_title">Show title</string> <string name="label_show_title">Show title</string>
<string name="label_extensions">Extensions</string> <string name="label_extensions">Extensions</string>
@ -43,6 +45,8 @@
<string name="action_sort_total">Total chapters</string> <string name="action_sort_total">Total chapters</string>
<string name="action_sort_last_read">Last read</string> <string name="action_sort_last_read">Last read</string>
<string name="action_sort_last_updated">Last updated</string> <string name="action_sort_last_updated">Last updated</string>
<string name="action_sort_first_updated">First updated</string>
<string name="action_sort_drag_and_drop">Drag &amp; Drop</string>
<string name="action_search">Search</string> <string name="action_search">Search</string>
<string name="action_skip_manga">Skip manga</string> <string name="action_skip_manga">Skip manga</string>
<string name="action_global_search">Global search</string> <string name="action_global_search">Global search</string>