Fix browse scrolling back to top

same with extensions + migrations
This commit is contained in:
Jays2Kings 2021-03-31 16:28:53 -04:00
parent f0fb7b3d49
commit 965af17a2a
9 changed files with 261 additions and 121 deletions

View File

@ -151,7 +151,7 @@ class PreferencesHelper(val context: Context) {
fun autoUpdateTrack() = prefs.getBoolean(Keys.autoUpdateTrack, true)
fun lastUsedCatalogueSource() = rxPrefs.getLong(Keys.lastUsedCatalogueSource, -1)
fun lastUsedCatalogueSource() = flowPrefs.getLong(Keys.lastUsedCatalogueSource, -1)
fun lastUsedCategory() = rxPrefs.getInteger(Keys.lastUsedCategory, 0)

View File

@ -6,6 +6,9 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.core.view.get
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.PagerAdapter.POSITION_NONE
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayout
import eu.davidea.flexibleadapter.FlexibleAdapter
@ -28,9 +31,7 @@ import eu.kanade.tachiyomi.util.view.collapse
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.view.expand
import eu.kanade.tachiyomi.util.view.isExpanded
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.withFadeTransaction
import eu.kanade.tachiyomi.widget.ViewPagerAdapter
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -49,9 +50,12 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
/**
* Adapter containing the list of extensions
*/
private var adapter: FlexibleAdapter<IFlexible<*>>? = null
private var adapter: ExtensionAdapter? = null
private var migAdapter: FlexibleAdapter<IFlexible<*>>? = null
val adapters
get() = listOf(adapter, migAdapter)
val presenter = ExtensionBottomPresenter(this)
private var extensions: List<ExtensionItem> = emptyList()
@ -59,9 +63,12 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
private lateinit var binding: ExtensionsBottomSheetBinding
lateinit var controller: BrowseController
var boundViews = arrayListOf<RecyclerWithScrollerView>()
val extensionFrameLayout = RecyclerWithScrollerBinding.inflate(LayoutInflater.from(context))
val migrationFrameLayout = RecyclerWithScrollerBinding.inflate(LayoutInflater.from(context))
val extensionFrameLayout: RecyclerWithScrollerView?
get() = binding.pager.findViewWithTag("TabbedRecycler0") as? RecyclerWithScrollerView
val migrationFrameLayout: RecyclerWithScrollerView?
get() = binding.pager.findViewWithTag("TabbedRecycler1") as? RecyclerWithScrollerView
override fun onFinishInflate() {
super.onFinishInflate()
@ -71,29 +78,22 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
fun onCreate(controller: BrowseController) {
// Initialize adapter, scroll listener and recycler views
adapter = ExtensionAdapter(this)
migAdapter = ExtensionAdapter(this)
adapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
if (migAdapter == null) {
migAdapter = SourceAdapter(this)
}
migAdapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
sheetBehavior = BottomSheetBehavior.from(this)
// Create recycler and set adapter.
val extRecyler = extensionFrameLayout.recycler
val migRecyler = migrationFrameLayout.recycler
extRecyler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
extRecyler.adapter = adapter
extRecyler.setHasFixedSize(true)
extRecyler.addItemDecoration(ExtensionDividerItemDecoration(context))
migRecyler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
migRecyler.setHasFixedSize(true)
adapter?.fastScroller = extensionFrameLayout.fastScroller
binding.pager.adapter = TabbedSheetAdapter()
binding.tabs.setupWithViewPager(binding.pager)
this.controller = controller
binding.pager.doOnApplyWindowInsets { _, _, _ ->
val bottomBar = controller.activityBinding?.bottomNav
extRecyler.updatePaddingRelative(bottom = bottomBar?.height ?: 0)
migRecyler.updatePaddingRelative(bottom = bottomBar?.height ?: 0)
// extRecyler?.updatePaddingRelative(bottom = bottomBar?.height ?: 0)
// migRecyler?.updatePaddingRelative(bottom = bottomBar?.height ?: 0)
}
binding.pager.adapter = TabbedSheetAdapter()
binding.tabs.setupWithViewPager(binding.pager)
binding.tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
if (canExpand) {
@ -103,18 +103,18 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
when (tab?.position) {
0 -> extensionFrameLayout
else -> migrationFrameLayout
}.recycler.isNestedScrollingEnabled = true
}?.binding?.recycler?.isNestedScrollingEnabled = true
when (tab?.position) {
0 -> extensionFrameLayout
else -> migrationFrameLayout
}.recycler.requestLayout()
}?.binding?.recycler?.requestLayout()
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
when (tab?.position) {
0 -> extensionFrameLayout
else -> migrationFrameLayout
}.recycler.isNestedScrollingEnabled = false
}?.binding?.recycler?.isNestedScrollingEnabled = false
if (tab?.position == 1) {
presenter.deselectSource()
}
@ -125,7 +125,7 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
when (tab?.position) {
0 -> extensionFrameLayout
else -> migrationFrameLayout
}.recycler.isNestedScrollingEnabled = true
}?.binding?.recycler?.isNestedScrollingEnabled = true
}
})
presenter.onCreate()
@ -144,7 +144,7 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
fun updatedNestedRecyclers() {
listOf(extensionFrameLayout, migrationFrameLayout).forEachIndexed { index, recyclerWithScrollerBinding ->
recyclerWithScrollerBinding.recycler.isNestedScrollingEnabled = binding.pager.currentItem == index
recyclerWithScrollerBinding?.binding?.recycler?.isNestedScrollingEnabled = binding.pager.currentItem == index
}
}
@ -162,7 +162,7 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
}
override fun onButtonClick(position: Int) {
val extension = (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return
val extension = (migAdapter?.getItem(position) as? ExtensionItem)?.extension ?: return
when (extension) {
is Extension.Installed -> {
if (!extension.hasUpdate) {
@ -249,21 +249,19 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
}
fun setMigrationSources(sources: List<SourceItem>) {
val migRecyler = migrationFrameLayout.recycler
if (migAdapter !is SourceAdapter) {
migAdapter = SourceAdapter(this)
migRecyler.adapter = migAdapter
migAdapter?.fastScroller = migrationFrameLayout.fastScroller
migrationFrameLayout?.onBind(migAdapter!!)
migAdapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
}
migAdapter?.updateDataSet(sources, true)
}
fun setMigrationManga(manga: List<MangaItem>?) {
val migRecyler = migrationFrameLayout.recycler
if (migAdapter !is MangaAdapter) {
migAdapter = MangaAdapter(this)
migRecyler.adapter = migAdapter
migAdapter?.fastScroller = migrationFrameLayout.fastScroller
migrationFrameLayout?.onBind(migAdapter!!)
migAdapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
}
migAdapter?.updateDataSet(manga, true)
}
@ -301,14 +299,7 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
presenter.uninstallExtension(pkgName)
}
private inner class TabbedSheetAdapter : ViewPagerAdapter() {
override fun createView(container: ViewGroup, position: Int): View {
return when (position) {
0 -> extensionFrameLayout.root
else -> migrationFrameLayout.root
}
}
private inner class TabbedSheetAdapter : RecyclerViewPagerAdapter() {
override fun getCount(): Int {
return 2
@ -322,5 +313,60 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
}
)
}
/**
* Creates a new view for this adapter.
*
* @return a new view.
*/
override fun createView(container: ViewGroup): View {
val binding = RecyclerWithScrollerBinding.inflate(LayoutInflater.from(container.context), container, false)
val view: RecyclerWithScrollerView = binding.root
view.setUp(this@ExtensionBottomSheet, binding)
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 RecyclerWithScrollerView).onBind(adapters[position]!!)
view.setTag("TabbedRecycler$position")
boundViews.add(view)
}
/**
* 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 RecyclerWithScrollerView).onRecycle()
boundViews.remove(view)
}
/**
* Returns the position of the view.
*/
override fun getItemPosition(obj: Any): Int {
val view = (obj as? RecyclerWithScrollerView) ?: return POSITION_NONE
val index = adapters.indexOfFirst { it == view.binding?.recycler?.adapter }
return if (index == -1) POSITION_NONE else index
}
/**
* Called when the view of this adapter is being destroyed.
*/
fun onDestroy() {
/*for (view in boundViews) {
if (view is LibraryCategoryView) {
view.onDestroy()
}
}*/
}
}
}

