MainActivity fixes (#6591)

* Reduce notifyDataSetChanged calls when category count is disabled

* Fix category tabs briefly showing when it's supposed to be disabled

Also fix tabs showing when activity recreated

* Lift appbar when tab is hidden

Check against tab visibility instead of viewpager

* Restore selected nav item after recreate

* Simplify SHORTCUT_MANGA intent handling

Don't need to change controller if the topmost controller is the target
This commit is contained in:
Ivan Iskandar 2022-02-12 22:58:58 +07:00 committed by GitHub
parent ae2a6a3d4f
commit 2932ed670f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 70 additions and 65 deletions

View File

@ -4,7 +4,10 @@ import com.google.android.material.tabs.TabLayout
interface TabbedController { interface TabbedController {
fun configureTabs(tabs: TabLayout) {} /**
* @return true to let activity updates tabs visibility (to visible)
*/
fun configureTabs(tabs: TabLayout): Boolean = true
fun cleanupTabs(tabs: TabLayout) {} fun cleanupTabs(tabs: TabLayout) {}
} }

View File

@ -79,11 +79,12 @@ class BrowseController :
} }
} }
override fun configureTabs(tabs: TabLayout) { override fun configureTabs(tabs: TabLayout): Boolean {
with(tabs) { with(tabs) {
tabGravity = TabLayout.GRAVITY_FILL tabGravity = TabLayout.GRAVITY_FILL
tabMode = TabLayout.MODE_FIXED tabMode = TabLayout.MODE_FIXED
} }
return true
} }
override fun cleanupTabs(tabs: TabLayout) { override fun cleanupTabs(tabs: TabLayout) {

View File

@ -28,24 +28,13 @@ class LibraryAdapter(
* The categories to bind in the adapter. * The categories to bind in the adapter.
*/ */
var categories: List<Category> = emptyList() var categories: List<Category> = emptyList()
// This setter helps to not refresh the adapter if the reference to the list doesn't change. private set
set(value) {
if (field !== value) {
field = value
notifyDataSetChanged()
}
}
/** /**
* The number of manga in each category. * The number of manga in each category.
* List order must be the same as [categories]
*/ */
var itemsPerCategory: Map<Int, Int> = emptyMap() private var itemsPerCategory: List<Int> = emptyList()
set(value) {
if (field !== value) {
field = value
notifyDataSetChanged()
}
}
private var boundViews = arrayListOf<View>() private var boundViews = arrayListOf<View>()
@ -62,6 +51,29 @@ class LibraryAdapter(
.launchIn(controller.viewScope) .launchIn(controller.viewScope)
} }
/**
* Pair of category and size of category
*/
fun updateCategories(new: List<Pair<Category, Int>>) {
var updated = false
val newCategories = new.map { it.first }
if (categories != newCategories) {
categories = newCategories
updated = true
}
val newItemsPerCategory = new.map { it.second }
if (itemsPerCategory !== newItemsPerCategory) {
itemsPerCategory = newItemsPerCategory
updated = true
}
if (updated) {
notifyDataSetChanged()
}
}
/** /**
* Creates a new view for this adapter. * Creates a new view for this adapter.
* *
@ -112,10 +124,11 @@ class LibraryAdapter(
* @return the title to display. * @return the title to display.
*/ */
override fun getPageTitle(position: Int): CharSequence { override fun getPageTitle(position: Int): CharSequence {
if (preferences.categoryNumberOfItems().get()) { return if (!preferences.categoryNumberOfItems().get()) {
return categories[position].let { "${it.name} (${itemsPerCategory[it.id]})" } categories[position].name
} else {
categories[position].let { "${it.name} (${itemsPerCategory[position]})" }
} }
return categories[position].name
} }
/** /**

View File

@ -8,6 +8,7 @@ import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.view.doOnAttach
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
@ -234,8 +235,9 @@ class LibraryController(
super.onDestroyView(view) super.onDestroyView(view)
} }
override fun configureTabs(tabs: TabLayout) { override fun configureTabs(tabs: TabLayout): Boolean {
with(tabs) { with(tabs) {
isVisible = false
tabGravity = TabLayout.GRAVITY_START tabGravity = TabLayout.GRAVITY_START
tabMode = TabLayout.MODE_SCROLLABLE tabMode = TabLayout.MODE_SCROLLABLE
} }
@ -247,6 +249,8 @@ class LibraryController(
mangaCountVisibilitySubscription = mangaCountVisibilityRelay.subscribe { mangaCountVisibilitySubscription = mangaCountVisibilityRelay.subscribe {
adapter?.notifyDataSetChanged() adapter?.notifyDataSetChanged()
} }
return false
} }
override fun cleanupTabs(tabs: TabLayout) { override fun cleanupTabs(tabs: TabLayout) {
@ -291,22 +295,17 @@ class LibraryController(
} }
// Set the categories // Set the categories
adapter.categories = categories adapter.updateCategories(categories.map { it to (mangaMap[it.id]?.size ?: 0) })
adapter.itemsPerCategory = adapter.categories
.map { (it.id ?: -1) to (mangaMap[it.id]?.size ?: 0) }
.toMap()
// Restore active category. // Restore active category.
binding.libraryPager.setCurrentItem(activeCat, false) binding.libraryPager.setCurrentItem(activeCat, false)
// Trigger display of tabs // Trigger display of tabs
onTabsSettingsChanged() onTabsSettingsChanged(firstLaunch = true)
// Delay the scroll position to allow the view to be properly measured. // Delay the scroll position to allow the view to be properly measured.
view.post { view.doOnAttach {
if (isAttached) { (activity as? MainActivity)?.binding?.tabs?.setScrollPosition(binding.libraryPager.currentItem, 0f, true)
(activity as? MainActivity)?.binding?.tabs?.setScrollPosition(binding.libraryPager.currentItem, 0f, true)
}
} }
// Send the manga map to child fragments after the adapter is updated. // Send the manga map to child fragments after the adapter is updated.
@ -338,9 +337,11 @@ class LibraryController(
presenter.requestBadgesUpdate() presenter.requestBadgesUpdate()
} }
private fun onTabsSettingsChanged() { private fun onTabsSettingsChanged(firstLaunch: Boolean = false) {
if (!firstLaunch) {
mangaCountVisibilityRelay.call(preferences.categoryNumberOfItems().get())
}
tabsVisibilityRelay.call(preferences.categoryTabs().get() && adapter?.categories?.size ?: 0 > 1) tabsVisibilityRelay.call(preferences.categoryTabs().get() && adapter?.categories?.size ?: 0 > 1)
mangaCountVisibilityRelay.call(preferences.categoryNumberOfItems().get())
updateTitle() updateTitle()
} }

View File

@ -27,6 +27,7 @@ import com.bluelinelabs.conductor.Conductor
import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.navigation.NavigationBarView import com.google.android.material.navigation.NavigationBarView
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
import dev.chrisbanes.insetter.applyInsetter import dev.chrisbanes.insetter.applyInsetter
@ -236,6 +237,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
if (didMigration && !BuildConfig.DEBUG) { if (didMigration && !BuildConfig.DEBUG) {
WhatsNewDialogController().showDialog(router) WhatsNewDialogController().showDialog(router)
} }
} else {
// Restore selected nav item
router.backstack.firstOrNull()?.tag()?.toIntOrNull()?.let {
nav.menu.findItem(it).isChecked = true
}
} }
merge(preferences.showUpdatesNavBadge().asFlow(), preferences.unreadUpdatesCount().asFlow()) merge(preferences.showUpdatesNavBadge().asFlow(), preferences.unreadUpdatesCount().asFlow())
@ -403,11 +409,12 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
} }
SHORTCUT_MANGA -> { SHORTCUT_MANGA -> {
val extras = intent.extras ?: return false val extras = intent.extras ?: return false
if (router.backstackSize > 1) { val fgController = router.backstack.last()?.controller as? MangaController
if (fgController?.manga?.id != extras.getLong(MangaController.MANGA_EXTRA)) {
router.popToRoot() router.popToRoot()
setSelectedNavItem(R.id.nav_library)
router.pushController(RouterTransaction.with(MangaController(extras)))
} }
setSelectedNavItem(R.id.nav_library)
router.pushController(MangaController(extras).withFadeTransaction())
} }
SHORTCUT_DOWNLOADS -> { SHORTCUT_DOWNLOADS -> {
if (router.backstackSize > 1) { if (router.backstackSize > 1) {
@ -553,11 +560,12 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
from.cleanupTabs(binding.tabs) from.cleanupTabs(binding.tabs)
} }
if (to is TabbedController) { if (to is TabbedController) {
to.configureTabs(binding.tabs) if (to.configureTabs(binding.tabs)) {
binding.tabs.isVisible = true
}
} else { } else {
binding.tabs.setupWithViewPager(null) binding.tabs.isVisible = false
} }
binding.tabs.isVisible = to is TabbedController
if (from is FabController) { if (from is FabController) {
from.cleanupFab(binding.fabLayout.rootFab) from.cleanupFab(binding.fabLayout.rootFab)

View File

@ -5,23 +5,15 @@ import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.coordinatorlayout.R import androidx.coordinatorlayout.R
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.doOnLayout import androidx.core.view.doOnLayout
import androidx.core.view.isVisible
import androidx.customview.view.AbsSavedState import androidx.customview.view.AbsSavedState
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.viewpager.widget.ViewPager
import com.bluelinelabs.conductor.ChangeHandlerFrameLayout
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.tabs.TabLayout
import eu.kanade.tachiyomi.util.system.isTablet import eu.kanade.tachiyomi.util.system.isTablet
import eu.kanade.tachiyomi.util.view.findChild import eu.kanade.tachiyomi.util.view.findChild
import eu.kanade.tachiyomi.util.view.findDescendant
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.HierarchyChangeEvent
import reactivecircus.flowbinding.android.view.hierarchyChangeEvents
/** /**
* [CoordinatorLayout] with its own app bar lift state handler. * [CoordinatorLayout] with its own app bar lift state handler.
@ -33,8 +25,6 @@ import reactivecircus.flowbinding.android.view.hierarchyChangeEvents
* With those conditions, this view expects the following direct child: * With those conditions, this view expects the following direct child:
* *
* 1. An [AppBarLayout]. * 1. An [AppBarLayout].
*
* 2. A [ChangeHandlerFrameLayout] that contains an optional [ViewPager].
*/ */
class TachiyomiCoordinatorLayout @JvmOverloads constructor( class TachiyomiCoordinatorLayout @JvmOverloads constructor(
context: Context, context: Context,
@ -48,7 +38,7 @@ class TachiyomiCoordinatorLayout @JvmOverloads constructor(
private val isTablet = context.isTablet() private val isTablet = context.isTablet()
private var appBarLayout: AppBarLayout? = null private var appBarLayout: AppBarLayout? = null
private var viewPager: ViewPager? = null private var tabLayout: TabLayout? = null
/** /**
* If true, [AppBarLayout] child will be lifted on nested scroll. * If true, [AppBarLayout] child will be lifted on nested scroll.
@ -72,32 +62,21 @@ class TachiyomiCoordinatorLayout @JvmOverloads constructor(
) { ) {
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed) super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed)
// Disable elevation overlay when tabs are visible // Disable elevation overlay when tabs are visible
if (canLiftAppBarOnScroll && viewPager == null) { if (canLiftAppBarOnScroll) {
appBarLayout?.isLifted = dyConsumed != 0 || dyUnconsumed >= 0 appBarLayout?.isLifted = (dyConsumed != 0 || dyUnconsumed >= 0) && tabLayout?.isVisible == false
} }
} }
override fun onAttachedToWindow() { override fun onAttachedToWindow() {
super.onAttachedToWindow() super.onAttachedToWindow()
appBarLayout = findChild() appBarLayout = findChild()
viewPager = findChild<ChangeHandlerFrameLayout>()?.findDescendant() tabLayout = appBarLayout?.findChild()
// Updates ViewPager reference when controller is changed
findViewTreeLifecycleOwner()?.lifecycle?.coroutineScope?.let { scope ->
findChild<ChangeHandlerFrameLayout>()?.hierarchyChangeEvents()
?.onEach {
if (it is HierarchyChangeEvent.ChildRemoved) {
viewPager = (it.parent as? ViewGroup)?.findDescendant()
}
}
?.launchIn(scope)
}
} }
override fun onDetachedFromWindow() { override fun onDetachedFromWindow() {
super.onDetachedFromWindow() super.onDetachedFromWindow()
appBarLayout = null appBarLayout = null
viewPager = null tabLayout = null
} }
override fun onSaveInstanceState(): Parcelable? { override fun onSaveInstanceState(): Parcelable? {