mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-19 02:49:18 +01:00
Library views recycling
This commit is contained in:
parent
97454ca162
commit
dfb2487640
@ -27,7 +27,7 @@ abstract class FlexibleViewHolder(view: View,
|
||||
return true
|
||||
}
|
||||
|
||||
protected fun toggleActivation() {
|
||||
fun toggleActivation() {
|
||||
itemView.isActivated = adapter.isSelected(adapterPosition)
|
||||
}
|
||||
|
||||
|
@ -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<Category>? = null
|
||||
var categories: List<Category> = emptyList()
|
||||
// This setter helps to not refresh the adapter if the reference to the list doesn't change.
|
||||
set(value) {
|
||||
if (field !== value) {
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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 }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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].
|
||||
* Subscription of the library selections.
|
||||
*/
|
||||
const val POSITION_KEY = "position_key"
|
||||
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)
|
||||
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
|
||||
|
||||
val presenter = fragment.presenter
|
||||
|
||||
searchSubscription = presenter.searchSubject.subscribe { text ->
|
||||
adapter.searchText = text
|
||||
adapter.updateDataSet()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
libraryMangaSubscription = libraryPresenter.libraryMangaSubject
|
||||
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
|
||||
|
||||
}
|
||||
|
@ -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<LibraryPresenter>(), 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<LibraryPresenter>(), 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<LibraryPresenter>(), 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<LibraryPresenter>(), 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<LibraryPresenter>(), ActionMode.Callback
|
||||
* Applies filter change
|
||||
*/
|
||||
private fun onFilterCheckboxChanged() {
|
||||
presenter.updateLibrary()
|
||||
adapter.refreshRegisteredAdapters()
|
||||
presenter.resubscribeLibrary()
|
||||
activity.supportInvalidateOptionsMenu()
|
||||
}
|
||||
|
||||
@ -278,11 +273,11 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), 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<LibraryPresenter>(), 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<LibraryPresenter>(), 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<LibraryPresenter>(), 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<LibraryPresenter>(), 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<LibraryPresenter>(), 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()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<LibraryFragment>() {
|
||||
/**
|
||||
* Categories of the library.
|
||||
*/
|
||||
lateinit var categories: List<Category>
|
||||
var categories: List<Category> = emptyList()
|
||||
|
||||
/**
|
||||
* Currently selected manga.
|
||||
*/
|
||||
var selectedMangas = mutableListOf<Manga>()
|
||||
val selectedMangas = mutableListOf<Manga>()
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
@ -149,7 +155,7 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
||||
/**
|
||||
* Resubscribes to library.
|
||||
*/
|
||||
fun updateLibrary() {
|
||||
fun resubscribeLibrary() {
|
||||
start(GET_LIBRARY)
|
||||
}
|
||||
|
||||
@ -219,11 +225,21 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
||||
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.
|
||||
*
|
||||
|
@ -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()
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
14
app/src/main/res/layout/item_library_category.xml
Normal file
14
app/src/main/res/layout/item_library_category.xml
Normal 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>
|
Loading…
Reference in New Issue
Block a user