diff options
author | Allan Wang <me@allanwang.ca> | 2017-12-21 02:16:34 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-21 02:16:34 -0500 |
commit | d683cae6ffe644a9f63eea6cf3b7e59d2bde617b (patch) | |
tree | 517fe1d44c27084ccd87507d9804ba28f15c1647 /app/src/main/kotlin/com/pitchedapps/frost/web | |
parent | 82f9aca96493316bc62008f2b3167d34a6029b38 (diff) | |
download | frost-d683cae6ffe644a9f63eea6cf3b7e59d2bde617b.tar.gz frost-d683cae6ffe644a9f63eea6cf3b7e59d2bde617b.tar.bz2 frost-d683cae6ffe644a9f63eea6cf3b7e59d2bde617b.zip |
Enhancement/fragment interface (#564)
* Begin fragment interfaces and themable contracts
* Prepare swiperefresh interface
* Snapshot
* Add compilable version
* Revamp once more
* Finalize layouts
* Cleanup
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/web')
7 files changed, 160 insertions, 346 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt index 2fa80830..344fcb27 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt @@ -5,9 +5,10 @@ import android.webkit.* import ca.allanwang.kau.permissions.PERMISSION_ACCESS_FINE_LOCATION import ca.allanwang.kau.permissions.kauRequestPermissions import com.pitchedapps.frost.R -import com.pitchedapps.frost.contracts.ActivityWebContract +import com.pitchedapps.frost.contracts.ActivityContract import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.frostSnackbar +import com.pitchedapps.frost.views.FrostWebView import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject @@ -45,12 +46,12 @@ class HeadlessChromeClient : WebChromeClient() { /** * The default chrome client */ -class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() { +class FrostChromeClient(web: FrostWebView) : WebChromeClient() { - val progressObservable: Subject<Int> = webCore.progressObservable - val titleObservable: BehaviorSubject<String> = webCore.titleObservable - val activityContract = (webCore.context as? ActivityWebContract) - val context = webCore.context!! + private val progress: Subject<Int> = web.parent.progressObservable + private val title: BehaviorSubject<String> = web.parent.titleObservable + private val activity = (web.context as? ActivityContract) + private val context = web.context!! override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean { if (consoleBlacklist.any { consoleMessage.message().contains(it) }) return true @@ -60,18 +61,18 @@ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() { override fun onReceivedTitle(view: WebView, title: String) { super.onReceivedTitle(view, title) - if (title.contains("http") || titleObservable.value == title) return - titleObservable.onNext(title) + if (title.contains("http") || this.title.value == title) return + this.title.onNext(title) } override fun onProgressChanged(view: WebView, newProgress: Int) { super.onProgressChanged(view, newProgress) - progressObservable.onNext(newProgress) + progress.onNext(newProgress) } override fun onShowFileChooser(webView: WebView, filePathCallback: ValueCallback<Array<Uri>?>, fileChooserParams: FileChooserParams): Boolean { - activityContract?.openFileChooser(filePathCallback, fileChooserParams) ?: webView.frostSnackbar(R.string.file_chooser_not_found) - return activityContract != null + activity?.openFileChooser(filePathCallback, fileChooserParams) ?: webView.frostSnackbar(R.string.file_chooser_not_found) + return activity != null } override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) { 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 6bdb459e..e8135f5b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -1,31 +1,24 @@ package com.pitchedapps.frost.web -import android.content.Context import android.support.v4.widget.SwipeRefreshLayout import android.webkit.JavascriptInterface import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.contracts.VideoViewHolder -import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.* +import com.pitchedapps.frost.views.FrostWebView import io.reactivex.subjects.Subject /** * Created by Allan Wang on 2017-06-01. */ -class FrostJSI(val webView: FrostWebViewCore) { +class FrostJSI(val web: FrostWebView) { - val context: Context - get() = webView.context - - val activity: MainActivity? - get() = (context as? MainActivity) - - val headerObservable: Subject<String>? = activity?.headerBadgeObservable - - val cookies: ArrayList<CookieModel> - get() = activity?.cookies() ?: arrayListOf() + private val context = web.context + private val activity = context as? MainActivity + private val header: Subject<String>? = activity?.headerBadgeObservable + private val cookies = activity?.cookies() ?: arrayListOf() /** * Attempts to load the url in an overlay @@ -34,12 +27,12 @@ class FrostJSI(val webView: FrostWebViewCore) { */ @JavascriptInterface fun loadUrl(url: String?): Boolean - = if (url == null) false else webView.requestWebOverlay(url) + = if (url == null) false else web.requestWebOverlay(url) @JavascriptInterface fun loadVideo(url: String?, isGif: Boolean) { if (url != null) - webView.post { + web.post { (context as? VideoViewHolder)?.showVideo(url, isGif) ?: L.d("Could not load video; contract not implemented") } @@ -48,9 +41,9 @@ class FrostJSI(val webView: FrostWebViewCore) { @JavascriptInterface fun reloadBaseUrl(animate: Boolean) { L.d("FrostJSI reload") - webView.post { - webView.stopLoading() - webView.loadBaseUrl(animate) + web.post { + web.stopLoading() + web.reloadBase(animate) } } @@ -58,7 +51,7 @@ class FrostJSI(val webView: FrostWebViewCore) { fun contextMenu(url: String, text: String?) { if (!text.isIndependent) return //url will be formatted through webcontext - webView.post { context.showWebContextMenu(WebContext(url, text)) } + web.post { context.showWebContextMenu(WebContext(url, text)) } } /** @@ -75,7 +68,7 @@ class FrostJSI(val webView: FrostWebViewCore) { */ @JavascriptInterface fun disableSwipeRefresh(disable: Boolean) { - webView.post { (webView.parent as? SwipeRefreshLayout)?.isEnabled = !disable } + web.post { (web.parent as? SwipeRefreshLayout)?.isEnabled = !disable } } @JavascriptInterface @@ -93,19 +86,19 @@ class FrostJSI(val webView: FrostWebViewCore) { @JavascriptInterface fun emit(flag: Int) { - webView.post { webView.frostWebClient.emit(flag) } + web.post { web.frostWebClient.emit(flag) } } @JavascriptInterface fun handleHtml(html: String?) { html ?: return - webView.post { webView.frostWebClient.handleHtml(html) } + web.post { web.frostWebClient.handleHtml(html) } } @JavascriptInterface fun handleHeader(html: String?) { html ?: return - headerObservable?.onNext(html) + header?.onNext(html) } }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt index d1f144a6..9255b5bb 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt @@ -9,6 +9,7 @@ import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.facebook.formattedFbUrl import com.pitchedapps.frost.utils.* +import com.pitchedapps.frost.views.FrostWebView import org.jetbrains.anko.runOnUiThread /** @@ -27,7 +28,7 @@ import org.jetbrains.anko.runOnUiThread * whether the user agent string should be changed. All propagated results will return false, * as we have no need of sending a new intent to the same activity */ -fun FrostWebViewCore.requestWebOverlay(url: String): Boolean { +fun FrostWebView.requestWebOverlay(url: String): Boolean { if (url == "#" || !url.isIndependent) { L.i("Forbid overlay switch", url) return false diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt deleted file mode 100644 index f6d64ab7..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.pitchedapps.frost.web - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.Color -import android.os.Build -import android.support.v4.widget.SwipeRefreshLayout -import android.util.AttributeSet -import android.view.View -import android.widget.FrameLayout -import android.widget.ProgressBar -import ca.allanwang.kau.utils.* -import com.pitchedapps.frost.R -import com.pitchedapps.frost.facebook.FbItem -import com.pitchedapps.frost.facebook.USER_AGENT_BASIC -import com.pitchedapps.frost.utils.Prefs -import com.pitchedapps.frost.utils.frostDownload -import io.reactivex.android.schedulers.AndroidSchedulers - -/** - * Created by Allan Wang on 2017-06-01. - */ -class FrostWebView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr, defStyleRes), SwipeRefreshLayout.OnRefreshListener { - - val refresh: SwipeRefreshLayout by bindView(R.id.swipe_refresh) - val web: FrostWebViewCore by bindView(R.id.frost_webview_core) - val progress: ProgressBar by bindView(R.id.progress_bar) - - init { - inflate(getContext(), R.layout.swipe_webview, this) - progress.tint(Prefs.textColor.withAlpha(180)) - refresh.setColorSchemeColors(Prefs.iconColor) - refresh.setProgressBackgroundColorSchemeColor(Prefs.headerColor.withAlpha(255)) - web.progressObservable.observeOn(AndroidSchedulers.mainThread()).subscribe { - progress.invisibleIf(it == 100) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) progress.setProgress(it, true) - else progress.progress = it - } - web.refreshObservable.observeOn(AndroidSchedulers.mainThread()).subscribe { - refresh.isRefreshing = it - refresh.isEnabled = true - } - refresh.setOnRefreshListener(this) - addOnAttachStateChangeListener(object : OnAttachStateChangeListener { - override fun onViewDetachedFromWindow(v: View) { - web.visible() - } - - override fun onViewAttachedToWindow(v: View) {} - }) - } - - @SuppressLint("SetJavaScriptEnabled") - fun setupWebview(url: String, enum: FbItem? = null) { - with(web) { - baseUrl = url - baseEnum = enum - with(settings) { - javaScriptEnabled = true - if (url.shouldUseBasicAgent) - userAgentString = USER_AGENT_BASIC - allowFileAccess = true - textZoom = Prefs.webTextScaling - } - setLayerType(View.LAYER_TYPE_HARDWARE, null) - frostWebClient = baseEnum?.webClient?.invoke(this) ?: FrostWebViewClient(this) - webViewClient = frostWebClient - webChromeClient = FrostChromeClient(this) - addJavascriptInterface(FrostJSI(this), "Frost") - setBackgroundColor(Color.TRANSPARENT) - setDownloadListener(context::frostDownload) - } - } - - //Some urls have postJavascript injections so make sure we load the base url - override fun onRefresh() { - when (web.baseUrl) { - FbItem.MENU.url -> web.loadBaseUrl(true) - else -> web.reload(true) - } - } - - fun onBackPressed(): Boolean { - if (web.canGoBack()) { - web.goBack() - return true - } - return false - } -}
\ No newline at end of file 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 5fc1ab27..71c71b66 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt @@ -15,6 +15,7 @@ import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.injectors.* import com.pitchedapps.frost.utils.* import com.pitchedapps.frost.utils.iab.IS_FROST_PRO +import com.pitchedapps.frost.views.FrostWebView import io.reactivex.subjects.Subject import org.jetbrains.anko.withAlpha @@ -38,16 +39,16 @@ open class BaseWebViewClient : WebViewClient() { /** * The default webview client */ -open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient() { +open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() { - val refreshObservable: Subject<Boolean> = webCore.refreshObservable - val isMain = webCore.baseEnum != null + private val refresh: Subject<Boolean> = web.parent.refreshObservable + private val isMain = web.parent.baseEnum != null override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) if (url == null) return L.d("FWV Loading", url) - refreshObservable.onNext(true) + refresh.onNext(true) } fun launchLogin(c: Context) { @@ -58,10 +59,10 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient } private fun injectBackgroundColor() { - webCore.setBackgroundColor( + web.setBackgroundColor( when { isMain -> Color.TRANSPARENT - webCore.url.isFacebookUrl -> Prefs.bgColor.withAlpha(255) + web.url.isFacebookUrl -> Prefs.bgColor.withAlpha(255) else -> Color.WHITE } ) @@ -80,7 +81,7 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient CssHider.PEOPLE_YOU_MAY_KNOW.maybe(!Prefs.showSuggestedFriends && IS_FROST_PRO), CssHider.SUGGESTED_GROUPS.maybe(!Prefs.showSuggestedGroups && IS_FROST_PRO), Prefs.themeInjector, - CssHider.NON_RECENT.maybe((webCore.url?.contains("?sk=h_chr") ?: false) + CssHider.NON_RECENT.maybe((web.url?.contains("?sk=h_chr") ?: false) && Prefs.aggressiveRecents)) } @@ -88,7 +89,7 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient url ?: return L.d("Page finished", url) if (!url.isFacebookUrl) { - refreshObservable.onNext(false) + refresh.onNext(false) return } onPageFinishedActions(url) @@ -96,22 +97,22 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient open internal fun onPageFinishedActions(url: String) { if (url.startsWith("${FbItem.MESSAGES.url}/read/") && Prefs.messageScrollToBottom) - webCore.pageDown(true) + web.pageDown(true) injectAndFinish() } internal fun injectAndFinish() { L.d("Page finished reveal") - refreshObservable.onNext(false) + refresh.onNext(false) injectBackgroundColor() - webCore.jsInject( + web.jsInject( JsActions.LOGIN_CHECK, JsAssets.CLICK_A, JsAssets.TEXTAREA_LISTENER, CssHider.ADS.maybe(!Prefs.showFacebookAds && IS_FROST_PRO), JsAssets.CONTEXT_A, JsAssets.MEDIA, - JsAssets.HEADER_BADGES.maybe(webCore.baseEnum != null) + JsAssets.HEADER_BADGES.maybe(web.parent.baseEnum != null) ) } @@ -130,13 +131,13 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient */ private fun launchRequest(request: WebResourceRequest): Boolean { L.d("Launching Url", request.url?.toString() ?: "null") - return webCore.requestWebOverlay(request.url.toString()) + return web.requestWebOverlay(request.url.toString()) } private fun launchImage(url: String, text: String? = null): Boolean { L.d("Launching Image", url) - webCore.context.launchImageActivity(url, text) - if (webCore.canGoBack()) webCore.goBack() + web.context.launchImageActivity(url, text) + if (web.canGoBack()) web.goBack() return true } @@ -161,7 +162,7 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient /** * Client variant for the menu view */ -class FrostWebViewClientMenu(webCore: FrostWebViewCore) : FrostWebViewClient(webCore) { +class FrostWebViewClientMenu(web: FrostWebView) : FrostWebViewClient(web) { private val String.shouldInjectMenu get() = when (removePrefix(FB_URL_BASE)) { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt deleted file mode 100644 index 15383a50..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt +++ /dev/null @@ -1,203 +0,0 @@ -package com.pitchedapps.frost.web - -import android.animation.ValueAnimator -import android.annotation.SuppressLint -import android.content.Context -import android.support.v4.view.NestedScrollingChild -import android.support.v4.view.NestedScrollingChildHelper -import android.support.v4.view.ViewCompat -import android.util.AttributeSet -import android.view.MotionEvent -import android.view.animation.DecelerateInterpolator -import android.webkit.WebView -import ca.allanwang.kau.utils.circularReveal -import ca.allanwang.kau.utils.fadeIn -import ca.allanwang.kau.utils.fadeOut -import ca.allanwang.kau.utils.isVisible -import com.pitchedapps.frost.facebook.FbItem -import com.pitchedapps.frost.utils.Prefs -import io.reactivex.Scheduler -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.subjects.BehaviorSubject -import io.reactivex.subjects.PublishSubject - -/** - * Created by Allan Wang on 2017-05-29. - * - */ -class FrostWebViewCore @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : WebView(context, attrs, defStyleAttr), NestedScrollingChild { - - private val childHelper = NestedScrollingChildHelper(this) - private var lastY: Int = 0 - private val scrollOffset = IntArray(2) - private val scrollConsumed = IntArray(2) - private var nestedOffsetY: Int = 0 - val progressObservable: PublishSubject<Int> // Keeps track of every progress change - val refreshObservable: PublishSubject<Boolean> // Only emits on page loads - val titleObservable: BehaviorSubject<String> // Only emits on different non http titles - - - var baseUrl: String? = null - var baseEnum: FbItem? = null //only viewpager items should pass the base enum - internal lateinit var frostWebClient: FrostWebViewClient - - /** - * Wrapper to the main userAgentString to cache it. - * This decouples it from the UiThread - * - * Note that this defaults to null, but the main purpose is to - * check if we've set our own agent. - * - * A null value may be interpreted as the default value - */ - var userAgentString: String? = null - get() = field - set(value) { - field = value - settings.userAgentString = value - } - - init { - isNestedScrollingEnabled = true - progressObservable = PublishSubject.create<Int>() - refreshObservable = PublishSubject.create<Boolean>() - titleObservable = BehaviorSubject.create<String>() - } - - fun loadUrl(url: String?, animate: Boolean) { - if (url == null) return - registerTransition(animate) - super.loadUrl(url) - } - - fun reload(animate: Boolean) { - registerTransition(animate) - super.reload() - } - - /** - * Hook onto the refresh observable for one cycle - * Animate toggles between the fancy ripple and the basic fade - * The cycle only starts on the first load since there may have been another process when this is registered - */ - fun registerTransition(animate: Boolean) { - var dispose: Disposable? = null - var loading = false - dispose = refreshObservable.subscribeOn(AndroidSchedulers.mainThread()).subscribe { - if (it) { - loading = true - if (isVisible) fadeOut(duration = 200L) - } else if (loading) { - dispose?.dispose() - if (animate && Prefs.animate) circularReveal(offset = WEB_LOAD_DELAY) - else fadeIn(duration = 100L) - } - } - } - - fun loadBaseUrl(animate: Boolean = true) { - loadUrl(baseUrl, animate) - } - - fun addTitleListener(subscriber: (title: String) -> Unit, scheduler: Scheduler = AndroidSchedulers.mainThread()): Disposable - = titleObservable.observeOn(scheduler).subscribe(subscriber) - - /** - * Handle nested scrolling against SwipeRecyclerView - * Courtesy of takahirom - * - * https://github.com/takahirom/webview-in-coordinatorlayout/blob/master/app/src/main/java/com/github/takahirom/webview_in_coodinator_layout/NestedWebView.java - */ - @SuppressLint("ClickableViewAccessibility") - override fun onTouchEvent(ev: MotionEvent): Boolean { - val event = MotionEvent.obtain(ev) - val action = event.action - if (action == MotionEvent.ACTION_DOWN) - nestedOffsetY = 0 - val eventY = event.y.toInt() - event.offsetLocation(0f, nestedOffsetY.toFloat()) - val returnValue: Boolean - when (action) { - MotionEvent.ACTION_MOVE -> { - var deltaY = lastY - eventY - // NestedPreScroll - if (dispatchNestedPreScroll(0, deltaY, scrollConsumed, scrollOffset)) { - deltaY -= scrollConsumed[1] - event.offsetLocation(0f, -scrollOffset[1].toFloat()) - nestedOffsetY += scrollOffset[1] - } - lastY = eventY - scrollOffset[1] - returnValue = super.onTouchEvent(event) - // NestedScroll - if (dispatchNestedScroll(0, scrollOffset[1], 0, deltaY, scrollOffset)) { - event.offsetLocation(0f, scrollOffset[1].toFloat()) - nestedOffsetY += scrollOffset[1] - lastY -= scrollOffset[1] - } - } - MotionEvent.ACTION_DOWN -> { - returnValue = super.onTouchEvent(event) - lastY = eventY - startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) - } - MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { - returnValue = super.onTouchEvent(event) - stopNestedScroll() - } - else -> return false - } - return returnValue - } - - /** - * If webview is already at the top, refresh - * Otherwise scroll to top - */ - fun scrollOrRefresh() { - if (scrollY < 5) loadBaseUrl() - else scrollToTop() - } - - fun scrollToTop() { - flingScroll(0, 0) // stop fling - if (scrollY > 10000) { - scrollTo(0, 0) - } else { - ValueAnimator.ofInt(scrollY, 0).apply { - duration = Math.min(scrollY, 500).toLong() - interpolator = DecelerateInterpolator() - addUpdateListener { scrollY = it.animatedValue as Int } - start() - } - } - } - - // Nested Scroll implements - override fun setNestedScrollingEnabled(enabled: Boolean) { - childHelper.isNestedScrollingEnabled = enabled - } - - override fun isNestedScrollingEnabled() = childHelper.isNestedScrollingEnabled - - override fun startNestedScroll(axes: Int) = childHelper.startNestedScroll(axes) - - override fun stopNestedScroll() = childHelper.stopNestedScroll() - - override fun hasNestedScrollingParent() = childHelper.hasNestedScrollingParent() - - override fun dispatchNestedScroll(dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?) - = childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow) - - override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?) - = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow) - - override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean) - = childHelper.dispatchNestedFling(velocityX, velocityY, consumed) - - override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float) - = childHelper.dispatchNestedPreFling(velocityX, velocityY) - -}
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/NestedWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/NestedWebView.kt new file mode 100644 index 00000000..f0bb6137 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/NestedWebView.kt @@ -0,0 +1,113 @@ +package com.pitchedapps.frost.web + +import android.annotation.SuppressLint +import android.content.Context +import android.support.v4.view.NestedScrollingChild +import android.support.v4.view.NestedScrollingChildHelper +import android.support.v4.view.ViewCompat +import android.util.AttributeSet +import android.view.MotionEvent +import android.webkit.WebView + + +/** + * Created by Allan Wang on 20/12/17. + * + * Webview extension that handles nested scrolls + */ +open class NestedWebView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : WebView(context, attrs, defStyleAttr), NestedScrollingChild { + + private lateinit var childHelper: NestedScrollingChildHelper + private var lastY: Int = 0 + private val scrollOffset = IntArray(2) + private val scrollConsumed = IntArray(2) + private var nestedOffsetY: Int = 0 + + init { + init() + } + + fun init() { + // To avoid leaking constructor + childHelper = NestedScrollingChildHelper(this) + } + + /** + * Handle nested scrolling against SwipeRecyclerView + * Courtesy of takahirom + * + * https://github.com/takahirom/webview-in-coordinatorlayout/blob/master/app/src/main/java/com/github/takahirom/webview_in_coodinator_layout/NestedWebView.java + */ + @SuppressLint("ClickableViewAccessibility") + override final fun onTouchEvent(ev: MotionEvent): Boolean { + val event = MotionEvent.obtain(ev) + val action = event.action + if (action == MotionEvent.ACTION_DOWN) + nestedOffsetY = 0 + val eventY = event.y.toInt() + event.offsetLocation(0f, nestedOffsetY.toFloat()) + val returnValue: Boolean + when (action) { + MotionEvent.ACTION_MOVE -> { + var deltaY = lastY - eventY + // NestedPreScroll + if (dispatchNestedPreScroll(0, deltaY, scrollConsumed, scrollOffset)) { + deltaY -= scrollConsumed[1] + event.offsetLocation(0f, -scrollOffset[1].toFloat()) + nestedOffsetY += scrollOffset[1] + } + lastY = eventY - scrollOffset[1] + returnValue = super.onTouchEvent(event) + // NestedScroll + if (dispatchNestedScroll(0, scrollOffset[1], 0, deltaY, scrollOffset)) { + event.offsetLocation(0f, scrollOffset[1].toFloat()) + nestedOffsetY += scrollOffset[1] + lastY -= scrollOffset[1] + } + } + MotionEvent.ACTION_DOWN -> { + returnValue = super.onTouchEvent(event) + lastY = eventY + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + returnValue = super.onTouchEvent(event) + stopNestedScroll() + } + else -> return false + } + return returnValue + } + + /* + * --------------------------------------------- + * Nested Scrolling Content + * --------------------------------------------- + */ + + override final fun setNestedScrollingEnabled(enabled: Boolean) { + childHelper.isNestedScrollingEnabled = enabled + } + + override final fun isNestedScrollingEnabled() = childHelper.isNestedScrollingEnabled + + override final fun startNestedScroll(axes: Int) = childHelper.startNestedScroll(axes) + + override final fun stopNestedScroll() = childHelper.stopNestedScroll() + + override final fun hasNestedScrollingParent() = childHelper.hasNestedScrollingParent() + + override final fun dispatchNestedScroll(dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?) + = childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow) + + override final fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?) + = childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow) + + override final fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean) + = childHelper.dispatchNestedFling(velocityX, velocityY, consumed) + + override final fun dispatchNestedPreFling(velocityX: Float, velocityY: Float) + = childHelper.dispatchNestedPreFling(velocityX, velocityY) +}
\ No newline at end of file |