0
0
mirror of https://github.com/ankidroid/Anki-Android.git synced 2024-09-20 12:02:16 +02:00

Fix snackbar swipe to dismiss behavior

While you can can dismiss snackbars by flinging, due to a bug in the
library, they stay in place while you do so, and only ever disappear
to the right, which might not be the direction of the fling.

This makes snackbars move along with the finger.
This commit is contained in:
oakkitten 2022-07-04 23:17:01 +01:00 committed by Mike Hardy
parent f0e6ee7ac9
commit 3223b526cf
2 changed files with 87 additions and 0 deletions

View File

@ -12,6 +12,7 @@ import android.widget.Toast
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.snackbar.fixSwipeDismissBehavior
import com.ichi2.async.CollectionTask.SaveCollection
import com.ichi2.async.TaskListener
import com.ichi2.async.TaskManager
@ -122,6 +123,7 @@ object UIUtils {
@JvmStatic
fun getSnackbar(activity: Activity?, mainText: String?, length: Int, actionTextResource: Int, listener: View.OnClickListener?, root: View, callback: Snackbar.Callback?): Snackbar {
val sb = Snackbar.make(root, mainText!!, length)
sb.fixSwipeDismissBehavior()
if (listener != null) {
sb.setAction(actionTextResource, listener)
}
@ -143,6 +145,7 @@ object UIUtils {
@JvmStatic
fun getDismissibleSnackbar(activity: Activity?, mainText: String, length: Int, dismissTextResource: Int, root: View): Snackbar {
val sb = Snackbar.make(root, mainText, length)
sb.fixSwipeDismissBehavior()
sb.setAction(dismissTextResource) {
sb.dismiss()
}

View File

@ -0,0 +1,84 @@
/*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.ichi2.anki.snackbar
import android.view.MotionEvent
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.customview.widget.ViewDragHelper
import com.google.android.material.behavior.SwipeDismissBehavior
import com.google.android.material.snackbar.Snackbar
/**
* This exists to help Snackbars actually move on the screen when you try to swipe them away.
* With the default SwipeDismissBehavior, while you can can dismiss them by flinging,
* they stay in place while you do so, and only ever disappear to the right,
* not into the direction of the fling. The library provides the functionality, it's just broken.
*
* The issue is, when we get a move event, ViewDragHelper will capture the view for dragging,
* and ask parent to stop intercepting further touches. This propagates to CoordinatorLayout,
* which then resets touch behavior for its children--including us.
*
* The sequence of unfortunate events:
* * [onInterceptTouchEvent]
* * [ViewDragHelper.shouldInterceptTouchEvent]
* * [ViewDragHelper.tryCaptureViewForDrag]
* * [ViewDragHelper.captureChildView]
* * [ViewDragHelper.Callback.onViewCaptured]
* * [CoordinatorLayout.requestDisallowInterceptTouchEvent]
* * [CoordinatorLayout.resetTouchBehaviors]
* * [onTouchEvent]
*
* This fix solves the issue in a very simple way, by ignoring calls to [onTouchEvent]
* during the call to [onInterceptTouchEvent].
*/
class SwipeDismissBehaviorFix<V : View> : SwipeDismissBehavior<V>() {
private var ignoreCallsToOnTouchEvent = false
override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean {
ignoreCallsToOnTouchEvent = true
return super.onInterceptTouchEvent(parent, child, event).also {
ignoreCallsToOnTouchEvent = false
}
}
override fun onTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean {
if (ignoreCallsToOnTouchEvent) return false
return super.onTouchEvent(parent, child, event)
}
}
/**
* This does three things:
* * Changes the default behavior to the fixed one;
* * Copies the listener from the default behavior. When dragging or settling,
* this listener pauses the timer that removes the snackbar,
* so it does not disappear from under your finger;
* * Allows swiping the snackbar to the left, as well as to the right.
*/
fun Snackbar.fixSwipeDismissBehavior() {
addCallback(object : Snackbar.Callback() {
override fun onShown(snackbar: Snackbar) {
super.onShown(snackbar)
val params = snackbar.view.layoutParams
if (params is CoordinatorLayout.LayoutParams) {
params.behavior = SwipeDismissBehaviorFix<View>().apply {
listener = (params.behavior as? SwipeDismissBehavior)?.listener
setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_ANY)
}
}
}
})
}