diff options
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/web')
5 files changed, 305 insertions, 21 deletions
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..90345aa2 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt @@ -34,7 +34,6 @@ import com.pitchedapps.frost.contracts.WebFileChooser import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.views.FrostWebView -import kotlinx.coroutines.channels.SendChannel /** * Created by Allan Wang on 2017-05-31. @@ -51,9 +50,10 @@ class FrostChromeClient( private val webFileChooser: WebFileChooser, ) : WebChromeClient() { - private val refresh: SendChannel<Boolean> = web.parent.refreshChannel - private val progress: SendChannel<Int> = web.parent.progressChannel - private val title: SendChannel<String> = web.parent.titleChannel +// private val refresh: SendChannel<Boolean> = web.parent.refreshChannel + private val refreshEmit = web.parent.refreshEmit + private val progressEmit = web.parent.progressEmit + private val titleEmit = web.parent.titleEmit private val context = web.context!! override fun getDefaultVideoPoster(): Bitmap? = @@ -68,12 +68,12 @@ class FrostChromeClient( override fun onReceivedTitle(view: WebView, title: String) { super.onReceivedTitle(view, title) if (title.startsWith("http")) return - this.title.offer(title) + titleEmit(title) } override fun onProgressChanged(view: WebView, newProgress: Int) { super.onProgressChanged(view, newProgress) - progress.offer(newProgress) + progressEmit(newProgress) } override fun onShowFileChooser( @@ -87,8 +87,8 @@ class FrostChromeClient( private fun JsResult.frostCancel() { cancel() - refresh.offer(false) - progress.offer(100) + refreshEmit(false) + progressEmit(100) } override fun onJsAlert( 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..4d92e8c2 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -32,24 +32,24 @@ import com.pitchedapps.frost.utils.isIndependent import com.pitchedapps.frost.utils.launchImageActivity import com.pitchedapps.frost.utils.showWebContextMenu import com.pitchedapps.frost.views.FrostWebView -import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.launch 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 headerEmit: FrostEmitter<String>? = mainActivity?.headerEmit private val cookies: List<CookieEntity> = activity.cookies() /** @@ -144,7 +144,8 @@ class FrostJSI @Inject internal constructor( @JavascriptInterface fun isReady() { if (web.frostWebClient !is FrostWebViewClientMenu) { - refresh.offer(false) + L.v { "JSI is ready" } + refreshEmit(false) } } @@ -157,7 +158,7 @@ class FrostJSI @Inject internal constructor( @JavascriptInterface fun handleHeader(html: String?) { html ?: return - header?.offer(html) + headerEmit?.invoke(html) } @JavascriptInterface 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..ba05a2c4 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWeb.kt @@ -0,0 +1,87 @@ +/* + * 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.MutableSharedFlow +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 + +fun <T> MutableSharedFlow<T>.asFrostEmitter(): FrostEmitter<T> = FrostEmitter { tryEmit(it) } + +@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 |