-
Notifications
You must be signed in to change notification settings - Fork 10
/
VoyagerNavigation.kt
109 lines (97 loc) · 3.89 KB
/
VoyagerNavigation.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package codes.chrishorner.socketweather.util.navigation
import android.content.Context
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import app.cash.molecule.RecompositionClock
import app.cash.molecule.launchMolecule
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.ScreenModelStore
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.ScreenKey
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import codes.chrishorner.socketweather.Navigator
import codes.chrishorner.socketweather.Presenter
import codes.chrishorner.socketweather.Screen
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.plus
import kotlinx.parcelize.Parcelize
import cafe.adriel.voyager.core.screen.Screen as VoyagerScreen
import cafe.adriel.voyager.navigator.Navigator as VoyagerNavigator
/**
* Takes in an initial [Screen] and uses Voyager as a [Navigator] implementation to
* push and pop destinations.
*/
@Composable
fun VoyagerNavigation(initialScreen: Screen<*, *>) {
VoyagerNavigator(initialScreen.toVoyagerScreen()) { navigator -> FadeThroughTransition(navigator) }
}
/**
* Adapts a [Screen] to Voyager's concept of a screen.
*/
private fun <Event, State> Screen<Event, State>.toVoyagerScreen(): VoyagerScreen {
return DelegatingVoyagerScreen(this)
}
private class DelegatingNavigator(var voyagerNavigator: VoyagerNavigator) : Navigator {
override val canPop: Boolean
get() = voyagerNavigator.canPop
override fun push(screen: Screen<*, *>) {
voyagerNavigator.push(screen.toVoyagerScreen())
}
override fun pop() {
voyagerNavigator.pop()
}
override fun replaceAllWith(screen: Screen<*, *>) {
voyagerNavigator.replaceAll(screen.toVoyagerScreen())
}
}
@Parcelize
private data class DelegatingVoyagerScreen<Event, State>(
private val screen: Screen<Event, State>,
) : VoyagerScreen, Parcelable {
@Composable
override fun Content() {
val context: Context = LocalContext.current.applicationContext
val voyagerNavigator = LocalNavigator.currentOrThrow
val navigator = remember(voyagerNavigator) { DelegatingNavigator(voyagerNavigator) }
val screenModel = rememberScreenModel {
MoleculeScreenModel(
navigator = navigator,
presenter = screen.onCreatePresenter(context, navigator),
)
}
// Voyager recreates its navigator instances on orientation change, so update our delegate if necessary.
screenModel.navigator.voyagerNavigator = voyagerNavigator
val state by screenModel.states.collectAsState()
screen.Content(state) { event -> screenModel.events.tryEmit(event) }
}
override val key: ScreenKey
get() = screen::class.qualifiedName!!
}
/**
* Similar to Voyager's [StateScreenModel], except using Molecule to handle state generation.
*/
private class MoleculeScreenModel<Event, State>(
val navigator: DelegatingNavigator,
presenter: Presenter<Event, State>,
) : ScreenModel {
val events = MutableSharedFlow<Event>(extraBufferCapacity = 1)
val states = moleculeScope.launchMolecule(RecompositionClock.Immediate) { presenter.states(events) }
}
private val ScreenModel.moleculeScope: CoroutineScope
get() = ScreenModelStore.getOrPutDependency(
screenModel = this,
name = "ScreenModelMoleculeScope",
factory = { key -> CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + CoroutineName(key) },
onDispose = { scope -> scope.cancel() }
)