Skip to content

Commit

Permalink
Onboarding avatar creator or upload (#2860)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
haileyok and estrattonbailey committed Apr 25, 2024
1 parent 282ad4b commit 087186e
Show file tree
Hide file tree
Showing 21 changed files with 904 additions and 20 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,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 @@ -207,7 +207,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 @@ -149,6 +149,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.
3 changes: 1 addition & 2 deletions src/screens/Onboarding/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
contentContainerStyle={{borderWidth: 0}}
// @ts-ignore web only --prf
dataSet={{'stable-gutters': 1}}>
<View
style={[a.flex_row, a.justify_center, gtMobile ? a.px_5xl : a.px_xl]}>
<View style={[a.flex_row, a.justify_center]}>
<View style={[a.flex_1, {maxWidth: COL_WIDTH}]}>
<View style={[a.w_full, a.align_center, paddingTop]}>
<View
Expand Down
5 changes: 3 additions & 2 deletions src/screens/Onboarding/StepAlgoFeeds/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '#/screens/Onboarding/Layout'
import {Context} from '#/screens/Onboarding/state'
import {FeedCard} from '#/screens/Onboarding/StepAlgoFeeds/FeedCard'
import {atoms as a, tokens, useTheme} from '#/alf'
import {atoms as a, tokens, useTheme, useBreakpoints} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Toggle from '#/components/forms/Toggle'
import {IconCircle} from '#/components/IconCircle'
Expand Down Expand Up @@ -63,6 +63,7 @@ export function StepAlgoFeeds() {
const {_} = useLingui()
const {track} = useAnalytics()
const t = useTheme()
const {gtMobile} = useBreakpoints()
const {state, dispatch} = React.useContext(Context)
const [primaryFeedUris, setPrimaryFeedUris] = React.useState<string[]>(
PRIMARY_FEEDS.map(f => (f.default ? f.uri : '')).filter(Boolean),
Expand Down Expand Up @@ -97,7 +98,7 @@ export function StepAlgoFeeds() {
}, [track])

return (
<View style={[a.align_start]}>
<View style={[a.align_start, gtMobile ? a.px_5xl : a.px_xl]}>
<IconCircle icon={ListSparkle} style={[a.mb_2xl]} />

<TitleText>
Expand Down
26 changes: 24 additions & 2 deletions src/screens/Onboarding/StepFinished.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ import {
OnboardingControls,
TitleText,
} from '#/screens/Onboarding/Layout'
import {uploadBlob} from 'lib/api'
import {Context} from '#/screens/Onboarding/state'
import {
bulkWriteFollows,
sortPrimaryAlgorithmFeeds,
} from '#/screens/Onboarding/util'
import {atoms as a, useTheme} from '#/alf'
import {atoms as a, useTheme, useBreakpoints} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {IconCircle} from '#/components/IconCircle'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
Expand All @@ -33,6 +34,7 @@ import {Text} from '#/components/Typography'
export function StepFinished() {
const {_} = useLingui()
const t = useTheme()
const {gtMobile} = useBreakpoints()
const {track} = useAnalytics()
const {state, dispatch} = React.useContext(Context)
const onboardDispatch = useOnboardingDispatch()
Expand All @@ -42,11 +44,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 All @@ -68,6 +72,24 @@ export function StepFinished() {
})
})(),
])

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 All @@ -87,7 +109,7 @@ export function StepFinished() {
}, [track])

return (
<View style={[a.align_start]}>
<View style={[a.align_start, gtMobile ? a.px_5xl : a.px_xl]}>
<IconCircle icon={Check} style={[a.mb_2xl]} />

<TitleText>
Expand Down
5 changes: 3 additions & 2 deletions src/screens/Onboarding/StepFollowingFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
TitleText,
} from '#/screens/Onboarding/Layout'
import {Context} from '#/screens/Onboarding/state'
import {atoms as a} from '#/alf'
import {atoms as a, useBreakpoints} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Divider} from '#/components/Divider'
import * as Toggle from '#/components/forms/Toggle'
Expand All @@ -26,6 +26,7 @@ import {Text} from '#/components/Typography'