View File

@ -0,0 +1,34 @@
package eu.kanade.tachiyomi.ui.extension
import android.view.View
import android.view.ViewGroup
import com.nightlynexus.viewstatepageradapter.ViewStatePagerAdapter
import java.util.Stack
abstract class RecyclerViewPagerAdapter : ViewStatePagerAdapter() {
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 createView(container: ViewGroup, position: Int): View {
val view = if (pool.isNotEmpty()) pool.pop() else createView(container)
bindView(view, position)
return view
}
override fun destroyView(container: ViewGroup, position: Int, view: View) {
recycleView(view, position)
if (recycle) pool.push(view)
}
}

View File

@ -0,0 +1,25 @@
package eu.kanade.tachiyomi.ui.extension
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.databinding.RecyclerWithScrollerBinding
class RecyclerWithScrollerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs) {
var binding: RecyclerWithScrollerBinding? = null
fun setUp(sheet: ExtensionBottomSheet, binding: RecyclerWithScrollerBinding) {
binding.recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
binding.recycler.setHasFixedSize(true)
binding.recycler.addItemDecoration(ExtensionDividerItemDecoration(context))
this.binding = binding
}
fun onBind(adapter: FlexibleAdapter<IFlexible<*>>) {
binding?.recycler?.adapter = adapter
adapter.fastScroller = binding?.fastScroller
}
}

