whiskers.git / crates / whiskers-core / src / entrance.rs

Whether the child's side of the app may show: the first-run gate.

The child's name and age are entered on the parents' side and nowhere else, and everything Whiskers says is written for them (the greeting included), so a device that has no [Child] has nothing it may say yet. Until both halves are filled in the child's side stays shut and the app opens on the parents' "About your child" screen.

"No profile" is judged after a sync with the service, not before: a reinstall that gets the household document back is not a first run. So the decision has three answers. With a profile the child's side opens at once (a device that already knows who it talks to never waits for the network). With none and no sync tried yet, the shell is told to sync first ([Entrance::SyncFirst]). With none after a sync, or after a sync that could not reach the service, it is a first run; [Entrance::FirstRun::service_reached] says which, so the screen can tell a parent that the fields will be shared once the service is back.

The guard's floor is unchanged (Age::YOUNGEST with no profile). This gate is why it is not reachable in normal use, not a replacement for it.

19use crate::profile::Child;

What the shell knows about syncing with the service, when it asks.

22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum SyncOutcome {

No sync has been tried since the app opened.

25    NotTried,

The service answered; whatever it holds has been merged in.

27    Reached,

The service could not be reached (offline, down, no address that works).

29    Unreachable,
30}

Where the app opens.

33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum Entrance {

A profile exists: the child's side may show.

36    ChildSide,

There is no profile and no sync has been tried: try one, then ask again.

38    SyncFirst,

There is still no profile after trying: the child's side stays shut and the parents fill in the name and the age.

41    FirstRun { service_reached: bool },
42}
44impl Entrance {

The one place the question is answered. child is the profile this device holds now.

46    pub fn of(child: Option<&Child>, sync: SyncOutcome) -> Entrance {
47        let entrance = match (child, sync) {
48            (Some(_), _) => Entrance::ChildSide,
49            (None, SyncOutcome::NotTried) => Entrance::SyncFirst,
50            (None, SyncOutcome::Reached) => Entrance::FirstRun { service_reached: true },
51            (None, SyncOutcome::Unreachable) => Entrance::FirstRun { service_reached: false },
52        };
53        ::log::info!("entrance: profile set = {}, sync {sync:?} -> {entrance:?}", child.is_some());
54        entrance
55    }

Whether the child's side may show. The only way to be told yes is a profile.

58    pub fn child_side_open(self) -> bool {
59        self == Entrance::ChildSide
60    }
61}
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::profile::{Age, ChildName};
67
68    fn ada() -> Child {
69        Child { name: ChildName::new("Ada").unwrap(), age: Age::new(6).unwrap() }
70    }
71
72    #[test]
73    fn a_profile_opens_the_child_side_without_waiting_for_the_network() {
74        for sync in [SyncOutcome::NotTried, SyncOutcome::Reached, SyncOutcome::Unreachable] {
75            assert_eq!(Entrance::of(Some(&ada()), sync), Entrance::ChildSide, "{sync:?}");
76        }
77    }
78
79    #[test]
80    fn no_profile_and_no_sync_yet_means_sync_first() {
81        assert_eq!(Entrance::of(None, SyncOutcome::NotTried), Entrance::SyncFirst);
82    }
83
84    #[test]
85    fn no_profile_after_reaching_the_service_is_a_first_run() {
86        assert_eq!(Entrance::of(None, SyncOutcome::Reached), Entrance::FirstRun { service_reached: true });
87    }
88
89    #[test]
90    fn no_profile_with_the_service_unreachable_is_still_a_first_run_and_says_so() {
91        assert_eq!(Entrance::of(None, SyncOutcome::Unreachable), Entrance::FirstRun { service_reached: false });
92    }
93
94    #[test]
95    fn only_a_profile_opens_the_child_side() {
96        assert!(Entrance::ChildSide.child_side_open());
97        assert!(!Entrance::SyncFirst.child_side_open());
98        assert!(!Entrance::FirstRun { service_reached: true }.child_side_open());
99        assert!(!Entrance::FirstRun { service_reached: false }.child_side_open());
100    }
101}