diff options
Diffstat (limited to 'src/screens')
-rw-r--r-- | src/screens/Feeds/NoFollowingFeed.tsx | 50 | ||||
-rw-r--r-- | src/screens/Feeds/NoSavedFeedsOfAnyType.tsx | 57 | ||||
-rw-r--r-- | src/screens/Home/NoFeedsPinned.tsx | 129 | ||||
-rw-r--r-- | src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx | 2 | ||||
-rw-r--r-- | src/screens/Onboarding/StepFinished.tsx | 57 |
5 files changed, 286 insertions, 9 deletions
diff --git a/src/screens/Feeds/NoFollowingFeed.tsx b/src/screens/Feeds/NoFollowingFeed.tsx new file mode 100644 index 000000000..03ced8ebd --- /dev/null +++ b/src/screens/Feeds/NoFollowingFeed.tsx @@ -0,0 +1,50 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {TIMELINE_SAVED_FEED} from '#/lib/constants' +import {useAddSavedFeedsMutation} from '#/state/queries/preferences' +import {atoms as a, useTheme} from '#/alf' +import {InlineLinkText} from '#/components/Link' +import {Text} from '#/components/Typography' + +export function NoFollowingFeed() { + const t = useTheme() + const {_} = useLingui() + const {mutateAsync: addSavedFeeds} = useAddSavedFeedsMutation() + + const addRecommendedFeeds = React.useCallback( + (e: any) => { + e.preventDefault() + + addSavedFeeds([ + { + ...TIMELINE_SAVED_FEED, + pinned: true, + }, + ]) + + // prevent navigation + return false + }, + [addSavedFeeds], + ) + + return ( + <View style={[a.flex_row, a.flex_wrap, a.align_center, a.py_md, a.px_lg]}> + <Text + style={[a.leading_snug, t.atoms.text_contrast_medium, {maxWidth: 310}]}> + <Trans>Looks like you're missing a following feed.</Trans>{' '} + </Text> + + <InlineLinkText + to="/" + label={_(msg`Add the default feed of only people you follow`)} + onPress={addRecommendedFeeds} + style={[a.leading_snug]}> + <Trans>Click here to add one.</Trans> + </InlineLinkText> + </View> + ) +} diff --git a/src/screens/Feeds/NoSavedFeedsOfAnyType.tsx b/src/screens/Feeds/NoSavedFeedsOfAnyType.tsx new file mode 100644 index 000000000..8f6bd9d2e --- /dev/null +++ b/src/screens/Feeds/NoSavedFeedsOfAnyType.tsx @@ -0,0 +1,57 @@ +import React from 'react' +import {View} from 'react-native' +import {TID} from '@atproto/common-web' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {RECOMMENDED_SAVED_FEEDS} from '#/lib/constants' +import {useOverwriteSavedFeedsMutation} from '#/state/queries/preferences' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' +import {Text} from '#/components/Typography' + +/** + * Explicitly named, since the CTA in this component will overwrite all saved + * feeds if pressed. It should only be presented to the user if they actually + * have no other feeds saved. + */ +export function NoSavedFeedsOfAnyType() { + const t = useTheme() + const {_} = useLingui() + const {isPending, mutateAsync: overwriteSavedFeeds} = + useOverwriteSavedFeedsMutation() + + const addRecommendedFeeds = React.useCallback(async () => { + await overwriteSavedFeeds( + RECOMMENDED_SAVED_FEEDS.map(f => ({ + ...f, + id: TID.nextStr(), + })), + ) + }, [overwriteSavedFeeds]) + + return ( + <View + style={[a.flex_row, a.flex_wrap, a.justify_between, a.p_xl, a.gap_md]}> + <Text + style={[a.leading_snug, t.atoms.text_contrast_medium, {maxWidth: 310}]}> + <Trans> + Looks like you haven't saved any feeds! Use our recommendations or + browse more below. + </Trans> + </Text> + + <Button + disabled={isPending} + label={_(msg`Apply default recommended feeds`)} + size="small" + variant="solid" + color="primary" + onPress={addRecommendedFeeds}> + <ButtonIcon icon={Plus} position="left" /> + <ButtonText>{_(msg`Use recommended`)}</ButtonText> + </Button> + </View> + ) +} diff --git a/src/screens/Home/NoFeedsPinned.tsx b/src/screens/Home/NoFeedsPinned.tsx new file mode 100644 index 000000000..e804e3e09 --- /dev/null +++ b/src/screens/Home/NoFeedsPinned.tsx @@ -0,0 +1,129 @@ +import React from 'react' +import {View} from 'react-native' +import {TID} from '@atproto/common-web' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {DISCOVER_SAVED_FEED, TIMELINE_SAVED_FEED} from '#/lib/constants' +import {isNative} from '#/platform/detection' +import {useOverwriteSavedFeedsMutation} from '#/state/queries/preferences' +import {UsePreferencesQueryResponse} from '#/state/queries/preferences' +import {NavigationProp} from 'lib/routes/types' +import {CenteredView} from '#/view/com/util/Views' +import {atoms as a} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {useHeaderOffset} from '#/components/hooks/useHeaderOffset' +import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle' +import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' +import {Link} from '#/components/Link' +import {Text} from '#/components/Typography' + +export function NoFeedsPinned({ + preferences, +}: { + preferences: UsePreferencesQueryResponse +}) { + const {_} = useLingui() + const headerOffset = useHeaderOffset() + const navigation = useNavigation<NavigationProp>() + const {isPending, mutateAsync: overwriteSavedFeeds} = + useOverwriteSavedFeedsMutation() + + const addRecommendedFeeds = React.useCallback(async () => { + let skippedTimeline = false + let skippedDiscover = false + let remainingSavedFeeds = [] + + // remove first instance of both timeline and discover, since we're going to overwrite them + for (const savedFeed of preferences.savedFeeds) { + if (savedFeed.type === 'timeline' && !skippedTimeline) { + skippedTimeline = true + } else if ( + savedFeed.value === DISCOVER_SAVED_FEED.value && + !skippedDiscover + ) { + skippedDiscover = true + } else { + remainingSavedFeeds.push(savedFeed) + } + } + + const toSave = [ + { + ...DISCOVER_SAVED_FEED, + pinned: true, + id: TID.nextStr(), + }, + { + ...TIMELINE_SAVED_FEED, + pinned: true, + id: TID.nextStr(), + }, + ...remainingSavedFeeds, + ] + + await overwriteSavedFeeds(toSave) + }, [overwriteSavedFeeds, preferences.savedFeeds]) + + const onPressFeedsLink = React.useCallback(() => { + if (isNative) { + // Hack that's necessary due to how our navigators are set up. + navigation.navigate('FeedsTab') + navigation.popToTop() + return false + } + }, [navigation]) + + return ( + <CenteredView sideBorders style={[a.h_full_vh]}> + <View + style={[ + a.align_center, + a.h_full_vh, + a.py_3xl, + a.px_xl, + { + paddingTop: headerOffset + a.py_3xl.paddingTop, + }, + ]}> + <View style={[a.align_center, a.gap_sm, a.pb_xl]}> + <Text style={[a.text_xl, a.font_bold]}> + <Trans>Whoops!</Trans> + </Text> + <Text + style={[a.text_md, a.text_center, a.leading_snug, {maxWidth: 340}]}> + <Trans> + Looks like you unpinned all your feeds. But don't worry, you can + add some below 😄 + </Trans> + </Text> + </View> + + <View style={[a.flex_row, a.gap_md, a.justify_center, a.flex_wrap]}> + <Button + disabled={isPending} + label={_(msg`Apply default recommended feeds`)} + size="medium" + variant="solid" + color="primary" + onPress={addRecommendedFeeds}> + <ButtonIcon icon={Plus} position="left" /> + <ButtonText>{_(msg`Add recommended feeds`)}</ButtonText> + </Button> + + <Link + label={_(msg`Browse other feeds`)} + to="/feeds" + onPress={onPressFeedsLink} + size="medium" + variant="solid" + color="secondary"> + <ButtonIcon icon={ListSparkle} position="left" /> + <ButtonText>{_(msg`Browse other feeds`)}</ButtonText> + </Link> + </View> + </View> + </CenteredView> + ) +} diff --git a/src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx b/src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx index d2b2a5f39..0aa063faa 100644 --- a/src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx +++ b/src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx @@ -2,7 +2,7 @@ import React from 'react' import {View} from 'react-native' import {Image} from 'expo-image' import {LinearGradient} from 'expo-linear-gradient' -import {Trans, msg} from '@lingui/macro' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {FeedSourceInfo, useFeedSourceInfoQuery} from '#/state/queries/feed' diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx index e7054fb1f..4cc611ef4 100644 --- a/src/screens/Onboarding/StepFinished.tsx +++ b/src/screens/Onboarding/StepFinished.tsx @@ -1,13 +1,15 @@ import React from 'react' import {View} from 'react-native' +import {TID} from '@atproto/common-web' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useAnalytics} from '#/lib/analytics/analytics' -import {BSKY_APP_ACCOUNT_DID} from '#/lib/constants' +import {BSKY_APP_ACCOUNT_DID, IS_PROD_SERVICE} from '#/lib/constants' +import {DISCOVER_SAVED_FEED, TIMELINE_SAVED_FEED} from '#/lib/constants' import {logEvent} from '#/lib/statsig/statsig' import {logger} from '#/logger' -import {useSetSaveFeedsMutation} from '#/state/queries/preferences' +import {useOverwriteSavedFeedsMutation} from '#/state/queries/preferences' import {useAgent} from '#/state/session' import {useOnboardingDispatch} from '#/state/shell' import { @@ -37,7 +39,7 @@ export function StepFinished() { const {state, dispatch} = React.useContext(Context) const onboardDispatch = useOnboardingDispatch() const [saving, setSaving] = React.useState(false) - const {mutateAsync: saveFeeds} = useSetSaveFeedsMutation() + const {mutateAsync: overwriteSavedFeeds} = useOverwriteSavedFeedsMutation() const {getAgent} = useAgent() const finishOnboarding = React.useCallback(async () => { @@ -64,10 +66,41 @@ export function StepFinished() { // these must be serial (async () => { await getAgent().setInterestsPref({tags: selectedInterests}) - await saveFeeds({ - saved: selectedFeeds, - pinned: selectedFeeds, - }) + + // TODO: In the reduced onboarding, we'll want to exit early here. + + const otherFeeds = selectedFeeds.length + ? selectedFeeds.map(f => ({ + type: 'feed', + value: f, + pinned: true, + id: TID.nextStr(), + })) + : [] + + /* + * If no selected feeds and we're in prod, add the discover feed + * (mimics old behavior) + */ + if ( + IS_PROD_SERVICE(getAgent().service.toString()) && + !otherFeeds.length + ) { + otherFeeds.push({ + ...DISCOVER_SAVED_FEED, + pinned: true, + id: TID.nextStr(), + }) + } + + await overwriteSavedFeeds([ + { + ...TIMELINE_SAVED_FEED, + pinned: true, + id: TID.nextStr(), + }, + ...otherFeeds, + ]) })(), ]) } catch (e: any) { @@ -82,7 +115,15 @@ export function StepFinished() { track('OnboardingV2:StepFinished:End') track('OnboardingV2:Complete') logEvent('onboarding:finished:nextPressed', {}) - }, [state, dispatch, onboardDispatch, setSaving, saveFeeds, track, getAgent]) + }, [ + state, + dispatch, + onboardDispatch, + setSaving, + overwriteSavedFeeds, + track, + getAgent, + ]) React.useEffect(() => { track('OnboardingV2:StepFinished:Start') |