1//! Whether the child's side of the app may show: the first-run gate. 2//! 3//! The child's name and age are entered on the parents' side and nowhere else, and everything 4//! Whiskers says is written for them (the greeting included), so a device that has no 5//! [`Child`] has nothing it may say yet. Until both halves are filled in the child's side stays 6//! shut and the app opens on the parents' "About your child" screen. 7//! 8//! "No profile" is judged **after a sync with the service**, not before: a reinstall that gets the 9//! household document back is not a first run. So the decision has three answers. With a profile 10//! the child's side opens at once (a device that already knows who it talks to never waits for the 11//! network). With none and no sync tried yet, the shell is told to sync first 12//! ([`Entrance::SyncFirst`]). With none after a sync, or after a sync that could not reach the 13//! service, it is a first run; [`Entrance::FirstRun::service_reached`] says which, so the screen can 14//! tell a parent that the fields will be shared once the service is back. 15//! 16//! The guard's floor is unchanged ([`Age::YOUNGEST`](crate::Age::YOUNGEST) with no profile). This 17//! gate is why it is not reachable in normal use, not a replacement for it. 18 19use crate::profile::Child; 20 21/// What the shell knows about syncing with the service, when it asks. 22#[derive(Clone, Copy, Debug, PartialEq, Eq)] 23pub enum SyncOutcome { 24 /// No sync has been tried since the app opened. 25 NotTried, 26 /// The service answered; whatever it holds has been merged in. 27 Reached, 28 /// The service could not be reached (offline, down, no address that works). 29 Unreachable, 30} 31 32/// Where the app opens. 33#[derive(Clone, Copy, Debug, PartialEq, Eq)] 34pub enum Entrance { 35 /// A profile exists: the child's side may show. 36 ChildSide, 37 /// There is no profile and no sync has been tried: try one, then ask again. 38 SyncFirst, 39 /// There is still no profile after trying: the child's side stays shut and the parents fill in 40 /// the name and the age. 41 FirstRun { service_reached: bool }, 42} 43 44impl Entrance { 45 /// 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 } 56 57 /// 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} 62 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}