export function StepFollowingFeed() {
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const {track} = useAnalytics()
const {dispatch} = React.useContext(Context)

Expand Down Expand Up @@ -55,7 +56,7 @@ export function StepFollowingFeed() {

return (
// Hack for now to move the image container up
<View style={[a.align_start]}>
<View style={[a.align_start, gtMobile ? a.px_5xl : a.px_xl]}>
<IconCircle icon={FilterTimeline} style={[a.mb_2xl]} />

<TitleText>
Expand Down
6 changes: 4 additions & 2 deletions 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 Expand Up @@ -143,7 +143,9 @@ export function StepInterests() {
)

return (
<View style={[a.align_start]} testID="onboardingInterests">
<View
style={[a.align_start, gtMobile ? a.px_5xl : a.px_xl]}
testID="onboardingInterests">
<IconCircle
icon={isError ? EmojiSad : Hashtag}
style={[
Expand Down
5 changes: 3 additions & 2 deletions src/screens/Onboarding/StepModeration/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {LABELS} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {atoms as a, useBreakpoints} from '#/alf'
import {useAnalytics} from '#/lib/analytics/analytics'
import {logEvent} from '#/lib/statsig/statsig'
import {usePreferencesQuery} from '#/state/queries/preferences'
Expand All @@ -16,7 +17,6 @@ import {
import {Context} from '#/screens/Onboarding/state'
import {AdultContentEnabledPref} from '#/screens/Onboarding/StepModeration/AdultContentEnabledPref'
import {ModerationOption} from '#/screens/Onboarding/StepModeration/ModerationOption'
import {atoms as a} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {IconCircle} from '#/components/IconCircle'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
Expand All @@ -25,6 +25,7 @@ import {Loader} from '#/components/Loader'

export function StepModeration() {
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const {track} = useAnalytics()
const {state, dispatch} = React.useContext(Context)
const {data: preferences} = usePreferencesQuery()
Expand Down Expand Up @@ -53,7 +54,7 @@ export function StepModeration() {
}, [track])

return (
<View style={[a.align_start]}>
<View style={[a.align_start, gtMobile ? a.px_5xl : a.px_xl]}>
<IconCircle icon={EyeSlash} style={[a.mb_2xl]} />

<TitleText>
Expand Down
85 changes: 85 additions & 0 deletions src/screens/Onboarding/StepProfile/AvatarCircle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react'
import {TouchableOpacity, TouchableOpacityProps, View} from 'react-native'
import {Image as ExpoImage} from 'expo-image'
import {atoms as a, native, useTheme, web} from '#/alf'
import {useAvatar} from '#/screens/Onboarding/StepProfile/index'
import {Pencil_Stroke2_Corner0_Rounded as Pencil} from '#/components/icons/Pencil'
import {StreamingLive_Stroke2_Corner0_Rounded as StreamingLive} from '#/components/icons/StreamingLive'
import {AvatarCreatorCircle} from '#/screens/Onboarding/StepProfile/AvatarCreatorCircle'

function AvatarBottomButton({...props}: TouchableOpacityProps) {
const t = useTheme()

return (
<TouchableOpacity
{...props}
style={[
a.absolute,
a.rounded_full,
a.align_center,
a.justify_center,
{backgroundColor: t.palette.primary_500},
{height: 48, width: 48, bottom: 2, right: 2},
]}>
{props.children}
</TouchableOpacity>
)
}

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

const styles = React.useMemo(
() => ({
imageContainer: [
a.rounded_full,
a.overflow_hidden,
a.align_center,
a.justify_center,
t.atoms.border_contrast_low,
t.atoms.bg_contrast_25,
{
height: 200,
width: 200,
},
web({borderWidth: 2}),
native({borderWidth: 1}),
],
}),
[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>
)}
<AvatarBottomButton
onPress={avatar.useCreatedAvatar ? openCreator : openLibrary}>
<Pencil size="md" style={{color: t.palette.white}} />
</AvatarBottomButton>
</View>
)
}
44 changes: 44 additions & 0 deletions src/screens/Onboarding/StepProfile/AvatarCreatorCircle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react'
import {View} from 'react-native'
import {Avatar} from '#/screens/Onboarding/StepProfile/index'

import {atoms as a, native, useTheme, web} 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,
t.atoms.border_contrast_high,
{
height: size,
width: size,
backgroundColor: avatar.backgroundColor,
},
web({borderWidth: 2}),
native({borderWidth: 1}),
],
}),
[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>
)
}
Loading

0 comments on commit 087186e

Please sign in to comment.