152 lines
3.9 KiB
TypeScript
152 lines
3.9 KiB
TypeScript
import React, {useCallback} from 'react'
|
|
import {StyleSheet, View, TouchableOpacity, Alert, Image} from 'react-native'
|
|
import Svg, {Circle, Text, Defs, LinearGradient, Stop} from 'react-native-svg'
|
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
import {
|
|
openCamera,
|
|
openCropper,
|
|
openPicker,
|
|
} from 'react-native-image-crop-picker'
|
|
import {getGradient} from '../../lib/asset-gen'
|
|
import {colors} from '../../lib/styles'
|
|
import {IMAGES_ENABLED} from '../../../build-flags'
|
|
|
|
export function UserAvatar({
|
|
size,
|
|
handle,
|
|
userAvatar,
|
|
displayName,
|
|
setUserAvatar,
|
|
}: {
|
|
size: number
|
|
handle: string
|
|
displayName: string | undefined
|
|
userAvatar: string | null | undefined
|
|
setUserAvatar?: React.Dispatch<React.SetStateAction<string | null>>
|
|
}) {
|
|
const initials = getInitials(displayName || handle)
|
|
const gradient = getGradient(handle)
|
|
|
|
const handleEditAvatar = useCallback(() => {
|
|
Alert.alert('Select upload method', '', [
|
|
{
|
|
text: 'Take a new photo',
|
|
onPress: () => {
|
|
openCamera({
|
|
mediaType: 'photo',
|
|
cropping: true,
|
|
width: 80,
|
|
height: 80,
|
|
cropperCircleOverlay: true,
|
|
}).then(item => {
|
|
if (setUserAvatar != null) {
|
|
setUserAvatar(item.path)
|
|
}
|
|
})
|
|
},
|
|
},
|
|
{
|
|
text: 'Select from gallery',
|
|
onPress: () => {
|
|
openPicker({
|
|
mediaType: 'photo',
|
|
}).then(async item => {
|
|
await openCropper({
|
|
mediaType: 'photo',
|
|
path: item.path,
|
|
width: 80,
|
|
height: 80,
|
|
cropperCircleOverlay: true,
|
|
}).then(croppedItem => {
|
|
if (setUserAvatar != null) {
|
|
setUserAvatar(croppedItem.path)
|
|
}
|
|
})
|
|
})
|
|
},
|
|
},
|
|
])
|
|
}, [setUserAvatar])
|
|
|
|
const renderSvg = (size: number, initials: string) => (
|
|
<Svg width={size} height={size} viewBox="0 0 100 100">
|
|
<Defs>
|
|
<LinearGradient id="grad" x1="0" y1="0" x2="1" y2="1">
|
|
<Stop offset="0" stopColor={gradient[0]} stopOpacity="1" />
|
|
<Stop offset="1" stopColor={gradient[1]} stopOpacity="1" />
|
|
</LinearGradient>
|
|
</Defs>
|
|
<Circle cx="50" cy="50" r="50" fill="url(#grad)" />
|
|
<Text
|
|
fill="white"
|
|
fontSize="50"
|
|
fontWeight="bold"
|
|
x="50"
|
|
y="67"
|
|
textAnchor="middle">
|
|
{initials}
|
|
</Text>
|
|
</Svg>
|
|
)
|
|
|
|
// setUserAvatar is only passed as prop on the EditProfile component
|
|
return setUserAvatar != null && IMAGES_ENABLED ? (
|
|
<TouchableOpacity onPress={handleEditAvatar}>
|
|
{userAvatar ? (
|
|
<Image style={styles.avatarImage} source={{uri: userAvatar}} />
|
|
) : (
|
|
renderSvg(size, initials)
|
|
)}
|
|
<View style={styles.editButtonContainer}>
|
|
<FontAwesomeIcon
|
|
icon="camera"
|
|
size={12}
|
|
style={{color: colors.white}}
|
|
/>
|
|
</View>
|
|
</TouchableOpacity>
|
|
) : userAvatar ? (
|
|
<Image
|
|
style={styles.avatarImage}
|
|
resizeMode="stretch"
|
|
source={{uri: userAvatar}}
|
|
/>
|
|
) : (
|
|
renderSvg(size, initials)
|
|
)
|
|
}
|
|
|
|
function getInitials(str: string): string {
|
|
const tokens = str
|
|
.toLowerCase()
|
|
.replace(/[^a-z]/g, '')
|
|
.split(' ')
|
|
.filter(Boolean)
|
|
.map(v => v.trim())
|
|
if (tokens.length >= 2 && tokens[0][0] && tokens[0][1]) {
|
|
return tokens[0][0].toUpperCase() + tokens[1][0].toUpperCase()
|
|
}
|
|
if (tokens.length === 1 && tokens[0][0]) {
|
|
return tokens[0][0].toUpperCase()
|
|
}
|
|
return 'X'
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
editButtonContainer: {
|
|
position: 'absolute',
|
|
width: 24,
|
|
height: 24,
|
|
bottom: 0,
|
|
right: 0,
|
|
borderRadius: 12,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
backgroundColor: colors.gray5,
|
|
},
|
|
avatarImage: {
|
|
width: 80,
|
|
height: 80,
|
|
borderRadius: 40,
|
|
},
|
|
})
|