Value prop screen - polish, convert to pager (#9133)
* turn value prop screen into a pager on native * rm ts-ingore * fix pager swipe on android --------- Co-authored-by: Eric Bailey <git@esb.lol>
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 125 KiB |
@@ -5,7 +5,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
|||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {isWeb} from '#/platform/detection'
|
import {isAndroid, isWeb} from '#/platform/detection'
|
||||||
import {useOnboardingDispatch} from '#/state/shell'
|
import {useOnboardingDispatch} from '#/state/shell'
|
||||||
import {Context} from '#/screens/Onboarding/state'
|
import {Context} from '#/screens/Onboarding/state'
|
||||||
import {
|
import {
|
||||||
@@ -59,13 +59,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
|
|||||||
aria-label={dialogLabel}
|
aria-label={dialogLabel}
|
||||||
accessibilityLabel={dialogLabel}
|
accessibilityLabel={dialogLabel}
|
||||||
accessibilityHint={_(msg`Customizes your Bluesky experience`)}
|
accessibilityHint={_(msg`Customizes your Bluesky experience`)}
|
||||||
style={[
|
style={[isWeb ? a.fixed : a.absolute, a.inset_0, a.flex_1, t.atoms.bg]}>
|
||||||
// @ts-ignore web only -prf
|
|
||||||
isWeb ? a.fixed : a.absolute,
|
|
||||||
a.inset_0,
|
|
||||||
a.flex_1,
|
|
||||||
t.atoms.bg,
|
|
||||||
]}>
|
|
||||||
{__DEV__ && (
|
{__DEV__ && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -135,7 +129,8 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
ref={scrollview}
|
ref={scrollview}
|
||||||
style={[a.h_full, a.w_full, {paddingTop: insets.top}]}
|
style={[a.h_full, a.w_full, {paddingTop: insets.top}]}
|
||||||
contentContainerStyle={{borderWidth: 0}}
|
contentContainerStyle={{borderWidth: 0, minHeight: '100%'}}
|
||||||
|
showsVerticalScrollIndicator={!isAndroid}
|
||||||
scrollIndicatorInsets={{bottom: footerHeight - insets.bottom}}
|
scrollIndicatorInsets={{bottom: footerHeight - insets.bottom}}
|
||||||
// @ts-expect-error web only --prf
|
// @ts-expect-error web only --prf
|
||||||
dataSet={{'stable-gutters': 1}}>
|
dataSet={{'stable-gutters': 1}}>
|
||||||
@@ -173,7 +168,9 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={[a.w_full, a.mb_5xl, a.pt_md]}>{children}</View>
|
<View style={[a.w_full, a.h_full, a.mb_5xl, a.pt_md]}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={{height: 100 + footerHeight}} />
|
<View style={{height: 100 + footerHeight}} />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.rounded_full,
|
||||||
|
{width: 8, height: 8},
|
||||||
|
active
|
||||||
|
? {backgroundColor: t.palette.primary_500}
|
||||||
|
: t.atoms.bg_contrast_50,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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<PagerView>(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 (
|
||||||
|
<View style={[a.h_full, {marginHorizontal: tokens.space.xl * -1}]}>
|
||||||
|
<PagerView
|
||||||
|
ref={ref}
|
||||||
|
style={[a.flex_1]}
|
||||||
|
initialPage={step}
|
||||||
|
onPageSelected={evt => {
|
||||||
|
const page = evt.nativeEvent.position as 0 | 1 | 2
|
||||||
|
if (step !== page) {
|
||||||
|
setActivePage(page)
|
||||||
|
setStep(page)
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{([0, 1, 2] as const).map(page => (
|
||||||
|
<Page
|
||||||
|
key={page}
|
||||||
|
page={page}
|
||||||
|
image={images[page]}
|
||||||
|
avatarUri={avatarUri}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</PagerView>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<View key={page}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.relative,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_center,
|
||||||
|
a.pointer_events_none,
|
||||||
|
]}>
|
||||||
|
<Image
|
||||||
|
source={image}
|
||||||
|
style={[a.w_full, a.aspect_square]}
|
||||||
|
alt={alt}
|
||||||
|
accessibilityIgnoresInvertColors={false} // I guess we do need it to blend into the background
|
||||||
|
/>
|
||||||
|
{page === 1 && (
|
||||||
|
<Image
|
||||||
|
source={avatarUri}
|
||||||
|
style={[
|
||||||
|
a.z_10,
|
||||||
|
a.absolute,
|
||||||
|
a.rounded_full,
|
||||||
|
{
|
||||||
|
width: `${(80 / 393) * 100}%`,
|
||||||
|
height: `${(80 / 393) * 100}%`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
accessibilityIgnoresInvertColors
|
||||||
|
alt={_(msg`Your profile picture`)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[a.mt_4xl, a.gap_2xl, a.px_xl, a.align_center]}>
|
||||||
|
<View style={[a.flex_row, a.gap_sm]}>
|
||||||
|
<Dot active={page === 0} />
|
||||||
|
<Dot active={page === 1} />
|
||||||
|
<Dot active={page === 2} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[a.gap_sm]}>
|
||||||
|
<Text style={[a.font_bold, a.text_3xl, a.text_center]}>{title}</Text>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
a.text_md,
|
||||||
|
a.leading_snug,
|
||||||
|
a.text_center,
|
||||||
|
]}>
|
||||||
|
{description}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<View>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.relative,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_center,
|
||||||
|
a.pointer_events_none,
|
||||||
|
]}>
|
||||||
|
<Image
|
||||||
|
source={image}
|
||||||
|
style={[a.w_full, {aspectRatio: 1}]}
|
||||||
|
alt={alt}
|
||||||
|
accessibilityIgnoresInvertColors={false} // I guess we do need it to blend into the background
|
||||||
|
/>
|
||||||
|
{step === 1 && (
|
||||||
|
<Image
|
||||||
|
source={avatarUri}
|
||||||
|
style={[
|
||||||
|
a.z_10,
|
||||||
|
a.absolute,
|
||||||
|
a.rounded_full,
|
||||||
|
{
|
||||||
|
width: `${(80 / 393) * 100}%`,
|
||||||
|
height: `${(80 / 393) * 100}%`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
accessibilityIgnoresInvertColors
|
||||||
|
alt={_(msg`Your profile picture`)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[a.mt_4xl, a.gap_2xl, a.align_center]}>
|
||||||
|
<View style={[a.flex_row, a.gap_sm]}>
|
||||||
|
<Dot active={step === 0} />
|
||||||
|
<Dot active={step === 1} />
|
||||||
|
<Dot active={step === 2} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[a.gap_sm]}>
|
||||||
|
<Text style={[a.font_bold, a.text_3xl, a.text_center]}>{title}</Text>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
a.text_md,
|
||||||
|
a.leading_snug,
|
||||||
|
a.text_center,
|
||||||
|
]}>
|
||||||
|
{description}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
import {useCallback, useContext, useState} from 'react'
|
import {useCallback, useContext, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import Animated, {
|
|
||||||
Easing,
|
|
||||||
LayoutAnimationConfig,
|
|
||||||
SlideInRight,
|
|
||||||
SlideOutLeft,
|
|
||||||
} from 'react-native-reanimated'
|
|
||||||
import {Image} from 'expo-image'
|
|
||||||
import {
|
import {
|
||||||
type AppBskyActorDefs,
|
type AppBskyActorDefs,
|
||||||
type AppBskyActorProfile,
|
type AppBskyActorProfile,
|
||||||
@@ -29,7 +22,7 @@ import {
|
|||||||
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
||||||
import {logEvent, useGate} from '#/lib/statsig/statsig'
|
import {logEvent, useGate} from '#/lib/statsig/statsig'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isWeb} from '#/platform/detection'
|
||||||
import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
|
import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
|
||||||
import {getAllListMembers} from '#/state/queries/list-members'
|
import {getAllListMembers} from '#/state/queries/list-members'
|
||||||
import {preferencesQueryKey} from '#/state/queries/preferences'
|
import {preferencesQueryKey} from '#/state/queries/preferences'
|
||||||
@@ -49,14 +42,7 @@ import {
|
|||||||
} from '#/screens/Onboarding/Layout'
|
} from '#/screens/Onboarding/Layout'
|
||||||
import {Context, type OnboardingState} from '#/screens/Onboarding/state'
|
import {Context, type OnboardingState} from '#/screens/Onboarding/state'
|
||||||
import {bulkWriteFollows} from '#/screens/Onboarding/util'
|
import {bulkWriteFollows} from '#/screens/Onboarding/util'
|
||||||
import {
|
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||||
atoms as a,
|
|
||||||
native,
|
|
||||||
platform,
|
|
||||||
tokens,
|
|
||||||
useBreakpoints,
|
|
||||||
useTheme,
|
|
||||||
} from '#/alf'
|
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {IconCircle} from '#/components/IconCircle'
|
import {IconCircle} from '#/components/IconCircle'
|
||||||
import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/icons/Arrow'
|
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 {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import * as bsky from '#/types/bsky'
|
import * as bsky from '#/types/bsky'
|
||||||
|
import {ValuePropositionPager} from './ValuePropositionPager'
|
||||||
|
|
||||||
export function StepFinished() {
|
export function StepFinished() {
|
||||||
const {state, dispatch} = useContext(Context)
|
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({
|
function ValueProposition({
|
||||||
finishOnboarding,
|
finishOnboarding,
|
||||||
saving,
|
saving,
|
||||||
@@ -312,12 +272,9 @@ function ValueProposition({
|
|||||||
state: OnboardingState
|
state: OnboardingState
|
||||||
}) {
|
}) {
|
||||||
const [subStep, setSubStep] = useState<0 | 1 | 2>(0)
|
const [subStep, setSubStep] = useState<0 | 1 | 2>(0)
|
||||||
const t = useTheme()
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
|
|
||||||
const image = [PROP_1[t.name], PROP_2[t.name], PROP_3[t.name]][subStep]
|
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
if (subStep === 2) {
|
if (subStep === 2) {
|
||||||
finishOnboarding() // has its own metrics
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{!gtMobile && (
|
{!gtMobile && (
|
||||||
@@ -382,75 +309,15 @@ function ValueProposition({
|
|||||||
</OnboardingHeaderSlot.Portal>
|
</OnboardingHeaderSlot.Portal>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<LayoutAnimationConfig skipEntering skipExiting>
|
<ValuePropositionPager
|
||||||
<Animated.View
|
step={subStep}
|
||||||
key={subStep}
|
setStep={ss => setSubStep(ss)}
|
||||||
entering={native(
|
avatarUri={state.profileStepResults.imageUri}
|
||||||
SlideInRight.easing(Easing.out(Easing.exp)).duration(500),
|
|
||||||
)}
|
|
||||||
exiting={native(
|
|
||||||
SlideOutLeft.easing(Easing.out(Easing.exp)).duration(500),
|
|
||||||
)}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.relative,
|
|
||||||
a.align_center,
|
|
||||||
a.justify_center,
|
|
||||||
isNative && {marginHorizontal: tokens.space.xl * -1},
|
|
||||||
a.pointer_events_none,
|
|
||||||
]}>
|
|
||||||
<Image
|
|
||||||
source={image}
|
|
||||||
style={[a.w_full, a.aspect_square]}
|
|
||||||
alt={alt}
|
|
||||||
accessibilityIgnoresInvertColors={false} // I guess we do need it to blend into the background
|
|
||||||
/>
|
/>
|
||||||
{subStep === 1 && (
|
|
||||||
<Image
|
|
||||||
source={state.profileStepResults.imageUri}
|
|
||||||
style={[
|
|
||||||
a.z_10,
|
|
||||||
a.absolute,
|
|
||||||
a.rounded_full,
|
|
||||||
{
|
|
||||||
width: `${(80 / 393) * 100}%`,
|
|
||||||
height: `${(80 / 393) * 100}%`,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
accessibilityIgnoresInvertColors
|
|
||||||
alt={_(msg`Your profile picture`)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.mt_4xl, a.gap_2xl, a.align_center]}>
|
|
||||||
<View style={[a.flex_row, a.gap_sm]}>
|
|
||||||
<Dot active={subStep === 0} />
|
|
||||||
<Dot active={subStep === 1} />
|
|
||||||
<Dot active={subStep === 2} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.gap_sm]}>
|
|
||||||
<Text style={[a.font_bold, a.text_3xl, a.text_center]}>
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
t.atoms.text_contrast_medium,
|
|
||||||
a.text_md,
|
|
||||||
a.leading_snug,
|
|
||||||
a.text_center,
|
|
||||||
]}>
|
|
||||||
{description}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</Animated.View>
|
|
||||||
</LayoutAnimationConfig>
|
|
||||||
|
|
||||||
<OnboardingControls.Portal>
|
<OnboardingControls.Portal>
|
||||||
<View style={gtMobile && [a.gap_md, a.flex_row]}>
|
<View style={gtMobile && [a.gap_md, a.flex_row]}>
|
||||||
{gtMobile && (
|
{gtMobile && (isWeb ? subStep !== 2 : true) && (
|
||||||
<Button
|
<Button
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -492,22 +359,6 @@ function ValueProposition({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Dot({active}: {active: boolean}) {
|
|
||||||
const t = useTheme()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.rounded_full,
|
|
||||||
{width: 8, height: 8},
|
|
||||||
active
|
|
||||||
? {backgroundColor: t.palette.primary_500}
|
|
||||||
: t.atoms.bg_contrast_50,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function LegacyFinalStep({
|
function LegacyFinalStep({
|
||||||
finishOnboarding,
|
finishOnboarding,
|
||||||
saving,
|
saving,
|
||||||