More updates to long list library

Added update button + sort option to each category header
Reset glide cache/memory back to default
This commit is contained in:
Jay 2020-02-17 22:53:38 -08:00
parent 73ecfa389b
commit 3df6f10316
11 changed files with 505 additions and 210 deletions

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.data.glide package eu.kanade.tachiyomi.data.glide
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.Registry import com.bumptech.glide.Registry
@ -9,9 +8,7 @@ import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.load.engine.cache.LruResourceCache
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.module.AppGlideModule import com.bumptech.glide.module.AppGlideModule
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
@ -27,10 +24,10 @@ import java.io.InputStream
class TachiGlideModule : AppGlideModule() { class TachiGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) { override fun applyOptions(context: Context, builder: GlideBuilder) {
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 100 * 1024 * 1024)) builder.setDiskCache(InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024))
builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565)) builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565))
val memoryCacheSizeBytes = 1024 * 1024 * 100 // 100mb //val memoryCacheSizeBytes = 1024 * 1024 * 100 // 100mb
builder.setMemoryCache(LruResourceCache(memoryCacheSizeBytes.toLong())) //builder.setMemoryCache(LruResourceCache(memoryCacheSizeBytes.toLong()))
/* builder.setDefaultTransitionOptions( /* builder.setDefaultTransitionOptions(
Drawable::class.java, Drawable::class.java,

View File

@ -35,17 +35,15 @@ 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.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.isServiceRunning import eu.kanade.tachiyomi.util.system.isServiceRunning
import eu.kanade.tachiyomi.util.system.notification import eu.kanade.tachiyomi.util.system.notification
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
@ -296,12 +294,9 @@ class LibraryUpdateService(
val target = intent.getSerializableExtra(KEY_TARGET) as? Target ?: return START_NOT_STICKY val target = intent.getSerializableExtra(KEY_TARGET) as? Target ?: return START_NOT_STICKY
// Unsubscribe from any previous subscription if needed. // Unsubscribe from any previous subscription if needed.
job?.cancel()
subscription?.unsubscribe() subscription?.unsubscribe()
val handler = CoroutineExceptionHandler { _, exception ->
Timber.e(exception)
stopSelf(startId)
}
val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault() val selectedScheme = preferences.libraryUpdatePrioritization().getOrDefault()
if (target == Target.CHAPTERS) { if (target == Target.CHAPTERS) {
updateChapters( updateChapters(
@ -331,15 +326,17 @@ class LibraryUpdateService(
private fun updateChapters(mangaToAdd: List<LibraryManga>, startId: Int) { private fun updateChapters(mangaToAdd: List<LibraryManga>, startId: Int) {
addManga(mangaToAdd) addManga(mangaToAdd)
if (job == null) { val handler = CoroutineExceptionHandler { _, exception ->
job = GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) { Timber.e(exception)
stopSelf(startId)
}
job = GlobalScope.launch(handler) {
updateChaptersJob() updateChaptersJob()
mangaToUpdate.clear() mangaToUpdate.clear()
categoryIds.clear() categoryIds.clear()
stopSelf(startId) stopSelf(startId)
} }
} }
}
private fun updateChaptersJob() { private fun updateChaptersJob() {
// Initialize the variables holding the progress of the updates. // Initialize the variables holding the progress of the updates.

View File

@ -160,5 +160,7 @@ class LibraryCategoryAdapter(val libraryListener: LibraryListener) :
fun startReading(position: Int) fun startReading(position: Int)
fun onItemReleased(position: Int) fun onItemReleased(position: Int)
fun canDrag(): Boolean fun canDrag(): Boolean
fun updateCategory(catId: Int): Boolean
fun sortCategory(catId: Int, sortBy: Int): String
} }
} }

View File

@ -90,7 +90,6 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
} }
} }
recycler.setHasFixedSize(true) recycler.setHasFixedSize(true)
recycler.adapter = adapter recycler.adapter = adapter
swipe_refresh.addView(recycler) swipe_refresh.addView(recycler)
@ -392,4 +391,12 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
controller.invalidateActionMode() controller.invalidateActionMode()
} }
override fun updateCategory(catId: Int): Boolean {
return true
}
override fun sortCategory(catId: Int, sortBy: Int): String {
return ""
}
} }

View File

@ -20,8 +20,8 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.AppCompatSpinner import androidx.appcompat.widget.AppCompatSpinner
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.math.MathUtils.clamp
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -67,13 +67,14 @@ import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.inflate import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
import kotlinx.android.synthetic.main.filter_bottom_sheet.* import kotlinx.android.synthetic.main.filter_bottom_sheet.*
import kotlinx.android.synthetic.main.library_controller.* import kotlinx.android.synthetic.main.library_controller.*
import kotlinx.coroutines.delay
import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -179,7 +180,7 @@ class LibraryController(
private var spinnerAdapter: SpinnerAdapter? = null private var spinnerAdapter: SpinnerAdapter? = null
var scrollLister = object : RecyclerView.OnScrollListener () { private var scrollListener = object : RecyclerView.OnScrollListener () {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
val position = val position =
@ -197,10 +198,10 @@ class LibraryController(
bottom_sheet.lastCategory = category bottom_sheet.lastCategory = category
if (preferences.librarySortingMode().getOrDefault() == LibrarySort.DRAG_AND_DROP) if (preferences.librarySortingMode().getOrDefault() == LibrarySort.DRAG_AND_DROP)
bottom_sheet.updateTitle() bottom_sheet.updateTitle()
// spinner.onItemSelectedListener = null updateScroll = true
spinner.setSelection(order + 1)
spinnerAdapter?.setCustomText(category?.name) spinnerAdapter?.setCustomText(category?.name)
//spinner.view
//spinner.post { spinner.onItemSelectedListener = listener }
} }
} }
} }
@ -259,11 +260,11 @@ class LibraryController(
else { else {
adapter = LibraryCategoryAdapter(this) adapter = LibraryCategoryAdapter(this)
recycler = if (preferences.libraryLayout().getOrDefault() == 0) { recycler = if (preferences.libraryLayout().getOrDefault() == 0) {
(swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply { (recycler_layout.inflate(R.layout.library_list_recycler) as RecyclerView).apply {
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
} }
} else { } else {
(swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply { (recycler_layout.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply {
spanCount = mangaPerRow spanCount = mangaPerRow
manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() { manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int { override fun getSpanSize(position: Int): Int {
@ -280,9 +281,9 @@ class LibraryController(
//adapter.setStickyHeaders(true) //adapter.setStickyHeaders(true)
recycler_layout.addView(recycler) recycler_layout.addView(recycler)
adapter.fastScroller = fast_scroller adapter.fastScroller = fast_scroller
recycler.addOnScrollListener(scrollLister) recycler.addOnScrollListener(scrollListener)
spinner = swipe_refresh.inflate(R.layout.library_spinner) as AppCompatSpinner spinner = recycler_layout.inflate(R.layout.library_spinner) as AppCompatSpinner
(activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(true) (activity as MainActivity).supportActionBar?.setDisplayShowCustomEnabled(true)
(activity as MainActivity).supportActionBar?.customView = spinner (activity as MainActivity).supportActionBar?.customView = spinner
spinnerAdapter = SpinnerAdapter(view.context, R.layout.library_spinner_textview, spinnerAdapter = SpinnerAdapter(view.context, R.layout.library_spinner_textview,
@ -299,7 +300,7 @@ class LibraryController(
//bottom_sheet.onCreate(pager_layout) //bottom_sheet.onCreate(pager_layout)
bottom_sheet.onCreate(if (usePager) pager_layout else swipe_refresh) bottom_sheet.onCreate(if (usePager) pager_layout else recycler_layout)
bottom_sheet.onGroupClicked = { bottom_sheet.onGroupClicked = {
when (it) { when (it) {
@ -334,9 +335,6 @@ class LibraryController(
if (!usePager && !phoneLandscape) { if (!usePager && !phoneLandscape) {
val height = view.context.resources.getDimensionPixelSize(R.dimen.rounder_radius) + 5.dpToPx val height = view.context.resources.getDimensionPixelSize(R.dimen.rounder_radius) + 5.dpToPx
recycler.updatePaddingRelative(bottom = height) recycler.updatePaddingRelative(bottom = height)
fast_scroller.updateLayoutParams<CoordinatorLayout.LayoutParams> {
bottomMargin = height
}
} }
if (presenter.isDownloading()) { if (presenter.isDownloading()) {
@ -350,7 +348,7 @@ class LibraryController(
if (library != null) presenter.updateViewBlocking() //onNextLibraryUpdate(presenter.categories, library) if (library != null) presenter.updateViewBlocking() //onNextLibraryUpdate(presenter.categories, library)
else { else {
library_pager.alpha = 0f library_pager.alpha = 0f
swipe_refresh.alpha = 0f recycler_layout.alpha = 0f
presenter.getLibraryBlocking() presenter.getLibraryBlocking()
} }
} }
@ -466,8 +464,8 @@ class LibraryController(
spinnerAdapter?.setCustomText(presenter.categories.find { it.order == activeCategory spinnerAdapter?.setCustomText(presenter.categories.find { it.order == activeCategory
}?.name ?: resources?.getString(R.string.label_library)) }?.name ?: resources?.getString(R.string.label_library))
justStarted = false justStarted = false
if (swipe_refresh.alpha == 0f) if (recycler_layout.alpha == 0f)
swipe_refresh.animate().alpha(1f).setDuration(500).start() recycler_layout.animate().alpha(1f).setDuration(500).start()
}else { }else {
@ -475,7 +473,18 @@ class LibraryController(
(recycler.layoutManager as LinearLayoutManager) (recycler.layoutManager as LinearLayoutManager)
.scrollToPositionWithOffset(position, 0) .scrollToPositionWithOffset(position, 0)
} }
adapter.isLongPressDragEnabled = canDrag()
bottom_sheet.lastCategory = presenter.categories[clamp(activeCategory,
0,
presenter.categories.size - 1)]
bottom_sheet.updateTitle()
updateScroll = false
spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { pos -> spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { pos ->
if (updateScroll) {
updateScroll = false
return@IgnoreFirstSpinnerListener
}
val headerPosition = adapter.indexOf(pos - 1) + 1 val headerPosition = adapter.indexOf(pos - 1) + 1
if (headerPosition > -1) { if (headerPosition > -1) {
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset( (recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
@ -589,11 +598,11 @@ class LibraryController(
recycler !is AutofitRecyclerView && preferences.libraryLayout().getOrDefault() > 0) { recycler !is AutofitRecyclerView && preferences.libraryLayout().getOrDefault() > 0) {
recycler_layout.removeView(recycler) recycler_layout.removeView(recycler)
recycler = if (preferences.libraryLayout().getOrDefault() == 0) { recycler = if (preferences.libraryLayout().getOrDefault() == 0) {
(swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply { (recycler_layout.inflate(R.layout.library_list_recycler) as RecyclerView).apply {
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
} }
} else { } else {
(swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply { (recycler_layout.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply {
spanCount = mangaPerRow spanCount = mangaPerRow
manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() { manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int { override fun getSpanSize(position: Int): Int {
@ -605,7 +614,7 @@ class LibraryController(
} }
} }
recycler.setHasFixedSize(true) recycler.setHasFixedSize(true)
recycler.addOnScrollListener(scrollLister) recycler.addOnScrollListener(scrollListener)
recycler_layout.addView(recycler) recycler_layout.addView(recycler)
} }
recycler.adapter = adapter recycler.adapter = adapter
@ -667,6 +676,8 @@ class LibraryController(
setOnQueryTextChangeListener(searchView) { setOnQueryTextChangeListener(searchView) {
query = it ?: "" query = it ?: ""
searchRelay.call(query) searchRelay.call(query)
adapter.setFilter(it)
adapter.performFilter()
true true
} }
searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() }) searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() })
@ -800,6 +811,11 @@ class LibraryController(
override fun onDestroyActionMode(mode: ActionMode?) { override fun onDestroyActionMode(mode: ActionMode?) {
// Clear all the manga selections and notify child views. // Clear all the manga selections and notify child views.
selectedMangas.clear() selectedMangas.clear()
adapter.mode = SelectableAdapter.Mode.SINGLE
adapter.clearSelection()
adapter.notifyDataSetChanged()
lastClickPosition = -1
adapter.isLongPressDragEnabled = canDrag()
selectionRelay.call(LibrarySelectionEvent.Cleared()) selectionRelay.call(LibrarySelectionEvent.Cleared())
actionMode = null actionMode = null
} }
@ -817,11 +833,33 @@ class LibraryController(
fun setSelection(manga: Manga, selected: Boolean) { fun setSelection(manga: Manga, selected: Boolean) {
if (selected) { if (selected) {
if (selectedMangas.add(manga)) { if (selectedMangas.add(manga)) {
selectionRelay.call(LibrarySelectionEvent.Selected(manga)) if (usePager) selectionRelay.call(LibrarySelectionEvent.Selected(manga))
else {
val position = adapter.indexOf(manga)
if (adapter.mode != SelectableAdapter.Mode.MULTI) {
adapter.mode = SelectableAdapter.Mode.MULTI
}
launchUI {
delay(100)
adapter.isLongPressDragEnabled = false
}
adapter.toggleSelection(position)
(recycler.findViewHolderForItemId(manga.id!!) as? LibraryHolder)?.toggleActivation()
}
} }
} else { } else {
if (selectedMangas.remove(manga)) { if (selectedMangas.remove(manga)) {
selectionRelay.call(LibrarySelectionEvent.Unselected(manga)) if (usePager) selectionRelay.call(LibrarySelectionEvent.Unselected(manga))
else {
val position = adapter.indexOf(manga)
lastClickPosition = -1
if (selectedMangas.isEmpty()) {
adapter.mode = SelectableAdapter.Mode.SINGLE
adapter.isLongPressDragEnabled = canDrag()
}
adapter.toggleSelection(position)
(recycler.findViewHolderForItemId(manga.id!!) as? LibraryHolder)?.toggleActivation()
}
} }
} }
} }
@ -876,16 +914,13 @@ class LibraryController(
override fun startReading(position: Int) { override fun startReading(position: Int) {
val activity = activity ?: return val activity = activity ?: return
val manga = (adapter.getItem(position) as? LibraryItem)?.manga ?: return val manga = (adapter.getItem(position) as? LibraryItem)?.manga ?: return
if (adapter.mode == SelectableAdapter.Mode.MULTI) toggleSelection(position)
val chapter = presenter.getFirstUnread(manga) ?: return val chapter = presenter.getFirstUnread(manga) ?: return
val intent = ReaderActivity.newIntent(activity, manga, chapter) val intent = ReaderActivity.newIntent(activity, manga, chapter)
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
startActivity(intent) startActivity(intent)
} }
override fun onItemReleased(position: Int) {
}
fun startReading(manga: Manga) { fun startReading(manga: Manga) {
val activity = activity ?: return val activity = activity ?: return
val chapter = presenter.getFirstUnread(manga) ?: return val chapter = presenter.getFirstUnread(manga) ?: return
@ -953,9 +988,9 @@ class LibraryController(
* @param position the position to toggle. * @param position the position to toggle.
*/ */
private fun toggleSelection(position: Int) { private fun toggleSelection(position: Int) {
val item = adapter.getItem(position) ?: return val item = adapter.getItem(position) as? LibraryItem ?: return
setSelection((item as LibraryItem).manga, !adapter.isSelected(position)) setSelection(item.manga, !adapter.isSelected(position))
invalidateActionMode() invalidateActionMode()
} }
@ -966,14 +1001,29 @@ class LibraryController(
* @param position the position to toggle. * @param position the position to toggle.
*/ */
private fun setSelection(position: Int) { private fun setSelection(position: Int) {
val item = adapter.getItem(position) ?: return val item = adapter.getItem(position) as? LibraryItem ?: return
setSelection((item as LibraryItem).manga, true) setSelection(item.manga, true)
invalidateActionMode() invalidateActionMode()
} }
override fun onItemMove(fromPosition: Int, toPosition: Int) { } override fun onItemMove(fromPosition: Int, toPosition: Int) { }
override fun onItemReleased(position: Int) {
if (adapter.selectedItemCount > 0) return
val item = adapter.getItem(position) as? LibraryItem ?: return
val newHeader = adapter.getSectionHeader(position) as? LibraryHeaderItem
val libraryItems = adapter.getSectionItems(adapter.getSectionHeader(position)).filterIsInstance<LibraryItem>()
val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id }
if (newHeader?.category?.id == item.manga.category) {
presenter.rearrangeCategory(item.manga.category, mangaIds)
} else {
presenter.moveMangaToCategory(item, newHeader?.category?.id, mangaIds)
Timber.d("Manga has Moved")
}
}
override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean { override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean {
if (adapter.selectedItemCount > 1) if (adapter.selectedItemCount > 1)
return false return false
@ -981,6 +1031,29 @@ class LibraryController(
toggleSelection(fromPosition) toggleSelection(fromPosition)
return true return true
} }
override fun updateCategory(catId: Int): Boolean {
val category = (adapter.getItem(catId) as? LibraryHeaderItem)?.category ?:
return false
val inQueue = LibraryUpdateService.categoryInQueue(category.id)
snack?.dismiss()
snack = snackbar_layout.snack(resources!!.getString(
when {
inQueue -> R.string.category_already_in_queue
LibraryUpdateService.isRunning(view!!.context) ->
R.string.adding_category_to_queue
else -> R.string.updating_category_x
}, category.name))
if (!inQueue)
LibraryUpdateService.start(view!!.context, category)
return true
}
override fun sortCategory(catId: Int, sortBy: Int): String {
presenter.sortCategory(catId, sortBy)
return ""
}
} }
object HeightTopWindowInsetsListener : View.OnApplyWindowInsetsListener { object HeightTopWindowInsetsListener : View.OnApplyWindowInsetsListener {

View File

@ -1,14 +1,25 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import android.graphics.drawable.Drawable
import android.view.View import android.view.View
import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.invisible
import eu.kanade.tachiyomi.util.view.visible
class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHeaderItem.Holder>() { class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHeaderItem.Holder>() {
@ -20,7 +31,7 @@ class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHead
view: View, view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>> adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
): Holder { ): Holder {
return Holder(view, adapter) return Holder(view, adapter as LibraryCategoryAdapter)
} }
override fun bindViewHolder( override fun bindViewHolder(
@ -49,16 +60,120 @@ class LibraryHeaderItem(val category: Category) : AbstractHeaderItem<LibraryHead
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return category.id!! return -(category.id!!)
} }
class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : class Holder(view: View, private val adapter: LibraryCategoryAdapter) :
FlexibleViewHolder(view, adapter, true) { FlexibleViewHolder(view, adapter, true) {
private val sectionText: TextView = view.findViewById(R.id.category_title) private val sectionText: TextView = view.findViewById(R.id.category_title)
private val sortText: TextView = view.findViewById(R.id.category_sort)
private val updateButton: MaterialButton = view.findViewById(R.id.update_button)
private val catProgress: ProgressBar = view.findViewById(R.id.cat_progress)
init {
updateButton.setOnClickListener { addCategoryToUpdate() }
sortText.setOnClickListener { showCatSortOptions() }
}
fun bind(item: LibraryHeaderItem) { fun bind(item: LibraryHeaderItem) {
sectionText.text = item.category.name sectionText.text = item.category.name
sortText.text = itemView.context.getString(
when (item.category.sortingMode()) {
LibrarySort.LAST_UPDATED -> R.string.action_sort_last_updated
LibrarySort.DRAG_AND_DROP -> R.string.action_sort_drag_and_drop
LibrarySort.TOTAL -> R.string.action_sort_total
LibrarySort.UNREAD -> R.string.action_filter_unread
LibrarySort.LAST_READ -> R.string.action_sort_last_read
LibrarySort.ALPHA -> R.string.title
else -> R.string.action_sort_drag_and_drop
}
)
if (LibraryUpdateService.categoryInQueue(item.category.id)) {
catProgress.visible()
updateButton.invisible()
} else {
catProgress.gone()
updateButton.visible()
}
}
private fun addCategoryToUpdate() {
if (adapter.libraryListener.updateCategory(adapterPosition)) {
catProgress.visible()
updateButton.invisible()
}
}
private fun showCatSortOptions() {
val category =
(adapter.getItem(adapterPosition) as? LibraryHeaderItem)?.category ?: return
// Create a PopupMenu, giving it the clicked view for an anchor
val popup = PopupMenu(itemView.context, sortText)
// Inflate our menu resource into the PopupMenu's Menu
popup.menuInflater.inflate(R.menu.cat_sort, popup.menu)
// Set a listener so we are notified if a menu item is clicked
popup.setOnMenuItemClickListener { menuItem ->
onCatSortClicked(category, menuItem.itemId)
true
}
val sortingMode = category.sortingMode()
val currentItem = if (sortingMode == null) null
else popup.menu.findItem(
when (sortingMode) {
LibrarySort.DRAG_AND_DROP -> R.id.action_drag_and_drop
LibrarySort.TOTAL -> R.id.action_total_chaps
LibrarySort.LAST_READ -> R.id.action_last_read
LibrarySort.UNREAD -> R.id.action_unread
LibrarySort.LAST_UPDATED -> R.id.action_update
else -> R.id.action_alpha
}
)
if (sortingMode != null && popup.menu is MenuBuilder) {
val m = popup.menu as MenuBuilder
m.setOptionalIconsVisible(true)
}
currentItem?.icon = tintVector(
if (category.isAscending()) R.drawable.ic_arrow_up_white_24dp
else R.drawable.ic_arrow_down_white_24dp
)
// Finally show the PopupMenu
popup.show()
}
private fun tintVector(resId: Int): Drawable? {
return ContextCompat.getDrawable(itemView.context, resId)?.mutate()?.apply {
setTint(itemView.context.getResourceColor(android.R.attr.colorAccent))
}
}
private fun onCatSortClicked(category: Category, menuId: Int?) {
val modType = if (menuId == null) {
val t = (category.mangaSort?.minus('a') ?: 0) + 1
if (t % 2 != 0) t + 1
else t - 1
}
else {
val order = when (menuId) {
R.id.action_last_read -> 3
R.id.action_unread -> 2
R.id.action_update -> 1
else -> 0
}
if (order == category.catSortingMode()) {
onCatSortClicked(category, null)
return
}
(2 * order + 1)
}
adapter.libraryListener.sortCategory(category.id!!, modType)
} }
} }
} }

