Bluesky app fork with some witchin' additions 💫

[Reduced Onboarding] Add new step, new state to reducer (#3931)

* Add new step, new state to reducer

* Don't set default feeds

authored by

Eric Bailey and committed by
GitHub
80ce6f98 08979f37

+132 -76
+38 -30
src/screens/Onboarding/StepFinished.tsx
··· 7 7 import {useAnalytics} from '#/lib/analytics/analytics' 8 8 import {BSKY_APP_ACCOUNT_DID, IS_PROD_SERVICE} from '#/lib/constants' 9 9 import {DISCOVER_SAVED_FEED, TIMELINE_SAVED_FEED} from '#/lib/constants' 10 - import {logEvent} from '#/lib/statsig/statsig' 10 + import {logEvent, useGate} from '#/lib/statsig/statsig' 11 11 import {logger} from '#/logger' 12 12 import {useOverwriteSavedFeedsMutation} from '#/state/queries/preferences' 13 13 import {useAgent} from '#/state/session' ··· 41 41 const [saving, setSaving] = React.useState(false) 42 42 const {mutateAsync: overwriteSavedFeeds} = useOverwriteSavedFeedsMutation() 43 43 const {getAgent} = useAgent() 44 + const gate = useGate() 44 45 45 46 const finishOnboarding = React.useCallback(async () => { 46 47 setSaving(true) ··· 67 68 (async () => { 68 69 await getAgent().setInterestsPref({tags: selectedInterests}) 69 70 70 - // TODO: In the reduced onboarding, we'll want to exit early here. 71 + /* 72 + * In the reduced onboading experiment, we'll rely on the default 73 + * feeds set in `createAgentAndCreateAccount`. No feeds will be 74 + * selected in onboarding and therefore we don't need to run this 75 + * code (which would overwrite the other feeds already set). 76 + */ 77 + if (!gate('reduced_onboarding_and_home_algo')) { 78 + const otherFeeds = selectedFeeds.length 79 + ? selectedFeeds.map(f => ({ 80 + type: 'feed', 81 + value: f, 82 + pinned: true, 83 + id: TID.nextStr(), 84 + })) 85 + : [] 71 86 72 - const otherFeeds = selectedFeeds.length 73 - ? selectedFeeds.map(f => ({ 74 - type: 'feed', 75 - value: f, 87 + /* 88 + * If no selected feeds and we're in prod, add the discover feed 89 + * (mimics old behavior) 90 + */ 91 + if ( 92 + IS_PROD_SERVICE(getAgent().service.toString()) && 93 + !otherFeeds.length 94 + ) { 95 + otherFeeds.push({ 96 + ...DISCOVER_SAVED_FEED, 76 97 pinned: true, 77 98 id: TID.nextStr(), 78 - })) 79 - : [] 99 + }) 100 + } 80 101 81 - /* 82 - * If no selected feeds and we're in prod, add the discover feed 83 - * (mimics old behavior) 84 - */ 85 - if ( 86 - IS_PROD_SERVICE(getAgent().service.toString()) && 87 - !otherFeeds.length 88 - ) { 89 - otherFeeds.push({ 90 - ...DISCOVER_SAVED_FEED, 91 - pinned: true, 92 - id: TID.nextStr(), 93 - }) 102 + await overwriteSavedFeeds([ 103 + { 104 + ...TIMELINE_SAVED_FEED, 105 + pinned: true, 106 + id: TID.nextStr(), 107 + }, 108 + ...otherFeeds, 109 + ]) 94 110 } 95 - 96 - await overwriteSavedFeeds([ 97 - { 98 - ...TIMELINE_SAVED_FEED, 99 - pinned: true, 100 - id: TID.nextStr(), 101 - }, 102 - ...otherFeeds, 103 - ]) 104 111 })(), 105 112 ]) 106 113 } catch (e: any) { ··· 123 130 overwriteSavedFeeds, 124 131 track, 125 132 getAgent, 133 + gate, 126 134 ]) 127 135 128 136 React.useEffect(() => {
+55
src/screens/Onboarding/StepProfile/index.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + 6 + import { 7 + DescriptionText, 8 + OnboardingControls, 9 + TitleText, 10 + } from '#/screens/Onboarding/Layout' 11 + import {Context} from '#/screens/Onboarding/state' 12 + import {atoms as a} from '#/alf' 13 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 14 + import {IconCircle} from '#/components/IconCircle' 15 + import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 16 + import {StreamingLive_Stroke2_Corner0_Rounded as StreamingLive} from '#/components/icons/StreamingLive' 17 + 18 + export function StepProfile() { 19 + const {_} = useLingui() 20 + const {dispatch} = React.useContext(Context) 21 + 22 + const onContinue = React.useCallback(() => { 23 + dispatch({type: 'next'}) 24 + }, [dispatch]) 25 + 26 + return ( 27 + <View style={[a.align_start]}> 28 + <IconCircle icon={StreamingLive} style={[a.mb_2xl]} /> 29 + 30 + <TitleText> 31 + <Trans>Give your profile a face</Trans> 32 + </TitleText> 33 + <DescriptionText> 34 + <Trans> 35 + Help people know you're not a bot by uploading a picture or creating 36 + an avatar. 37 + </Trans> 38 + </DescriptionText> 39 + 40 + <OnboardingControls.Portal> 41 + <Button 42 + variant="gradient" 43 + color="gradient_sky" 44 + size="large" 45 + label={_(msg`Continue to next step`)} 46 + onPress={onContinue}> 47 + <ButtonText> 48 + <Trans>Continue</Trans> 49 + </ButtonText> 50 + <ButtonIcon icon={ChevronRight} position="right" /> 51 + </Button> 52 + </OnboardingControls.Portal> 53 + </View> 54 + ) 55 + }
+2
src/screens/Onboarding/index.tsx
··· 16 16 import {StepFollowingFeed} from '#/screens/Onboarding/StepFollowingFeed' 17 17 import {StepInterests} from '#/screens/Onboarding/StepInterests' 18 18 import {StepModeration} from '#/screens/Onboarding/StepModeration' 19 + import {StepProfile} from '#/screens/Onboarding/StepProfile' 19 20 import {StepSuggestedAccounts} from '#/screens/Onboarding/StepSuggestedAccounts' 20 21 import {StepTopicalFeeds} from '#/screens/Onboarding/StepTopicalFeeds' 21 22 import {Portal} from '#/components/Portal' ··· 65 66 [state, dispatch, interestsDisplayNames], 66 67 )}> 67 68 <Layout> 69 + {state.activeStep === 'profile' && <StepProfile />} 68 70 {state.activeStep === 'interests' && <StepInterests />} 69 71 {state.activeStep === 'suggestedAccounts' && ( 70 72 <StepSuggestedAccounts />
+37 -46
src/screens/Onboarding/state.ts
··· 6 6 hasPrev: boolean 7 7 totalSteps: number 8 8 activeStep: 9 + | 'profile' 9 10 | 'interests' 10 11 | 'suggestedAccounts' 11 12 | 'followingFeed' ··· 27 28 } 28 29 topicalFeedsStepResults: { 29 30 feedUris: string[] 31 + } 32 + profileStepResults: { 33 + imageUri?: string 34 + imageMime?: string 30 35 } 31 36 } 32 37 ··· 57 62 type: 'setTopicalFeedsStepResults' 58 63 feedUris: string[] 59 64 } 65 + | { 66 + type: 'setProfileStepResults' 67 + imageUri: string 68 + imageMime: string 69 + } 60 70 61 71 export type ApiResponseMap = { 62 72 interests: string[] ··· 90 100 }, 91 101 topicalFeedsStepResults: { 92 102 feedUris: [], 103 + }, 104 + profileStepResults: { 105 + imageUri: '', 106 + imageMime: '', 93 107 }, 94 108 } 95 109 ··· 240 254 241 255 export const initialStateReduced: OnboardingState = { 242 256 hasPrev: false, 243 - totalSteps: 7, 244 - activeStep: 'interests', 257 + totalSteps: 3, 258 + activeStep: 'profile', 245 259 activeStepIndex: 1, 246 260 247 261 interestsStepResults: { ··· 261 275 topicalFeedsStepResults: { 262 276 feedUris: [], 263 277 }, 278 + profileStepResults: { 279 + imageUri: '', 280 + imageMime: '', 281 + }, 264 282 } 265 283 266 284 export function reducerReduced( ··· 271 289 272 290 switch (a.type) { 273 291 case 'next': { 274 - if (s.activeStep === 'interests') { 275 - next.activeStep = 'suggestedAccounts' 292 + if (s.activeStep === 'profile') { 293 + next.activeStep = 'interests' 276 294 next.activeStepIndex = 2 277 - } else if (s.activeStep === 'suggestedAccounts') { 278 - next.activeStep = 'followingFeed' 279 - next.activeStepIndex = 3 280 - } else if (s.activeStep === 'followingFeed') { 281 - next.activeStep = 'algoFeeds' 282 - next.activeStepIndex = 4 283 - } else if (s.activeStep === 'algoFeeds') { 284 - next.activeStep = 'topicalFeeds' 285 - next.activeStepIndex = 5 286 - } else if (s.activeStep === 'topicalFeeds') { 287 - next.activeStep = 'moderation' 288 - next.activeStepIndex = 6 289 - } else if (s.activeStep === 'moderation') { 295 + } else if (s.activeStep === 'interests') { 290 296 next.activeStep = 'finished' 291 - next.activeStepIndex = 7 297 + next.activeStepIndex = 3 292 298 } 293 299 break 294 300 } 295 301 case 'prev': { 296 - if (s.activeStep === 'suggestedAccounts') { 297 - next.activeStep = 'interests' 302 + if (s.activeStep === 'interests') { 303 + next.activeStep = 'profile' 298 304 next.activeStepIndex = 1 299 - } else if (s.activeStep === 'followingFeed') { 300 - next.activeStep = 'suggestedAccounts' 305 + } else if (s.activeStep === 'finished') { 306 + next.activeStep = 'interests' 301 307 next.activeStepIndex = 2 302 - } else if (s.activeStep === 'algoFeeds') { 303 - next.activeStep = 'followingFeed' 304 - next.activeStepIndex = 3 305 - } else if (s.activeStep === 'topicalFeeds') { 306 - next.activeStep = 'algoFeeds' 307 - next.activeStepIndex = 4 308 - } else if (s.activeStep === 'moderation') { 309 - next.activeStep = 'topicalFeeds' 310 - next.activeStepIndex = 5 311 - } else if (s.activeStep === 'finished') { 312 - next.activeStep = 'moderation' 313 - next.activeStepIndex = 6 314 308 } 315 309 break 316 310 } 317 311 case 'finish': { 318 - next = initialState 312 + next = initialStateReduced 319 313 break 320 314 } 321 315 case 'setInterestsStepResults': { ··· 326 320 break 327 321 } 328 322 case 'setSuggestedAccountsStepResults': { 329 - next.suggestedAccountsStepResults = { 330 - accountDids: next.suggestedAccountsStepResults.accountDids.concat( 331 - a.accountDids, 332 - ), 333 - } 334 323 break 335 324 } 336 325 case 'setAlgoFeedsStepResults': { 337 - next.algoFeedsStepResults = { 338 - feedUris: a.feedUris, 339 - } 340 326 break 341 327 } 342 328 case 'setTopicalFeedsStepResults': { 343 - next.topicalFeedsStepResults = { 344 - feedUris: next.topicalFeedsStepResults.feedUris.concat(a.feedUris), 329 + break 330 + } 331 + case 'setProfileStepResults': { 332 + next.profileStepResults = { 333 + imageUri: a.imageUri, 334 + imageMime: a.imageMime, 345 335 } 346 336 break 347 337 } ··· 349 339 350 340 const state = { 351 341 ...next, 352 - hasPrev: next.activeStep !== 'interests', 342 + hasPrev: next.activeStep !== 'profile', 353 343 } 354 344 355 345 logger.debug(`onboarding`, { ··· 362 352 suggestedAccountsStepResults: state.suggestedAccountsStepResults, 363 353 algoFeedsStepResults: state.algoFeedsStepResults, 364 354 topicalFeedsStepResults: state.topicalFeedsStepResults, 355 + profileStepResults: state.profileStepResults, 365 356 }) 366 357 367 358 if (s.activeStep !== state.activeStep) {