Library views recycling

This commit is contained in:
len 2016-08-22 12:54:16 +02:00
parent 97454ca162
commit dfb2487640
9 changed files with 295 additions and 236 deletions

View File

@ -27,7 +27,7 @@ abstract class FlexibleViewHolder(view: View,
return true return true
} }
protected fun toggleActivation() { fun toggleActivation() {
itemView.isActivated = adapter.isSelected(adapterPosition) itemView.isActivated = adapter.isSelected(adapterPosition)
} }

View File

@ -1,22 +1,23 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import android.support.v4.app.Fragment import android.view.View
import android.support.v4.app.FragmentManager import android.view.ViewGroup
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.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. * This adapter stores the categories from the library, used with a ViewPager.
* *
* @param fm the fragment manager.
* @constructor creates an instance of the adapter. * @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. * The categories to bind in the adapter.
*/ */
var categories: List<Category>? = null var categories: List<Category> = emptyList()
// This setter helps to not refresh the adapter if the reference to the list doesn't change. // This setter helps to not refresh the adapter if the reference to the list doesn't change.
set(value) { set(value) {
if (field !== 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 new view.
* @return a fragment for the given position.
*/ */
override fun getItem(position: Int): Fragment { override fun createView(container: ViewGroup): View {
return LibraryCategoryFragment.newInstance(position) 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. * @return the number of categories or 0 if the list is null.
*/ */
override fun getCount(): Int { 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. * @return the title to display.
*/ */
override fun getPageTitle(position: Int): CharSequence { override fun getPageTitle(position: Int): CharSequence {
return categories!![position].name 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()
}
} }
} }

View File

@ -84,7 +84,7 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) :
* @return a new view holder for a manga. * @return a new view holder for a manga.
*/ */
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LibraryHolder { 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) { if (parent is AutofitRecyclerView) {
val view = parent.inflate(R.layout.item_catalogue_grid).apply { val view = parent.inflate(R.layout.item_catalogue_grid).apply {
val coverHeight = parent.itemWidth / 3 * 4 val coverHeight = parent.itemWidth / 3 * 4
@ -96,7 +96,6 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) :
val view = parent.inflate(R.layout.item_library_list) val view = parent.inflate(R.layout.item_library_list)
return LibraryListHolder(view, this, fragment) return LibraryListHolder(view, this, fragment)
} }
} }
/** /**
@ -109,8 +108,17 @@ class LibraryCategoryAdapter(val fragment: LibraryCategoryFragment) :
val manga = getItem(position) val manga = getItem(position)
holder.onSetValues(manga) 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) 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 }
}
} }

View File

@ -1,24 +1,23 @@
package eu.kanade.tachiyomi.ui.library 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.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater import android.util.AttributeSet
import android.view.View import android.widget.FrameLayout
import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder 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.ui.manga.MangaActivity
import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.widget.AutofitRecyclerView 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 rx.Subscription
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -26,23 +25,33 @@ import uy.kohesive.injekt.injectLazy
* Fragment containing the library manga for a certain category. * Fragment containing the library manga for a certain category.
* Uses R.layout.fragment_library_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. * 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. * Adapter to hold the manga in this category.
*/ */
lateinit var adapter: LibraryCategoryAdapter private lateinit var adapter: LibraryCategoryAdapter
private set
/**
* Position in the adapter from [LibraryAdapter].
*/
private var position: Int = 0
/** /**
* Subscription for the library manga. * Subscription for the library manga.
@ -54,69 +63,30 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
*/ */
private var searchSubscription: Subscription? = null private var searchSubscription: Subscription? = null
companion object { /**
/** * Subscription of the library selections.
* Key to save and restore [position] from a [Bundle]. */
*/ private var selectionSubscription: Subscription? = null
const val POSITION_KEY = "position_key"
/** fun onCreate(fragment: LibraryFragment) {
* Creates a new instance of this class. this.fragment = fragment
*
* @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
return fragment recycler = if (preferences.libraryAsList().getOrDefault()) {
}
}
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()) {
(swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply { (swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply {
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
} }
} else { } else {
(swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply { (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 adapter = LibraryCategoryAdapter(this)
// 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
recycler.setHasFixedSize(true) recycler.setHasFixedSize(true)
recycler.adapter = adapter recycler.adapter = adapter
swipe_refresh.addView(recycler) 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() { recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recycler: RecyclerView, newState: Int) { override fun onScrollStateChanged(recycler: RecyclerView, newState: Int) {
// Disable swipe refresh when view is not at the top // 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.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt())
swipe_refresh.setOnRefreshListener { swipe_refresh.setOnRefreshListener {
if (!LibraryUpdateService.isRunning(context)) { if (!LibraryUpdateService.isRunning(context)) {
libraryPresenter.categories.getOrNull(position)?.let { LibraryUpdateService.start(context, true, category)
LibraryUpdateService.start(context, true, it) context.toast(R.string.updating_category)
context.toast(R.string.updating_category)
}
} }
// It can be a very long operation, so we disable swipe refresh and show a toast. // It can be a very long operation, so we disable swipe refresh and show a toast.
swipe_refresh.isRefreshing = false swipe_refresh.isRefreshing = false
} }
} }
override fun onDestroyView() { fun onBind(category: Category) {
searchSubscription?.unsubscribe() this.category = category
super.onDestroyView()
}
override fun onResume() { val presenter = fragment.presenter
super.onResume()
libraryMangaSubscription = libraryPresenter.libraryMangaSubject 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) } .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() libraryMangaSubscription?.unsubscribe()
super.onPause() selectionSubscription?.unsubscribe()
} super.onDetachedFromWindow()
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(POSITION_KEY, position)
adapter.onSaveInstanceState(outState)
super.onSaveInstanceState(outState)
} }
/** /**
@ -169,17 +150,61 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
* @param event the event received. * @param event the event received.
*/ */
fun onNextLibraryManga(event: LibraryMangaEvent) { 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. // 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. // Update the category with its manga.
adapter.setItems(mangaForCategory) 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 { override fun onListItemClick(position: Int): Boolean {
// If the action mode is created and the position is valid, toggle the selection. // If the action mode is created and the position is valid, toggle the selection.
val item = adapter.getItem(position) ?: return false val item = adapter.getItem(position) ?: return false
if (libraryFragment.actionMode != null) { if (adapter.mode == FlexibleAdapter.MODE_MULTI) {
toggleSelection(position) toggleSelection(position)
return true return true
} else { } else {
@ -206,7 +231,7 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
* @param position the position of the element clicked. * @param position the position of the element clicked.
*/ */
override fun onListItemLongClick(position: Int) { override fun onListItemLongClick(position: Int) {
libraryFragment.createActionModeIfNeeded() fragment.createActionModeIfNeeded()
toggleSelection(position) toggleSelection(position)
} }
@ -217,63 +242,24 @@ class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemCli
*/ */
private fun openManga(manga: Manga) { private fun openManga(manga: Manga) {
// Notify the presenter a manga is being opened. // Notify the presenter a manga is being opened.
libraryPresenter.onOpenManga() fragment.presenter.onOpenManga()
// Create a new activity with the manga. // Create a new activity with the manga.
val intent = MangaActivity.newIntent(context, 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. * @param position the position to toggle.
*/ */
private fun toggleSelection(position: Int) { private fun toggleSelection(position: Int) {
val library = libraryFragment val manga = adapter.getItem(position) ?: return
// Toggle the selection. fragment.presenter.setSelection(manga, !adapter.isSelected(position))
adapter.toggleSelection(position, false) fragment.invalidateActionMode()
// 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()
}
} }
/**
* 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
} }

View File

@ -11,7 +11,6 @@ import android.support.v7.widget.SearchView
import android.view.* import android.view.*
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga 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 kotlinx.android.synthetic.main.fragment_library.*
import nucleus.factory.RequiresPresenter import nucleus.factory.RequiresPresenter
import rx.Subscription import rx.Subscription
import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.IOException import java.io.IOException
@ -66,8 +66,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
/** /**
* Action mode for manga selection. * Action mode for manga selection.
*/ */
var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private set
/** /**
* Selected manga for editing its cover. * Selected manga for editing its cover.
@ -91,14 +90,8 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
private set 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 private var numColumnsSubscription: Subscription? = null
companion object { companion object {
@ -141,7 +134,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
override fun onViewCreated(view: View, savedState: Bundle?) { override fun onViewCreated(view: View, savedState: Bundle?) {
setToolbarTitle(getString(R.string.label_library)) setToolbarTitle(getString(R.string.label_library))
adapter = LibraryAdapter(childFragmentManager) adapter = LibraryAdapter(this)
view_pager.adapter = adapter view_pager.adapter = adapter
view_pager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { view_pager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
@ -154,6 +147,9 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
activeCategory = savedState.getInt(CATEGORY_KEY) activeCategory = savedState.getInt(CATEGORY_KEY)
query = savedState.getString(QUERY_KEY) query = savedState.getString(QUERY_KEY)
presenter.searchSubject.onNext(query) presenter.searchSubject.onNext(query)
if (presenter.selectedMangas.isNotEmpty()) {
createActionModeIfNeeded()
}
} else { } else {
activeCategory = preferences.lastUsedCategory().getOrDefault() activeCategory = preferences.lastUsedCategory().getOrDefault()
} }
@ -261,8 +257,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
* Applies filter change * Applies filter change
*/ */
private fun onFilterCheckboxChanged() { private fun onFilterCheckboxChanged() {
presenter.updateLibrary() presenter.resubscribeLibrary()
adapter.refreshRegisteredAdapters()
activity.supportInvalidateOptionsMenu() activity.supportInvalidateOptionsMenu()
} }
@ -278,11 +273,11 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
* Reattaches the adapter to the view pager to recreate fragments * Reattaches the adapter to the view pager to recreate fragments
*/ */
private fun reattachAdapter() { private fun reattachAdapter() {
// pool.clear()
// pool = RecyclerView.RecycledViewPool()
val position = view_pager.currentItem val position = view_pager.currentItem
adapter.recycle = false
view_pager.adapter = adapter view_pager.adapter = adapter
view_pager.currentItem = position view_pager.currentItem = position
adapter.recycle = true
} }
/** /**
@ -323,7 +318,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
R.string.information_empty_library, R.drawable.ic_book_black_128dp) R.string.information_empty_library, R.drawable.ic_book_black_128dp)
// Get the current active category. // 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 // Set the categories
adapter.categories = categories adapter.categories = categories
@ -339,31 +334,42 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
} }
/** /**
* Sets the title of the action mode. * Creates the action mode if it's not created already.
*
* @param count the number of items selected.
*/ */
fun setContextTitle(count: Int) { fun createActionModeIfNeeded() {
actionMode?.title = getString(R.string.label_selected, count) if (actionMode == null) {
actionMode = activity.startSupportActionMode(this)
}
} }
/** /**
* Sets the visibility of the edit cover item. * Destroys the action mode.
*
* @param count the number of items selected.
*/ */
fun setVisibilityOfCoverEdit(count: Int) { fun destroyActionModeIfNeeded() {
// If count = 1 display edit button actionMode?.finish()
actionMode?.menu?.findItem(R.id.action_edit_cover)?.isVisible = count == 1 }
/**
* Invalidates the action mode, forcing it to refresh its content.
*/
fun invalidateActionMode() {
actionMode?.invalidate()
} }
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.library_selection, menu) mode.menuInflater.inflate(R.menu.library_selection, menu)
adapter.setSelectionMode(FlexibleAdapter.MODE_MULTI)
return true return true
} }
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { 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 return false
} }
@ -381,18 +387,10 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
} }
override fun onDestroyActionMode(mode: ActionMode) { override fun onDestroyActionMode(mode: ActionMode) {
adapter.setSelectionMode(FlexibleAdapter.MODE_SINGLE) presenter.clearSelections()
presenter.selectedMangas.clear()
actionMode = null actionMode = null
} }
/**
* Destroys the action mode.
*/
fun destroyActionModeIfNeeded() {
actionMode?.finish()
}
/** /**
* Changes the cover for the selected manga. * Changes the cover for the selected manga.
* *
@ -422,14 +420,14 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
context.contentResolver.openInputStream(data.data).use { context.contentResolver.openInputStream(data.data).use {
// Update cover to selected file, show error if something went wrong // Update cover to selected file, show error if something went wrong
if (presenter.editCoverWithStream(it, manga)) { if (presenter.editCoverWithStream(it, manga)) {
adapter.refreshRegisteredAdapters() // TODO refresh cover
} else { } else {
context.toast(R.string.notification_manga_update_failed) context.toast(R.string.notification_manga_update_failed)
} }
} }
} catch (e: IOException) { } catch (error: IOException) {
context.toast(R.string.notification_manga_update_failed) context.toast(R.string.notification_manga_update_failed)
e.printStackTrace() Timber.e(error, error.message)
} }
} }
@ -476,20 +474,4 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
.show() .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()
}
} }

View File

@ -16,6 +16,7 @@ import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import rx.subjects.BehaviorSubject import rx.subjects.BehaviorSubject
import rx.subjects.PublishSubject
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -29,22 +30,27 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
/** /**
* Categories of the library. * Categories of the library.
*/ */
lateinit var categories: List<Category> var categories: List<Category> = emptyList()
/** /**
* Currently selected manga. * Currently selected manga.
*/ */
var selectedMangas = mutableListOf<Manga>() val selectedMangas = mutableListOf<Manga>()
/** /**
* Search query of the library. * Search query of the library.
*/ */
val searchSubject: BehaviorSubject<String> = BehaviorSubject.create<String>() val searchSubject: BehaviorSubject<String> = BehaviorSubject.create()
/** /**
* Subject to notify the library's viewpager for updates. * Subject to notify the library's viewpager for updates.
*/ */
val libraryMangaSubject: BehaviorSubject<LibraryMangaEvent> = BehaviorSubject.create<LibraryMangaEvent>() val libraryMangaSubject: BehaviorSubject<LibraryMangaEvent> = BehaviorSubject.create()
/**
* Subject to notify the UI of selection updates.
*/
val selectionSubject: PublishSubject<LibrarySelectionEvent> = PublishSubject.create()
/** /**
* Database. * Database.
@ -149,7 +155,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
/** /**
* Resubscribes to library. * Resubscribes to library.
*/ */
fun updateLibrary() { fun resubscribeLibrary() {
start(GET_LIBRARY) start(GET_LIBRARY)
} }
@ -219,11 +225,21 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
fun setSelection(manga: Manga, selected: Boolean) { fun setSelection(manga: Manga, selected: Boolean) {
if (selected) { if (selected) {
selectedMangas.add(manga) selectedMangas.add(manga)
selectionSubject.onNext(LibrarySelectionEvent.Selected(manga))
} else { } else {
selectedMangas.remove(manga) 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. * Returns the common categories for the given list of manga.
* *

View File

@ -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()
}

View File

@ -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<View>()
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
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<eu.kanade.tachiyomi.ui.library.LibraryCategoryFragment
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.widget.SwipeRefreshLayout>
</eu.kanade.tachiyomi.ui.library.LibraryCategoryFragment>