Clean up of library presenter

Using progress bar again for when a category is updating and ripple for sort button
Fixed #165
This commit is contained in:
Jay 2020-04-09 23:45:44 -04:00
parent 55b7b8ca2a
commit 2dd8c8810a
6 changed files with 157 additions and 387 deletions

View File

@ -177,17 +177,17 @@ class LibraryController(
} }
RecyclerView.SCROLL_STATE_IDLE -> { RecyclerView.SCROLL_STATE_IDLE -> {
scrollAnim = fast_scroller.animate().setStartDelay(1000).setDuration(250) scrollAnim = fast_scroller.animate().setStartDelay(1000).setDuration(250)
.translationX(22f.dpToPx) .translationX(25f.dpToPx)
scrollAnim?.start() scrollAnim?.start()
} }
} }
} }
} }
private fun hideScroller() { private fun hideScroller(duration: Long = 1000) {
if (alwaysShowScroller) return if (alwaysShowScroller) return
scrollAnim = scrollAnim =
fast_scroller.animate().setStartDelay(1000).setDuration(250).translationX(22f.dpToPx) fast_scroller.animate().setStartDelay(duration).setDuration(250).translationX(25f.dpToPx)
scrollAnim?.start() scrollAnim?.start()
} }
@ -207,10 +207,12 @@ class LibraryController(
super.onViewCreated(view) super.onViewCreated(view)
view.applyWindowInsetsForRootController(activity!!.bottom_nav) view.applyWindowInsetsForRootController(activity!!.bottom_nav)
if (!::presenter.isInitialized) presenter = LibraryPresenter(this) if (!::presenter.isInitialized) presenter = LibraryPresenter(this)
fast_scroller.translationX = 22f.dpToPx fast_scroller.translationX = 25f.dpToPx
setFastScrollBackground() setFastScrollBackground()
adapter = LibraryCategoryAdapter(this) adapter = LibraryCategoryAdapter(this)
adapter.expandItemsAtStartUp()
adapter.isRecursiveCollapse = true
setRecyclerLayout() setRecyclerLayout()
recycler.manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() { recycler.manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int { override fun getSpanSize(position: Int): Int {
@ -238,13 +240,11 @@ class LibraryController(
itemPosition: Int itemPosition: Int
) { ) {
fast_scroller.translationX = 0f fast_scroller.translationX = 0f
hideScroller() hideScroller(2000)
textAnim?.cancel() textAnim?.cancel()
textAnim = text_view_m.animate().alpha(0f).setDuration(250L).setStartDelay(1000) textAnim = text_view_m.animate().alpha(0f).setDuration(250L).setStartDelay(2000)
this@LibraryController.view?.post { textAnim?.start()
textAnim?.start()
}
text_view_m.translationY = indicatorCenterY.toFloat() - text_view_m.height / 2 text_view_m.translationY = indicatorCenterY.toFloat() - text_view_m.height / 2
text_view_m.alpha = 1f text_view_m.alpha = 1f
@ -324,9 +324,9 @@ class LibraryController(
recycler.updatePaddingRelative(bottom = height) recycler.updatePaddingRelative(bottom = height)
presenter.onRestore() presenter.onRestore()
val library = presenter.getAllManga() if (presenter.libraryItems.isNotEmpty())
if (library != null) presenter.updateViewBlocking() onNextLibraryUpdate(presenter.libraryItems, true)
else { else {
recycler_layout.alpha = 0f recycler_layout.alpha = 0f
presenter.getLibraryBlocking() presenter.getLibraryBlocking()
} }
@ -429,7 +429,7 @@ class LibraryController(
super.onDestroyView(view) super.onDestroyView(view)
} }
fun onNextLibraryUpdate(mangaMap: List<LibraryItem>, freshStart: Boolean) { fun onNextLibraryUpdate(mangaMap: List<LibraryItem>, freshStart: Boolean = false) {
if (view == null) return if (view == null) return
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
if (mangaMap.isNotEmpty()) { if (mangaMap.isNotEmpty()) {
@ -442,6 +442,7 @@ class LibraryController(
) )
} }
adapter.setItems(mangaMap) adapter.setItems(mangaMap)
adapter.collapse(0)
singleCategory = presenter.categories.size <= 1 singleCategory = presenter.categories.size <= 1
setTitle() setTitle()
@ -452,7 +453,9 @@ class LibraryController(
} else if (justStarted && freshStart) { } else if (justStarted && freshStart) {
scrollToHeader(activeCategory) scrollToHeader(activeCategory)
fast_scroller.translationX = 0f fast_scroller.translationX = 0f
hideScroller() view?.post {
hideScroller(2000)
}
} }
adapter.isLongPressDragEnabled = canDrag() adapter.isLongPressDragEnabled = canDrag()
} }
@ -914,7 +917,7 @@ class LibraryController(
anchorView = bottom_sheet anchorView = bottom_sheet
var undoing = false var undoing = false
setAction(R.string.undo) { setAction(R.string.undo) {
presenter.addMangas(mangas) presenter.reAddMangas(mangas)
undoing = true undoing = true
} }
addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() { addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {

View File

@ -4,6 +4,7 @@ import android.graphics.drawable.Drawable
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
@ -23,6 +24,7 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.invisible
import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import kotlinx.android.synthetic.main.library_category_header_item.view.* import kotlinx.android.synthetic.main.library_category_header_item.view.*
@ -84,6 +86,7 @@ class LibraryHeaderItem(
private val sortText: TextView = view.findViewById(R.id.category_sort) private val sortText: TextView = view.findViewById(R.id.category_sort)
private val updateButton: ImageView = view.findViewById(R.id.update_button) private val updateButton: ImageView = view.findViewById(R.id.update_button)
private val checkboxImage: ImageView = view.findViewById(R.id.checkbox) private val checkboxImage: ImageView = view.findViewById(R.id.checkbox)
private val catProgress: ProgressBar = view.findViewById(R.id.cat_progress)
init { init {
sortText.updateLayoutParams<ViewGroup.MarginLayoutParams> { sortText.updateLayoutParams<ViewGroup.MarginLayoutParams> {
@ -122,6 +125,7 @@ class LibraryHeaderItem(
adapter.mode == SelectableAdapter.Mode.MULTI -> { adapter.mode == SelectableAdapter.Mode.MULTI -> {
checkboxImage.visible() checkboxImage.visible()
updateButton.gone() updateButton.gone()
catProgress.gone()
setSelection() setSelection()
} }
category.id == -1 -> { category.id == -1 -> {
@ -130,14 +134,12 @@ class LibraryHeaderItem(
} }
LibraryUpdateService.categoryInQueue(category.id) -> { LibraryUpdateService.categoryInQueue(category.id) -> {
checkboxImage.gone() checkboxImage.gone()
updateButton.drawable.setTint(ContextCompat.getColor(itemView.context, catProgress.visible()
R.color.material_on_surface_disabled)) updateButton.invisible()
updateButton.visible()
} }
else -> { else -> {
catProgress.gone()
checkboxImage.gone() checkboxImage.gone()
updateButton.drawable.setTint(itemView.context.getResourceColor(
R.attr.colorAccent))
updateButton.visible() updateButton.visible()
} }
} }
@ -145,8 +147,8 @@ class LibraryHeaderItem(
private fun addCategoryToUpdate() { private fun addCategoryToUpdate() {
if (adapter.libraryListener.updateCategory(adapterPosition)) { if (adapter.libraryListener.updateCategory(adapterPosition)) {
updateButton.drawable.setTint(ContextCompat.getColor(itemView.context, catProgress.visible()
R.color.material_on_surface_disabled)) updateButton.invisible()
} }
} }
private fun showCatSortOptions() { private fun showCatSortOptions() {

View File

@ -13,14 +13,10 @@ 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.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet
import eu.kanade.tachiyomi.ui.migration.MigrationFlags
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.removeArticles import eu.kanade.tachiyomi.util.lang.removeArticles
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_EXCLUDE import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_EXCLUDE
@ -32,25 +28,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.ArrayList import java.util.ArrayList
import java.util.Collections import java.util.Collections
import java.util.Comparator import java.util.Comparator
/**
* Class containing library information.
*/
private data class Library(val categories: List<Category>, val mangaMap: LibraryMap)
/**
* Typealias for the library manga, using the category as keys, and list of manga as values.
*/
private typealias LibraryMap = Map<Int, List<LibraryItem>>
/** /**
* Presenter of [LibraryController]. * Presenter of [LibraryController].
*/ */
@ -66,6 +49,7 @@ class LibraryPresenter(
private val context = preferences.context private val context = preferences.context
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } } private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
/** /**
* Categories of the library. * Categories of the library.
*/ */
@ -74,26 +58,28 @@ class LibraryPresenter(
var allCategories: List<Category> = emptyList() var allCategories: List<Category> = emptyList()
private set private set
/** /**
* List of all manga to update the * List of all manga to update the
*/ */
private var rawMangaMap: LibraryMap? = null // private var rawMangaMap: LibraryMap? = null
var libraryItems: List<LibraryItem> = emptyList()
private var allLibraryItems: List<LibraryItem> = emptyList()
private var currentMangaMap: LibraryMap? = null // private var currentMangaMap: LibraryMap? = null
private var totalChapters: Map<Long, Int>? = null private var totalChapters: Map<Long, Int>? = null
fun isDownloading() = downloadManager.hasQueue()
fun onDestroy() { fun onDestroy() {
if (currentMangaMap != null) lastLibraryItems = libraryItems
currentLibrary = Library(categories, currentMangaMap!!) lastCategories = categories
} }
fun onRestore() { fun onRestore() {
categories = currentLibrary?.categories ?: return libraryItems = lastLibraryItems ?: return
currentMangaMap = currentLibrary?.mangaMap categories = lastCategories ?: return
currentLibrary = null lastCategories = null
lastLibraryItems = null
} }
fun getLibrary() { fun getLibrary() {
@ -101,15 +87,15 @@ class LibraryPresenter(
totalChapters = null totalChapters = null
val mangaMap = withContext(Dispatchers.IO) { val mangaMap = withContext(Dispatchers.IO) {
val library = getLibraryFromDB() val library = getLibraryFromDB()
library.apply { setDownloadCount(library.mangaMap) } library.apply { setDownloadCount(library) }
rawMangaMap = library.mangaMap allLibraryItems = library
var mangaMap = library.mangaMap var mangaMap = library
mangaMap = applyFilters(mangaMap) mangaMap = applyFilters(mangaMap)
mangaMap = applySort(mangaMap) mangaMap = applySort(mangaMap)
mangaMap mangaMap
} }
currentMangaMap = mangaMap libraryItems = mangaMap
updateView(categories, mangaMap) view.onNextLibraryUpdate(libraryItems)
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
setTotalChapters() setTotalChapters()
} }
@ -119,34 +105,25 @@ class LibraryPresenter(
fun getLibraryBlocking() { fun getLibraryBlocking() {
val mangaMap = { val mangaMap = {
val library = getLibraryFromDB() val library = getLibraryFromDB()
library.apply { setDownloadCount(library.mangaMap) } library.apply { setDownloadCount(library) }
rawMangaMap = library.mangaMap allLibraryItems = library
var mangaMap = library.mangaMap var mangaMap = library
mangaMap = applyFilters(mangaMap) mangaMap = applyFilters(mangaMap)
mangaMap = applySort(mangaMap) mangaMap = applySort(mangaMap)
mangaMap mangaMap
}() }()
currentMangaMap = mangaMap libraryItems = mangaMap
launchUI { launchUI {
updateView(categories, mangaMap, true) view.onNextLibraryUpdate(libraryItems)
} }
} }
fun getAllManga(): LibraryMap? {
return currentMangaMap
}
fun getMangaInCategory(catId: Int?): List<LibraryItem>? {
val categoryId = catId ?: return null
return currentMangaMap?.get(categoryId)
}
/** /**
* Applies library filters to the given map of manga. * Applies library filters to the given map of manga.
* *
* @param map the map to filter. * @param map the map to filter.
*/ */
private fun applyFilters(map: LibraryMap): LibraryMap { private fun applyFilters(map: List<LibraryItem>): List<LibraryItem> {
val filterDownloaded = preferences.filterDownloaded().getOrDefault() val filterDownloaded = preferences.filterDownloaded().getOrDefault()
val filterUnread = preferences.filterUnread().getOrDefault() val filterUnread = preferences.filterUnread().getOrDefault()
@ -159,36 +136,29 @@ class LibraryPresenter(
val filterTrackers = FilterBottomSheet.FILTER_TRACKER val filterTrackers = FilterBottomSheet.FILTER_TRACKER
val filterFn: (LibraryItem) -> Boolean = f@{ item -> return map.filter f@{ item ->
if (item.manga.isBlank()) { if (item.manga.isBlank()) {
return@f filterDownloaded == 0 && return@f filterDownloaded == 0 && filterUnread == 0 && filterCompleted == 0 &&
filterUnread == 0 && filterTracked == 0 && filterMangaType == 0
filterCompleted == 0 &&
filterTracked == 0 &&
filterMangaType == 0
} }
// Filter for unread chapters // Filter for unread chapters
if (filterUnread == STATE_INCLUDE && if (filterUnread == STATE_INCLUDE && (item.manga.unread == 0 || db.getChapters(item.manga)
(item.manga.unread == 0 || db.getChapters(item.manga).executeAsBlocking() .executeAsBlocking().size != item.manga.unread)
.size != item.manga.unread)) return@f false ) return@f false
if (filterUnread == STATE_EXCLUDE && if (filterUnread == STATE_EXCLUDE && (item.manga.unread == 0 || db.getChapters(item.manga)
(item.manga.unread == 0 || .executeAsBlocking().size == item.manga.unread)
db.getChapters(item.manga).executeAsBlocking().size == item.manga.unread)) ) return@f false
return@f false
if (filterUnread == STATE_REALLY_EXCLUDE && item.manga.unread > 0) return@f false if (filterUnread == STATE_REALLY_EXCLUDE && item.manga.unread > 0) return@f false
if (filterMangaType > 0) { if (filterMangaType > 0) {
if (if (filterMangaType == Manga.TYPE_MANHWA) if (if (filterMangaType == Manga.TYPE_MANHWA) (filterMangaType != item.manga.mangaType() && filterMangaType != Manga.TYPE_WEBTOON)
(filterMangaType != item.manga.mangaType() && else filterMangaType != item.manga.mangaType()
filterMangaType != Manga.TYPE_WEBTOON) ) return@f false
else filterMangaType != item.manga.mangaType()) return@f false
} }
// Filter for completed status of manga // Filter for completed status of manga
if (filterCompleted == STATE_INCLUDE && item.manga.status != SManga.COMPLETED) if (filterCompleted == STATE_INCLUDE && item.manga.status != SManga.COMPLETED) return@f false
return@f false if (filterCompleted == STATE_EXCLUDE && item.manga.status == SManga.COMPLETED) return@f false
if (filterCompleted == STATE_EXCLUDE && item.manga.status == SManga.COMPLETED)
return@f false
// Filter for tracked (or per tracked service) // Filter for tracked (or per tracked service)
if (filterTracked != STATE_IGNORE) { if (filterTracked != STATE_IGNORE) {
@ -230,8 +200,6 @@ class LibraryPresenter(
} }
true true
} }
return map.mapValues { entry -> entry.value.filter(filterFn) }
} }
/** /**
@ -239,72 +207,33 @@ class LibraryPresenter(
* *
* @param map the map of manga. * @param map the map of manga.
*/ */
private fun setDownloadCount(map: LibraryMap) { private fun setDownloadCount(itemList: List<LibraryItem>) {
if (!preferences.downloadBadge().getOrDefault()) { if (!preferences.downloadBadge().getOrDefault()) {
// Unset download count if the preference is not enabled. // Unset download count if the preference is not enabled.
for ((_, itemList) in map) { for (item in itemList) {
for (item in itemList) { item.downloadCount = -1
item.downloadCount = -1
}
} }
return return
} }
for ((_, itemList) in map) { for (item in itemList) {
for (item in itemList) { item.downloadCount = downloadManager.getDownloadCount(item.manga)
item.downloadCount = downloadManager.getDownloadCount(item.manga)
}
} }
} }
private fun setUnreadBadge(map: LibraryMap) { private fun setUnreadBadge(itemList: List<LibraryItem>) {
val unreadType = preferences.unreadBadgeType().getOrDefault() val unreadType = preferences.unreadBadgeType().getOrDefault()
for ((_, itemList) in map) { for (item in itemList) {
for (item in itemList) { item.unreadType = unreadType
item.unreadType = unreadType
}
} }
} }
private fun applyCatSort(map: LibraryMap, catId: Int?): LibraryMap {
if (catId == null) return map
val categoryManga = map[catId] ?: return map
val catSorted = applySort(mapOf(catId to categoryManga), catId)
val mutableMap = map.toMutableMap()
mutableMap[catId] = catSorted.values.first()
return mutableMap
}
private fun applySort(map: LibraryMap, catId: Int?): LibraryMap {
if (catId == null) return map
val category = if (catId == 0) createDefaultCategory() else
db.getCategories().executeAsBlocking().find { it.id == catId } ?: return map
allCategories.find { it.id == catId }?.apply {
mangaOrder = category.mangaOrder
mangaSort = category.mangaSort
}
val lastReadManga by lazy {
var counter = 0
db.getLastReadManga().executeAsBlocking().associate { it.id!! to counter++ }
}
val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
i1.chapterCount = -1
i2.chapterCount = -1
sortCategory(i1, i2, lastReadManga, category)
}
val comparator = Comparator(sortFn)
return map.mapValues { entry -> entry.value.sortedWith(comparator) }
}
/** /**
* Applies library sorting to the given map of manga. * Applies library sorting to the given map of manga.
* *
* @param map the map to sort. * @param map the map to sort.
*/ */
private fun applySort(map: LibraryMap): LibraryMap { private fun applySort(map: List<LibraryItem>): List<LibraryItem> {
val sortingMode = preferences.librarySortingMode().getOrDefault() val sortingMode = preferences.librarySortingMode().getOrDefault()
val lastReadManga by lazy { val lastReadManga by lazy {
@ -316,8 +245,10 @@ class LibraryPresenter(
val useDnD = !preferences.hideCategories().getOrDefault() val useDnD = !preferences.hideCategories().getOrDefault()
val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 -> val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
i1.chapterCount = -1 if (!(sortingMode == LibrarySort.DRAG_AND_DROP || useDnD)) {
i2.chapterCount = -1 i1.chapterCount = -1
i2.chapterCount = -1
}
val compare = when { val compare = when {
sortingMode == LibrarySort.DRAG_AND_DROP || useDnD -> sortingMode == LibrarySort.DRAG_AND_DROP || useDnD ->
sortCategory(i1, i2, lastReadManga) sortCategory(i1, i2, lastReadManga)
@ -361,17 +292,15 @@ class LibraryPresenter(
else else
Collections.reverseOrder(sortFn) Collections.reverseOrder(sortFn)
return map.mapValues { entry -> entry.value.sortedWith(comparator) } return map.sortedWith(comparator)
} }
private fun setTotalChapters() { private fun setTotalChapters() {
if (totalChapters != null) return if (totalChapters != null) return
val mangaMap = rawMangaMap ?: return val mangaMap = allLibraryItems
totalChapters = mangaMap.flatMap { totalChapters = mangaMap.map {
it.value
}.associate {
it.manga.id!! to db.getChapters(it.manga).executeAsBlocking().size it.manga.id!! to db.getChapters(it.manga).executeAsBlocking().size
} }.toMap()
} }
private fun getCategory(categoryId: Int): Category { private fun getCategory(categoryId: Int): Category {
@ -394,9 +323,12 @@ class LibraryPresenter(
val category = initCat ?: allCategories.find { it.id == i1.manga.category } ?: return 0 val category = initCat ?: allCategories.find { it.id == i1.manga.category } ?: return 0
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().set(category.mangaSort.toString()) if (category.id == 0) preferences.defaultMangaOrder()
.set(category.mangaSort.toString())
else db.insertCategory(category).asRxObservable().subscribe() else db.insertCategory(category).asRxObservable().subscribe()
} }
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()) {
@ -462,11 +394,10 @@ class LibraryPresenter(
* *
* @return an observable of the categories and its manga. * @return an observable of the categories and its manga.
*/ */
private fun getLibraryFromDB(): Library { private fun getLibraryFromDB(): List<LibraryItem> {
val categories = db.getCategories().executeAsBlocking().toMutableList() val categories = db.getCategories().executeAsBlocking().toMutableList()
val libraryLayout = preferences.libraryLayout() val libraryLayout = preferences.libraryLayout()
val showCategories = !preferences.hideCategories().getOrDefault() val showCategories = !preferences.hideCategories().getOrDefault()
val unreadBadgeType = preferences.unreadBadgeType().getOrDefault()
var libraryManga = db.getLibraryMangas().executeAsBlocking() var libraryManga = db.getLibraryMangas().executeAsBlocking()
val seekPref = preferences.alwaysShowSeeker() val seekPref = preferences.alwaysShowSeeker()
if (!showCategories) if (!showCategories)
@ -475,54 +406,46 @@ class LibraryPresenter(
preferences.librarySortingMode().getOrDefault(), preferences.librarySortingMode().getOrDefault(),
preferences.librarySortingAscending().getOrDefault()) preferences.librarySortingAscending().getOrDefault())
val catItemAll = LibraryHeaderItem({ categoryAll }, -1, seekPref) val catItemAll = LibraryHeaderItem({ categoryAll }, -1, seekPref)
val libraryMap = val categorySet = mutableSetOf<Int>()
libraryManga.groupBy { manga -> val headerItems = (categories.mapNotNull { category ->
if (showCategories) manga.category else -1 val id = category.id
// LibraryItem(manga, libraryLayout).apply { unreadType = unreadBadgeType } if (id == null) null
}.map { entry -> else id to LibraryHeaderItem({ getCategory(id) }, id, seekPref)
val categoryItem = } + (-1 to catItemAll) +
if (!showCategories) catItemAll else (0 to LibraryHeaderItem({ getCategory(0) }, 0, seekPref))).toMap()
(LibraryHeaderItem({ getCategory(it) }, entry.key, seekPref)) val items = libraryManga.map {
entry.value.map { val headerItem = if (!showCategories) catItemAll else
LibraryItem( headerItems[it.category]
it, libraryLayout, preferences.uniformGrid(), seekPref, categoryItem categorySet.add(it.category)
).apply { unreadType = unreadBadgeType } LibraryItem(it, libraryLayout, preferences.uniformGrid(), seekPref, headerItem)
} }.toMutableList()
}.map {
val cat = if (showCategories) it.firstOrNull()?.manga?.category ?: 0 else -1
cat to it
// LibraryItem(manga, libraryLayout).apply { unreadType = unreadBadgeType }
}.toMap().toMutableMap()
if (showCategories) { if (showCategories) {
categories.forEach { category -> categories.forEach { category ->
if (category.id ?: 0 <= 0 && !libraryMap.containsKey(category.id)) { if (category.id ?: 0 <= 0 && !categorySet.contains(category.id)) {
val headerItem = val headerItem = headerItems[category.id ?: 0]
LibraryHeaderItem({ getCategory(category.id!!) }, category.id!!, seekPref) items.add(LibraryItem(
libraryMap[category.id!!] = listOf( LibraryManga.createBlank(category.id!!),
LibraryItem( libraryLayout,
LibraryManga.createBlank(category.id!!), preferences.uniformGrid(),
libraryLayout, preferences.alwaysShowSeeker(),
preferences.uniformGrid(), headerItem
preferences.alwaysShowSeeker(), ))
headerItem
)
)
} }
} }
} }
if (libraryMap.containsKey(0)) if (categories.size == 1 && showCategories) categories.first().name =
categories.add(0, createDefaultCategory()) context.getString(R.string.library)
if (categories.size == 1 && showCategories) if (categorySet.contains(0))
categories.first().name = context.getString(R.string.library) categories.add(0, createDefaultCategory())
this.allCategories = categories this.allCategories = categories
this.categories = if (!showCategories) arrayListOf(categoryAll) this.categories = if (!showCategories) arrayListOf(categoryAll)
else categories else categories
return Library(this.categories, libraryMap) return items
} }
private fun createDefaultCategory(): Category { private fun createDefaultCategory(): Category {
@ -539,65 +462,26 @@ class LibraryPresenter(
*/ */
fun requestFilterUpdate() { fun requestFilterUpdate() {
launchUI { launchUI {
var mangaMap = rawMangaMap ?: return@launchUI var mangaMap = allLibraryItems
mangaMap = withContext(Dispatchers.IO) { applyFilters(mangaMap) } mangaMap = withContext(Dispatchers.IO) { applyFilters(mangaMap) }
mangaMap = withContext(Dispatchers.IO) { applySort(mangaMap) } mangaMap = withContext(Dispatchers.IO) { applySort(mangaMap) }
currentMangaMap = mangaMap libraryItems = mangaMap
updateView(categories, mangaMap) view.onNextLibraryUpdate(libraryItems)
} }
} }
private suspend fun updateView(
categories: List<Category>,
mangaMap: LibraryMap,
freshStart: Boolean =
false
) {
val mangaList = withContext(Dispatchers.IO) {
val list = mutableListOf<LibraryItem>()
for (element in mangaMap.toSortedMap(compareBy { entry ->
categories.find { it.id == entry }?.order ?: -1
})) {
list.addAll(element.value)
}
list
}
view.onNextLibraryUpdate(mangaList, freshStart)
}
fun getList(): List<LibraryItem> {
val list = mutableListOf<LibraryItem>()
for (element in currentMangaMap!!.toSortedMap(compareBy { entry ->
categories.find { it.id == entry }?.order ?: -1
})) {
list.addAll(element.value)
}
return list
}
fun updateViewBlocking() {
val mangaMap = currentMangaMap ?: return
val list = mutableListOf<LibraryItem>()
for (element in mangaMap.toSortedMap(compareBy { entry ->
categories.find { it.id == entry }?.order ?: -1
})) {
list.addAll(element.value)
}
view.onNextLibraryUpdate(list, true)
}
/** /**
* Requests the library to have download badges added/removed. * Requests the library to have download badges added/removed.
*/ */
fun requestDownloadBadgesUpdate() { fun requestDownloadBadgesUpdate() {
launchUI { launchUI {
val mangaMap = rawMangaMap ?: return@launchUI val mangaMap = allLibraryItems
withContext(Dispatchers.IO) { setDownloadCount(mangaMap) } withContext(Dispatchers.IO) { setDownloadCount(mangaMap) }
rawMangaMap = mangaMap allLibraryItems = mangaMap
val current = currentMangaMap ?: return@launchUI val current = libraryItems
withContext(Dispatchers.IO) { setDownloadCount(current) } withContext(Dispatchers.IO) { setDownloadCount(current) }
currentMangaMap = current libraryItems = current
updateView(categories, current) view.onNextLibraryUpdate(libraryItems)
} }
} }
@ -605,36 +489,26 @@ class LibraryPresenter(
* Requests the library to have unread badges changed. * Requests the library to have unread badges changed.
*/ */
fun requestUnreadBadgesUpdate() { fun requestUnreadBadgesUpdate() {
// getLibrary()
launchUI { launchUI {
val mangaMap = rawMangaMap ?: return@launchUI val mangaMap = allLibraryItems
withContext(Dispatchers.IO) { setUnreadBadge(mangaMap) } withContext(Dispatchers.IO) { setUnreadBadge(mangaMap) }
rawMangaMap = mangaMap libraryItems = mangaMap
val current = currentMangaMap ?: return@launchUI val current = libraryItems
withContext(Dispatchers.IO) { setUnreadBadge(current) } withContext(Dispatchers.IO) { setUnreadBadge(current) }
currentMangaMap = current libraryItems = current
updateView(categories, current) view.onNextLibraryUpdate(libraryItems)
} }
} }
/** /**
* Requests the library to be sorted. * Requests the library to be sorted.
*/ */
fun requestSortUpdate() { private fun requestSortUpdate() {
launchUI { launchUI {
var mangaMap = currentMangaMap ?: return@launchUI var mangaMap = libraryItems
mangaMap = withContext(Dispatchers.IO) { applySort(mangaMap) } mangaMap = withContext(Dispatchers.IO) { applySort(mangaMap) }
currentMangaMap = mangaMap libraryItems = mangaMap
updateView(categories, mangaMap) view.onNextLibraryUpdate(libraryItems)
}
}
fun requestCatSortUpdate(catId: Int) {
launchUI {
var mangaMap = currentMangaMap ?: return@launchUI
mangaMap = withContext(Dispatchers.IO) { applyCatSort(mangaMap, catId) }
currentMangaMap = mangaMap
updateView(categories, mangaMap)
} }
} }
@ -654,10 +528,8 @@ class LibraryPresenter(
* Remove the selected manga from the library. * Remove the selected manga from the library.
* *
* @param mangas the list of manga to delete. * @param mangas the list of manga to delete.
* @param deleteChapters whether to also delete downloaded chapters.
*/ */
fun removeMangaFromLibrary(mangas: List<Manga>) { fun removeMangaFromLibrary(mangas: List<Manga>) {
GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) { GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) {
// Create a set of the list // Create a set of the list
val mangaToDelete = mangas.distinctBy { it.id } val mangaToDelete = mangas.distinctBy { it.id }
@ -683,19 +555,15 @@ class LibraryPresenter(
fun updateManga(manga: LibraryManga) { fun updateManga(manga: LibraryManga) {
GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) { GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) {
val rawMap = rawMangaMap ?: return@launch val rawMap = allLibraryItems ?: return@launch
val currentMap = currentMangaMap ?: return@launch val currentMap = libraryItems ?: return@launch
val id = manga.id ?: return@launch val id = manga.id ?: return@launch
val dbManga = db.getLibraryManga(id).executeAsBlocking() ?: return@launch val dbManga = db.getLibraryManga(id).executeAsBlocking() ?: return@launch
arrayOf(rawMap, currentMap).forEach { map -> arrayOf(rawMap, currentMap).forEach { map ->
map.apply { map.forEach { item ->
forEach { entry -> if (item.manga.id == dbManga.id) {
entry.value.forEach { item -> item.manga.last_update = dbManga.last_update
if (item.manga.id == dbManga.id) { item.manga.unread = dbManga.unread
item.manga.last_update = dbManga.last_update
item.manga.unread = dbManga.unread
}
}
} }
} }
} }
@ -703,8 +571,7 @@ class LibraryPresenter(
} }
} }
fun addMangas(mangas: List<Manga>) { fun reAddMangas(mangas: List<Manga>) {
GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) { GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) {
val mangaToAdd = mangas.distinctBy { it.id } val mangaToAdd = mangas.distinctBy { it.id }
mangaToAdd.forEach { it.favorite = true } mangaToAdd.forEach { it.favorite = true }
@ -728,86 +595,8 @@ class LibraryPresenter(
mc.add(MangaCategory.create(manga, cat)) mc.add(MangaCategory.create(manga, cat))
} }
} }
db.setMangaCategories(mc, mangas) db.setMangaCategories(mc, mangas)
} getLibrary()
fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) {
val source = sourceManager.get(manga.source) ?: return
// state = state.copy(isReplacingManga = true)
Observable.defer { source.fetchChapterList(manga) }
.onErrorReturn { emptyList() }
.doOnNext { migrateMangaInternal(source, it, prevManga, manga, replace) }
.onErrorReturn { emptyList() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
// .doOnUnsubscribe { state = state.copy(isReplacingManga = false) }
.subscribe()
}
private fun migrateMangaInternal(
source: Source,
sourceChapters: List<SChapter>,
prevManga: Manga,
manga: Manga,
replace: Boolean
) {
val flags = preferences.migrateFlags().getOrDefault()
val migrateChapters = MigrationFlags.hasChapters(flags)
val migrateCategories = MigrationFlags.hasCategories(flags)
val migrateTracks = MigrationFlags.hasTracks(flags)
db.inTransaction {
// Update chapters read
if (migrateChapters) {
try {
syncChaptersWithSource(db, sourceChapters, manga, source)
} catch (e: Exception) {
// Worst case, chapters won't be synced
}
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
val maxChapterRead =
prevMangaChapters.filter { it.read }.maxBy { it.chapter_number }?.chapter_number
if (maxChapterRead != null) {
val dbChapters = db.getChapters(manga).executeAsBlocking()
for (chapter in dbChapters) {
if (chapter.isRecognizedNumber && chapter.chapter_number <= maxChapterRead) {
chapter.read = true
}
}
db.insertChapters(dbChapters).executeAsBlocking()
}
}
// Update categories
if (migrateCategories) {
val categories = db.getCategoriesForManga(prevManga).executeAsBlocking()
val mangaCategories = categories.map { MangaCategory.create(manga, it) }
db.setMangaCategories(mangaCategories, listOf(manga))
}
// Update track
if (migrateTracks) {
val tracks = db.getTracks(prevManga).executeAsBlocking()
for (track in tracks) {
track.id = null
track.manga_id = manga.id!!
}
db.insertTracks(tracks).executeAsBlocking()
}
// Update favorite status
if (replace) {
prevManga.favorite = false
db.updateMangaFavorite(prevManga).executeAsBlocking()
}
manga.favorite = true
db.updateMangaFavorite(manga).executeAsBlocking()
// SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title
db.updateMangaTitle(manga).executeAsBlocking()
}
} }
fun getFirstUnread(manga: Manga): Chapter? { fun getFirstUnread(manga: Manga): Chapter? {
@ -826,7 +615,7 @@ class LibraryPresenter(
} else { } else {
if (category.id == 0) preferences.defaultMangaOrder().set(category.mangaSort.toString()) if (category.id == 0) preferences.defaultMangaOrder().set(category.mangaSort.toString())
else Injekt.get<DatabaseHelper>().insertCategory(category).executeAsBlocking() else Injekt.get<DatabaseHelper>().insertCategory(category).executeAsBlocking()
requestCatSortUpdate(category.id!!) requestSortUpdate()
} }
} }
@ -837,7 +626,7 @@ class LibraryPresenter(
category.mangaOrder = mangaIds category.mangaOrder = mangaIds
if (category.id == 0) preferences.defaultMangaOrder().set(mangaIds.joinToString("/")) if (category.id == 0) preferences.defaultMangaOrder().set(mangaIds.joinToString("/"))
else db.insertCategory(category).executeAsBlocking() else db.insertCategory(category).executeAsBlocking()
requestCatSortUpdate(category.id!!) requestSortUpdate()
} }
} }
@ -884,7 +673,8 @@ class LibraryPresenter(
} }
companion object { companion object {
private var currentLibrary: Library? = null private var lastLibraryItems: List<LibraryItem>? = null
private var lastCategories: List<Category>? = null
fun updateDB() { fun updateDB() {
val db: DatabaseHelper = Injekt.get() val db: DatabaseHelper = Injekt.get()

View File

@ -1,38 +0,0 @@
package eu.kanade.tachiyomi.ui.library
import android.content.Context
import android.util.AttributeSet
import android.widget.ArrayAdapter
import android.widget.Spinner
class ReSpinner : Spinner {
constructor(context: Context?) : super(context) {}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {}
constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(
context, attrs, defStyle
)
override fun setSelection(position: Int, animate: Boolean) {
val sameSelected = position == selectedItemPosition
super.setSelection(position, animate)
if (sameSelected) { // Spinner does not call the OnItemSelectedListener if the same item is selected, so do it manually now
onItemSelectedListener?.onItemSelected(
this, selectedView, position, selectedItemId
)
}
}
override fun setSelection(position: Int) {
val sameSelected = position == selectedItemPosition
super.setSelection(position)
if (sameSelected) { // Spinner does not call the OnItemSelectedListener if the same item is selected, so do it manually now
onItemSelectedListener?.onItemSelected(
this, selectedView, position, selectedItemId
)
}
}
}
class SpinnerAdapter(context: Context, layoutId: Int, val array: Array<String>) :
ArrayAdapter<String>
(context, layoutId, array)

View File

@ -62,7 +62,7 @@
android:padding="5dp" android:padding="5dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
tools:tint="?attr/colorAccent" android:tint="?attr/colorAccent"
android:src="@drawable/ic_refresh_white_24dp" android:src="@drawable/ic_refresh_white_24dp"
app:layout_constraintBottom_toBottomOf="@id/category_title" app:layout_constraintBottom_toBottomOf="@id/category_title"
app:layout_constraintTop_toTopOf="@id/category_title" app:layout_constraintTop_toTopOf="@id/category_title"
@ -70,6 +70,18 @@
app:layout_constraintStart_toEndOf="@id/category_title" app:layout_constraintStart_toEndOf="@id/category_title"
app:rippleColor="@color/fullRippleColor" /> app:rippleColor="@color/fullRippleColor" />
<ProgressBar
android:id="@+id/cat_progress"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="5dp"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintTop_toTopOf="@id/update_button"
app:layout_constraintBottom_toBottomOf="@id/update_button"
app:layout_constraintStart_toStartOf="@id/update_button"
app:layout_constraintEnd_toEndOf="@id/update_button"/>
<TextView <TextView
android:id="@+id/category_sort" android:id="@+id/category_sort"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -82,6 +94,7 @@
android:ellipsize="start" android:ellipsize="start"
android:focusable="true" android:focusable="true"
android:gravity="center|end" android:gravity="center|end"
android:background="@drawable/square_ripple"
android:maxLines="2" android:maxLines="2"
android:padding="6dp" android:padding="6dp"
android:textAlignment="textEnd" android:textAlignment="textEnd"

View File

@ -27,7 +27,7 @@
<com.reddit.indicatorfastscroll.FastScrollerView <com.reddit.indicatorfastscroll.FastScrollerView
android:id="@+id/fast_scroller" android:id="@+id/fast_scroller"
android:textColor="?android:attr/textColorPrimaryInverse" android:textColor="?android:attr/textColorPrimaryInverse"
android:layout_width="22dp" android:layout_width="25dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:elevation="10dp" android:elevation="10dp"
android:layout_gravity="end" android:layout_gravity="end"