From e4679b1663fa78a99c6c8225e454595c6c6f4e38 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 17 Jul 2017 12:38:42 -0700 Subject: Fix notifications and long press for albums (#69) * Allow for album images to be viewed * Update listing info * Web refractoring * Test message notifications * Fix notifications and context press --- .../com/pitchedapps/frost/web/SearchWebView.kt | 145 +++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt (limited to 'app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt') diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt new file mode 100644 index 00000000..325d0333 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt @@ -0,0 +1,145 @@ +package com.pitchedapps.frost.web + +import android.annotation.SuppressLint +import android.content.Context +import android.view.View +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.USER_AGENT_BASIC +import com.pitchedapps.frost.injectors.JsAssets +import com.pitchedapps.frost.injectors.JsBuilder +import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.Prefs +import io.reactivex.schedulers.Schedulers +import io.reactivex.subjects.PublishSubject +import org.jetbrains.anko.runOnUiThread +import org.jsoup.Jsoup +import java.util.concurrent.TimeUnit + +@SuppressLint("ViewConstructor") +/** + * Created by Allan Wang on 2017-06-25. + * + * A bare bone headless search view meant solely to extract search results from the web + * Having a single webview allows us to avoid loading the whole page with each query + */ +class SearchWebView(context: Context, val contract: SearchContract) : WebView(context) { + + val searchSubject = PublishSubject.create() + + init { + gone() + setupWebview() + } + + /** + * Basic info of last search results, so we can check if the list has actually changed + * 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) + + fun saveResultFrame(result: List, String>>) { + synchronized(previousResult) { + previousResult = Pair(result.lastOrNull()?.second, result.size) + } + } + + @SuppressLint("SetJavaScriptEnabled") + private fun setupWebview() { + settings.javaScriptEnabled = true + settings.userAgentString = USER_AGENT_BASIC + webViewClient = HeadlessWebViewClient("Search", JsAssets.SEARCH) + webChromeClient = QuietChromeClient() + addJavascriptInterface(SearchJSI(), "Frost") + searchSubject.debounce(300, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.newThread()) + .map { + Jsoup.parse(it).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() } + val pair = Pair(texts, element.attr("href")) + L.v("Search element potential $pair") + pair + }.filter { it.first.isNotEmpty() } + } + .filter { content -> Pair(content.lastOrNull()?.second, content.size) != previousResult } + .subscribe { + content: List, String>> -> + saveResultFrame(content) + L.d("Search element count ${content.size}") + contract.emitSearchResponse(content.map { + (texts, href) -> + SearchItem(href, texts[0], texts.getOrNull(1)) + }) + } + reload() + } + + /** + * Toggles web activity + * Should be done in conjunction with showing/hiding the search view + */ + var pauseLoad: Boolean + get() = settings.blockNetworkLoads + set(value) { + context.runOnUiThread { settings.blockNetworkLoads = value } + } + + override fun reload() { + super.loadUrl(FbTab.SEARCH.url) + } + + /** + * Sets the input to have our given text, then dispatches the input event so the webpage recognizes it + */ + fun query(input: String) { + pauseLoad = false + L.d("Searching attempt", input) + JsBuilder().js("var e=document.getElementById('main-search-input');if(e){e.value='$input';var n=new Event('input',{bubbles:!0,cancelable:!0});e.dispatchEvent(n),e.dispatchEvent(new Event('focus'))}else console.log('Input field not found');").build().inject(this) + } + + inner class SearchJSI { + @JavascriptInterface + fun handleHtml(html: String) { + L.d("Search received response ${contract.isSearchOpened}") + if (!contract.isSearchOpened) pauseLoad = true + searchSubject.onNext(html) + } + + @JavascriptInterface + fun emit(flag: Int) { + when (flag) { + 0 -> { + 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") + Prefs.searchBar = false + searchSubject.onComplete() + contract.searchOverlayDispose() + } + } + } + } + + /** + * Clear up some components + */ + fun dispose() { + searchSubject.onComplete() + } + + interface SearchContract { + fun searchOverlayDispose() + fun emitSearchResponse(items: List) + val isSearchOpened: Boolean + } +} + + + -- cgit v1.2.3