import React, {memo, useMemo} from 'react' import {Image, StyleSheet, View} from 'react-native' import Svg, {Circle, Rect, Path} from 'react-native-svg' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {HighPriorityImage} from 'view/com/util/images/Image' import {ModerationUI} from '@atproto/api' import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' import { usePhotoLibraryPermission, useCameraPermission, } from 'lib/hooks/usePermissions' import {colors} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' import {isWeb, isAndroid} from 'platform/detection' import {Image as RNImage} from 'react-native-image-crop-picker' import {UserPreviewLink} from './UserPreviewLink' import {DropdownItem, NativeDropdown} from './forms/NativeDropdown' import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' export type UserAvatarType = 'user' | 'algo' | 'list' interface BaseUserAvatarProps { type?: UserAvatarType size: number avatar?: string | null } interface UserAvatarProps extends BaseUserAvatarProps { moderation?: ModerationUI usePlainRNImage?: boolean } interface EditableUserAvatarProps extends BaseUserAvatarProps { onSelectNewAvatar: (img: RNImage | null) => void } interface PreviewableUserAvatarProps extends BaseUserAvatarProps { moderation?: ModerationUI did: string handle: string } const BLUR_AMOUNT = isWeb ? 5 : 100 let DefaultAvatar = ({ type, size, }: { type: UserAvatarType size: number }): React.ReactNode => { if (type === 'algo') { // Font Awesome Pro 6.4.0 by @fontawesome -https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. return ( ) } if (type === 'list') { // Font Awesome Pro 6.4.0 by @fontawesome -https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. return ( ) } return ( ) } DefaultAvatar = memo(DefaultAvatar) export {DefaultAvatar} let UserAvatar = ({ type = 'user', size, avatar, moderation, usePlainRNImage = false, }: UserAvatarProps): React.ReactNode => { const pal = usePalette('default') const aviStyle = useMemo(() => { if (type === 'algo' || type === 'list') { return { width: size, height: size, borderRadius: size > 32 ? 8 : 3, } } return { width: size, height: size, borderRadius: Math.floor(size / 2), } }, [type, size]) const alert = useMemo(() => { if (!moderation?.alert) { return null } return ( ) }, [moderation?.alert, size, pal]) return avatar && !((moderation?.blur && isAndroid) /* android crashes with blur */) ? ( {usePlainRNImage ? ( ) : ( )} {alert} ) : ( {alert} ) } UserAvatar = memo(UserAvatar) export {UserAvatar} let EditableUserAvatar = ({ type = 'user', size, avatar, onSelectNewAvatar, }: EditableUserAvatarProps): React.ReactNode => { const pal = usePalette('default') const {_} = useLingui() const {requestCameraAccessIfNeeded} = useCameraPermission() const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission() const aviStyle = useMemo(() => { if (type === 'algo' || type === 'list') { return { width: size, height: size, borderRadius: size > 32 ? 8 : 3, } } return { width: size, height: size, borderRadius: Math.floor(size / 2), } }, [type, size]) const dropdownItems = useMemo( () => [ !isWeb && { testID: 'changeAvatarCameraBtn', label: _(msg`Camera`), icon: { ios: { name: 'camera', }, android: 'ic_menu_camera', web: 'camera', }, onPress: async () => { if (!(await requestCameraAccessIfNeeded())) { return } onSelectNewAvatar( await openCamera({ width: 1000, height: 1000, cropperCircleOverlay: true, }), ) }, }, { testID: 'changeAvatarLibraryBtn', label: _(msg`Library`), icon: { ios: { name: 'photo.on.rectangle.angled', }, android: 'ic_menu_gallery', web: 'gallery', }, onPress: async () => { if (!(await requestPhotoAccessIfNeeded())) { return } const items = await openPicker({ aspect: [1, 1], }) const item = items[0] if (!item) { return } const croppedImage = await openCropper({ mediaType: 'photo', cropperCircleOverlay: true, height: item.height, width: item.width, path: item.path, }) onSelectNewAvatar(croppedImage) }, }, !!avatar && { label: 'separator', }, !!avatar && { testID: 'changeAvatarRemoveBtn', label: _(msg`Remove`), icon: { ios: { name: 'trash', }, android: 'ic_delete', web: ['far', 'trash-can'], }, onPress: async () => { onSelectNewAvatar(null) }, }, ].filter(Boolean) as DropdownItem[], [ avatar, onSelectNewAvatar, requestCameraAccessIfNeeded, requestPhotoAccessIfNeeded, _, ], ) return ( {avatar ? ( ) : ( )} ) } EditableUserAvatar = memo(EditableUserAvatar) export {EditableUserAvatar} let PreviewableUserAvatar = ( props: PreviewableUserAvatarProps, ): React.ReactNode => { return ( ) } PreviewableUserAvatar = memo(PreviewableUserAvatar) export {PreviewableUserAvatar} const styles = StyleSheet.create({ editButtonContainer: { position: 'absolute', width: 24, height: 24, bottom: 0, right: 0, borderRadius: 12, alignItems: 'center', justifyContent: 'center', backgroundColor: colors.gray5, }, alertIconContainer: { position: 'absolute', right: 0, bottom: 0, borderRadius: 100, }, alertIcon: { color: colors.red3, }, })