From 4c551386b100ea4b694c1e8f44596ba369e6b068 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 2 Jul 2019 16:29:28 -0700 Subject: Prevent horizontal swipes if html element can scroll horizontally --- .../com/pitchedapps/frost/injectors/JsAssets.kt | 2 +- .../kotlin/com/pitchedapps/frost/web/FrostJSI.kt | 5 ++ .../pitchedapps/frost/web/FrostWebViewClients.kt | 1 + .../com/pitchedapps/frost/web/NestedWebView.kt | 18 +++++-- app/src/web/ts/click_a.ts | 10 ++-- app/src/web/ts/horizontal_scrolling.ts | 61 ++++++++++++++++++++++ app/src/web/typings/frost.d.ts | 26 ++++----- 7 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 app/src/web/ts/horizontal_scrolling.ts (limited to 'app') diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt index ad42418e..d46422b8 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt @@ -34,7 +34,7 @@ import java.util.Locale */ enum class JsAssets : InjectorContract { MENU, CLICK_A, CONTEXT_A, MEDIA, HEADER_BADGES, TEXTAREA_LISTENER, NOTIF_MSG, - DOCUMENT_WATCHER + DOCUMENT_WATCHER, HORIZONTAL_SCROLLING ; @VisibleForTesting diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt index 0d980ba0..d75a4f1f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -140,4 +140,9 @@ class FrostJSI(val web: FrostWebView) { html ?: return header?.offer(html) } + + @JavascriptInterface + fun allowHorizontalScrolling(enable: Boolean) { + activity?.viewPager?.enableSwipe = enable + } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt index 003ed7f9..85914f33 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt @@ -102,6 +102,7 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() { Prefs.aggressiveRecents ), JsAssets.DOCUMENT_WATCHER, + JsAssets.HORIZONTAL_SCROLLING, JsAssets.CLICK_A, CssHider.ADS.maybe(!Prefs.showFacebookAds), JsAssets.CONTEXT_A, diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/NestedWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/NestedWebView.kt index 7c3c48e7..da0ebf0d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/NestedWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/NestedWebView.kt @@ -120,10 +120,20 @@ open class NestedWebView @JvmOverloads constructor( dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray? - ) = childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow) - - final override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?) = - childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow) + ) = childHelper.dispatchNestedScroll( + dxConsumed, + dyConsumed, + dxUnconsumed, + dyUnconsumed, + offsetInWindow + ) + + final override fun dispatchNestedPreScroll( + dx: Int, + dy: Int, + consumed: IntArray?, + offsetInWindow: IntArray? + ) = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow) final override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean) = childHelper.dispatchNestedFling(velocityX, velocityY, consumed) diff --git a/app/src/web/ts/click_a.ts b/app/src/web/ts/click_a.ts index 8d1d545b..1fd63683 100644 --- a/app/src/web/ts/click_a.ts +++ b/app/src/web/ts/click_a.ts @@ -91,14 +91,16 @@ prevented = true; }; + const _frostAllowClick = () => { + prevented = false; + clearTimeout(clickTimeout) + }; + document.addEventListener('click', _frostAClick, true); let clickTimeout: number | undefined = undefined; document.addEventListener('touchstart', () => { clickTimeout = setTimeout(_frostPreventClick, 400); }, true); - document.addEventListener('touchend', () => { - prevented = false; - clearTimeout(clickTimeout) - }, true); + document.addEventListener('touchend', _frostAllowClick, true); }).call(undefined); diff --git a/app/src/web/ts/horizontal_scrolling.ts b/app/src/web/ts/horizontal_scrolling.ts new file mode 100644 index 00000000..b104725e --- /dev/null +++ b/app/src/web/ts/horizontal_scrolling.ts @@ -0,0 +1,61 @@ +(function () { + + /** + * Go up at most [depth] times, to retrieve a parent matching the provided predicate + * If one is found, it is returned immediately. + * Otherwise, null is returned. + */ + function _parentEl(el: HTMLElement, depth: number, predicate: (el: HTMLElement) => boolean): HTMLElement | null { + for (let i = 0; i < depth + 1; i++) { + if (predicate(el)) { + return el + } + const parent = el.parentElement; + if (!parent) { + return null + } + el = parent + } + return null + } + + /** + * Check if element can scroll horizontally. + * We primarily rely on the overflow-x field. + * For performance reasons, we will check scrollWidth first to see if scrolling is a possibility + */ + function _canScrollHorizontally(el: HTMLElement): boolean { + /* + * Sometimes the offsetWidth is off by < 10px. We use the multiplier + * since the trays are typically more than 2 times greater + */ + if (el.scrollWidth > el.offsetWidth * 1.2) { + return true + } + const styles = window.getComputedStyle(el); + /* + * Works well in testing, but on mobile it just shows 'visible' + */ + return styles.overflowX === 'scroll'; + } + + const _frostCheckHorizontalScrolling = (e: Event) => { + const target = e.target || e.currentTarget || e.srcElement; + if (!(target instanceof HTMLElement)) { + return + } + const scrollable = _parentEl(target, 5, _canScrollHorizontally) !== null; + if (scrollable) { + console.log('Pause horizontal scrolling'); + Frost.allowHorizontalScrolling(false); + } + }; + + const _frostResetHorizontalScrolling = (e: Event) => { + Frost.allowHorizontalScrolling(true) + }; + + document.addEventListener('touchstart', _frostCheckHorizontalScrolling, true); + document.addEventListener('touchend', _frostResetHorizontalScrolling, true); +}).call(undefined); + diff --git a/app/src/web/typings/frost.d.ts b/app/src/web/typings/frost.d.ts index 8f60c9dd..ae7c97ab 100644 --- a/app/src/web/typings/frost.d.ts +++ b/app/src/web/typings/frost.d.ts @@ -1,27 +1,29 @@ declare interface FrostJSI { - loadUrl(url: string | null): boolean + loadUrl(url: string | null): boolean - loadVideo(url: string | null, isGif: boolean): boolean + loadVideo(url: string | null, isGif: boolean): boolean - reloadBaseUrl(animate: boolean) + reloadBaseUrl(animate: boolean) - contextMenu(url: string | null, text: string | null) + contextMenu(url: string | null, text: string | null) - longClick(start: boolean) + longClick(start: boolean) - disableSwipeRefresh(disable: boolean) + disableSwipeRefresh(disable: boolean) - loadLogin() + loadLogin() - loadImage(imageUrl: string, text: string | null) + loadImage(imageUrl: string, text: string | null) - emit(flag: number) + emit(flag: number) - isReady() + isReady() - handleHtml(html: string | null) + handleHtml(html: string | null) - handleHeader(html: string | null) + handleHeader(html: string | null) + + allowHorizontalScrolling(enable: boolean) } declare var Frost: FrostJSI; -- cgit v1.2.3