aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2021-11-22 22:24:17 -0800
committerAllan Wang <me@allanwang.ca>2021-11-22 22:24:17 -0800
commit1dd7be9174f1740aa1cae29f6d62d6f83f5917ba (patch)
tree5734afee77ddd8ea498df7065a1ec3de28a387f7
parenteb2e0d07f278eb2079666ffabcbee007173c17af (diff)
downloadfrost-1dd7be9174f1740aa1cae29f6d62d6f83f5917ba.tar.gz
frost-1dd7be9174f1740aa1cae29f6d62d6f83f5917ba.tar.bz2
frost-1dd7be9174f1740aa1cae29f6d62d6f83f5917ba.zip
Migrate refresh channel to flow
-rw-r--r--app/build.gradle2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt12
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt79
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt7
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt56
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt5
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt8
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWeb.kt84
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt14
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients2.kt196
11 files changed, 380 insertions, 89 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 2655f695..7e064152 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -270,6 +270,8 @@ dependencies {
implementation kau.Dependencies.kau('searchview', KAU)
implementation kau.Dependencies.coreKtx
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
+
implementation kau.Dependencies.swipeRefreshLayout
implementation "androidx.biometric:biometric:${Versions.andxBiometric}"
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
index ef7579a8..171583ed 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
@@ -68,6 +68,8 @@ import com.pitchedapps.frost.views.FrostWebView
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import javax.inject.Inject
@@ -97,10 +99,8 @@ class FrostWebActivity : WebOverlayActivityBase() {
* We will subscribe to the load cycle once,
* and pop a dialog giving the user the option to copy the shared text
*/
- val refreshReceiver = content.refreshChannel.openSubscription()
content.scope.launch(Dispatchers.IO) {
- refreshReceiver.receive()
- refreshReceiver.cancel()
+ content.refreshFlow.take(1).collect()
withMainContext {
materialDialog {
title(R.string.invalid_share_url)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt
index b8d0d86f..1d429138 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt
@@ -18,9 +18,11 @@ package com.pitchedapps.frost.contracts
import android.view.View
import com.pitchedapps.frost.facebook.FbItem
+import com.pitchedapps.frost.web.FrostEmitter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BroadcastChannel
+import kotlinx.coroutines.flow.SharedFlow
/**
* Created by Allan Wang on 20/12/17.
@@ -56,7 +58,9 @@ interface FrostContentParent : DynamicUiContract {
/**
* Observable to get data on whether view is refreshing or not
*/
- val refreshChannel: BroadcastChannel<Boolean>
+ val refreshFlow: SharedFlow<Boolean>
+
+ val refreshEmit: FrostEmitter<Boolean>
/**
* Observable to get data on refresh progress, with range [0, 100]
@@ -124,17 +128,15 @@ interface FrostContentCore : DynamicUiContract {
* Reference to parent
* Bound through calling [FrostContentParent.bind]
*/
- var parent: FrostContentParent
+ val parent: FrostContentParent
/**
* Initializes view through given [container]
*
* The content may be free to extract other data from
* the container if necessary
- *
- * [parent] must be bounded before calling this!
*/
- fun bind(container: FrostContentContainer): View
+ fun bind(parent: FrostContentParent, container: FrostContentContainer): View
/**
* Call to reload wrapped data
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
index c30ee199..1891a786 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
@@ -42,12 +42,23 @@ import com.pitchedapps.frost.injectors.ThemeProvider
import com.pitchedapps.frost.kotlin.subscribeDuringJob
import com.pitchedapps.frost.prefs.Prefs
import com.pitchedapps.frost.utils.L
+import com.pitchedapps.frost.web.FrostEmitter
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BroadcastChannel
+import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.runningFold
+import kotlinx.coroutines.flow.transformWhile
import javax.inject.Inject
class FrostContentWeb @JvmOverloads constructor(
@@ -119,11 +130,22 @@ abstract class FrostContentViewBase(
private val refresh: SwipeRefreshLayout by bindView(R.id.content_refresh)
private val progress: ProgressBar by bindView(R.id.content_progress)
+ private val coreView: View by bindView(R.id.content_core)
+
/**
* While this can be conflated, there exist situations where we wish to watch refresh cycles.
* Here, we'd need to make sure we don't skip events
*/
- override val refreshChannel: BroadcastChannel<Boolean> = BroadcastChannel(10)
+ private val refreshMutableFlow = MutableSharedFlow<Boolean>(
+ extraBufferCapacity = 10,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
+
+ override val refreshFlow: SharedFlow<Boolean> = refreshMutableFlow.asSharedFlow()
+
+ override val refreshEmit: FrostEmitter<Boolean> =
+ FrostEmitter { refreshMutableFlow.tryEmit(it) }
+
override val progressChannel: BroadcastChannel<Int> = ConflatedBroadcastChannel()
override val titleChannel: BroadcastChannel<String> = ConflatedBroadcastChannel()
@@ -160,7 +182,6 @@ abstract class FrostContentViewBase(
*/
protected fun init() {
inflate(context, layoutRes, this)
- core.parent = this
reloadThemeSelf()
}
@@ -169,15 +190,15 @@ abstract class FrostContentViewBase(
baseEnum = container.baseEnum
init()
scope = container
- core.bind(container)
+ core.bind(this, container)
refresh.setOnRefreshListener {
core.reload(true)
}
- refreshChannel.subscribeDuringJob(scope, ContextHelper.coroutineContext) { r ->
+ refreshFlow.distinctUntilChanged().onEach { r ->
L.v { "Refreshing $r" }
refresh.isRefreshing = r
- }
+ }.launchIn(scope)
progressChannel.subscribeDuringJob(scope, ContextHelper.coroutineContext) { p ->
progress.invisibleIf(p == 100)
@@ -220,33 +241,37 @@ abstract class FrostContentViewBase(
* The cycle only starts on the first load since there may have been another process when this is registered
*/
override fun registerTransition(urlChanged: Boolean, animate: Boolean): Boolean {
- if (!urlChanged && refreshReceiver != null) {
+ if (!urlChanged && transitionStart != -1L) {
L.v { "Consuming url load" }
return false // still in progress; do not bother with load
}
+ coreView.transition(animate)
+ return true
+ }
+
+ private fun View.transition(animate: Boolean) {
L.v { "Registered transition" }
- with(core) {
- refreshReceiver = refreshChannel.openSubscription().also { receiver ->
- scope.launchMain {
- var loading = false
- for (r in receiver) {
- if (r) {
- loading = true
- transitionStart = System.currentTimeMillis()
- clearAnimation()
- if (isVisible)
- fadeOut(duration = 200L)
- } else if (loading) {
- if (animate && prefs.animate) circularReveal(offset = WEB_LOAD_DELAY)
- else fadeIn(duration = 200L, offset = WEB_LOAD_DELAY)
- L.v { "Transition loaded in ${System.currentTimeMillis() - transitionStart} ms" }
- receiver.cancel()
- refreshReceiver = null
- }
+ transitionStart = 0L // Marker for pending transition
+ scope.launchMain {
+ refreshFlow.distinctUntilChanged()
+ // Pseudo windowed mode
+ .runningFold(false to false) { (_, prev), curr -> prev to curr }
+ // Take until prev was loading and current is not loading
+ // Unlike takeWhile, we include the last state (first non matching)
+ .transformWhile { emit(it); it != (true to false) }
+ .onEach { (prev, curr) ->
+ if (curr) {
+ transitionStart = System.currentTimeMillis()
+ clearAnimation()
+ if (isVisible)
+ fadeOut(duration = 200L)
+ } else if (prev) { // prev && !curr
+ if (animate && prefs.animate) circularReveal(offset = WEB_LOAD_DELAY)
+ else fadeIn(duration = 200L, offset = WEB_LOAD_DELAY)
+ L.v { "Transition loaded in ${System.currentTimeMillis() - transitionStart} ms" }
}
- }
- }
+ }.collect()
+ transitionStart = -1L
}
- return true
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt
index 2ab00916..a2b71572 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt
@@ -61,7 +61,8 @@ class FrostRecyclerView @JvmOverloads constructor(
layoutManager = LinearLayoutManager(context)
}
- override fun bind(container: FrostContentContainer): View {
+ override fun bind(parent: FrostContentParent, container: FrostContentContainer): View {
+ this.parent = parent
if (container !is RecyclerContentContract)
throw IllegalStateException("FrostRecyclerView must bind to a container that is a RecyclerContentContract")
this.recyclerContract = container
@@ -78,10 +79,10 @@ class FrostRecyclerView @JvmOverloads constructor(
override fun reloadBase(animate: Boolean) {
if (prefs.animate) fadeOut(onFinish = onReloadClear)
scope.launch {
- parent.refreshChannel.offer(true)
+ parent.refreshEmit(true)
recyclerContract.reload { parent.progressChannel.offer(it) }
parent.progressChannel.offer(100)
- parent.refreshChannel.offer(false)
+ parent.refreshEmit(false)
if (prefs.animate) circularReveal()
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
index f384d134..140b4901 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
@@ -35,25 +35,23 @@ import com.pitchedapps.frost.db.CookieDao
import com.pitchedapps.frost.db.currentCookie
import com.pitchedapps.frost.facebook.FB_HOME_URL
import com.pitchedapps.frost.facebook.FbCookie
+import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.USER_AGENT
-import com.pitchedapps.frost.fragments.WebFragment
import com.pitchedapps.frost.injectors.ThemeProvider
import com.pitchedapps.frost.prefs.Prefs
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.frostDownload
import com.pitchedapps.frost.web.FrostChromeClient
-import com.pitchedapps.frost.web.FrostJSI
+import com.pitchedapps.frost.web.FrostWebClientEntryPoint
+import com.pitchedapps.frost.web.FrostWebComponentBuilder
+import com.pitchedapps.frost.web.FrostWebEntryPoint
import com.pitchedapps.frost.web.FrostWebViewClient
+import com.pitchedapps.frost.web.FrostWebViewClientMenu
+import com.pitchedapps.frost.web.FrostWebViewClientMessenger
import com.pitchedapps.frost.web.NestedWebView
-import dagger.BindsInstance
-import dagger.hilt.DefineComponent
-import dagger.hilt.EntryPoint
import dagger.hilt.EntryPoints
-import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
-import dagger.hilt.android.components.ViewComponent
import javax.inject.Inject
-import javax.inject.Scope
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -103,9 +101,11 @@ class FrostWebView @JvmOverloads constructor(
get() = url ?: ""
@SuppressLint("SetJavaScriptEnabled")
- override fun bind(container: FrostContentContainer): View {
- val component = frostWebComponentBuilder.frostWebView(this).build()
- val entryPoint = EntryPoints.get(component, FrostWebEntryPoint::class.java)
+ override fun bind(parent: FrostContentParent, container: FrostContentContainer): View {
+ this.parent = parent
+ val component = frostWebComponentBuilder.frostParent(parent).frostWebView(this).build()
+ val webEntryPoint = EntryPoints.get(component, FrostWebEntryPoint::class.java)
+ val clientEntryPoint = EntryPoints.get(component, FrostWebClientEntryPoint::class.java)
userAgentString = USER_AGENT
with(settings) {
javaScriptEnabled = true
@@ -116,10 +116,14 @@ class FrostWebView @JvmOverloads constructor(
}
setLayerType(LAYER_TYPE_HARDWARE, null)
// attempt to get custom client; otherwise fallback to original
- frostWebClient = (container as? WebFragment)?.client(this) ?: FrostWebViewClient(this)
+ frostWebClient = when (parent.baseEnum) {
+ FbItem.MESSENGER -> FrostWebViewClientMessenger(this)
+ FbItem.MENU -> FrostWebViewClientMenu(this)
+ else -> clientEntryPoint.webClient()
+ }
webViewClient = frostWebClient
webChromeClient = FrostChromeClient(this, themeProvider, webFileChooser)
- addJavascriptInterface(entryPoint.frostJsi(), "Frost")
+ addJavascriptInterface(webEntryPoint.frostJsi(), "Frost")
setBackgroundColor(Color.TRANSPARENT)
setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
context.ctxCoroutine.launchMain {
@@ -251,29 +255,3 @@ class FrostWebView @JvmOverloads constructor(
super.destroy()
}
}
-
-@Scope
-@Retention(AnnotationRetention.BINARY)
-@Target(
- AnnotationTarget.FUNCTION,
- AnnotationTarget.TYPE,
- AnnotationTarget.CLASS
-)
-annotation class FrostWebScoped
-
-@FrostWebScoped
-@DefineComponent(parent = ViewComponent::class)
-interface FrostWebComponent
-
-@DefineComponent.Builder
-interface FrostWebComponentBuilder {
- fun frostWebView(@BindsInstance web: FrostWebView): FrostWebComponentBuilder
- fun build(): FrostWebComponent
-}
-
-@EntryPoint
-@InstallIn(FrostWebComponent::class)
-interface FrostWebEntryPoint {
- @FrostWebScoped
- fun frostJsi(): FrostJSI
-}
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 61a76e70..372a7bad 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt
@@ -51,7 +51,8 @@ class FrostChromeClient(
private val webFileChooser: WebFileChooser,
) : WebChromeClient() {
- private val refresh: SendChannel<Boolean> = web.parent.refreshChannel
+// private val refresh: SendChannel<Boolean> = web.parent.refreshChannel
+ private val refreshEmit = web.parent.refreshEmit
private val progress: SendChannel<Int> = web.parent.progressChannel
private val title: SendChannel<String> = web.parent.titleChannel
private val context = web.context!!
@@ -87,7 +88,7 @@ class FrostChromeClient(
private fun JsResult.frostCancel() {
cancel()
- refresh.offer(false)
+ refreshEmit(false)
progress.offer(100)
}
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 3ead78f4..f43f3b81 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
@@ -39,17 +39,18 @@ import javax.inject.Inject
/**
* Created by Allan Wang on 2017-06-01.
*/
+@FrostWebScoped
class FrostJSI @Inject internal constructor(
val web: FrostWebView,
private val activity: Activity,
private val fbCookie: FbCookie,
- private val prefs: Prefs
+ private val prefs: Prefs,
+ @FrostRefresh private val refreshEmit: FrostEmitter<Boolean>
) {
private val mainActivity: MainActivity? = activity as? MainActivity
private val webActivity: WebOverlayActivityBase? = activity as? WebOverlayActivityBase
private val header: SendChannel<String>? = mainActivity?.headerBadgeChannel
- private val refresh: SendChannel<Boolean> = web.parent.refreshChannel
private val cookies: List<CookieEntity> = activity.cookies()
/**
@@ -144,7 +145,8 @@ class FrostJSI @Inject internal constructor(
@JavascriptInterface
fun isReady() {
if (web.frostWebClient !is FrostWebViewClientMenu) {
- refresh.offer(false)
+ L.v { "JSI is ready" }
+ refreshEmit(false)
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWeb.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWeb.kt
new file mode 100644
index 00000000..30845a79
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWeb.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2021 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.web
+
+import com.pitchedapps.frost.contracts.FrostContentParent
+import com.pitchedapps.frost.views.FrostWebView
+import dagger.BindsInstance
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.DefineComponent
+import dagger.hilt.EntryPoint
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ViewComponent
+import kotlinx.coroutines.flow.SharedFlow
+import javax.inject.Qualifier
+import javax.inject.Scope
+
+/**
+ * Defines a new scope for Frost web related content.
+ *
+ * This is a subset of [dagger.hilt.android.scopes.ViewScoped]
+ */
+@Scope
+@Retention(AnnotationRetention.BINARY)
+@Target(
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.TYPE,
+ AnnotationTarget.CLASS
+)
+annotation class FrostWebScoped
+
+@FrostWebScoped
+@DefineComponent(parent = ViewComponent::class)
+interface FrostWebComponent
+
+@DefineComponent.Builder
+interface FrostWebComponentBuilder {
+ fun frostParent(@BindsInstance parent: FrostContentParent): FrostWebComponentBuilder
+ fun frostWebView(@BindsInstance web: FrostWebView): FrostWebComponentBuilder
+ fun build(): FrostWebComponent
+}
+
+@EntryPoint
+@InstallIn(FrostWebComponent::class)
+interface FrostWebEntryPoint {
+ fun frostJsi(): FrostJSI
+}
+
+fun interface FrostEmitter<T> : (T) -> Unit
+
+@Module
+@InstallIn(FrostWebComponent::class)
+object FrostWebFlowModule {
+ @Provides
+ @FrostWebScoped
+ @FrostRefresh
+ fun refreshFlow(parent: FrostContentParent): SharedFlow<Boolean> = parent.refreshFlow
+
+ @Provides
+ @FrostWebScoped
+ @FrostRefresh
+ fun refreshEmit(parent: FrostContentParent): FrostEmitter<Boolean> = parent.refreshEmit
+}
+
+/**
+ * Observable to get data on whether view is refreshing or not
+ */
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class FrostRefresh
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 3b332199..ba19989d 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
@@ -53,7 +53,6 @@ import com.pitchedapps.frost.utils.isMessengerUrl
import com.pitchedapps.frost.utils.launchImageActivity
import com.pitchedapps.frost.utils.startActivityForUri
import com.pitchedapps.frost.views.FrostWebView
-import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.launch
/**
@@ -83,7 +82,7 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() {
protected val fbCookie: FbCookie get() = web.fbCookie
protected val prefs: Prefs get() = web.prefs
protected val themeProvider: ThemeProvider get() = web.themeProvider
- protected val refresh: SendChannel<Boolean> = web.parent.refreshChannel
+// protected val refresh: SendChannel<Boolean> = web.parent.refreshChannel
protected val isMain = web.parent.baseEnum != null
/**
@@ -156,7 +155,7 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() {
super.onPageStarted(view, url, favicon)
if (url == null) return
v { "loading $url ${web.settings.userAgentString}" }
- refresh.offer(true)
+// refresh.offer(true)
}
private fun injectBackgroundColor() {
@@ -182,7 +181,7 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() {
view.messengerJsInject()
}
else -> {
- refresh.offer(false)
+// refresh.offer(false)
}
}
}
@@ -191,7 +190,7 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() {
url ?: return
v { "finished $url" }
if (!url.isFacebookUrl && !url.isMessengerUrl) {
- refresh.offer(false)
+// refresh.offer(false)
return
}
onPageFinishedActions(url)
@@ -204,9 +203,10 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() {
injectAndFinish()
}
- internal fun injectAndFinish() {
+ // Temp open
+ internal open fun injectAndFinish() {
v { "page finished reveal" }
- refresh.offer(false)
+// refresh.offer(false)
injectBackgroundColor()
when {
web.url.isFacebookUrl -> {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients2.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients2.kt
new file mode 100644
index 00000000..008b1197
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients2.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2018 Allan Wang
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.pitchedapps.frost.web
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.webkit.WebResourceRequest
+import android.webkit.WebView
+import ca.allanwang.kau.utils.withAlpha
+import com.pitchedapps.frost.enums.ThemeCategory
+import com.pitchedapps.frost.injectors.JsActions
+import com.pitchedapps.frost.injectors.JsAssets
+import com.pitchedapps.frost.injectors.jsInject
+import com.pitchedapps.frost.utils.L
+import com.pitchedapps.frost.utils.isFacebookUrl
+import com.pitchedapps.frost.utils.isMessengerUrl
+import com.pitchedapps.frost.utils.launchImageActivity
+import com.pitchedapps.frost.views.FrostWebView
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.EntryPoint
+import dagger.hilt.InstallIn
+import javax.inject.Inject
+import javax.inject.Qualifier
+
+/**
+ * Created by Allan Wang on 2017-05-31.
+ *
+ * Collection of webview clients
+ */
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class FrostWebClient
+
+@EntryPoint
+@InstallIn(FrostWebComponent::class)
+interface FrostWebClientEntryPoint {
+
+ @FrostWebScoped
+ @FrostWebClient
+ fun webClient(): FrostWebViewClient
+}
+
+@Module
+@InstallIn(FrostWebComponent::class)
+interface FrostWebViewClientModule {
+ @Binds
+ @FrostWebClient
+ fun webClient(binds: FrostWebViewClient2): FrostWebViewClient
+}
+
+/**
+ * The default webview client
+ */
+open class FrostWebViewClient2 @Inject constructor(
+ web: FrostWebView,
+ @FrostRefresh private val refreshEmit: FrostEmitter<Boolean>
+) : FrostWebViewClient(web) {
+
+ init {
+ L.i { "Refresh web client 2" }
+ }
+
+ override fun doUpdateVisitedHistory(view: WebView, url: String?, isReload: Boolean) {
+ super.doUpdateVisitedHistory(view, url, isReload)
+ urlSupportsRefresh = urlSupportsRefresh(url)
+ web.parent.swipeAllowedByPage = urlSupportsRefresh
+ view.jsInject(
+ JsAssets.AUTO_RESIZE_TEXTAREA.maybe(prefs.autoExpandTextBox),
+ prefs = prefs
+ )
+ v { "History $url; refresh $urlSupportsRefresh" }
+ }
+
+ private fun urlSupportsRefresh(url: String?): Boolean {
+ if (url == null) return false
+ if (url.isMessengerUrl) return false
+ if (!url.isFacebookUrl) return true
+ if (url.contains("soft=composer")) return false
+ if (url.contains("sharer.php") || url.contains("sharer-dialog.php")) return false
+ return true
+ }
+
+ private fun WebView.facebookJsInject() {
+ jsInject(*facebookJsInjectors.toTypedArray(), prefs = prefs)
+ }
+
+ private fun WebView.messengerJsInject() {
+ jsInject(
+ themeProvider.injector(ThemeCategory.MESSENGER),
+ prefs = prefs
+ )
+ }
+
+ override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
+ super.onPageStarted(view, url, favicon)
+ if (url == null) return
+ v { "loading $url ${web.settings.userAgentString}" }
+ refreshEmit(true)
+ }
+
+ private fun injectBackgroundColor() {
+ web.setBackgroundColor(
+ when {
+ isMain -> Color.TRANSPARENT
+ web.url.isFacebookUrl -> themeProvider.bgColor.withAlpha(255)
+ else -> Color.WHITE
+ }
+ )
+ }
+
+ override fun onPageCommitVisible(view: WebView, url: String?) {
+ super.onPageCommitVisible(view, url)
+ injectBackgroundColor()
+ when {
+ url.isFacebookUrl -> {
+ v { "FB Page commit visible" }
+ view.facebookJsInject()
+ }
+ url.isMessengerUrl -> {
+ v { "Messenger Page commit visible" }
+ view.messengerJsInject()
+ }
+ else -> {
+ refreshEmit(false)
+ }
+ }
+ }
+
+ override fun onPageFinished(view: WebView, url: String?) {
+ url ?: return
+ v { "finished $url" }
+ if (!url.isFacebookUrl && !url.isMessengerUrl) {
+ refreshEmit(false)
+ return
+ }
+ onPageFinishedActions(url)
+ }
+
+ internal override fun injectAndFinish() {
+ v { "page finished reveal" }
+ refreshEmit(false)
+ injectBackgroundColor()
+ when {
+ web.url.isFacebookUrl -> {
+ web.jsInject(
+ JsActions.LOGIN_CHECK,
+ JsAssets.TEXTAREA_LISTENER,
+ JsAssets.HEADER_BADGES.maybe(isMain),
+ prefs = prefs
+ )
+ web.facebookJsInject()
+ }
+ web.url.isMessengerUrl -> {
+ web.messengerJsInject()
+ }
+ }
+ }
+
+ /**
+ * Helper to format the request and launch it
+ * returns true to override the url
+ * returns false if we are already in an overlaying activity
+ */
+ private fun launchRequest(request: WebResourceRequest): Boolean {
+ v { "Launching url: ${request.url}" }
+ return web.requestWebOverlay(request.url.toString())
+ }
+
+ private fun launchImage(url: String, text: String? = null, cookie: String? = null): Boolean {
+ v { "Launching image: $url" }
+ web.context.launchImageActivity(url, text, cookie)
+ if (web.canGoBack()) web.goBack()
+ return true
+ }
+}
+
+private const val EMIT_THEME = 0b1
+private const val EMIT_ID = 0b10
+private const val EMIT_COMPLETE = EMIT_THEME or EMIT_ID
+private const val EMIT_FINISH = 0