View File

@ -486,20 +486,12 @@ class LibraryPresenter(
suspend fun updateView(categories: List<Category>, mangaMap: LibraryMap, freshStart:Boolean suspend fun updateView(categories: List<Category>, mangaMap: LibraryMap, freshStart:Boolean
= false) { = false) {
/* val list = withContext(Dispatchers.IO) {
val showCategories = !preferences.hideCategories().getOrDefault()
val current = mangaMap.values.first()
current.groupBy {
if (showCategories) it.manga.category else 0
}.flatMap { it.value }
}*/
if (preferences.libraryUsingPager().getOrDefault()) { if (preferences.libraryUsingPager().getOrDefault()) {
view.onNextLibraryUpdate(categories, mangaMap, true) view.onNextLibraryUpdate(categories, mangaMap, true)
} }
else { else {
val mangaList = withContext(Dispatchers.IO) { val mangaList = withContext(Dispatchers.IO) {
val list = mutableListOf<LibraryItem>() val list = mutableListOf<LibraryItem>()
val many = categories.size > 1
for (element in mangaMap.toSortedMap(compareBy { entry -> for (element in mangaMap.toSortedMap(compareBy { entry ->
categories.find { it.id == entry }?.order ?: -1 categories.find { it.id == entry }?.order ?: -1
})) { })) {
@ -771,8 +763,67 @@ class LibraryPresenter(
} }
fun sortCategory(catId: Int, order: Int) {
val category = categories.find { catId == it.id } ?: return
category.mangaSort = ('a' + (order - 1))
if (category.id == 0)
preferences.defaultMangaOrder().set(category.mangaSort.toString())
else
Injekt.get<DatabaseHelper>().insertCategory(category).asRxObservable().subscribe()
requestCatSortUpdate(category.id!!)
}
fun rearrangeCategory(catId: Int?, mangaIds: List<Long>) {
GlobalScope.launch(Dispatchers.IO) {
val category = categories.find { catId == it.id } ?: return@launch
category.mangaSort = null
category.mangaOrder = mangaIds
if (category.id == 0) preferences.defaultMangaOrder().set(mangaIds.joinToString("/"))
else db.insertCategory(category).executeAsBlocking()
requestCatSortUpdate(category.id!!)
}
}
fun moveMangaToCategory(item: LibraryItem, catId: Int?, mangaIds: List<Long>) {
GlobalScope.launch(Dispatchers.IO) {
val categoryId = catId ?: return@launch
val category = categories.find { catId == it.id } ?: return@launch
val manga = item.manga
val mangaMap = currentMangaMap?.toMutableMap() ?: return@launch
val oldCatId = item.manga.category
val oldCatMap = mangaMap[manga.category]?.toMutableList() ?: return@launch
val newCatMap = mangaMap[catId]?.toMutableList() ?: return@launch
oldCatMap.remove(item)
newCatMap.add(item)
mangaMap[oldCatId] = oldCatMap
mangaMap[catId] = newCatMap
currentMangaMap = mangaMap
item.manga.category = categoryId
val mc = ArrayList<MangaCategory>()
val categories =
db.getCategoriesForManga(manga).executeAsBlocking().filter { it.id != oldCatId } + listOf(category)
for (cat in categories) {
mc.add(MangaCategory.create(manga, cat))
}
db.setMangaCategories(mc, listOf(manga))
category.mangaSort = null
val ids = mangaIds.toMutableList()
if (!ids.contains(manga.id!!))
ids.add(manga.id!!)
category.mangaOrder = ids
if (category.id == 0) preferences.defaultMangaOrder().set(mangaIds.joinToString("/"))
else db.insertCategory(category).executeAsBlocking()
getLibrary()
}
}
private companion object { private companion object {
var currentLibrary:Library? = null var currentLibrary:Library? = null
var currentList:List<LibraryItem>? = null
} }
} }

View File

@ -10,11 +10,10 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.RadioButton
import android.widget.RadioGroup
import android.widget.Spinner import android.widget.Spinner
import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
@ -122,6 +121,7 @@ class SortFilterBottomSheet @JvmOverloads constructor(context: Context, attrs: A
updateTitle() updateTitle()
val shadow2:View = (pagerView.parent as ViewGroup).findViewById(R.id.shadow2) val shadow2:View = (pagerView.parent as ViewGroup).findViewById(R.id.shadow2)
val shadow:View = (pagerView.parent as ViewGroup).findViewById(R.id.shadow) val shadow:View = (pagerView.parent as ViewGroup).findViewById(R.id.shadow)
val fastScroller:View = (pagerView.parent as ViewGroup).findViewById(R.id.fast_scroller)
val coordLayout:View = (pagerView.parent as ViewGroup).findViewById(R.id.snackbar_layout) val coordLayout:View = (pagerView.parent as ViewGroup).findViewById(R.id.snackbar_layout)
val phoneLandscape = (isLandscape() && !isTablet()) val phoneLandscape = (isLandscape() && !isTablet())
if (phoneLandscape) if (phoneLandscape)
@ -167,6 +167,9 @@ class SortFilterBottomSheet @JvmOverloads constructor(context: Context, attrs: A
} }
if (sheetBehavior?.state == BottomSheetBehavior.STATE_COLLAPSED) { if (sheetBehavior?.state == BottomSheetBehavior.STATE_COLLAPSED) {
val height = context.resources.getDimensionPixelSize(R.dimen.rounder_radius) val height = context.resources.getDimensionPixelSize(R.dimen.rounder_radius)
fastScroller.updateLayoutParams<CoordinatorLayout.LayoutParams> {
bottomMargin = if (phoneLandscape) 0 else (top_bar.height - height)
}
pager?.setPadding(0, 0, 0, if (phoneLandscape) 0 else pager?.setPadding(0, 0, 0, if (phoneLandscape) 0 else
(top_bar.height - height)) (top_bar.height - height))
coordLayout.setPadding(0, 0, 0, peekingHeight) coordLayout.setPadding(0, 0, 0, peekingHeight)

View File

@ -1,7 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/manga_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_layout" android:id="@+id/constraint_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -151,4 +157,5 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
tools:text="Sample artist" /> tools:text="Sample artist" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,27 +1,74 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="15dp"
android:paddingBottom="5dp"
android:gravity="center_vertical"> android:gravity="center_vertical">
<TextView <TextView
android:id="@+id/category_title" android:id="@+id/category_title"
style="@style/TextAppearance.MaterialComponents.Headline5" style="@style/TextAppearance.MaterialComponents.Headline6"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginBottom="4dp"
android:gravity="center|start" android:gravity="center|start"
android:inputType="none" android:inputType="none"
android:layout_marginStart="10dp"
android:maxLines="1" android:maxLines="1"
android:textColor="?attr/actionBarTintColor" android:textColor="?attr/actionBarTintColor"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="@id/update_button"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintWidth_default="wrap"
tools:text="Title" /> tools:text="Title" />
<TextView
android:id="@+id/category_sort"
android:padding="6dp"
android:layout_width="0dp"
android:layout_marginBottom="4dp"
android:layout_height="wrap_content"
app:layout_constraintWidth_default="wrap"
android:background="@drawable/square_ripple"
android:clickable="true"
android:layout_marginEnd="10dp"
android:drawableEnd="@drawable/ic_sort_white_24dp"
android:drawablePadding="6dp"
android:focusable="true"
android:gravity="start|center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
android:textColor="?android:attr/textColorPrimary"
android:textSize="12sp"
android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@id/update_button"
tools:text="Sort by: Recent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/update_button"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_marginTop="32dp"
android:layout_marginBottom="6dp"
android:clickable="true"
android:focusable="true"
android:text="@string/ext_update"
android:textColor="?android:attr/colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/category_title"
app:layout_constraintTop_toTopOf="parent"
app:rippleColor="@color/fullRippleColor" />
<ProgressBar
android:id="@+id/cat_progress"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="0dp"
app:layout_constraintBottom_toBottomOf="@+id/category_title"
app:layout_constraintStart_toEndOf="@+id/category_title"
app:layout_constraintTop_toTopOf="@+id/category_title" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -6,16 +6,12 @@
android:id="@+id/library_layout" android:id="@+id/library_layout"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout <FrameLayout
android:id="@+id/recycler_layout" android:id="@+id/recycler_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
</FrameLayout> </FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/pager_layout" android:id="@+id/pager_layout"