From d6e7905e018e671c158f0bffea03cb89f48cb53e Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Fri, 13 Jan 2023 19:51:09 +0100 Subject: [PATCH] Reduce horizontal swipe sensitivity in timelines (#3148) * Reduce horizontal swipe sensitivity in timelines Fixes https://github.com/tuskyapp/Tusky/issues/2725, fixes https://github.com/tuskyapp/Tusky/issues/2112, fixes https://github.com/tuskyapp/Tusky/issues/2530, fixes https://github.com/tuskyapp/Tusky/issues/2200, fixes https://github.com/tuskyapp/Tusky/issues/2176, fixes https://github.com/tuskyapp/Tusky/issues/2112, fixes https://github.com/tuskyapp/Tusky/issues/1912, fixes https://github.com/tuskyapp/Tusky/issues/1718, fixes https://github.com/tuskyapp/Tusky/issues/1336 * Set scale factor to 4 * Catch exceptions, just in case --- .../com/keylesspalace/tusky/MainActivity.kt | 3 ++ .../components/account/AccountActivity.kt | 2 + .../tusky/components/search/SearchActivity.kt | 2 + .../tusky/util/ViewExtensions.kt | 37 +++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 08edcb6c..7e02d4d5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -89,6 +89,7 @@ import com.keylesspalace.tusky.util.deleteStaleCachedMedia import com.keylesspalace.tusky.util.emojify import com.keylesspalace.tusky.util.getDimension import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.reduceSwipeSensitivity import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.updateShortcut import com.keylesspalace.tusky.util.viewBinding @@ -253,6 +254,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } } + binding.viewPager.reduceSwipeSensitivity() + setupDrawer(savedInstanceState, addSearchButton = hideTopToolbar) /* Fetch user info while we're doing other things. This has to be done after setting up the diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt index f5683eb8..09f11308 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/AccountActivity.kt @@ -78,6 +78,7 @@ import com.keylesspalace.tusky.util.getDomain import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.loadAvatar import com.keylesspalace.tusky.util.parseAsMastodonHtml +import com.keylesspalace.tusky.util.reduceSwipeSensitivity import com.keylesspalace.tusky.util.setClickableText import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.viewBinding @@ -235,6 +236,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI // Setup the tabs and timeline pager. adapter = AccountPagerAdapter(this, viewModel.accountId) + binding.accountFragmentViewPager.reduceSwipeSensitivity() binding.accountFragmentViewPager.adapter = adapter binding.accountFragmentViewPager.offscreenPageLimit = 2 diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt index d0fdebc1..209548e8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt @@ -30,6 +30,7 @@ import com.keylesspalace.tusky.components.search.adapter.SearchPagerAdapter import com.keylesspalace.tusky.databinding.ActivitySearchBinding import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.settings.PrefKeys +import com.keylesspalace.tusky.util.reduceSwipeSensitivity import com.keylesspalace.tusky.util.viewBinding import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector @@ -62,6 +63,7 @@ class SearchActivity : BottomSheetActivity(), HasAndroidInjector { } private fun setupPages() { + binding.pages.reduceSwipeSensitivity() binding.pages.adapter = SearchPagerAdapter(this) val enableSwipeForTabs = preferences.getBoolean(PrefKeys.ENABLE_SWIPE_FOR_TABS, true) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt b/app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt index 07a9539f..fbb55e0f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt @@ -18,8 +18,11 @@ package com.keylesspalace.tusky.util import android.text.Editable import android.text.TextWatcher +import android.util.Log import android.view.View import android.widget.EditText +import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 fun View.show() { this.visibility = View.VISIBLE @@ -63,3 +66,37 @@ inline fun EditText.afterTextChanged( } }) } + +/** + * Reduce ViewPager2's sensitivity to horizontal swipes. + */ +fun ViewPager2.reduceSwipeSensitivity() { + // ViewPager2 is very sensitive to horizontal motion when swiping vertically, and will + // trigger a page transition if the user's swipe is only a few tens of degrees off from + // vertical. This is a problem if the underlying content is a list that the user wants + // to scroll vertically -- it's far too easy to trigger an accidental horizontal swipe. + // + // One way to stop this is to reach in to ViewPager2's RecyclerView and adjust the amount + // of touch slop it has. Scaling by 2 appears to work well. + // + // See https://issuetracker.google.com/issues/139867645 and + // https://bladecoder.medium.com/fixing-recyclerview-nested-scrolling-in-opposite-direction-f587be5c1a04 + // for more (the approach in that Medium article works, but is still quite sensitive to + // horizontal movement while scrolling). + try { + val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView") + recyclerViewField.isAccessible = true + val recyclerView = recyclerViewField.get(this) as RecyclerView + + val touchSlopField = RecyclerView::class.java.getDeclaredField("mTouchSlop") + touchSlopField.isAccessible = true + val touchSlop = touchSlopField.get(recyclerView) as Int + // 4 seems to be a sweet-spot. 2-3 still causes a horizontal swipe right if the user drags + // down-left at ~ 45 degree angle. Experimentally, 4 requires the swipe to be +/- ~ 10 degrees + // from horizontal to register as a horizontal and not a vertical swipe. + val scaleFactor = 4 + touchSlopField.set(recyclerView, touchSlop * scaleFactor) + } catch (e: Exception) { + Log.w("reduceSwipeSensitivity", e) + } +}