Skip to content

Commit

Permalink
For mozilla-mobile#11862 - Add swipe to show tab tray gesture for bot…
Browse files Browse the repository at this point in the history
…tom toolbar.

- Create a new gestures package for classes that implement gestures that
  work on multiple screens

- Move SwipeGestureLayout to the gestures package

- Rename ToolbarGestureHandler to SwitchTabsGestureListener

- Gate swipe to show tab tray behind a feature flag

- Update home fragment snackbars to explicitly pass a
  CoordinatorLayout as the view to attach them to

- Extract common gesture function into utility file.
  • Loading branch information
person808 committed Oct 6, 2020
1 parent a5de2c0 commit da5e83e
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 33 deletions.
5 changes: 5 additions & 0 deletions app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,9 @@ object FeatureFlags {
* Enables ETP cookie purging
*/
val etpCookiePurging = Config.channel.isNightlyOrDebug

/**
* Enables additional swipe gestures on the browser chrome.
*/
val browserChromeGestures = Config.channel.isNightlyOrDebug
}
14 changes: 13 additions & 1 deletion app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tabs.WindowFeature
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.addons.runIfFragmentIsAttached
import org.mozilla.fenix.components.FenixSnackbar
Expand All @@ -40,6 +41,7 @@ import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.navigateSafe
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.gestures.ShowTabTrayGestureListener
import org.mozilla.fenix.shortcut.PwaOnboardingObserver
import org.mozilla.fenix.trackingprotection.TrackingProtectionOverlay

