From dfb2487640c889bc46a022dad1f2e35ee2a629bc Mon Sep 17 00:00:00 2001 From: len Date: Mon, 22 Aug 2016 12:54:16 +0200 Subject: [PATCH] Library views recycling --- .../ui/base/adapter/FlexibleViewHolder.kt | 2 +- .../tachiyomi/ui/library/LibraryAdapter.kt | 69 ++--- .../ui/library/LibraryCategoryAdapter.kt | 14 +- .../ui/library/LibraryCategoryFragment.kt | 260 +++++++++--------- .../tachiyomi/ui/library/LibraryFragment.kt | 94 +++---- .../tachiyomi/ui/library/LibraryPresenter.kt | 26 +- .../ui/library/LibrarySelectionEvent.kt | 10 + .../widget/RecyclerViewPagerAdapter.kt | 42 +++ .../main/res/layout/item_library_category.xml | 14 + 9 files changed, 295 insertions(+), 236 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt create mode 100644 app/src/main/res/layout/item_library_category.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/FlexibleViewHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/FlexibleViewHolder.kt index ed5e39e65d..2fe0ddfd6f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/FlexibleViewHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/adapter/FlexibleViewHolder.kt @@ -27,7 +27,7 @@ abstract class FlexibleViewHolder(view: View, return true } - protected fun toggleActivation() { + fun toggleActivation() { itemView.isActivated = adapter.isSelected(adapterPosition) } 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 index 8cf6540d40..8519a3279c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt @@ -1,22 +1,23 @@ package eu.kanade.tachiyomi.ui.library -import android.support.v4.app.Fragment -import android.support.v4.app.FragmentManager +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.ui.base.adapter.SmartFragmentStatePagerAdapter +import eu.kanade.tachiyomi.util.inflate +import eu.kanade.tachiyomi.widget.RecyclerViewPagerAdapter /** * This adapter stores the categories from the library, used with a ViewPager. * - * @param fm the fragment manager. * @constructor creates an instance of the adapter. */ -class LibraryAdapter(fm: FragmentManager) : SmartFragmentStatePagerAdapter(fm) { +class LibraryAdapter(private val fragment: LibraryFragment) : RecyclerViewPagerAdapter() { /** * The categories to bind in the adapter. */ - var categories: List? = null + 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) { @@ -26,13 +27,34 @@ class LibraryAdapter(fm: FragmentManager) : SmartFragmentStatePagerAdapter(fm) { } /** - * Creates a new fragment for the given position when it's called. + * Creates a new view for this adapter. * - * @param position the position to instantiate. - * @return a fragment for the given position. + * @return a new view. */ - override fun getItem(position: Int): Fragment { - return LibraryCategoryFragment.newInstance(position) + override fun createView(container: ViewGroup): View { + val view = container.inflate(R.layout.item_library_category) as LibraryCategoryFragment + view.onCreate(fragment) + 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 LibraryCategoryFragment).onBind(categories[position]) + } + + /** + * 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 LibraryCategoryFragment).onRecycle() } /** @@ -41,7 +63,7 @@ class LibraryAdapter(fm: FragmentManager) : SmartFragmentStatePagerAdapter(fm) { * @return the number of categories or 0 if the list is null. */ override fun getCount(): Int { - return categories?.size ?: 0 + return categories.size } /** @@ -51,28 +73,7 @@ class LibraryAdapter(fm: FragmentManager) : SmartFragmentStatePagerAdapter(fm) { * @return the title to display. */ override fun getPageTitle(position: Int): CharSequence { - return categories!![position].name - } - - /** - * Method to enable or disable the action mode (multiple selection) for all the instantiated - * fragments. - * - * @param mode the mode to set. - */ - fun setSelectionMode(mode: Int) { - for (fragment in getRegisteredFragments()) { - (fragment as LibraryCategoryFragment).setSelectionMode(mode) - } - } - - /** - * Notifies the adapters in all the registered fragments to refresh their content. - */ - fun refreshRegisteredAdapters() { - for (fragment in getRegisteredFragments()) { - (fragment as LibraryCategoryFragment).adapter.notifyDataSetChanged() - } + return categories[position].name } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index eb3c528daf..18e31a65b1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -84,7 +84,7 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : * @return a new view holder for a manga. */ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LibraryHolder { - //depending on preferences, display a list or display a grid + // Depending on preferences, display a list or display a grid if (parent is AutofitRecyclerView) { val view = parent.inflate(R.layout.item_catalogue_grid).apply { val coverHeight = parent.itemWidth / 3 * 4 @@ -96,7 +96,6 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : val view = parent.inflate(R.layout.item_library_list) return LibraryListHolder(view, this, fragment) } - } /** @@ -109,8 +108,17 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) : val manga = getItem(position) holder.onSetValues(manga) - //When user scrolls this bind the correct selection status + // When user scrolls this bind the correct selection status holder.itemView.isActivated = isSelected(position) } + /** + * Returns the position in the adapter for the given manga. + * + * @param manga the manga to find. + */ + fun indexOf(manga: Manga): Int { + return mangas.orEmpty().indexOfFirst { it.id == manga.id } + } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt index 1b28363f2a..6f2778398a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt @@ -1,24 +1,23 @@ package eu.kanade.tachiyomi.ui.library -import android.os.Bundle +import android.content.Context import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.util.AttributeSet +import android.widget.FrameLayout import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R +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.ui.base.adapter.FlexibleViewHolder -import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment import eu.kanade.tachiyomi.ui.manga.MangaActivity import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.widget.AutofitRecyclerView -import kotlinx.android.synthetic.main.fragment_library_category.* +import kotlinx.android.synthetic.main.item_library_category.view.* import rx.Subscription import uy.kohesive.injekt.injectLazy @@ -26,23 +25,33 @@ import uy.kohesive.injekt.injectLazy * Fragment containing the library manga for a certain category. * Uses R.layout.fragment_library_category. */ -class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemClickListener { +class LibraryCategoryFragment @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) +: FrameLayout(context, attrs), FlexibleViewHolder.OnListItemClickListener { /** * Preferences. */ - val preferences: PreferencesHelper by injectLazy() + private val preferences: PreferencesHelper by injectLazy() + + /** + * The fragment containing this view. + */ + private lateinit var fragment: LibraryFragment + + /** + * Category for this view. + */ + private lateinit var category: Category + + /** + * Recycler view of the list of manga. + */ + private lateinit var recycler: RecyclerView /** * Adapter to hold the manga in this category. */ - lateinit var adapter: LibraryCategoryAdapter - private set - - /** - * Position in the adapter from [LibraryAdapter]. - */ - private var position: Int = 0 + private lateinit var adapter: LibraryCategoryAdapter /** * Subscription for the library manga. @@ -54,69 +63,30 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli */ private var searchSubscription: Subscription? = null - companion object { - /** - * Key to save and restore [position] from a [Bundle]. - */ - const val POSITION_KEY = "position_key" + /** + * Subscription of the library selections. + */ + private var selectionSubscription: Subscription? = null - /** - * Creates a new instance of this class. - * - * @param position the position in the adapter from [LibraryAdapter]. - * @return a new instance of [LibraryCategoryFragment]. - */ - fun newInstance(position: Int): LibraryCategoryFragment { - val fragment = LibraryCategoryFragment() - fragment.position = position + fun onCreate(fragment: LibraryFragment) { + this.fragment = fragment - return fragment - } - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_library_category, container, false) - } - - override fun onViewCreated(view: View, savedState: Bundle?) { - adapter = LibraryCategoryAdapter(this) - - val recycler = if (preferences.libraryAsList().getOrDefault()) { + recycler = if (preferences.libraryAsList().getOrDefault()) { (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 = libraryFragment.mangaPerRow + spanCount = fragment.mangaPerRow } } - // This crashes when opening a manga after changing categories, but then viewholders aren't - // recycled between pages. It may be fixed if this fragment is replaced with a custom view. - //(recycler.layoutManager as LinearLayoutManager).recycleChildrenOnDetach = true - //recycler.recycledViewPool = libraryFragment.pool + adapter = LibraryCategoryAdapter(this) + recycler.setHasFixedSize(true) recycler.adapter = adapter swipe_refresh.addView(recycler) - if (libraryFragment.actionMode != null) { - setSelectionMode(FlexibleAdapter.MODE_MULTI) - } - - searchSubscription = libraryPresenter.searchSubject.subscribe { text -> - adapter.searchText = text - adapter.updateDataSet() - } - - if (savedState != null) { - position = savedState.getInt(POSITION_KEY) - adapter.onRestoreInstanceState(savedState) - - if (adapter.mode == FlexibleAdapter.MODE_SINGLE) { - adapter.clearSelection() - } - } - recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recycler: RecyclerView, newState: Int) { // Disable swipe refresh when view is not at the top @@ -130,36 +100,47 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli swipe_refresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt()) swipe_refresh.setOnRefreshListener { if (!LibraryUpdateService.isRunning(context)) { - libraryPresenter.categories.getOrNull(position)?.let { - LibraryUpdateService.start(context, true, it) - context.toast(R.string.updating_category) - } + LibraryUpdateService.start(context, true, category) + context.toast(R.string.updating_category) } // It can be a very long operation, so we disable swipe refresh and show a toast. swipe_refresh.isRefreshing = false } } - override fun onDestroyView() { - searchSubscription?.unsubscribe() - super.onDestroyView() - } + fun onBind(category: Category) { + this.category = category - override fun onResume() { - super.onResume() - libraryMangaSubscription = libraryPresenter.libraryMangaSubject + val presenter = fragment.presenter + + searchSubscription = presenter.searchSubject.subscribe { text -> + adapter.searchText = text + adapter.updateDataSet() + } + + adapter.mode = if (presenter.selectedMangas.isNotEmpty()) { + FlexibleAdapter.MODE_MULTI + } else { + FlexibleAdapter.MODE_SINGLE + } + + libraryMangaSubscription = presenter.libraryMangaSubject .subscribe { onNextLibraryManga(it) } + + selectionSubscription = presenter.selectionSubject + .subscribe { onSelectionChanged(it) } } - override fun onPause() { + fun onRecycle() { + adapter.setItems(emptyList()) + adapter.clearSelection() + } + + override fun onDetachedFromWindow() { + searchSubscription?.unsubscribe() libraryMangaSubscription?.unsubscribe() - super.onPause() - } - - override fun onSaveInstanceState(outState: Bundle) { - outState.putInt(POSITION_KEY, position) - adapter.onSaveInstanceState(outState) - super.onSaveInstanceState(outState) + selectionSubscription?.unsubscribe() + super.onDetachedFromWindow() } /** @@ -169,17 +150,61 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli * @param event the event received. */ fun onNextLibraryManga(event: LibraryMangaEvent) { - // Get the categories from the parent fragment. - val categories = libraryFragment.adapter.categories ?: return - - // When a category is deleted, the index can be greater than the number of categories. - if (position >= categories.size) return - // Get the manga list for this category. - val mangaForCategory = event.getMangaForCategory(categories[position]) ?: emptyList() + val mangaForCategory = event.getMangaForCategory(category).orEmpty() // Update the category with its manga. adapter.setItems(mangaForCategory) + + if (adapter.mode == FlexibleAdapter.MODE_MULTI) { + fragment.presenter.selectedMangas.forEach { manga -> + val position = adapter.indexOf(manga) + if (position != -1 && !adapter.isSelected(position)) { + adapter.toggleSelection(position) + (recycler.findViewHolderForItemId(manga.id!!) 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 != FlexibleAdapter.MODE_MULTI) { + adapter.mode = FlexibleAdapter.MODE_MULTI + } + findAndToggleSelection(event.manga) + } + is LibrarySelectionEvent.Unselected -> { + findAndToggleSelection(event.manga) + if (fragment.presenter.selectedMangas.isEmpty()) { + adapter.mode = FlexibleAdapter.MODE_SINGLE + } + } + is LibrarySelectionEvent.Cleared -> { + adapter.mode = FlexibleAdapter.MODE_SINGLE + adapter.clearSelection() + } + } + } + + /** + * 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.findViewHolderForItemId(manga.id!!) as? LibraryHolder)?.toggleActivation() + } } /** @@ -191,7 +216,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli override fun onListItemClick(position: Int): Boolean { // If the action mode is created and the position is valid, toggle the selection. val item = adapter.getItem(position) ?: return false - if (libraryFragment.actionMode != null) { + if (adapter.mode == FlexibleAdapter.MODE_MULTI) { toggleSelection(position) return true } else { @@ -206,7 +231,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli * @param position the position of the element clicked. */ override fun onListItemLongClick(position: Int) { - libraryFragment.createActionModeIfNeeded() + fragment.createActionModeIfNeeded() toggleSelection(position) } @@ -217,63 +242,24 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli */ private fun openManga(manga: Manga) { // Notify the presenter a manga is being opened. - libraryPresenter.onOpenManga() + fragment.presenter.onOpenManga() // Create a new activity with the manga. val intent = MangaActivity.newIntent(context, manga) - startActivity(intent) + fragment.startActivity(intent) } /** - * Toggles the selection for a manga. + * Tells the presenter to toggle the selection for the given position. * * @param position the position to toggle. */ private fun toggleSelection(position: Int) { - val library = libraryFragment + val manga = adapter.getItem(position) ?: return - // Toggle the selection. - adapter.toggleSelection(position, false) - - // Notify the selection to the presenter. - library.presenter.setSelection(adapter.getItem(position), adapter.isSelected(position)) - - // Get the selected count. - val count = library.presenter.selectedMangas.size - if (count == 0) { - // Destroy action mode if there are no items selected. - library.destroyActionModeIfNeeded() - } else { - // Update action mode with the new selection. - library.setContextTitle(count) - library.setVisibilityOfCoverEdit(count) - library.invalidateActionMode() - } + fragment.presenter.setSelection(manga, !adapter.isSelected(position)) + fragment.invalidateActionMode() } - /** - * Sets the mode for the adapter. - * - * @param mode the mode to set. It should be MODE_SINGLE or MODE_MULTI. - */ - fun setSelectionMode(mode: Int) { - adapter.mode = mode - if (mode == FlexibleAdapter.MODE_SINGLE) { - adapter.clearSelection() - } - } - - /** - * Property to get the library fragment. - */ - private val libraryFragment: LibraryFragment - get() = parentFragment as LibraryFragment - - /** - * Property to get the library presenter. - */ - private val libraryPresenter: LibraryPresenter - get() = libraryFragment.presenter - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index ac0b77b6d3..62d441e882 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt @@ -11,7 +11,6 @@ import android.support.v7.widget.SearchView import android.view.* import com.afollestad.materialdialogs.MaterialDialog import com.f2prateek.rx.preferences.Preference -import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Manga @@ -26,6 +25,7 @@ import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_library.* import nucleus.factory.RequiresPresenter import rx.Subscription +import timber.log.Timber import uy.kohesive.injekt.injectLazy import java.io.IOException @@ -66,8 +66,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback /** * Action mode for manga selection. */ - var actionMode: ActionMode? = null - private set + private var actionMode: ActionMode? = null /** * Selected manga for editing its cover. @@ -91,14 +90,8 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback private set /** - * A pool to share view holders between all the registered categories (fragments). + * Subscription for the number of manga per row. */ - // TODO find out why this breaks sometimes -// var pool = RecyclerView.RecycledViewPool().apply { setMaxRecycledViews(0, 20) } -// private set(value) { -// field = value.apply { setMaxRecycledViews(0, 20) } -// } - private var numColumnsSubscription: Subscription? = null companion object { @@ -141,7 +134,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback override fun onViewCreated(view: View, savedState: Bundle?) { setToolbarTitle(getString(R.string.label_library)) - adapter = LibraryAdapter(childFragmentManager) + adapter = LibraryAdapter(this) view_pager.adapter = adapter view_pager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { override fun onPageSelected(position: Int) { @@ -154,6 +147,9 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback activeCategory = savedState.getInt(CATEGORY_KEY) query = savedState.getString(QUERY_KEY) presenter.searchSubject.onNext(query) + if (presenter.selectedMangas.isNotEmpty()) { + createActionModeIfNeeded() + } } else { activeCategory = preferences.lastUsedCategory().getOrDefault() } @@ -261,8 +257,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback * Applies filter change */ private fun onFilterCheckboxChanged() { - presenter.updateLibrary() - adapter.refreshRegisteredAdapters() + presenter.resubscribeLibrary() activity.supportInvalidateOptionsMenu() } @@ -278,11 +273,11 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback * Reattaches the adapter to the view pager to recreate fragments */ private fun reattachAdapter() { -// pool.clear() -// pool = RecyclerView.RecycledViewPool() val position = view_pager.currentItem + adapter.recycle = false view_pager.adapter = adapter view_pager.currentItem = position + adapter.recycle = true } /** @@ -323,7 +318,7 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback R.string.information_empty_library, R.drawable.ic_book_black_128dp) // Get the current active category. - val activeCat = if (adapter.categories != null) view_pager.currentItem else activeCategory + val activeCat = if (adapter.categories.isNotEmpty()) view_pager.currentItem else activeCategory // Set the categories adapter.categories = categories @@ -339,31 +334,42 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback } /** - * Sets the title of the action mode. - * - * @param count the number of items selected. + * Creates the action mode if it's not created already. */ - fun setContextTitle(count: Int) { - actionMode?.title = getString(R.string.label_selected, count) + fun createActionModeIfNeeded() { + if (actionMode == null) { + actionMode = activity.startSupportActionMode(this) + } } /** - * Sets the visibility of the edit cover item. - * - * @param count the number of items selected. + * Destroys the action mode. */ - fun setVisibilityOfCoverEdit(count: Int) { - // If count = 1 display edit button - actionMode?.menu?.findItem(R.id.action_edit_cover)?.isVisible = count == 1 + fun destroyActionModeIfNeeded() { + actionMode?.finish() + } + + /** + * Invalidates the action mode, forcing it to refresh its content. + */ + fun invalidateActionMode() { + actionMode?.invalidate() } override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { mode.menuInflater.inflate(R.menu.library_selection, menu) - adapter.setSelectionMode(FlexibleAdapter.MODE_MULTI) return true } override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + val count = presenter.selectedMangas.size + if (count == 0) { + // Destroy action mode if there are no items selected. + destroyActionModeIfNeeded() + } else { + mode.title = getString(R.string.label_selected, count) + menu.findItem(R.id.action_edit_cover)?.isVisible = count == 1 + } return false } @@ -381,18 +387,10 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback } override fun onDestroyActionMode(mode: ActionMode) { - adapter.setSelectionMode(FlexibleAdapter.MODE_SINGLE) - presenter.selectedMangas.clear() + presenter.clearSelections() actionMode = null } - /** - * Destroys the action mode. - */ - fun destroyActionModeIfNeeded() { - actionMode?.finish() - } - /** * Changes the cover for the selected manga. * @@ -422,14 +420,14 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback context.contentResolver.openInputStream(data.data).use { // Update cover to selected file, show error if something went wrong if (presenter.editCoverWithStream(it, manga)) { - adapter.refreshRegisteredAdapters() + // TODO refresh cover } else { context.toast(R.string.notification_manga_update_failed) } } - } catch (e: IOException) { + } catch (error: IOException) { context.toast(R.string.notification_manga_update_failed) - e.printStackTrace() + Timber.e(error, error.message) } } @@ -476,20 +474,4 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback .show() } - /** - * Creates the action mode if it's not created already. - */ - fun createActionModeIfNeeded() { - if (actionMode == null) { - actionMode = activity.startSupportActionMode(this) - } - } - - /** - * Invalidates the action mode, forcing it to refresh its content. - */ - fun invalidateActionMode() { - actionMode?.invalidate() - } - } 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 2173be875d..51a81cc9e5 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 @@ -16,6 +16,7 @@ import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import rx.subjects.BehaviorSubject +import rx.subjects.PublishSubject import uy.kohesive.injekt.injectLazy import java.io.IOException import java.io.InputStream @@ -29,22 +30,27 @@ class LibraryPresenter : BasePresenter() { /** * Categories of the library. */ - lateinit var categories: List + var categories: List = emptyList() /** * Currently selected manga. */ - var selectedMangas = mutableListOf() + val selectedMangas = mutableListOf() /** * Search query of the library. */ - val searchSubject: BehaviorSubject = BehaviorSubject.create() + val searchSubject: BehaviorSubject = BehaviorSubject.create() /** * Subject to notify the library's viewpager for updates. */ - val libraryMangaSubject: BehaviorSubject = BehaviorSubject.create() + val libraryMangaSubject: BehaviorSubject = BehaviorSubject.create() + + /** + * Subject to notify the UI of selection updates. + */ + val selectionSubject: PublishSubject = PublishSubject.create() /** * Database. @@ -149,7 +155,7 @@ class LibraryPresenter : BasePresenter() { /** * Resubscribes to library. */ - fun updateLibrary() { + fun resubscribeLibrary() { start(GET_LIBRARY) } @@ -219,11 +225,21 @@ class LibraryPresenter : BasePresenter() { fun setSelection(manga: Manga, selected: Boolean) { if (selected) { selectedMangas.add(manga) + selectionSubject.onNext(LibrarySelectionEvent.Selected(manga)) } else { selectedMangas.remove(manga) + selectionSubject.onNext(LibrarySelectionEvent.Unselected(manga)) } } + /** + * Clears all the manga selections and notifies the UI. + */ + fun clearSelections() { + selectedMangas.clear() + selectionSubject.onNext(LibrarySelectionEvent.Cleared()) + } + /** * Returns the common categories for the given list of manga. * 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 new file mode 100644 index 0000000000..e490e43649 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt @@ -0,0 +1,10 @@ +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() +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt new file mode 100644 index 0000000000..6d0044fe84 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt @@ -0,0 +1,42 @@ +package eu.kanade.tachiyomi.widget + +import android.support.v4.view.PagerAdapter +import android.view.View +import android.view.ViewGroup +import java.util.* + +abstract class RecyclerViewPagerAdapter : PagerAdapter() { + + private val pool = Stack() + + var recycle = true + set(value) { + if (!value) pool.clear() + field = value + } + + protected abstract fun createView(container: ViewGroup): View + + protected abstract fun bindView(view: View, position: Int) + + protected open fun recycleView(view: View, position: Int) {} + + override fun instantiateItem(container: ViewGroup, position: Int): Any { + val view = if (pool.isNotEmpty()) pool.pop() else createView(container) + bindView(view, position) + container.addView(view) + return view + } + + override fun destroyItem(container: ViewGroup, position: Int, obj: Any) { + val view = obj as View + recycleView(view, position) + container.removeView(view) + if (recycle) pool.push(view) + } + + override fun isViewFromObject(view: View, obj: Any): Boolean { + return view === obj + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_library_category.xml b/app/src/main/res/layout/item_library_category.xml new file mode 100644 index 0000000000..8c0e1c9740 --- /dev/null +++ b/app/src/main/res/layout/item_library_category.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file