@@ -44,6 +44,7 @@ import { useLandingScroll } from "../hooks/useLandingScroll";
4444import { useNotificationScheduler } from "../hooks/useNotificationScheduler" ;
4545import { useTimerState } from "../hooks/useTimerState" ;
4646import EclipsePreviewScreen , { type PreviewPayload } from "../screens/EclipsePreviewScreen" ;
47+ import HelpScreen from "../screens/HelpScreen" ;
4748import LandingScreen from "../screens/LandingScreen" ;
4849import LocationSettingsScreen from "../screens/LocationSettingsScreen" ;
4950import NotificationSettingsScreen from "../screens/NotificationSettingsScreen" ;
@@ -56,6 +57,8 @@ import { type FavoriteLocation, useAppState } from "../state/appState";
5657import { useAppTheme } from "../theme/useAppTheme" ;
5758import { localYmdNow } from "../utils/date" ;
5859import { kindCodeForRecord , kindLabelFromCode } from "../utils/eclipse" ;
60+ import FirstRunOnboardingOverlay from "./FirstRunOnboardingOverlay" ;
61+ import { ONBOARDING_WALKTHROUGH_STEPS , type OnboardingRouteName } from "./onboardingWalkthrough" ;
5962import SideMenu , { type MenuRouteName } from "./SideMenu" ;
6063
6164enableScreens ( ) ;
@@ -65,6 +68,7 @@ type RootStackParamList = {
6568 Timer : undefined ;
6669 Preview : { payload : PreviewPayload } ;
6770 Settings : undefined ;
71+ Help : undefined ;
6872 ThemeSettings : undefined ;
6973 NotificationSettings : undefined ;
7074 LocationSettings : undefined ;
@@ -77,6 +81,7 @@ const linking: LinkingOptions<RootStackParamList> = {
7781 Landing : "landing" ,
7882 Timer : "timer" ,
7983 Settings : "settings" ,
84+ Help : "settings/help" ,
8085 ThemeSettings : "settings/theme" ,
8186 NotificationSettings : "notifications" ,
8287 LocationSettings : "locations" ,
@@ -168,6 +173,7 @@ function toMenuRouteName(route: keyof RootStackParamList): MenuRouteName | null
168173 if ( route === "Landing" || route === "Timer" ) return route ;
169174 if (
170175 route === "Settings" ||
176+ route === "Help" ||
171177 route === "ThemeSettings" ||
172178 route === "NotificationSettings" ||
173179 route === "LocationSettings"
@@ -468,13 +474,18 @@ function SettingsRoute({ navigation, onOpenMenu }: SettingsRouteProps) {
468474 return (
469475 < SettingsScreen
470476 onOpenMenu = { onOpenMenu }
477+ onOpenHelp = { ( ) => navigation . navigate ( "Help" ) }
471478 onOpenThemeSettings = { ( ) => navigation . navigate ( "ThemeSettings" ) }
472479 onOpenNotificationSettings = { ( ) => navigation . navigate ( "NotificationSettings" ) }
473480 onOpenLocationSettings = { ( ) => navigation . navigate ( "LocationSettings" ) }
474481 />
475482 ) ;
476483}
477484
485+ function HelpRoute ( { onOpenMenu } : RouteWithMenuProps ) {
486+ return < HelpScreen onOpenMenu = { onOpenMenu } /> ;
487+ }
488+
478489function ThemeSettingsRoute ( { onOpenMenu } : ThemeSettingsRouteProps ) {
479490 const { state, actions } = useAppState ( ) ;
480491
@@ -517,12 +528,13 @@ function LocationSettingsRoute({ onOpenMenu }: RouteWithMenuProps) {
517528}
518529
519530export default function RootNavigator ( ) {
520- const { state : appState , actions } = useAppState ( ) ;
531+ const { state : appState , hasHydratedPreferences , actions } = useAppState ( ) ;
521532 const { colors, resolvedTheme } = useAppTheme ( ) ;
522533 const navigationRef = useNavigationContainerRef < RootStackParamList > ( ) ;
523534 const pendingFeaturedDeepLinkActionRef = useRef < FeaturedEclipseDeepLinkAction | null > ( null ) ;
524535 const [ catalog , setCatalog ] = useState < EclipseRecord [ ] | null > ( null ) ;
525536 const [ isMenuOpen , setIsMenuOpen ] = useState ( false ) ;
537+ const [ onboardingStepIndex , setOnboardingStepIndex ] = useState ( 0 ) ;
526538 const [ currentRouteName , setCurrentRouteName ] = useState < keyof RootStackParamList > ( "Landing" ) ;
527539 const navigationTheme = useMemo < NavigationTheme > ( ( ) => {
528540 const base = resolvedTheme === "light" ? NavigationLightTheme : NavigationDarkTheme ;
@@ -665,6 +677,48 @@ export default function RootNavigator() {
665677 ) ;
666678
667679 const activeMenuRoute = useMemo ( ( ) => toMenuRouteName ( currentRouteName ) , [ currentRouteName ] ) ;
680+ const onboardingStepCount = ONBOARDING_WALKTHROUGH_STEPS . length ;
681+ const onboardingStep = ONBOARDING_WALKTHROUGH_STEPS [ onboardingStepIndex ] ?? null ;
682+ const isFirstRunOnboardingVisible =
683+ hasHydratedPreferences && ! appState . hasCompletedOnboarding && onboardingStepCount > 0 ;
684+ const isOnboardingStepRouteActive = onboardingStep
685+ ? currentRouteName === onboardingStep . route
686+ : false ;
687+
688+ const completeOnboarding = useCallback ( ( ) => {
689+ closeMenu ( ) ;
690+ setOnboardingStepIndex ( 0 ) ;
691+ actions . setOnboardingCompleted ( true ) ;
692+ } , [ actions , closeMenu ] ) ;
693+
694+ const goToOnboardingStepRoute = useCallback (
695+ ( route : OnboardingRouteName ) => {
696+ closeMenu ( ) ;
697+ if ( ! navigationRef . isReady ( ) ) return ;
698+ const currentRoute = navigationRef . getCurrentRoute ( ) ?. name ;
699+ if ( currentRoute === route ) return ;
700+ navigationRef . navigate ( route ) ;
701+ } ,
702+ [ closeMenu , navigationRef ] ,
703+ ) ;
704+
705+ const goToNextOnboardingStep = useCallback ( ( ) => {
706+ if ( ! onboardingStepCount ) {
707+ completeOnboarding ( ) ;
708+ return ;
709+ }
710+ if ( onboardingStepIndex >= onboardingStepCount - 1 ) {
711+ completeOnboarding ( ) ;
712+ return ;
713+ }
714+
715+ const nextStepIndex = onboardingStepIndex + 1 ;
716+ setOnboardingStepIndex ( nextStepIndex ) ;
717+
718+ const nextStep = ONBOARDING_WALKTHROUGH_STEPS [ nextStepIndex ] ;
719+ if ( ! nextStep ) return ;
720+ goToOnboardingStepRoute ( nextStep . route ) ;
721+ } , [ completeOnboarding , goToOnboardingStepRoute , onboardingStepCount , onboardingStepIndex ] ) ;
668722
669723 useEffect ( ( ) => {
670724 if ( ! isMenuOpen ) return ;
@@ -675,6 +729,21 @@ export default function RootNavigator() {
675729 return ( ) => subscription . remove ( ) ;
676730 } , [ closeMenu , isMenuOpen ] ) ;
677731
732+ useEffect ( ( ) => {
733+ if ( ! isFirstRunOnboardingVisible ) {
734+ setOnboardingStepIndex ( 0 ) ;
735+ return ;
736+ }
737+ if ( onboardingStepIndex >= onboardingStepCount ) {
738+ setOnboardingStepIndex ( Math . max ( 0 , onboardingStepCount - 1 ) ) ;
739+ }
740+ } , [ isFirstRunOnboardingVisible , onboardingStepCount , onboardingStepIndex ] ) ;
741+
742+ useEffect ( ( ) => {
743+ if ( ! isFirstRunOnboardingVisible || ! isMenuOpen ) return ;
744+ closeMenu ( ) ;
745+ } , [ closeMenu , isFirstRunOnboardingVisible , isMenuOpen ] ) ;
746+
678747 useEffect ( ( ) => {
679748 let didCancel = false ;
680749
@@ -737,6 +806,7 @@ export default function RootNavigator() {
737806 < Stack . Screen name = "Settings" >
738807 { ( props ) => < SettingsRoute { ...props } onOpenMenu = { openMenu } /> }
739808 </ Stack . Screen >
809+ < Stack . Screen name = "Help" > { ( ) => < HelpRoute onOpenMenu = { openMenu } /> } </ Stack . Screen >
740810 < Stack . Screen name = "ThemeSettings" >
741811 { ( props ) => < ThemeSettingsRoute { ...props } onOpenMenu = { openMenu } /> }
742812 </ Stack . Screen >
@@ -753,6 +823,16 @@ export default function RootNavigator() {
753823 onClose = { closeMenu }
754824 onNavigate = { onNavigateFromMenu }
755825 />
826+ < FirstRunOnboardingOverlay
827+ visible = { isFirstRunOnboardingVisible }
828+ step = { onboardingStep }
829+ stepIndex = { onboardingStepIndex }
830+ stepCount = { onboardingStepCount }
831+ isStepRouteActive = { isOnboardingStepRouteActive }
832+ onGoToStepRoute = { goToOnboardingStepRoute }
833+ onNext = { goToNextOnboardingStep }
834+ onSkip = { completeOnboarding }
835+ />
756836 </ View >
757837 </ NavigationContainer >
758838 ) ;
0 commit comments