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

View File

@ -14,12 +14,12 @@ class CategoryImpl : Category {
override var mangaSort: Char? = null override var mangaSort: Char? = null
override var isFirst: Boolean? = null override var isAlone: Boolean = false
override var isLast: Boolean? = null
override var isHidden: Boolean = false override var isHidden: Boolean = false
override var isDynamic: Boolean = false
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

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

View File

@ -18,7 +18,7 @@ class LibraryMangaGetResolver : DefaultGetResolver<LibraryManga>(), BaseMangaGet
mapBaseFromCursor(manga, cursor) mapBaseFromCursor(manga, cursor)
manga.unread = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_UNREAD)) manga.unread = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_UNREAD))
manga.category = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_CATEGORY)) 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 return manga
} }

View File

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

View File

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

View File

@ -143,7 +143,7 @@ class LibraryHeaderItem(
} }
val category = item.category val category = item.category
if (category.isFirst == true && category.isLast == true) sectionText.text = "" if (category.isAlone) sectionText.text = ""
else sectionText.text = category.name else sectionText.text = category.name
sortText.text = itemView.context.getString( sortText.text = itemView.context.getString(
R.string.sort_by_, itemView.context.getString(category.sortRes()) R.string.sort_by_, itemView.context.getString(category.sortRes())
@ -165,14 +165,15 @@ class LibraryHeaderItem(
when { when {
adapter.mode == SelectableAdapter.Mode.MULTI -> { adapter.mode == SelectableAdapter.Mode.MULTI -> {
checkboxImage.visibleIf(!category.isHidden) checkboxImage.visibleIf(!category.isHidden)
expandImage.visibleIf(category.isHidden && !adapter.isSingleCategory) expandImage.visibleIf(category.isHidden && !adapter.isSingleCategory && !category.isDynamic)
updateButton.gone() updateButton.gone()
catProgress.gone() catProgress.gone()
setSelection() setSelection()
} }
category.id == -1 -> { category.isDynamic -> {
expandImage.gone() expandImage.gone()
checkboxImage.gone() checkboxImage.gone()
catProgress.gone()
updateButton.gone() updateButton.gone()
} }
LibraryUpdateService.categoryInQueue(category.id) -> { LibraryUpdateService.categoryInQueue(category.id) -> {
@ -185,7 +186,7 @@ class LibraryHeaderItem(
expandImage.visibleIf(!adapter.isSingleCategory) expandImage.visibleIf(!adapter.isSingleCategory)
catProgress.gone() catProgress.gone()
checkboxImage.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) abstract fun onSetValues(item: LibraryItem)
fun setUnreadBadge(badge: LibraryBadge, item: LibraryItem) { fun setUnreadBadge(badge: LibraryBadge, item: LibraryItem) {
val showTotal = item.header.category.sortingMode() == LibrarySort.TOTAL
badge.setUnreadDownload( badge.setUnreadDownload(
when { when {
item.chapterCount > -1 -> item.chapterCount showTotal -> item.manga.totalChapters
item.unreadType == 2 -> item.manga.unread item.unreadType == 2 -> item.manga.unread
item.unreadType == 1 -> if (item.manga.unread > 0) -1 else -2 item.unreadType == 1 -> if (item.manga.unread > 0) -1 else -2
else -> -2 else -> -2
@ -38,7 +39,7 @@ abstract class LibraryHolder(
item.manga.source == LocalSource.ID -> -2 item.manga.source == LocalSource.ID -> -2
else -> item.downloadCount else -> item.downloadCount
}, },
item.chapterCount > -1) showTotal)
} }
fun setReadingButton(item: LibraryItem) { fun setReadingButton(item: LibraryItem) {

View File

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

View File

@ -54,6 +54,10 @@ class LibraryPresenter(
var categories: List<Category> = emptyList() var categories: List<Category> = emptyList()
private set 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 */ /** All categories of the library, in case they are hidden because of hide categories is on */
var allCategories: List<Category> = emptyList() var allCategories: List<Category> = emptyList()
private set private set
@ -67,8 +71,6 @@ class LibraryPresenter(
val showAllCategories val showAllCategories
get() = preferences.showAllCategories().get() get() = preferences.showAllCategories().get()
private var totalChapters: Map<Long, Int>? = null
/** Save the current list to speed up loading later */ /** Save the current list to speed up loading later */
fun onDestroy() { fun onDestroy() {
lastLibraryItems = libraryItems lastLibraryItems = libraryItems
@ -86,7 +88,6 @@ class LibraryPresenter(
/** Get favorited manga for library and sort and filter it */ /** Get favorited manga for library and sort and filter it */
fun getLibrary() { fun getLibrary() {
scope.launch { scope.launch {
totalChapters = null
val library = withContext(Dispatchers.IO) { getLibraryFromDB() } val library = withContext(Dispatchers.IO) { getLibraryFromDB() }
library.apply { library.apply {
setDownloadCount(library) setDownloadCount(library)
@ -98,9 +99,6 @@ class LibraryPresenter(
mangaMap = applySort(mangaMap) mangaMap = applySort(mangaMap)
val freshStart = libraryItems.isEmpty() val freshStart = libraryItems.isEmpty()
sectionLibrary(mangaMap, freshStart) sectionLibrary(mangaMap, freshStart)
withContext(Dispatchers.IO) {
setTotalChapters()
}
} }
} }
@ -313,111 +311,20 @@ class LibraryPresenter(
* @param itemList the map to sort. * @param itemList the map to sort.
*/ */
private fun applySort(itemList: List<LibraryItem>): List<LibraryItem> { private fun applySort(itemList: List<LibraryItem>): List<LibraryItem> {
val sortingMode = preferences.librarySortingMode().getOrDefault()
val lastReadManga by lazy { val lastReadManga by lazy {
var counter = 0 var counter = 0
db.getLastReadManga().executeAsBlocking().associate { it.id!! to counter++ } 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 -> val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
if (!(sortingMode == LibrarySort.DRAG_AND_DROP || useDnD)) { if (i1.header.category.id == i2.header.category.id) {
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)
}
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)
}
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)
}
sortingMode == LibrarySort.DATE_ADDED -> {
i2.manga.date_added.compareTo(i1.manga.date_added)
}
else -> 0
}
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()
}
/** Gets the category by id
*
* @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
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 val category = i1.header.category
if (category.mangaOrder.isNullOrEmpty() && category.mangaSort == null) { if (category.mangaOrder.isNullOrEmpty() && category.mangaSort == null) {
category.changeSortTo(preferences.librarySortingMode().getOrDefault()) category.changeSortTo(preferences.librarySortingMode().getOrDefault())
if (category.id == 0) preferences.defaultMangaOrder() if (category.id == 0) preferences.defaultMangaOrder()
.set(category.mangaSort.toString()) .set(category.mangaSort.toString())
else if (category.id ?: 0 > 0) db.insertCategory(category).executeAsBlocking() else if (!category.isDynamic) db.insertCategory(category).executeAsBlocking()
} }
i1.chapterCount = -1
i2.chapterCount = -1
val compare = when { val compare = when {
category.mangaSort != null -> { category.mangaSort != null -> {
var sort = when (category.sortingMode()) { var sort = when (category.sortingMode()) {
@ -435,15 +342,22 @@ class LibraryPresenter(
manga1LastRead.compareTo(manga2LastRead) manga1LastRead.compareTo(manga2LastRead)
} }
LibrarySort.TOTAL -> { LibrarySort.TOTAL -> {
setTotalChapters() i1.manga.totalChapters.compareTo(i2.manga.totalChapters)
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) LibrarySort.DATE_ADDED -> i2.manga.date_added.compareTo(i1.manga.date_added)
else -> sortAlphabetical(i1, i2) 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 if (!category.isAscending()) sort *= -1
sort sort
@ -471,6 +385,19 @@ class LibraryPresenter(
} }
} }
return itemList.sortedWith(Comparator(sortFn))
}
/** Gets the category by id
*
* @param categoryId id of the categoty to get
*/
private fun getCategory(categoryId: Int): Category {
val category = hashCategories[categoryId] ?: createDefaultCategory()
category.isAlone = categories.size <= 1
return category
}
/** /**
* Sort 2 manga by the their title (and remove articles if need be) * 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 * @param i2 the second manga to compare
*/ */
private fun sortAlphabetical(i1: LibraryItem, i2: LibraryItem): Int { 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) 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. * @return an list of all the manga in a itemized form.
*/ */
private fun getLibraryFromDB(): List<LibraryItem> { private fun getLibraryFromDB(): List<LibraryItem> {
removeArticles = preferences.removeArticles().getOrDefault()
val categories = db.getCategories().executeAsBlocking().toMutableList() val categories = db.getCategories().executeAsBlocking().toMutableList()
val showCategories = !preferences.hideCategories().getOrDefault() val showCategories = !preferences.hideCategories().getOrDefault()
var libraryManga = db.getLibraryMangas().executeAsBlocking() var libraryManga = db.getLibraryMangas().executeAsBlocking()
@ -550,6 +480,10 @@ class LibraryPresenter(
this.categories = if (!showCategories) arrayListOf(categoryAll) this.categories = if (!showCategories) arrayListOf(categoryAll)
else categories else categories
hashCategories = HashMap(this.categories.mapNotNull {
it.id!! to it
}.toMap())
return items return items
} }
@ -694,7 +628,7 @@ class LibraryPresenter(
fun sortCategory(catId: Int, order: Int) { fun sortCategory(catId: Int, order: Int) {
val category = categories.find { catId == it.id } ?: return val category = categories.find { catId == it.id } ?: return
category.mangaSort = ('a' + (order - 1)) category.mangaSort = ('a' + (order - 1))
if (catId == -1) { if (catId == -1 || category.isDynamic) {
val sort = category.sortingMode() ?: LibrarySort.ALPHA val sort = category.sortingMode() ?: LibrarySort.ALPHA
preferences.librarySortingMode().set(sort) preferences.librarySortingMode().set(sort)
preferences.librarySortingAscending().set(category.isAscending()) preferences.librarySortingAscending().set(category.isAscending())
@ -709,6 +643,7 @@ class LibraryPresenter(
fun rearrangeCategory(catId: Int?, mangaIds: List<Long>) { fun rearrangeCategory(catId: Int?, mangaIds: List<Long>) {
scope.launch { scope.launch {
val category = categories.find { catId == it.id } ?: return@launch val category = categories.find { catId == it.id } ?: return@launch
if (category.isDynamic) return@launch
category.mangaSort = null category.mangaSort = null
category.mangaOrder = mangaIds category.mangaOrder = mangaIds
if (category.id == 0) preferences.defaultMangaOrder().set(mangaIds.joinToString("/")) if (category.id == 0) preferences.defaultMangaOrder().set(mangaIds.joinToString("/"))
@ -726,6 +661,7 @@ class LibraryPresenter(
scope.launch { scope.launch {
val categoryId = catId ?: return@launch val categoryId = catId ?: return@launch
val category = categories.find { catId == it.id } ?: return@launch val category = categories.find { catId == it.id } ?: return@launch
if (category.isDynamic) return@launch
val oldCatId = manga.category val oldCatId = manga.category
manga.category = categoryId manga.category = categoryId
@ -762,7 +698,7 @@ class LibraryPresenter(
} }
fun toggleCategoryVisibility(categoryId: Int) { 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 { val categoriesHidden = preferences.collapsedCategories().getOrDefault().mapNotNull {
it.toIntOrNull() it.toIntOrNull()
}.toMutableSet() }.toMutableSet()

View File

@ -14,7 +14,12 @@ fun String.chop(count: Int, replacement: String = "..."): String {
} }
fun String.removeArticles(): 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
}
} }
/** /**