aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt112
1 files changed, 73 insertions, 39 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
index c30ee199..16c28c02 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,17 +38,27 @@ 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
+import com.pitchedapps.frost.web.asFrostEmitter
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.BroadcastChannel
-import kotlinx.coroutines.channels.ConflatedBroadcastChannel
-import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.BufferOverflow
+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
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.runningFold
+import kotlinx.coroutines.flow.transformWhile
import javax.inject.Inject
+@ExperimentalCoroutinesApi
class FrostContentWeb @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@@ -60,6 +69,7 @@ class FrostContentWeb @JvmOverloads constructor(
override val layoutRes: Int = R.layout.view_content_base_web
}
+@ExperimentalCoroutinesApi
class FrostContentRecycler @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@@ -70,7 +80,7 @@ class FrostContentRecycler @JvmOverloads constructor(
override val layoutRes: Int = R.layout.view_content_base_recycler
}
-@UseExperimental(ExperimentalCoroutinesApi::class)
+@ExperimentalCoroutinesApi
abstract class FrostContentView<out T> @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@@ -88,8 +98,8 @@ abstract class FrostContentView<out T> @JvmOverloads constructor(
/**
* Subsection of [FrostContentView] that is [AndroidEntryPoint] friendly (no generics)
*/
-@UseExperimental(ExperimentalCoroutinesApi::class)
@AndroidEntryPoint
+@ExperimentalCoroutinesApi
abstract class FrostContentViewBase(
context: Context,
attrs: AttributeSet?,
@@ -119,13 +129,35 @@ abstract class FrostContentViewBase(
private val refresh: SwipeRefreshLayout by bindView(R.id.content_refresh)
private val progress: ProgressBar by bindView(R.id.content_progress)
+ private val coreView: View by bindView(R.id.content_core)
+
/**
* While this can be conflated, there exist situations where we wish to watch refresh cycles.
* Here, we'd need to make sure we don't skip events
+ *
+ * TODO ensure there is only one flow provider is this is still separated in login
+ * Use case for shared flow is to avoid emitting before subscribing; buffer can probably be size 1
*/
- override val refreshChannel: BroadcastChannel<Boolean> = BroadcastChannel(10)
- override val progressChannel: BroadcastChannel<Int> = ConflatedBroadcastChannel()
- override val titleChannel: BroadcastChannel<String> = ConflatedBroadcastChannel()
+ private val refreshMutableFlow = MutableSharedFlow<Boolean>(
+ extraBufferCapacity = 10,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
+
+ override val refreshFlow: SharedFlow<Boolean> = refreshMutableFlow.asSharedFlow()
+
+ override val refreshEmit: FrostEmitter<Boolean> = refreshMutableFlow.asFrostEmitter()
+
+ private val progressMutableFlow = MutableStateFlow(0)
+
+ override val progressFlow: SharedFlow<Int> = progressMutableFlow.asSharedFlow()
+
+ override val progressEmit: FrostEmitter<Int> = progressMutableFlow.asFrostEmitter()
+
+ private val titleMutableFlow = MutableStateFlow("")
+
+ override val titleFlow: SharedFlow<String> = titleMutableFlow.asSharedFlow()
+
+ override val titleEmit: FrostEmitter<String> = titleMutableFlow.asFrostEmitter()
override lateinit var scope: CoroutineScope
@@ -160,7 +192,6 @@ abstract class FrostContentViewBase(
*/
protected fun init() {
inflate(context, layoutRes, this)
- core.parent = this
reloadThemeSelf()
}
@@ -169,23 +200,23 @@ abstract class FrostContentViewBase(
baseEnum = container.baseEnum
init()
scope = container
- core.bind(container)
+ core.bind(this, container)
refresh.setOnRefreshListener {
core.reload(true)
}
- refreshChannel.subscribeDuringJob(scope, ContextHelper.coroutineContext) { r ->
+ refreshFlow.distinctUntilChanged().onEach { r ->
L.v { "Refreshing $r" }
refresh.isRefreshing = r
- }
+ }.launchIn(scope)
- progressChannel.subscribeDuringJob(scope, ContextHelper.coroutineContext) { p ->
+ 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() {
@@ -212,7 +243,6 @@ abstract class FrostContentViewBase(
}
private var transitionStart: Long = -1
- private var refreshReceiver: ReceiveChannel<Boolean>? = null
/**
* Hook onto the refresh observable for one cycle
@@ -220,33 +250,37 @@ abstract class FrostContentViewBase(
* The cycle only starts on the first load since there may have been another process when this is registered
*/
override fun registerTransition(urlChanged: Boolean, animate: Boolean): Boolean {
- if (!urlChanged && refreshReceiver != null) {
+ if (!urlChanged && transitionStart != -1L) {
L.v { "Consuming url load" }
return false // still in progress; do not bother with load
}
+ coreView.transition(animate)
+ return true
+ }
+
+ private fun View.transition(animate: Boolean) {
L.v { "Registered transition" }
- with(core) {
- refreshReceiver = refreshChannel.openSubscription().also { receiver ->
- scope.launchMain {
- var loading = false
- for (r in receiver) {
- if (r) {
- loading = true
- transitionStart = System.currentTimeMillis()
- clearAnimation()
- if (isVisible)
- fadeOut(duration = 200L)
- } else if (loading) {
- if (animate && prefs.animate) circularReveal(offset = WEB_LOAD_DELAY)
- else fadeIn(duration = 200L, offset = WEB_LOAD_DELAY)
- L.v { "Transition loaded in ${System.currentTimeMillis() - transitionStart} ms" }
- receiver.cancel()
- refreshReceiver = null
- }
+ transitionStart = 0L // Marker for pending transition
+ scope.launchMain {
+ refreshFlow.distinctUntilChanged()
+ // Pseudo windowed mode
+ .runningFold(false to false) { (_, prev), curr -> prev to curr }
+ // Take until prev was loading and current is not loading
+ // Unlike takeWhile, we include the last state (first non matching)
+ .transformWhile { emit(it); it != (true to false) }
+ .onEach { (prev, curr) ->
+ if (curr) {
+ transitionStart = System.currentTimeMillis()
+ clearAnimation()
+ if (isVisible)
+ fadeOut(duration = 200L)
+ } else if (prev) { // prev && !curr
+ if (animate && prefs.animate) circularReveal(offset = WEB_LOAD_DELAY)
+ else fadeIn(duration = 200L, offset = WEB_LOAD_DELAY)
+ L.v { "Transition loaded in ${System.currentTimeMillis() - transitionStart} ms" }
}
- }
- }
+ }.collect()
+ transitionStart = -1L
}
- return true
}
}