View File

@ -367,7 +367,7 @@ class RecentsPresenter(
getRecents()
}
companion object {
private companion object {
var lastRecents: List<RecentMangaItem>? = null
}
}

View File

@ -13,6 +13,7 @@ import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.RouterTransaction
@ -27,7 +28,7 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.databinding.BrowseControllerBinding
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.extension.SettingsExtensionsController
import eu.kanade.tachiyomi.ui.main.BottomSheetController
import eu.kanade.tachiyomi.ui.main.MainActivity
@ -66,7 +67,7 @@ import kotlin.math.min
* [SourceAdapter.OnLatestClickListener] call function data on latest item click
*/
class BrowseController :
NucleusController<BrowseControllerBinding, SourcePresenter>(),
BaseController<BrowseControllerBinding>(),
FlexibleAdapter.OnItemClickListener,
SourceAdapter.SourceListener,
RootSearchInterface,
@ -109,9 +110,7 @@ class BrowseController :
} else view?.context?.getString(R.string.browse)
}
override fun createPresenter(): SourcePresenter {
return SourcePresenter()
}
val presenter = SourcePresenter(this)
override fun createBinding(inflater: LayoutInflater) = BrowseControllerBinding.inflate(inflater)
@ -119,29 +118,34 @@ class BrowseController :
super.onViewCreated(view)
adapter = SourceAdapter(this)
// Create binding.sourceRecycler and set adapter.
binding.sourceRecycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
// Create binding.recycler and set adapter.
binding.recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
binding.recycler.adapter = adapter
binding.sourceRecycler.adapter = adapter
adapter?.isSwipeEnabled = true
// binding.recycler.addItemDecoration(SourceDividerItemDecoration(view.context))
adapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
// binding.sourceRecycler.addItemDecoration(SourceDividerItemDecoration(view.context))
val attrsArray = intArrayOf(android.R.attr.actionBarSize)
val array = view.context.obtainStyledAttributes(attrsArray)
val appBarHeight = array.getDimensionPixelSize(0, 0)
array.recycle()
scrollViewWith(
binding.recycler,
binding.sourceRecycler,
afterInsets = {
headerHeight = it.systemWindowInsetTop + appBarHeight
binding.recycler.updatePaddingRelative(bottom = activityBinding?.bottomNav?.height ?: 0)
binding.sourceRecycler.updatePaddingRelative(bottom = activityBinding?.bottomNav?.height ?: 0)
},
onBottomNavUpdate = {
setBottomPadding()
}
)
binding.recycler.post {
binding.sourceRecycler.post {
setBottomSheetTabs(if (binding.bottomSheet.root.sheetBehavior.isCollapsed()) 0f else 1f)
activityBinding?.appBar?.elevation = min(
if (binding.bottomSheet.root.sheetBehavior.isCollapsed()) 0f else 1f * 15f,
if (binding.sourceRecycler.canScrollVertically(-1)) 15f else 0f
)
}
requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301)
@ -154,7 +158,7 @@ class BrowseController :
binding.shadow2.alpha = (1 - max(0f, progress)) * 0.25f
activityBinding?.appBar?.elevation = min(
(1f - progress) * 15f,
if (binding.recycler.canScrollVertically(-1)) 15f else 0f
if (binding.sourceRecycler.canScrollVertically(-1)) 15f else 0f
)
activityBinding?.appBar?.y = max(activityBinding!!.appBar.y, -headerHeight * (1 - progress))
val oldShow = showingExtensions
@ -200,6 +204,10 @@ class BrowseController :
if (showingExtensions) {
binding.bottomSheet.root.sheetBehavior?.expand()
}
presenter.onCreate()
if (presenter.sourceItems.isNotEmpty()) {
setSources(presenter.sourceItems, presenter.lastUsedItem)
}
}
fun updateTitleAndMenu() {
@ -256,10 +264,10 @@ class BrowseController :
)
binding.shadow2.translationY = pad
binding.bottomSheet.root.sheetBehavior?.peekHeight = 58.spToPx + padding
binding.bottomSheet.root.extensionFrameLayout.fastScroller.updateLayoutParams<ViewGroup.MarginLayoutParams> {
binding.bottomSheet.root.extensionFrameLayout?.binding?.fastScroller?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = -pad.toInt()
}
binding.bottomSheet.root.migrationFrameLayout.fastScroller.updateLayoutParams<ViewGroup.MarginLayoutParams> {
binding.bottomSheet.root.migrationFrameLayout?.binding?.fastScroller?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = -pad.toInt()
}
}
@ -293,6 +301,11 @@ class BrowseController :
super.onDestroyView(view)
}
override fun onDestroy() {
super.onDestroy()
presenter.onDestroy()
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (!type.isPush) {
@ -305,7 +318,7 @@ class BrowseController :
activityBinding?.appBar?.elevation =
when {
binding.bottomSheet.root.sheetBehavior.isExpanded() -> 0f
binding.recycler.canScrollVertically(-1) -> 15f
binding.sourceRecycler.canScrollVertically(-1) -> 15f
else -> 0f
}
} else {
@ -483,8 +496,9 @@ class BrowseController :
/**
* Called to update adapter containing sources.
*/
fun setSources(sources: List<IFlexible<*>>) {
adapter?.updateDataSet(sources)
fun setSources(sources: List<IFlexible<*>>, lastUsed: SourceItem?) {
adapter?.updateDataSet(sources, false)
setLastUsedSource(lastUsed)
}
/**

View File

@ -1,19 +1,20 @@
package eu.kanade.tachiyomi.ui.source
import android.os.Bundle
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import eu.kanade.tachiyomi.util.system.withUIContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.TreeMap
import java.util.concurrent.TimeUnit
/**
* Presenter of [BrowseController]
@ -23,32 +24,38 @@ import java.util.concurrent.TimeUnit
* @param preferences application preferences.
*/
class SourcePresenter(
val controller: BrowseController,
val sourceManager: SourceManager = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get()
) : BasePresenter<BrowseController>() {
) {
private var scope = CoroutineScope(Job() + Dispatchers.Default)
var sources = getEnabledSources()
/**
* Subscription for retrieving enabled sources.
*/
private var sourceSubscription: Subscription? = null
private var lastUsedSubscription: Subscription? = null
var sourceItems = emptyList<SourceItem>()
var lastUsedItem: SourceItem? = null
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
var lastUsedJob: Job? = null
fun onCreate() {
if (lastSources != null) {
if (sourceItems.isEmpty()) {
sourceItems = lastSources ?: emptyList()
}
lastUsedItem = lastUsedItemRem
lastSources = null
lastUsedItemRem = null
}
// Load enabled and last used sources
loadSources()
loadLastUsedSource()
}
/**
* Unsubscribe and create a new subscription to fetch enabled sources.
*/
private fun loadSources() {
sourceSubscription?.unsubscribe()
scope.launch {
val pinnedSources = mutableListOf<SourceItem>()
val pinnedCatalogues = preferences.pinnedCatalogues().getOrDefault()
@ -61,7 +68,7 @@ class SourcePresenter(
}
}
val byLang = sources.groupByTo(map, { it.lang })
var sourceItems = byLang.flatMap {
sourceItems = byLang.flatMap {
val langItem = LangItem(it.key)
it.value.map { source ->
val isPinned = source.id.toString() in pinnedCatalogues
@ -77,32 +84,42 @@ class SourcePresenter(
sourceItems = pinnedSources + sourceItems
}
sourceSubscription = Observable.just(sourceItems)
.subscribeLatestCache(BrowseController::setSources)
lastUsedItem = getLastUsedSource(preferences.lastUsedCatalogueSource().get())
withUIContext {
controller.setSources(sourceItems, lastUsedItem)
loadLastUsedSource()
}
}
}
private fun loadLastUsedSource() {
lastUsedSubscription?.unsubscribe()
val sharedObs = preferences.lastUsedCatalogueSource().asObservable().share()
lastUsedJob?.cancel()
lastUsedJob = preferences.lastUsedCatalogueSource().asFlow()
.onEach {
lastUsedItem = getLastUsedSource(it)
withUIContext {
controller.setLastUsedSource(lastUsedItem)
}
}.launchIn(scope)
}
// Emit the first item immediately but delay subsequent emissions by 500ms.
lastUsedSubscription = Observable.merge(
sharedObs.take(1),
sharedObs.skip(1).delay(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
).distinctUntilChanged().map {
(sourceManager.get(it) as? CatalogueSource)?.let { source ->
private fun getLastUsedSource(value: Long): SourceItem? {
return (sourceManager.get(value) as? CatalogueSource)?.let { source ->
val pinnedCatalogues = preferences.pinnedCatalogues().getOrDefault()
val isPinned = source.id.toString() in pinnedCatalogues
if (isPinned) null
else SourceItem(source, null, isPinned)
}
}.subscribeLatestCache(BrowseController::setLastUsedSource)
}
fun updateSources() {
sources = getEnabledSources()
loadSources()
loadLastUsedSource()
}
fun onDestroy() {
lastSources = sourceItems
lastUsedItemRem = lastUsedItem
}
/**
@ -124,5 +141,8 @@ class SourcePresenter(
companion object {
const val PINNED_KEY = "pinned"
const val LAST_USED_KEY = "last_used"
private var lastSources: List<SourceItem>? = null
private var lastUsedItemRem: SourceItem? = null
}
}

View File

@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/browse_layout"
xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout
@ -12,7 +13,7 @@
android:background="?android:attr/colorBackground">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:id="@+id/source_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<eu.kanade.tachiyomi.ui.extension.RecyclerWithScrollerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/recycler_layout"
@ -21,4 +21,4 @@
android:layout_height="match_parent"
app:fastScrollerBubbleEnabled="false" />
</FrameLayout>
</eu.kanade.tachiyomi.ui.extension.RecyclerWithScrollerView>