diff --git a/lib/types/avatar-types.js b/lib/types/avatar-types.js index 28836abdf..f25dd57db 100644 --- a/lib/types/avatar-types.js +++ b/lib/types/avatar-types.js @@ -1,79 +1,81 @@ // @flow import t, { type TUnion, type TInterface } from 'tcomb'; import type { CreateUpdatesResult } from './update-types.js'; import { validHexColorRegex } from '../shared/account-utils.js'; import { onlyOneEmojiRegex } from '../shared/emojis.js'; import { tRegex, tShape, tString } from '../utils/validation-utils.js'; export type EmojiAvatarDBContent = { +type: 'emoji', +emoji: string, +color: string, // hex, without "#" or "0x" }; export const emojiAvatarDBContentValidator: TInterface = tShape({ type: tString('emoji'), emoji: tRegex(onlyOneEmojiRegex), color: tRegex(validHexColorRegex), }); export type ImageAvatarDBContent = { +type: 'image', +uploadID: string, }; export type ENSAvatarDBContent = { +type: 'ens', }; export const ensAvatarDBContentValidator: TInterface = tShape({ type: tString('ens') }); export type AvatarDBContent = | EmojiAvatarDBContent | ImageAvatarDBContent | ENSAvatarDBContent; export type UpdateUserAvatarRemoveRequest = { +type: 'remove' }; export type UpdateUserAvatarRequest = | AvatarDBContent | UpdateUserAvatarRemoveRequest; export type ClientEmojiAvatar = EmojiAvatarDBContent; const clientEmojiAvatarValidator = emojiAvatarDBContentValidator; export type ClientImageAvatar = { +type: 'image', +uri: string, }; const clientImageAvatarValidator = tShape({ type: tString('image'), uri: t.String, }); export type ClientENSAvatar = ENSAvatarDBContent; const clientENSAvatarValidator = ensAvatarDBContentValidator; export type ClientAvatar = | ClientEmojiAvatar | ClientImageAvatar | ClientENSAvatar; export const clientAvatarValidator: TUnion = t.union([ clientEmojiAvatarValidator, clientImageAvatarValidator, clientENSAvatarValidator, ]); export type ResolvedClientAvatar = ClientEmojiAvatar | ClientImageAvatar; export type UpdateUserAvatarResponse = { +updates: CreateUpdatesResult, }; export type GenericUserInfoWithAvatar = { +username?: ?string, +avatar?: ?ClientAvatar, ... }; + +export type AvatarSize = 'XS' | 'S' | 'M' | 'L' | 'XL'; diff --git a/native/avatars/avatar.react.js b/native/avatars/avatar.react.js index bf2b2f3e5..5713e7e92 100644 --- a/native/avatars/avatar.react.js +++ b/native/avatars/avatar.react.js @@ -1,142 +1,143 @@ // @flow import * as React from 'react'; import { View, Text, StyleSheet } from 'react-native'; -import type { ResolvedClientAvatar } from 'lib/types/avatar-types.js'; +import type { + ResolvedClientAvatar, + AvatarSize, +} from 'lib/types/avatar-types.js'; import Multimedia from '../media/multimedia.react.js'; -export type AvatarSize = 'XS' | 'S' | 'M' | 'L' | 'XL'; - type Props = { +avatarInfo: ResolvedClientAvatar, +size: AvatarSize, }; function Avatar(props: Props): React.Node { const { avatarInfo, size } = props; const containerSizeStyle = React.useMemo(() => { if (size === 'XS') { return styles.xSmall; } else if (size === 'S') { return styles.small; } else if (size === 'M') { return styles.medium; } else if (size === 'L') { return styles.large; } return styles.xLarge; }, [size]); const emojiContainerStyle = React.useMemo(() => { const containerStyles = [styles.emojiContainer, containerSizeStyle]; if (avatarInfo.type === 'emoji') { const backgroundColor = { backgroundColor: `#${avatarInfo.color}` }; containerStyles.push(backgroundColor); } return containerStyles; }, [avatarInfo, containerSizeStyle]); const emojiSizeStyle = React.useMemo(() => { if (size === 'XS') { return styles.emojiXSmall; } else if (size === 'S') { return styles.emojiSmall; } else if (size === 'M') { return styles.emojiMedium; } else if (size === 'L') { return styles.emojiLarge; } return styles.emojiXLarge; }, [size]); const avatar = React.useMemo(() => { if (avatarInfo.type === 'image') { const avatarMediaInfo = { type: 'photo', uri: avatarInfo.uri, }; return ( ); } return ( {avatarInfo.emoji} ); }, [ avatarInfo.emoji, avatarInfo.type, avatarInfo.uri, containerSizeStyle, emojiContainerStyle, emojiSizeStyle, ]); return avatar; } const styles = StyleSheet.create({ emojiContainer: { alignItems: 'center', justifyContent: 'center', }, emojiLarge: { fontSize: 64, textAlign: 'center', }, emojiMedium: { fontSize: 28, textAlign: 'center', }, emojiSmall: { fontSize: 14, textAlign: 'center', }, emojiXLarge: { fontSize: 80, textAlign: 'center', }, emojiXSmall: { fontSize: 9, textAlign: 'center', }, imageContainer: { overflow: 'hidden', }, large: { borderRadius: 45, height: 90, width: 90, }, medium: { borderRadius: 20, height: 40, width: 40, }, small: { borderRadius: 12, height: 24, width: 24, }, xLarge: { borderRadius: 56, height: 112, width: 112, }, xSmall: { borderRadius: 8, height: 16, width: 16, }, }); export default Avatar; diff --git a/native/avatars/thread-avatar.react.js b/native/avatars/thread-avatar.react.js index b52bfe416..f318be649 100644 --- a/native/avatars/thread-avatar.react.js +++ b/native/avatars/thread-avatar.react.js @@ -1,52 +1,53 @@ // @flow import * as React from 'react'; import { useAvatarForThread, useENSResolvedAvatar, } from 'lib/shared/avatar-utils.js'; import { getSingleOtherUser } from 'lib/shared/thread-utils.js'; +import type { AvatarSize } from 'lib/types/avatar-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import type { RawThreadInfo, ThreadInfo, ResolvedThreadInfo, } from 'lib/types/thread-types.js'; -import Avatar, { type AvatarSize } from './avatar.react.js'; +import Avatar from './avatar.react.js'; import { useSelector } from '../redux/redux-utils.js'; type Props = { +threadInfo: RawThreadInfo | ThreadInfo | ResolvedThreadInfo, +size: AvatarSize, }; function ThreadAvatar(props: Props): React.Node { const { threadInfo, size } = props; const avatarInfo = useAvatarForThread(threadInfo); const viewerID = useSelector( state => state.currentUserInfo && state.currentUserInfo.id, ); let displayUserIDForThread; if (threadInfo.type === threadTypes.PRIVATE) { displayUserIDForThread = viewerID; } else if (threadInfo.type === threadTypes.PERSONAL) { displayUserIDForThread = getSingleOtherUser(threadInfo, viewerID); } const displayUser = useSelector(state => displayUserIDForThread ? state.userStore.userInfos[displayUserIDForThread] : null, ); const resolvedThreadAvatar = useENSResolvedAvatar(avatarInfo, displayUser); return ; } export default ThreadAvatar; diff --git a/native/avatars/user-avatar.react.js b/native/avatars/user-avatar.react.js index f8ffd27c3..f759d39d1 100644 --- a/native/avatars/user-avatar.react.js +++ b/native/avatars/user-avatar.react.js @@ -1,37 +1,40 @@ // @flow import * as React from 'react'; import { getAvatarForUser, useENSResolvedAvatar, } from 'lib/shared/avatar-utils.js'; -import type { GenericUserInfoWithAvatar } from 'lib/types/avatar-types.js'; +import type { + GenericUserInfoWithAvatar, + AvatarSize, +} from 'lib/types/avatar-types.js'; -import Avatar, { type AvatarSize } from './avatar.react.js'; +import Avatar from './avatar.react.js'; import { useSelector } from '../redux/redux-utils.js'; type Props = | { +userID: ?string, +size: AvatarSize } | { +userInfo: ?GenericUserInfoWithAvatar, +size: AvatarSize }; function UserAvatar(props: Props): React.Node { const { userID, userInfo: userInfoProp, size } = props; const userInfo = useSelector(state => { if (!userID) { return userInfoProp; } else if (userID === state.currentUserInfo?.id) { return state.currentUserInfo; } else { return state.userStore.userInfos[userID]; } }); const avatarInfo = getAvatarForUser(userInfo); const resolvedUserAvatar = useENSResolvedAvatar(avatarInfo, userInfo); return ; } export default UserAvatar;