aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps/frost/views
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/views')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt79
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt7
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt56
3 files changed, 73 insertions, 69 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..1891a786 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostContentView.kt
@@ -42,12 +42,23 @@ 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 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.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
class FrostContentWeb @JvmOverloads constructor(
@@ -119,11 +130,22 @@ 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
*/
- override val refreshChannel: BroadcastChannel<Boolean> = BroadcastChannel(10)
+ private val refreshMutableFlow = MutableSharedFlow<Boolean>(
+ extraBufferCapacity = 10,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
+
+ override val refreshFlow: SharedFlow<Boolean> = refreshMutableFlow.asSharedFlow()
+
+ override val refreshEmit: FrostEmitter<Boolean> =
+ FrostEmitter { refreshMutableFlow.tryEmit(it) }
+
override val progressChannel: BroadcastChannel<Int> = ConflatedBroadcastChannel()
override val titleChannel: BroadcastChannel<String> = ConflatedBroadcastChannel()
@@ -160,7 +182,6 @@ abstract class FrostContentViewBase(
*/
protected fun init() {
inflate(context, layoutRes, this)
- core.parent = this
reloadThemeSelf()
}
@@ -169,15 +190,15 @@ 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 ->
progress.invisibleIf(p == 100)
@@ -220,33 +241,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
}
}
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 2ab00916..a2b71572 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostRecyclerView.kt
@@ -61,7 +61,8 @@ class FrostRecyclerView @JvmOverloads constructor(
layoutManager = LinearLayoutManager(context)
}
- override fun bind(container: FrostContentContainer): View {
+ override fun bind(parent: FrostContentParent, container: FrostContentContainer): View {
+ this.parent = parent
if (container !is RecyclerContentContract)
throw IllegalStateException("FrostRecyclerView must bind to a container that is a RecyclerContentContract")
this.recyclerContract = container
@@ -78,10 +79,10 @@ class FrostRecyclerView @JvmOverloads constructor(
override fun reloadBase(animate: Boolean) {
if (prefs.animate) fadeOut(onFinish = onReloadClear)
scope.launch {
- parent.refreshChannel.offer(true)
+ parent.refreshEmit(true)
recyclerContract.reload { parent.progressChannel.offer(it) }
parent.progressChannel.offer(100)
- parent.refreshChannel.offer(false)
+ parent.refreshEmit(false)
if (prefs.animate) circularReveal()
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
index f384d134..140b4901 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
@@ -35,25 +35,23 @@ import com.pitchedapps.frost.db.CookieDao
import com.pitchedapps.frost.db.currentCookie
import com.pitchedapps.frost.facebook.FB_HOME_URL
import com.pitchedapps.frost.facebook.FbCookie
+import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.USER_AGENT
-import com.pitchedapps.frost.fragments.WebFragment
import com.pitchedapps.frost.injectors.ThemeProvider
import com.pitchedapps.frost.prefs.Prefs
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.frostDownload
import com.pitchedapps.frost.web.FrostChromeClient
-import com.pitchedapps.frost.web.FrostJSI
+import com.pitchedapps.frost.web.FrostWebClientEntryPoint
+import com.pitchedapps.frost.web.FrostWebComponentBuilder
+import com.pitchedapps.frost.web.FrostWebEntryPoint
import com.pitchedapps.frost.web.FrostWebViewClient
+import com.pitchedapps.frost.web.FrostWebViewClientMenu
+import com.pitchedapps.frost.web.FrostWebViewClientMessenger
import com.pitchedapps.frost.web.NestedWebView
-import dagger.BindsInstance
-import dagger.hilt.DefineComponent
-import dagger.hilt.EntryPoint
import dagger.hilt.EntryPoints
-import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
-import dagger.hilt.android.components.ViewComponent
import javax.inject.Inject
-import javax.inject.Scope
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -103,9 +101,11 @@ class FrostWebView @JvmOverloads constructor(
get() = url ?: ""
@SuppressLint("SetJavaScriptEnabled")
- override fun bind(container: FrostContentContainer): View {
- val component = frostWebComponentBuilder.frostWebView(this).build()
- val entryPoint = EntryPoints.get(component, FrostWebEntryPoint::class.java)
+ override fun bind(parent: FrostContentParent, container: FrostContentContainer): View {
+ this.parent = parent
+ val component = frostWebComponentBuilder.frostParent(parent).frostWebView(this).build()
+ val webEntryPoint = EntryPoints.get(component, FrostWebEntryPoint::class.java)
+ val clientEntryPoint = EntryPoints.get(component, FrostWebClientEntryPoint::class.java)
userAgentString = USER_AGENT
with(settings) {
javaScriptEnabled = true
@@ -116,10 +116,14 @@ class FrostWebView @JvmOverloads constructor(
}
setLayerType(LAYER_TYPE_HARDWARE, null)
// attempt to get custom client; otherwise fallback to original
- frostWebClient = (container as? WebFragment)?.client(this) ?: FrostWebViewClient(this)
+ frostWebClient = when (parent.baseEnum) {
+ FbItem.MESSENGER -> FrostWebViewClientMessenger(this)
+ FbItem.MENU -> FrostWebViewClientMenu(this)
+ else -> clientEntryPoint.webClient()
+ }
webViewClient = frostWebClient
webChromeClient = FrostChromeClient(this, themeProvider, webFileChooser)
- addJavascriptInterface(entryPoint.frostJsi(), "Frost")
+ addJavascriptInterface(webEntryPoint.frostJsi(), "Frost")
setBackgroundColor(Color.TRANSPARENT)
setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
context.ctxCoroutine.launchMain {
@@ -251,29 +255,3 @@ class FrostWebView @JvmOverloads constructor(
super.destroy()
}
}
-
-@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 frostWebView(@BindsInstance web: FrostWebView): FrostWebComponentBuilder
- fun build(): FrostWebComponent
-}
-
-@EntryPoint
-@InstallIn(FrostWebComponent::class)
-interface FrostWebEntryPoint {
- @FrostWebScoped
- fun frostJsi(): FrostJSI
-}