diff options
Diffstat (limited to 'src/screens')
-rw-r--r-- | src/screens/Onboarding/Layout.tsx | 2 | ||||
-rw-r--r-- | src/screens/Onboarding/StepAlgoFeeds/index.tsx | 20 | ||||
-rw-r--r-- | src/screens/Onboarding/StepFinished.tsx | 2 | ||||
-rw-r--r-- | src/screens/Onboarding/StepFollowingFeed.tsx | 2 | ||||
-rw-r--r-- | src/screens/Onboarding/StepInterests/InterestButton.tsx | 6 | ||||
-rw-r--r-- | src/screens/Onboarding/StepInterests/data.ts | 36 | ||||
-rw-r--r-- | src/screens/Onboarding/StepInterests/index.tsx | 13 | ||||
-rw-r--r-- | src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx | 125 | ||||
-rw-r--r-- | src/screens/Onboarding/StepModeration/ModerationOption.tsx | 49 | ||||
-rw-r--r-- | src/screens/Onboarding/StepModeration/index.tsx | 52 | ||||
-rw-r--r-- | src/screens/Onboarding/StepSuggestedAccounts/index.tsx | 16 | ||||
-rw-r--r-- | src/screens/Onboarding/StepTopicalFeeds.tsx | 10 | ||||
-rw-r--r-- | src/screens/Onboarding/index.tsx | 35 | ||||
-rw-r--r-- | src/screens/Onboarding/state.ts | 40 | ||||
-rw-r--r-- | src/screens/Onboarding/util.ts | 31 |
15 files changed, 258 insertions, 181 deletions
diff --git a/src/screens/Onboarding/Layout.tsx b/src/screens/Onboarding/Layout.tsx index 50487c189..b9683999f 100644 --- a/src/screens/Onboarding/Layout.tsx +++ b/src/screens/Onboarding/Layout.tsx @@ -173,7 +173,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) { ? a.py_2xl : { paddingTop: a.pt_lg.paddingTop, - paddingBottom: insets.bottom, + paddingBottom: insets.bottom + 10, }, ]}> <View diff --git a/src/screens/Onboarding/StepAlgoFeeds/index.tsx b/src/screens/Onboarding/StepAlgoFeeds/index.tsx index 4920c5ad7..7a87318e8 100644 --- a/src/screens/Onboarding/StepAlgoFeeds/index.tsx +++ b/src/screens/Onboarding/StepAlgoFeeds/index.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native' import {useLingui} from '@lingui/react' import {msg, Trans} from '@lingui/macro' +import {IS_PROD} from '#/env' import {atoms as a, tokens, useTheme} from '#/alf' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' import {Button, ButtonIcon, ButtonText} from '#/components/Button' @@ -27,15 +28,15 @@ export type FeedConfig = { gradient?: typeof tokens.gradients.midnight | typeof tokens.gradients.nordic } -const PRIMARY_FEEDS: FeedConfig[] = [ +export const PRIMARY_FEEDS: FeedConfig[] = [ { - default: true, - uri: 'at://did:plc:wqowuobffl66jv3kpsvo7ak4/app.bsky.feed.generator/the-algorithm', + default: IS_PROD, // these feeds are only available in prod + uri: 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot', gradient: tokens.gradients.midnight, }, { - default: false, - uri: 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot', + default: IS_PROD, // these feeds are only available in prod + uri: 'at://did:plc:wqowuobffl66jv3kpsvo7ak4/app.bsky.feed.generator/the-algorithm', gradient: tokens.gradients.midnight, }, ] @@ -99,11 +100,12 @@ export function StepAlgoFeeds() { <IconCircle icon={ListSparkle} style={[a.mb_2xl]} /> <Title> - <Trans>Choose your algorithmic feeds</Trans> + <Trans>Choose your main feeds</Trans> </Title> <Description> <Trans> - Feeds are created by users and can give you entirely new experiences. + Custom feeds built by the community bring you new experiences and help + you find the content you love. </Trans> </Description> @@ -114,12 +116,12 @@ export function StepAlgoFeeds() { label={_(msg`Select your primary algorithmic feeds`)}> <Text style={[a.text_md, a.pt_4xl, a.pb_md, t.atoms.text_contrast_700]}> - <Trans>We recommend "For You" by Skygaze:</Trans> + <Trans>We recommend our "Discover" feed:</Trans> </Text> <FeedCard config={PRIMARY_FEEDS[0]} /> <Text style={[a.text_md, a.pt_4xl, a.pb_lg, t.atoms.text_contrast_700]}> - <Trans>Or you can try our "Discover" algorithm:</Trans> + <Trans>We also think you'll like "For You" by Skygaze:</Trans> </Text> <FeedCard config={PRIMARY_FEEDS[1]} /> </Toggle.Group> diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx index 02c45f590..af73c6fc1 100644 --- a/src/screens/Onboarding/StepFinished.tsx +++ b/src/screens/Onboarding/StepFinished.tsx @@ -116,7 +116,7 @@ export function StepFinished() { </Text> <Text style={[t.atoms.text_contrast_500, a.text_md, a.leading_snug]}> - <Trans>Never lose access to your followers and data.</Trans> + <Trans>Never lose access to your followers or data.</Trans> </Text> </View> </View> diff --git a/src/screens/Onboarding/StepFollowingFeed.tsx b/src/screens/Onboarding/StepFollowingFeed.tsx index 4b3c62889..114e274b6 100644 --- a/src/screens/Onboarding/StepFollowingFeed.tsx +++ b/src/screens/Onboarding/StepFollowingFeed.tsx @@ -61,7 +61,7 @@ export function StepFollowingFeed() { <Trans>Your default feed is "Following"</Trans> </Title> <Description style={[a.mb_md]}> - <Trans>It show posts from the people your follow as they happen.</Trans> + <Trans>It shows posts from the people you follow as they happen.</Trans> </Description> <View style={[a.w_full]}> diff --git a/src/screens/Onboarding/StepInterests/InterestButton.tsx b/src/screens/Onboarding/StepInterests/InterestButton.tsx index 02413b18d..cc692dafd 100644 --- a/src/screens/Onboarding/StepInterests/InterestButton.tsx +++ b/src/screens/Onboarding/StepInterests/InterestButton.tsx @@ -4,11 +4,13 @@ import {View, ViewStyle, TextStyle} from 'react-native' import {useTheme, atoms as a, native} from '#/alf' import * as Toggle from '#/components/forms/Toggle' import {Text} from '#/components/Typography' +import {capitalize} from '#/lib/strings/capitalize' -import {INTEREST_TO_DISPLAY_NAME} from '#/screens/Onboarding/StepInterests/data' +import {Context} from '#/screens/Onboarding/state' export function InterestButton({interest}: {interest: string}) { const t = useTheme() + const {interestsDisplayNames} = React.useContext(Context) const ctx = Toggle.useItemContext() const styles = React.useMemo(() => { @@ -72,7 +74,7 @@ export function InterestButton({interest}: {interest: string}) { native({paddingTop: 2}), ctx.selected ? styles.textSelected : {}, ]}> - {INTEREST_TO_DISPLAY_NAME[interest]} + {interestsDisplayNames[interest] || capitalize(interest)} </Text> </View> ) diff --git a/src/screens/Onboarding/StepInterests/data.ts b/src/screens/Onboarding/StepInterests/data.ts deleted file mode 100644 index 00a25331c..000000000 --- a/src/screens/Onboarding/StepInterests/data.ts +++ /dev/null @@ -1,36 +0,0 @@ -export const INTEREST_TO_DISPLAY_NAME: { - [key: string]: string -} = { - news: 'News', - journalism: 'Journalism', - nature: 'Nature', - art: 'Art', - comics: 'Comics', - writers: 'Writers', - culture: 'Culture', - sports: 'Sports', - pets: 'Pets', - animals: 'Animals', - books: 'Books', - education: 'Education', - climate: 'Climate', - science: 'Science', - politics: 'Politics', - fitness: 'Fitness', - tech: 'Tech', - dev: 'Software Dev', - comedy: 'Comedy', - gaming: 'Video Games', - food: 'Food', - cooking: 'Cooking', -} - -export type ApiResponseMap = { - interests: string[] - suggestedAccountDids: { - [key: string]: string[] - } - suggestedFeedUris: { - [key: string]: string[] - } -} diff --git a/src/screens/Onboarding/StepInterests/index.tsx b/src/screens/Onboarding/StepInterests/index.tsx index 6f60991d5..5440dcd2b 100644 --- a/src/screens/Onboarding/StepInterests/index.tsx +++ b/src/screens/Onboarding/StepInterests/index.tsx @@ -17,17 +17,14 @@ import {getAgent} from '#/state/session' import {useAnalytics} from '#/lib/analytics/analytics' import {Text} from '#/components/Typography' import {useOnboardingDispatch} from '#/state/shell' +import {capitalize} from '#/lib/strings/capitalize' -import {Context} from '#/screens/Onboarding/state' +import {Context, ApiResponseMap} from '#/screens/Onboarding/state' import { Title, Description, OnboardingControls, } from '#/screens/Onboarding/Layout' -import { - ApiResponseMap, - INTEREST_TO_DISPLAY_NAME, -} from '#/screens/Onboarding/StepInterests/data' import {InterestButton} from '#/screens/Onboarding/StepInterests/InterestButton' import {IconCircle} from '#/screens/Onboarding/IconCircle' @@ -36,7 +33,7 @@ export function StepInterests() { const t = useTheme() const {track} = useAnalytics() const {gtMobile} = useBreakpoints() - const {state, dispatch} = React.useContext(Context) + const {state, dispatch, interestsDisplayNames} = React.useContext(Context) const [saving, setSaving] = React.useState(false) const [interests, setInterests] = React.useState<string[]>( state.interestsStepResults.selectedInterests.map(i => i), @@ -202,7 +199,9 @@ export function StepInterests() { <Toggle.Item key={interest} name={interest} - label={INTEREST_TO_DISPLAY_NAME[interest]}> + label={ + interestsDisplayNames[interest] || capitalize(interest) + }> <InterestButton interest={interest} /> </Toggle.Item> ))} diff --git a/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx b/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx index bc4c0387f..6b456de80 100644 --- a/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx +++ b/src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx @@ -2,19 +2,17 @@ import React from 'react' import {View} from 'react-native' import {useLingui} from '@lingui/react' import {msg, Trans} from '@lingui/macro' +import {UseMutateFunction} from '@tanstack/react-query' -import {isIOS} from '#/platform/detection' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' -import { - usePreferencesQuery, - usePreferencesSetAdultContentMutation, -} from '#/state/queries/preferences' +import {usePreferencesQuery} from '#/state/queries/preferences' import {logger} from '#/logger' import {Text} from '#/components/Typography' -import {InlineLink} from '#/components/Link' import * as Toggle from '#/components/forms/Toggle' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' +import * as Prompt from '#/components/Prompt' +import {isIOS} from '#/platform/detection' function Card({children}: React.PropsWithChildren<{}>) { const t = useTheme() @@ -36,16 +34,25 @@ function Card({children}: React.PropsWithChildren<{}>) { ) } -export function AdultContentEnabledPref() { +export function AdultContentEnabledPref({ + mutate, + variables, +}: { + mutate: UseMutateFunction<void, unknown, {enabled: boolean}, unknown> + variables: {enabled: boolean} | undefined +}) { const {_} = useLingui() const t = useTheme() + const prompt = Prompt.usePromptControl() // Reuse logic here form ContentFilteringSettings.tsx const {data: preferences} = usePreferencesQuery() - const {mutate, variables} = usePreferencesSetAdultContentMutation() const onToggleAdultContent = React.useCallback(async () => { - if (isIOS) return + if (isIOS) { + prompt.open() + return + } try { mutate({ @@ -57,15 +64,33 @@ export function AdultContentEnabledPref() { ) logger.error('Failed to update preferences with server', {error: e}) } - }, [variables, preferences, mutate, _]) + }, [variables, preferences, mutate, _, prompt]) if (!preferences) return null - if (isIOS) { - if (preferences?.adultContentEnabled === true) { - return null - } else { - return ( + return ( + <> + {preferences.userAge && preferences.userAge >= 18 ? ( + <View style={[a.w_full, a.px_xs]}> + <Toggle.Item + name={_(msg`Enable adult content in your feeds`)} + label={_(msg`Enable adult content in your feeds`)} + value={variables?.enabled ?? preferences?.adultContentEnabled} + onChange={onToggleAdultContent}> + <View + style={[ + a.flex_row, + a.w_full, + a.justify_between, + a.align_center, + a.py_md, + ]}> + <Text style={[a.font_bold]}>Enable Adult Content</Text> + <Toggle.Switch /> + </View> + </Toggle.Item> + </View> + ) : ( <Card> <CircleInfo size="sm" fill={t.palette.contrast_500} /> <Text @@ -75,61 +100,23 @@ export function AdultContentEnabledPref() { a.leading_snug, {paddingTop: 1}, ]}> - <Trans> - Adult content can only be enabled via the Web at{' '} - <InlineLink style={[a.leading_snug]} to="https://bsky.app"> - bsky.app - </InlineLink> - . - </Trans> + <Trans>You must be 18 years or older to enable adult content</Trans> </Text> </Card> - ) - } - } else { - if (preferences?.userAge) { - if (preferences.userAge >= 18) { - return ( - <View style={[a.w_full]}> - <Toggle.Item - name={_(msg`Enable adult content in your feeds`)} - label={_(msg`Enable adult content in your feeds`)} - value={variables?.enabled ?? preferences?.adultContentEnabled} - onChange={onToggleAdultContent}> - <View - style={[ - a.flex_row, - a.w_full, - a.justify_between, - a.align_center, - a.py_md, - ]}> - <Text style={[a.font_bold]}>Enable Adult Content</Text> - <Toggle.Switch /> - </View> - </Toggle.Item> - </View> - ) - } else { - return ( - <Card> - <CircleInfo size="sm" fill={t.palette.contrast_500} /> - <Text - style={[ - a.flex_1, - t.atoms.text_contrast_700, - a.leading_snug, - {paddingTop: 1}, - ]}> - <Trans> - You must be 18 years or older to enable adult content - </Trans> - </Text> - </Card> - ) - } - } + )} - return null - } + <Prompt.Outer control={prompt}> + <Prompt.Title>Adult Content</Prompt.Title> + <Prompt.Description> + <Trans> + Due to Apple policies, adult content can only be enabled on the web + after completing sign up. + </Trans> + </Prompt.Description> + <Prompt.Actions> + <Prompt.Action onPress={prompt.close}>OK</Prompt.Action> + </Prompt.Actions> + </Prompt.Outer> + </> + ) } diff --git a/src/screens/Onboarding/StepModeration/ModerationOption.tsx b/src/screens/Onboarding/StepModeration/ModerationOption.tsx index 904c47299..d216692d0 100644 --- a/src/screens/Onboarding/StepModeration/ModerationOption.tsx +++ b/src/screens/Onboarding/StepModeration/ModerationOption.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native' import {LabelPreference} from '@atproto/api' import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' +import Animated, {Easing, Layout, FadeIn} from 'react-native-reanimated' import { CONFIGURABLE_LABEL_GROUPS, @@ -16,8 +17,10 @@ import * as ToggleButton from '#/components/forms/ToggleButton' export function ModerationOption({ labelGroup, + isMounted, }: { labelGroup: ConfigurableLabelGroup + isMounted: React.MutableRefObject<boolean> }) { const {_} = useLingui() const t = useTheme() @@ -41,7 +44,7 @@ export function ModerationOption({ } return ( - <View + <Animated.View style={[ a.flex_row, a.justify_between, @@ -49,7 +52,9 @@ export function ModerationOption({ a.py_xs, a.px_xs, a.align_center, - ]}> + ]} + layout={Layout.easing(Easing.ease).duration(200)} + entering={isMounted.current ? FadeIn : undefined}> <View style={[a.gap_xs, {width: '50%'}]}> <Text style={[a.font_bold]}>{groupInfo.title}</Text> <Text style={[t.atoms.text_contrast_700, a.leading_snug]}> @@ -57,29 +62,23 @@ export function ModerationOption({ </Text> </View> <View style={[a.justify_center, {minHeight: 35}]}> - {!preferences?.adultContentEnabled && groupInfo.isAdultImagery ? ( - <View style={[a.justify_center, {minHeight: 40}]}> - <Text style={[a.font_bold]}>{labels.hide}</Text> - </View> - ) : ( - <ToggleButton.Group - label={_( - msg`Configure content filtering setting for category: ${groupInfo.title.toLowerCase()}`, - )} - values={[visibility ?? 'hide']} - onChange={onChange}> - <ToggleButton.Button name="hide" label={labels.hide}> - {labels.hide} - </ToggleButton.Button> - <ToggleButton.Button name="warn" label={labels.warn}> - {labels.warn} - </ToggleButton.Button> - <ToggleButton.Button name="ignore" label={labels.show}> - {labels.show} - </ToggleButton.Button> - </ToggleButton.Group> - )} + <ToggleButton.Group + label={_( + msg`Configure content filtering setting for category: ${groupInfo.title.toLowerCase()}`, + )} + values={[visibility ?? 'hide']} + onChange={onChange}> + <ToggleButton.Button name="hide" label={labels.hide}> + {labels.hide} + </ToggleButton.Button> + <ToggleButton.Button name="warn" label={labels.warn}> + {labels.warn} + </ToggleButton.Button> + <ToggleButton.Button name="ignore" label={labels.show}> + {labels.show} + </ToggleButton.Button> + </ToggleButton.Group> </View> - </View> + </Animated.View> ) } diff --git a/src/screens/Onboarding/StepModeration/index.tsx b/src/screens/Onboarding/StepModeration/index.tsx index be605e407..c831b6880 100644 --- a/src/screens/Onboarding/StepModeration/index.tsx +++ b/src/screens/Onboarding/StepModeration/index.tsx @@ -2,9 +2,14 @@ import React from 'react' import {View} from 'react-native' import {useLingui} from '@lingui/react' import {msg, Trans} from '@lingui/macro' +import Animated, {Easing, Layout} from 'react-native-reanimated' import {atoms as a} from '#/alf' -import {configurableLabelGroups} from 'state/queries/preferences' +import { + configurableAdultLabelGroups, + configurableOtherLabelGroups, + usePreferencesSetAdultContentMutation, +} from 'state/queries/preferences' import {Divider} from '#/components/Divider' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' @@ -23,11 +28,32 @@ import {AdultContentEnabledPref} from '#/screens/Onboarding/StepModeration/Adult import {Context} from '#/screens/Onboarding/state' import {IconCircle} from '#/screens/Onboarding/IconCircle' +function AnimatedDivider() { + return ( + <Animated.View layout={Layout.easing(Easing.ease).duration(200)}> + <Divider /> + </Animated.View> + ) +} + export function StepModeration() { const {_} = useLingui() const {track} = useAnalytics() const {state, dispatch} = React.useContext(Context) const {data: preferences} = usePreferencesQuery() + const {mutate, variables} = usePreferencesSetAdultContentMutation() + + // We need to know if the screen is mounted so we know if we want to run entering animations + // https://github.com/software-mansion/react-native-reanimated/discussions/2513 + const isMounted = React.useRef(false) + React.useLayoutEffect(() => { + isMounted.current = true + }, []) + + const adultContentEnabled = !!( + (variables && variables.enabled) || + (!variables && preferences?.adultContentEnabled) + ) const onContinue = React.useCallback(() => { dispatch({type: 'next'}) @@ -43,12 +69,11 @@ export function StepModeration() { <IconCircle icon={EyeSlash} style={[a.mb_2xl]} /> <Title> - <Trans>You are in control</Trans> + <Trans>You're in control</Trans> </Title> <Description style={[a.mb_xl]}> <Trans> - Select the types of content that you want to see (or not see), and - we'll handle the rest. + Select what you want to see (or not see), and we’ll handle the rest. </Trans> </Description> @@ -58,14 +83,23 @@ export function StepModeration() { </View> ) : ( <> - <AdultContentEnabledPref /> + <AdultContentEnabledPref mutate={mutate} variables={variables} /> <View style={[a.gap_sm, a.w_full]}> - {configurableLabelGroups.map((g, index) => ( + {adultContentEnabled && + configurableAdultLabelGroups.map((g, index) => ( + <React.Fragment key={index}> + {index === 0 && <AnimatedDivider />} + <ModerationOption labelGroup={g} isMounted={isMounted} /> + <AnimatedDivider /> + </React.Fragment> + ))} + + {configurableOtherLabelGroups.map((g, index) => ( <React.Fragment key={index}> - {index === 0 && <Divider />} - <ModerationOption labelGroup={g} /> - <Divider /> + {!adultContentEnabled && index === 0 && <AnimatedDivider />} + <ModerationOption labelGroup={g} isMounted={isMounted} /> + <AnimatedDivider /> </React.Fragment> ))} </View> diff --git a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx index 723e53a98..965dae334 100644 --- a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx +++ b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx @@ -14,6 +14,7 @@ import {Loader} from '#/components/Loader' import * as Toggle from '#/components/forms/Toggle' import {useModerationOpts} from '#/state/queries/preferences' import {useAnalytics} from '#/lib/analytics/analytics' +import {capitalize} from '#/lib/strings/capitalize' import {Context} from '#/screens/Onboarding/state' import { @@ -25,7 +26,6 @@ import { SuggestedAccountCard, SuggestedAccountCardPlaceholder, } from '#/screens/Onboarding/StepSuggestedAccounts/SuggestedAccountCard' -import {INTEREST_TO_DISPLAY_NAME} from '#/screens/Onboarding/StepInterests/data' import {aggregateInterestItems} from '#/screens/Onboarding/util' import {IconCircle} from '#/screens/Onboarding/IconCircle' @@ -70,7 +70,7 @@ export function Inner({ export function StepSuggestedAccounts() { const {_} = useLingui() const {track} = useAnalytics() - const {state, dispatch} = React.useContext(Context) + const {state, dispatch, interestsDisplayNames} = React.useContext(Context) const {gtMobile} = useBreakpoints() const suggestedDids = React.useMemo(() => { return aggregateInterestItems( @@ -93,10 +93,10 @@ export function StepSuggestedAccounts() { const interestsText = React.useMemo(() => { const i = state.interestsStepResults.selectedInterests.map( - i => INTEREST_TO_DISPLAY_NAME[i], + i => interestsDisplayNames[i] || capitalize(i), ) return i.join(', ') - }, [state.interestsStepResults.selectedInterests]) + }, [state.interestsStepResults.selectedInterests, interestsDisplayNames]) const handleContinue = React.useCallback(async () => { setSaving(true) @@ -107,7 +107,7 @@ export function StepSuggestedAccounts() { setSaving(false) dispatch({type: 'next'}) - track('OnboardingV2:StepSuggestedAccounts:Start', { + track('OnboardingV2:StepSuggestedAccounts:End', { selectedAccountsLength: dids.length, }) }, [dids, setSaving, dispatch, track]) @@ -129,13 +129,13 @@ export function StepSuggestedAccounts() { <IconCircle icon={At} style={[a.mb_2xl]} /> <Title> - <Trans>Here are some accounts for your to follow</Trans> + <Trans>Here are some accounts for you to follow</Trans> </Title> <Description> {state.interestsStepResults.selectedInterests.length ? ( <Trans>Based on your interest in {interestsText}</Trans> ) : ( - <Trans>These are popular accounts you might like.</Trans> + <Trans>These are popular accounts you might like:</Trans> )} </Description> @@ -171,7 +171,7 @@ export function StepSuggestedAccounts() { color="gradient_sky" size="large" label={_( - msg`Follow selected accounts and continue to then next step`, + msg`Follow selected accounts and continue to the next step`, )} onPress={handleContinue}> <ButtonText> diff --git a/src/screens/Onboarding/StepTopicalFeeds.tsx b/src/screens/Onboarding/StepTopicalFeeds.tsx index 516c18e6e..3640b764d 100644 --- a/src/screens/Onboarding/StepTopicalFeeds.tsx +++ b/src/screens/Onboarding/StepTopicalFeeds.tsx @@ -3,6 +3,7 @@ import {View} from 'react-native' import {useLingui} from '@lingui/react' import {msg, Trans} from '@lingui/macro' +import {IS_PROD} from '#/env' import {atoms as a} from '#/alf' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' import {ListMagnifyingGlass_Stroke2_Corner0_Rounded as ListMagnifyingGlass} from '#/components/icons/ListMagnifyingGlass' @@ -10,6 +11,7 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Toggle from '#/components/forms/Toggle' import {Loader} from '#/components/Loader' import {useAnalytics} from '#/lib/analytics/analytics' +import {capitalize} from '#/lib/strings/capitalize' import {Context} from '#/screens/Onboarding/state' import { @@ -18,17 +20,17 @@ import { OnboardingControls, } from '#/screens/Onboarding/Layout' import {FeedCard} from '#/screens/Onboarding/StepAlgoFeeds/FeedCard' -import {INTEREST_TO_DISPLAY_NAME} from '#/screens/Onboarding/StepInterests/data' import {aggregateInterestItems} from '#/screens/Onboarding/util' import {IconCircle} from '#/screens/Onboarding/IconCircle' export function StepTopicalFeeds() { const {_} = useLingui() const {track} = useAnalytics() - const {state, dispatch} = React.useContext(Context) + const {state, dispatch, interestsDisplayNames} = React.useContext(Context) const [selectedFeedUris, setSelectedFeedUris] = React.useState<string[]>([]) const [saving, setSaving] = React.useState(false) const suggestedFeedUris = React.useMemo(() => { + if (!IS_PROD) return [] return aggregateInterestItems( state.interestsStepResults.selectedInterests, state.interestsStepResults.apiResponse.suggestedFeedUris, @@ -38,10 +40,10 @@ export function StepTopicalFeeds() { const interestsText = React.useMemo(() => { const i = state.interestsStepResults.selectedInterests.map( - i => INTEREST_TO_DISPLAY_NAME[i], + i => interestsDisplayNames[i] || capitalize(i), ) return i.join(', ') - }, [state.interestsStepResults.selectedInterests]) + }, [state.interestsStepResults.selectedInterests, interestsDisplayNames]) const saveFeeds = React.useCallback(async () => { setSaving(true) diff --git a/src/screens/Onboarding/index.tsx b/src/screens/Onboarding/index.tsx index a4eb04012..9e5029e87 100644 --- a/src/screens/Onboarding/index.tsx +++ b/src/screens/Onboarding/index.tsx @@ -1,4 +1,6 @@ import React from 'react' +import {useLingui} from '@lingui/react' +import {msg} from '@lingui/macro' import {Portal} from '#/components/Portal' @@ -13,13 +15,44 @@ import {StepFinished} from '#/screens/Onboarding/StepFinished' import {StepModeration} from '#/screens/Onboarding/StepModeration' export function Onboarding() { + const {_} = useLingui() const [state, dispatch] = React.useReducer(reducer, {...initialState}) + const interestsDisplayNames = React.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`), + } + }, [_]) + return ( <Portal> <OnboardingControls.Provider> <Context.Provider - value={React.useMemo(() => ({state, dispatch}), [state, dispatch])}> + value={React.useMemo( + () => ({state, dispatch, interestsDisplayNames}), + [state, dispatch, interestsDisplayNames], + )}> <Layout> {state.activeStep === 'interests' && <StepInterests />} {state.activeStep === 'suggestedAccounts' && ( diff --git a/src/screens/Onboarding/state.ts b/src/screens/Onboarding/state.ts index 164c2f5f3..bd8205ca2 100644 --- a/src/screens/Onboarding/state.ts +++ b/src/screens/Onboarding/state.ts @@ -1,6 +1,5 @@ import React from 'react' -import {ApiResponseMap} from '#/screens/Onboarding/StepInterests/data' import {logger} from '#/logger' export type OnboardingState = { @@ -59,6 +58,16 @@ export type OnboardingAction = feedUris: string[] } +export type ApiResponseMap = { + interests: string[] + suggestedAccountDids: { + [key: string]: string[] + } + suggestedFeedUris: { + [key: string]: string[] + } +} + export const initialState: OnboardingState = { hasPrev: false, totalSteps: 7, @@ -84,12 +93,41 @@ export const initialState: OnboardingState = { }, } +export const INTEREST_TO_DISPLAY_NAME_DEFAULTS: { + [key: string]: string +} = { + news: 'News', + journalism: 'Journalism', + nature: 'Nature', + art: 'Art', + comics: 'Comics', + writers: 'Writers', + culture: 'Culture', + sports: 'Sports', + pets: 'Pets', + animals: 'Animals', + books: 'Books', + education: 'Education', + climate: 'Climate', + science: 'Science', + politics: 'Politics', + fitness: 'Fitness', + tech: 'Tech', + dev: 'Software Dev', + comedy: 'Comedy', + gaming: 'Video Games', + food: 'Food', + cooking: 'Cooking', +} + export const Context = React.createContext<{ state: OnboardingState dispatch: React.Dispatch<OnboardingAction> + interestsDisplayNames: {[key: string]: string} }>({ state: {...initialState}, dispatch: () => {}, + interestsDisplayNames: INTEREST_TO_DISPLAY_NAME_DEFAULTS, }) export function reducer( diff --git a/src/screens/Onboarding/util.ts b/src/screens/Onboarding/util.ts index 2a709a67b..1a0b8d21b 100644 --- a/src/screens/Onboarding/util.ts +++ b/src/screens/Onboarding/util.ts @@ -2,6 +2,7 @@ import {AppBskyGraphFollow, AppBskyGraphGetFollows} from '@atproto/api' import {until} from '#/lib/async/until' import {getAgent} from '#/state/session' +import {PRIMARY_FEEDS} from './StepAlgoFeeds' function shuffle(array: any) { let currentIndex = array.length, @@ -31,7 +32,15 @@ export function aggregateInterestItems( const selected = interests.length const all = interests .map(i => { - const suggestions = shuffle(map[i]) + // suggestions from server + const rawSuggestions = map[i] + + // safeguard against a missing interest->suggestion mapping + if (!rawSuggestions || !rawSuggestions.length) { + return [] + } + + const suggestions = shuffle(rawSuggestions) if (selected === 1) { return suggestions // return all @@ -102,11 +111,19 @@ async function whenFollowsIndexed( * feed after Following */ export function sortPrimaryAlgorithmFeeds(uris: string[]) { - return uris.sort(uri => { - return uri.includes('the-algorithm') - ? -1 - : uri.includes('whats-hot') - ? 0 - : 1 + return uris.sort((a, b) => { + if (a === PRIMARY_FEEDS[0].uri) { + return -1 + } + if (b === PRIMARY_FEEDS[0].uri) { + return 1 + } + if (a === PRIMARY_FEEDS[1].uri) { + return -1 + } + if (b === PRIMARY_FEEDS[1].uri) { + return 1 + } + return a.localeCompare(b) }) } |