From 5d9a3fd7fb8f2f9d0f592c89446824980c9841c6 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 14 Aug 2017 20:48:39 -0700 Subject: v1.4.5 (#174) * Update/kau (#125) * Update logger * Clean imports and bring back reactive libs * Update dependencies and make billing async * Misc (#128) * Update null * Attempt to improve transparent theme backgrounds * Update menu * Move injections to visible method and reduce offset * Update searchview and logging * Clean temp strings and add network states * Move console blacklist to web state * Change some logs to info * Move glide loader to onCreate (#135) * Remove commit number increments (#139) * Fix/misc (#140) * Add canadian locale to toLowerCase * Add try catch to JsAssets * Disable error throwing for bad search subject * Log more throwables quietly * Check internet connection before fetching username * Remove name check in frost notifications * Add activity lifecycle logger * Add rxjava to lib showcase * Move network checker to io thread (#150) * Update dependency * Blank * Feature/jsoup debugger (#152) * Create debugger * Update debugger content * Create debugging logic * Finalize and test debugger * Add reload listener * Fix/pro crash without play store (#155) * Update changelog * Check if iab service exists * Add checker before launching play store request * Separate strings * Enhancement/message notifications (#157) * Map message notifs to the headless html extractor * Update strings * Bring im notifs out of alpha * Update changelog * Remove confirmation dialog (#159) * Separate message notifications and add click intents (#171) * Separate message notifications and add click intent for group notifications * Add comments and finalize * Feature/scroll down on message thread (#172) * Add hook for scroll * Update changelog * Add custom navdrawer layout (#173) * Add faq for auto play * Update changelog * Fix page banner bg (#163) --- .../pitchedapps/frost/web/FrostChromeClients.kt | 12 +-- .../kotlin/com/pitchedapps/frost/web/FrostJSI.kt | 6 +- .../frost/web/FrostRequestInterceptor.kt | 29 ++++--- .../com/pitchedapps/frost/web/FrostWebView.kt | 6 +- .../pitchedapps/frost/web/FrostWebViewClients.kt | 82 +++++++++++--------- .../com/pitchedapps/frost/web/FrostWebViewCore.kt | 6 +- .../pitchedapps/frost/web/HeadlessHtmlExtractor.kt | 88 ++++++++++++++++++++++ .../com/pitchedapps/frost/web/LoginWebView.kt | 2 +- .../com/pitchedapps/frost/web/MessageWebView.kt | 67 ---------------- .../com/pitchedapps/frost/web/SearchWebView.kt | 33 +++++--- .../kotlin/com/pitchedapps/frost/web/WebStates.kt | 11 +++ 11 files changed, 201 insertions(+), 141 deletions(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/HeadlessHtmlExtractor.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/WebStates.kt (limited to 'app/src/main/kotlin/com/pitchedapps/frost/web') 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 6bc27256..61711092 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt @@ -34,15 +34,9 @@ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() { val activityContract = (webCore.context as? ActivityWebContract) val context = webCore.context!! - companion object { - val consoleBlacklist = setOf( - "edge-chat" - ) - } - override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean { if (consoleBlacklist.any { consoleMessage.message().contains(it) }) return true - L.i("Chrome Console ${consoleMessage.lineNumber()}: ${consoleMessage.message()}") + L.d("Chrome Console ${consoleMessage.lineNumber()}: ${consoleMessage.message()}") return true } @@ -63,10 +57,10 @@ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() { } override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) { - L.d("Requesting geolocation") + L.i("Requesting geolocation") context.kauRequestPermissions(PERMISSION_ACCESS_FINE_LOCATION) { granted, _ -> - L.d("Geolocation response received; ${if (granted) "granted" else "denied"}") + L.i("Geolocation response received; ${if (granted) "granted" else "denied"}") callback(origin, granted, true) } } 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 018ad737..f24a7a51 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -81,12 +81,14 @@ class FrostJSI(val webView: FrostWebViewCore) { } @JavascriptInterface - fun handleHtml(html: String) { + fun handleHtml(html: String?) { + html ?: return webView.post { webView.frostWebClient.handleHtml(html) } } @JavascriptInterface - fun handleHeader(html: String) { + fun handleHeader(html: String?) { + html ?: return headerObservable?.onNext(html) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt index 3f2891d0..1a907f7f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt @@ -5,6 +5,7 @@ import android.webkit.WebResourceResponse import android.webkit.WebView import ca.allanwang.kau.utils.use import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.Prefs import okhttp3.HttpUrl import java.io.ByteArrayInputStream @@ -15,17 +16,17 @@ import java.io.ByteArrayInputStream * Handler to decide when a request should be done by us * This is the crux of Frost's optimizations for the web browser */ -val blankResource: WebResourceResponse by lazy { WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray())) } +private val blankResource: WebResourceResponse by lazy { WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray())) } //these hosts will redirect to a blank resource -val blacklistHost: Set by lazy { +private val blacklistHost: Set by lazy { setOf( "edge-chat.facebook.com" ) } //these hosts will return null and skip logging -val whitelistHost: Set by lazy { +private val whitelistHost: Set by lazy { setOf( "static.xx.fbcdn.net", "m.facebook.com", @@ -35,13 +36,13 @@ val whitelistHost: Set by lazy { //these hosts will skip ad inspection //this list does not have to include anything from the two above -val adWhitelistHost: Set by lazy { +private val adWhitelistHost: Set by lazy { setOf( "scontent-sea1-1.xx.fbcdn.net" ) } -var adblock: Set? = null +private var adblock: Set? = null fun shouldFrostInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { val httpUrl = HttpUrl.parse(request.url?.toString() ?: return null) ?: return null @@ -53,7 +54,8 @@ fun shouldFrostInterceptRequest(view: WebView, request: WebResourceRequest): Web if (adblock == null) adblock = view.context.assets.open("adblock.txt").bufferedReader().use { it.readLines().toSet() } if (adblock?.any { url.contains(it) } ?: false) return blankResource } - L.v("Intercept Request ${host} ${url}") + if (!shouldLoadImages && !Prefs.loadMediaOnMeteredNetwork && request.isMedia) return blankResource + L.v("Intercept Request", "$host $url") return null } @@ -64,16 +66,25 @@ fun WebResourceRequest.query(action: (url: String) -> Boolean): Boolean { return action(url?.path ?: return false) } +val WebResourceRequest.isImage: Boolean + get() = query { it.contains(".jpg") || it.contains(".png") } + +val WebResourceRequest.isMedia: Boolean + get() = query { it.contains(".jpg") || it.contains(".png") || it.contains("video") } + /** * Generic filter passthrough * If Resource is already nonnull, pass it, otherwise check if filter is met and override the response accordingly */ -fun WebResourceResponse?.filter(request: WebResourceRequest, filter: (url: String) -> Boolean): WebResourceResponse? - = this ?: if (request.query { filter(it) }) blankResource else null +fun WebResourceResponse?.filter(request: WebResourceRequest, filter: (url: String) -> Boolean) + = filter(request.query { filter(it) }) + +fun WebResourceResponse?.filter(filter: Boolean): WebResourceResponse? + = this ?: if (filter) blankResource else null fun WebResourceResponse?.filterCss(request: WebResourceRequest): WebResourceResponse? = filter(request) { it.endsWith(".css") } fun WebResourceResponse?.filterImage(request: WebResourceRequest): WebResourceResponse? - = filter(request) { it.contains(".jpg") || it.contains(".png") } + = filter(request.isImage) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt index 79ca1fdf..89ad766d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt @@ -11,7 +11,7 @@ import android.widget.FrameLayout import android.widget.ProgressBar import ca.allanwang.kau.utils.* import com.pitchedapps.frost.R -import com.pitchedapps.frost.facebook.FbTab +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 @@ -53,7 +53,7 @@ class FrostWebView @JvmOverloads constructor( } @SuppressLint("SetJavaScriptEnabled") - fun setupWebview(url: String, enum: FbTab? = null) { + fun setupWebview(url: String, enum: FbItem? = null) { with(web) { baseUrl = url baseEnum = enum @@ -77,7 +77,7 @@ class FrostWebView @JvmOverloads constructor( //Some urls have postJavascript injections so make sure we load the base url override fun onRefresh() { when (web.baseUrl) { - FbTab.MENU.url -> web.loadBaseUrl(true) + FbItem.MENU.url -> web.loadBaseUrl(true) else -> web.reload(true) } } 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 94bff3c3..5f679c65 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt @@ -1,8 +1,8 @@ package com.pitchedapps.frost.web import android.content.Context -import android.content.Intent import android.graphics.Bitmap +import android.graphics.Color import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView @@ -14,10 +14,12 @@ import com.pitchedapps.frost.activities.WebOverlayActivity import com.pitchedapps.frost.facebook.FACEBOOK_COM import com.pitchedapps.frost.facebook.FB_URL_BASE import com.pitchedapps.frost.facebook.FbCookie +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 io.reactivex.subjects.Subject +import org.jetbrains.anko.withAlpha /** * Created by Allan Wang on 2017-05-31. @@ -42,18 +44,19 @@ open class BaseWebViewClient : WebViewClient() { open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient() { val refreshObservable: Subject = webCore.refreshObservable + val isMain = webCore.baseEnum != null override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) if (url == null) return - L.i("FWV Loading $url") -// L.v("Cookies ${CookieManager.getInstance().getCookie(url)}") + L.i("FWV Loading", url) refreshObservable.onNext(true) if (!url.contains(FACEBOOK_COM)) return if (url.contains("logout.php")) FbCookie.logout(Prefs.userId, { launchLogin(view.context) }) else if (url.contains("login.php")) FbCookie.reset({ launchLogin(view.context) }) } + fun launchLogin(c: Context) { if (c is MainActivity && c.cookies().isNotEmpty()) c.launchNewTask(SelectorActivity::class.java, c.cookies()) @@ -61,44 +64,52 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient c.launchNewTask(LoginActivity::class.java) } + fun injectBackgroundColor() + = webCore.setBackgroundColor(if (isMain) Color.TRANSPARENT else Prefs.bgColor.withAlpha(255)) + + + override fun onPageCommitVisible(view: WebView, url: String?) { + super.onPageCommitVisible(view, url) + injectBackgroundColor() + view.jsInject( + CssAssets.ROUND_ICONS.maybe(Prefs.showRoundedIcons), + CssHider.HEADER, + CssHider.PEOPLE_YOU_MAY_KNOW.maybe(!Prefs.showSuggestedFriends && IS_FROST_PRO), + Prefs.themeInjector, + CssHider.NON_RECENT.maybe(webCore.url?.contains("?sk=h_chr") ?: false)) + } + override fun onPageFinished(view: WebView, url: String?) { - super.onPageFinished(view, url) - if (url == null) return - L.i("Page finished $url") + url ?: return + L.i("Page finished", url) if (!url.contains(FACEBOOK_COM)) { refreshObservable.onNext(false) return } - view.jsInject( - CssAssets.ROUND_ICONS.maybe(Prefs.showRoundedIcons), - CssHider.PEOPLE_YOU_MAY_KNOW.maybe(!Prefs.showSuggestedFriends && IS_FROST_PRO), - CssHider.ADS.maybe(!Prefs.showFacebookAds && IS_FROST_PRO) - ) onPageFinishedActions(url) } open internal fun onPageFinishedActions(url: String) { + if (url.startsWith("${FbItem.MESSAGES.url}/read/") && Prefs.messageScrollToBottom) + webCore.pageDown(true) injectAndFinish() } internal fun injectAndFinish() { L.d("Page finished reveal") - webCore.jsInject(CssHider.HEADER, - CssHider.NON_RECENT.maybe(webCore.url.contains("?sk=h_chr")), - Prefs.themeInjector, - callback = { - refreshObservable.onNext(false) - webCore.jsInject( - JsActions.LOGIN_CHECK, - JsAssets.CLICK_A.maybe(webCore.baseEnum != null && Prefs.overlayEnabled), - JsAssets.TEXTAREA_LISTENER, - JsAssets.CONTEXT_A, - JsAssets.HEADER_BADGES.maybe(webCore.baseEnum != null) - ) - }) - } - - open fun handleHtml(html: String) { + refreshObservable.onNext(false) + injectBackgroundColor() + webCore.jsInject( + JsActions.LOGIN_CHECK, + JsAssets.CLICK_A.maybe(webCore.baseEnum != null && Prefs.overlayEnabled), + JsAssets.TEXTAREA_LISTENER, + CssHider.ADS.maybe(!Prefs.showFacebookAds && IS_FROST_PRO), + JsAssets.CONTEXT_A, + JsAssets.HEADER_BADGES.maybe(webCore.baseEnum != null) + ) + } + + open fun handleHtml(html: String?) { L.d("Handle Html") } @@ -112,26 +123,26 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient * returns false if we are already in an overlaying activity */ private fun launchRequest(request: WebResourceRequest): Boolean { - L.d("Launching Url", request.url.toString()) + L.d("Launching Url", request.url?.toString() ?: "null") if (webCore.context is WebOverlayActivity) return false webCore.context.launchWebOverlay(request.url.toString()) return true } - private fun launchImage(request: WebResourceRequest, text: String? = null): Boolean { - L.d("Launching Image", request.url.toString()) - webCore.context.launchImageActivity(request.url.toString(), text) + private fun launchImage(url: String, text: String? = null): Boolean { + L.d("Launching Image", url) + webCore.context.launchImageActivity(url, text) if (webCore.canGoBack()) webCore.goBack() return true } override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { - L.i("Url Loading ${request.url}") - val path = request.url.path ?: return super.shouldOverrideUrlLoading(view, request) - L.v("Url Loading Path $path") + L.i("Url Loading", request.url?.toString()) + val path = request.url?.path ?: return super.shouldOverrideUrlLoading(view, request) + L.v("Url Loading Path", path) if (path.startsWith("/composer/")) return launchRequest(request) if (request.url.toString().contains("scontent-sea1-1.xx.fbcdn.net") && (path.endsWith(".jpg") || path.endsWith(".png"))) - return launchImage(request) + return launchImage(request.url.toString()) if (view.context.resolveActivityForUri(request.url)) return true return super.shouldOverrideUrlLoading(view, request) } @@ -163,6 +174,7 @@ class FrostWebViewClientMenu(webCore: FrostWebViewCore) : FrostWebViewClient(web } override fun onPageFinishedActions(url: String) { + L.d("Should inject ${url.shouldInjectMenu}") if (!url.shouldInjectMenu) injectAndFinish() } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt index d8edc15c..6dbc7c8d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt @@ -14,7 +14,7 @@ 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.FbTab +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.utils.Prefs import io.reactivex.Scheduler import io.reactivex.android.schedulers.AndroidSchedulers @@ -41,7 +41,7 @@ class FrostWebViewCore @JvmOverloads constructor( var baseUrl: String? = null - var baseEnum: FbTab? = null //only viewpager items should pass the base enum + var baseEnum: FbItem? = null //only viewpager items should pass the base enum internal lateinit var frostWebClient: FrostWebViewClient init { @@ -76,7 +76,7 @@ class FrostWebViewCore @JvmOverloads constructor( if (isVisible) fadeOut(duration = 200L) } else if (loading) { dispose?.dispose() - if (animate && Prefs.animate) circularReveal(offset = 150L) + if (animate && Prefs.animate) circularReveal(offset = WEB_LOAD_DELAY) else fadeIn(duration = 100L) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/HeadlessHtmlExtractor.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/HeadlessHtmlExtractor.kt new file mode 100644 index 00000000..50f2f6bc --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/HeadlessHtmlExtractor.kt @@ -0,0 +1,88 @@ +package com.pitchedapps.frost.web + +import android.annotation.SuppressLint +import android.content.Context +import android.webkit.JavascriptInterface +import android.webkit.WebView +import ca.allanwang.kau.utils.gone +import com.pitchedapps.frost.R +import com.pitchedapps.frost.facebook.USER_AGENT_BASIC +import com.pitchedapps.frost.injectors.InjectorContract +import com.pitchedapps.frost.utils.L +import io.reactivex.Single +import io.reactivex.SingleEmitter +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import org.jetbrains.anko.runOnUiThread +import java.util.concurrent.TimeUnit + +/** + * Created by Allan Wang on 2017-08-12. + * + * Launches a headless html request and returns a result pair + * When successful, the pair will contain the html content and -1 + * When unsuccessful, the pair will contain an empty string and a StringRes for the given error + * + * All errors are rerouted to success calls, so no exceptions should occur. + * The headless extractor will also destroy itself on cancellation or when the request is finished + */ +fun Context.launchHeadlessHtmlExtractor(url: String, injector: InjectorContract, action: (Single>) -> Unit) { + val single = Single.create> { e: SingleEmitter> -> + val extractor = HeadlessHtmlExtractor(this, url, injector, e) + e.setCancellable { + runOnUiThread { extractor.destroy() } + e.onSuccess("" to R.string.html_extraction_cancelled) + } + }.subscribeOn(AndroidSchedulers.mainThread()) + .timeout(20, TimeUnit.SECONDS, Schedulers.io(), { it.onSuccess("" to R.string.html_extraction_timeout) }) + .onErrorReturn { "" to R.string.html_extraction_error } + action(single) +} + +/** + * Given a link and some javascript, will load the link and load the JS on completion + * The JS is expected to call [HeadlessHtmlExtractor.HtmlJSI.handleHtml], which will be sent + * to the [emitter] + */ +@SuppressLint("ViewConstructor") +private class HeadlessHtmlExtractor( + context: Context, url: String, val injector: InjectorContract, val emitter: SingleEmitter> +) : WebView(context) { + + val startTime = System.currentTimeMillis() + + init { + L.v("Created HeadlessHtmlExtractor for $url") + gone() + setupWebview(url) + } + + @SuppressLint("SetJavaScriptEnabled") + private fun setupWebview(url: String) { + settings.javaScriptEnabled = true + settings.userAgentString = USER_AGENT_BASIC + webViewClient = HeadlessWebViewClient(url, injector) // basic client that loads our JS once the page has loaded + webChromeClient = QuietChromeClient() // basic client that disables logging + addJavascriptInterface(HtmlJSI(), "Frost") + loadUrl(url) + } + + inner class HtmlJSI { + @JavascriptInterface + fun handleHtml(html: String?) { + val time = System.currentTimeMillis() - startTime + emitter.onSuccess((html ?: "") to -1) + post { + L.d("HeadlessHtmlExtractor fetched $url in $time ms") + settings.javaScriptEnabled = false + settings.blockNetworkLoads = true + destroy() + } + } + } + + override fun destroy() { + super.destroy() + L.d("HeadlessHtmlExtractor destroyed") + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt index 31be4450..aea25337 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt @@ -60,7 +60,7 @@ class LoginWebView @JvmOverloads constructor( view.jsInject(CssHider.HEADER.maybe(containsFacebook), CssHider.CORE.maybe(containsFacebook), Prefs.themeInjector.maybe(containsFacebook), - callback = { if (!view.isVisible) view.fadeIn(offset = 150L) }) + callback = { if (!view.isVisible) view.fadeIn(offset = WEB_LOAD_DELAY) }) } fun checkForLogin(url: String?, onFound: (id: Long, cookie: String) -> Unit) { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt deleted file mode 100644 index 53fa0657..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.pitchedapps.frost.web - -import android.annotation.SuppressLint -import android.app.job.JobParameters -import android.webkit.JavascriptInterface -import android.webkit.WebView -import ca.allanwang.kau.utils.gone -import com.pitchedapps.frost.dbflow.CookieModel -import com.pitchedapps.frost.facebook.FbTab -import com.pitchedapps.frost.facebook.USER_AGENT_BASIC -import com.pitchedapps.frost.injectors.JsAssets -import com.pitchedapps.frost.services.NotificationService -import com.pitchedapps.frost.utils.L -import com.pitchedapps.frost.utils.frostAnswersCustom -import org.jetbrains.anko.doAsync - -/** - * Created by Allan Wang on 2017-07-17. - * - * Bare boned headless view made solely to extract conversation info - */ -@SuppressLint("ViewConstructor") -class MessageWebView(val service: NotificationService, val params: JobParameters?, val cookie: CookieModel) : WebView(service) { - - private val startTime = System.currentTimeMillis() - private var isCancelled = false - - init { - gone() - setupWebview() - } - - @SuppressLint("SetJavaScriptEnabled") - private fun setupWebview() { - settings.javaScriptEnabled = true - settings.userAgentString = USER_AGENT_BASIC - webViewClient = HeadlessWebViewClient("MessageNotifs", JsAssets.NOTIF_MSG) - webChromeClient = QuietChromeClient() - addJavascriptInterface(MessageJSI(), "Frost") - loadUrl(FbTab.MESSAGES.url) - } - - fun finish() { - if (isCancelled) return - isCancelled = true - post { destroy() } - service.finish(params) - } - - override fun destroy() { - L.d("MessageWebView destroyed") - super.destroy() - } - - inner class MessageJSI { - @JavascriptInterface - fun handleHtml(html: String) { - if (isCancelled) return - if (html.length < 10) return finish() - val time = System.currentTimeMillis() - startTime - L.d("Notif messages fetched in $time ms") - frostAnswersCustom("NotificationTime", "Type" to "IM Headless", "Duration" to time) - doAsync { service.fetchMessageNotifications(cookie, html); finish() } - } - } - -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt index 05d56f92..da6d8ad3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt @@ -6,7 +6,7 @@ import android.webkit.JavascriptInterface import android.webkit.WebView import ca.allanwang.kau.searchview.SearchItem import ca.allanwang.kau.utils.gone -import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.injectors.JsAssets import com.pitchedapps.frost.injectors.JsBuilder @@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit */ class SearchWebView(context: Context, val contract: SearchContract) : WebView(context) { - val searchSubject = PublishSubject.create() + val searchSubject = PublishSubject.create()!! init { gone() @@ -39,11 +39,11 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co * Contains the last item's href (search more) as well as the number of items found * This holder is synchronized */ - var previousResult: Pair = Pair(null, 0) + var previousResult: Pair = Pair("", 0) fun saveResultFrame(result: List, String>>) { synchronized(previousResult) { - previousResult = Pair(result.lastOrNull()?.second, result.size) + previousResult = Pair(result.last().second, result.size) } } @@ -56,17 +56,22 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co addJavascriptInterface(SearchJSI(), "Frost") searchSubject.debounce(300, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.newThread()) .map { - Jsoup.parse(it).select("a:not([rel*='keywords(']):not([href=#])[rel]").map { + val doc = Jsoup.parse(it) + L.d(doc.getElementById("main-search_input")?.html()) + val searchQuery = doc.getElementById("main-search-input")?.text() ?: "Null input" + L.d("Search query", searchQuery) + doc.select("a:not([rel*='keywords(']):not([href=#])[rel]").map { element -> //split text into separate items - L.v("Search element ${element.attr("href")}") - val texts = element.select("div").map { (it.text()) }.filter { it.isNotBlank() } + L.v("Search element", element.attr("href")) + val texts = element.select("div").map { it.text() }.filter { !it.isNullOrBlank() } val pair = Pair(texts, element.attr("href")) - L.v("Search element potential $pair") + L.v("Search element potential", pair.toString()) pair }.filter { it.first.isNotEmpty() } } - .filter { content -> Pair(content.lastOrNull()?.second, content.size) != previousResult } + .filter { it.isNotEmpty() } + .filter { Pair(it.last().second, it.size) != previousResult } .subscribe { content: List, String>> -> saveResultFrame(content) @@ -90,7 +95,7 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co } override fun reload() { - super.loadUrl(FbTab.SEARCH.url) + super.loadUrl(FbItem.SEARCH.url) } /** @@ -104,7 +109,8 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co inner class SearchJSI { @JavascriptInterface - fun handleHtml(html: String) { + fun handleHtml(html: String?) { + html ?: return L.d("Search received response ${contract.isSearchOpened}") if (!contract.isSearchOpened) pauseLoad = true searchSubject.onNext(html) @@ -117,11 +123,14 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co L.d("Search loaded successfully") } 1 -> { //something is not found in the search view; this is effectively useless - L.eThrow("Search subject error; reverting to full overlay") + L.e("Search subject error; reverting to full overlay") Prefs.searchBar = false searchSubject.onComplete() contract.searchOverlayDispose() } + 2 -> { + L.v("Search emission received") + } } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/WebStates.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/WebStates.kt new file mode 100644 index 00000000..ad1fe467 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/WebStates.kt @@ -0,0 +1,11 @@ +package com.pitchedapps.frost.web + +/** + * Created by Allan Wang on 2017-08-08. + * + * Global variables that are define states or constants for web contents + */ +const val WEB_LOAD_DELAY = 50L +var shouldLoadImages = false + +val consoleBlacklist = setOf("edge-chat") \ No newline at end of file -- cgit v1.2.3