diff --git a/assets/images/onboarding/value_prop_1_dark.webp b/assets/images/onboarding/value_prop_1_dark.webp index 259e6155b2..28dc7c18d3 100644 Binary files a/assets/images/onboarding/value_prop_1_dark.webp and b/assets/images/onboarding/value_prop_1_dark.webp differ diff --git a/assets/images/onboarding/value_prop_1_dark_borderless.webp b/assets/images/onboarding/value_prop_1_dark_borderless.webp index 5c44735f9b..733bd4ccd7 100644 Binary files a/assets/images/onboarding/value_prop_1_dark_borderless.webp and b/assets/images/onboarding/value_prop_1_dark_borderless.webp differ diff --git a/assets/images/onboarding/value_prop_1_dim.webp b/assets/images/onboarding/value_prop_1_dim.webp index 2fea1b9916..614a16f361 100644 Binary files a/assets/images/onboarding/value_prop_1_dim.webp and b/assets/images/onboarding/value_prop_1_dim.webp differ diff --git a/assets/images/onboarding/value_prop_1_dim_borderless.webp b/assets/images/onboarding/value_prop_1_dim_borderless.webp index 98c7542b44..f17dc1975e 100644 Binary files a/assets/images/onboarding/value_prop_1_dim_borderless.webp and b/assets/images/onboarding/value_prop_1_dim_borderless.webp differ diff --git a/assets/images/onboarding/value_prop_1_light.webp b/assets/images/onboarding/value_prop_1_light.webp index 13ffbf02b3..fd48f3a42f 100644 Binary files a/assets/images/onboarding/value_prop_1_light.webp and b/assets/images/onboarding/value_prop_1_light.webp differ diff --git a/assets/images/onboarding/value_prop_1_light_borderless.webp b/assets/images/onboarding/value_prop_1_light_borderless.webp index 1cf0cd032e..f1917c5f20 100644 Binary files a/assets/images/onboarding/value_prop_1_light_borderless.webp and b/assets/images/onboarding/value_prop_1_light_borderless.webp differ diff --git a/src/screens/Onboarding/Layout.tsx b/src/screens/Onboarding/Layout.tsx index c815bde844..a3627a179e 100644 --- a/src/screens/Onboarding/Layout.tsx +++ b/src/screens/Onboarding/Layout.tsx @@ -173,7 +173,9 @@ export function Layout({children}: React.PropsWithChildren<{}>) { - {children} + + {children} + diff --git a/src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx b/src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx new file mode 100644 index 0000000000..d3773880b0 --- /dev/null +++ b/src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx @@ -0,0 +1,55 @@ +import {View} from 'react-native' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {atoms as a, useTheme} from '#/alf' + +export function useValuePropText(step: 0 | 1 | 2) { + const {_} = useLingui() + + return [ + { + title: _(msg`Free your feed`), + description: _( + msg`No more doomscrolling junk-filled algorithms. Find feeds that work for you, not against you.`, + ), + alt: _( + msg`A collection of popular feeds you can find on Bluesky, including News, Booksky, Game Dev, Blacksky, and Fountain Pens`, + ), + }, + { + title: _(msg`Find your people`), + description: _( + msg`Ditch the trolls and clickbait. Find real people and conversations that matter to you.`, + ), + alt: _( + msg`Your profile picture surrounded by concentric circles of other users' profile pictures`, + ), + }, + { + title: _(msg`Forget the noise`), + description: _( + msg`No ads, no invasive tracking, no engagement traps. Bluesky respects your time and attention.`, + ), + alt: _( + msg`An illustration of several Bluesky posts alongside repost, like, and comment icons`, + ), + }, + ][step] +} + +export function Dot({active}: {active: boolean}) { + const t = useTheme() + + return ( + + ) +} diff --git a/src/screens/Onboarding/StepFinished/ValuePropositionPager.tsx b/src/screens/Onboarding/StepFinished/ValuePropositionPager.tsx new file mode 100644 index 0000000000..56d0efe114 --- /dev/null +++ b/src/screens/Onboarding/StepFinished/ValuePropositionPager.tsx @@ -0,0 +1,127 @@ +import {useRef, useState} from 'react' +import {View} from 'react-native' +import PagerView from 'react-native-pager-view' +import {Image} from 'expo-image' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {atoms as a, tokens, useTheme} from '#/alf' +import {Text} from '#/components/Typography' +import {PROP_1, PROP_2, PROP_3} from './images' +import {Dot, useValuePropText} from './ValuePropositionPager.shared' + +export function ValuePropositionPager({ + step, + setStep, + avatarUri, +}: { + step: 0 | 1 | 2 + setStep: (step: 0 | 1 | 2) => void + avatarUri?: string +}) { + const t = useTheme() + const [activePage, setActivePage] = useState(step) + const ref = useRef(null) + + if (step !== activePage) { + setActivePage(step) + ref.current?.setPage(step) + } + + const images = [PROP_1[t.name], PROP_2[t.name], PROP_3[t.name]] + + return ( + + { + const page = evt.nativeEvent.position as 0 | 1 | 2 + if (step !== page) { + setActivePage(page) + setStep(page) + } + }}> + {([0, 1, 2] as const).map(page => ( + + ))} + + + ) +} + +function Page({ + page, + image, + avatarUri, +}: { + page: 0 | 1 | 2 + image: string + avatarUri?: string +}) { + const {_} = useLingui() + const t = useTheme() + const {title, description, alt} = useValuePropText(page) + + return ( + + + {alt} + {page === 1 && ( + {_(msg`Your + )} + + + + + + + + + + + {title} + + {description} + + + + + ) +} diff --git a/src/screens/Onboarding/StepFinished/ValuePropositionPager.web.tsx b/src/screens/Onboarding/StepFinished/ValuePropositionPager.web.tsx new file mode 100644 index 0000000000..f90cec0fa3 --- /dev/null +++ b/src/screens/Onboarding/StepFinished/ValuePropositionPager.web.tsx @@ -0,0 +1,80 @@ +import {View} from 'react-native' +import {Image} from 'expo-image' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {atoms as a, useTheme} from '#/alf' +import {Text} from '#/components/Typography' +import {PROP_1, PROP_2, PROP_3} from './images' +import {Dot, useValuePropText} from './ValuePropositionPager.shared' + +export function ValuePropositionPager({ + step, + avatarUri, +}: { + step: 0 | 1 | 2 + avatarUri?: string +}) { + const t = useTheme() + const {_} = useLingui() + + const image = [PROP_1[t.name], PROP_2[t.name], PROP_3[t.name]][step] + + const {title, description, alt} = useValuePropText(step) + + return ( + + + {alt} + {step === 1 && ( + {_(msg`Your + )} + + + + + + + + + + + {title} + + {description} + + + + + ) +} diff --git a/src/screens/Onboarding/StepFinished/images.ts b/src/screens/Onboarding/StepFinished/images.ts new file mode 100644 index 0000000000..48a68a1326 --- /dev/null +++ b/src/screens/Onboarding/StepFinished/images.ts @@ -0,0 +1,28 @@ +import {platform} from '#/alf' + +export const PROP_1 = { + light: platform({ + native: require('../../../../assets/images/onboarding/value_prop_1_light.webp'), + web: require('../../../../assets/images/onboarding/value_prop_1_light_borderless.webp'), + }), + dim: platform({ + native: require('../../../../assets/images/onboarding/value_prop_1_dim.webp'), + web: require('../../../../assets/images/onboarding/value_prop_1_dim_borderless.webp'), + }), + dark: platform({ + native: require('../../../../assets/images/onboarding/value_prop_1_dark.webp'), + web: require('../../../../assets/images/onboarding/value_prop_1_dark_borderless.webp'), + }), +} as const + +export const PROP_2 = { + light: require('../../../../assets/images/onboarding/value_prop_2_light.webp'), + dim: require('../../../../assets/images/onboarding/value_prop_2_dim.webp'), + dark: require('../../../../assets/images/onboarding/value_prop_2_dark.webp'), +} as const + +export const PROP_3 = { + light: require('../../../../assets/images/onboarding/value_prop_3_light.webp'), + dim: require('../../../../assets/images/onboarding/value_prop_3_dim.webp'), + dark: require('../../../../assets/images/onboarding/value_prop_3_dark.webp'), +} as const diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished/index.tsx similarity index 73% rename from src/screens/Onboarding/StepFinished.tsx rename to src/screens/Onboarding/StepFinished/index.tsx index 3ed268a575..d2ccb9db91 100644 --- a/src/screens/Onboarding/StepFinished.tsx +++ b/src/screens/Onboarding/StepFinished/index.tsx @@ -1,12 +1,5 @@ import {useCallback, useContext, useState} from 'react' import {View} from 'react-native' -import Animated, { - Easing, - LayoutAnimationConfig, - SlideInRight, - SlideOutLeft, -} from 'react-native-reanimated' -import {Image} from 'expo-image' import { type AppBskyActorDefs, type AppBskyActorProfile, @@ -29,7 +22,7 @@ import { import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' import {logEvent, useGate} from '#/lib/statsig/statsig' import {logger} from '#/logger' -import {isNative} from '#/platform/detection' +import {isWeb} from '#/platform/detection' import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' import {getAllListMembers} from '#/state/queries/list-members' import {preferencesQueryKey} from '#/state/queries/preferences' @@ -49,14 +42,7 @@ import { } from '#/screens/Onboarding/Layout' import {Context, type OnboardingState} from '#/screens/Onboarding/state' import {bulkWriteFollows} from '#/screens/Onboarding/util' -import { - atoms as a, - native, - platform, - tokens, - useBreakpoints, - useTheme, -} from '#/alf' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {IconCircle} from '#/components/IconCircle' import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/icons/Arrow' @@ -67,6 +53,7 @@ import {Trending2_Stroke2_Corner2_Rounded as Trending} from '#/components/icons/ import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import * as bsky from '#/types/bsky' +import {ValuePropositionPager} from './ValuePropositionPager' export function StepFinished() { const {state, dispatch} = useContext(Context) @@ -275,33 +262,6 @@ export function StepFinished() { ) } -const PROP_1 = { - light: platform({ - native: require('../../../assets/images/onboarding/value_prop_1_light.webp'), - web: require('../../../assets/images/onboarding/value_prop_1_light_borderless.webp'), - }), - dim: platform({ - native: require('../../../assets/images/onboarding/value_prop_1_dim.webp'), - web: require('../../../assets/images/onboarding/value_prop_1_dim_borderless.webp'), - }), - dark: platform({ - native: require('../../../assets/images/onboarding/value_prop_1_dark.webp'), - web: require('../../../assets/images/onboarding/value_prop_1_dark_borderless.webp'), - }), -} as const - -const PROP_2 = { - light: require('../../../assets/images/onboarding/value_prop_2_light.webp'), - dim: require('../../../assets/images/onboarding/value_prop_2_dim.webp'), - dark: require('../../../assets/images/onboarding/value_prop_2_dark.webp'), -} as const - -const PROP_3 = { - light: require('../../../assets/images/onboarding/value_prop_3_light.webp'), - dim: require('../../../assets/images/onboarding/value_prop_3_dim.webp'), - dark: require('../../../assets/images/onboarding/value_prop_3_dark.webp'), -} as const - function ValueProposition({ finishOnboarding, saving, @@ -312,12 +272,9 @@ function ValueProposition({ state: OnboardingState }) { const [subStep, setSubStep] = useState<0 | 1 | 2>(0) - const t = useTheme() const {_} = useLingui() const {gtMobile} = useBreakpoints() - const image = [PROP_1[t.name], PROP_2[t.name], PROP_3[t.name]][subStep] - const onPress = () => { if (subStep === 2) { finishOnboarding() // has its own metrics @@ -330,36 +287,6 @@ function ValueProposition({ } } - const {title, description, alt} = [ - { - title: _(msg`Free your feed`), - description: _( - msg`No more doomscrolling junk-filled algorithms. Find feeds that work for you, not against you.`, - ), - alt: _( - msg`A collection of popular feeds you can find on Bluesky, including News, Booksky, Game Dev, Blacksky, and Fountain Pens`, - ), - }, - { - title: _(msg`Find your people`), - description: _( - msg`Ditch the trolls and clickbait. Find real people and conversations that matter to you.`, - ), - alt: _( - msg`Your profile picture surrounded by concentric circles of other users' profile pictures`, - ), - }, - { - title: _(msg`Forget the noise`), - description: _( - msg`No ads, no invasive tracking, no engagement traps. Bluesky respects your time and attention.`, - ), - alt: _( - msg`An illustration of several Bluesky posts alongside repost, like, and comment icons`, - ), - }, - ][subStep] - return ( <> {!gtMobile && ( @@ -382,75 +309,15 @@ function ValueProposition({ )} - - - - {alt} - {subStep === 1 && ( - {_(msg`Your - )} - - - - - - - - - - - - {title} - - - {description} - - - - - + setSubStep(ss)} + avatarUri={state.profileStepResults.imageUri} + /> - {gtMobile && ( + {gtMobile && (isWeb ? subStep !== 2 : true) && (