Skip to content

Commit

Permalink
Add pausable presenter class
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbanes committed Apr 10, 2024
1 parent d3e89bc commit ad075f8
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,16 @@ 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
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
Expand Down Expand Up @@ -88,71 +86,42 @@ 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) =
circuit.onUnavailableContent,
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)
}
}
}

Expand All @@ -165,24 +134,16 @@ public fun <UiState : CircuitUiState> 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 <UiState : CircuitUiState> CircuitContent(
screen: Screen,
modifier: Modifier,
presenter: Presenter<UiState>,
presenterEnabled: Boolean,
isPaused: Boolean,
ui: Ui<UiState>,
modifier: Modifier = Modifier,
eventListener: EventListener = EventListener.NONE,
key: Any? = screen,
) {
Expand All @@ -197,37 +158,25 @@ internal fun <UiState : CircuitUiState> CircuitContent(
onDispose { eventListener.onDisposePresent() }
}

var lastState by remember { mutableStateOf<UiState?>(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?
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ public fun <R : Record> 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,
)
}
}
Expand Down Expand Up @@ -187,15 +187,14 @@ private fun <R : Record> BackStack<R>.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,
)
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -177,3 +182,33 @@ public inline fun <UiState : CircuitUiState> presenterOf(
}
}
}

public abstract class PauseablePresenter<UiState : CircuitUiState>(isPaused: Boolean = false) :
Presenter<UiState> {

public var isPaused: Boolean by mutableStateOf(isPaused)

@Composable
override fun present(): UiState {
var lastState by remember { mutableStateOf<UiState?>(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 <UiState : CircuitUiState> Presenter<UiState>.toPauseablePresenter(
isPaused: Boolean = false
): PauseablePresenter<UiState> {
if (this is PauseablePresenter<UiState>) return this
// Else we wrap the presenter
return object : PauseablePresenter<UiState>(isPaused) {
@Composable override fun _present(): UiState = this@toPauseablePresenter.present()
}
}

0 comments on commit ad075f8

Please sign in to comment.