From e4679b1663fa78a99c6c8225e454595c6c6f4e38 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 17 Jul 2017 12:38:42 -0700 Subject: [PATCH] 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 --- README.md | 3 +- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 2 +- app/src/main/assets/js/context_a.js | 5 +- app/src/main/assets/js/context_a.min.js | 5 +- .../frost/activities/MainActivity.kt | 10 +-- .../pitchedapps/frost/facebook/FbCookie.kt | 10 ++- .../pitchedapps/frost/injectors/JsActions.kt | 1 + .../frost/services/NotificationService.kt | 43 ++++++---- .../frost/web/BaseWebViewClient.kt | 16 ---- ...tChromeClient.kt => FrostChromeClients.kt} | 13 +++ .../frost/web/FrostRequestInterceptor.kt | 24 ++++-- .../frost/web/FrostWebViewClientMenu.kt | 35 -------- ...ebViewClient.kt => FrostWebViewClients.kt} | 76 ++++++++++++++++- .../pitchedapps/frost/web/MessageWebView.kt | 81 +++++++++++++++++++ ...FrostWebViewSearch.kt => SearchWebView.kt} | 39 ++------- .../main/play/en-CA/listing/fulldescription | 5 +- app/src/main/res/xml/changelog.xml | 12 +-- 18 files changed, 250 insertions(+), 131 deletions(-) delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/BaseWebViewClient.kt rename app/src/main/kotlin/com/pitchedapps/frost/web/{FrostChromeClient.kt => FrostChromeClients.kt} (88%) delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClientMenu.kt rename app/src/main/kotlin/com/pitchedapps/frost/web/{FrostWebViewClient.kt => FrostWebViewClients.kt} (62%) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt rename app/src/main/kotlin/com/pitchedapps/frost/web/{FrostWebViewSearch.kt => SearchWebView.kt} (80%) diff --git a/README.md b/README.md index 7f6203daef6..0a347c2b1fb 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ It contains many features, including: * Full theming across all activities * Overlaying browser to read posts and get right back to your previous task * Extensive notification support, with bundling, filtering, battery friendly scheduling, icons, and multi user support -* Context menu from any link through long press +* Context menu from any link via long press +* Native image viewer and downloader via long press * Reactive based loading * The transparency of open sourced development diff --git a/app/build.gradle b/app/build.gradle index a354d2e99f3..8729ed692cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -71,6 +71,7 @@ android { versionNameSuffix "-debug" resValue "string", "app_name", "Frost Debug" resValue "string", "frost_web", "Frost Web Debug" + ext.enableCrashlytics = false } releaseTest { minifyEnabled true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4135ab59fc6..5758f38c7a9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,7 +9,7 @@ - + diff --git a/app/src/main/assets/js/context_a.js b/app/src/main/assets/js/context_a.js index c760176404c..689c6f0d64c 100644 --- a/app/src/main/assets/js/context_a.js +++ b/app/src/main/assets/js/context_a.js @@ -25,8 +25,9 @@ if (!window.hasOwnProperty('frost_context_a')) { if (!url) return; var text = element.parentNode.innerText; - //check if image item exists - var image = element.parentNode.querySelector('[style*="background-image: url("]'); + //check if image item exists, first in children and then in parent + var image = element.querySelector('[style*="background-image: url("]'); + if (!image) image = element.parentNode.querySelector('[style*="background-image: url("]'); if (image) { var imageUrl = window.getComputedStyle(image, null).backgroundImage.slice(5, -2); console.log('Context image', imageUrl); diff --git a/app/src/main/assets/js/context_a.min.js b/app/src/main/assets/js/context_a.min.js index 5c5f033af0d..97799c33679 100644 --- a/app/src/main/assets/js/context_a.min.js +++ b/app/src/main/assets/js/context_a.min.js @@ -9,8 +9,9 @@ longClick=!0 "A"!==t.tagName&&(t=t.parentNode),"A"===t.tagName&&"#"!==t.getAttribute("href"))){ var o=t.getAttribute("href") ;if(!o)return -;var n=t.parentNode.innerText,r=t.parentNode.querySelector('[style*="background-image: url("]') -;if(r){ +;var n=t.parentNode.innerText,r=t.querySelector('[style*="background-image: url("]') +;if(r||(r=t.parentNode.querySelector('[style*="background-image: url("]')), +r){ var a=window.getComputedStyle(r,null).backgroundImage.slice(5,-2) ;console.log("Context image",a), "undefined"!=typeof Frost&&Frost.loadImage(a,n) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt index ba76e594b7d..1227fd6bf78 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt @@ -55,7 +55,7 @@ import com.pitchedapps.frost.utils.* import com.pitchedapps.frost.utils.iab.validatePro import com.pitchedapps.frost.views.BadgedIcon import com.pitchedapps.frost.views.FrostViewPager -import com.pitchedapps.frost.web.FrostWebViewSearch +import com.pitchedapps.frost.web.SearchWebView import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers @@ -63,7 +63,7 @@ import io.reactivex.subjects.PublishSubject import org.jsoup.Jsoup import java.util.concurrent.TimeUnit -class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract, +class MainActivity : BaseActivity(), SearchWebView.SearchContract, ActivityWebContract, FileChooserContract by FileChooserDelegate() { lateinit var adapter: SectionsPagerAdapter @@ -78,13 +78,13 @@ class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract, var webFragmentObservable = PublishSubject.create()!! var lastPosition = -1 val headerBadgeObservable = PublishSubject.create() - var hiddenSearchView: FrostWebViewSearch? = null + var hiddenSearchView: SearchWebView? = null var firstLoadFinished = false set(value) { L.d("First fragment load has finished") field = value if (value && hiddenSearchView == null) { - hiddenSearchView = FrostWebViewSearch(this, this) + hiddenSearchView = SearchWebView(this, this) } } var searchView: SearchView? = null @@ -354,7 +354,7 @@ class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract, R.id.action_settings to GoogleMaterial.Icon.gmd_settings, R.id.action_search to GoogleMaterial.Icon.gmd_search) if (Prefs.searchBar) { - if (firstLoadFinished && hiddenSearchView == null) hiddenSearchView = FrostWebViewSearch(this, this) + if (firstLoadFinished && hiddenSearchView == null) hiddenSearchView = SearchWebView(this, this) if (searchView == null) searchView = bindSearchView(menu, R.id.action_search, Prefs.iconColor) { textObserver = { observable, _ -> diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt index 875f1c49d69..3b0125bea8a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt @@ -94,12 +94,14 @@ object FbCookie { * When coming back to the main app, switch back to our original account before continuing */ fun switchBackUser(callback: () -> Unit) { - if (Prefs.prevId != -1L && Prefs.prevId != Prefs.userId) { - switchUser(Prefs.prevId) { - L.d("Switch back user", "${Prefs.userId} to ${Prefs.prevId}") + if (Prefs.prevId == -1L) return callback() + val prevId = Prefs.prevId + Prefs.prevId = -1L + if (prevId != Prefs.userId) { + switchUser(prevId) { + L.d("Switch back user", "${Prefs.userId} to ${prevId}") callback() } } else callback() - if (Prefs.prevId != -1L) Prefs.prevId = -1L } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt index 4c44c1bf1ca..de2709484d7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt @@ -15,6 +15,7 @@ enum class JsActions(body: String) : InjectorContract { */ LOGIN_CHECK("document.getElementById('signup-button')&&Frost.loadLogin();"), BASE_HREF("document.write(\"\");"), + GET_MESSAGES("setTimeout(function(){Frost.handleHtml(document.getElementById('threadlist_rows').outerHtml)},1000)"), EMPTY(""); val function = "!function(){$body}();" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt index 38282bf7fe2..ad977d1aba1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -17,7 +17,9 @@ import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostAnswersCustom +import com.pitchedapps.frost.web.MessageWebView import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread import org.jsoup.Jsoup import org.jsoup.nodes.Element import java.util.concurrent.Future @@ -45,17 +47,29 @@ class NotificationService : JobService() { return false } + override fun onStartJob(params: JobParameters?): Boolean { future = doAsync { if (Prefs.notificationAllAccounts) { - loadFbCookiesSync().forEach { - data -> - fetchNotifications(data) - } + val cookies = loadFbCookiesSync() + cookies.forEach { fetchGeneralNotifications(it) } +// if (Prefs.notificationsInstantMessages) { +// Prefs.prevId = Prefs.userId +// uiThread { +// val messageWebView = MessageWebView(this@NotificationService, params) +// cookies.forEach { messageWebView.request(it) } +// } +// return@doAsync +// } } else { val currentCookie = loadFbCookie(Prefs.userId) - if (currentCookie != null) - fetchNotifications(currentCookie) + if (currentCookie != null) { + fetchGeneralNotifications(currentCookie) +// if (Prefs.notificationsInstantMessages) { +// uiThread { MessageWebView(this@NotificationService, params).request(currentCookie) } +// return@doAsync +// } + } } L.d("Finished notifications") jobFinished(params, false) @@ -69,12 +83,6 @@ class NotificationService : JobService() { return null } - fun fetchNotifications(data: CookieModel) { - fetchGeneralNotifications(data) -// fetchMessageNotifications(data) - debugNotification("Hello") - } - fun fetchGeneralNotifications(data: CookieModel) { L.i("Notif fetch for $data") val doc = Jsoup.connect(FbTab.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).userAgent(USER_AGENT_BASIC).get() @@ -96,7 +104,8 @@ class NotificationService : JobService() { newLatestEpoch = notif.timestamp notifCount++ } - if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epoch = newLatestEpoch).update() + if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epoch = newLatestEpoch).save() + L.d("Notif new latest epoch ${lastNotificationTime(data.id).epoch}") frostAnswersCustom("Notifications") { putCustomAttribute("Type", "General") putCustomAttribute("Count", notifCount) @@ -120,10 +129,9 @@ class NotificationService : JobService() { return NotificationContent(data, notifId.toInt(), a.attr("href"), null, text, epoch, pUrl) } - fun fetchMessageNotifications(data: CookieModel) { - if (!Prefs.notificationsInstantMessages) return + fun fetchMessageNotifications(data: CookieModel, content: String) { L.i("Notif IM fetch for $data") - val doc = Jsoup.connect(FbTab.MESSAGES.url).cookie(FACEBOOK_COM, data.cookie).userAgent(USER_AGENT_BASIC).get() + val doc = Jsoup.parseBodyFragment(content) val unreadNotifications = (doc.getElementById("threadlist_rows") ?: return L.eThrow("Notification messages not found")).getElementsByClass("aclb") var notifCount = 0 L.d("IM notif count ${unreadNotifications.size}") @@ -146,7 +154,8 @@ class NotificationService : JobService() { newLatestEpoch = notif.timestamp notifCount++ } -// if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epochIm = newLatestEpoch).update() + if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epochIm = newLatestEpoch).save() + L.d("Notif new latest im epoch ${lastNotificationTime(data.id).epochIm}") frostAnswersCustom("Notifications") { putCustomAttribute("Type", "Message") putCustomAttribute("Count", notifCount) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/BaseWebViewClient.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/BaseWebViewClient.kt deleted file mode 100644 index 09241254b82..00000000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/BaseWebViewClient.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.pitchedapps.frost.web - -import android.webkit.WebResourceRequest -import android.webkit.WebResourceResponse -import android.webkit.WebView -import android.webkit.WebViewClient - -/** - * Created by Allan Wang on 2017-07-13. - */ -open class BaseWebViewClient : WebViewClient() { - - override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? - = shouldFrostInterceptRequest(view, request) - -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt similarity index 88% rename from app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt rename to app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt index 4df6d6a7b43..b8ba0d1d4cf 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt @@ -14,6 +14,19 @@ import io.reactivex.subjects.Subject /** * Created by Allan Wang on 2017-05-31. + * + * Collection of chrome clients + */ + +/** + * Nothing more than a client without logging + */ +class QuietChromeClient : WebChromeClient() { + override fun onConsoleMessage(consoleMessage: ConsoleMessage) = true +} + +/** + * The default chrome client */ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() { 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 45dc83aa100..3f2891d0c44 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt @@ -1,17 +1,12 @@ package com.pitchedapps.frost.web -import android.graphics.Bitmap.CompressFormat import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView import ca.allanwang.kau.utils.use -import com.pitchedapps.frost.utils.GlideApp import com.pitchedapps.frost.utils.L -import com.pitchedapps.frost.utils.Prefs import okhttp3.HttpUrl import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.InputStream /** @@ -62,6 +57,23 @@ fun shouldFrostInterceptRequest(view: WebView, request: WebResourceRequest): Web return null } +/** + * Wrapper to ensure that null exceptions are not reached + */ +fun WebResourceRequest.query(action: (url: String) -> Boolean): Boolean { + return action(url?.path ?: return false) +} + +/** + * 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?.filterCss(request: WebResourceRequest): WebResourceResponse? - = this ?: if (request.url.path.endsWith(".css")) blankResource else null + = filter(request) { it.endsWith(".css") } + +fun WebResourceResponse?.filterImage(request: WebResourceRequest): WebResourceResponse? + = filter(request) { it.contains(".jpg") || it.contains(".png") } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClientMenu.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClientMenu.kt deleted file mode 100644 index 10648e734c6..00000000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClientMenu.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.pitchedapps.frost.web - -import android.webkit.WebView -import com.pitchedapps.frost.facebook.FB_URL_BASE -import com.pitchedapps.frost.injectors.JsAssets -import com.pitchedapps.frost.injectors.jsInject - -/** - * Created by Allan Wang on 2017-05-31. - */ -class FrostWebViewClientMenu(webCore: FrostWebViewCore) : FrostWebViewClient(webCore) { - - private val String.shouldInjectMenu - get() = when (removePrefix(FB_URL_BASE)) { - "settings", - "settings#", - "settings#!/settings?soft=bookmarks" -> true - else -> false - } - - override fun onPageFinished(view: WebView, url: String) { - super.onPageFinished(view, url) - if (url.shouldInjectMenu) jsInject(JsAssets.MENU) - } - - override fun emit(flag: Int) { - super.emit(flag) - super.injectAndFinish() - } - - override fun onPageFinishedActions(url: String) { - if (!url.shouldInjectMenu) injectAndFinish() - } - -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt similarity index 62% rename from app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt rename to app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt index 5b2b4bfd012..3e6ddd061a3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt @@ -3,11 +3,14 @@ package com.pitchedapps.frost.web import android.content.Context import android.graphics.Bitmap import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse import android.webkit.WebView +import android.webkit.WebViewClient import com.pitchedapps.frost.activities.LoginActivity import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.activities.SelectorActivity 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.injectors.* import com.pitchedapps.frost.utils.* @@ -15,6 +18,23 @@ import io.reactivex.subjects.Subject /** * Created by Allan Wang on 2017-05-31. + * + * Collection of webview clients + */ + +/** + * The base of all webview clients + * Used to ensure that resources are properly intercepted + */ +open class BaseWebViewClient : WebViewClient() { + + override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? + = shouldFrostInterceptRequest(view, request) + +} + +/** + * The default webview client */ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient() { @@ -96,9 +116,57 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient return super.shouldOverrideUrlLoading(view, request) } -// override fun onPageCommitVisible(view: WebView?, url: String?) { -// L.d("ASDF PCV") -// super.onPageCommitVisible(view, url) -// } +} + +/** + * Client variant for the menu view + */ +class FrostWebViewClientMenu(webCore: FrostWebViewCore) : FrostWebViewClient(webCore) { + + private val String.shouldInjectMenu + get() = when (removePrefix(FB_URL_BASE)) { + "settings", + "settings#", + "settings#!/settings?soft=bookmarks" -> true + else -> false + } + + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + if (url.shouldInjectMenu) jsInject(JsAssets.MENU) + } + + override fun emit(flag: Int) { + super.emit(flag) + super.injectAndFinish() + } + + override fun onPageFinishedActions(url: String) { + if (!url.shouldInjectMenu) injectAndFinish() + } +} + +/** + * Headless client that injects content after a page load + * The JSI is meant to handle everything else + */ +class HeadlessWebViewClient(val tag: String, val postInjection: InjectorContract) : BaseWebViewClient() { + + override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + L.d("Headless Page $tag Started", url) + } + + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + L.d("Headless Page $tag Finished", url) + postInjection.inject(view) + } + + /** + * In addition to general filtration, we will also strip away css and images + */ + override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? + = super.shouldInterceptRequest(view, request).filterCss(request).filterImage(request) } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt new file mode 100644 index 00000000000..0f3a12b6e8d --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt @@ -0,0 +1,81 @@ +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.FbCookie +import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.USER_AGENT_BASIC +import com.pitchedapps.frost.injectors.JsActions +import com.pitchedapps.frost.services.NotificationService +import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.frostAnswersCustom +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.runOnUiThread + +@SuppressLint("ViewConstructor") +/** + * Created by Allan Wang on 2017-07-17. + * + * Bare boned headless view made solely to extract conversation info + */ +class MessageWebView(val service: NotificationService, val params: JobParameters?) : WebView(service) { + + init { + gone() + setupWebview() + } + + @SuppressLint("SetJavaScriptEnabled") + private fun setupWebview() { + settings.javaScriptEnabled = true + settings.userAgentString = USER_AGENT_BASIC + webViewClient = HeadlessWebViewClient("MessageNotifs", JsActions.GET_MESSAGES) + webChromeClient = QuietChromeClient() + addJavascriptInterface(MessageJSI(), "Frost") + } + + private val startTime = System.currentTimeMillis() + private val endTime: Long by lazy { System.currentTimeMillis() } + private var inProgress = false + private val pendingRequests: MutableList = mutableListOf() + private lateinit var data: CookieModel + + fun request(data: CookieModel) { + pendingRequests.add(data) + if (inProgress) return + inProgress = true + load(data) + } + + private fun load(data: CookieModel) { + L.d("Notif retrieving messages", data.toString()) + this.data = data + FbCookie.setWebCookie(data.cookie) { context.runOnUiThread { L.d("Notif messages load"); loadUrl(FbTab.MESSAGES.url) } } + } + + inner class MessageJSI { + @JavascriptInterface + fun handleHtml(html: String) { + L.d("Notif messages received", data.toString()) + doAsync { service.fetchMessageNotifications(data, html) } + pendingRequests.remove(data) + if (pendingRequests.isEmpty()) { + val time = endTime - startTime + L.d("Notif messages finished $time") + frostAnswersCustom("Notifications") { + putCustomAttribute("Message retrieval duration", time) + } + post { destroy() } + service.jobFinished(params, false) + service.future = null + } else { + load(pendingRequests.first()) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt similarity index 80% rename from app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt rename to app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt index bcadf32af30..325d0333db2 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt @@ -3,14 +3,14 @@ package com.pitchedapps.frost.web import android.annotation.SuppressLint import android.content.Context import android.view.View -import android.webkit.* +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.injectors.jsInject import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import io.reactivex.schedulers.Schedulers @@ -23,11 +23,10 @@ import java.util.concurrent.TimeUnit /** * 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 + * 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 FrostWebViewSearch(context: Context, val contract: SearchContract) : WebView(context) { +class SearchWebView(context: Context, val contract: SearchContract) : WebView(context) { val searchSubject = PublishSubject.create() @@ -50,12 +49,11 @@ class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebVi } @SuppressLint("SetJavaScriptEnabled") - fun setupWebview() { + private fun setupWebview() { settings.javaScriptEnabled = true settings.userAgentString = USER_AGENT_BASIC - setLayerType(View.LAYER_TYPE_HARDWARE, null) - webViewClient = SearchWebViewClient() - webChromeClient = SearchChromeClient() + webViewClient = HeadlessWebViewClient("Search", JsAssets.SEARCH) + webChromeClient = QuietChromeClient() addJavascriptInterface(SearchJSI(), "Frost") searchSubject.debounce(300, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.newThread()) .map { @@ -105,29 +103,6 @@ class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebVi 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) } - /** - * Created by Allan Wang on 2017-05-31. - * - * Barebones client that does what [FrostWebViewSearch] needs - */ - inner class SearchWebViewClient : BaseWebViewClient() { - - override fun onPageFinished(view: WebView, url: String) { - super.onPageFinished(view, url) - L.i("Search Page finished $url") - view.jsInject(JsAssets.SEARCH) - } - - override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? - = super.shouldInterceptRequest(view, request).filterCss(request) - } - - class SearchChromeClient : WebChromeClient() { - - //mute console - override fun onConsoleMessage(consoleMessage: ConsoleMessage) = true - } - inner class SearchJSI { @JavascriptInterface fun handleHtml(html: String) { diff --git a/app/src/main/play/en-CA/listing/fulldescription b/app/src/main/play/en-CA/listing/fulldescription index 61c94833f46..32cf05037f3 100644 --- a/app/src/main/play/en-CA/listing/fulldescription +++ b/app/src/main/play/en-CA/listing/fulldescription @@ -3,6 +3,7 @@ While being a web wrapper, Frost contains many unique and native features such a • True multi user interactions - More than just an option in a settings menu, Frost's account switcher is right in the drawer. You are one tap away from switching accounts, and everything refreshes on the switch so that you can view other accounts instantaneously. Furthermore, the notification service will fetch notifications from all accounts, and will let you know which account has the new notification. • Better multitasking - Frost contains an overlaying web browser that can be drawn on top of your foreground task. Open links and notifications with a full screen view, then swipe away to get back to your previous task. +• Contextual awareness - Frost integrates additional features via long presses. Need to copy a block of text or share a link? Long press the text. Need to zoom into an image or download it? Long press the image! • Material Design - Built for lollipop and up, Frost focuses strongly on a good UI, and embraces material transitions and dimensions. • Complete theme engine - Frost contains very comprehensive themes that customize all components of the app. Frost is also the only app to support transparent themes. • Fully opened - Nothing speaks for privacy more than being open sourced. Frost is proud to be one of those apps, and can be found on github (Link in the app's about section) @@ -11,7 +12,9 @@ Permissions used and why: • Internet, Network State, Wifi State - Frost fetches the pages from Facebook's mobile website. It also needs the network state so as to limit internet usage when you are on a metered network. • Receive Boot Completed - Frost notifications persist on reboot, and need this permission to be added each time. -• Read external storage - Needed to upload photos in a new status +• Read/write external storage - Needed to upload photos in a new status and save photos when prompted +• Vibrate - Needed to vibrate phone for notifications; this can be toggled in the settings +• Billing - For purchasing pro and unlocking all of Frost's features • That's it! No privacy intrusion and no extra demands. Permissions NOT used and why: diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml index cf56eaeb9ca..cb1f5f64fa9 100644 --- a/app/src/main/res/xml/changelog.xml +++ b/app/src/main/res/xml/changelog.xml @@ -9,16 +9,18 @@ --> - - - - - + + + + + + +