Optimizations to sorting + category refactoring

Category:
• new bool called isDynamic used for all category
• new bool called isAlone, for when using no categories or when hiding all categories
Sort Optimizing:
•Using SQLite query for library mangas to also set the total chapters, instead of  checking the total count later
 •No longer using regex for removing articles from text, because it's pretty heavy
•All category now uses the same catergory sorting logic as regular categories
This commit is contained in:
Jay 2020-05-17 02:15:24 -04:00
parent 7083d41ffe
commit d9ad689506
11 changed files with 122 additions and 171 deletions

View File

@ -19,14 +19,15 @@ interface Category : Serializable {
var mangaSort: Char?
var isFirst: Boolean?
var isLast: Boolean?
var isAlone: Boolean
val nameLower: String
get() = name.toLowerCase()
var isHidden: Boolean
var isDynamic: Boolean
fun isAscending(): Boolean {
return ((mangaSort?.minus('a') ?: 0) % 2) != 1
}
@ -49,7 +50,7 @@ interface Category : Serializable {
LAST_READ_ASC, LAST_READ_DSC -> R.string.last_read
TOTAL_ASC, TOTAL_DSC -> R.string.total_chapters
DATE_ADDED_ASC, DATE_ADDED_DSC -> R.string.date_added
else -> if (id == -1) R.string.category else R.string.drag_and_drop
else -> if (isDynamic) R.string.category else R.string.drag_and_drop
}
fun catSortingMode(): Int? = when (mangaSort) {
@ -96,12 +97,10 @@ interface Category : Serializable {
fun createDefault(context: Context): Category =
create(context.getString(R.string.default_value)).apply {
id = 0
isFirst = true
}
fun createAll(context: Context, libSort: Int, ascending: Boolean): Category =
create(context.getString(R.string.all)).apply {
id = -1
fun createCustom(name: String, libSort: Int, ascending: Boolean): Category =
create(name).apply {
mangaSort = when (libSort) {
LibrarySort.ALPHA -> ALPHA_ASC
LibrarySort.LATEST_CHAPTER -> UPDATED_ASC
@ -113,11 +112,15 @@ interface Category : Serializable {
else -> DRAG_AND_DROP
}
if (mangaSort != DRAG_AND_DROP && !ascending) {
mangaSort?.plus(1)
mangaSort = mangaSort?.plus(1)
}
isDynamic = true
}
fun createAll(context: Context, libSort: Int, ascending: Boolean): Category =
createCustom(context.getString(R.string.all), libSort, ascending).apply {
id = -1
order = -1
isFirst = true
isLast = true
}
}
}

View File

@ -14,12 +14,12 @@ class CategoryImpl : Category {
override var mangaSort: Char? = null
override var isFirst: Boolean? = null
override var isLast: Boolean? = null
override var isAlone: Boolean = false
override var isHidden: Boolean = false
override var isDynamic: Boolean = false
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || javaClass != other.javaClass) return false

View File

@ -3,10 +3,15 @@ package eu.kanade.tachiyomi.data.database.models
class LibraryManga : MangaImpl() {
var unread: Int = 0
var read: Int = 0
var category: Int = 0
var hasRead: Boolean = false
val totalChapters
get() = read + unread
val hasRead
get() = read > 0
companion object {
fun createBlank(categoryId: Int): LibraryManga = LibraryManga().apply {

View File

@ -18,7 +18,7 @@ class LibraryMangaGetResolver : DefaultGetResolver<LibraryManga>(), BaseMangaGet
mapBaseFromCursor(manga, cursor)
manga.unread = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_UNREAD))
manga.category = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_CATEGORY))
manga.hasRead = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_HAS_READ)) > 0
manga.read = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_HAS_READ))
return manga
}

View File

