integrate existing flow into onboarding
This commit is contained in:
@@ -2,21 +2,23 @@ import {GetContacts} from './screens/GetContacts'
|
||||
import {PhoneInput} from './screens/PhoneInput'
|
||||
import {VerifyNumber} from './screens/VerifyNumber'
|
||||
import {ViewMatches} from './screens/ViewMatches'
|
||||
import {type Action, type State} from './state'
|
||||
import {type Action, FindContactsGoBackContext, type State} from './state'
|
||||
|
||||
export function FindContactsFlow({
|
||||
state,
|
||||
dispatch,
|
||||
onBack,
|
||||
onCancel,
|
||||
context = 'Standalone',
|
||||
}: {
|
||||
state: State
|
||||
dispatch: React.Dispatch<Action>
|
||||
onBack?: () => void
|
||||
onCancel: () => void
|
||||
context: 'Onboarding' | 'Standalone'
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<FindContactsGoBackContext value={onBack}>
|
||||
{state.step === '1: phone input' && (
|
||||
<PhoneInput
|
||||
state={state}
|
||||
@@ -37,8 +39,13 @@ export function FindContactsFlow({
|
||||
<GetContacts state={state} dispatch={dispatch} onCancel={onCancel} />
|
||||
)}
|
||||
{state.step === '4: view matches' && (
|
||||
<ViewMatches state={state} dispatch={dispatch} />
|
||||
<ViewMatches
|
||||
state={state}
|
||||
dispatch={dispatch}
|
||||
context={context}
|
||||
onNext={onCancel}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</FindContactsGoBackContext>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
getCountryCodeFromPastedNumber,
|
||||
processPhoneNumber,
|
||||
} from '../phone-number'
|
||||
import {type Action, type State} from '../state'
|
||||
import {type Action, type State, useOnPressBackButton} from '../state'
|
||||
|
||||
export function PhoneInput({
|
||||
state,
|
||||
@@ -103,10 +103,12 @@ export function PhoneInput({
|
||||
|
||||
const paddingBottom = Math.max(insets.bottom, tokens.space.xl)
|
||||
|
||||
const onPressBack = useOnPressBackButton()
|
||||
|
||||
return (
|
||||
<View style={[a.h_full]}>
|
||||
<Layout.Header.Outer noBottomBorder>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.BackButton onPress={onPressBack} />
|
||||
<Layout.Header.Content />
|
||||
{showSkipButton ? (
|
||||
<Button
|
||||
@@ -204,12 +206,12 @@ export function PhoneInput({
|
||||
<View style={[gutters, {paddingBottom}]}>
|
||||
<Button
|
||||
disabled={!phoneNumber || isPending}
|
||||
label={_(msg`Next`)}
|
||||
label={_(msg`Send code`)}
|
||||
size="large"
|
||||
color="primary"
|
||||
onPress={onSubmitNumber}>
|
||||
<ButtonText>
|
||||
<Trans>Next</Trans>
|
||||
<Trans>Send code</Trans>
|
||||
</ButtonText>
|
||||
{isPending && <ButtonIcon icon={Loader} />}
|
||||
</Button>
|
||||
|
||||
@@ -19,7 +19,7 @@ import {Loader} from '#/components/Loader'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {OTPInput} from '../components/OTPInput'
|
||||
import {type Action, type State} from '../state'
|
||||
import {type Action, type State, useOnPressBackButton} from '../state'
|
||||
export function VerifyNumber({
|
||||
state,
|
||||
dispatch,
|
||||
@@ -121,10 +121,12 @@ export function VerifyNumber({
|
||||
[state.phoneCountryCode],
|
||||
)
|
||||
|
||||
const onPressBack = useOnPressBackButton()
|
||||
|
||||
return (
|
||||
<View style={[a.h_full]}>
|
||||
<Layout.Header.Outer noBottomBorder>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.BackButton onPress={onPressBack} />
|
||||
<Layout.Header.Content />
|
||||
{showSkipButton ? (
|
||||
<Button
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {useMemo, useRef, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import * as SMS from 'expo-sms'
|
||||
import {type ModerationOpts} from '@atproto/api'
|
||||
import {msg, Plural, Trans} from '@lingui/macro'
|
||||
@@ -19,7 +20,7 @@ import {useAgent, useSession} from '#/state/session'
|
||||
import {List, type ListMethods} from '#/view/com/util/List'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {bulkWriteFollows} from '#/screens/Onboarding/util'
|
||||
import {atoms as a, useGutters, useTheme} from '#/alf'
|
||||
import {atoms as a, tokens, useGutters, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {SearchInput} from '#/components/forms/SearchInput'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
@@ -68,16 +69,22 @@ type Item =
|
||||
export function ViewMatches({
|
||||
state,
|
||||
dispatch,
|
||||
context,
|
||||
onNext,
|
||||
}: {
|
||||
state: Extract<State, {step: '4: view matches'}>
|
||||
dispatch: React.ActionDispatch<[Action]>
|
||||
context: 'Onboarding' | 'Standalone'
|
||||
onNext: () => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const gutter = useGutters([0, 'wide'])
|
||||
const moderationOpts = useModerationOpts()
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const insets = useSafeAreaInsets()
|
||||
const listRef = useRef<ListMethods>(null)
|
||||
|
||||
// TEMP!!!
|
||||
@@ -322,13 +329,15 @@ export function ViewMatches({
|
||||
|
||||
return (
|
||||
<View style={[a.h_full]}>
|
||||
<Layout.Header.Outer noBottomBorder>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content />
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
{context === 'Standalone' && (
|
||||
<Layout.Header.Outer noBottomBorder>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content />
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
)}
|
||||
{!isTotallyEmpty && (
|
||||
<View style={[gutter, a.mb_md]}>
|
||||
<View style={[gutter, a.mb_md, context === 'Onboarding' && a.mt_sm]}>
|
||||
<SearchInput
|
||||
placeholder={_(msg`Search contacts`)}
|
||||
value={search}
|
||||
@@ -355,6 +364,29 @@ export function ViewMatches({
|
||||
ListFooterComponent={!isEmpty ? <ListFooter /> : null}
|
||||
keyExtractor={keyExtractor}
|
||||
/>
|
||||
{context === 'Onboarding' && (
|
||||
<View
|
||||
style={[
|
||||
t.atoms.bg,
|
||||
t.atoms.border_contrast_low,
|
||||
a.border_t,
|
||||
a.align_center,
|
||||
a.align_stretch,
|
||||
gutter,
|
||||
a.pt_md,
|
||||
{paddingBottom: insets.bottom + tokens.space.md},
|
||||
]}>
|
||||
<Button
|
||||
label={_(msg`Next`)}
|
||||
onPress={onNext}
|
||||
size="large"
|
||||
color="primary">
|
||||
<ButtonText>
|
||||
<Trans>Next</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {useReducer} from 'react'
|
||||
import {createContext, useContext, useReducer} from 'react'
|
||||
import {type GestureResponderEvent} from 'react-native'
|
||||
import {type ExistingContact} from 'expo-contacts'
|
||||
|
||||
import {type CountryCode} from '#/lib/international-telephone-codes'
|
||||
@@ -176,3 +177,17 @@ export function useFindContactsFlowState(
|
||||
) {
|
||||
return useReducer(reducer, initialState)
|
||||
}
|
||||
|
||||
export const FindContactsGoBackContext = createContext<
|
||||
(() => void) | undefined
|
||||
>(undefined)
|
||||
export function useOnPressBackButton() {
|
||||
const goBack = useContext(FindContactsGoBackContext)
|
||||
if (!goBack) {
|
||||
return undefined
|
||||
}
|
||||
return (evt: GestureResponderEvent) => {
|
||||
evt.preventDefault()
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import {useCallback} from 'react'
|
||||
import {LayoutAnimationConfig} from 'react-native-reanimated'
|
||||
import {SafeAreaView} from 'react-native-safe-area-context'
|
||||
|
||||
import {FindContactsFlow} from '#/components/contacts/FindContactsFlow'
|
||||
import {useFindContactsFlowState} from '#/components/contacts/state'
|
||||
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||
import {useOnboardingInternalState} from '../state'
|
||||
|
||||
export function StepFindContacts() {
|
||||
const [fcfState, fcfDispatch] = useFindContactsFlowState()
|
||||
const {dispatch} = useOnboardingInternalState()
|
||||
|
||||
const onSkip = useCallback(() => {
|
||||
dispatch({type: 'next'})
|
||||
}, [dispatch])
|
||||
|
||||
const canGoBack = fcfState.step === '2: verify number'
|
||||
const onBack = useCallback(() => {
|
||||
if (canGoBack) {
|
||||
fcfDispatch({type: 'BACK'})
|
||||
} else {
|
||||
dispatch({type: 'prev'})
|
||||
}
|
||||
}, [dispatch, fcfDispatch, canGoBack])
|
||||
|
||||
return (
|
||||
<SafeAreaView edges={['left', 'top', 'right']}>
|
||||
<LayoutAnimationConfig skipEntering skipExiting>
|
||||
<ScreenTransition key={fcfState.step} direction="Forward">
|
||||
<FindContactsFlow
|
||||
context="Onboarding"
|
||||
state={fcfState}
|
||||
dispatch={fcfDispatch}
|
||||
onCancel={onSkip}
|
||||
onBack={onBack}
|
||||
/>
|
||||
</ScreenTransition>
|
||||
</LayoutAnimationConfig>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import {useMemo, useReducer} from 'react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {View} from 'react-native'
|
||||
import * as bcp47Match from 'bcp-47-match'
|
||||
|
||||
import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
@@ -19,15 +19,17 @@ import {
|
||||
import {StepFinished} from '#/screens/Onboarding/StepFinished'
|
||||
import {StepInterests} from '#/screens/Onboarding/StepInterests'
|
||||
import {StepProfile} from '#/screens/Onboarding/StepProfile'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Portal} from '#/components/Portal'
|
||||
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||
import {ENV} from '#/env'
|
||||
import {StepFindContacts} from './StepFindContacts'
|
||||
import {StepSuggestedAccounts} from './StepSuggestedAccounts'
|
||||
import {StepSuggestedStarterpacks} from './StepSuggestedStarterpacks'
|
||||
|
||||
export function Onboarding() {
|
||||
const {_} = useLingui()
|
||||
const gate = useGate()
|
||||
const t = useTheme()
|
||||
|
||||
const {contentLanguages} = useLanguagePrefs()
|
||||
const probablySpeaksEnglish = useMemo(() => {
|
||||
@@ -52,61 +54,39 @@ export function Onboarding() {
|
||||
createInitialOnboardingState,
|
||||
)
|
||||
|
||||
const interestsDisplayNames = useMemo(() => {
|
||||
return {
|
||||
news: _(msg`News`),
|
||||
journalism: _(msg`Journalism`),
|
||||
nature: _(msg`Nature`),
|
||||
art: _(msg`Art`),
|
||||
comics: _(msg`Comics`),
|
||||
writers: _(msg`Writers`),
|
||||
culture: _(msg`Culture`),
|
||||
sports: _(msg`Sports`),
|
||||
pets: _(msg`Pets`),
|
||||
animals: _(msg`Animals`),
|
||||
books: _(msg`Books`),
|
||||
education: _(msg`Education`),
|
||||
climate: _(msg`Climate`),
|
||||
science: _(msg`Science`),
|
||||
politics: _(msg`Politics`),
|
||||
fitness: _(msg`Fitness`),
|
||||
tech: _(msg`Tech`),
|
||||
dev: _(msg`Software Dev`),
|
||||
comedy: _(msg`Comedy`),
|
||||
gaming: _(msg`Video Games`),
|
||||
food: _(msg`Food`),
|
||||
cooking: _(msg`Cooking`),
|
||||
}
|
||||
}, [_])
|
||||
useEnableKeyboardControllerScreen(true)
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<OnboardingControls.Provider>
|
||||
<OnboardingHeaderSlot.Provider>
|
||||
<Context.Provider
|
||||
value={useMemo(
|
||||
() => ({state, dispatch, interestsDisplayNames}),
|
||||
[state, dispatch, interestsDisplayNames],
|
||||
)}>
|
||||
<Layout>
|
||||
<View style={[a.absolute, a.inset_0, t.atoms.bg]}>
|
||||
<OnboardingControls.Provider>
|
||||
<OnboardingHeaderSlot.Provider>
|
||||
<Context.Provider
|
||||
value={useMemo(() => ({state, dispatch}), [state, dispatch])}>
|
||||
<ScreenTransition
|
||||
key={state.activeStep}
|
||||
direction={state.stepTransitionDirection}>
|
||||
{state.activeStep === 'profile' && <StepProfile />}
|
||||
{state.activeStep === 'interests' && <StepInterests />}
|
||||
{state.activeStep === 'suggested-accounts' && (
|
||||
<StepSuggestedAccounts />
|
||||
direction={state.stepTransitionDirection}
|
||||
style={a.flex_1}>
|
||||
{state.activeStep === 'find-contacts' ? (
|
||||
<StepFindContacts />
|
||||
) : (
|
||||
<Layout>
|
||||
{state.activeStep === 'profile' && <StepProfile />}
|
||||
{state.activeStep === 'interests' && <StepInterests />}
|
||||
{state.activeStep === 'suggested-accounts' && (
|
||||
<StepSuggestedAccounts />
|
||||
)}
|
||||
{state.activeStep === 'suggested-starterpacks' && (
|
||||
<StepSuggestedStarterpacks />
|
||||
)}
|
||||
{state.activeStep === 'finished' && <StepFinished />}
|
||||
</Layout>
|
||||
)}
|
||||
{state.activeStep === 'suggested-starterpacks' && (
|
||||
<StepSuggestedStarterpacks />
|
||||
)}
|
||||
{state.activeStep === 'find-contacts' && null}
|
||||
{state.activeStep === 'finished' && <StepFinished />}
|
||||
</ScreenTransition>
|
||||
</Layout>
|
||||
</Context.Provider>
|
||||
</OnboardingHeaderSlot.Provider>
|
||||
</OnboardingControls.Provider>
|
||||
</Context.Provider>
|
||||
</OnboardingHeaderSlot.Provider>
|
||||
</OnboardingControls.Provider>
|
||||
</View>
|
||||
</Portal>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user