import React, {memo, useMemo} from 'react' import {Image, StyleSheet, TouchableOpacity, View} from 'react-native' import Svg, {Circle, Rect, Path} from 'react-native-svg' import {Image as RNImage} from 'react-native-image-crop-picker' import {useLingui} from '@lingui/react' import {msg, Trans} from '@lingui/macro' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {ModerationUI} from '@atproto/api' import {HighPriorityImage} from 'view/com/util/images/Image' 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, isNative} from 'platform/detection' import {UserPreviewLink} from './UserPreviewLink' import * as Menu from '#/components/Menu' import { Camera_Stroke2_Corner0_Rounded as Camera, Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled, } from '#/components/icons/Camera' import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' import {useTheme, tokens} from '#/alf' export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler' 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 ( ) } if (type === 'labeler') { return ( ) } return ( ) } DefaultAvatar = memo(DefaultAvatar) export {DefaultAvatar} let UserAvatar = ({ type = 'user', size, avatar, moderation, usePlainRNImage = false, }: UserAvatarProps): React.ReactNode => { const pal = usePalette('default') const backgroundColor = pal.colors.backgroundLight const aviStyle = useMemo(() => { if (type === 'algo' || type === 'list' || type === 'labeler') { return { width: size, height: size, borderRadius: size > 32 ? 8 : 3, backgroundColor, } } return { width: size, height: size, borderRadius: Math.floor(size / 2), backgroundColor, } }, [type, size, backgroundColor]) 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 t = useTheme() 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 onOpenCamera = React.useCallback(async () => { if (!(await requestCameraAccessIfNeeded())) { return } onSelectNewAvatar( await openCamera({ width: 1000, height: 1000, cropperCircleOverlay: true, }), ) }, [onSelectNewAvatar, requestCameraAccessIfNeeded]) const onOpenLibrary = React.useCallback(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) }, [onSelectNewAvatar, requestPhotoAccessIfNeeded]) const onRemoveAvatar = React.useCallback(() => { onSelectNewAvatar(null) }, [onSelectNewAvatar]) return ( {({props}) => ( {avatar ? ( ) : ( )} )} {isNative && ( Upload from Camera )} {isNative ? ( Upload from Library ) : ( Upload from Files )} {!!avatar && ( <> Remove 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, }, })