mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2025-01-11 14:29:08 +01:00
Cleaning out the old LibraryController
This commit is contained in:
parent
19c1192233
commit
836291a59a
@ -64,6 +64,9 @@ abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateContr
|
||||
super.onChangeStarted(handler, type)
|
||||
}
|
||||
|
||||
val onRoot: Boolean
|
||||
get() = router.backstack.lastOrNull()?.controller() == this
|
||||
|
||||
open fun handleRootBack(): Boolean = false
|
||||
|
||||
open fun getTitle(): String? {
|
||||
|
@ -283,7 +283,7 @@ class CatalogueController : NucleusController<CataloguePresenter>(),
|
||||
* @param inflater used to load the menu xml.
|
||||
*/
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
(activity as? MainActivity)?.setDismissIcon(showingExtenions)
|
||||
if (onRoot) (activity as? MainActivity)?.setDismissIcon(showingExtenions)
|
||||
if (showingExtenions) {
|
||||
// Inflate menu
|
||||
inflater.inflate(R.menu.extension_main, menu)
|
||||
|
@ -1,102 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.library
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.util.view.inflate
|
||||
import eu.kanade.tachiyomi.widget.RecyclerViewPagerAdapter
|
||||
|
||||
/**
|
||||
* This adapter stores the categories from the library, used with a ViewPager.
|
||||
*
|
||||
* @constructor creates an instance of the adapter.
|
||||
*/
|
||||
class LibraryAdapter(private val controller: LibraryController) : RecyclerViewPagerAdapter() {
|
||||
|
||||
/**
|
||||
* The categories to bind in the adapter.
|
||||
*/
|
||||
var categories: List<Category> = emptyList()
|
||||
// This setter helps to not refresh the adapter if the reference to the list doesn't change.
|
||||
set(value) {
|
||||
if (field !== value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private var boundViews = arrayListOf<View>()
|
||||
|
||||
/**
|
||||
* Creates a new view for this adapter.
|
||||
*
|
||||
* @return a new view.
|
||||
*/
|
||||
override fun createView(container: ViewGroup): View {
|
||||
val view = container.inflate(R.layout.library_category) as LibraryCategoryView
|
||||
view.onCreate(controller)
|
||||
return view
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a view with a position.
|
||||
*
|
||||
* @param view the view to bind.
|
||||
* @param position the position in the adapter.
|
||||
*/
|
||||
override fun bindView(view: View, position: Int) {
|
||||
(view as LibraryCategoryView).onBind(categories[position])
|
||||
boundViews.add(view)
|
||||
}
|
||||
|
||||
/**
|
||||
* Recycles a view.
|
||||
*
|
||||
* @param view the view to recycle.
|
||||
* @param position the position in the adapter.
|
||||
*/
|
||||
override fun recycleView(view: View, position: Int) {
|
||||
(view as LibraryCategoryView).onRecycle()
|
||||
boundViews.remove(view)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of categories.
|
||||
*
|
||||
* @return the number of categories or 0 if the list is null.
|
||||
*/
|
||||
override fun getCount(): Int {
|
||||
return categories.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title to display for a category.
|
||||
*
|
||||
* @param position the position of the element.
|
||||
* @return the title to display.
|
||||
*/
|
||||
override fun getPageTitle(position: Int): CharSequence {
|
||||
return categories[position].name
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the view.
|
||||
*/
|
||||
override fun getItemPosition(obj: Any): Int {
|
||||
val view = obj as? LibraryCategoryView ?: return POSITION_NONE
|
||||
val index = categories.indexOfFirst { it.id == view.category.id }
|
||||
return if (index == -1) POSITION_NONE else index
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the view of this adapter is being destroyed.
|
||||
*/
|
||||
fun onDestroy() {
|
||||
for (view in boundViews) {
|
||||
if (view is LibraryCategoryView) {
|
||||
view.unsubscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,403 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.library
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.SelectableAdapter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.util.lang.plusAssign
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import eu.kanade.tachiyomi.util.view.inflate
|
||||
import eu.kanade.tachiyomi.util.view.snack
|
||||
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
|
||||
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
||||
import kotlinx.android.synthetic.main.filter_bottom_sheet.*
|
||||
import kotlinx.android.synthetic.main.library_category.view.*
|
||||
import kotlinx.coroutines.delay
|
||||
import rx.subscriptions.CompositeSubscription
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
/**
|
||||
* Fragment containing the library manga for a certain category.
|
||||
*/
|
||||
class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
CoordinatorLayout(context, attrs),
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
FlexibleAdapter.OnItemLongClickListener,
|
||||
FlexibleAdapter.OnItemMoveListener,
|
||||
LibraryCategoryAdapter.LibraryListener {
|
||||
|
||||
/**
|
||||
* Preferences.
|
||||
*/
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
|
||||
/**
|
||||
* The fragment containing this view.
|
||||
*/
|
||||
private lateinit var controller: LibraryController
|
||||
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
/**
|
||||
* Category for this view.
|
||||
*/
|
||||
lateinit var category: Category
|
||||
private set
|
||||
|
||||
/**
|
||||
* Recycler view of the list of manga.
|
||||
*/
|
||||
private lateinit var recycler: RecyclerView
|
||||
|
||||
/**
|
||||
* Adapter to hold the manga in this category.
|
||||
*/
|
||||
private lateinit var adapter: LibraryCategoryAdapter
|
||||
|
||||
/**
|
||||
* Subscriptions while the view is bound.
|
||||
*/
|
||||
private var subscriptions = CompositeSubscription()
|
||||
|
||||
private var lastTouchUpY = 0f
|
||||
|
||||
private var lastClickPosition = -1
|
||||
|
||||
fun onCreate(controller: LibraryController) {
|
||||
this.controller = controller
|
||||
|
||||
adapter = LibraryCategoryAdapter(this)
|
||||
recycler = if (preferences.libraryLayout().getOrDefault() == 0) {
|
||||
(swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
}
|
||||
} else {
|
||||
(swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply {
|
||||
spanCount = controller.mangaPerRow
|
||||
}
|
||||
}
|
||||
|
||||
recycler.setHasFixedSize(true)
|
||||
recycler.adapter = adapter
|
||||
swipe_refresh.addView(recycler)
|
||||
adapter.fastScroller = fast_scroller
|
||||
|
||||
if (::category.isInitialized) {
|
||||
val mangaForCategory = controller.presenter.getMangaInCategory(category.id)
|
||||
if (mangaForCategory != null)
|
||||
adapter.setItems(mangaForCategory)
|
||||
}
|
||||
val config = resources?.configuration
|
||||
|
||||
val phoneLandscape = (config?.orientation == Configuration.ORIENTATION_LANDSCAPE &&
|
||||
(config.screenLayout.and(Configuration.SCREENLAYOUT_SIZE_MASK)) <
|
||||
Configuration.SCREENLAYOUT_SIZE_LARGE)
|
||||
// pad the recycler if the filter bottom sheet is visible
|
||||
if (!phoneLandscape) {
|
||||
val height = context.resources.getDimensionPixelSize(R.dimen.rounder_radius) + 5.dpToPx
|
||||
recycler.updatePaddingRelative(bottom = height)
|
||||
}
|
||||
|
||||
// Double the distance required to trigger sync
|
||||
swipe_refresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt())
|
||||
swipe_refresh.setOnRefreshListener {
|
||||
val inQueue = LibraryUpdateService.categoryInQueue(category.id)
|
||||
controller.snack?.dismiss()
|
||||
controller.snack = controller.view?.snack(
|
||||
resources.getString(
|
||||
when {
|
||||
inQueue -> R.string.category_already_in_queue
|
||||
LibraryUpdateService.isRunning() -> R.string.adding_category_to_queue
|
||||
else -> R.string.updating_category_x
|
||||
}, category.name)) {
|
||||
anchorView = controller.bottom_sheet
|
||||
}
|
||||
if (!inQueue)
|
||||
LibraryUpdateService.start(context, category)
|
||||
}
|
||||
}
|
||||
|
||||
fun onBind(category: Category) {
|
||||
this.category = category
|
||||
|
||||
adapter.mode = if (controller.selectedMangas.isNotEmpty()) {
|
||||
SelectableAdapter.Mode.MULTI
|
||||
} else {
|
||||
SelectableAdapter.Mode.SINGLE
|
||||
}
|
||||
adapter.isLongPressDragEnabled = canDrag()
|
||||
|
||||
subscriptions += controller.searchRelay
|
||||
.doOnNext { adapter.setFilter(it) }
|
||||
.skip(1)
|
||||
.subscribe { adapter.performFilter() }
|
||||
|
||||
subscriptions += controller.libraryMangaRelay
|
||||
.subscribe { onNextLibraryManga(it) }
|
||||
|
||||
subscriptions += controller.selectionRelay
|
||||
.subscribe { onSelectionChanged(it) }
|
||||
|
||||
subscriptions += controller.selectAllRelay
|
||||
.subscribe {
|
||||
if (it == category.id) {
|
||||
adapter.currentItems.forEach { item ->
|
||||
controller.setSelection((item as LibraryItem).manga, true)
|
||||
}
|
||||
controller.invalidateActionMode()
|
||||
}
|
||||
}
|
||||
|
||||
subscriptions += controller.reorganizeRelay
|
||||
.subscribe {
|
||||
if (it.first == category.id) {
|
||||
if (it.second in -2..-1) {
|
||||
val items = adapter.currentItems.toMutableList()
|
||||
val mangas = controller.selectedMangas
|
||||
val selectedManga = items.filter { item -> (item as LibraryItem).manga in
|
||||
mangas }
|
||||
items.removeAll(selectedManga)
|
||||
if (it.second == -1) items.addAll(0, selectedManga)
|
||||
else items.addAll(selectedManga)
|
||||
adapter.setItems(items.filterIsInstance<LibraryItem>())
|
||||
adapter.notifyDataSetChanged()
|
||||
saveDragSort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subscriptions += controller.stopRefreshRelay.subscribe {
|
||||
swipe_refresh?.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun canDrag(): Boolean {
|
||||
val sortingMode = preferences.librarySortingMode().getOrDefault()
|
||||
val filterOff = preferences.filterCompleted().getOrDefault() +
|
||||
preferences.filterTracked().getOrDefault() +
|
||||
preferences.filterUnread().getOrDefault() +
|
||||
preferences.filterMangaType().getOrDefault() +
|
||||
preferences.filterCompleted().getOrDefault() == 0 &&
|
||||
!preferences.hideCategories().getOrDefault()
|
||||
return sortingMode == LibrarySort.DRAG_AND_DROP && filterOff &&
|
||||
adapter.mode != SelectableAdapter.Mode.MULTI
|
||||
}
|
||||
|
||||
fun onRecycle() {
|
||||
adapter.setItems(emptyList())
|
||||
adapter.clearSelection()
|
||||
unsubscribe()
|
||||
}
|
||||
|
||||
fun unsubscribe() {
|
||||
subscriptions.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to [LibraryMangaEvent]. When an event is received, it updates the content of the
|
||||
* adapter.
|
||||
*
|
||||
* @param event the event received.
|
||||
*/
|
||||
private fun onNextLibraryManga(event: LibraryMangaEvent) {
|
||||
// Get the manga list for this category.
|
||||
adapter.isLongPressDragEnabled = canDrag()
|
||||
val mangaForCategory = event.getMangaForCategory(category).orEmpty()
|
||||
|
||||
adapter.setItems(mangaForCategory)
|
||||
|
||||
swipe_refresh.isEnabled = !preferences.hideCategories().getOrDefault()
|
||||
swipe_refresh.isRefreshing = LibraryUpdateService.categoryInQueue(category.id)
|
||||
|
||||
if (adapter.mode == SelectableAdapter.Mode.MULTI) {
|
||||
controller.selectedMangas.forEach { manga ->
|
||||
val position = adapter.indexOf(manga)
|
||||
if (position != -1 && !adapter.isSelected(position)) {
|
||||
adapter.toggleSelection(position)
|
||||
(recycler.findViewHolderForAdapterPosition(position) as? LibraryHolder)?.toggleActivation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to [LibrarySelectionEvent]. When an event is received, it updates the selection
|
||||
* depending on the type of event received.
|
||||
*
|
||||
* @param event the selection event received.
|
||||
*/
|
||||
private fun onSelectionChanged(event: LibrarySelectionEvent) {
|
||||
when (event) {
|
||||
is LibrarySelectionEvent.Selected -> {
|
||||
if (adapter.mode != SelectableAdapter.Mode.MULTI) {
|
||||
adapter.mode = SelectableAdapter.Mode.MULTI
|
||||
}
|
||||
launchUI {
|
||||
delay(100)
|
||||
adapter.isLongPressDragEnabled = false
|
||||
}
|
||||
findAndToggleSelection(event.manga)
|
||||
}
|
||||
is LibrarySelectionEvent.Unselected -> {
|
||||
findAndToggleSelection(event.manga)
|
||||
if (adapter.indexOf(event.manga) != -1) lastClickPosition = -1
|
||||
if (controller.selectedMangas.isEmpty()) {
|
||||
adapter.mode = SelectableAdapter.Mode.SINGLE
|
||||
adapter.isLongPressDragEnabled = canDrag()
|
||||
}
|
||||
}
|
||||
is LibrarySelectionEvent.Cleared -> {
|
||||
adapter.mode = SelectableAdapter.Mode.SINGLE
|
||||
adapter.clearSelection()
|
||||
adapter.notifyDataSetChanged()
|
||||
lastClickPosition = -1
|
||||
adapter.isLongPressDragEnabled = canDrag()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the selection for the given manga and updates the view if needed.
|
||||
*
|
||||
* @param manga the manga to toggle.
|
||||
*/
|
||||
private fun findAndToggleSelection(manga: Manga) {
|
||||
val position = adapter.indexOf(manga)
|
||||
if (position != -1) {
|
||||
adapter.toggleSelection(position)
|
||||
(recycler.findViewHolderForAdapterPosition(position) as? LibraryHolder)?.toggleActivation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a manga is clicked.
|
||||
*
|
||||
* @param position the position of the element clicked.
|
||||
* @return true if the item should be selected, false otherwise.
|
||||
*/
|
||||
override fun onItemClick(view: View?, position: Int): Boolean {
|
||||
// If the action mode is created and the position is valid, toggle the selection.
|
||||
val item = adapter.getItem(position) as? LibraryItem ?: return false
|
||||
return if (adapter.mode == SelectableAdapter.Mode.MULTI) {
|
||||
lastClickPosition = position
|
||||
toggleSelection(position)
|
||||
true
|
||||
} else {
|
||||
openManga(item.manga, lastTouchUpY)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
|
||||
when (ev?.action) {
|
||||
MotionEvent.ACTION_UP -> lastTouchUpY = ev.y
|
||||
}
|
||||
return super.dispatchTouchEvent(ev)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a manga is long clicked.
|
||||
*
|
||||
* @param position the position of the element clicked.
|
||||
*/
|
||||
override fun onItemLongClick(position: Int) {
|
||||
controller.createActionModeIfNeeded()
|
||||
when {
|
||||
lastClickPosition == -1 -> setSelection(position)
|
||||
lastClickPosition > position -> for (i in position until lastClickPosition)
|
||||
setSelection(i)
|
||||
lastClickPosition < position -> for (i in lastClickPosition + 1..position)
|
||||
setSelection(i)
|
||||
else -> setSelection(position)
|
||||
}
|
||||
lastClickPosition = position
|
||||
}
|
||||
|
||||
override fun onItemMove(fromPosition: Int, toPosition: Int) { }
|
||||
|
||||
override fun onItemReleased(position: Int) {
|
||||
if (adapter.selectedItemCount == 0) saveDragSort()
|
||||
}
|
||||
|
||||
override fun startReading(position: Int) {
|
||||
val manga = (adapter.getItem(position) as? LibraryItem)?.manga ?: return
|
||||
if (adapter.mode == SelectableAdapter.Mode.MULTI) toggleSelection(position)
|
||||
else controller.startReading(manga)
|
||||
}
|
||||
|
||||
private fun saveDragSort() {
|
||||
val mangaIds = adapter.currentItems.mapNotNull { (it as? LibraryItem)?.manga?.id }
|
||||
category.mangaSort = null
|
||||
category.mangaOrder = mangaIds
|
||||
if (category.id == 0)
|
||||
preferences.defaultMangaOrder().set(mangaIds.joinToString("/"))
|
||||
else
|
||||
db.insertCategory(category).asRxObservable().subscribe()
|
||||
controller.onCatSortChanged(category.id)
|
||||
}
|
||||
override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean {
|
||||
if (adapter.selectedItemCount > 1)
|
||||
return false
|
||||
if (adapter.isSelected(fromPosition))
|
||||
toggleSelection(fromPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActionStateChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||
val position = viewHolder?.adapterPosition ?: return
|
||||
if (actionState == 2) onItemLongClick(position)
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a manga.
|
||||
*
|
||||
* @param manga the manga to open.
|
||||
*/
|
||||
private fun openManga(manga: Manga, startY: Float?) {
|
||||
controller.openManga(manga, startY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the presenter to toggle the selection for the given position.
|
||||
*
|
||||
* @param position the position to toggle.
|
||||
*/
|
||||
private fun toggleSelection(position: Int) {
|
||||
val item = adapter.getItem(position) ?: return
|
||||
|
||||
controller.setSelection((item as LibraryItem).manga, !adapter.isSelected(position))
|
||||
controller.invalidateActionMode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the presenter to set the selection for the given position.
|
||||
*
|
||||
* @param position the position to toggle.
|
||||
*/
|
||||
private fun setSelection(position: Int) {
|
||||
val item = adapter.getItem(position) ?: return
|
||||
|
||||
controller.setSelection((item as LibraryItem).manga, true)
|
||||
controller.invalidateActionMode()
|
||||
}
|
||||
|
||||
// unused for this view
|
||||
override fun updateCategory(catId: Int): Boolean = true
|
||||
override fun sortCategory(catId: Int, sortBy: Int) { }
|
||||
override fun selectAll(position: Int) { }
|
||||
override fun allSelected(position: Int): Boolean = false
|
||||
override fun recyclerIsScrolling() = false
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,829 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.library
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.Activity
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.checkbox.checkBoxPrompt
|
||||
import com.afollestad.materialdialogs.checkbox.isCheckPromptChecked
|
||||
import com.afollestad.materialdialogs.list.listItemsSingleChoice
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.SelectableAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.ui.main.OnTouchEventInterface
|
||||
import eu.kanade.tachiyomi.ui.main.SpinnerTitleInterface
|
||||
import eu.kanade.tachiyomi.ui.main.SwipeGestureInterface
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
||||
import eu.kanade.tachiyomi.util.view.snack
|
||||
import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
||||
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
|
||||
import kotlinx.android.synthetic.main.filter_bottom_sheet.*
|
||||
import kotlinx.android.synthetic.main.library_grid_recycler.*
|
||||
import kotlinx.android.synthetic.main.library_list_controller.*
|
||||
import kotlinx.android.synthetic.main.main_activity.*
|
||||
import kotlinx.coroutines.delay
|
||||
import java.util.Locale
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.sign
|
||||
|
||||
class
|
||||
LibraryListController(bundle: Bundle? = null) : LibraryController(bundle),
|
||||
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener,
|
||||
FlexibleAdapter.OnItemMoveListener, LibraryCategoryAdapter.LibraryListener,
|
||||
SpinnerTitleInterface, OnTouchEventInterface, SwipeGestureInterface {
|
||||
|
||||
private lateinit var adapter: LibraryCategoryAdapter
|
||||
|
||||
private var lastClickPosition = -1
|
||||
|
||||
private var updateScroll = true
|
||||
|
||||
private var lastItemPosition: Int? = null
|
||||
private var lastItem: IFlexible<*>? = null
|
||||
|
||||
private var switchingCategories = false
|
||||
var scrollDistance = 0f
|
||||
|
||||
private var startPosX: Float? = null
|
||||
private var startPosY: Float? = null
|
||||
private var moved = false
|
||||
private var lockedRecycler = false
|
||||
private var lockedY = false
|
||||
private var nextCategory: Int? = null
|
||||
private var ogCategory: Int? = null
|
||||
private var prevCategory: Int? = null
|
||||
private val swipeDistance = 500f
|
||||
private var flinging = false
|
||||
private var isDragging = false
|
||||
private val scrollDistanceTilHidden = 1000.dpToPx
|
||||
|
||||
override fun contentView(): View = recycler_layout
|
||||
|
||||
override fun getTitle(): String? {
|
||||
return if (view != null && presenter.categories.size > 1) presenter.categories.find {
|
||||
it.order == activeCategory
|
||||
}?.name ?: super.getTitle()
|
||||
else super.getTitle()
|
||||
}
|
||||
|
||||
private var scrollListener = object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
val order = getCategoryOrder()
|
||||
if (bottom_sheet.canHide()) {
|
||||
scrollDistance += abs(dy)
|
||||
if (scrollDistance > scrollDistanceTilHidden) {
|
||||
bottom_sheet.hideIfPossible()
|
||||
scrollDistance = 0f
|
||||
}
|
||||
} else scrollDistance = 0f
|
||||
if (order != null && order != activeCategory) {
|
||||
preferences.lastUsedCategory().set(order)
|
||||
activeCategory = order
|
||||
setTitle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
// pad the recycler if the filter bottom sheet is visible
|
||||
val height = view.context.resources.getDimensionPixelSize(R.dimen.rounder_radius) + 4.dpToPx
|
||||
recycler.updatePaddingRelative(bottom = height)
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent?) {
|
||||
if (event == null) {
|
||||
resetScrollingValues()
|
||||
resetRecyclerY()
|
||||
return
|
||||
}
|
||||
if (flinging || presenter.categories.size <= 1) return
|
||||
if (isDragging) {
|
||||
resetScrollingValues()
|
||||
resetRecyclerY(false)
|
||||
return
|
||||
}
|
||||
val sheetRect = Rect()
|
||||
val recyclerRect = Rect()
|
||||
val appBarRect = Rect()
|
||||
bottom_sheet.getGlobalVisibleRect(sheetRect)
|
||||
view?.getGlobalVisibleRect(recyclerRect)
|
||||
activity?.appbar?.getGlobalVisibleRect(appBarRect)
|
||||
|
||||
if (startPosX == null) {
|
||||
startPosX = event.rawX
|
||||
startPosY = event.rawY
|
||||
val position =
|
||||
(recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
||||
val order = activeCategory
|
||||
ogCategory = order
|
||||
var newOffsetN = order + 1
|
||||
while (adapter.indexOf(newOffsetN) == -1 && presenter.categories.any { it.order == newOffsetN }) {
|
||||
newOffsetN += 1
|
||||
}
|
||||
if (adapter.indexOf(newOffsetN) != -1) nextCategory = newOffsetN
|
||||
|
||||
if (position == 0) prevCategory = null
|
||||
else {
|
||||
var newOffsetP = order - 1
|
||||
while (adapter.indexOf(newOffsetP) == -1 && presenter.categories.any { it.order == newOffsetP }) {
|
||||
newOffsetP -= 1
|
||||
}
|
||||
if (adapter.indexOf(newOffsetP) != -1) prevCategory = newOffsetP
|
||||
}
|
||||
return
|
||||
}
|
||||
if (event.actionMasked == MotionEvent.ACTION_UP) {
|
||||
recycler_layout.post {
|
||||
if (!flinging) {
|
||||
resetScrollingValues()
|
||||
resetRecyclerY(true)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if (startPosX != null && startPosY != null && (sheetRect.contains(
|
||||
startPosX!!.toInt(),
|
||||
startPosY!!.toInt()
|
||||
) || !recyclerRect.contains(
|
||||
startPosX!!.toInt(),
|
||||
startPosY!!.toInt()
|
||||
) || appBarRect.contains(startPosX!!.toInt(), startPosY!!.toInt()))
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (event.actionMasked != MotionEvent.ACTION_UP && startPosX != null) {
|
||||
val distance = abs(event.rawX - startPosX!!)
|
||||
val sign = sign(event.rawX - startPosX!!)
|
||||
|
||||
if (lockedY) return
|
||||
|
||||
if (distance > 60 && abs(event.rawY - startPosY!!) <= 30 && !lockedRecycler) {
|
||||
swipe_refresh.isEnabled = false
|
||||
lockedRecycler = true
|
||||
switchingCategories = true
|
||||
recycler.suppressLayout(true)
|
||||
} else if (!lockedRecycler && abs(event.rawY - startPosY!!) > 30) {
|
||||
lockedY = true
|
||||
resetRecyclerY()
|
||||
return
|
||||
}
|
||||
if (abs(event.rawY - startPosY!!) <= 30 || recycler.isLayoutSuppressed || lockedRecycler) {
|
||||
|
||||
if ((prevCategory == null && sign > 0) || (nextCategory == null && sign < 0)) {
|
||||
recycler_layout.x = sign * distance.pow(0.6f)
|
||||
recycler_layout.alpha = 1f
|
||||
} else if (distance <= swipeDistance * 1.1f) {
|
||||
recycler_layout.x = sign * (distance / (swipeDistance / 3f)).pow(3.5f)
|
||||
recycler_layout.alpha =
|
||||
(1f - (distance - (swipeDistance * 0.1f)) / swipeDistance)
|
||||
if (moved) {
|
||||
scrollToHeader(ogCategory ?: -1)
|
||||
moved = false
|
||||
}
|
||||
} else {
|
||||
if (!moved) {
|
||||
scrollToHeader((if (sign <= 0) nextCategory else prevCategory) ?: -1)
|
||||
moved = true
|
||||
}
|
||||
recycler_layout.x = -sign * (max(0f, (swipeDistance * 2 - distance)) /
|
||||
(swipeDistance / 3f)).pow(3.5f)
|
||||
recycler_layout.alpha = ((distance - swipeDistance * 1.1f) / swipeDistance)
|
||||
recycler_layout.alpha = min(1f, recycler_layout.alpha)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCategoryOrder(): Int? {
|
||||
val position =
|
||||
(recycler.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
|
||||
var order = when (val item = adapter.getItem(position)) {
|
||||
is LibraryHeaderItem -> item.category.order
|
||||
is LibraryItem -> presenter.categories.find { it.id == item.manga.category }?.order
|
||||
else -> null
|
||||
}
|
||||
if (order == null) {
|
||||
val fPosition =
|
||||
(recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
||||
order = when (val item = adapter.getItem(fPosition)) {
|
||||
is LibraryHeaderItem -> item.category.order
|
||||
is LibraryItem -> presenter.categories.find { it.id == item.manga.category }?.order
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
return order
|
||||
}
|
||||
|
||||
private fun resetScrollingValues() {
|
||||
swipe_refresh.isEnabled = true
|
||||
startPosX = null
|
||||
startPosY = null
|
||||
nextCategory = null
|
||||
prevCategory = null
|
||||
ogCategory = null
|
||||
lockedY = false
|
||||
}
|
||||
|
||||
private fun resetRecyclerY(animated: Boolean = false, time: Long = 100) {
|
||||
swipe_refresh.isEnabled = true
|
||||
moved = false
|
||||
lockedRecycler = false
|
||||
if (animated) {
|
||||
val set = AnimatorSet()
|
||||
val translationXAnimator = ValueAnimator.ofFloat(recycler_layout.x, 0f)
|
||||
translationXAnimator.duration = time
|
||||
translationXAnimator.addUpdateListener { animation ->
|
||||
recycler_layout.x = animation.animatedValue as Float
|
||||
}
|
||||
|
||||
val translationAlphaAnimator = ValueAnimator.ofFloat(recycler_layout.alpha, 1f)
|
||||
translationAlphaAnimator.duration = time
|
||||
translationAlphaAnimator.addUpdateListener { animation ->
|
||||
recycler_layout.alpha = animation.animatedValue as Float
|
||||
}
|
||||
set.playTogether(translationXAnimator, translationAlphaAnimator)
|
||||
set.start()
|
||||
|
||||
launchUI {
|
||||
delay(time)
|
||||
if (!lockedRecycler) switchingCategories = false
|
||||
}
|
||||
} else {
|
||||
recycler_layout.x = 0f
|
||||
recycler_layout.alpha = 1f
|
||||
switchingCategories = false
|
||||
}
|
||||
recycler.suppressLayout(false)
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
return inflater.inflate(R.layout.library_list_controller, container, false)
|
||||
}
|
||||
|
||||
override fun layoutView(view: View) {
|
||||
adapter = LibraryCategoryAdapter(this)
|
||||
setRecyclerLayout()
|
||||
recycler.manager.spanSizeLookup = (object : GridLayoutManager.SpanSizeLookup() {
|
||||
override fun getSpanSize(position: Int): Int {
|
||||
if (libraryLayout == 0) return 1
|
||||
val item = this@LibraryListController.adapter.getItem(position)
|
||||
return if (item is LibraryHeaderItem) recycler.manager.spanCount
|
||||
else if (item is LibraryItem && item.manga.isBlank()) recycler.manager.spanCount
|
||||
else 1
|
||||
}
|
||||
})
|
||||
recycler.setHasFixedSize(true)
|
||||
recycler.adapter = adapter
|
||||
adapter.fastScroller = fast_scroller
|
||||
recycler.addOnScrollListener(scrollListener)
|
||||
|
||||
val tv = TypedValue()
|
||||
activity!!.theme.resolveAttribute(R.attr.actionBarTintColor, tv, true)
|
||||
|
||||
scrollViewWith(recycler, swipeRefreshLayout = swipe_refresh) { insets ->
|
||||
fast_scroller.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = insets.systemWindowInsetTop
|
||||
}
|
||||
}
|
||||
|
||||
swipe_refresh.setOnRefreshListener {
|
||||
swipe_refresh.isRefreshing = false
|
||||
if (!LibraryUpdateService.isRunning()) {
|
||||
when {
|
||||
presenter.allCategories.size <= 1 -> updateLibrary()
|
||||
preferences.updateOnRefresh().getOrDefault() == -1 -> {
|
||||
MaterialDialog(activity!!).title(R.string.what_should_update)
|
||||
.negativeButton(android.R.string.cancel)
|
||||
.listItemsSingleChoice(items = listOf(
|
||||
view.context.getString(
|
||||
R.string.top_category, presenter.allCategories.first().name
|
||||
), view.context.getString(
|
||||
R.string.categories_in_global_update
|
||||
)
|
||||
), selection = { _, index, _ ->
|
||||
preferences.updateOnRefresh().set(index)
|
||||
when (index) {
|
||||
0 -> updateLibrary(presenter.allCategories.first())
|
||||
else -> updateLibrary()
|
||||
}
|
||||
})
|
||||
.positiveButton(R.string.action_update)
|
||||
.show()
|
||||
}
|
||||
else -> {
|
||||
when (preferences.updateOnRefresh().getOrDefault()) {
|
||||
0 -> updateLibrary(presenter.allCategories.first())
|
||||
else -> updateLibrary()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLibrary(category: Category? = null) {
|
||||
val view = view ?: return
|
||||
LibraryUpdateService.start(view.context, category)
|
||||
snack = view.snack(R.string.updating_library) {
|
||||
anchorView = bottom_sheet
|
||||
}
|
||||
}
|
||||
|
||||
private fun setRecyclerLayout() {
|
||||
if (libraryLayout == 0) {
|
||||
recycler.spanCount = 1
|
||||
recycler.updatePaddingRelative(start = 0, end = 0)
|
||||
} else {
|
||||
recycler.columnWidth = (90 + (preferences.gridSize().getOrDefault() * 30)).dpToPx
|
||||
recycler.updatePaddingRelative(start = 5.dpToPx, end = 5.dpToPx)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||
super.onChangeStarted(handler, type)
|
||||
if (type.isEnter) {
|
||||
if (presenter.categories.size > 1) {
|
||||
activity?.toolbar?.showSpinner()
|
||||
} else {
|
||||
activity?.toolbar?.removeSpinner()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
super.onActivityResumed(activity)
|
||||
if (view == null) return
|
||||
resetScrollingValues()
|
||||
resetRecyclerY()
|
||||
}
|
||||
|
||||
override fun onNextLibraryUpdate(mangaMap: List<LibraryItem>, freshStart: Boolean) {
|
||||
val recyclerLayout = view ?: return
|
||||
destroyActionModeIfNeeded()
|
||||
if (mangaMap.isNotEmpty()) {
|
||||
empty_view?.hide()
|
||||
} else {
|
||||
empty_view?.show(
|
||||
R.drawable.ic_book_black_128dp,
|
||||
if (bottom_sheet.hasActiveFilters()) R.string.information_empty_library_filtered
|
||||
else R.string.information_empty_library
|
||||
)
|
||||
}
|
||||
adapter.setItems(mangaMap)
|
||||
|
||||
val categoryNames = presenter.categories.map { it.name }.toTypedArray()
|
||||
|
||||
val isCurrentController = router?.backstack?.lastOrNull()?.controller() == this
|
||||
|
||||
setTitle()
|
||||
updateScroll = false
|
||||
if (!freshStart) {
|
||||
justStarted = false
|
||||
if (contentView().alpha == 0f) contentView().animate().alpha(1f).setDuration(500)
|
||||
.start()
|
||||
} else if (justStarted) {
|
||||
if (freshStart) scrollToHeader(activeCategory)
|
||||
} else {
|
||||
updateScroll = true
|
||||
}
|
||||
adapter.isLongPressDragEnabled = canDrag()
|
||||
|
||||
val popupMenu = if (presenter.categories.size > 1 && isCurrentController) {
|
||||
activity?.toolbar?.showSpinner()
|
||||
} else {
|
||||
activity?.toolbar?.removeSpinner()
|
||||
null
|
||||
}
|
||||
|
||||
presenter.categories.forEach { category ->
|
||||
popupMenu?.menu?.add(0, category.order, max(0, category.order), category.name)
|
||||
}
|
||||
|
||||
popupMenu?.setOnMenuItemClickListener { item ->
|
||||
scrollToHeader(item.itemId)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun scrollToHeader(pos: Int) {
|
||||
val headerPosition = adapter.indexOf(pos)
|
||||
switchingCategories = true
|
||||
if (headerPosition > -1) {
|
||||
val appbar = activity?.appbar
|
||||
recycler.suppressLayout(true)
|
||||
val appbarOffset = if (appbar?.y ?: 0f > -20) 0 else (appbar?.y?.plus(
|
||||
view?.rootWindowInsets?.systemWindowInsetTop ?: 0
|
||||
) ?: 0f).roundToInt() + 30.dpToPx
|
||||
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
|
||||
headerPosition, (if (headerPosition == 0) 0 else (-40).dpToPx) + appbarOffset
|
||||
)
|
||||
|
||||
/*val headerItem = adapter.getItem(headerPosition) as? LibraryHeaderItem
|
||||
if (headerItem != null) {
|
||||
setTitle()
|
||||
}*/
|
||||
recycler.suppressLayout(false)
|
||||
}
|
||||
launchUI {
|
||||
delay(100)
|
||||
switchingCategories = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun reattachAdapter() {
|
||||
libraryLayout = preferences.libraryLayout().getOrDefault()
|
||||
setRecyclerLayout()
|
||||
val position =
|
||||
(recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
||||
recycler.adapter = adapter
|
||||
|
||||
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(position, 0)
|
||||
}
|
||||
|
||||
override fun onSearch(query: String?): Boolean {
|
||||
this.query = query ?: ""
|
||||
adapter.setFilter(query)
|
||||
adapter.performFilter()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
super.onDestroyActionMode(mode)
|
||||
adapter.mode = SelectableAdapter.Mode.SINGLE
|
||||
adapter.clearSelection()
|
||||
adapter.notifyDataSetChanged()
|
||||
lastClickPosition = -1
|
||||
adapter.isLongPressDragEnabled = canDrag()
|
||||
}
|
||||
|
||||
override fun setSelection(manga: Manga, selected: Boolean) {
|
||||
val currentMode = adapter.mode
|
||||
if (selected) {
|
||||
if (selectedMangas.add(manga)) {
|
||||
val positions = adapter.allIndexOf(manga)
|
||||
if (adapter.mode != SelectableAdapter.Mode.MULTI) {
|
||||
adapter.mode = SelectableAdapter.Mode.MULTI
|
||||
}
|
||||
launchUI {
|
||||
delay(100)
|
||||
adapter.isLongPressDragEnabled = false
|
||||
}
|
||||
positions.forEach { position ->
|
||||
adapter.addSelection(position)
|
||||
(recycler.findViewHolderForAdapterPosition(position) as? LibraryHolder)?.toggleActivation()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (selectedMangas.remove(manga)) {
|
||||
val positions = adapter.allIndexOf(manga)
|
||||
lastClickPosition = -1
|
||||
if (selectedMangas.isEmpty()) {
|
||||
adapter.mode = SelectableAdapter.Mode.SINGLE
|
||||
adapter.isLongPressDragEnabled = canDrag()
|
||||
}
|
||||
positions.forEach { position ->
|
||||
adapter.removeSelection(position)
|
||||
(recycler.findViewHolderForAdapterPosition(position) as? LibraryHolder)?.toggleActivation()
|
||||
}
|
||||
}
|
||||
}
|
||||
updateHeaders(currentMode != adapter.mode)
|
||||
}
|
||||
|
||||
private fun updateHeaders(changedMode: Boolean = false) {
|
||||
val headerPositions = adapter.getHeaderPositions()
|
||||
headerPositions.forEach {
|
||||
if (changedMode) {
|
||||
adapter.notifyItemChanged(it)
|
||||
} else {
|
||||
(recycler.findViewHolderForAdapterPosition(it) as? LibraryHeaderItem.Holder)?.setSelection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun startReading(position: Int) {
|
||||
if (recyclerIsScrolling()) return
|
||||
if (adapter.mode == SelectableAdapter.Mode.MULTI) {
|
||||
toggleSelection(position)
|
||||
return
|
||||
}
|
||||
val manga = (adapter.getItem(position) as? LibraryItem)?.manga ?: return
|
||||
startReading(manga)
|
||||
}
|
||||
|
||||
private fun toggleSelection(position: Int) {
|
||||
val item = adapter.getItem(position) as? LibraryItem ?: return
|
||||
if (item.manga.isBlank()) return
|
||||
setSelection(item.manga, !adapter.isSelected(position))
|
||||
invalidateActionMode()
|
||||
}
|
||||
|
||||
override fun canDrag(): Boolean {
|
||||
val filterOff =
|
||||
!bottom_sheet.hasActiveFilters() && !preferences.hideCategories().getOrDefault()
|
||||
return filterOff && adapter.mode != SelectableAdapter.Mode.MULTI
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a manga is clicked.
|
||||
*
|
||||
* @param position the position of the element clicked.
|
||||
* @return true if the item should be selected, false otherwise.
|
||||
*/
|
||||
override fun onItemClick(view: View?, position: Int): Boolean {
|
||||
if (recyclerIsScrolling()) return false
|
||||
val item = adapter.getItem(position) as? LibraryItem ?: return false
|
||||
return if (adapter.mode == SelectableAdapter.Mode.MULTI) {
|
||||
lastClickPosition = position
|
||||
toggleSelection(position)
|
||||
false
|
||||
} else {
|
||||
openManga(item.manga, null)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a manga is long clicked.
|
||||
*
|
||||
* @param position the position of the element clicked.
|
||||
*/
|
||||
override fun onItemLongClick(position: Int) {
|
||||
if (recyclerIsScrolling()) return
|
||||
if (adapter.getItem(position) is LibraryHeaderItem) return
|
||||
createActionModeIfNeeded()
|
||||
when {
|
||||
lastClickPosition == -1 -> setSelection(position)
|
||||
lastClickPosition > position -> for (i in position until lastClickPosition) setSelection(
|
||||
i
|
||||
)
|
||||
lastClickPosition < position -> for (i in lastClickPosition + 1..position) setSelection(
|
||||
i
|
||||
)
|
||||
else -> setSelection(position)
|
||||
}
|
||||
lastClickPosition = position
|
||||
}
|
||||
|
||||
override fun onActionStateChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||
val position = viewHolder?.adapterPosition ?: return
|
||||
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
|
||||
isDragging = true
|
||||
activity?.appbar?.y = 0f
|
||||
if (lastItemPosition != null && position != lastItemPosition && lastItem == adapter.getItem(
|
||||
position
|
||||
)
|
||||
) {
|
||||
// because for whatever reason you can repeatedly tap on a currently dragging manga
|
||||
adapter.removeSelection(position)
|
||||
(recycler.findViewHolderForAdapterPosition(position) as? LibraryHolder)?.toggleActivation()
|
||||
adapter.moveItem(position, lastItemPosition!!)
|
||||
} else {
|
||||
lastItem = adapter.getItem(position)
|
||||
lastItemPosition = position
|
||||
onItemLongClick(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUpdateManga(manga: LibraryManga) {
|
||||
if (manga.id == null) adapter.notifyDataSetChanged()
|
||||
else super.onUpdateManga(manga)
|
||||
}
|
||||
|
||||
private fun setSelection(position: Int, selected: Boolean = true) {
|
||||
val item = adapter.getItem(position) as? LibraryItem ?: return
|
||||
|
||||
setSelection(item.manga, selected)
|
||||
invalidateActionMode()
|
||||
}
|
||||
|
||||
override fun onItemMove(fromPosition: Int, toPosition: Int) {
|
||||
// Because padding a recycler causes it to scroll up we have to scroll it back down... wild
|
||||
if ((adapter.getItem(fromPosition) is LibraryItem && adapter.getItem(fromPosition) is LibraryItem) || adapter.getItem(
|
||||
fromPosition
|
||||
) == null
|
||||
) recycler.scrollBy(0, recycler.paddingTop)
|
||||
activity?.appbar?.y = 0f
|
||||
if (lastItemPosition == toPosition) lastItemPosition = null
|
||||
else if (lastItemPosition == null) lastItemPosition = fromPosition
|
||||
}
|
||||
|
||||
override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean {
|
||||
if (adapter.isSelected(fromPosition)) toggleSelection(fromPosition)
|
||||
val item = adapter.getItem(fromPosition) as? LibraryItem ?: return false
|
||||
val newHeader = adapter.getSectionHeader(toPosition) as? LibraryHeaderItem
|
||||
if (toPosition <= 1) return false
|
||||
return (adapter.getItem(toPosition) !is LibraryHeaderItem) && (newHeader?.category?.id == item.manga.category || !presenter.mangaIsInCategory(
|
||||
item.manga,
|
||||
newHeader?.category?.id
|
||||
))
|
||||
}
|
||||
|
||||
override fun onItemReleased(position: Int) {
|
||||
isDragging = false
|
||||
if (adapter.selectedItemCount > 0) {
|
||||
lastItemPosition = null
|
||||
return
|
||||
}
|
||||
destroyActionModeIfNeeded()
|
||||
// if nothing moved
|
||||
if (lastItemPosition == null) 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 {
|
||||
if (presenter.mangaIsInCategory(item.manga, newHeader?.category?.id)) {
|
||||
adapter.moveItem(position, lastItemPosition!!)
|
||||
snack = view?.snack(R.string.already_in_category) {
|
||||
anchorView = bottom_sheet
|
||||
}
|
||||
return
|
||||
}
|
||||
if (newHeader?.category?.mangaSort == null) {
|
||||
moveMangaToCategory(item.manga, newHeader?.category, mangaIds, true)
|
||||
} else {
|
||||
val keepCatSort = preferences.keepCatSort().getOrDefault()
|
||||
if (keepCatSort == 0) {
|
||||
MaterialDialog(activity!!).message(R.string.switch_to_dnd)
|
||||
.positiveButton(R.string.action_switch) {
|
||||
moveMangaToCategory(
|
||||
item.manga, newHeader.category, mangaIds, true
|
||||
)
|
||||
if (it.isCheckPromptChecked()) preferences.keepCatSort().set(2)
|
||||
}.checkBoxPrompt(R.string.remember_choice) {}.negativeButton(
|
||||
text = resources?.getString(
|
||||
R.string.keep_current_sort,
|
||||
resources!!.getString(newHeader.category.sortRes()).toLowerCase(
|
||||
Locale.getDefault()
|
||||
)
|
||||
)
|
||||
) {
|
||||
moveMangaToCategory(
|
||||
item.manga, newHeader.category, mangaIds, false
|
||||
)
|
||||
if (it.isCheckPromptChecked()) preferences.keepCatSort().set(1)
|
||||
}.cancelOnTouchOutside(false).show()
|
||||
} else {
|
||||
moveMangaToCategory(
|
||||
item.manga, newHeader.category, mangaIds, keepCatSort == 2
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
lastItemPosition = null
|
||||
}
|
||||
|
||||
private fun moveMangaToCategory(
|
||||
manga: LibraryManga,
|
||||
category: Category?,
|
||||
mangaIds: List<Long>,
|
||||
useDND: Boolean
|
||||
) {
|
||||
if (category?.id == null) return
|
||||
val oldCatId = manga.category
|
||||
presenter.moveMangaToCategory(manga, category.id, mangaIds, useDND)
|
||||
snack?.dismiss()
|
||||
snack = view?.snack(
|
||||
resources!!.getString(R.string.moved_to_category, category.name)
|
||||
) {
|
||||
anchorView = bottom_sheet
|
||||
setAction(R.string.action_undo) {
|
||||
manga.category = category.id!!
|
||||
presenter.moveMangaToCategory(manga, oldCatId, mangaIds, useDND)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = view?.snack(
|
||||
resources!!.getString(
|
||||
when {
|
||||
inQueue -> R.string.category_already_in_queue
|
||||
LibraryUpdateService.isRunning() -> R.string.adding_category_to_queue
|
||||
else -> R.string.updating_category_x
|
||||
}, category.name
|
||||
), Snackbar.LENGTH_LONG
|
||||
) {
|
||||
anchorView = bottom_sheet
|
||||
}
|
||||
if (!inQueue) LibraryUpdateService.start(view!!.context, category)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun sortCategory(catId: Int, sortBy: Int) {
|
||||
presenter.sortCategory(catId, sortBy)
|
||||
}
|
||||
|
||||
override fun selectAll(position: Int) {
|
||||
val header = adapter.getSectionHeader(position) ?: return
|
||||
val items = adapter.getSectionItemPositions(header)
|
||||
val allSelected = allSelected(position)
|
||||
for (i in items) setSelection(i, !allSelected)
|
||||
}
|
||||
|
||||
override fun allSelected(position: Int): Boolean {
|
||||
val header = adapter.getSectionHeader(position) ?: return false
|
||||
val items = adapter.getSectionItemPositions(header)
|
||||
return items.all { adapter.isSelected(it) }
|
||||
}
|
||||
|
||||
override fun onSwipeBottom(x: Float, y: Float) {}
|
||||
override fun onSwipeTop(x: Float, y: Float) {
|
||||
val sheetRect = Rect()
|
||||
activity!!.bottom_nav.getGlobalVisibleRect(sheetRect)
|
||||
if (sheetRect.contains(x.toInt(), y.toInt())) {
|
||||
if (bottom_sheet.sheetBehavior?.state != BottomSheetBehavior.STATE_EXPANDED) toggleFilters()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSwipeLeft(x: Float, xPos: Float) = goToNextCategory(x, xPos)
|
||||
override fun onSwipeRight(x: Float, xPos: Float) = goToNextCategory(x, xPos)
|
||||
|
||||
private fun goToNextCategory(x: Float, xPos: Float) {
|
||||
if (lockedRecycler && abs(x) > 1000f) {
|
||||
val sign = sign(x).roundToInt()
|
||||
if ((sign < 0 && nextCategory == null) || (sign > 0) && prevCategory == null) return
|
||||
val distance = recycler_layout.alpha
|
||||
val speed = max(5000f / abs(x), 0.75f)
|
||||
if (sign(recycler_layout.x) == sign(x)) {
|
||||
flinging = true
|
||||
val duration = (distance * 100 * speed).toLong()
|
||||
val set = AnimatorSet()
|
||||
val translationXAnimator = ValueAnimator.ofFloat(abs(xPos - startPosX!!),
|
||||
swipeDistance)
|
||||
translationXAnimator.duration = duration
|
||||
translationXAnimator.addUpdateListener { animation ->
|
||||
recycler_layout.x = sign *
|
||||
(animation.animatedValue as Float / (swipeDistance / 3f)).pow(3.5f)
|
||||
}
|
||||
|
||||
val translationAlphaAnimator = ValueAnimator.ofFloat(recycler_layout.alpha, 0f)
|
||||
translationAlphaAnimator.duration = duration
|
||||
translationAlphaAnimator.addUpdateListener { animation ->
|
||||
recycler_layout.alpha = animation.animatedValue as Float
|
||||
}
|
||||
set.playTogether(translationXAnimator, translationAlphaAnimator)
|
||||
set.start()
|
||||
set.addListener(object : Animator.AnimatorListener {
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
recycler_layout.x = -sign * (swipeDistance / (swipeDistance / 3f)).pow(3.5f)
|
||||
recycler_layout.alpha = 0f
|
||||
recycler_layout.post {
|
||||
scrollToHeader((if (sign <= 0) nextCategory else prevCategory) ?: -1)
|
||||
recycler_layout.post {
|
||||
resetScrollingValues()
|
||||
resetRecyclerY(true, (100 * speed).toLong())
|
||||
flinging = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator?) {}
|
||||
override fun onAnimationRepeat(animation: Animator?) {}
|
||||
override fun onAnimationStart(animation: Animator?) {}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun recyclerIsScrolling() = switchingCategories || lockedRecycler || lockedY
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.library
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
|
||||
class LibraryMangaEvent(val mangas: Map<Int, List<LibraryItem>>) {
|
||||
|
||||
fun getMangaForCategory(category: Category): List<LibraryItem>? {
|
||||
return mangas[category.id]
|
||||
}
|
||||
}
|
@ -566,9 +566,6 @@ class LibraryPresenter(
|
||||
freshStart: Boolean =
|
||||
false
|
||||
) {
|
||||
if (view !is LibraryListController) {
|
||||
view.onNextLibraryUpdate(categories, mangaMap, freshStart)
|
||||
} else {
|
||||
val mangaList = withContext(Dispatchers.IO) {
|
||||
val list = mutableListOf<LibraryItem>()
|
||||
for (element in mangaMap.toSortedMap(compareBy { entry ->
|
||||
@ -580,7 +577,6 @@ class LibraryPresenter(
|
||||
}
|
||||
view.onNextLibraryUpdate(mangaList, freshStart)
|
||||
}
|
||||
}
|
||||
|
||||
fun getList(): List<LibraryItem> {
|
||||
val list = mutableListOf<LibraryItem>()
|
||||
@ -594,11 +590,6 @@ class LibraryPresenter(
|
||||
|
||||
fun updateViewBlocking() {
|
||||
val mangaMap = currentMangaMap ?: return
|
||||
if (view !is LibraryListController) {
|
||||
if (mangaMap.values.firstOrNull()?.firstOrNull()?.header != null)
|
||||
return
|
||||
view.onNextLibraryUpdate(categories, mangaMap, true)
|
||||
} else {
|
||||
val list = mutableListOf<LibraryItem>()
|
||||
for (element in mangaMap.toSortedMap(compareBy { entry ->
|
||||
categories.find { it.id == entry }?.order ?: -1
|
||||
@ -607,7 +598,6 @@ class LibraryPresenter(
|
||||
}
|
||||
view.onNextLibraryUpdate(list, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the library to have download badges added/removed.
|
||||
|
@ -1,10 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.library
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
|
||||
sealed class LibrarySelectionEvent {
|
||||
|
||||
class Selected(val manga: Manga) : LibrarySelectionEvent()
|
||||
class Unselected(val manga: Manga) : LibrarySelectionEvent()
|
||||
class Cleared() : LibrarySelectionEvent()
|
||||
}
|
@ -49,7 +49,6 @@ import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
|
||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
|
||||
import eu.kanade.tachiyomi.ui.download.DownloadController
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryListController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
|
||||
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
|
||||
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
|
||||
@ -152,7 +151,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
val currentRoot = router.backstack.firstOrNull()
|
||||
if (currentRoot?.tag()?.toIntOrNull() != id) {
|
||||
setRoot(when (id) {
|
||||
R.id.nav_library -> LibraryListController()
|
||||
R.id.nav_library -> LibraryController()
|
||||
R.id.nav_recents -> RecentsController()
|
||||
else -> CatalogueController()
|
||||
}, id)
|
||||
|
@ -292,7 +292,7 @@ class RecentsController(bundle: Bundle? = null) : BaseController(bundle),
|
||||
override fun isSearching() = presenter.query.isNotEmpty()
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
(activity as? MainActivity)?.setDismissIcon(showingDownloads)
|
||||
if (onRoot) (activity as? MainActivity)?.setDismissIcon(showingDownloads)
|
||||
if (showingDownloads) {
|
||||
inflater.inflate(R.menu.download_queue, menu)
|
||||
} else {
|
||||
|
@ -16,7 +16,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Target
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryListController
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
@ -154,7 +154,7 @@ class SettingsAdvancedController : SettingsController() {
|
||||
private fun clearDatabase() {
|
||||
// Avoid weird behavior by going back to the library.
|
||||
val newBackstack = listOf(RouterTransaction.with(
|
||||
LibraryListController())) +
|
||||
LibraryController())) +
|
||||
router.backstack.drop(1)
|
||||
|
||||
router.setBackstack(newBackstack, FadeChangeHandler())
|
||||
|
@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<eu.kanade.tachiyomi.ui.library.LibraryCategoryView
|
||||
android:id="@+id/layout"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipe_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
<eu.davidea.fastscroller.FastScroller
|
||||
android:id="@+id/fast_scroller"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
app:fastScrollerIgnoreTouchesOutsideHandle="true"
|
||||
app:fastScrollerBubbleEnabled="true"
|
||||
tools:visibility="visible" />
|
||||
</eu.kanade.tachiyomi.ui.library.LibraryCategoryView>
|
Loading…
x
Reference in New Issue
Block a user