From f9e3a324e47a81a30aade003cf6f829d03c81414 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 27 Dec 2018 14:34:29 -0500 Subject: Convert remaining view observables --- .../frost/activities/WebOverlayActivity.kt | 29 ++++++---- .../frost/contracts/FrostContentContract.kt | 7 +-- .../frost/contracts/FrostObservables.kt | 46 ---------------- .../pitchedapps/frost/views/FrostContentView.kt | 62 +++++++++++----------- .../pitchedapps/frost/views/FrostRecyclerView.kt | 6 +-- .../pitchedapps/frost/web/FrostChromeClients.kt | 6 ++- .../kotlin/com/pitchedapps/frost/web/FrostJSI.kt | 5 +- .../pitchedapps/frost/web/FrostWebViewClients.kt | 11 ++-- .../test/kotlin/com/pitchedapps/frost/MiscTest.kt | 35 ------------ 9 files changed, 66 insertions(+), 141 deletions(-) delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostObservables.kt 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 bf04c524..816524ba 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt @@ -73,6 +73,9 @@ import com.pitchedapps.frost.views.FrostWebView import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import okhttp3.HttpUrl /** @@ -99,12 +102,15 @@ class FrostWebActivity : WebOverlayActivityBase(false) { * We will subscribe to the load cycle once, * and pop a dialog giving the user the option to copy the shared text */ - var disposable: Disposable? = null - disposable = content.refreshObservable.subscribe { - disposable?.dispose() - materialDialogThemed { - title(R.string.invalid_share_url) - content(R.string.invalid_share_url_desc) + val refreshReceiver = content.refreshChannel.openSubscription() + content.scope.launch(Dispatchers.IO) { + refreshReceiver.receive() + refreshReceiver.cancel() + withContext(Dispatchers.Main) { + materialDialogThemed { + title(R.string.invalid_share_url) + content(R.string.invalid_share_url_desc) + } } } } @@ -197,10 +203,13 @@ open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseAc content.bind(this) - content.titleObservable - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { toolbar.title = it } - .disposeOnDestroy() + val titleReceiver = content.titleChannel.openSubscription() + + launch { + for (t in titleReceiver) { + toolbar.title = t + } + } with(web) { if (forceBasicAgent) //todo check; the webview already adds it dynamically 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 a2c2b05a..0f8c49d3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt @@ -59,22 +59,17 @@ interface FrostContentParent : DynamicUiContract { /** * Observable to get data on whether view is refreshing or not */ - val refreshObservable: PublishSubject - val refreshChannel: BroadcastChannel /** * Observable to get data on refresh progress, with range [0, 100] */ - val progressObservable: PublishSubject - val progressChannel: BroadcastChannel /** * Observable to get new title data (unique values only) */ - val titleObservable: BehaviorSubject - + // todo note that this should be like a behavior subject vs publish subject val titleChannel: BroadcastChannel var baseUrl: String diff --git a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostObservables.kt b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostObservables.kt deleted file mode 100644 index b3b93b66..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostObservables.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.contracts - -import io.reactivex.subjects.BehaviorSubject -import io.reactivex.subjects.PublishSubject - -/** - * Created by Allan Wang on 2017-11-07. - */ -interface FrostObservables { - /** - * Observable to get data on whether view is refreshing or not - */ - var refreshObservable: PublishSubject - - /** - * Observable to get data on refresh progress, with range [0, 100] - */ - var progressObservable: PublishSubject - - /** - * Observable to get new title data (unique values only) - */ - var titleObservable: BehaviorSubject - - fun passObservablesTo(other: FrostObservables) { - other.refreshObservable = refreshObservable - other.progressObservable = progressObservable - other.titleObservable = titleObservable - } -} 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 72b81b37..38591d38 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt @@ -39,17 +39,14 @@ import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.WEB_LOAD_DELAY import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable -import io.reactivex.rxkotlin.addTo import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.PublishSubject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.BroadcastChannel -import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -89,13 +86,9 @@ abstract class FrostContentView @JvmOverloads constructor( override val core: FrostContentCore get() = coreView - override val progressObservable: PublishSubject = PublishSubject.create() - override val refreshObservable: PublishSubject = PublishSubject.create() - override val titleObservable: BehaviorSubject = BehaviorSubject.create() - - override val refreshChannel: BroadcastChannel = BroadcastChannel(Channel.UNLIMITED) - override val progressChannel: BroadcastChannel = BroadcastChannel(Channel.UNLIMITED) - override val titleChannel: BroadcastChannel = BroadcastChannel(Channel.UNLIMITED) + override val refreshChannel: BroadcastChannel = BroadcastChannel(100) + override val progressChannel: BroadcastChannel = BroadcastChannel(100) + override val titleChannel: BroadcastChannel = BroadcastChannel(100) override lateinit var scope: CoroutineScope @@ -133,9 +126,13 @@ abstract class FrostContentView @JvmOverloads constructor( reload(true) } } + // Begin subscription in the main thread + val refreshReceiver = refreshChannel.openSubscription() + val progressReceiver = progressChannel.openSubscription() + scope.launch(Dispatchers.Default) { launch { - for (r in refreshChannel.openSubscription()) { + for (r in refreshReceiver) { withContext(Dispatchers.Main) { refresh.isRefreshing = r refresh.isEnabled = true @@ -143,7 +140,7 @@ abstract class FrostContentView @JvmOverloads constructor( } } launch { - for (p in progressChannel.openSubscription()) { + for (p in progressReceiver) { withContext(Dispatchers.Main) { progress.invisibleIf(p == 100) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) @@ -184,6 +181,7 @@ abstract class FrostContentView @JvmOverloads constructor( private var dispose: Disposable? = null private var transitionStart: Long = -1 + private var refreshReceiver: ReceiveChannel? = null /** * Hook onto the refresh observable for one cycle @@ -191,32 +189,32 @@ abstract class FrostContentView @JvmOverloads constructor( * 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 && dispose != null) { + if (!urlChanged && refreshReceiver != null) { L.v { "Consuming url load" } return false // still in progress; do not bother with load } L.v { "Registered transition" } with(coreView) { - var loading = dispose != null - dispose?.dispose() - dispose = refreshObservable - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - if (it) { - loading = true - transitionStart = System.currentTimeMillis() - clearAnimation() - if (isVisible) - fadeOut(duration = 200L) - } else if (loading) { - loading = false - 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" } - dispose?.dispose() - dispose = null + refreshReceiver = refreshChannel.openSubscription().also { receiver -> + scope.launch(Dispatchers.Main) { + 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 + } } } + } } 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 fb20c3ba..2ba78c5e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt @@ -73,10 +73,10 @@ class FrostRecyclerView @JvmOverloads constructor( override fun reloadBase(animate: Boolean) { if (Prefs.animate) fadeOut(onFinish = onReloadClear) scope.launch { - parent.refreshChannel.send(true) + parent.refreshChannel.offer(true) val loaded = recyclerContract.reload { parent.progressChannel.offer(it) } - parent.progressChannel.send(100) - parent.refreshChannel.send(false) + parent.progressChannel.offer(100) + parent.refreshChannel.offer(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 1f8118da..f3538ec3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt @@ -31,6 +31,8 @@ import com.pitchedapps.frost.utils.frostSnackbar import com.pitchedapps.frost.views.FrostWebView import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.SendChannel /** * Created by Allan Wang on 2017-05-31. @@ -43,8 +45,8 @@ import io.reactivex.subjects.Subject */ class FrostChromeClient(web: FrostWebView) : WebChromeClient() { - private val progress = web.parent.progressChannel - private val title = web.parent.titleChannel + private val progress: SendChannel = web.parent.progressChannel + private val title: SendChannel = web.parent.titleChannel private var prevTitle: String? = null private val activity = (web.context as? ActivityContract) private val context = web.context!! 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 2afb28c9..3945a9a0 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -30,6 +30,7 @@ import com.pitchedapps.frost.utils.launchImageActivity import com.pitchedapps.frost.utils.showWebContextMenu import com.pitchedapps.frost.views.FrostWebView import io.reactivex.subjects.Subject +import kotlinx.coroutines.channels.SendChannel /** * Created by Allan Wang on 2017-06-01. @@ -39,7 +40,7 @@ class FrostJSI(val web: FrostWebView) { private val context = web.context private val activity = context as? MainActivity private val header: Subject? = activity?.headerBadgeObservable - private val refresh: Subject = web.parent.refreshObservable + private val refresh: SendChannel = web.parent.refreshChannel private val cookies = activity?.cookies() ?: arrayListOf() /** @@ -120,7 +121,7 @@ class FrostJSI(val web: FrostWebView) { @JavascriptInterface fun isReady() { - refresh.onNext(false) + refresh.offer(false) } @JavascriptInterface 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 d75f03bb..5a137c44 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt @@ -41,6 +41,7 @@ import com.pitchedapps.frost.utils.launchImageActivity import com.pitchedapps.frost.utils.resolveActivityForUri import com.pitchedapps.frost.views.FrostWebView import io.reactivex.subjects.Subject +import kotlinx.coroutines.channels.SendChannel import org.jetbrains.anko.withAlpha /** @@ -64,7 +65,7 @@ open class BaseWebViewClient : WebViewClient() { */ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() { - private val refresh: Subject = web.parent.refreshObservable + private val refresh: SendChannel = web.parent.refreshChannel private val isMain = web.parent.baseEnum != null protected inline fun v(crossinline message: () -> Any?) = L.v { "web client: ${message()}" } @@ -73,7 +74,7 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() { super.onPageStarted(view, url, favicon) if (url == null) return v { "loading $url" } - refresh.onNext(true) + refresh.offer(true) } private fun injectBackgroundColor() { @@ -110,14 +111,14 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() { JsAssets.MEDIA ) else - refresh.onNext(false) + refresh.offer(false) } override fun onPageFinished(view: WebView, url: String?) { url ?: return v { "finished $url" } if (!url.isFacebookUrl) { - refresh.onNext(false) + refresh.offer(false) return } onPageFinishedActions(url) @@ -131,7 +132,7 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() { internal fun injectAndFinish() { v { "page finished reveal" } - refresh.onNext(false) + refresh.offer(false) injectBackgroundColor() web.jsInject( JsActions.LOGIN_CHECK, diff --git a/app/src/test/kotlin/com/pitchedapps/frost/MiscTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/MiscTest.kt index 2676e37d..20610b2a 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/MiscTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/MiscTest.kt @@ -17,15 +17,7 @@ package com.pitchedapps.frost import com.pitchedapps.frost.facebook.requests.zip -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.channels.BroadcastChannel -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import org.junit.Test -import java.util.concurrent.Executors import kotlin.test.assertTrue /** @@ -53,31 +45,4 @@ class MiscTest { "zip did not seem to work on different threads" ) } - -@Test -@UseExperimental(ExperimentalCoroutinesApi::class) -fun channel() { - val c = BroadcastChannel(100) - runBlocking { - launch(Dispatchers.IO) { - println("1 start ${Thread.currentThread()}") - for (i in c.openSubscription()) { - println("1 $i") - } - println("1 end ${Thread.currentThread()}") - } - launch(Dispatchers.IO) { - println("2 start ${Thread.currentThread()}") - for (i in c.openSubscription()) { - println("2 $i") - } - println("2 end ${Thread.currentThread()}") - } - c.send(1) - c.send(2) - c.send(3) - delay(1000) - c.close() - } -} } -- cgit v1.2.3