use std::{cell::RefCell, rc::Rc};
use cranpose_animation::{AnimationType, Easing, tween};
use cranpose_coroflow::ProvideViewModelStore;
use cranpose_services::{BackGesture, PredictiveBackHandler, ProvideSaveableScope};
use cranpose_ui::{
ContentSeek, SeekableCrossfade, composable,
widgets::{Dialog, DialogSpec},
};
use crate::controller::{Entry, HostView, NavController};
const DEFAULT_TRANSITION_MILLIS: u64 = 700;
#[composable(no_skip)]
pub fn NavHost<R, F>(controller: NavController<R>, content: F)
where
R: Clone + PartialEq + 'static,
F: FnMut(R) + 'static,
{
NavHostWith(
controller,
tween(DEFAULT_TRANSITION_MILLIS, Easing::FastOutSlowInEasing),
content,
);
}
#[composable(no_skip)]
pub fn NavHostWith<R, F>(controller: NavController<R>, transition: AnimationType, content: F)
where
R: Clone + PartialEq + 'static,
F: FnMut(R) + 'static,
{
NavHostWithDialogs(controller, transition, |_| None, content);
}
#[composable(no_skip)]
pub fn NavHostWithDialogs<R, D, F>(
controller: NavController<R>,
transition: AnimationType,
dialog: D,
content: F,
) where
R: Clone + PartialEq + 'static,
D: Fn(&R) -> Option<DialogSpec> + 'static,
F: FnMut(R) + 'static,
{
let HostView {
screen,
toward,
dialogs,
} = controller
.stack
.read(|stack| stack.screens_and_dialogs(&dialog));
let gesture = cranpose_core::rememberMutableStateOf(|| None::<f32>);
PredictiveBackHandler(
dialogs.is_empty() && toward.is_some(),
move |step| {
gesture.set(match step {
BackGesture::Started(event) | BackGesture::Progressed(event) => {
Some(event.progress)
}
BackGesture::Cancelled => None,
});
},
move || {
gesture.set(None);
controller.navigate_up();
},
);
let seek = gesture
.get()
.zip(toward)
.map(|(fraction, toward)| ContentSeek {
toward: Some(toward),
fraction,
});
let content = Rc::new(RefCell::new(content));
let shown = Rc::clone(&content);
SeekableCrossfade(screen, seek, transition, move |entry| {
if let Some(entry) = entry {
compose_entry(entry, &shown);
}
});
for (depth, entry, spec) in dialogs {
let content = Rc::clone(&content);
cranpose_core::key(entry.id(), move || {
Dialog(
spec,
move |_| controller.pop_to_depth(depth),
move || compose_entry(entry.clone(), &content),
);
});
}
}
fn compose_entry<R, F: FnMut(R)>(entry: Entry<R>, content: &RefCell<F>) {
let Entry {
route,
store,
saveables,
..
} = entry;
ProvideSaveableScope(saveables, || {
ProvideViewModelStore(store, || (content.borrow_mut())(route));
});
}