@ -155,7 +155,7 @@ class LibraryCategoryAdapter(val controller: LibraryController) :
else recyclerView.context.getString(R.string.read)
}
LibrarySort.TOTAL -> {
val total = item.chapterCount
val total = item.manga.totalChapters
if (total > 0) recyclerView.resources.getQuantityString(
R.plurals.chapters, total, total
)

View File

@ -1109,8 +1109,9 @@ class LibraryController(
override fun manageCategory(position: Int) {
val category = (adapter.getItem(position) as? LibraryHeaderItem)?.category ?: return
if (category.id ?: 0 > -1)
if (!category.isDynamic) {
ManageCategoryDialog(this, category).showDialog(router)
}
}
override fun sortCategory(catId: Int, sortBy: Int) {

View File

@ -143,7 +143,7 @@ class LibraryHeaderItem(
}
val category = item.category
if (category.isFirst == true && category.isLast == true) sectionText.text = ""
if (category.isAlone) sectionText.text = ""
else sectionText.text = category.name
sortText.text = itemView.context.getString(
R.string.sort_by_, itemView.context.getString(category.sortRes())
@ -165,14 +165,15 @@ class LibraryHeaderItem(
when {
adapter.mode == SelectableAdapter.Mode.MULTI -> {
checkboxImage.visibleIf(!category.isHidden)
expandImage.visibleIf(category.isHidden && !adapter.isSingleCategory)
expandImage.visibleIf(category.isHidden && !adapter.isSingleCategory && !category.isDynamic)
updateButton.gone()
catProgress.gone()
setSelection()
}
category.id == -1 -> {
category.isDynamic -> {
expandImage.gone()
checkboxImage.gone()
catProgress.gone()
updateButton.gone()
}
LibraryUpdateService.categoryInQueue(category.id) -> {
@ -185,7 +186,7 @@ class LibraryHeaderItem(
expandImage.visibleIf(!adapter.isSingleCategory)
catProgress.gone()
checkboxImage.gone()
updateButton.visibleIf(category.id ?: 0 > -1 && !adapter.isSingleCategory)
updateButton.visibleIf(!adapter.isSingleCategory)
}
}
}

View File

@ -26,9 +26,10 @@ abstract class LibraryHolder(
abstract fun onSetValues(item: LibraryItem)
fun setUnreadBadge(badge: LibraryBadge, item: LibraryItem) {
val showTotal = item.header.category.sortingMode() == LibrarySort.TOTAL
badge.setUnreadDownload(
when {
item.chapterCount > -1 -> item.chapterCount
showTotal -> item.manga.totalChapters
item.unreadType == 2 -> item.manga.unread
item.unreadType == 1 -> if (item.manga.unread > 0) -1 else -2
else -> -2
@ -38,7 +39,7 @@ abstract class LibraryHolder(
item.manga.source == LocalSource.ID -> -2
else -> item.downloadCount
},
item.chapterCount > -1)
showTotal)
}
fun setReadingButton(item: LibraryItem) {

View File

@ -35,7 +35,6 @@ class LibraryItem(
var downloadCount = -1
var unreadType = 2
var chapterCount = -1
private val uniformSize: Boolean
get() = preferences.uniformGrid().getOrDefault()

View File

@ -54,6 +54,10 @@ class LibraryPresenter(
var categories: List<Category> = emptyList()
private set
var hashCategories: HashMap<Int, Category> = hashMapOf()
var removeArticles: Boolean = preferences.removeArticles().getOrDefault()
/** All categories of the library, in case they are hidden because of hide categories is on */
var allCategories: List<Category> = emptyList()
private set
@ -67,8 +71,6 @@ class LibraryPresenter(
val showAllCategories
get() = preferences.showAllCategories().get()
private var totalChapters: Map<Long, Int>? = null
/** Save the current list to speed up loading later */
fun onDestroy() {
lastLibraryItems = libraryItems
@ -86,7 +88,6 @@ class LibraryPresenter(
/** Get favorited manga for library and sort and filter it */
fun getLibrary() {
scope.launch {
totalChapters = null
val library = withContext(Dispatchers.IO) { getLibraryFromDB() }
library.apply {
setDownloadCount(library)
@ -98,9 +99,6 @@ class LibraryPresenter(
mangaMap = applySort(mangaMap)
val freshStart = libraryItems.isEmpty()
sectionLibrary(mangaMap, freshStart)
withContext(Dispatchers.IO) {
setTotalChapters()
}
}
}
@ -313,74 +311,81 @@ class LibraryPresenter(
* @param itemList the map to sort.
*/
private fun applySort(itemList: List<LibraryItem>): List<LibraryItem> {
val sortingMode = preferences.librarySortingMode().getOrDefault()
val lastReadManga by lazy {
var counter = 0
db.getLastReadManga().executeAsBlocking().associate { it.id!! to counter++ }
}
val ascending = preferences.librarySortingAscending().getOrDefault()
val useDnD = !preferences.hideCategories().getOrDefault()
val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
if (!(sortingMode == LibrarySort.DRAG_AND_DROP || useDnD)) {
i1.chapterCount = -1
i2.chapterCount = -1
}
val compare = when {
sortingMode == LibrarySort.DRAG_AND_DROP || useDnD ->
sortCategory(i1, i2, lastReadManga)
sortingMode == LibrarySort.ALPHA -> sortAlphabetical(i1, i2)
sortingMode == LibrarySort.LAST_READ -> {
// Get index of manga, set equal to list if size unknown.
val manga1LastRead = lastReadManga[i1.manga.id!!] ?: lastReadManga.size
val manga2LastRead = lastReadManga[i2.manga.id!!] ?: lastReadManga.size
manga1LastRead.compareTo(manga2LastRead)
if (i1.header.category.id == i2.header.category.id) {
val category = i1.header.category
if (category.mangaOrder.isNullOrEmpty() && category.mangaSort == null) {
category.changeSortTo(preferences.librarySortingMode().getOrDefault())
if (category.id == 0) preferences.defaultMangaOrder()
.set(category.mangaSort.toString())
else if (!category.isDynamic) db.insertCategory(category).executeAsBlocking()
}
sortingMode == LibrarySort.LATEST_CHAPTER -> i2.manga.last_update.compareTo(i1
.manga.last_update)
sortingMode == LibrarySort.UNREAD ->
when {
i1.manga.unread == i2.manga.unread -> 0
i1.manga.unread == 0 -> if (ascending) 1 else -1
i2.manga.unread == 0 -> if (ascending) -1 else 1
else -> i1.manga.unread.compareTo(i2.manga.unread)
val compare = when {
category.mangaSort != null -> {
var sort = when (category.sortingMode()) {
LibrarySort.ALPHA -> sortAlphabetical(i1, i2)
LibrarySort.LATEST_CHAPTER -> i2.manga.last_update.compareTo(i1.manga.last_update)
LibrarySort.UNREAD -> when {
i1.manga.unread == i2.manga.unread -> 0
i1.manga.unread == 0 -> if (category.isAscending()) 1 else -1
i2.manga.unread == 0 -> if (category.isAscending()) -1 else 1
else -> i1.manga.unread.compareTo(i2.manga.unread)
}
LibrarySort.LAST_READ -> {
val manga1LastRead = lastReadManga[i1.manga.id!!] ?: lastReadManga.size
val manga2LastRead = lastReadManga[i2.manga.id!!] ?: lastReadManga.size
manga1LastRead.compareTo(manga2LastRead)
}
LibrarySort.TOTAL -> {
i1.manga.totalChapters.compareTo(i2.manga.totalChapters)
}
LibrarySort.DATE_ADDED -> i2.manga.date_added.compareTo(i1.manga.date_added)
else -> {
if (LibrarySort.DRAG_AND_DROP == category.sortingMode() && category.isDynamic) {
val category1 =
allCategories.find { i1.manga.category == it.id }?.order
?: 0
val category2 =
allCategories.find { i2.manga.category == it.id }?.order
?: 0
category1.compareTo(category2)
} else {
sortAlphabetical(i1, i2)
}
}
}
if (!category.isAscending()) sort *= -1
sort
}
sortingMode == LibrarySort.TOTAL -> {
setTotalChapters()
val manga1TotalChapter = totalChapters!![i1.manga.id!!] ?: 0
val mange2TotalChapter = totalChapters!![i2.manga.id!!] ?: 0
i1.chapterCount = totalChapters!![i1.manga.id!!] ?: 0
i2.chapterCount = totalChapters!![i2.manga.id!!] ?: 0
manga1TotalChapter.compareTo(mange2TotalChapter)
category.mangaOrder.isNotEmpty() -> {
val order = category.mangaOrder
val index1 = order.indexOf(i1.manga.id!!)
val index2 = order.indexOf(i2.manga.id!!)
when {
index1 == index2 -> 0
index1 == -1 -> -1
index2 == -1 -> 1
else -> index1.compareTo(index2)
}
}
else -> 0
}
sortingMode == LibrarySort.DATE_ADDED -> {
i2.manga.date_added.compareTo(i1.manga.date_added)
}
else -> 0
if (compare == 0) {
sortAlphabetical(i1, i2)
} else compare
} else {
val category = i1.header.category.order
val category2 = i2.header.category.order
category.compareTo(category2)
}
if (!(sortingMode == LibrarySort.DRAG_AND_DROP || useDnD) && compare == 0) {
if (ascending) sortAlphabetical(i1, i2)
else sortAlphabetical(i2, i1)
} else compare
}
val comparator = if (ascending || useDnD)
Comparator(sortFn)
else
Collections.reverseOrder(sortFn)
return itemList.sortedWith(comparator)
}
/** Set the total chapters for the manga in the library */
private fun setTotalChapters() {
if (totalChapters != null) return
val mangaMap = allLibraryItems
totalChapters = mangaMap.map {
it.manga.id!! to db.getChapters(it.manga).executeAsBlocking().size
}.toMap()
return itemList.sortedWith(Comparator(sortFn))
}
/** Gets the category by id
@ -388,89 +393,11 @@ class LibraryPresenter(
* @param categoryId id of the categoty to get
*/
private fun getCategory(categoryId: Int): Category {
val category = categories.find { it.id == categoryId } ?: createDefaultCategory()
if (category.isFirst == null) {
category.isFirst = categories.minBy { it.order }?.id == category.id
}
if (category.isLast == null) category.isLast = categories.lastOrNull()?.id == category.id
val category = hashCategories[categoryId] ?: createDefaultCategory()
category.isAlone = categories.size <= 1
return category
}
/**
* Sort 2 manga by the category's sorting
*
* @param i1 the first manga
* @param i2 the second manga to compare
* @param lastReadManga map of the last read of the library
*/
private fun sortCategory(
i1: LibraryItem,
i2: LibraryItem,
lastReadManga: Map<Long, Int>
): Int {
return if (i1.header.category.id == i2.header.category.id) {
val category = i1.header.category
if (category.mangaOrder.isNullOrEmpty() && category.mangaSort == null) {
category.changeSortTo(preferences.librarySortingMode().getOrDefault())
if (category.id == 0) preferences.defaultMangaOrder()
.set(category.mangaSort.toString())
else if (category.id ?: 0 > 0) db.insertCategory(category).executeAsBlocking()
}
i1.chapterCount = -1
i2.chapterCount = -1
val compare = when {
category.mangaSort != null -> {
var sort = when (category.sortingMode()) {
LibrarySort.ALPHA -> sortAlphabetical(i1, i2)
LibrarySort.LATEST_CHAPTER -> i2.manga.last_update.compareTo(i1.manga.last_update)
LibrarySort.UNREAD -> when {
i1.manga.unread == i2.manga.unread -> 0
i1.manga.unread == 0 -> if (category.isAscending()) 1 else -1
i2.manga.unread == 0 -> if (category.isAscending()) -1 else 1
else -> i1.manga.unread.compareTo(i2.manga.unread)
}
LibrarySort.LAST_READ -> {
val manga1LastRead = lastReadManga[i1.manga.id!!] ?: lastReadManga.size
val manga2LastRead = lastReadManga[i2.manga.id!!] ?: lastReadManga.size
manga1LastRead.compareTo(manga2LastRead)
}
LibrarySort.TOTAL -> {
setTotalChapters()
val manga1TotalChapter = totalChapters!![i1.manga.id!!] ?: 0
val mange2TotalChapter = totalChapters!![i2.manga.id!!] ?: 0
i1.chapterCount = totalChapters!![i1.manga.id!!] ?: 0
i2.chapterCount = totalChapters!![i2.manga.id!!] ?: 0
manga1TotalChapter.compareTo(mange2TotalChapter)
}
LibrarySort.DATE_ADDED -> i2.manga.date_added.compareTo(i1.manga.date_added)
else -> sortAlphabetical(i1, i2)
}
if (!category.isAscending()) sort *= -1
sort
}
category.mangaOrder.isNotEmpty() -> {
val order = category.mangaOrder
val index1 = order.indexOf(i1.manga.id!!)
val index2 = order.indexOf(i2.manga.id!!)
when {
index1 == index2 -> 0
index1 == -1 -> -1
index2 == -1 -> 1
else -> index1.compareTo(index2)
}
}
else -> 0
}
if (compare == 0) {
sortAlphabetical(i1, i2)
} else compare
} else {
val category = i1.header.category.order
val category2 = i2.header.category.order
category.compareTo(category2)
}
}
/**
* Sort 2 manga by the their title (and remove articles if need be)
*
@ -478,9 +405,11 @@ class LibraryPresenter(
* @param i2 the second manga to compare
*/
private fun sortAlphabetical(i1: LibraryItem, i2: LibraryItem): Int {
return if (preferences.removeArticles().getOrDefault())
return if (removeArticles) {
i1.manga.title.removeArticles().compareTo(i2.manga.title.removeArticles(), true)
else i1.manga.title.compareTo(i2.manga.title, true)
} else {
i1.manga.title.compareTo(i2.manga.title, true)
}
}
/**
@ -489,6 +418,7 @@ class LibraryPresenter(
* @return an list of all the manga in a itemized form.
*/
private fun getLibraryFromDB(): List<LibraryItem> {
removeArticles = preferences.removeArticles().getOrDefault()
val categories = db.getCategories().executeAsBlocking().toMutableList()
val showCategories = !preferences.hideCategories().getOrDefault()
var libraryManga = db.getLibraryMangas().executeAsBlocking()
@ -550,6 +480,10 @@ class LibraryPresenter(
this.categories = if (!showCategories) arrayListOf(categoryAll)
else categories
hashCategories = HashMap(this.categories.mapNotNull {
it.id!! to it
}.toMap())
return items
}
@ -694,7 +628,7 @@ class LibraryPresenter(
fun sortCategory(catId: Int, order: Int) {
val category = categories.find { catId == it.id } ?: return
category.mangaSort = ('a' + (order - 1))
if (catId == -1) {
if (catId == -1 || category.isDynamic) {
val sort = category.sortingMode() ?: LibrarySort.ALPHA
preferences.librarySortingMode().set(sort)
preferences.librarySortingAscending().set(category.isAscending())
@ -709,6 +643,7 @@ class LibraryPresenter(
fun rearrangeCategory(catId: Int?, mangaIds: List<Long>) {
scope.launch {
val category = categories.find { catId == it.id } ?: return@launch
if (category.isDynamic) return@launch
category.mangaSort = null
category.mangaOrder = mangaIds
if (category.id == 0) preferences.defaultMangaOrder().set(mangaIds.joinToString("/"))
@ -726,6 +661,7 @@ class LibraryPresenter(
scope.launch {
val categoryId = catId ?: return@launch
val category = categories.find { catId == it.id } ?: return@launch
if (category.isDynamic) return@launch
val oldCatId = manga.category
manga.category = categoryId
@ -762,7 +698,7 @@ class LibraryPresenter(
}
fun toggleCategoryVisibility(categoryId: Int) {
if (categoryId <= -1) return
if (categoryId <= -1 || categories.find { it.id == categoryId }?.isDynamic == true) return
val categoriesHidden = preferences.collapsedCategories().getOrDefault().mapNotNull {
it.toIntOrNull()
}.toMutableSet()

View File

@ -14,7 +14,12 @@ fun String.chop(count: Int, replacement: String = "..."): String {
}
fun String.removeArticles(): String {
return this.replace(Regex("^(an|a|the) ", RegexOption.IGNORE_CASE), "")
return when {
startsWith("a ", true) -> substring(2)
startsWith("an ", true) -> substring(3)
startsWith("the ", true) -> substring(4)
else -> this
}
}
/**