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 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) fun lastUsedCategory() = rxPrefs.getInteger(Keys.lastUsedCategory, 0)

View File

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

View File

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

View File

@ -1,19 +1,20 @@
package eu.kanade.tachiyomi.ui.source package eu.kanade.tachiyomi.ui.source
import android.os.Bundle
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.util.system.withUIContext
import rx.Observable import kotlinx.coroutines.CoroutineScope
import rx.Subscription import kotlinx.coroutines.Dispatchers
import rx.android.schedulers.AndroidSchedulers 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.TreeMap import java.util.TreeMap
import java.util.concurrent.TimeUnit
/** /**
* Presenter of [BrowseController] * Presenter of [BrowseController]
@ -23,86 +24,102 @@ import java.util.concurrent.TimeUnit
* @param preferences application preferences. * @param preferences application preferences.
*/ */
class SourcePresenter( class SourcePresenter(
val controller: BrowseController,
val sourceManager: SourceManager = Injekt.get(), val sourceManager: SourceManager = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get() private val preferences: PreferencesHelper = Injekt.get()
) : BasePresenter<BrowseController>() { ) {
private var scope = CoroutineScope(Job() + Dispatchers.Default)
var sources = getEnabledSources() var sources = getEnabledSources()
/** var sourceItems = emptyList<SourceItem>()
* Subscription for retrieving enabled sources. var lastUsedItem: SourceItem? = null
*/
private var sourceSubscription: Subscription? = null
private var lastUsedSubscription: Subscription? = null
override fun onCreate(savedState: Bundle?) { var lastUsedJob: Job? = null
super.onCreate(savedState)
fun onCreate() {
if (lastSources != null) {
if (sourceItems.isEmpty()) {
sourceItems = lastSources ?: emptyList()
}
lastUsedItem = lastUsedItemRem
lastSources = null
lastUsedItemRem = null
}
// Load enabled and last used sources // Load enabled and last used sources
loadSources() loadSources()
loadLastUsedSource()
} }
/** /**
* Unsubscribe and create a new subscription to fetch enabled sources. * Unsubscribe and create a new subscription to fetch enabled sources.
*/ */
private fun loadSources() { private fun loadSources() {
sourceSubscription?.unsubscribe() scope.launch {
val pinnedSources = mutableListOf<SourceItem>()
val pinnedCatalogues = preferences.pinnedCatalogues().getOrDefault()
val pinnedSources = mutableListOf<SourceItem>() val map = TreeMap<String, MutableList<CatalogueSource>> { d1, d2 ->
val pinnedCatalogues = preferences.pinnedCatalogues().getOrDefault() // Catalogues without a lang defined will be placed at the end
when {
val map = TreeMap<String, MutableList<CatalogueSource>> { d1, d2 -> d1 == "" && d2 != "" -> 1
// Catalogues without a lang defined will be placed at the end d2 == "" && d1 != "" -> -1
when { else -> d1.compareTo(d2)
d1 == "" && d2 != "" -> 1
d2 == "" && d1 != "" -> -1
else -> d1.compareTo(d2)
}
}
val byLang = sources.groupByTo(map, { it.lang })
var sourceItems = byLang.flatMap {
val langItem = LangItem(it.key)
it.value.map { source ->
val isPinned = source.id.toString() in pinnedCatalogues
if (source.id.toString() in pinnedCatalogues) {
pinnedSources.add(SourceItem(source, LangItem(PINNED_KEY)))
} }
}
val byLang = sources.groupByTo(map, { it.lang })
sourceItems = byLang.flatMap {
val langItem = LangItem(it.key)
it.value.map { source ->
val isPinned = source.id.toString() in pinnedCatalogues
if (source.id.toString() in pinnedCatalogues) {
pinnedSources.add(SourceItem(source, LangItem(PINNED_KEY)))
}
SourceItem(source, langItem, isPinned) SourceItem(source, langItem, isPinned)
}
}
if (pinnedSources.isNotEmpty()) {
sourceItems = pinnedSources + sourceItems
}
lastUsedItem = getLastUsedSource(preferences.lastUsedCatalogueSource().get())
withUIContext {
controller.setSources(sourceItems, lastUsedItem)
loadLastUsedSource()
} }
} }
if (pinnedSources.isNotEmpty()) {
sourceItems = pinnedSources + sourceItems
}
sourceSubscription = Observable.just(sourceItems)
.subscribeLatestCache(BrowseController::setSources)
} }
private fun loadLastUsedSource() { private fun loadLastUsedSource() {
lastUsedSubscription?.unsubscribe() lastUsedJob?.cancel()
val sharedObs = preferences.lastUsedCatalogueSource().asObservable().share() 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. private fun getLastUsedSource(value: Long): SourceItem? {
lastUsedSubscription = Observable.merge( return (sourceManager.get(value) as? CatalogueSource)?.let { source ->
sharedObs.take(1), val pinnedCatalogues = preferences.pinnedCatalogues().getOrDefault()
sharedObs.skip(1).delay(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) val isPinned = source.id.toString() in pinnedCatalogues
).distinctUntilChanged().map { if (isPinned) null
(sourceManager.get(it) as? CatalogueSource)?.let { source -> else SourceItem(source, null, isPinned)
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() { fun updateSources() {
sources = getEnabledSources() sources = getEnabledSources()
loadSources() loadSources()
loadLastUsedSource() }
fun onDestroy() {
lastSources = sourceItems
lastUsedItemRem = lastUsedItem
} }
/** /**
@ -124,5 +141,8 @@ class SourcePresenter(
companion object { companion object {
const val PINNED_KEY = "pinned" const val PINNED_KEY = "pinned"
const val LAST_USED_KEY = "last_used" 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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/browse_layout"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout <FrameLayout
@ -12,7 +13,7 @@
android:background="?android:attr/colorBackground"> android:background="?android:attr/colorBackground">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler" android:id="@+id/source_recycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"

View File

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