Simplify avi creator

This commit is contained in:
Eric Bailey
2024-04-25 16:27:11 -05:00
parent c9a72bb5f5
commit c38e69af16
3 changed files with 128 additions and 160 deletions
@@ -1,8 +1,8 @@
import React from 'react' import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {Avatar} from '#/screens/Onboarding/StepProfile/index'
import {atoms as a, native, useTheme, web} from '#/alf' import {Avatar} from '#/screens/Onboarding/StepProfile/index'
import {atoms as a, useTheme} from '#/alf'
export function AvatarCreatorCircle({ export function AvatarCreatorCircle({
avatar, avatar,
@@ -21,14 +21,13 @@ export function AvatarCreatorCircle({
a.overflow_hidden, a.overflow_hidden,
a.align_center, a.align_center,
a.justify_center, a.justify_center,
a.border,
t.atoms.border_contrast_high, t.atoms.border_contrast_high,
{ {
height: size, height: size,
width: size, width: size,
backgroundColor: avatar.backgroundColor, backgroundColor: avatar.backgroundColor,
}, },
web({borderWidth: 2}),
native({borderWidth: 1}),
], ],
}), }),
[avatar.backgroundColor, size, t.atoms.border_contrast_high], [avatar.backgroundColor, size, t.atoms.border_contrast_high],
@@ -1,10 +1,8 @@
import React from 'react' import React from 'react'
import {ListRenderItemInfo, Pressable, View} from 'react-native' import {View} from 'react-native'
// Using the FlatList from RNGH allows us to nest the scroll views on Android. The default FlatList won't allow import {msg, Trans} from '@lingui/macro'
// scrolling inside of the vertical ScrollView import {useLingui} from '@lingui/react'
import {FlatList} from 'react-native-gesture-handler'
import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf'
import {Avatar} from '#/screens/Onboarding/StepProfile/index' import {Avatar} from '#/screens/Onboarding/StepProfile/index'
import { import {
AvatarColor, AvatarColor,
@@ -13,100 +11,18 @@ import {
EmojiName, EmojiName,
emojiNames, emojiNames,
} from '#/screens/Onboarding/StepProfile/types' } from '#/screens/Onboarding/StepProfile/types'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {Text} from '#/components/Typography'
function Circle({children}: React.PropsWithChildren<{}>) { const ACTIVE_BORDER_WIDTH = 3
const t = useTheme() const ACTIVE_BORDER_STYLES = {
top: -ACTIVE_BORDER_WIDTH,
const styles = React.useMemo( bottom: -ACTIVE_BORDER_WIDTH,
() => ({ left: -ACTIVE_BORDER_WIDTH,
container: [ right: -ACTIVE_BORDER_WIDTH,
a.rounded_full, opacity: 0.5,
a.overflow_hidden, borderWidth: 3,
a.align_center,
a.justify_center,
t.atoms.bg_contrast_25,
web({borderWidth: 2}),
native({borderWidth: 1}),
{
height: 60,
width: 60,
margin: 4,
},
],
}),
[t.atoms.bg_contrast_25],
)
return (
<View style={[styles.container, t.atoms.border_contrast_high]}>
{children}
</View>
)
}
function ColorItem({
color,
setAvatar,
}: {
color: AvatarColor
setAvatar: React.Dispatch<React.SetStateAction<Avatar>>
}) {
const onPress = React.useCallback(() => {
setAvatar(prev => ({
...prev,
backgroundColor: color,
}))
}, [color, setAvatar])
return (
<Circle>
<Pressable
accessibilityRole="button"
style={[a.h_full, a.w_full, {backgroundColor: color}]}
onPress={onPress}
/>
</Circle>
)
}
function EmojiItem({
emojiName,
avatar,
setAvatar,
}: {
emojiName: EmojiName
avatar: Avatar
setAvatar: React.Dispatch<React.SetStateAction<Avatar>>
}) {
const t = useTheme()
const Icon = React.useMemo(() => emojiItems[emojiName].component, [emojiName])
const onPress = React.useCallback(() => {
setAvatar(prev => ({
...prev,
placeholder: emojiItems[emojiName],
}))
}, [emojiName, setAvatar])
const selected = React.useMemo(
() => avatar.placeholder.name === emojiName,
[avatar.placeholder.name, emojiName],
)
return (
<Circle>
<Pressable
accessibilityRole="button"
style={[a.flex_1, a.justify_center, a.align_center]}
onPress={onPress}>
<Icon
style={selected ? t.atoms.text : t.atoms.text_contrast_low}
height={30}
width={30}
/>
</Pressable>
</Circle>
)
} }
export function AvatarCreatorItems({ export function AvatarCreatorItems({
@@ -118,58 +34,112 @@ export function AvatarCreatorItems({
avatar: Avatar avatar: Avatar
setAvatar: React.Dispatch<React.SetStateAction<Avatar>> setAvatar: React.Dispatch<React.SetStateAction<Avatar>>
}) { }) {
const {gtMobile} = useBreakpoints() const {_} = useLingui()
const t = useTheme()
const isEmojis = type === 'emojis'
const styles = React.useMemo( const onSelectEmoji = React.useCallback(
() => ({ (emoji: EmojiName) => {
flatListOuter: gtMobile setAvatar(prev => ({
? { ...prev,
height: 338, placeholder: emojiItems[emoji],
} }))
: [a.flex_row, a.align_center, {height: 70}],
}),
[gtMobile],
)
const colorRenderItem = React.useCallback(
({item}: ListRenderItemInfo<AvatarColor>) => {
return <ColorItem color={item} setAvatar={setAvatar} />
}, },
[setAvatar], [setAvatar],
) )
const emojiRenderItem = React.useCallback( const onSelectColor = React.useCallback(
({item}: ListRenderItemInfo<EmojiName>) => { (color: AvatarColor) => {
return ( setAvatar(prev => ({
<EmojiItem emojiName={item} avatar={avatar} setAvatar={setAvatar} /> ...prev,
) backgroundColor: color,
}))
}, },
[avatar, setAvatar], [setAvatar],
) )
return ( 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 <View
style={[ style={[
styles.flatListOuter, a.flex_row,
gtMobile && type === 'colors' && {width: 125}, a.align_start,
a.justify_start,
a.flex_wrap,
a.gap_md,
]}> ]}>
<FlatList<any> {isEmojis
// Changing the value of numColumns on the fly isn't supported, so we want the flatlist to re-render whenever ? emojiNames.map(emojiName => (
// the size of the screen changes. Should only happen when `isTabletOrDesktop` changes. <Button
key={gtMobile ? 0 : 1} key={emojiName}
data={type === 'colors' ? avatarColors : emojiNames} label={_(msg`Select the ${emojiName} emoji as your avatar`)}
renderItem={type === 'colors' ? colorRenderItem : emojiRenderItem} size="small"
keyExtractor={item => item} shape="round"
style={[gtMobile && {marginHorizontal: 10}]} variant="solid"
contentContainerStyle={[ color="secondary"
a.align_center, onPress={() => onSelectEmoji(emojiName)}>
gtMobile && type === 'colors' && a.pr_xs, <ButtonIcon icon={emojiItems[emojiName].component} />
!gtMobile && {paddingHorizontal: 40}, {avatar.placeholder.name === emojiName && (
<View
style={[
a.absolute,
a.rounded_full,
ACTIVE_BORDER_STYLES,
{
borderColor: avatar.backgroundColor,
},
]} ]}
numColumns={gtMobile && type === 'emojis' ? 4 : undefined}
showsHorizontalScrollIndicator={gtMobile && type === 'colors'}
horizontal={!gtMobile}
/> />
)}
</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> </View>
) )
} }
+14 -15
View File
@@ -63,7 +63,7 @@ export function StepProfile() {
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission() const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
const creatorControl = Dialog.useDialogControl() const creatorControl = Dialog.useDialogControl()
const {state, dispatch} = React.useContext(Context) const {dispatch} = React.useContext(Context)
const [avatar, setAvatar] = React.useState<Avatar>({ const [avatar, setAvatar] = React.useState<Avatar>({
placeholder: emojiItems.at, placeholder: emojiItems.at,
backgroundColor: randomColor, backgroundColor: randomColor,
@@ -173,18 +173,17 @@ export function StepProfile() {
an avatar. an avatar.
</Trans> </Trans>
</DescriptionText> </DescriptionText>
<View style={[a.w_full, a.align_center, {paddingTop: 80}]}> <View
style={[a.w_full, a.align_center, {paddingTop: gtMobile ? 80 : 40}]}>
<AvatarCircle <AvatarCircle
openLibrary={openLibrary} openLibrary={openLibrary}
openCreator={creatorControl.open} openCreator={creatorControl.open}
/> />
</View> </View>
<View style={[a.w_full, a.px_2xl, a.pt_5xl]} />
<OnboardingControls.Portal> <OnboardingControls.Portal>
<View style={[a.gap_md, gtMobile && {flexDirection: 'row-reverse'}]}> <View style={[a.gap_md, gtMobile && {flexDirection: 'row-reverse'}]}>
<Button <Button
key={state.activeStep} // remove focus state on nav
variant="gradient" variant="gradient"
color="gradient_sky" color="gradient_sky"
size="large" size="large"
@@ -196,7 +195,6 @@ export function StepProfile() {
<ButtonIcon icon={ChevronRight} position="right" /> <ButtonIcon icon={ChevronRight} position="right" />
</Button> </Button>
<Button <Button
key={state.activeStep} // remove focus state on nav
variant="ghost" variant="ghost"
color="primary" color="primary"
size="large" size="large"
@@ -216,18 +214,19 @@ export function StepProfile() {
<Dialog.Outer control={creatorControl}> <Dialog.Outer control={creatorControl}>
<Dialog.Handle /> <Dialog.Handle />
<Dialog.Inner label="Avatar creator"> <Dialog.Inner
label="Avatar creator"
style={[
{
width: 'auto',
maxWidth: 410,
},
]}>
<View style={[a.align_center, {paddingTop: 20}]}> <View style={[a.align_center, {paddingTop: 20}]}>
<AvatarCreatorCircle avatar={avatar} /> <AvatarCreatorCircle avatar={avatar} />
</View> </View>
<View <View style={[a.pt_3xl, a.gap_lg]}>
style={[
a.pt_2xl,
a.align_center,
a.justify_center,
gtMobile && a.flex_row,
]}>
<AvatarCreatorItems <AvatarCreatorItems
type="emojis" type="emojis"
avatar={avatar} avatar={avatar}
@@ -239,9 +238,8 @@ export function StepProfile() {
setAvatar={setAvatar} setAvatar={setAvatar}
/> />
</View> </View>
<View style={[a.px_xl, a.pt_4xl]}> <View style={[a.pt_4xl]}>
<Button <Button
key={state.activeStep} // remove focus state on nav
variant="solid" variant="solid"
color="primary" color="primary"
size="large" size="large"
@@ -254,6 +252,7 @@ export function StepProfile() {
</View> </View>
</Dialog.Inner> </Dialog.Inner>
</Dialog.Outer> </Dialog.Outer>
<PlaceholderCanvas ref={canvasRef} /> <PlaceholderCanvas ref={canvasRef} />
</AvatarContext.Provider> </AvatarContext.Provider>
) )