Bluesky app fork with some witchin' additions 馃挮
at readme-update 122 lines 3.4 kB view raw
1import React from 'react' 2 3import * as persisted from '#/state/persisted' 4 5export const OnboardingScreenSteps = { 6 Welcome: 'Welcome', 7 RecommendedFeeds: 'RecommendedFeeds', 8 RecommendedFollows: 'RecommendedFollows', 9 Home: 'Home', 10} as const 11 12type OnboardingStep = 13 (typeof OnboardingScreenSteps)[keyof typeof OnboardingScreenSteps] 14const OnboardingStepsArray = Object.values(OnboardingScreenSteps) 15 16type Action = 17 | {type: 'set'; step: OnboardingStep} 18 | {type: 'next'; currentStep?: OnboardingStep} 19 | {type: 'start'} 20 | {type: 'finish'} 21 | {type: 'skip'} 22 23export type StateContext = persisted.Schema['onboarding'] & { 24 isComplete: boolean 25 isActive: boolean 26} 27export type DispatchContext = (action: Action) => void 28 29const stateContext = React.createContext<StateContext>( 30 compute(persisted.defaults.onboarding), 31) 32stateContext.displayName = 'OnboardingStateContext' 33const dispatchContext = React.createContext<DispatchContext>((_: Action) => {}) 34dispatchContext.displayName = 'OnboardingDispatchContext' 35 36function reducer(state: StateContext, action: Action): StateContext { 37 switch (action.type) { 38 case 'set': { 39 if (OnboardingStepsArray.includes(action.step)) { 40 persisted.write('onboarding', {step: action.step}) 41 return compute({...state, step: action.step}) 42 } 43 return state 44 } 45 case 'next': { 46 const currentStep = action.currentStep || state.step 47 let nextStep = 'Home' 48 if (currentStep === 'Welcome') { 49 nextStep = 'RecommendedFeeds' 50 } else if (currentStep === 'RecommendedFeeds') { 51 nextStep = 'RecommendedFollows' 52 } else if (currentStep === 'RecommendedFollows') { 53 nextStep = 'Home' 54 } 55 persisted.write('onboarding', {step: nextStep}) 56 return compute({...state, step: nextStep}) 57 } 58 case 'start': { 59 persisted.write('onboarding', {step: 'Welcome'}) 60 return compute({...state, step: 'Welcome'}) 61 } 62 case 'finish': { 63 persisted.write('onboarding', {step: 'Home'}) 64 return compute({...state, step: 'Home'}) 65 } 66 case 'skip': { 67 persisted.write('onboarding', {step: 'Home'}) 68 return compute({...state, step: 'Home'}) 69 } 70 default: { 71 throw new Error('Invalid action') 72 } 73 } 74} 75 76export function Provider({children}: React.PropsWithChildren<{}>) { 77 const [state, dispatch] = React.useReducer( 78 reducer, 79 compute(persisted.get('onboarding')), 80 ) 81 82 React.useEffect(() => { 83 return persisted.onUpdate('onboarding', nextOnboarding => { 84 const next = nextOnboarding.step 85 // TODO we've introduced a footgun 86 if (state.step !== next) { 87 dispatch({ 88 type: 'set', 89 step: nextOnboarding.step as OnboardingStep, 90 }) 91 } 92 }) 93 }, [state, dispatch]) 94 95 return ( 96 <stateContext.Provider value={state}> 97 <dispatchContext.Provider value={dispatch}> 98 {children} 99 </dispatchContext.Provider> 100 </stateContext.Provider> 101 ) 102} 103 104export function useOnboardingState() { 105 return React.useContext(stateContext) 106} 107 108export function useOnboardingDispatch() { 109 return React.useContext(dispatchContext) 110} 111 112export function isOnboardingActive() { 113 return compute(persisted.get('onboarding')).isActive 114} 115 116function compute(state: persisted.Schema['onboarding']): StateContext { 117 return { 118 ...state, 119 isActive: state.step !== 'Home', 120 isComplete: state.step === 'Home', 121 } 122}