For #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.upstream-sync
parent
d0fd3e82c5
commit
3311e68d14
@ -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