Skip to content

Commit

Permalink
[Reduced Onboarding] Add profile step (#3933)
Browse files Browse the repository at this point in the history
* Onboarding avatar creator or upload (#2860)

* add screen to onboarding flow

* update base

* add icon

* fix icon

* fix after merge

* create flatlist

* add emoji list

* add state context, pressables

* select/update

* add camera icon

* add photo selection button

* image selection

* cleanup

* add most needed icons

* fix icon naming

* add icons

* export path strings for emoji

* canvas drawing for web

* types

* move breakpoints to individual steps

* create canvas

* canvas working 🎉

* update state

* it works!

* working on both platforms

* remove comments

* remove log

* remove unused web canvas

* animate picture selection/removal

* compress images on web correctly

* add times icon

* scrollable horizontal flatlist on web

* prefetch

* adjustments

* add more assets

* remove unused smiles

* add all the icons

* adjust color options

* animate grow/shrink selections

* change layout on tablet/desktop

* better web layout

* fix path

* adjust web layout

* organize

* organize imports and cleanup styles

* make generated images smaller

* implement design changes

use row for buttons on web

use RNGH FlatList

random color at start

improve logic

update dialog for web

update dialog style on mobile

some more progress

create dialog

simplify context

start implementing design

* rm change

* cleanup imports

* trigger a pr label

* Formatting

---------

Co-authored-by: Eric Bailey <[email protected]>
(cherry picked from commit 087186e)

* UI tweaks

* Revert layout change

* Gate avi upload

* Support returning to profile step

* Add Statsig

---------

Co-authored-by: Hailey <[email protected]>
Co-authored-by: Dan Abramov <[email protected]>
  • Loading branch information
3 people authored May 11, 2024
1 parent 80ce6f9 commit 4e37e2f
Show file tree
Hide file tree
Showing 17 changed files with 878 additions and 38 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@
"react-native-uitextview": "^1.1.6",
"react-native-url-polyfill": "^1.3.0",
"react-native-uuid": "^2.0.1",
"react-native-view-shot": "^3.8.0",
"react-native-web": "~0.19.6",
"react-native-web-webview": "^1.0.2",
"react-native-webview": "13.6.4",
Expand Down
3 changes: 2 additions & 1 deletion src/components/Dialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ export function Inner({children, style}: DialogInnerProps) {
return (
<BottomSheetView
style={[
a.p_xl,
a.py_xl,
a.px_xl,
{
paddingTop: 40,
borderTopLeftRadius: 40,
Expand Down
2 changes: 2 additions & 0 deletions src/lib/analytics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ export type TrackPropertiesMap = {
}
'OnboardingV2:StepModeration:Start': {}
'OnboardingV2:StepModeration:End': {}
'OnboardingV2:StepProfile:Start': {}
'OnboardingV2:StepProfile:End': {}
'OnboardingV2:StepFinished:Start': {}
'OnboardingV2:StepFinished:End': {}
'OnboardingV2:Complete': {}
Expand Down
Empty file.
1 change: 1 addition & 0 deletions src/lib/statsig/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export type LogEvents = {
selectedFeedsLength: number
}
'onboarding:moderation:nextPressed': {}
'onboarding:profile:nextPressed': {}
'onboarding:finished:nextPressed': {}
'home:feedDisplayed': {
feedUrl: string
Expand Down
2 changes: 1 addition & 1 deletion src/screens/Onboarding/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {createPortalGroup} from '#/components/Portal'
import {leading, P, Text} from '#/components/Typography'
import {IS_DEV} from '#/env'

const COL_WIDTH = 500
const COL_WIDTH = 420

export const OnboardingControls = createPortalGroup()

Expand Down
23 changes: 23 additions & 0 deletions src/screens/Onboarding/StepFinished.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {logger} from '#/logger'
import {useOverwriteSavedFeedsMutation} from '#/state/queries/preferences'
import {useAgent} from '#/state/session'
import {useOnboardingDispatch} from '#/state/shell'
import {uploadBlob} from 'lib/api'
import {
DescriptionText,
OnboardingControls,
Expand Down Expand Up @@ -46,11 +47,13 @@ export function StepFinished() {
const finishOnboarding = React.useCallback(async () => {
setSaving(true)

// TODO uncomment
const {
interestsStepResults,
suggestedAccountsStepResults,
algoFeedsStepResults,
topicalFeedsStepResults,
profileStepResults,
} = state
const {selectedInterests} = interestsStepResults
const selectedFeeds = [
Expand Down Expand Up @@ -110,6 +113,26 @@ export function StepFinished() {
}
})(),
])

if (gate('reduced_onboarding_and_home_algo')) {
await getAgent().upsertProfile(async existing => {
existing = existing ?? {}

if (profileStepResults.imageUri && profileStepResults.imageMime) {
const res = await uploadBlob(
getAgent(),
profileStepResults.imageUri,
profileStepResults.imageMime,
)

if (res.data.blob) {
existing.avatar = res.data.blob
}
}

return existing
})
}
} catch (e: any) {
logger.info(`onboarding: bulk save failed`)
logger.error(e)
Expand Down
2 changes: 1 addition & 1 deletion src/screens/Onboarding/StepInterests/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ import {Text} from '#/components/Typography'
export function StepInterests() {
const {_} = useLingui()
const t = useTheme()
const {track} = useAnalytics()
const {gtMobile} = useBreakpoints()
const {track} = useAnalytics()
const {state, dispatch, interestsDisplayNames} = React.useContext(Context)
const [saving, setSaving] = React.useState(false)
const [interests, setInterests] = React.useState<string[]>(
Expand Down
77 changes: 77 additions & 0 deletions src/screens/Onboarding/StepProfile/AvatarCircle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react'
import {View} from 'react-native'
import {Image as ExpoImage} from 'expo-image'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {AvatarCreatorCircle} from '#/screens/Onboarding/StepProfile/AvatarCreatorCircle'
import {useAvatar} from '#/screens/Onboarding/StepProfile/index'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {Pencil_Stroke2_Corner0_Rounded as Pencil} from '#/components/icons/Pencil'
import {StreamingLive_Stroke2_Corner0_Rounded as StreamingLive} from '#/components/icons/StreamingLive'

export function AvatarCircle({
openLibrary,
openCreator,
}: {
openLibrary: () => unknown
openCreator: () => unknown
}) {
const {_} = useLingui()
const t = useTheme()
const {avatar} = useAvatar()

const styles = React.useMemo(
() => ({
imageContainer: [
a.rounded_full,
a.overflow_hidden,
a.align_center,
a.justify_center,
a.border,
t.atoms.border_contrast_low,
t.atoms.bg_contrast_25,
{
height: 200,
width: 200,
},
],
}),
[t.atoms.bg_contrast_25, t.atoms.border_contrast_low],
)

return (
<View>
{avatar.useCreatedAvatar ? (
<AvatarCreatorCircle avatar={avatar} size={200} />
) : avatar.image ? (
<ExpoImage
source={avatar.image.path}
style={styles.imageContainer}
accessibilityIgnoresInvertColors
transition={{duration: 300, effect: 'cross-dissolve'}}
/>
) : (
<View style={styles.imageContainer}>
<StreamingLive
height={100}
width={100}
style={{color: t.palette.contrast_200}}
/>
</View>
)}
<View style={[a.absolute, {bottom: 2, right: 2}]}>
<Button
label={_(msg`Select an avatar`)}
size="large"
shape="round"
variant="solid"
color="primary"
onPress={avatar.useCreatedAvatar ? openCreator : openLibrary}>
<ButtonIcon icon={Pencil} />
</Button>
</View>
</View>
)
}
43 changes: 43 additions & 0 deletions src/screens/Onboarding/StepProfile/AvatarCreatorCircle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'
import {View} from 'react-native'

import {Avatar} from '#/screens/Onboarding/StepProfile/index'
import {atoms as a, useTheme} from '#/alf'

export function AvatarCreatorCircle({
avatar,
size = 125,
}: {
avatar: Avatar
size?: number
}) {
const t = useTheme()
const Icon = avatar.placeholder.component

const styles = React.useMemo(
() => ({
imageContainer: [
a.rounded_full,
a.overflow_hidden,
a.align_center,
a.justify_center,
a.border,
t.atoms.border_contrast_high,
{
height: size,
width: size,
backgroundColor: avatar.backgroundColor,
},
],
}),
[avatar.backgroundColor, size, t.atoms.border_contrast_high],
)

return (
<View>
<View style={styles.imageContainer}>
<Icon height={85} width={85} style={{color: t.palette.white}} />
</View>
</View>
)
}
145 changes: 145 additions & 0 deletions src/screens/Onboarding/StepProfile/AvatarCreatorItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {Avatar} from '#/screens/Onboarding/StepProfile/index'
import {
AvatarColor,
avatarColors,
emojiItems,
EmojiName,
emojiNames,
} from '#/screens/Onboarding/StepProfile/types'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {Text} from '#/components/Typography'

const ACTIVE_BORDER_WIDTH = 3
const ACTIVE_BORDER_STYLES = {
top: -ACTIVE_BORDER_WIDTH,
bottom: -ACTIVE_BORDER_WIDTH,
left: -ACTIVE_BORDER_WIDTH,
right: -ACTIVE_BORDER_WIDTH,
opacity: 0.5,
borderWidth: 3,
}

export function AvatarCreatorItems({
type,
avatar,
setAvatar,
}: {
type: 'emojis' | 'colors'
avatar: Avatar
setAvatar: React.Dispatch<React.SetStateAction<Avatar>>
}) {
const {_} = useLingui()
const t = useTheme()
const isEmojis = type === 'emojis'

const onSelectEmoji = React.useCallback(
(emoji: EmojiName) => {
setAvatar(prev => ({
...prev,
placeholder: emojiItems[emoji],
}))
},
[setAvatar],
)

const onSelectColor = React.useCallback(
(color: AvatarColor) => {
setAvatar(prev => ({
...prev,
backgroundColor: color,
}))
},
[setAvatar],
)

return (
<View style={[a.w_full]}>
<Text style={[a.pb_md, t.atoms.text_contrast_medium]}>
{isEmojis ? (
<Trans>Select an emoji</Trans>
) : (
<Trans>Select a color</Trans>
)}
</Text>

<View
style={[
a.flex_row,
a.align_start,
a.justify_start,
a.flex_wrap,
a.gap_md,
]}>
{isEmojis
? emojiNames.map(emojiName => (
<Button
key={emojiName}
label={_(msg`Select the ${emojiName} emoji as your avatar`)}
size="small"
shape="round"
variant="solid"
color="secondary"
onPress={() => onSelectEmoji(emojiName)}>
<ButtonIcon icon={emojiItems[emojiName].component} />
{avatar.placeholder.name === emojiName && (
<View
style={[
a.absolute,
a.rounded_full,
ACTIVE_BORDER_STYLES,
{
borderColor: avatar.backgroundColor,
},
]}
/>
)}
</Button>
))
: avatarColors.map(color => (
<Button
key={color}
label={_(msg`Choose this color as your avatar`)}
size="small"
shape="round"
variant="solid"
onPress={() => onSelectColor(color)}>
{ctx => (
<>
<View
style={[
a.absolute,
a.inset_0,
a.rounded_full,
{
opacity: ctx.hovered || ctx.pressed ? 0.8 : 1,
backgroundColor: color,
},
]}
/>

{avatar.backgroundColor === color && (
<View
style={[
a.absolute,
a.rounded_full,
ACTIVE_BORDER_STYLES,
{
borderColor: color,
},
]}
/>
)}
</>
)}
</Button>
))}
</View>
</View>
)
}
Loading

0 comments on commit 4e37e2f

Please sign in to comment.