Improve performance
This commit is contained in:
@@ -83,6 +83,7 @@ dependencies {
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
implementation 'androidx.asynclayoutinflater:asynclayoutinflater:1.0.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.work:work-runtime-ktx:2.7.1'
|
||||
implementation 'com.google.android.material:material:1.6.0-alpha03'
|
||||
|
||||
@@ -12,11 +12,14 @@ import org.koitharu.kotatsu.local.data.CbzFetcher
|
||||
val uiModule
|
||||
get() = module {
|
||||
single {
|
||||
val httpClient = get<OkHttpClient>().newBuilder()
|
||||
.cache(CoilUtils.createDefaultCache(androidContext()))
|
||||
.build()
|
||||
val httpClientFactory = {
|
||||
get<OkHttpClient>().newBuilder()
|
||||
.cache(CoilUtils.createDefaultCache(androidContext()))
|
||||
.build()
|
||||
}
|
||||
ImageLoader.Builder(androidContext())
|
||||
.okHttpClient(httpClient)
|
||||
.okHttpClient(httpClientFactory)
|
||||
.launchInterceptorChainOnMainThread(false)
|
||||
.componentRegistry(
|
||||
ComponentRegistry.Builder()
|
||||
.add(CbzFetcher())
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.view.*
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
@@ -19,17 +18,13 @@ import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
|
||||
import org.koitharu.kotatsu.main.ui.AppBarOwner
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.utils.RecycledViewPoolHolder
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.measureHeight
|
||||
import org.koitharu.kotatsu.utils.ext.showPopupMenu
|
||||
import java.util.*
|
||||
|
||||
class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
|
||||
FavouritesTabLongClickListener, CategoriesEditDelegate.CategoriesEditCallback,
|
||||
RecycledViewPoolHolder {
|
||||
|
||||
override val recycledViewPool = RecyclerView.RecycledViewPool()
|
||||
FavouritesTabLongClickListener, CategoriesEditDelegate.CategoriesEditCallback {
|
||||
|
||||
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
|
||||
private val editDelegate by lazy(LazyThreadSafetyMode.NONE) {
|
||||
|
||||
@@ -24,16 +24,21 @@ import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.databinding.FragmentListBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.list.ui.adapter.AsyncViewFactory
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter.Companion.ITEM_TYPE_MANGA_GRID
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.main.ui.AppBarOwner
|
||||
import org.koitharu.kotatsu.main.ui.MainActivity
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.utils.RecycledViewPoolHolder
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
|
||||
private const val PREFETCH_ITEM_LIST = 10
|
||||
private const val PREFETCH_ITEM_DETAILED = 8
|
||||
private const val PREFETCH_ITEM_GRID = 16
|
||||
|
||||
abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
PaginationScrollListener.Callback, MangaListListener,
|
||||
SwipeRefreshLayout.OnRefreshListener {
|
||||
@@ -45,6 +50,7 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
private val listCommitCallback = Runnable {
|
||||
spanSizeLookup.invalidateCache()
|
||||
}
|
||||
private var asyncViewFactory: AsyncViewFactory? = null
|
||||
open val isSwipeRefreshEnabled = true
|
||||
|
||||
protected abstract val viewModel: MangaListViewModel
|
||||
@@ -61,10 +67,12 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
asyncViewFactory = AsyncViewFactory(binding.recyclerView)
|
||||
listAdapter = MangaListAdapter(
|
||||
coil = get(),
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
listener = this,
|
||||
viewFactory = checkNotNull(asyncViewFactory),
|
||||
)
|
||||
paginationListener = PaginationScrollListener(4, this)
|
||||
with(binding.recyclerView) {
|
||||
@@ -79,10 +87,6 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
isEnabled = isSwipeRefreshEnabled
|
||||
}
|
||||
|
||||
(parentFragment as? RecycledViewPoolHolder)?.let {
|
||||
binding.recyclerView.setRecycledViewPool(it.recycledViewPool)
|
||||
}
|
||||
|
||||
viewModel.content.observe(viewLifecycleOwner, ::onListChanged)
|
||||
viewModel.onError.observe(viewLifecycleOwner, ::onError)
|
||||
viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged)
|
||||
@@ -93,6 +97,8 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
override fun onDestroyView() {
|
||||
listAdapter = null
|
||||
paginationListener = null
|
||||
asyncViewFactory?.clear()
|
||||
asyncViewFactory = null
|
||||
spanSizeLookup.invalidateCache()
|
||||
super.onDestroyView()
|
||||
}
|
||||
@@ -215,18 +221,26 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
with(binding.recyclerView) {
|
||||
clearItemDecorations()
|
||||
removeOnLayoutChangeListener(spanResolver)
|
||||
asyncViewFactory?.clear()
|
||||
val isListPending = viewModel.isListPending()
|
||||
when (mode) {
|
||||
ListMode.LIST -> {
|
||||
layoutManager = FitHeightLinearLayoutManager(context)
|
||||
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
|
||||
addItemDecoration(SpacingItemDecoration(spacing))
|
||||
updatePadding(left = spacing, right = spacing)
|
||||
if (isListPending) {
|
||||
asyncViewFactory?.prefetch(R.layout.item_manga_list, PREFETCH_ITEM_LIST)
|
||||
}
|
||||
}
|
||||
ListMode.DETAILED_LIST -> {
|
||||
layoutManager = FitHeightLinearLayoutManager(context)
|
||||
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
|
||||
updatePadding(left = spacing, right = spacing)
|
||||
addItemDecoration(SpacingItemDecoration(spacing))
|
||||
if (isListPending) {
|
||||
asyncViewFactory?.prefetch(R.layout.item_manga_list_details, PREFETCH_ITEM_DETAILED)
|
||||
}
|
||||
}
|
||||
ListMode.GRID -> {
|
||||
layoutManager = FitHeightGridLayoutManager(context, spanResolver.spanCount).also {
|
||||
@@ -236,6 +250,9 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
addItemDecoration(SpacingItemDecoration(spacing))
|
||||
updatePadding(left = spacing, right = spacing)
|
||||
addOnLayoutChangeListener(spanResolver)
|
||||
if (isListPending) {
|
||||
asyncViewFactory?.prefetch(R.layout.item_manga_grid, PREFETCH_ITEM_GRID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,7 +273,7 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
val total =
|
||||
(binding.recyclerView.layoutManager as? GridLayoutManager)?.spanCount ?: return 1
|
||||
return when (listAdapter?.getItemViewType(position)) {
|
||||
MangaListAdapter.ITEM_TYPE_MANGA_GRID -> 1
|
||||
ITEM_TYPE_MANGA_GRID -> 1
|
||||
else -> total
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaListModel
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
|
||||
@@ -41,4 +44,10 @@ abstract class MangaListViewModel(
|
||||
abstract fun onRefresh()
|
||||
|
||||
abstract fun onRetry()
|
||||
|
||||
fun isListPending(): Boolean {
|
||||
return content.value?.any {
|
||||
it is MangaListModel || it is MangaGridModel || it is MangaListDetailedModel
|
||||
} != true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import android.util.Log
|
||||
import android.util.SparseArray
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
|
||||
import androidx.core.util.valueIterator
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import java.util.*
|
||||
|
||||
class AsyncViewFactory(private val parent: ViewGroup) : AsyncLayoutInflater.OnInflateFinishedListener {
|
||||
|
||||
private val asyncInflater = AsyncLayoutInflater(parent.context)
|
||||
private val pool = SparseArray<LinkedList<View>>()
|
||||
|
||||
override fun onInflateFinished(view: View, resid: Int, parent: ViewGroup?) {
|
||||
var list = pool.get(resid)
|
||||
if (list != null) {
|
||||
list.addLast(view)
|
||||
} else {
|
||||
list = LinkedList()
|
||||
list.add(view)
|
||||
pool.put(resid, list)
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
pool.valueIterator().forEach {
|
||||
if (it.isNotEmpty()) {
|
||||
Log.w("AsyncViewFactory", "You have ${it.size} unconsumed prefetched items")
|
||||
}
|
||||
}
|
||||
}
|
||||
pool.clear()
|
||||
}
|
||||
|
||||
fun prefetch(@LayoutRes resId: Int, count: Int) {
|
||||
if (count <= 0) return
|
||||
repeat(count) {
|
||||
asyncInflater.inflate(resId, parent, this)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(@LayoutRes resId: Int): View? {
|
||||
val result = pool.get(resId)?.removeFirstOrNull()
|
||||
if (BuildConfig.DEBUG && result == null) {
|
||||
Log.w("AsyncViewFactory", "Item requested but missing")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun getCount(@LayoutRes resId: Int): Int {
|
||||
return pool[resId]?.size ?: 0
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,16 @@ import org.koitharu.kotatsu.utils.ext.referer
|
||||
fun mangaGridItemAD(
|
||||
coil: ImageLoader,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
clickListener: OnListItemClickListener<Manga>
|
||||
clickListener: OnListItemClickListener<Manga>,
|
||||
viewFactory: AsyncViewFactory,
|
||||
) = adapterDelegateViewBinding<MangaGridModel, ListModel, ItemMangaGridBinding>(
|
||||
{ inflater, parent -> ItemMangaGridBinding.inflate(inflater, parent, false) }
|
||||
{ inflater, parent ->
|
||||
viewFactory[R.layout.item_manga_grid]?.let {
|
||||
ItemMangaGridBinding.bind(it)
|
||||
} ?: run {
|
||||
ItemMangaGridBinding.inflate(inflater, parent, false)
|
||||
}
|
||||
}
|
||||
) {
|
||||
|
||||
var imageRequest: Disposable? = null
|
||||
@@ -52,6 +59,7 @@ fun mangaGridItemAD(
|
||||
itemView.clearBadge(badge)
|
||||
badge = null
|
||||
imageRequest?.dispose()
|
||||
imageRequest = null
|
||||
CoilUtils.clear(binding.imageViewCover)
|
||||
binding.imageViewCover.setImageDrawable(null)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
||||
@@ -12,19 +13,20 @@ class MangaListAdapter(
|
||||
coil: ImageLoader,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
listener: MangaListListener,
|
||||
viewFactory: AsyncViewFactory,
|
||||
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
|
||||
|
||||
init {
|
||||
delegatesManager
|
||||
.addDelegate(
|
||||
ITEM_TYPE_MANGA_LIST,
|
||||
mangaListItemAD(coil, lifecycleOwner, listener)
|
||||
mangaListItemAD(coil, lifecycleOwner, listener, viewFactory)
|
||||
)
|
||||
.addDelegate(
|
||||
ITEM_TYPE_MANGA_LIST_DETAILED,
|
||||
mangaListDetailedItemAD(coil, lifecycleOwner, listener)
|
||||
mangaListDetailedItemAD(coil, lifecycleOwner, listener, viewFactory)
|
||||
)
|
||||
.addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, listener))
|
||||
.addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, listener, viewFactory))
|
||||
.addDelegate(ITEM_TYPE_LOADING_FOOTER, loadingFooterAD())
|
||||
.addDelegate(ITEM_TYPE_LOADING_STATE, loadingStateAD())
|
||||
.addDelegate(ITEM_TYPE_DATE, relatedDateItemAD())
|
||||
|
||||
@@ -20,9 +20,16 @@ import org.koitharu.kotatsu.utils.ext.textAndVisible
|
||||
fun mangaListDetailedItemAD(
|
||||
coil: ImageLoader,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
clickListener: OnListItemClickListener<Manga>
|
||||
clickListener: OnListItemClickListener<Manga>,
|
||||
viewFactory: AsyncViewFactory,
|
||||
) = adapterDelegateViewBinding<MangaListDetailedModel, ListModel, ItemMangaListDetailsBinding>(
|
||||
{ inflater, parent -> ItemMangaListDetailsBinding.inflate(inflater, parent, false) }
|
||||
{ inflater, parent ->
|
||||
viewFactory[R.layout.item_manga_list_details]?.let {
|
||||
ItemMangaListDetailsBinding.bind(it)
|
||||
} ?: run {
|
||||
ItemMangaListDetailsBinding.inflate(inflater, parent, false)
|
||||
}
|
||||
}
|
||||
) {
|
||||
|
||||
var imageRequest: Disposable? = null
|
||||
@@ -56,6 +63,7 @@ fun mangaListDetailedItemAD(
|
||||
itemView.clearBadge(badge)
|
||||
badge = null
|
||||
imageRequest?.dispose()
|
||||
imageRequest = null
|
||||
CoilUtils.clear(binding.imageViewCover)
|
||||
binding.imageViewCover.setImageDrawable(null)
|
||||
}
|
||||
|
||||
@@ -20,9 +20,16 @@ import org.koitharu.kotatsu.utils.ext.textAndVisible
|
||||
fun mangaListItemAD(
|
||||
coil: ImageLoader,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
clickListener: OnListItemClickListener<Manga>
|
||||
clickListener: OnListItemClickListener<Manga>,
|
||||
viewFactory: AsyncViewFactory,
|
||||
) = adapterDelegateViewBinding<MangaListModel, ListModel, ItemMangaListBinding>(
|
||||
{ inflater, parent -> ItemMangaListBinding.inflate(inflater, parent, false) }
|
||||
{ inflater, parent ->
|
||||
viewFactory[R.layout.item_manga_list]?.let {
|
||||
ItemMangaListBinding.bind(it)
|
||||
} ?: run {
|
||||
ItemMangaListBinding.inflate(inflater, parent, false)
|
||||
}
|
||||
}
|
||||
) {
|
||||
|
||||
var imageRequest: Disposable? = null
|
||||
@@ -54,6 +61,7 @@ fun mangaListItemAD(
|
||||
itemView.clearBadge(badge)
|
||||
badge = null
|
||||
imageRequest?.dispose()
|
||||
imageRequest = null
|
||||
CoilUtils.clear(binding.imageViewCover)
|
||||
binding.imageViewCover.setImageDrawable(null)
|
||||
}
|
||||
|
||||
@@ -63,8 +63,11 @@ private const val TAG_PRIMARY = "primary"
|
||||
private const val TAG_SEARCH = "search"
|
||||
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>(),
|
||||
NavigationView.OnNavigationItemSelectedListener, AppBarOwner,
|
||||
View.OnClickListener, View.OnFocusChangeListener, SearchSuggestionListener {
|
||||
NavigationView.OnNavigationItemSelectedListener,
|
||||
AppBarOwner,
|
||||
View.OnClickListener,
|
||||
View.OnFocusChangeListener,
|
||||
SearchSuggestionListener {
|
||||
|
||||
private val viewModel by viewModel<MainViewModel>()
|
||||
private val searchSuggestionViewModel by viewModel<SearchSuggestionViewModel>()
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.koitharu.kotatsu.utils
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
interface RecycledViewPoolHolder {
|
||||
|
||||
val recycledViewPool: RecyclerView.RecycledViewPool
|
||||
}
|
||||
Reference in New Issue
Block a user