diff --git a/circuit-foundation/src/commonMain/kotlin/com/slack/circuit/foundation/CircuitContent.kt b/circuit-foundation/src/commonMain/kotlin/com/slack/circuit/foundation/CircuitContent.kt index e6ec34b6e..d9886dc32 100644 --- a/circuit-foundation/src/commonMain/kotlin/com/slack/circuit/foundation/CircuitContent.kt +++ b/circuit-foundation/src/commonMain/kotlin/com/slack/circuit/foundation/CircuitContent.kt @@ -9,11 +9,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import com.slack.circuit.runtime.CircuitContext @@ -21,6 +18,7 @@ import com.slack.circuit.runtime.CircuitUiState import com.slack.circuit.runtime.InternalCircuitApi import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter +import com.slack.circuit.runtime.presenter.toPauseablePresenter import com.slack.circuit.runtime.screen.PopResult import com.slack.circuit.runtime.screen.Screen import com.slack.circuit.runtime.ui.Ui @@ -88,22 +86,14 @@ public fun CircuitContent( circuit.onUnavailableContent, key: Any? = screen, ) { - CircuitContent( - screen = screen, - navigator = navigator, - presenterEnabled = true, - modifier = modifier, - circuit = circuit, - unavailableContent = unavailableContent, - key = key, - ) + CircuitContent(screen, navigator, isPaused = false, modifier, circuit, unavailableContent, key) } @Composable internal fun CircuitContent( screen: Screen, navigator: Navigator, - presenterEnabled: Boolean, + isPaused: Boolean, modifier: Modifier = Modifier, circuit: Circuit = requireNotNull(LocalCircuit.current), unavailableContent: (@Composable (screen: Screen, modifier: Modifier) -> Unit) = @@ -111,48 +101,27 @@ internal fun CircuitContent( key: Any? = screen, ) { val parent = LocalCircuitContext.current + @OptIn(InternalCircuitApi::class) val context = remember(screen, navigator, circuit, parent) { CircuitContext(parent).also { it.circuit = circuit } } CompositionLocalProvider(LocalCircuitContext provides context) { - CircuitContent( - screen = screen, - modifier = modifier, - navigator = navigator, - circuit = circuit, - unavailableContent = unavailableContent, - context = context, - key = key, - presenterEnabled = presenterEnabled, - ) - } -} - -@Composable -private fun CircuitContent( - screen: Screen, - modifier: Modifier, - navigator: Navigator, - circuit: Circuit, - unavailableContent: (@Composable (screen: Screen, modifier: Modifier) -> Unit), - context: CircuitContext, - key: Any? = screen, - presenterEnabled: Boolean, -) { - val eventListener = rememberEventListener(screen, context, factory = circuit.eventListenerFactory) - DisposableEffect(eventListener, screen, context) { onDispose { eventListener.dispose() } } + val eventListener = + rememberEventListener(screen, context, factory = circuit.eventListenerFactory) + DisposableEffect(eventListener, screen, context) { onDispose { eventListener.dispose() } } - val presenter = rememberPresenter(screen, navigator, context, eventListener, circuit::presenter) + val presenter = rememberPresenter(screen, navigator, context, eventListener, circuit::presenter) - val ui = rememberUi(screen, context, eventListener, circuit::ui) + val ui = rememberUi(screen, context, eventListener, circuit::ui) - if (ui != null && presenter != null) { - CircuitContent(screen, presenter, presenterEnabled, ui, modifier, eventListener, key) - } else { - eventListener.onUnavailableContent(screen, presenter, ui, context) - unavailableContent(screen, modifier) + if (ui != null && presenter != null) { + CircuitContent(screen, presenter, isPaused, ui, modifier, eventListener, key) + } else { + eventListener.onUnavailableContent(screen, presenter, ui, context) + unavailableContent(screen, modifier) + } } } @@ -165,24 +134,16 @@ public fun CircuitContent( eventListener: EventListener = EventListener.NONE, key: Any? = screen, ) { - CircuitContent( - screen = screen, - modifier = modifier, - presenter = presenter, - presenterEnabled = true, - ui = ui, - eventListener = eventListener, - key = key, - ) + CircuitContent(screen, presenter, isPaused = false, ui, modifier, eventListener, key) } @Composable internal fun CircuitContent( screen: Screen, - modifier: Modifier, presenter: Presenter, - presenterEnabled: Boolean, + isPaused: Boolean, ui: Ui, + modifier: Modifier = Modifier, eventListener: EventListener = EventListener.NONE, key: Any? = screen, ) { @@ -197,37 +158,25 @@ internal fun CircuitContent( onDispose { eventListener.onDisposePresent() } } - var lastState by remember { mutableStateOf(null) } - val state = - if (presenterEnabled) { - presenter.present() - } else { - lastState - } + val pauseablePresenter = remember(presenter) { presenter.toPauseablePresenter(isPaused) } + + SideEffect { pauseablePresenter.isPaused = isPaused } - SideEffect { lastState = state } + val state = pauseablePresenter.present() // TODO not sure why stateFlow + LaunchedEffect + distinctUntilChanged doesn't work here - SideEffect { - if (state != null) { - eventListener.onState(state) - } - } + SideEffect { eventListener.onState(state) } DisposableEffect(screen) { eventListener.onStartContent() onDispose { eventListener.onDisposeContent() } } - if (state != null) { - Box { - ui.Content(state, modifier) + Box { + ui.Content(state, modifier) - if (!presenterEnabled) { - // Just for debugging. Easier to see if presenters are enabled or not - Spacer(Modifier.matchParentSize().background(Color.Magenta.copy(alpha = 0.25f))) - } + if (pauseablePresenter.isPaused) { + // Just for debugging. Easier to see if presenters are enabled or not + Spacer(Modifier.matchParentSize().background(Color.Magenta.copy(alpha = 0.25f))) } - } else { - // TODO: What to do? } } } diff --git a/circuit-foundation/src/commonMain/kotlin/com/slack/circuit/foundation/NavigableCircuitContent.kt b/circuit-foundation/src/commonMain/kotlin/com/slack/circuit/foundation/NavigableCircuitContent.kt index ac9cabddb..94b2e993b 100644 --- a/circuit-foundation/src/commonMain/kotlin/com/slack/circuit/foundation/NavigableCircuitContent.kt +++ b/circuit-foundation/src/commonMain/kotlin/com/slack/circuit/foundation/NavigableCircuitContent.kt @@ -130,8 +130,8 @@ public fun NavigableCircuitContent( ) { provider.content( record, - // presenterEnabled. We only enable the presenter for the top record - backStack.topRecord == record, + // isPaused. We pause the presenter if it is not the top record + backStack.topRecord != record, ) } } @@ -187,15 +187,14 @@ private fun BackStack.buildCircuitContentProviders( RecordContentProvider( record = record, content = - movableContentOf { record, presenterEnabled -> + movableContentOf { record, isPaused -> CircuitContent( screen = record.screen, - modifier = Modifier, navigator = lastNavigator, circuit = lastCircuit, unavailableContent = lastUnavailableRoute, key = record.key, - presenterEnabled = presenterEnabled, + isPaused = isPaused, ) }, ) diff --git a/circuit-runtime-presenter/src/commonMain/kotlin/com/slack/circuit/runtime/presenter/Presenter.kt b/circuit-runtime-presenter/src/commonMain/kotlin/com/slack/circuit/runtime/presenter/Presenter.kt index 1b5cdf0c3..b8b87f05a 100644 --- a/circuit-runtime-presenter/src/commonMain/kotlin/com/slack/circuit/runtime/presenter/Presenter.kt +++ b/circuit-runtime-presenter/src/commonMain/kotlin/com/slack/circuit/runtime/presenter/Presenter.kt @@ -3,7 +3,12 @@ package com.slack.circuit.runtime.presenter import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import com.slack.circuit.runtime.CircuitContext import com.slack.circuit.runtime.CircuitUiState import com.slack.circuit.runtime.Navigator @@ -177,3 +182,33 @@ public inline fun presenterOf( } } } + +public abstract class PauseablePresenter(isPaused: Boolean = false) : + Presenter { + + public var isPaused: Boolean by mutableStateOf(isPaused) + + @Composable + override fun present(): UiState { + var lastState by remember { mutableStateOf(null) } + + return when { + // If we're paused, return the last state if we have one. If we don't we'll + // just have to call the presenter regardless + isPaused -> lastState ?: _present() + else -> _present() + }.also { SideEffect { lastState = it } } + } + + @Composable protected abstract fun _present(): UiState +} + +public fun Presenter.toPauseablePresenter( + isPaused: Boolean = false +): PauseablePresenter { + if (this is PauseablePresenter) return this + // Else we wrap the presenter + return object : PauseablePresenter(isPaused) { + @Composable override fun _present(): UiState = this@toPauseablePresenter.present() + } +}