Expand Down Expand Up @@ -74,7 +76,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
return super.initializeUI(view)?.also {
if (context.settings().isSwipeToolbarToSwitchTabsEnabled) {
gestureLayout.addGestureListener(
ToolbarGestureHandler(
SwitchTabsGestureListener(
activity = requireActivity(),
contentLayout = browserLayout,
tabPreview = tabPreview,
Expand All @@ -84,6 +86,16 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
)
}

if (FeatureFlags.browserChromeGestures) {
gestureLayout.addGestureListener(
ShowTabTrayGestureListener(
activity = requireActivity(),
toolbarLayout = browserToolbarView.view,
showTabTray = browserInteractor::onTabCounterClicked
)
)
}

val readerModeAction =
BrowserToolbar.ToggleButton(
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_readermode)!!,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,26 @@ import android.graphics.Rect
import android.view.View
import android.view.ViewConfiguration
import androidx.core.animation.doOnEnd
import androidx.core.graphics.contains
import androidx.core.graphics.toPoint
import androidx.core.view.isVisible
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.support.ktx.android.view.getRectWithViewLocation
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getRectWithScreenLocation
import org.mozilla.fenix.ext.getWindowInsets
import org.mozilla.fenix.ext.isKeyboardVisible
import org.mozilla.fenix.ext.sessionsOfType
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.gestures.SwipeGestureListener
import org.mozilla.fenix.gestures.isInToolbar
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

/**
* Handles intercepting touch events on the toolbar for swipe gestures and executes the
* Handles intercepting touch events on the toolbar for swipe to switch tabs and executes the
* necessary animations.
*/
@Suppress("LargeClass", "TooManyFunctions")
class ToolbarGestureHandler(
class SwitchTabsGestureListener(
private val activity: Activity,
private val contentLayout: View,
private val tabPreview: TabPreview,
Expand All @@ -61,7 +58,8 @@ class ToolbarGestureHandler(
private val touchSlop = ViewConfiguration.get(activity).scaledTouchSlop
private val minimumFlingVelocity = ViewConfiguration.get(activity).scaledMinimumFlingVelocity

private var gestureDirection = GestureDirection.LEFT_TO_RIGHT
private var gestureDirection =
GestureDirection.LEFT_TO_RIGHT

override fun onSwipeStarted(start: PointF, next: PointF): Boolean {
val dx = next.x - start.x
Expand All @@ -75,7 +73,7 @@ class ToolbarGestureHandler(
@Suppress("ComplexCondition")
return if (
!activity.window.decorView.isKeyboardVisible() &&
start.isInToolbar() &&
start.isInToolbar(toolbarLayout) &&
abs(dx) > touchSlop &&
abs(dy) < abs(dx)
) {
Expand Down Expand Up @@ -168,7 +166,9 @@ class ToolbarGestureHandler(
}

if (index < sessions.count() && index >= 0) {
Destination.Tab(sessions.elementAt(index))
Destination.Tab(
sessions.elementAt(index)
)
} else {
Destination.None
}
Expand Down Expand Up @@ -271,18 +271,6 @@ class ToolbarGestureHandler(
}.start()
}

private fun PointF.isInToolbar(): Boolean {
val toolbarLocation = toolbarLayout.getRectWithScreenLocation()
// In Android 10, the system gesture touch area overlaps the bottom of the toolbar, so
// lets make our swipe area taller by that amount
activity.window.decorView.getWindowInsets()?.let { insets ->
if (activity.settings().shouldUseBottomToolbar) {
toolbarLocation.top -= (insets.mandatorySystemGestureInsets.bottom - insets.stableInsetBottom)
}
}
return toolbarLocation.contains(toPoint())
}

private val Rect.visibleWidth: Int
get() = if (left < 0) {
right
Expand Down
29 changes: 29 additions & 0 deletions app/src/main/java/org/mozilla/fenix/gestures/GestureUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* 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.gestures

import android.graphics.PointF
import android.view.View
import androidx.core.graphics.contains
import androidx.core.graphics.toPoint
import org.mozilla.fenix.ext.getRectWithScreenLocation
import org.mozilla.fenix.ext.getWindowInsets
import org.mozilla.fenix.ext.settings

/**
* Checks if a point is within the bounds of a toolbar view. This accounts for the overlap
* between the bottom toolbar and the system gesture area.
*/
fun PointF.isInToolbar(toolbar: View): Boolean {
val toolbarLocation = toolbar.getRectWithScreenLocation()
// In Android 10, the system gesture touch area overlaps the bottom of the toolbar, so
// lets make our swipe area taller by that amount
toolbar.getWindowInsets()?.let { insets ->
if (toolbar.context.settings().shouldUseBottomToolbar) {
toolbarLocation.top -= (insets.mandatorySystemGestureInsets.bottom - insets.stableInsetBottom)
}
}
return toolbarLocation.contains(toPoint())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* 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.gestures

import android.app.Activity
import android.graphics.PointF
import android.view.View
import android.view.ViewConfiguration
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.isKeyboardVisible
import kotlin.math.abs

class ShowTabTrayGestureListener(
private val activity: Activity,
private val toolbarLayout: View,
private val showTabTray: () -> Unit
) : SwipeGestureListener {

private val touchSlop = ViewConfiguration.get(activity).scaledTouchSlop
private val minimumFlingVelocity = ViewConfiguration.get(activity).scaledMinimumFlingVelocity

override fun onSwipeStarted(start: PointF, next: PointF): Boolean {
val dx = next.x - start.x
// negative dy = swiping up
val dy = next.y - start.y

return activity.components.settings.toolbarPosition == ToolbarPosition.BOTTOM &&
!toolbarLayout.isKeyboardVisible() && start.isInToolbar(toolbarLayout) &&
-dy >= touchSlop && abs(dx) < abs(dy)
}

override fun onSwipeUpdate(distanceX: Float, distanceY: Float) {
// Do nothing
}

override fun onSwipeFinished(velocityX: Float, velocityY: Float) {
// Negative velocityY = swiping up
if (-velocityY >= minimumFlingVelocity) {
showTabTray.invoke()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* 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
package org.mozilla.fenix.gestures

import android.content.Context
import android.graphics.PointF
Expand Down
32 changes: 25 additions & 7 deletions app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.fragment_home.view.*
import kotlinx.android.synthetic.main.home_gesture_wrapper.*
import kotlinx.android.synthetic.main.no_collections_message.view.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
Expand Down Expand Up @@ -71,6 +72,7 @@ import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.content.res.resolveAttribute
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions
Expand All @@ -92,6 +94,7 @@ import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.gestures.ShowTabTrayGestureListener
import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController
import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor
import org.mozilla.fenix.home.sessioncontrol.SessionControlView
Expand Down Expand Up @@ -179,7 +182,11 @@ class HomeFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_home, container, false)
val view = if (FeatureFlags.browserChromeGestures) {
inflater.inflate(R.layout.home_gesture_wrapper, container, false)
} else {
inflater.inflate(R.layout.fragment_home, container, false)
}
val activity = activity as HomeActivity
val components = requireComponents

Expand Down Expand Up @@ -361,6 +368,17 @@ class HomeFragment : Fragment() {
}
}

// gestureLayout won't exist if the flag is off so check the flag here
if (FeatureFlags.browserChromeGestures) {
gestureLayout.addGestureListener(
ShowTabTrayGestureListener(
activity = requireActivity(),
toolbarLayout = view.toolbarLayout,
showTabTray = ::openTabTray
)
)
}

createHomeMenu(requireContext(), WeakReference(view.menuButton))
val tabCounterMenu = TabCounterMenu(
view.context,
Expand Down Expand Up @@ -480,7 +498,7 @@ class HomeFragment : Fragment() {
}

viewLifecycleOwner.lifecycleScope.allowUndo(
requireView(),
requireView().homeLayout,
snackbarMessage,
requireContext().getString(R.string.snackbar_deleted_undo),
{
Expand All @@ -503,7 +521,7 @@ class HomeFragment : Fragment() {
}

viewLifecycleOwner.lifecycleScope.allowUndo(
requireView(),
requireView().homeLayout,
snackbarMessage,
requireContext().getString(R.string.snackbar_deleted_undo),
{
Expand Down Expand Up @@ -565,7 +583,7 @@ class HomeFragment : Fragment() {
requireComponents.backgroundServices.accountManager.register(object : AccountObserver {
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
if (authType != AuthType.Existing) {
view?.let {
view?.homeLayout?.let {
FenixSnackbar.make(
view = it,
duration = Snackbar.LENGTH_SHORT,
Expand Down Expand Up @@ -798,7 +816,7 @@ class HomeFragment : Fragment() {
deleteAndQuit(
activity,
viewLifecycleOwner.lifecycleScope,
view?.let { view ->
view?.homeLayout?.let { view ->
FenixSnackbar.make(
view = view,
isDisplayedWithBrowserToolbar = false
Expand Down Expand Up @@ -919,7 +937,7 @@ class HomeFragment : Fragment() {
private fun showSavedSnackbar() {
viewLifecycleOwner.lifecycleScope.launch {
delay(ANIM_SNACKBAR_DELAY)
view?.let { view ->
view?.homeLayout?.let { view ->
FenixSnackbar.make(
view = view,
duration = Snackbar.LENGTH_LONG,
Expand All @@ -933,7 +951,7 @@ class HomeFragment : Fragment() {
}

private fun showRenamedSnackbar() {
view?.let { view ->
view?.homeLayout?.let { view ->
val string = view.context.getString(R.string.snackbar_collection_renamed)
FenixSnackbar.make(
view = view,
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/res/layout/fragment_browser.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<!-- 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/. -->
<org.mozilla.fenix.browser.SwipeGestureLayout xmlns:android="http://schemas.android.com/apk/res/android"
<org.mozilla.fenix.gestures.SwipeGestureLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/gestureLayout"
Expand Down Expand Up @@ -79,4 +79,4 @@
android:clickable="false"
android:focusable="false"
android:visibility="gone" />
</org.mozilla.fenix.browser.SwipeGestureLayout>
</org.mozilla.fenix.gestures.SwipeGestureLayout>
12 changes: 12 additions & 0 deletions app/src/main/res/layout/home_gesture_wrapper.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->

<org.mozilla.fenix.gestures.SwipeGestureLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/gestureLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<include layout="@layout/fragment_home" />
</org.mozilla.fenix.gestures.SwipeGestureLayout>

0 comments on commit da5e83e

Please sign in to comment.