[fenix] For https://github.com/mozilla-mobile/fenix/issues/17686 - Use a custom behavior to scroll InfoBanner with the top toolbar
Previously when the toolbar was on top the banner was inflated in the toolbar's parent - an AppBarLayout. After migrating to use a custom behavior for scrolling the toolbar and not use anymore the AppbarLayout for this we needed a new solution. Using a new behavior to keep this banner in sync with the y translation of the toolbar gives us most of the old behavior back.pull/600/head
parent
9c5bc17da9
commit
7e3969c01d
@ -0,0 +1,42 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.browser.infobanner
|
||||
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
|
||||
/**
|
||||
* [InfoBanner] that will automatically scroll with the top [BrowserToolbar].
|
||||
* Only to be used with [BrowserToolbar]s placed at the top of the screen.
|
||||
*
|
||||
* @param shouldScrollWithTopToolbar whether to follow the Y translation of the top toolbar or not
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class DynamicInfoBanner(
|
||||
private val context: Context,
|
||||
container: ViewGroup,
|
||||
@VisibleForTesting
|
||||
internal val shouldScrollWithTopToolbar: Boolean = false,
|
||||
message: String,
|
||||
dismissText: String,
|
||||
actionText: String? = null,
|
||||
dismissByHiding: Boolean = false,
|
||||
dismissAction: (() -> Unit)? = null,
|
||||
actionToPerform: (() -> Unit)? = null
|
||||
) : InfoBanner(
|
||||
context, container, message, dismissText, actionText, dismissByHiding, dismissAction, actionToPerform
|
||||
) {
|
||||
override fun showBanner() {
|
||||
super.showBanner()
|
||||
|
||||
if (shouldScrollWithTopToolbar) {
|
||||
(bannerLayout.layoutParams as CoordinatorLayout.LayoutParams).behavior = DynamicInfoBannerBehavior(
|
||||
context, null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.browser.infobanner
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||
|
||||
/**
|
||||
* A [CoordinatorLayout.Behavior] implementation to be used when placing [InfoBanner]
|
||||
* below the BrowserToolbar with which is has to scroll.
|
||||
*
|
||||
* This Behavior will keep the Y translations of [InfoBanner] and the top [BrowserToolbar] in sync
|
||||
* so that the banner will be shown between:
|
||||
* - the top of the container, being translated over the initial toolbar height (toolbar fully collapsed)
|
||||
* - immediately below the toolbar (toolbar fully expanded).
|
||||
*/
|
||||
class DynamicInfoBannerBehavior(
|
||||
context: Context?,
|
||||
attrs: AttributeSet?
|
||||
) : CoordinatorLayout.Behavior<View>(context, attrs) {
|
||||
@VisibleForTesting
|
||||
internal var toolbarHeight: Int = 0
|
||||
|
||||
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
|
||||
if (dependency::class == BrowserToolbar::class) {
|
||||
toolbarHeight = dependency.height
|
||||
setBannerYTranslation(child, dependency.translationY)
|
||||
return true
|
||||
}
|
||||
|
||||
return super.layoutDependsOn(parent, child, dependency)
|
||||
}
|
||||
|
||||
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
|
||||
setBannerYTranslation(child, dependency.translationY)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun setBannerYTranslation(banner: View, newYTranslation: Float) {
|
||||
banner.translationY = toolbarHeight + newYTranslation
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.browser.infobanner
|
||||
|
||||
import android.view.View
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class DynamicInfoBannerBehaviorTest {
|
||||
@Test
|
||||
fun `layoutDependsOn should not do anything if not for BrowserToolbar as a dependency`() {
|
||||
val behavior = spyk(DynamicInfoBannerBehavior(mockk(), null))
|
||||
|
||||
behavior.layoutDependsOn(mockk(), mockk(), mockk())
|
||||
|
||||
verify(exactly = 0) { behavior.toolbarHeight }
|
||||
verify(exactly = 0) { behavior.toolbarHeight = any() }
|
||||
verify(exactly = 0) { behavior.setBannerYTranslation(any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `layoutDependsOn should update toolbarHeight and translate the banner`() {
|
||||
val behavior = spyk(DynamicInfoBannerBehavior(mockk(), null))
|
||||
val banner: View = mockk(relaxed = true)
|
||||
val toolbar: BrowserToolbar = mockk {
|
||||
every { height } returns 99
|
||||
every { translationY } returns -33f
|
||||
}
|
||||
|
||||
assertEquals(0, behavior.toolbarHeight)
|
||||
|
||||
behavior.layoutDependsOn(mockk(), banner, toolbar)
|
||||
|
||||
assertEquals(99, behavior.toolbarHeight)
|
||||
verify { behavior.setBannerYTranslation(banner, -33f) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onDependentViewChanged should translate the banner`() {
|
||||
val behavior = spyk(DynamicInfoBannerBehavior(mockk(), null))
|
||||
val banner: View = mockk(relaxed = true)
|
||||
val toolbar: BrowserToolbar = mockk {
|
||||
every { height } returns 50
|
||||
every { translationY } returns -23f
|
||||
}
|
||||
|
||||
behavior.layoutDependsOn(mockk(), banner, toolbar)
|
||||
|
||||
verify { behavior.setBannerYTranslation(banner, -23f) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setBannerYTranslation should set banner translation to be toolbarHeight + it's translation`() {
|
||||
val behavior = spyk(DynamicInfoBannerBehavior(mockk(), null))
|
||||
val banner: View = mockk(relaxed = true)
|
||||
behavior.toolbarHeight = 30
|
||||
|
||||
behavior.setBannerYTranslation(banner, -20f)
|
||||
|
||||
verify { banner.translationY = 10f }
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.browser.infobanner
|
||||
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import io.mockk.spyk
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class DynamicInfoBannerTest {
|
||||
@Test
|
||||
fun `showBanner should set DynamicInfoBannerBehavior as behavior if scrollWithTopToolbar`() {
|
||||
val banner = spyk(DynamicInfoBanner(
|
||||
testContext, CoordinatorLayout(testContext), true, "", ""
|
||||
))
|
||||
|
||||
banner.showBanner()
|
||||
|
||||
assertTrue((banner.bannerLayout.layoutParams as CoordinatorLayout.LayoutParams).behavior is DynamicInfoBannerBehavior)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `showBanner should not set a behavior if not scrollWithTopToolbar`() {
|
||||
val banner = spyk(DynamicInfoBanner(
|
||||
testContext, CoordinatorLayout(testContext), false, "", ""
|
||||
))
|
||||
|
||||
banner.showBanner()
|
||||
|
||||
assertNull((banner.bannerLayout.layoutParams as CoordinatorLayout.LayoutParams).behavior)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue