aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/main/assets/js/search.js7
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt49
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt15
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt128
-rw-r--r--build.gradle2
6 files changed, 152 insertions, 51 deletions
diff --git a/app/src/main/assets/js/search.js b/app/src/main/assets/js/search.js
index 3d10e6f5..d3b319d4 100644
--- a/app/src/main/assets/js/search.js
+++ b/app/src/main/assets/js/search.js
@@ -1,10 +1,13 @@
//binds callbacks to an invisible webview to take in the search events
console.log('Binding Search');
-var page = document.querySelector('#page');
+var _f_page = document.querySelector('#page');
+var _f_input = document.querySelector('#main-search-input')
+if (!_f_page || !_f_input) return Frost.emit(1);
+Frost.emit(0);
var x = new MutationObserver(function(mutations) {
Frost.handleHtml(page.innerHTML);
});
-x.observe(page, {
+x.observe(_f_page, {
childList: true,
subtree: true
});
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt
index b8feef0f..cccb3e0c 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt
@@ -9,6 +9,7 @@ import android.support.design.widget.FloatingActionButton
import android.support.design.widget.Snackbar
import android.support.design.widget.TabLayout
import android.support.v4.app.ActivityOptionsCompat
+import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentPagerAdapter
import android.support.v4.view.ViewPager
@@ -39,13 +40,15 @@ import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL
import com.pitchedapps.frost.fragments.WebFragment
import com.pitchedapps.frost.utils.*
import com.pitchedapps.frost.views.BadgedIcon
+import com.pitchedapps.frost.web.FrostWebViewSearch
import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import org.jsoup.Jsoup
import java.util.concurrent.TimeUnit
-class MainActivity : BaseActivity() {
+class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract {
lateinit var adapter: SectionsPagerAdapter
val toolbar: Toolbar by bindView(R.id.toolbar)
@@ -58,6 +61,16 @@ class MainActivity : BaseActivity() {
var webFragmentObservable = PublishSubject.create<Int>()!!
var lastPosition = -1
val headerBadgeObservable = PublishSubject.create<String>()
+ var hiddenSearchView: FrostWebViewSearch? = null
+ var firstLoadFinished = false
+ set(value) {
+ L.d("First fragment load has finished")
+ field = value
+ if (value && hiddenSearchView == null) {
+ hiddenSearchView = FrostWebViewSearch(this, this)
+ currentFragment.frostWebView.addView(hiddenSearchView)
+ }
+ }
companion object {
const val FRAGMENT_REFRESH = 99
@@ -283,6 +296,21 @@ class MainActivity : BaseActivity() {
onClick { _ -> onClick(); false }
}
+
+ /**
+ * Something happened where the normal search function won't work
+ * Fallback to overlay style
+ */
+ override fun searchOverlayError() {
+ hiddenSearchView = null
+ //todo remove true searchview and add contract
+ }
+
+ //todo add args
+ override fun emitSearchResponse() {
+
+ }
+
fun refreshAll() {
webFragmentObservable.onNext(FRAGMENT_REFRESH)
}
@@ -331,7 +359,24 @@ class MainActivity : BaseActivity() {
inner class SectionsPagerAdapter(fm: FragmentManager, val pages: List<FbTab>) : FragmentPagerAdapter(fm) {
- override fun getItem(position: Int) = WebFragment(pages[position], position)
+ override fun getItem(position: Int): Fragment {
+ val fragment = WebFragment(pages[position], position)
+ //If first load hasn't occurred, add a listener
+ if (!firstLoadFinished) {
+ var disposable: Disposable? = null
+ fragment.post {
+ disposable = it.web.refreshObservable.subscribe {
+ if (!it) {
+ //Ensure first load finisher only happens once
+ if (!firstLoadFinished) firstLoadFinished = true
+ disposable?.dispose()
+ disposable = null
+ }
+ }
+ }
+ }
+ return fragment
+ }
override fun getCount() = pages.size
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt
index cd15c2c3..bd8b58ab 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt
@@ -32,7 +32,7 @@ class WebFragment : Fragment() {
putString(ARG_URL, data.url)
putInt(ARG_POSITION, position)
putSerializable(ARG_URL_ENUM, when (data) {
- //If is feed, check if sorting method is specified
+ //If is feed, check if sorting method is specified
FbTab.FEED -> when (FeedSort(Prefs.feedSort)) {
FeedSort.DEFAULT -> data
FeedSort.MOST_RECENT -> FbTab.FEED_MOST_RECENT
@@ -48,9 +48,18 @@ class WebFragment : Fragment() {
val url: String by lazy { arguments.getString(ARG_URL) }
val urlEnum: FbTab by lazy { arguments.getSerializable(ARG_URL_ENUM) as FbTab }
val position: Int by lazy { arguments.getInt(ARG_POSITION) }
- lateinit private var frostWebView: FrostWebView
+ lateinit var frostWebView: FrostWebView
private var firstLoad = true
private var activityDisposable: Disposable? = null
+ private var onCreateRunnable: ((fragment: WebFragment) -> Unit)? = null
+
+ /**
+ * Hook to run action once fragment is properly created
+ * This is not saved elsewhere and may not always execute
+ */
+ fun post(action: (fragment: WebFragment) -> Unit) {
+ onCreateRunnable = action
+ }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
@@ -61,6 +70,8 @@ class WebFragment : Fragment() {
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ onCreateRunnable?.invoke(this)
+ onCreateRunnable = null
firstLoad()
}
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 a176042e..3ef1430d 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt
@@ -10,7 +10,7 @@ import com.pitchedapps.frost.utils.L
* //TODO add folder mapping using Prefs
*/
enum class JsAssets : InjectorContract {
- MENU, CLICK_A, CLICK_INTERCEPTOR, HEADER_BADGES
+ MENU, CLICK_A, CLICK_INTERCEPTOR, HEADER_BADGES, SEARCH
;
var file = "${name.toLowerCase()}.min.js"
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt
index 0795efe2..85dfb254 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt
@@ -2,80 +2,122 @@ package com.pitchedapps.frost.web
import android.annotation.SuppressLint
import android.content.Context
-import android.graphics.Color
-import android.support.v4.view.NestedScrollingChild
-import android.util.AttributeSet
+import android.os.Handler
import android.view.View
+import android.webkit.JavascriptInterface
import android.webkit.WebView
-import ca.allanwang.kau.utils.*
+import android.webkit.WebViewClient
import com.pitchedapps.frost.facebook.FbTab
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
-import com.pitchedapps.frost.utils.Prefs
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.disposables.Disposable
-import io.reactivex.subjects.BehaviorSubject
+import com.pitchedapps.frost.injectors.JsAssets
+import com.pitchedapps.frost.injectors.JsBuilder
+import com.pitchedapps.frost.injectors.jsInject
+import com.pitchedapps.frost.utils.L
+import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
+import org.jsoup.Jsoup
+import org.jsoup.nodes.TextNode
+import java.util.concurrent.TimeUnit
+@SuppressLint("ViewConstructor")
/**
* Created by Allan Wang on 2017-06-25.
*
* A bare bone search view meant solely to extract data from the web
* This should be hidden
*/
-class FrostWebViewSearch (context: Context) : WebView(context) {
- var baseUrl: String? = null
- var baseEnum: FbTab? = null
- internal var frostWebClient: FrostWebViewClient? = null
+class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebView(context) {
+
+ val searchSubject = PublishSubject.create<String>()
init {
- gone()
+// gone()
setupWebview()
}
@SuppressLint("SetJavaScriptEnabled")
- fun setupWebview(url: String, enum: FbTab? = null) {
- baseUrl = url
- baseEnum = enum
+ fun setupWebview() {
settings.javaScriptEnabled = true
settings.userAgentString = USER_AGENT_BASIC
-// settings.domStorageEnabled = true
setLayerType(View.LAYER_TYPE_HARDWARE, null)
- frostWebClient = baseEnum?.webClient?.invoke(this) ?: FrostWebViewClient(this)
- webViewClient = frostWebClient
- webChromeClient = FrostChromeClient(this)
- addJavascriptInterface(FrostJSI(context, this), "Frost")
- setBackgroundColor(Color.TRANSPARENT)
+ webViewClient = FrostWebViewClientSearch()
+ addJavascriptInterface(SearchJSI(), "Frost")
+ searchSubject.debounce(200, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.newThread())
+ .map {
+ Jsoup.parse(it).select("a:not([rel*='keywords(']):not([href=#])[rel]").map {
+ element ->
+// L.d("Search element ${element.text()} ${element.textNodes().size} ${element.attr("href")}")
+ Pair(element.textNodes(), element.attr("href"))
+ }.filter { it.first.isNotEmpty() }
+ }
+ .subscribe {
+ content: List<Pair<List<TextNode>, String>> ->
+ content.forEach {
+// L.e("Search result ${it.second}")
+ }
+ contract.emitSearchResponse()
+ }
+ reload()
+ Handler().postDelayed({
+ query("hi")
+ }, 5000)
}
- fun loadUrl(url: String?, animate: Boolean) {
- if (url == null) return
- registerTransition(animate)
- super.loadUrl(url)
+ override fun reload() {
+ super.loadUrl(FbTab.SEARCH.url)
}
- fun reload(animate: Boolean) {
- registerTransition(animate)
- super.reload()
+ fun query(input: String) {
+ JsBuilder().js("var input=document.getElementById('main-search-input');input.click(),input.value='$input';").build().inject(this) {
+ L.d("Searching for $input")
+ }
}
/**
- * 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
+ * Created by Allan Wang on 2017-05-31.
+ *
+ * Barebones client that does what [FrostWebViewSearch] needs
*/
- 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 = 150L)
- else fadeIn(duration = 100L)
+ inner class FrostWebViewClientSearch : WebViewClient() {
+
+ override fun onPageFinished(view: WebView, url: String) {
+ super.onPageFinished(view, url)
+ L.i("Search Page finished $url")
+ view.jsInject(JsAssets.SEARCH)
+ }
+ }
+
+ inner class SearchJSI {
+ @JavascriptInterface
+ fun handleHtml(html: String) {
+// L.d("Search received response $html")
+ searchSubject.onNext(html)
+ }
+
+ @JavascriptInterface
+ fun emit(flag: Int) {
+ L.d("Search flag")
+ when (flag) {
+ 0 -> {
+ JsBuilder().js("document.getElementById('main-search-input').click()").build().inject(this@FrostWebViewSearch) {
+ L.d("Search click")
+ }
+ }
+ 1 -> { //something is not found in the search view; this is effectively useless
+ L.d("Search subject error; reverting to full overlay")
+ searchSubject.onComplete()
+ contract.searchOverlayError()
+ }
}
}
}
+ interface SearchContract {
+ fun searchOverlayError()
+ //todo add args
+ fun emitSearchResponse()
+ }
}
+
+
+
diff --git a/build.gradle b/build.gradle
index 083ca65f..df3b344f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.1.3'
+ ext.kotlin_version = '1.1.2-5'
repositories {
jcenter()
maven { url 'https://maven.fabric.io/public' }