Skip to content

Commit a13db57

Browse files
Mugurellplingurar@mozilla.com
authored andcommitted
Bug 1991654 - part 3 - Use new EngineViewScrollingDataBehavior r=android-reviewers,Roger
This will animate the bottom toolbar based on scroll data exposed by GeckoView instead of inferring the scroll distances based on intercepted MotionEvents. As a result the dynamic toolbar will be always kept in sync with the engine view being scrolled. Differential Revision: https://phabricator.services.mozilla.com/D267223
1 parent ad7bf74 commit a13db57

File tree

8 files changed

+381
-10
lines changed

8 files changed

+381
-10
lines changed

mobile/android/android-components/components/ui/widgets/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
testImplementation project(':components:support-test-fakes')
3030

3131
testImplementation libs.androidx.test.junit
32+
testImplementation libs.kotlinx.coroutines.test
3233
testImplementation libs.robolectric
3334
}
3435

mobile/android/android-components/components/ui/widgets/src/main/java/mozilla/components/ui/widgets/behavior/EngineViewScrollingBehaviorFactory.kt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ package mozilla.components.ui.widgets.behavior
77
import android.view.View
88
import mozilla.components.concept.base.crash.CrashReporting
99
import mozilla.components.concept.engine.EngineView
10+
import mozilla.components.ui.widgets.behavior.DependencyGravity.Bottom
1011

