From 836291a59a40412b73f1c3158cc9ef598bc48ad8 Mon Sep 17 00:00:00 2001 From: Jay Date: Fri, 3 Apr 2020 19:15:29 -0400 Subject: [PATCH] Cleaning out the old LibraryController --- .../ui/base/controller/BaseController.kt | 3 + .../ui/catalogue/CatalogueController.kt | 2 +- .../tachiyomi/ui/library/LibraryAdapter.kt | 102 -- .../ui/library/LibraryCategoryView.kt | 403 ------ .../tachiyomi/ui/library/LibraryController.kt | 1167 ++++++++++++----- .../ui/library/LibraryListController.kt | 829 ------------ .../tachiyomi/ui/library/LibraryMangaEvent.kt | 10 - .../tachiyomi/ui/library/LibraryPresenter.kt | 38 +- .../ui/library/LibrarySelectionEvent.kt | 10 - .../kanade/tachiyomi/ui/main/MainActivity.kt | 3 +- .../tachiyomi/ui/recents/RecentsController.kt | 2 +- .../ui/setting/SettingsAdvancedController.kt | 4 +- app/src/main/res/layout/library_category.xml | 25 - 13 files changed, 844 insertions(+), 1754 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt delete mode 100644 app/src/main/res/layout/library_category.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt index 911f446c3b..6518a33e99 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt @@ -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? { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt index 2a871d6095..32520e9e52 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueController.kt @@ -283,7 +283,7 @@ class CatalogueController : NucleusController(), * @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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt deleted file mode 100644 index 27bb231829..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt +++ /dev/null @@ -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 = 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() - - /** - * 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() - } - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt deleted file mode 100644 index 27f24a7e08..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt +++ /dev/null @@ -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()) - 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 -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 440ac12177..e680f1b215 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -1,14 +1,18 @@ package eu.kanade.tachiyomi.ui.library +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ValueAnimator import android.app.Activity import android.content.Context -import android.content.res.Configuration -import android.graphics.Color +import android.graphics.Rect import android.os.Bundle +import android.util.TypedValue import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager @@ -16,159 +20,170 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView -import androidx.core.graphics.drawable.DrawableCompat -import androidx.viewpager.widget.ViewPager +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.f2prateek.rx.preferences.Preference import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar -import com.google.android.material.tabs.TabLayout -import com.jakewharton.rxrelay.BehaviorRelay -import com.jakewharton.rxrelay.PublishRelay +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.download.DownloadService -import eu.kanade.tachiyomi.data.download.DownloadServiceListener import eu.kanade.tachiyomi.data.library.LibraryServiceListener 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.ui.base.controller.BaseController -import eu.kanade.tachiyomi.ui.base.controller.TabbedController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.ui.main.OnTouchEventInterface import eu.kanade.tachiyomi.ui.main.RootSearchInterface +import eu.kanade.tachiyomi.ui.main.SpinnerTitleInterface +import eu.kanade.tachiyomi.ui.main.SwipeGestureInterface import eu.kanade.tachiyomi.ui.manga.MangaDetailsController -import eu.kanade.tachiyomi.ui.migration.MigrationInterface import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig import eu.kanade.tachiyomi.ui.reader.ReaderActivity -import eu.kanade.tachiyomi.util.system.getResourceColor +import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController +import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener 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_controller.* +import kotlinx.android.synthetic.main.library_grid_recycler.* +import kotlinx.android.synthetic.main.library_list_controller.* import kotlinx.android.synthetic.main.main_activity.* -import rx.Subscription +import kotlinx.coroutines.delay import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get - -open class LibraryController( +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 LibraryController( bundle: Bundle? = null, - protected val preferences: PreferencesHelper = Injekt.get() -) : BaseController(bundle), TabbedController, ActionMode.Callback, - ChangeMangaCategoriesDialog.Listener, MigrationInterface, DownloadServiceListener, + private val preferences: PreferencesHelper = Injekt.get() +) : BaseController(bundle), + ActionMode.Callback, + ChangeMangaCategoriesDialog.Listener, + FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, + FlexibleAdapter.OnItemMoveListener, LibraryCategoryAdapter.LibraryListener, + SpinnerTitleInterface, OnTouchEventInterface, SwipeGestureInterface, RootSearchInterface, LibraryServiceListener { + init { + setHasOptionsMenu(true) + retainViewMode = RetainViewMode.RETAIN_DETACH + } + /** * Position of the active category. */ - protected var activeCategory: Int = preferences.lastUsedCategory().getOrDefault() + private var activeCategory: Int = preferences.lastUsedCategory().getOrDefault() + + private var justStarted = true /** * Action mode for selections. */ private var actionMode: ActionMode? = null + private var libraryLayout: Int = preferences.libraryLayout().getOrDefault() + /** * Library search query. */ - protected var query = "" + private var query = "" /** * Currently selected mangas. */ - val selectedMangas = mutableSetOf() + private val selectedMangas = mutableSetOf() - /** - * Current mangas to move. - */ - private var migratingMangas = mutableSetOf() + private lateinit var adapter: LibraryCategoryAdapter - /** - * Relay to notify the UI of selection updates. - */ - val selectionRelay: PublishRelay = PublishRelay.create() + private var lastClickPosition = -1 - /** - * Relay to notify search query changes. - */ - val searchRelay: BehaviorRelay = BehaviorRelay.create() + private var updateScroll = true - /** - * Relay to notify the library's viewpager for updates. - */ - val libraryMangaRelay: BehaviorRelay = BehaviorRelay.create() + private var lastItemPosition: Int? = null + private var lastItem: IFlexible<*>? = null - /** - * Relay to notify the library's viewpager to select all manga - */ - val selectAllRelay: PublishRelay = PublishRelay.create() + private var switchingCategories = false + var scrollDistance = 0f - /** - * Relay to notify the library's viewpager to reotagnize all - */ - val reorganizeRelay: PublishRelay> = PublishRelay.create() - - val stopRefreshRelay: PublishRelay = PublishRelay.create() - - /** - * Number of manga per row in grid mode. - */ - var mangaPerRow = 0 + lateinit var presenter: LibraryPresenter private set - /** - * Adapter of the view pager. - */ - private var pagerAdapter: LibraryAdapter? = null - - /** - * Drawer listener to allow swipe only for closing the drawer. - */ - protected var tabsVisibilityRelay: BehaviorRelay = BehaviorRelay.create(false) - - private var tabsVisibilitySubscription: Subscription? = null - private var observeLater: Boolean = false var snack: Snackbar? = null - lateinit var presenter: LibraryPresenter - private set - - protected var justStarted = true - - var libraryLayout: Int = preferences.libraryLayout().getOrDefault() - - open fun contentView(): View = pager_layout - - init { - setHasOptionsMenu(true) - retainViewMode = RetainViewMode.RETAIN_DETACH - } + // Horizontal scroll values + 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 getTitle(): String? { - return resources?.getString(R.string.label_library) + return if (view != null && presenter.categories.size > 1) presenter.categories.find { + it.order == activeCategory + }?.name ?: super.getTitle() + else super.getTitle() } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - return inflater.inflate(R.layout.library_controller, container, false) + 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) view.applyWindowInsetsForRootController(activity!!.bottom_nav) - mangaPerRow = getColumnsPreferenceForCurrentOrientation().getOrDefault() if (!::presenter.isInitialized) presenter = LibraryPresenter(this) layoutView(view) @@ -177,8 +192,7 @@ open class LibraryController( createActionModeIfNeeded() } - // bottom_sheet.onCreate(pager_layout) - bottom_sheet.onCreate(contentView()) + bottom_sheet.onCreate(recycler_layout) bottom_sheet.onGroupClicked = { when (it) { @@ -190,42 +204,277 @@ open class LibraryController( } } + // 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) + presenter.onRestore() val library = presenter.getAllManga() if (library != null) presenter.updateViewBlocking() else { - contentView().alpha = 0f + recycler_layout.alpha = 0f presenter.getLibraryBlocking() } } - open fun layoutView(view: View) { - pagerAdapter = LibraryAdapter(this) - library_pager.adapter = pagerAdapter - library_pager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { - override fun onPageSelected(position: Int) { - preferences.lastUsedCategory().set(position) - activeCategory = position + 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 - override fun onPageScrolled( - position: Int, - positionOffset: Float, - positionOffsetPixels: Int - ) { + 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 + } - override fun onPageScrollStateChanged(state: Int) {} + 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) + } + + private 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@LibraryController.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 { + 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 (library_pager != null) - // activity?.tabs?.setupWithViewPager(library_pager) + if (presenter.categories.size > 1) { + activity?.toolbar?.showSpinner() + } else { + activity?.toolbar?.removeSpinner() + } presenter.getLibrary() - DownloadService.addListener(this) DownloadService.callListeners() LibraryUpdateService.setListener(this) } @@ -234,9 +483,12 @@ open class LibraryController( override fun onActivityResumed(activity: Activity) { super.onActivityResumed(activity) + if (view == null) return if (observeLater && ::presenter.isInitialized) { presenter.getLibrary() } + resetScrollingValues() + resetRecyclerY() } override fun onActivityPaused(activity: Activity) { @@ -251,220 +503,472 @@ open class LibraryController( } override fun onDestroyView(view: View) { - pagerAdapter?.onDestroy() - DownloadService.removeListener(this) LibraryUpdateService.removeListener(this) - pagerAdapter = null actionMode = null - tabsVisibilitySubscription?.unsubscribe() - tabsVisibilitySubscription = null super.onDestroyView(view) } - override fun downloadStatusChanged(downloading: Boolean) { - /* launchUI { - val scale = if (downloading) 1f else 0f - val fab = fab ?: return@launchUI - fab.animate().scaleX(scale).scaleY(scale).setDuration(200).start() - fab.isClickable = downloading - fab.isFocusable = downloading - bottom_sheet?.adjustFiltersMargin(downloading) - }*/ + fun onNextLibraryUpdate(mangaMap: List, freshStart: Boolean) { + if (view == null) 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 isCurrentController = router?.backstack?.lastOrNull()?.controller() == this + + setTitle() + updateScroll = false + if (!freshStart) { + justStarted = false + if (recycler_layout.alpha == 0f) recycler_layout.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 + } } - override fun onUpdateManga(manga: LibraryManga) { - if (manga.id != null) presenter.updateManga(manga) - else stopRefreshRelay.call(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 onDetach(view: View) { + private fun onRefresh() { + activity?.invalidateOptionsMenu() + presenter.getLibrary() destroyActionModeIfNeeded() - snack?.dismiss() - snack = null - super.onDetach(view) } - override fun configureTabs(tabs: TabLayout) { - /* with(tabs) { - tabGravity = TabLayout.GRAVITY_CENTER - tabMode = TabLayout.MODE_SCROLLABLE - } - tabsVisibilitySubscription?.unsubscribe() - tabsVisibilitySubscription = tabsVisibilityRelay.subscribe { visible -> - val tabAnimator = (activity as? MainActivity)?.tabAnimator ?: return@subscribe - if (visible) { - tabAnimator.expand() - } else if (!visible) { - tabAnimator.collapse() - } - }*/ + /** + * Called when a filter is changed. + */ + private fun onFilterChanged() { + activity?.invalidateOptionsMenu() + presenter.requestFilterUpdate() + destroyActionModeIfNeeded() } - override fun cleanupTabs(tabs: TabLayout) { - tabsVisibilitySubscription?.unsubscribe() - tabsVisibilitySubscription = null + fun reattachAdapter() { + libraryLayout = preferences.libraryLayout().getOrDefault() + setRecyclerLayout() + val position = + (recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() + recycler.adapter = adapter + + (recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(position, 0) } - open fun onNextLibraryUpdate(mangaMap: List, freshStart: Boolean = false) {} + fun search(query: String?): Boolean { + this.query = query ?: "" + adapter.setFilter(query) + adapter.performFilter() + return true + } - fun onNextLibraryUpdate( - categories: List, - mangaMap: Map>, - freshStart: Boolean = false - ) { - val view = view ?: return - val adapter = pagerAdapter ?: return + override fun onDestroyActionMode(mode: ActionMode?) { + selectedMangas.clear() + actionMode = null + adapter.mode = SelectableAdapter.Mode.SINGLE + adapter.clearSelection() + adapter.notifyDataSetChanged() + lastClickPosition = -1 + adapter.isLongPressDragEnabled = canDrag() + } - // Show empty view if needed - if (mangaMap.isNotEmpty()) { - empty_view.hide() + private 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 { - empty_view.show(R.drawable.ic_book_black_128dp, R.string.information_empty_library) + 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) + } - // Get the current active category. - val activeCat = if (adapter.categories.isNotEmpty()) library_pager.currentItem - else activeCategory - - categories.find { it.id == 0 }?.let { - it.name = resources?.getString( - if (categories.size == 1) R.string.pref_category_library - else R.string.default_columns - ) ?: "Default" + 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() + } } - // Set the categories - adapter.categories = categories - - // Restore active category. - library_pager.setCurrentItem(activeCat, false) - - // tabsVisibilityRelay.call(categories.size > 1) - - libraryMangaRelay.call(LibraryMangaEvent(mangaMap)) + } - view.post { - // if (isAttached) { - // activity?.tabs?.setScrollPosition(library_pager.currentItem, 0f, true) - // } + 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) + } - if (!freshStart && justStarted) { - if (!freshStart) { - justStarted = false - if (pager_layout.alpha == 0f) pager_layout.animate().alpha(1f).setDuration(500) - .start() - } - } - // Delay the scroll position to allow the view to be properly measured. + 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() + } - // Send the manga map to child fragments after the adapter is updated. + override fun canDrag(): Boolean { + val filterOff = + !bottom_sheet.hasActiveFilters() && !preferences.hideCategories().getOrDefault() + return filterOff && adapter.mode != SelectableAdapter.Mode.MULTI } /** - * Returns a preference for the number of manga per row based on the current orientation. + * Called when a manga is clicked. * - * @return the preference. + * @param position the position of the element clicked. + * @return true if the item should be selected, false otherwise. */ - private fun getColumnsPreferenceForCurrentOrientation(): Preference { - return if (resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) preferences.portraitColumns() - else preferences.landscapeColumns() + 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) + false + } } + private fun openManga(manga: Manga) = router.pushController(MangaDetailsController( + manga + ).withFadeTransaction()) + /** - * Called when a filter is changed. + * Called when a manga is long clicked. + * + * @param position the position of the element clicked. */ - private fun onFilterChanged() { - activity?.invalidateOptionsMenu() - presenter.requestFilterUpdate() - destroyActionModeIfNeeded() + 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 } - private fun onRefresh() { - activity?.invalidateOptionsMenu() - presenter.getLibrary() - destroyActionModeIfNeeded() + 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) + } + } } - /** - * Called when the sorting mode is changed. - */ - private fun onSortChanged() { - presenter.requestSortUpdate() - destroyActionModeIfNeeded() + override fun onUpdateManga(manga: LibraryManga) { + if (manga.id == null) adapter.notifyDataSetChanged() + else presenter.updateManga(manga) } - open fun onCatSortChanged(id: Int? = null) { - val catId = - (id ?: pagerAdapter?.categories?.getOrNull(library_pager.currentItem)?.id) ?: return - presenter.requestCatSortUpdate(catId) + private fun setSelection(position: Int, selected: Boolean = true) { + val item = adapter.getItem(position) as? LibraryItem ?: return + + setSelection(item.manga, selected) + invalidateActionMode() } - /** - * Reattaches the adapter to the view pager to recreate fragments - */ - open fun reattachAdapter() { - val adapter = pagerAdapter ?: return + 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 + } - val position = library_pager.currentItem + fun toggleFilters() { + if (bottom_sheet.sheetBehavior?.isHideable == true && bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) bottom_sheet.sheetBehavior?.state = + BottomSheetBehavior.STATE_HIDDEN + else if (bottom_sheet.sheetBehavior?.state != BottomSheetBehavior.STATE_COLLAPSED && bottom_sheet.sheetBehavior?.skipCollapsed == false) bottom_sheet.sheetBehavior?.state = + BottomSheetBehavior.STATE_COLLAPSED + else bottom_sheet.sheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED + } - adapter.recycle = false - library_pager.adapter = adapter - library_pager.currentItem = position - adapter.recycle = true + 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 + )) } - /** - * Creates the action mode if it's not created already. - */ - fun createActionModeIfNeeded() { - if (actionMode == null) { - actionMode = (activity as AppCompatActivity).startSupportActionMode(this) - val view = activity?.window?.currentFocus ?: return - val imm = - activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager - ?: return - imm.hideSoftInputFromWindow(view.windowToken, 0) + 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() + 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 } - /** - * Destroys the action mode. - */ - protected fun destroyActionModeIfNeeded() { - actionMode?.finish() + private fun moveMangaToCategory( + manga: LibraryManga, + category: Category?, + mangaIds: List, + 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 onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.library, menu) + 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 + } - val searchItem = menu.findItem(R.id.action_search) - val searchView = searchItem.actionView as SearchView - searchView.queryHint = resources?.getString(R.string.library_search_hint) + override fun sortCategory(catId: Int, sortBy: Int) { + presenter.sortCategory(catId, sortBy) + } - searchItem.collapseActionView() - if (query.isNotEmpty()) { - searchItem.expandActionView() - searchView.setQuery(query, true) - searchView.clearFocus() - } + 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) + } - // Mutate the filter icon because it needs to be tinted and the resource is shared. - menu.findItem(R.id.action_library_filter).icon.mutate() + override fun allSelected(position: Int): Boolean { + val header = adapter.getSectionHeader(position) ?: return false + val items = adapter.getSectionItemPositions(header) + return items.all { adapter.isSelected(it) } + } - setOnQueryTextChangeListener(searchView) { - onSearch(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() } - searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() }) } - open fun onSearch(query: String?): Boolean { - this.query = query ?: "" - searchRelay.call(query) - return true - } + 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) + } - open fun search(query: String) { - onSearch(query) + 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 handleRootBack(): Boolean { @@ -476,38 +980,58 @@ open class LibraryController( return false } - override fun onPrepareOptionsMenu(menu: Menu) { - val navView = bottom_sheet ?: return + //region Toolbar options methods + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.library, menu) + + val searchItem = menu.findItem(R.id.action_search) + val searchView = searchItem.actionView as SearchView + searchView.queryHint = resources?.getString(R.string.library_search_hint) + + searchItem.collapseActionView() + if (query.isNotEmpty()) { + searchItem.expandActionView() + searchView.setQuery(query, true) + searchView.clearFocus() + } - val filterItem = menu.findItem(R.id.action_library_filter) + // Mutate the filter icon because it needs to be tinted and the resource is shared. + menu.findItem(R.id.action_library_filter).icon.mutate() - // Tint icon if there's a filter active - val filterColor = if (navView.hasActiveFilters()) Color.rgb(255, 238, 7) - else activity?.getResourceColor(R.attr.actionBarTintColor) ?: Color.WHITE - DrawableCompat.setTint(filterItem.icon, filterColor) + setOnQueryTextChangeListener(searchView) { search(it) } + searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() }) } override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_search -> expandActionViewFromInteraction = true - R.id.action_library_filter -> { - toggleFilters() - } - R.id.action_library_display -> { - DisplayBottomSheet(this).show() - } + R.id.action_library_display -> DisplayBottomSheet(this).show() else -> return super.onOptionsItemSelected(item) } - return true } + //endregion - fun toggleFilters() { - if (bottom_sheet.sheetBehavior?.isHideable == true && bottom_sheet.sheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) bottom_sheet.sheetBehavior?.state = - BottomSheetBehavior.STATE_HIDDEN - else if (bottom_sheet.sheetBehavior?.state != BottomSheetBehavior.STATE_COLLAPSED && bottom_sheet.sheetBehavior?.skipCollapsed == false) bottom_sheet.sheetBehavior?.state = - BottomSheetBehavior.STATE_COLLAPSED - else bottom_sheet.sheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED + //region Action Mode Methods + /** + * Creates the action mode if it's not created already. + */ + private fun createActionModeIfNeeded() { + if (actionMode == null) { + actionMode = (activity as AppCompatActivity).startSupportActionMode(this) + val view = activity?.window?.currentFocus ?: return + val imm = + activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + ?: return + imm.hideSoftInputFromWindow(view.windowToken, 0) + } + } + + /** + * Destroys the action mode. + */ + private fun destroyActionModeIfNeeded() { + actionMode?.finish() } /** @@ -540,6 +1064,7 @@ open class LibraryController( } return false } + //endregion override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { when (item.itemId) { @@ -550,11 +1075,6 @@ open class LibraryController( deleteMangasFromLibrary() }.negativeButton(android.R.string.no).show() } - R.id.action_select_all -> { - pagerAdapter?.categories?.getOrNull(library_pager.currentItem)?.id?.let { - selectAllRelay.call(it) - } - } R.id.action_migrate -> { val skipPre = preferences.skipPreMigration().getOrDefault() router.pushController( @@ -568,75 +1088,11 @@ open class LibraryController( ) destroyActionModeIfNeeded() } - /*R.id.action_to_top, R.id.action_to_bottom -> { - adapter?.categories?.getOrNull(library_pager.currentItem)?.id?.let { - reorganizeRelay.call(it to if (item.itemId == R.id.action_to_top) -1 else -2) - } - destroyActionModeIfNeeded() - }*/ else -> return false } return true } - override fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? { - if (manga.id != prevManga.id) { - presenter.migrateManga(prevManga, manga, replace = replace) - } - val nextManga = migratingMangas.firstOrNull() ?: return null - migratingMangas.remove(nextManga) - return nextManga - } - - override fun onDestroyActionMode(mode: ActionMode?) { - // Clear all the manga selections and notify child views. - selectedMangas.clear() - selectionRelay.call(LibrarySelectionEvent.Cleared()) - actionMode = null - } - - fun openManga(manga: Manga, startY: Float?) { - router.pushController(MangaDetailsController(manga).withFadeTransaction()) - // router.pushController(MangaController(manga, startY).withFadeTransaction()) - } - - /** - * Sets the selection for a given manga. - * - * @param manga the manga whose selection has changed. - * @param selected whether it's now selected or not. - */ - open fun setSelection(manga: Manga, selected: Boolean) { - if (selected) { - if (selectedMangas.add(manga)) { - selectionRelay.call(LibrarySelectionEvent.Selected(manga)) - } - } else { - if (selectedMangas.remove(manga)) { - selectionRelay.call(LibrarySelectionEvent.Unselected(manga)) - } - } - } - - /** - * Move the selected manga to a list of categories. - */ - private fun showChangeMangaCategoriesDialog() { - // Create a copy of selected manga - val mangas = selectedMangas.toList() - - // Hide the default category because it has a different behavior than the ones from db. - val categories = presenter.allCategories.filter { it.id != 0 } - - // Get indexes of the common categories to preselect. - val commonCategoriesIndexes = - presenter.getCommonCategories(mangas).map { categories.indexOf(it) }.toTypedArray() - - ChangeMangaCategoriesDialog(this, mangas, categories, commonCategoriesIndexes).showDialog( - router - ) - } - private fun deleteMangasFromLibrary() { val mangas = selectedMangas.toList() presenter.removeMangaFromLibrary(mangas) @@ -667,11 +1123,32 @@ open class LibraryController( } // / Method for the category view - fun startReading(manga: Manga) { + private fun startReading(manga: Manga) { val activity = activity ?: return val chapter = presenter.getFirstUnread(manga) ?: return val intent = ReaderActivity.newIntent(activity, manga, chapter) destroyActionModeIfNeeded() startActivity(intent) } + + /** + * Move the selected manga to a list of categories. + */ + private fun showChangeMangaCategoriesDialog() { + // Create a copy of selected manga + val mangas = selectedMangas.toList() + + // Hide the default category because it has a different behavior than the ones from db. + val categories = presenter.allCategories.filter { it.id != 0 } + + // Get indexes of the common categories to preselect. + val commonCategoriesIndexes = + presenter.getCommonCategories(mangas).map { categories.indexOf(it) }.toTypedArray() + + ChangeMangaCategoriesDialog(this, mangas, categories, commonCategoriesIndexes).showDialog( + router + ) + } + + override fun recyclerIsScrolling() = switchingCategories || lockedRecycler || lockedY } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListController.kt deleted file mode 100644 index af22fbc2d7..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListController.kt +++ /dev/null @@ -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 { - 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, 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() - 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, - 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 -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt deleted file mode 100644 index e5dffe3086..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt +++ /dev/null @@ -1,10 +0,0 @@ -package eu.kanade.tachiyomi.ui.library - -import eu.kanade.tachiyomi.data.database.models.Category - -class LibraryMangaEvent(val mangas: Map>) { - - fun getMangaForCategory(category: Category): List? { - return mangas[category.id] - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 905081ac06..cb49102167 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -566,20 +566,16 @@ class LibraryPresenter( freshStart: Boolean = false ) { - if (view !is LibraryListController) { - view.onNextLibraryUpdate(categories, mangaMap, freshStart) - } else { - val mangaList = withContext(Dispatchers.IO) { - val list = mutableListOf() - for (element in mangaMap.toSortedMap(compareBy { entry -> - categories.find { it.id == entry }?.order ?: -1 - })) { - list.addAll(element.value) - } - list + val mangaList = withContext(Dispatchers.IO) { + val list = mutableListOf() + for (element in mangaMap.toSortedMap(compareBy { entry -> + categories.find { it.id == entry }?.order ?: -1 + })) { + list.addAll(element.value) } - view.onNextLibraryUpdate(mangaList, freshStart) + list } + view.onNextLibraryUpdate(mangaList, freshStart) } fun getList(): List { @@ -594,19 +590,13 @@ 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() - for (element in mangaMap.toSortedMap(compareBy { entry -> - categories.find { it.id == entry }?.order ?: -1 - })) { - list.addAll(element.value) - } - view.onNextLibraryUpdate(list, true) + val list = mutableListOf() + for (element in mangaMap.toSortedMap(compareBy { entry -> + categories.find { it.id == entry }?.order ?: -1 + })) { + list.addAll(element.value) } + view.onNextLibraryUpdate(list, true) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt deleted file mode 100644 index 2af28fca8f..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt +++ /dev/null @@ -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() -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index fb62a44d9c..3275d425fe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt index 31260b4cb8..0d561d0b8c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt @@ -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 { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt index e12efa0563..360ab9841e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt @@ -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()) diff --git a/app/src/main/res/layout/library_category.xml b/app/src/main/res/layout/library_category.xml deleted file mode 100644 index e9b50b1f9c..0000000000 --- a/app/src/main/res/layout/library_category.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - -