organize
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
LayoutAnimation,
|
||||
Pressable,
|
||||
PressableProps,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions'
|
||||
import {openPicker} from 'lib/media/picker.shared'
|
||||
import {isNative, isWeb} from 'platform/detection'
|
||||
import {openCropper} from 'lib/media/picker'
|
||||
import {compressIfNeeded} from 'lib/media/manip'
|
||||
import {Image} from 'expo-image'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
|
||||
import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera'
|
||||
import {useAvatar, useSetAvatar} from '#/screens/Onboarding/StepProfile/index'
|
||||
|
||||
export function AvatarBottomButton({...props}: PressableProps) {
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
{...props}
|
||||
style={[
|
||||
a.absolute,
|
||||
a.rounded_full,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
t.name === 'light' ? t.atoms.bg_contrast_800 : t.atoms.bg_contrast_200,
|
||||
{height: 48, width: 48, bottom: 2, right: 2},
|
||||
]}>
|
||||
{props.children}
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
export function AvatarCircle() {
|
||||
const t = useTheme()
|
||||
const avatar = useAvatar()
|
||||
const setAvatar = useSetAvatar()
|
||||
const Icon = avatar.placeholder.component
|
||||
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
|
||||
|
||||
const onCameraPress = React.useCallback(async () => {
|
||||
if (!(await requestPhotoAccessIfNeeded())) {
|
||||
return
|
||||
}
|
||||
|
||||
const items = await openPicker({
|
||||
aspect: [1, 1],
|
||||
})
|
||||
let image = items[0]
|
||||
if (!image) return
|
||||
|
||||
// TODO we need an alf modal for the cropper
|
||||
if (!isWeb) {
|
||||
image = await openCropper({
|
||||
mediaType: 'photo',
|
||||
cropperCircleOverlay: true,
|
||||
height: image.height,
|
||||
width: image.width,
|
||||
path: image.path,
|
||||
})
|
||||
}
|
||||
image = await compressIfNeeded(image, 1000000)
|
||||
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
|
||||
// If we are on mobile, prefetching the image will load the image into memory before we try and display it,
|
||||
// stopping any brief flickers.
|
||||
if (isNative) {
|
||||
await Image.prefetch(image.path)
|
||||
}
|
||||
|
||||
setAvatar(prev => ({
|
||||
...prev,
|
||||
image,
|
||||
}))
|
||||
}, [requestPhotoAccessIfNeeded, setAvatar])
|
||||
|
||||
const onPressRemoveAvatar = React.useCallback(() => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setAvatar(prev => ({
|
||||
...prev,
|
||||
image: undefined,
|
||||
}))
|
||||
}, [setAvatar])
|
||||
|
||||
if (avatar.image) {
|
||||
return (
|
||||
<View>
|
||||
<Image
|
||||
source={avatar.image.path}
|
||||
style={[styles.imageContainer, t.atoms.border_contrast_high]}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
<AvatarBottomButton onPress={onPressRemoveAvatar}>
|
||||
<Times size="lg" style={{color: t.palette.white}} />
|
||||
</AvatarBottomButton>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View
|
||||
style={[
|
||||
styles.imageContainer,
|
||||
t.atoms.border_contrast_high,
|
||||
{
|
||||
backgroundColor: avatar.backgroundColor,
|
||||
},
|
||||
]}>
|
||||
<Icon height={85} width={85} style={{color: t.palette.white}} />
|
||||
</View>
|
||||
<AvatarBottomButton onPress={onCameraPress}>
|
||||
<Camera size="xl" style={{color: t.palette.white}} />
|
||||
</AvatarBottomButton>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
imageContainer: {
|
||||
borderRadius: 100,
|
||||
height: 150,
|
||||
width: 150,
|
||||
overflow: 'hidden',
|
||||
borderWidth: 2,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,187 @@
|
||||
import React from 'react'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated'
|
||||
import {
|
||||
AvatarColor,
|
||||
avatarColors,
|
||||
emojiItems,
|
||||
EmojiName,
|
||||
emojiNames,
|
||||
} from '#/screens/Onboarding/StepProfile/types'
|
||||
import {
|
||||
FlatList,
|
||||
FlatListProps,
|
||||
ListRenderItemInfo,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {useAvatar, useSetAvatar} from '#/screens/Onboarding/StepProfile/index'
|
||||
|
||||
const WITH_TIMING_CONFIG = {duration: 150}
|
||||
|
||||
function AnimatedCircle({
|
||||
selected,
|
||||
children,
|
||||
}: React.PropsWithChildren<{selected: boolean}>) {
|
||||
const t = useTheme()
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
const styles = useStyles()
|
||||
const size = useSharedValue(selected ? 1.2 : 1)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (selected && size.value !== 1.2) {
|
||||
size.value = withTiming(1.2, WITH_TIMING_CONFIG)
|
||||
} else if (!selected && size.value !== 1) {
|
||||
size.value = withTiming(1, WITH_TIMING_CONFIG)
|
||||
}
|
||||
}, [selected, size])
|
||||
|
||||
// On mobile we want to expand the height/width of the container so the items around it get moved as well. On
|
||||
// desktop, we don't want anything around the item to move so we just increase the scale.
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
if (isTabletOrDesktop) {
|
||||
return {
|
||||
transform: [{scale: size.value}],
|
||||
}
|
||||
}
|
||||
return {
|
||||
height: 70 * size.value,
|
||||
width: 70 * size.value,
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.imageContainer,
|
||||
styles.paletteContainer,
|
||||
t.atoms.border_contrast_high,
|
||||
animatedStyle,
|
||||
]}>
|
||||
{children}
|
||||
</Animated.View>
|
||||
)
|
||||
}
|
||||
|
||||
function ColorItem({color}: {color: AvatarColor}) {
|
||||
const avatar = useAvatar()
|
||||
const setAvatar = useSetAvatar()
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
setAvatar(prev => ({
|
||||
...prev,
|
||||
backgroundColor: color,
|
||||
}))
|
||||
}, [color, setAvatar])
|
||||
|
||||
return (
|
||||
<AnimatedCircle selected={avatar.backgroundColor === color}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
style={[a.h_full, a.w_full, {backgroundColor: color}]}
|
||||
onPress={onPress}
|
||||
/>
|
||||
</AnimatedCircle>
|
||||
)
|
||||
}
|
||||
|
||||
function EmojiItem({emojiName}: {emojiName: EmojiName}) {
|
||||
const t = useTheme()
|
||||
const avatar = useAvatar()
|
||||
const setAvatar = useSetAvatar()
|
||||
const Icon = React.useMemo(() => emojiItems[emojiName].component, [emojiName])
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
setAvatar(prev => ({
|
||||
...prev,
|
||||
placeholder: emojiItems[emojiName],
|
||||
}))
|
||||
}, [emojiName, setAvatar])
|
||||
|
||||
return (
|
||||
<AnimatedCircle selected={avatar.placeholder.name === emojiName}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
style={[a.flex_1, a.justify_center, a.align_center]}
|
||||
onPress={onPress}>
|
||||
<Icon style={[t.atoms.text_contrast_medium]} height={40} width={40} />
|
||||
</Pressable>
|
||||
</AnimatedCircle>
|
||||
)
|
||||
}
|
||||
|
||||
function colorRenderItem({item}: ListRenderItemInfo<AvatarColor>) {
|
||||
return <ColorItem color={item} />
|
||||
}
|
||||
function emojiRenderItem({item}: ListRenderItemInfo<EmojiName>) {
|
||||
return <EmojiItem emojiName={item} />
|
||||
}
|
||||
|
||||
export function AvatarCreatorItems({type}: {type: 'emojis' | 'colors'}) {
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<View style={styles.flatListOuter}>
|
||||
<FlatList
|
||||
// Changing the value of numColumns on the fly isn't supported, so we want the flatlist to re-render whenever
|
||||
// the size of the screen changes
|
||||
key={isTabletOrDesktop ? 0 : 1}
|
||||
data={type === 'colors' ? avatarColors : emojiNames}
|
||||
renderItem={type === 'colors' ? colorRenderItem : emojiRenderItem}
|
||||
style={[isTabletOrDesktop && {marginHorizontal: 10}]}
|
||||
contentContainerStyle={[
|
||||
a.align_center,
|
||||
{height: 100},
|
||||
!isTabletOrDesktop && styles.flatListContainer,
|
||||
isTabletOrDesktop && type === 'colors' && a.pr_xs,
|
||||
]}
|
||||
numColumns={isTabletOrDesktop && type === 'emojis' ? 4 : undefined}
|
||||
showsHorizontalScrollIndicator={isTabletOrDesktop && type === 'colors'}
|
||||
horizontal={!isTabletOrDesktop}
|
||||
{...commonFlatListProps}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = () => {
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
|
||||
return StyleSheet.create({
|
||||
imageContainer: {
|
||||
borderRadius: 100,
|
||||
height: 150,
|
||||
width: 150,
|
||||
overflow: 'hidden',
|
||||
borderWidth: 2,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
paletteContainer: {
|
||||
height: 70,
|
||||
width: 70,
|
||||
margin: isTabletOrDesktop ? 8 : 2,
|
||||
},
|
||||
flatListOuter: isTabletOrDesktop
|
||||
? {
|
||||
height: 435,
|
||||
}
|
||||
: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
height: 100,
|
||||
},
|
||||
flatListContainer: {
|
||||
paddingHorizontal: 40,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const commonFlatListProps: Partial<FlatListProps<any>> = {}
|
||||
@@ -1,91 +0,0 @@
|
||||
import React from 'react'
|
||||
import {LayoutAnimation, Pressable, StyleSheet} from 'react-native'
|
||||
import {Image} from 'expo-image'
|
||||
import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera'
|
||||
import {useSetAvatar} from '#/screens/Onboarding/StepProfile/index'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions'
|
||||
import {openPicker} from 'lib/media/picker.shared'
|
||||
import {openCropper} from 'lib/media/picker'
|
||||
import {compressIfNeeded} from 'lib/media/manip'
|
||||
import {isNative, isWeb} from 'platform/detection'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
|
||||
export function SelectImageButton() {
|
||||
const t = useTheme()
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
const setAvatar = useSetAvatar()
|
||||
const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
|
||||
|
||||
const onCameraPress = React.useCallback(async () => {
|
||||
if (!(await requestPhotoAccessIfNeeded())) {
|
||||
return
|
||||
}
|
||||
|
||||
const items = await openPicker({
|
||||
aspect: [1, 1],
|
||||
})
|
||||
let image = items[0]
|
||||
if (!image) return
|
||||
|
||||
// TODO we need an alf modal for the cropper
|
||||
if (!isWeb) {
|
||||
image = await openCropper({
|
||||
mediaType: 'photo',
|
||||
cropperCircleOverlay: true,
|
||||
height: image.height,
|
||||
width: image.width,
|
||||
path: image.path,
|
||||
})
|
||||
}
|
||||
image = await compressIfNeeded(image, 1000000)
|
||||
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
|
||||
// If we are on mobile, prefetching the image will load the image into memory before we try and display it,
|
||||
// stopping any brief flickers.
|
||||
if (isNative) {
|
||||
await Image.prefetch(image.path)
|
||||
}
|
||||
|
||||
setAvatar(prev => ({
|
||||
...prev,
|
||||
image,
|
||||
}))
|
||||
}, [requestPhotoAccessIfNeeded, setAvatar])
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.imageContainer,
|
||||
styles.paletteContainer,
|
||||
a.mr_2xl,
|
||||
t.atoms.border_contrast_high,
|
||||
{height: 70, width: 70, marginRight: isTabletOrDesktop ? 0 : 5},
|
||||
]}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
onPress={onCameraPress}
|
||||
style={[a.flex_1, a.align_center, a.justify_center]}>
|
||||
<Camera height={40} width={40} style={[t.atoms.text_contrast_medium]} />
|
||||
</Pressable>
|
||||
</Animated.View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
imageContainer: {
|
||||
borderRadius: 100,
|
||||
borderWidth: 2,
|
||||
height: 150,
|
||||
width: 150,
|
||||
overflow: 'hidden',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
paletteContainer: {
|
||||
height: 70,
|
||||
width: 70,
|
||||
},
|
||||
})
|
||||
@@ -1,13 +1,5 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
FlatList,
|
||||
FlatListProps,
|
||||
LayoutAnimation,
|
||||
ListRenderItemInfo,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {View} from 'react-native'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
|
||||
@@ -23,27 +15,15 @@ import {
|
||||
OnboardingControls,
|
||||
} from '#/screens/Onboarding/Layout'
|
||||
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
|
||||
import {IconCircle} from '#/components/IconCircle'
|
||||
import {Image} from 'expo-image'
|
||||
import {
|
||||
Emoji,
|
||||
EmojiName,
|
||||
emojiItems,
|
||||
emojiNames,
|
||||
AvatarColor,
|
||||
avatarColors,
|
||||
} from './types'
|
||||
import {Emoji, emojiItems, AvatarColor, avatarColors} from './types'
|
||||
import {
|
||||
PlaceholderCanvas,
|
||||
PlaceholderCanvasRef,
|
||||
} from '#/screens/Onboarding/StepProfile/PlaceholderCanvas'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated'
|
||||
import {AvatarCircle} from '#/screens/Onboarding/StepProfile/AvatarCircle'
|
||||
import {AvatarCreatorItems} from '#/screens/Onboarding/StepProfile/AvatarCreatorItems'
|
||||
|
||||
interface Avatar {
|
||||
image?: {
|
||||
@@ -57,8 +37,6 @@ interface Avatar {
|
||||
placeholder: Emoji
|
||||
}
|
||||
|
||||
const WITH_TIMING_CONFIG = {duration: 100}
|
||||
|
||||
const AvatarContext = React.createContext<Avatar>({} as Avatar)
|
||||
const SetAvatarContext = React.createContext<
|
||||
React.Dispatch<React.SetStateAction<Avatar>>
|
||||
@@ -132,8 +110,8 @@ export function StepProfile() {
|
||||
? [a.flex_row, a.justify_between]
|
||||
: undefined
|
||||
}>
|
||||
<Items type="emojis" />
|
||||
<Items type="colors" />
|
||||
<AvatarCreatorItems type="emojis" />
|
||||
<AvatarCreatorItems type="colors" />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
@@ -159,220 +137,3 @@ export function StepProfile() {
|
||||
</SetAvatarContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarCircle() {
|
||||
const t = useTheme()
|
||||
const styles = useStyles()
|
||||
const avatar = useAvatar()
|
||||
const setAvatar = useSetAvatar()
|
||||
const Icon = avatar.placeholder.component
|
||||
|
||||
const onPressRemoveAvatar = React.useCallback(() => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setAvatar(prev => ({
|
||||
...prev,
|
||||
image: undefined,
|
||||
}))
|
||||
}, [setAvatar])
|
||||
|
||||
if (avatar.image) {
|
||||
return (
|
||||
<View>
|
||||
<Image
|
||||
source={avatar.image.path}
|
||||
style={[styles.imageContainer, t.atoms.border_contrast_high]}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
style={[
|
||||
a.absolute,
|
||||
a.rounded_full,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
a.border,
|
||||
a.shadow_lg,
|
||||
t.atoms.border_contrast_high,
|
||||
t.atoms.bg_contrast_300,
|
||||
{height: 40, width: 40, bottom: 5, right: 5},
|
||||
]}
|
||||
onPress={onPressRemoveAvatar}>
|
||||
<Times size="lg" style={{color: t.palette.white}} />
|
||||
</Pressable>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.imageContainer,
|
||||
t.atoms.border_contrast_high,
|
||||
{
|
||||
backgroundColor: avatar.backgroundColor,
|
||||
},
|
||||
]}>
|
||||
<Icon height={85} width={85} style={{color: t.palette.white}} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function AnimatedCircle({
|
||||
selected,
|
||||
children,
|
||||
}: React.PropsWithChildren<{selected: boolean}>) {
|
||||
const t = useTheme()
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
const styles = useStyles()
|
||||
const size = useSharedValue(selected ? 1.2 : 1)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (selected && size.value !== 1.2) {
|
||||
size.value = withTiming(1.2, WITH_TIMING_CONFIG)
|
||||
} else if (!selected && size.value !== 1) {
|
||||
size.value = withTiming(1, WITH_TIMING_CONFIG)
|
||||
}
|
||||
}, [selected, size])
|
||||
|
||||
// On mobile we want to expand the height/width of the container so the items around it get moved as well. On
|
||||
// desktop, we don't want anything around the item to move so we just increase the scale.
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
if (isTabletOrDesktop) {
|
||||
return {
|
||||
transform: [{scale: size.value}],
|
||||
}
|
||||
}
|
||||
return {
|
||||
height: 70 * size.value,
|
||||
width: 70 * size.value,
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.imageContainer,
|
||||
styles.paletteContainer,
|
||||
t.atoms.border_contrast_high,
|
||||
animatedStyle,
|
||||
]}>
|
||||
{children}
|
||||
</Animated.View>
|
||||
)
|
||||
}
|
||||
|
||||
function ColorItem({color}: {color: AvatarColor}) {
|
||||
const avatar = useAvatar()
|
||||
const setAvatar = useSetAvatar()
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
setAvatar(prev => ({
|
||||
...prev,
|
||||
backgroundColor: color,
|
||||
}))
|
||||
}, [color, setAvatar])
|
||||
|
||||
return (
|
||||
<AnimatedCircle selected={avatar.backgroundColor === color}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
style={[a.h_full, a.w_full, {backgroundColor: color}]}
|
||||
onPress={onPress}
|
||||
/>
|
||||
</AnimatedCircle>
|
||||
)
|
||||
}
|
||||
function colorRenderItem({item}: ListRenderItemInfo<AvatarColor>) {
|
||||
return <ColorItem color={item} />
|
||||
}
|
||||
|
||||
function EmojiItem({emojiName}: {emojiName: EmojiName}) {
|
||||
const t = useTheme()
|
||||
const avatar = useAvatar()
|
||||
const setAvatar = useSetAvatar()
|
||||
const Icon = React.useMemo(() => emojiItems[emojiName].component, [emojiName])
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
setAvatar(prev => ({
|
||||
...prev,
|
||||
placeholder: emojiItems[emojiName],
|
||||
}))
|
||||
}, [emojiName, setAvatar])
|
||||
|
||||
return (
|
||||
<AnimatedCircle selected={avatar.placeholder.name === emojiName}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
style={[a.flex_1, a.justify_center, a.align_center]}
|
||||
onPress={onPress}>
|
||||
<Icon style={[t.atoms.text_contrast_medium]} height={40} width={40} />
|
||||
</Pressable>
|
||||
</AnimatedCircle>
|
||||
)
|
||||
}
|
||||
function emojiRenderItem({item}: ListRenderItemInfo<EmojiName>) {
|
||||
return <EmojiItem emojiName={item} />
|
||||
}
|
||||
|
||||
function Items({type}: {type: 'emojis' | 'colors'}) {
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<View style={styles.flatListOuter}>
|
||||
<FlatList
|
||||
// Changing the value of numColumns on the fly isn't supported, so we want the flatlist to re-render whenever
|
||||
// the size of the screen changes
|
||||
key={isTabletOrDesktop ? 0 : 1}
|
||||
data={type === 'colors' ? avatarColors : emojiNames}
|
||||
renderItem={type === 'colors' ? colorRenderItem : emojiRenderItem}
|
||||
style={[isTabletOrDesktop && {marginHorizontal: 10}]}
|
||||
contentContainerStyle={[
|
||||
a.align_center,
|
||||
{height: 100},
|
||||
!isTabletOrDesktop && styles.flatListContainer,
|
||||
isTabletOrDesktop && type === 'colors' && a.pr_xs,
|
||||
]}
|
||||
numColumns={isTabletOrDesktop && type === 'emojis' ? 4 : undefined}
|
||||
showsHorizontalScrollIndicator={isTabletOrDesktop && type === 'colors'}
|
||||
horizontal={!isTabletOrDesktop}
|
||||
{...commonFlatListProps}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = () => {
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
|
||||
return StyleSheet.create({
|
||||
imageContainer: {
|
||||
borderRadius: 100,
|
||||
height: 150,
|
||||
width: 150,
|
||||
overflow: 'hidden',
|
||||
borderWidth: 2,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
paletteContainer: {
|
||||
height: 70,
|
||||
width: 70,
|
||||
margin: isTabletOrDesktop ? 8 : 2,
|
||||
},
|
||||
flatListOuter: isTabletOrDesktop
|
||||
? {
|
||||
height: 435,
|
||||
}
|
||||
: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
height: 100,
|
||||
},
|
||||
flatListContainer: {
|
||||
paddingHorizontal: 40,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const commonFlatListProps: Partial<FlatListProps<any>> = {}
|
||||
|
||||
Reference in New Issue
Block a user