mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-09-10 21:13:24 +02:00
Drag & Drop Sorting in Library
This commit is contained in:
parent
5261864aba
commit
b872ab837a
@ -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) {
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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) {
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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
|
||||||
}
|
}
|
@ -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`.
|
||||||
|
@ -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>
|
||||||
|
@ -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 & 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>
|
||||||
|
Loading…
Reference in New Issue
Block a user