cranpose_navigation/host.rs
1use cranpose_animation::{AnimationType, Easing, tween};
2use cranpose_coroflow::ProvideViewModelStore;
3use cranpose_services::BackHandler;
4use cranpose_ui::{Crossfade, composable};
5
6use crate::controller::NavController;
7
8/// How long Compose's `NavHost` fades between destinations.
9const DEFAULT_TRANSITION_MILLIS: u64 = 700;
10
11/// Shows the screen on top of `controller`'s back stack — Android's
12/// `NavHost`.
13///
14/// `content` receives the route and composes its screen, usually with a
15/// `match`, so the compiler checks that every route has one:
16///
17/// ```
18/// use cranpose_navigation::{NavHost, rememberNavController};
19///
20/// #[derive(Clone, PartialEq)]
21/// enum Screen {
22/// Home,
23/// Detail { id: u32 },
24/// }
25///
26/// # #[allow(non_snake_case)]
27/// fn App() {
28/// let nav = rememberNavController(Screen::Home);
29/// NavHost(nav, move |screen| match screen {
30/// Screen::Home => HomeScreen(move |id| nav.navigate(Screen::Detail { id })),
31/// Screen::Detail { id } => DetailScreen(id),
32/// });
33/// }
34/// # #[allow(non_snake_case)]
35/// # fn HomeScreen(_open: impl Fn(u32) + 'static) {}
36/// # #[allow(non_snake_case)]
37/// # fn DetailScreen(_id: u32) {}
38/// ```
39///
40/// Screens crossfade the way Compose's do. The platform back gesture pops the
41/// back stack while there is a screen beneath the top one. Every entry has
42/// its own view model store: `viewModel` in a screen finds the screen's view
43/// models, which are kept while other screens cover it and dropped once it is
44/// popped and has faded out.
45#[composable(no_skip)]
46pub fn NavHost<R, F>(controller: NavController<R>, content: F)
47where
48 R: Clone + PartialEq + 'static,
49 F: FnMut(R) + 'static,
50{
51 NavHostWith(
52 controller,
53 tween(DEFAULT_TRANSITION_MILLIS, Easing::FastOutSlowInEasing),
54 content,
55 );
56}
57
58/// [`NavHost`] with its own `transition` between screens — Compose's
59/// `NavHost(enterTransition, exitTransition)` for a fade.
60#[composable(no_skip)]
61pub fn NavHostWith<R, F>(controller: NavController<R>, transition: AnimationType, mut content: F)
62where
63 R: Clone + PartialEq + 'static,
64 F: FnMut(R) + 'static,
65{
66 let (top, can_pop) = controller
67 .stack
68 .read(|stack| (stack.top(), stack.can_pop()));
69 BackHandler(can_pop, move || {
70 controller.navigate_up();
71 });
72 Crossfade(top, transition, move |entry| {
73 if let Some(entry) = entry {
74 let route = entry.route;
75 ProvideViewModelStore(entry.store, || content(route));
76 }
77 });
78}