Simplify avi creator
This commit is contained in:
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user