From 1dd7be9174f1740aa1cae29f6d62d6f83f5917ba Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 22 Nov 2021 22:24:17 -0800 Subject: Migrate refresh channel to flow --- .../pitchedapps/frost/web/FrostChromeClients.kt | 5 +- .../kotlin/com/pitchedapps/frost/web/FrostJSI.kt | 8 +- .../kotlin/com/pitchedapps/frost/web/FrostWeb.kt | 84 +++++++++ .../pitchedapps/frost/web/FrostWebViewClients.kt | 14 +- .../pitchedapps/frost/web/FrostWebViewClients2.kt | 196 +++++++++++++++++++++ 5 files changed, 295 insertions(+), 12 deletions(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/FrostWeb.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients2.kt (limited to 'app/src/main/kotlin/com/pitchedapps/frost/web') 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 = web.parent.refreshChannel +// private val refresh: SendChannel = web.parent.refreshChannel + private val refreshEmit = web.parent.refreshEmit private val progress: SendChannel = web.parent.progressChannel private val title: SendChannel = 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 ) { private val mainActivity: MainActivity? = activity as? MainActivity private val webActivity: WebOverlayActivityBase? = activity as? WebOverlayActivityBase private val header: SendChannel? = mainActivity?.headerBadgeChannel - private val refresh: SendChannel = web.parent.refreshChannel private val cookies: List = 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 . + */ +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) -> Unit + +@Module +@InstallIn(FrostWebComponent::class) +object FrostWebFlowModule { + @Provides + @FrostWebScoped + @FrostRefresh + fun refreshFlow(parent: FrostContentParent): SharedFlow = parent.refreshFlow + + @Provides + @FrostWebScoped + @FrostRefresh + fun refreshEmit(parent: FrostContentParent): FrostEmitter = 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 = web.parent.refreshChannel +// protected val refresh: SendChannel = 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 . + */ +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 +) : 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 -- cgit v1.2.3 From 779ec08188f4bda736b3e0f2940570f1f7eb49e1 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 22 Nov 2021 22:53:00 -0800 Subject: Migrate progress channel to flow --- .../pitchedapps/frost/contracts/FrostContentContract.kt | 4 +++- .../com/pitchedapps/frost/views/FrostContentView.kt | 15 ++++++++++----- .../com/pitchedapps/frost/views/FrostRecyclerView.kt | 4 ++-- .../com/pitchedapps/frost/web/FrostChromeClients.kt | 6 +++--- 4 files changed, 18 insertions(+), 11 deletions(-) (limited to 'app/src/main/kotlin/com/pitchedapps/frost/web') 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 1d429138..8ebf7af7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt @@ -65,7 +65,9 @@ interface FrostContentParent : DynamicUiContract { /** * Observable to get data on refresh progress, with range [0, 100] */ - val progressChannel: BroadcastChannel + val progressFlow: SharedFlow + + val progressEmit: FrostEmitter /** * Observable to get new title data (unique values only) 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 1891a786..3ec80f36 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt @@ -22,7 +22,6 @@ import android.util.AttributeSet import android.view.View import android.widget.FrameLayout import android.widget.ProgressBar -import ca.allanwang.kau.utils.ContextHelper import ca.allanwang.kau.utils.bindView import ca.allanwang.kau.utils.circularReveal import ca.allanwang.kau.utils.fadeIn @@ -39,7 +38,6 @@ import com.pitchedapps.frost.contracts.FrostContentParent import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.WEB_LOAD_DELAY 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 @@ -51,6 +49,7 @@ import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.collect @@ -146,7 +145,13 @@ abstract class FrostContentViewBase( override val refreshEmit: FrostEmitter = FrostEmitter { refreshMutableFlow.tryEmit(it) } - override val progressChannel: BroadcastChannel = ConflatedBroadcastChannel() + private val progressMutableFlow = MutableStateFlow(0) + + override val progressFlow: SharedFlow = progressMutableFlow.asSharedFlow() + + override val progressEmit: FrostEmitter = + FrostEmitter { progressMutableFlow.tryEmit(it) } + override val titleChannel: BroadcastChannel = ConflatedBroadcastChannel() override lateinit var scope: CoroutineScope @@ -200,13 +205,13 @@ abstract class FrostContentViewBase( refresh.isRefreshing = r }.launchIn(scope) - progressChannel.subscribeDuringJob(scope, ContextHelper.coroutineContext) { p -> + progressFlow.onEach { p -> progress.invisibleIf(p == 100) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) progress.setProgress(p, true) else progress.progress = p - } + }.launchIn(scope) } override fun reloadTheme() { 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 a2b71572..9e21ede8 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt @@ -80,8 +80,8 @@ class FrostRecyclerView @JvmOverloads constructor( if (prefs.animate) fadeOut(onFinish = onReloadClear) scope.launch { parent.refreshEmit(true) - recyclerContract.reload { parent.progressChannel.offer(it) } - parent.progressChannel.offer(100) + recyclerContract.reload { parent.progressEmit(it) } + parent.progressEmit(100) parent.refreshEmit(false) if (prefs.animate) circularReveal() } 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 372a7bad..9f2437b0 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt @@ -53,7 +53,7 @@ class FrostChromeClient( // private val refresh: SendChannel = web.parent.refreshChannel private val refreshEmit = web.parent.refreshEmit - private val progress: SendChannel = web.parent.progressChannel + private val progressEmit = web.parent.progressEmit private val title: SendChannel = web.parent.titleChannel private val context = web.context!! @@ -74,7 +74,7 @@ class FrostChromeClient( override fun onProgressChanged(view: WebView, newProgress: Int) { super.onProgressChanged(view, newProgress) - progress.offer(newProgress) + progressEmit(newProgress) } override fun onShowFileChooser( @@ -89,7 +89,7 @@ class FrostChromeClient( private fun JsResult.frostCancel() { cancel() refreshEmit(false) - progress.offer(100) + progressEmit(100) } override fun onJsAlert( -- cgit v1.2.3 From bbfac885b89a79af2c85f5f0df7635770b49a07a Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 22 Nov 2021 22:57:55 -0800 Subject: Convert title channel to flow --- .../kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt | 8 +++----- .../com/pitchedapps/frost/contracts/FrostContentContract.kt | 6 +++--- .../main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt | 8 ++++++-- .../main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt | 5 ++--- 4 files changed, 14 insertions(+), 13 deletions(-) (limited to 'app/src/main/kotlin/com/pitchedapps/frost/web') 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 171583ed..ae8d442f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt @@ -27,7 +27,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout import ca.allanwang.kau.swipe.SwipeBackContract import ca.allanwang.kau.swipe.kauSwipeOnCreate import ca.allanwang.kau.swipe.kauSwipeOnDestroy -import ca.allanwang.kau.utils.ContextHelper import ca.allanwang.kau.utils.bindView import ca.allanwang.kau.utils.copyToClipboard import ca.allanwang.kau.utils.darken @@ -56,7 +55,6 @@ import com.pitchedapps.frost.facebook.USER_AGENT import com.pitchedapps.frost.facebook.USER_AGENT_DESKTOP_CONST import com.pitchedapps.frost.facebook.USER_AGENT_MOBILE_CONST import com.pitchedapps.frost.facebook.formattedFbUrl -import com.pitchedapps.frost.kotlin.subscribeDuringJob import com.pitchedapps.frost.utils.ARG_URL import com.pitchedapps.frost.utils.ARG_USER_ID import com.pitchedapps.frost.utils.BiometricUtils @@ -69,6 +67,8 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import okhttp3.HttpUrl.Companion.toHttpUrlOrNull @@ -215,9 +215,7 @@ abstract class WebOverlayActivityBase(private val userAgent: String = USER_AGENT content.bind(this) - content.titleChannel.subscribeDuringJob(this, ContextHelper.coroutineContext) { - toolbar.title = it - } + content.titleFlow.onEach { toolbar.title = it }.launchIn(this) with(web) { userAgentString = userAgent 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 8ebf7af7..d32cb873 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt @@ -21,7 +21,6 @@ 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 /** @@ -72,8 +71,9 @@ interface FrostContentParent : DynamicUiContract { /** * Observable to get new title data (unique values only) */ - // todo note that this should be like a behavior subject vs publish subject - val titleChannel: BroadcastChannel + val titleFlow: SharedFlow + + val titleEmit: FrostEmitter var baseUrl: String 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 3ec80f36..b76f6e39 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt @@ -80,7 +80,6 @@ class FrostContentRecycler @JvmOverloads constructor( override val layoutRes: Int = R.layout.view_content_base_recycler } -@UseExperimental(ExperimentalCoroutinesApi::class) abstract class FrostContentView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, @@ -152,7 +151,12 @@ abstract class FrostContentViewBase( override val progressEmit: FrostEmitter = FrostEmitter { progressMutableFlow.tryEmit(it) } - override val titleChannel: BroadcastChannel = ConflatedBroadcastChannel() + private val titleMutableFlow = MutableStateFlow("") + + override val titleFlow: SharedFlow = titleMutableFlow.asSharedFlow() + + override val titleEmit: FrostEmitter = + FrostEmitter { titleMutableFlow.tryEmit(it) } override lateinit var scope: CoroutineScope 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 9f2437b0..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. @@ -54,7 +53,7 @@ class FrostChromeClient( // private val refresh: SendChannel = web.parent.refreshChannel private val refreshEmit = web.parent.refreshEmit private val progressEmit = web.parent.progressEmit - private val title: SendChannel = web.parent.titleChannel + private val titleEmit = web.parent.titleEmit private val context = web.context!! override fun getDefaultVideoPoster(): Bitmap? = @@ -69,7 +68,7 @@ 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) { -- cgit v1.2.3 From dcd0db9282d92beacd35b3418d924ff3c607dead Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 22 Nov 2021 23:21:34 -0800 Subject: Convert header channel to flow --- .../pitchedapps/frost/activities/MainActivity.kt | 39 ++++++++++++++++------ .../frost/contracts/ActivityContract.kt | 7 +++- .../pitchedapps/frost/views/FrostContentView.kt | 2 -- .../kotlin/com/pitchedapps/frost/web/FrostJSI.kt | 5 ++- 4 files changed, 36 insertions(+), 17 deletions(-) (limited to 'app/src/main/kotlin/com/pitchedapps/frost/web') 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 755064cd..4b18088c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt @@ -18,23 +18,33 @@ package com.pitchedapps.frost.activities import android.os.Bundle import androidx.viewpager.widget.ViewPager -import ca.allanwang.kau.utils.withMainContext import com.google.android.material.tabs.TabLayout import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.parsers.BadgeParser -import com.pitchedapps.frost.kotlin.subscribeDuringJob import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.views.BadgedIcon +import com.pitchedapps.frost.web.FrostEmitter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.BroadcastChannel -import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach @UseExperimental(ExperimentalCoroutinesApi::class) class MainActivity : BaseMainActivity() { override val fragmentChannel = BroadcastChannel(10) - override val headerBadgeChannel = BroadcastChannel(Channel.CONFLATED) + + private val headerMutableFlow = MutableStateFlow("") + override val headerFlow: SharedFlow = headerMutableFlow.asSharedFlow() + override val headerEmit: FrostEmitter = FrostEmitter { headerMutableFlow.tryEmit(it) } override fun onNestedCreate(savedInstanceState: Bundle?) { with(contentBinding) { @@ -90,12 +100,18 @@ class MainActivity : BaseMainActivity() { (tab.customView as BadgedIcon).badgeText = null } }) - headerBadgeChannel.subscribeDuringJob(this@MainActivity, Dispatchers.IO) { html -> - val data = - BadgeParser.parseFromData(cookie = fbCookie.webCookie, text = html)?.data - ?: return@subscribeDuringJob - L.v { "Badges $data" } - withMainContext { + headerFlow + .filter { it.isNotBlank() } + .mapNotNull { html -> + BadgeParser.parseFromData( + cookie = fbCookie.webCookie, + text = html + )?.data + } + .distinctUntilChanged() + .flowOn(Dispatchers.IO) + .onEach { data -> + L.v { "Badges $data" } tabsForEachView { _, view -> when (view.iicon) { FbItem.FEED.icon -> view.badgeText = data.feed @@ -105,6 +121,7 @@ class MainActivity : BaseMainActivity() { } } } - } + .flowOn(Dispatchers.Main) + .launchIn(this@MainActivity) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt b/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt index 756b1f3d..83110417 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt @@ -18,13 +18,18 @@ package com.pitchedapps.frost.contracts import com.mikepenz.iconics.typeface.IIcon import com.pitchedapps.frost.fragments.BaseFragment +import com.pitchedapps.frost.web.FrostEmitter import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.BroadcastChannel +import kotlinx.coroutines.flow.SharedFlow @UseExperimental(ExperimentalCoroutinesApi::class) interface MainActivityContract : MainFabContract { val fragmentChannel: BroadcastChannel - val headerBadgeChannel: BroadcastChannel + + val headerFlow: SharedFlow + val headerEmit: FrostEmitter + fun setTitle(res: Int) fun setTitle(text: CharSequence) 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 b76f6e39..75d1ffe4 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt @@ -44,9 +44,7 @@ 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.MutableStateFlow 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 f43f3b81..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,7 +32,6 @@ 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 @@ -50,7 +49,7 @@ class FrostJSI @Inject internal constructor( private val mainActivity: MainActivity? = activity as? MainActivity private val webActivity: WebOverlayActivityBase? = activity as? WebOverlayActivityBase - private val header: SendChannel? = mainActivity?.headerBadgeChannel + private val headerEmit: FrostEmitter? = mainActivity?.headerEmit private val cookies: List = activity.cookies() /** @@ -159,7 +158,7 @@ class FrostJSI @Inject internal constructor( @JavascriptInterface fun handleHeader(html: String?) { html ?: return - header?.offer(html) + headerEmit?.invoke(html) } @JavascriptInterface -- cgit v1.2.3 From 0c97abd838ee834ef17d1b2e746fa26ea9663bd5 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 23 Nov 2021 11:50:06 -0800 Subject: Create extension for converting emitters --- .../kotlin/com/pitchedapps/frost/activities/MainActivity.kt | 8 ++++---- .../kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt | 1 - .../kotlin/com/pitchedapps/frost/fragments/BaseFragment.kt | 1 - .../kotlin/com/pitchedapps/frost/views/FrostContentView.kt | 10 ++++------ app/src/main/kotlin/com/pitchedapps/frost/web/FrostWeb.kt | 3 +++ 5 files changed, 11 insertions(+), 12 deletions(-) (limited to 'app/src/main/kotlin/com/pitchedapps/frost/web') 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 97067b21..2e44e5f9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt @@ -24,9 +24,9 @@ import com.pitchedapps.frost.facebook.parsers.BadgeParser import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.views.BadgedIcon import com.pitchedapps.frost.web.FrostEmitter +import com.pitchedapps.frost.web.asFrostEmitter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.BroadcastChannel import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -41,14 +41,14 @@ import kotlinx.coroutines.flow.onEach @UseExperimental(ExperimentalCoroutinesApi::class) class MainActivity : BaseMainActivity() { - + private val fragmentMutableFlow = MutableSharedFlow(extraBufferCapacity = 10, onBufferOverflow = BufferOverflow.DROP_OLDEST) override val fragmentFlow: SharedFlow = fragmentMutableFlow.asSharedFlow() - override val fragmentEmit: FrostEmitter = FrostEmitter { fragmentMutableFlow.tryEmit(it) } + override val fragmentEmit: FrostEmitter = fragmentMutableFlow.asFrostEmitter() private val headerMutableFlow = MutableStateFlow("") override val headerFlow: SharedFlow = headerMutableFlow.asSharedFlow() - override val headerEmit: FrostEmitter = FrostEmitter { headerMutableFlow.tryEmit(it) } + override val headerEmit: FrostEmitter = headerMutableFlow.asFrostEmitter() override fun onNestedCreate(savedInstanceState: Bundle?) { with(contentBinding) { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt b/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt index 3c5d7412..721282aa 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt @@ -20,7 +20,6 @@ import com.mikepenz.iconics.typeface.IIcon import com.pitchedapps.frost.fragments.BaseFragment import com.pitchedapps.frost.web.FrostEmitter import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.BroadcastChannel import kotlinx.coroutines.flow.SharedFlow @UseExperimental(ExperimentalCoroutinesApi::class) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/BaseFragment.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/BaseFragment.kt index 5c97de65..e10fd141 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/BaseFragment.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/BaseFragment.kt @@ -47,7 +47,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive 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 75d1ffe4..f9d04ad1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt @@ -41,6 +41,7 @@ import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.web.FrostEmitter +import com.pitchedapps.frost.web.asFrostEmitter import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -139,22 +140,19 @@ abstract class FrostContentViewBase( override val refreshFlow: SharedFlow = refreshMutableFlow.asSharedFlow() - override val refreshEmit: FrostEmitter = - FrostEmitter { refreshMutableFlow.tryEmit(it) } + override val refreshEmit: FrostEmitter = refreshMutableFlow.asFrostEmitter() private val progressMutableFlow = MutableStateFlow(0) override val progressFlow: SharedFlow = progressMutableFlow.asSharedFlow() - override val progressEmit: FrostEmitter = - FrostEmitter { progressMutableFlow.tryEmit(it) } + override val progressEmit: FrostEmitter = progressMutableFlow.asFrostEmitter() private val titleMutableFlow = MutableStateFlow("") override val titleFlow: SharedFlow = titleMutableFlow.asSharedFlow() - override val titleEmit: FrostEmitter = - FrostEmitter { titleMutableFlow.tryEmit(it) } + override val titleEmit: FrostEmitter = titleMutableFlow.asFrostEmitter() override lateinit var scope: CoroutineScope diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWeb.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWeb.kt index 30845a79..ba05a2c4 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWeb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWeb.kt @@ -25,6 +25,7 @@ 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 @@ -62,6 +63,8 @@ interface FrostWebEntryPoint { fun interface FrostEmitter : (T) -> Unit +fun MutableSharedFlow.asFrostEmitter(): FrostEmitter = FrostEmitter { tryEmit(it) } + @Module @InstallIn(FrostWebComponent::class) object FrostWebFlowModule { -- cgit v1.2.3