mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 06:17:57 +01:00 
			
		
		
		
	Library views recycling
This commit is contained in:
		| @@ -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]. | ||||
|          */ | ||||
|         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 | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|     } | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user