1112
/**
1213
* Factory for [EngineViewScrollingBehavior] instances.
1314
*/
14-
object EngineViewScrollingBehaviorFactory {
15+
class EngineViewScrollingBehaviorFactory(
16+
private val useScrollData: Boolean = false,
17+
) {
1518
/**
1619
* Create a new [EngineViewScrollingBehavior] instance.
1720
*
@@ -25,10 +28,18 @@ object EngineViewScrollingBehaviorFactory {
2528
dependency: View,
2629
dependencyGravity: DependencyGravity,
2730
crashReporting: CrashReporting? = null,
28-
) = EngineViewScrollingGesturesBehavior(
29-
engineView = engineView,
30-
dependency = dependency,
31-
dependencyGravity = dependencyGravity,
32-
crashReporting = crashReporting,
33-
)
31+
) = when (useScrollData && dependencyGravity is Bottom) {
32+
true -> EngineViewScrollingDataBehavior(
33+
engineView = engineView,
34+
dependency = dependency,
35+
dependencyGravity = dependencyGravity,
36+
)
37+
38+
false -> EngineViewScrollingGesturesBehavior(
39+
engineView = engineView,
40+
dependency = dependency,
41+
dependencyGravity = dependencyGravity,
42+
crashReporting = crashReporting,
43+
)
44+
}
3445
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
package mozilla.components.ui.widgets.behavior
6+
7+
import android.view.View
8+
import androidx.coordinatorlayout.widget.CoordinatorLayout
9+
import androidx.core.view.isVisible
10+
import kotlinx.coroutines.CoroutineScope
11+
import kotlinx.coroutines.Job
12+
import kotlinx.coroutines.isActive
13+
import kotlinx.coroutines.launch
14+
import mozilla.components.concept.engine.EngineView
15+
import mozilla.components.support.ktx.android.view.toScope
16+
17+
/**
18+
* A [CoordinatorLayout.Behavior] implementation to be used for moving [dependency] up/down
19+
* depending on scroll data exposed by [engineView].
20+
*
21+
* This is safe to use even if [dependency] has it's visibility modified.
22+
*
23+
* This implementation will:
24+
* - Show/Hide the [View] automatically when scrolling vertically.
25+
* - Snap the [View] to be hidden or visible when the user stops scrolling.
26+
*/
27+
class EngineViewScrollingDataBehavior(
28+
private val engineView: EngineView,
29+
private val dependency: View,
30+
dependencyGravity: DependencyGravity,
31+
private val scrollListenerScope: CoroutineScope = engineView.asView().toScope(),
32+
) : EngineViewScrollingBehavior(engineView, dependency, dependencyGravity) {
33+
private var scrollUpdatesJob: Job? = null
34+
35+
override fun onStartNestedScroll(
36+
coordinatorLayout: CoordinatorLayout,
37+
child: View,
38+
directTargetChild: View,
39+
target: View,
40+
axes: Int,
41+
type: Int,
42+
): Boolean = when (dependency.isVisible && isScrollEnabled) {
43+
true -> {
44+
yTranslator.cancelInProgressTranslation()
45+
46+
scrollUpdatesJob = scrollListenerScope.launch {
47+
engineView.verticalScrollDelta.collect {
48+
if (isActive) { yTranslator.translate(dependency, it) }
49+
}
50+
}
51+
true
52+
}
53+
false -> false // not interested in subsequent scroll events
54+
}
55+
56+
override fun onStopNestedScroll(
57+
coordinatorLayout: CoordinatorLayout, child: View, target: View, type: Int,
58+
) {
59+
scrollUpdatesJob?.cancel()
60+
61+
if (dependency.isVisible) {
62+
yTranslator.snapWithAnimation(dependency)
63+
}
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
package mozilla.components.ui.widgets.behavior
6+
7+
import android.view.View
8+
import androidx.test.ext.junit.runners.AndroidJUnit4
9+
import mozilla.components.concept.engine.EngineView
10+
import mozilla.components.support.test.mock
11+
import mozilla.components.support.test.robolectric.testContext
12+
import mozilla.components.ui.widgets.behavior.DependencyGravity.Bottom
13+
import mozilla.components.ui.widgets.behavior.DependencyGravity.Top
14+
import org.junit.Assert.assertTrue
15+
import org.junit.Before
16+
import org.junit.Test
17+
import org.junit.runner.RunWith
18+
import org.mockito.Mockito.doReturn
19+
20+
@RunWith(AndroidJUnit4::class)
21+
class EngineViewScrollingBehaviorFactoryTest {
22+
private val engineView: EngineView = mock()
23+
24+
@Before
25+
fun setUp() {
26+
doReturn(View(testContext)).`when`(engineView).asView()
27+
}
28+
29+
@Test
30+
fun `GIVEN should use scroll data and dependency is at bottom WHEN building a scrolling behavior THEN return one using scroll data`() {
31+
val result = EngineViewScrollingBehaviorFactory(true).build(
32+
engineView = engineView,
33+
dependency = mock(),
34+
dependencyGravity = Bottom,
35+
)
36+
37+
assertTrue(result is EngineViewScrollingDataBehavior)
38+
}
39+
40+
@Test
41+
fun `GIVEN should not use scroll data and dependency is at bottom WHEN building a scrolling behavior THEN return one using scroll gestures`() {
42+
val result = EngineViewScrollingBehaviorFactory(false).build(
43+
engineView = engineView,
44+
dependency = mock(),
45+
dependencyGravity = Bottom,
46+
)
47+
48+
assertTrue(result is EngineViewScrollingGesturesBehavior)
49+
}
50+
51+
@Test
52+
fun `GIVEN should use scroll data and dependency is at top WHEN building a scrolling behavior THEN return one using scroll gestures`() {
53+
val result = EngineViewScrollingBehaviorFactory(true).build(
54+
engineView = engineView,
55+
dependency = mock(),
56+
dependencyGravity = Top,
57+
)
58+
59+
assertTrue(result is EngineViewScrollingGesturesBehavior)
60+
}
61+
62+
@Test
63+
fun `GIVEN should not use scroll data and dependency is at top WHEN building a scrolling behavior THEN return one using scroll gestures`() {
64+
val result = EngineViewScrollingBehaviorFactory(false).build(
65+
engineView = engineView,
66+
dependency = mock(),
67+
dependencyGravity = Top,
68+
)
69+
70+
assertTrue(result is EngineViewScrollingGesturesBehavior)
71+
}
72+
}

0 commit comments

Comments
 (0)