aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/build.gradle2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt16
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt9
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt56
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentContract.kt12
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt29
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt1
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/KotlinUtils.kt20
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt7
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt1
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt (renamed from app/src/test/kotlin/com/pitchedapps/frost/views/FrostContentViewAsyncTest.kt)72
17 files changed, 150 insertions, 95 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 75ffed98..84d2d694 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -101,7 +101,7 @@ android {
resValue "string", "frost_web", "Frost Web Debug"
ext.enableBugsnag = false
- kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental"]
+ kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental", "-XXLanguage:+InlineClasses"]
}
releaseTest {
minifyEnabled true
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt
index a110071c..283477d7 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt
@@ -176,7 +176,8 @@ class AboutActivity : AboutActivityBase(null, {
}
val set = ConstraintSet()
set.clone(container)
- set.createHorizontalChain(ConstraintSet.PARENT_ID,
+ set.createHorizontalChain(
+ ConstraintSet.PARENT_ID,
ConstraintSet.LEFT,
ConstraintSet.PARENT_ID,
ConstraintSet.RIGHT,
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt
index 20b5727f..78f6bfb9 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt
@@ -341,7 +341,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
private fun refreshAll() {
L.d { "Refresh all" }
- fragmentSubject.onNext(REQUEST_REFRESH)
+ fragmentChannel.offer(REQUEST_REFRESH)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -424,9 +424,9 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
/*
* These results can be stacked
*/
- if (resultCode and REQUEST_REFRESH > 0) fragmentSubject.onNext(REQUEST_REFRESH)
+ if (resultCode and REQUEST_REFRESH > 0) fragmentChannel.offer(REQUEST_REFRESH)
if (resultCode and REQUEST_NAV > 0) frostNavigationBar()
- if (resultCode and REQUEST_TEXT_ZOOM > 0) fragmentSubject.onNext(REQUEST_TEXT_ZOOM)
+ if (resultCode and REQUEST_TEXT_ZOOM > 0) fragmentChannel.offer(REQUEST_TEXT_ZOOM)
if (resultCode and REQUEST_SEARCH > 0) invalidateOptionsMenu()
}
}
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 d03c6496..cc375800 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
@@ -24,12 +24,15 @@ import com.pitchedapps.frost.views.BadgedIcon
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BroadcastChannel
import org.jsoup.Jsoup
import java.util.concurrent.TimeUnit
+@UseExperimental(ExperimentalCoroutinesApi::class)
class MainActivity : BaseMainActivity() {
- override val fragmentSubject = PublishSubject.create<Int>()
+ override val fragmentChannel = BroadcastChannel<Int>(10)
var lastPosition = -1
val headerBadgeObservable = PublishSubject.create<String>()
@@ -43,8 +46,8 @@ class MainActivity : BaseMainActivity() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
if (lastPosition == position) return
- if (lastPosition != -1) fragmentSubject.onNext(-(lastPosition + 1))
- fragmentSubject.onNext(position)
+ if (lastPosition != -1) fragmentChannel.offer(-(lastPosition + 1))
+ fragmentChannel.offer(position)
lastPosition = position
}
@@ -62,7 +65,7 @@ class MainActivity : BaseMainActivity() {
}
}
})
- viewPager.post { fragmentSubject.onNext(0); lastPosition = 0 } //trigger hook so title is set
+ viewPager.post { fragmentChannel.offer(0); lastPosition = 0 } //trigger hook so title is set
}
private fun setupTabs() {
@@ -101,8 +104,9 @@ class MainActivity : BaseMainActivity() {
}
}.disposeOnDestroy()
adapter.pages.forEach {
- tabs.addTab(tabs.newTab()
- .setCustomView(BadgedIcon(this).apply { iicon = it.icon })
+ tabs.addTab(
+ tabs.newTab()
+ .setCustomView(BadgedIcon(this).apply { iicon = it.icon })
)
}
}
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 816524ba..19a1109f 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt
@@ -67,13 +67,12 @@ import com.pitchedapps.frost.utils.Showcase
import com.pitchedapps.frost.utils.frostSnackbar
import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.utils.setFrostColors
+import com.pitchedapps.frost.utils.uniqueOnly
import com.pitchedapps.frost.views.FrostContentWeb
import com.pitchedapps.frost.views.FrostVideoViewer
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.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
@@ -91,6 +90,7 @@ import okhttp3.HttpUrl
* Used by notifications. Unlike the other overlays, this runs as a singleInstance
* Going back will bring you back to the previous app
*/
+@UseExperimental(ExperimentalCoroutinesApi::class)
class FrostWebActivity : WebOverlayActivityBase(false) {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -151,6 +151,7 @@ class WebOverlayBasicActivity : WebOverlayActivityBase(true)
class WebOverlayActivity : WebOverlayActivityBase(false)
@SuppressLint("Registered")
+@UseExperimental(ExperimentalCoroutinesApi::class)
open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseActivity(),
ActivityContract, FrostContentContainer,
VideoViewHolder, FileChooserContract by FileChooserDelegate() {
@@ -203,7 +204,7 @@ open class WebOverlayActivityBase(private val forceBasicAgent: Boolean) : BaseAc
content.bind(this)
- val titleReceiver = content.titleChannel.openSubscription()
+ val titleReceiver = content.titleChannel.openSubscription().uniqueOnly(this)
launch {
for (t in titleReceiver) {
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 2ce83871..9a70436e 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/ActivityContract.kt
@@ -19,7 +19,7 @@ package com.pitchedapps.frost.contracts
import com.mikepenz.iconics.typeface.IIcon
import com.pitchedapps.frost.activities.MainActivity
import com.pitchedapps.frost.fragments.BaseFragment
-import io.reactivex.subjects.PublishSubject
+import kotlinx.coroutines.channels.BroadcastChannel
/**
* All the contracts for [MainActivity]
@@ -27,7 +27,7 @@ import io.reactivex.subjects.PublishSubject
interface ActivityContract : FileChooserActivityContract
interface MainActivityContract : ActivityContract, MainFabContract {
- val fragmentSubject: PublishSubject<Int>
+ val fragmentChannel: BroadcastChannel<Int>
fun setTitle(res: Int)
fun setTitle(text: CharSequence)
/**
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 0f8c49d3..8a6e57af 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FrostContentContract.kt
@@ -18,12 +18,9 @@ package com.pitchedapps.frost.contracts
import android.view.View
import com.pitchedapps.frost.facebook.FbItem
-import io.reactivex.subjects.BehaviorSubject
-import io.reactivex.subjects.PublishSubject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BroadcastChannel
-import kotlinx.coroutines.channels.Channel
/**
* Created by Allan Wang on 20/12/17.
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt
index 2c46edbc..c5880840 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt
@@ -39,12 +39,13 @@ import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.REQUEST_REFRESH
import com.pitchedapps.frost.utils.REQUEST_TEXT_ZOOM
import com.pitchedapps.frost.utils.frostEvent
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.disposables.Disposable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
/**
@@ -87,7 +88,7 @@ abstract class BaseFragment : Fragment(), CoroutineScope, FragmentContract, Dyna
override var valid: Boolean
get() = arguments!!.getBoolean(ARG_VALID, true)
set(value) {
- if (value || this is WebFragment) return
+ if (!isActive || value || this is WebFragment) return
arguments!!.putBoolean(ARG_VALID, value)
L.e { "Invalidating position $position" }
frostEvent(
@@ -98,7 +99,7 @@ abstract class BaseFragment : Fragment(), CoroutineScope, FragmentContract, Dyna
}
override var firstLoad: Boolean = true
- private var activityDisposable: Disposable? = null
+ private var activityReceiver: ReceiveChannel<Int>? = null
private var onCreateRunnable: ((FragmentContract) -> Unit)? = null
override var content: FrostContentParent? = null
@@ -154,29 +155,34 @@ abstract class BaseFragment : Fragment(), CoroutineScope, FragmentContract, Dyna
(context as? MainActivityContract)?.setTitle(title)
}
- override fun attachMainObservable(contract: MainActivityContract): Disposable =
- contract.fragmentSubject.observeOn(AndroidSchedulers.mainThread()).subscribe {
- when (it) {
- REQUEST_REFRESH -> {
- core?.apply {
- clearHistory()
- firstLoad = true
- firstLoadRequest()
+ override fun attachMainObservable(contract: MainActivityContract): ReceiveChannel<Int> {
+ val receiver = contract.fragmentChannel.openSubscription()
+ launch {
+ for (flag in receiver) {
+ when (flag) {
+ REQUEST_REFRESH -> {
+ core?.apply {
+ clearHistory()
+ firstLoad = true
+ firstLoadRequest()
+ }
+ }
+ position -> {
+ contract.setTitle(baseEnum.titleId)
+ updateFab(contract)
+ core?.active = true
+ }
+ -(position + 1) -> {
+ core?.active = false
+ }
+ REQUEST_TEXT_ZOOM -> {
+ reloadTextSize()
}
- }
- position -> {
- contract.setTitle(baseEnum.titleId)
- updateFab(contract)
- core?.active = true
- }
- -(position + 1) -> {
- core?.active = false
- }
- REQUEST_TEXT_ZOOM -> {
- reloadTextSize()
}
}
}
+ return receiver
+ }
override fun updateFab(contract: MainFabContract) {
contract.hideFab() // default
@@ -195,14 +201,14 @@ abstract class BaseFragment : Fragment(), CoroutineScope, FragmentContract, Dyna
}
override fun detachMainObservable() {
- activityDisposable?.dispose()
+ activityReceiver?.cancel()
}
override fun onAttach(context: Context) {
super.onAttach(context)
detachMainObservable()
if (context is MainActivityContract)
- activityDisposable = attachMainObservable(context)
+ activityReceiver = attachMainObservable(context)
}
override fun onDetach() {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentContract.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentContract.kt
index eaef17e5..10c612c5 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentContract.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentContract.kt
@@ -22,7 +22,7 @@ import com.pitchedapps.frost.contracts.FrostContentParent
import com.pitchedapps.frost.contracts.MainActivityContract
import com.pitchedapps.frost.contracts.MainFabContract
import com.pitchedapps.frost.views.FrostRecyclerView
-import io.reactivex.disposables.Disposable
+import kotlinx.coroutines.channels.ReceiveChannel
/**
* Created by Allan Wang on 2017-11-07.
@@ -34,8 +34,9 @@ interface FragmentContract : FrostContentContainer {
/**
* Defines whether the fragment is valid in the viewpager
- * Or if it needs to be recreated
- * May be called from any thread to toggle status
+ * or if it needs to be recreated
+ * May be called from any thread to toggle status.
+ * Note that calls beyond the fragment lifecycle will be ignored
*/
var valid: Boolean
@@ -75,9 +76,10 @@ interface FragmentContract : FrostContentContainer {
/**
* Call whenever a fragment is attached so that it may listen
- * to activity emissions
+ * to activity emissions.
+ * Returns a means of closing the listener, which can be called from [detachMainObservable]
*/
- fun attachMainObservable(contract: MainActivityContract): Disposable
+ fun attachMainObservable(contract: MainActivityContract): ReceiveChannel<Int>
/**
* Call when fragment is detached so that any existing
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt
index ddb9fde6..c03ac0e2 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragmentBase.kt
@@ -21,7 +21,6 @@ import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.IItem
import com.mikepenz.fastadapter.adapters.ItemAdapter
import com.mikepenz.fastadapter.adapters.ModelAdapter
-import com.mikepenz.fastadapter_extensions.items.ProgressItem
import com.pitchedapps.frost.R
import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.facebook.parsers.FrostParser
@@ -142,31 +141,3 @@ abstract class FrostParserFragment<T : Any, Item : IItem<*, *>> : RecyclerFragme
return@withContext items
}
}
-
-//abstract class PagedRecyclerFragment<T : Any, Item : IItem<*, *>> : RecyclerFragment<T, Item>() {
-//
-// var allowPagedLoading = true
-//
-// val footerAdapter = ItemAdapter<FrostProgress>()
-//
-// val footerScrollListener = object : EndlessRecyclerOnScrollListener(footerAdapter) {
-// override fun onLoadMore(currentPage: Int) {
-// TODO("not implemented")
-//
-// }
-//
-// }
-//
-// override fun getAdapter() = fastAdapter(adapter, footerAdapter)
-//
-// override fun bindImpl(recyclerView: FrostRecyclerView) {
-// recyclerView.addOnScrollListener(footerScrollListener)
-// }
-//
-// override fun reload(progress: (Int) -> Unit, callback: (Boolean) -> Unit) {
-// footerScrollListener.
-// super.reload(progress, callback)
-// }
-//}
-
-class FrostProgress : ProgressItem()
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt
index 45cf2290..f7ed9937 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/RecyclerFragments.kt
@@ -37,7 +37,6 @@ import com.pitchedapps.frost.iitems.NotificationIItem
import com.pitchedapps.frost.utils.frostJsoup
import com.pitchedapps.frost.views.FrostRecyclerView
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/KotlinUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/KotlinUtils.kt
new file mode 100644
index 00000000..320aeb69
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/KotlinUtils.kt
@@ -0,0 +1,20 @@
+package com.pitchedapps.frost.utils
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.produce
+import kotlinx.coroutines.isActive
+
+@UseExperimental(ExperimentalCoroutinesApi::class)
+fun <T> ReceiveChannel<T>.uniqueOnly(scope: CoroutineScope): ReceiveChannel<T> = scope.produce {
+ var previous: T? = null
+ for (current in this@uniqueOnly) {
+ if (!scope.isActive) {
+ cancel()
+ } else if (previous != current) {
+ previous = current
+ send(current)
+ }
+ }
+} \ No newline at end of file
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 38591d38..9619eecc 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
@@ -40,8 +40,6 @@ import com.pitchedapps.frost.facebook.WEB_LOAD_DELAY
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import io.reactivex.disposables.Disposable
-import io.reactivex.subjects.BehaviorSubject
-import io.reactivex.subjects.PublishSubject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
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 2ba78c5e..f7cb2214 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt
@@ -23,13 +23,11 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import ca.allanwang.kau.utils.circularReveal
import ca.allanwang.kau.utils.fadeOut
-import com.pitchedapps.frost.R.string.reload
import com.pitchedapps.frost.contracts.FrostContentContainer
import com.pitchedapps.frost.contracts.FrostContentCore
import com.pitchedapps.frost.contracts.FrostContentParent
import com.pitchedapps.frost.fragments.RecyclerContentContract
import com.pitchedapps.frost.utils.Prefs
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
/**
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 f3538ec3..da90e7e5 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt
@@ -29,9 +29,6 @@ import com.pitchedapps.frost.contracts.ActivityContract
import com.pitchedapps.frost.utils.L
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
/**
@@ -47,7 +44,6 @@ class FrostChromeClient(web: FrostWebView) : WebChromeClient() {
private val progress: SendChannel<Int> = web.parent.progressChannel
private val title: SendChannel<String> = web.parent.titleChannel
- private var prevTitle: String? = null
private val activity = (web.context as? ActivityContract)
private val context = web.context!!
@@ -58,8 +54,7 @@ class FrostChromeClient(web: FrostWebView) : WebChromeClient() {
override fun onReceivedTitle(view: WebView, title: String) {
super.onReceivedTitle(view, title)
- if (title.startsWith("http") || prevTitle == title) return
- prevTitle = title
+ if (title.startsWith("http")) return
this.title.offer(title)
}
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 5a137c44..cb212b0a 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
@@ -40,7 +40,6 @@ import com.pitchedapps.frost.utils.isIndirectImageUrl
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
diff --git a/app/src/test/kotlin/com/pitchedapps/frost/views/FrostContentViewAsyncTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt
index 13c40aa3..f930e529 100644
--- a/app/src/test/kotlin/com/pitchedapps/frost/views/FrostContentViewAsyncTest.kt
+++ b/app/src/test/kotlin/com/pitchedapps/frost/utils/CoroutineTest.kt
@@ -1,12 +1,14 @@
-package com.pitchedapps.frost.views
+package com.pitchedapps.frost.utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.async
import kotlinx.coroutines.channels.BroadcastChannel
+import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.count
+import kotlinx.coroutines.delay
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
@@ -19,10 +21,10 @@ import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
- * Collection of tests around the view thread logic
+ * Collection of tests around coroutines
*/
@UseExperimental(ExperimentalCoroutinesApi::class)
-class FrostContentViewAsyncTest {
+class CoroutineTest {
/**
* Hooks onto the refresh channel for one true -> false cycle.
@@ -39,7 +41,7 @@ class FrostContentViewAsyncTest {
}
}
- private suspend fun <T> listen(channel: ReceiveChannel<T>, shouldEnd: (T) -> Boolean = { false }): List<T> =
+ private suspend fun <T> listen(channel: ReceiveChannel<T>, shouldEnd: suspend (T) -> Boolean = { false }): List<T> =
withContext(Dispatchers.IO) {
val data = mutableListOf<T>()
for (c in channel) {
@@ -127,4 +129,66 @@ class FrostContentViewAsyncTest {
assertEquals(4, receiver2.count(), "Not all events received")
}
}
+
+ /**
+ * Not a true throttle, but for things like fetching header badges, we want to avoid simultaneous fetches.
+ * As a result, I want to test that the usage of offer along with a rendezvous channel will work as I expect.
+ * Events should be consumed when there is no pending consumer on previous elements.
+ */
+ @Test
+ fun throttledChannel() {
+ val channel = Channel<Int>(Channel.RENDEZVOUS)
+ runBlocking {
+ val deferred = async {
+ listen(channel) {
+ // Throttle consumer
+ delay(10)
+ return@listen false
+ }
+ }
+ (0..100).forEach {
+ channel.offer(it)
+ delay(1)
+ }
+ channel.close()
+ val received = deferred.await()
+ assertTrue(
+ received.size < 20,
+ "Received data should be throttled; expected that around 1/10th of all events are consumed"
+ )
+ println(received)
+ }
+ }
+
+ @Test
+ fun uniqueOnly() {
+ val channel = BroadcastChannel<Int>(100)
+ runBlocking {
+ val fullReceiver = channel.openSubscription()
+ val uniqueReceiver = channel.openSubscription().uniqueOnly(this)
+
+ val fullDeferred = async { listen(fullReceiver) }
+ val uniqueDeferred = async { listen(uniqueReceiver) }
+
+ listOf(0, 1, 2, 3, 3, 3, 4, 3, 5, 5, 1).forEach {
+ channel.offer(it)
+ }
+ channel.close()
+
+ val fullData = fullDeferred.await()
+ val uniqueData = uniqueDeferred.await()
+
+ assertEquals(
+ listOf(0, 1, 2, 3, 3, 3, 4, 3, 5, 5, 1),
+ fullData,
+ "Full receiver should get all channel events"
+ )
+ assertEquals(
+ listOf(0, 1, 2, 3, 4, 3, 5, 1),
+ uniqueData,
+ "Unique receiver should not have two consecutive events that are equal"
+ )
+
+ }
+ }
} \ No newline at end of file