import * as React from 'react'; import { EmitterSubscription, Pressable, StyleProp, ViewStyle, } from 'react-native'; import { ICON_ASSETS } from '../../assets'; import { ChatServiceListener, DisconnectReasonType, PresenceUtil, useChatContext, useChatListener, } from '../../chat'; import { useConfigContext } from '../../config'; import { useEventEmitter } from '../../dispatch'; import { useColors, useForceUpdate, useGetStyleProps } from '../../hook'; import { usePaletteContext, useThemeContext } from '../../theme'; import { DefaultIconImage, DefaultIconImageProps } from '../../ui/Image'; import type { StatusType } from '../types'; import { AvatarStatus } from './AvatarStatus'; import { gEventAvatarStatus } from './const'; export type AvatarProps = DefaultIconImageProps; /** * Avatar component. * * If the url fails to load, the default avatar is displayed. The default avatar can be customized by setting `Config.personAvatar`. * * @param props {@link DefaultIconImageProps} */ export function Avatar(props: AvatarProps) { const { size, style, localIcon, ...others } = props; const { cornerRadius: corner } = useThemeContext(); const { cornerRadius } = usePaletteContext(); const { getBorderRadius } = useGetStyleProps(); const { personAvatar } = useConfigContext(); return ( ); } /** * Group avatar component. * * If the url fails to load, the default avatar is displayed. The default avatar can be customized by setting `Config.groupAvatar`. */ export function GroupAvatar(props: AvatarProps) { const { localIcon, ...others } = props; const { groupAvatar } = useConfigContext(); return ( ); } export type StatusAvatarProps = AvatarProps & { userId?: string; onClicked?: () => void; statusContainerStyle?: StyleProp; statusStyle?: StyleProp; disableStatus?: boolean; }; /** * Status avatar component. * * Compared with ordinary avatar components, it has custom status. The `presence` service needs to be activated. */ export function StatusAvatar(props: StatusAvatarProps) { const { userId, onClicked, statusContainerStyle, statusStyle, url, disableStatus = false, ...others } = props; const parentSize = props.size; const childrenPaddingSize = parentSize >= 100 ? 4 : parentSize >= 50 ? 3 : parentSize >= 40 ? 2.5 : 2; const scale = parentSize >= 100 ? 100 / 26 : parentSize >= 50 ? 50 / 16 : parentSize >= 40 ? 40 / 12 : 32 / 11.68; const urlRef = React.useRef(url); const [status, setStatus] = React.useState(''); const { AvatarStatusRender, enablePresence } = useConfigContext(); const { updater } = useForceUpdate(); const { cornerRadius } = useThemeContext(); const { avatar: avatarCornerRadius } = cornerRadius; const { emitAvatarStatusEvent } = useAvatarStatus(); const im = useChatContext(); const { colors } = usePaletteContext(); const { getColor } = useColors({ online: { light: colors.secondary[5], dark: colors.secondary[5], }, busy: { light: colors.error[7], dark: colors.error[6], }, leave: { light: colors.neutral[7], dark: colors.neutral[7], }, custom: { light: '#FFE145', dark: '#FFE145', }, }); const getStatusColor = (status: string) => { if (status === 'online') { return getColor('online'); } else if (status === 'busy') { return getColor('busy'); } else if (status === 'leave' || status === 'offline') { return getColor('leave'); } else if (status === 'custom') { return getColor('custom'); } return getColor('custom'); }; const onStatusChanged = React.useCallback( (description: string) => { setStatus(description); emitAvatarStatusEvent({ status: description as StatusType, }); }, [emitAvatarStatusEvent] ); const listener = React.useMemo(() => { return { onPresenceStatusChanged: (list) => { if (list.length > 0) { const user = list.find((u) => { return u.publisher === userId; }); if (user) { onStatusChanged(PresenceUtil.convertFromProtocol(user)); } } }, onFinished: (params) => { if (params.event === 'updateSelfInfo') { const ret = im.user(im.userId); if (ret && ret.avatarURL && ret.avatarURL.length > 0) { urlRef.current = ret.avatarURL; updater(); } } }, onConnected: () => { if (userId) { im.fetchPresence({ userIds: [userId], onResult: (res) => { if (res.isOk === true) { onStatusChanged(res.value?.get(userId) ?? 'offline'); } }, }); } }, onDisconnected: (reason: DisconnectReasonType) => { if (reason === DisconnectReasonType.others) { onStatusChanged('offline'); } }, } as ChatServiceListener; }, [im, onStatusChanged, updater, userId]); useChatListener(listener); React.useEffect(() => { if (userId) { if (im.userId !== userId) { im.loginState().then((state) => { if (state === 'logged') { im.subPresence({ userIds: [userId] }); } }); } im.fetchPresence({ userIds: [userId], onResult: (res) => { if (res.isOk === true) { onStatusChanged(res.value?.get(userId) ?? 'offline'); } }, }); } return () => { if (userId && im.userId !== userId) { im.loginState().then((state) => { if (state === 'logged') { im.unSubPresence({ userIds: [userId] }); } }); } }; }, [im, onStatusChanged, userId]); React.useEffect(() => { if (url !== urlRef.current && url && url.length > 0) { urlRef.current = url; updater(); } }, [updater, url]); if (enablePresence !== true) { return ; } return ( {disableStatus === false ? ( ) : null} ); } export function useAvatarStatus() { const { addEventListener, removeEventListenerBySub, emitEvent } = useEventEmitter(); const _addListener = React.useCallback( (listener: (params: { status: StatusType }) => void) => { return addEventListener(gEventAvatarStatus, listener); }, [addEventListener] ); const _removeListener = React.useCallback( (sub: EmitterSubscription) => { removeEventListenerBySub(gEventAvatarStatus, sub); }, [removeEventListenerBySub] ); const _emitEvent = React.useCallback( (...params: any[]) => { emitEvent(gEventAvatarStatus, ...params); }, [emitEvent] ); return { emitAvatarStatusEvent: _emitEvent, addAvatarStatusListener: _addListener, removeAvatarStatusListener: _removeListener, }; }