diff --git a/lib/hooks/fc-cache.js b/lib/hooks/fc-cache.js index ffab3ee6f..acfe38598 100644 --- a/lib/hooks/fc-cache.js +++ b/lib/hooks/fc-cache.js @@ -1,184 +1,184 @@ // @flow import * as React from 'react'; import { NeynarClientContext } from '../components/neynar-client-provider.react.js'; import { getFCNames } from '../utils/farcaster-helpers.js'; type BaseFCInfo = { +fid?: ?string, +farcasterUsername?: ?string, ... }; export type UseFCNamesOptions = { +allAtOnce?: ?boolean, }; function useFCNames( users: $ReadOnlyArray, options?: ?UseFCNamesOptions, ): T[] { const neynarClientContext = React.useContext(NeynarClientContext); const fcCache = neynarClientContext?.fcCache; const allAtOnce = options?.allAtOnce ?? false; const cachedInfo = React.useMemo( () => users.map(user => { if (!user) { return user; } const { fid, farcasterUsername } = user; let cachedResult = null; if (farcasterUsername) { cachedResult = farcasterUsername; } else if (fid && fcCache) { cachedResult = fcCache.getCachedFarcasterUserForFID(fid)?.username; } return { input: user, fid, cachedResult, }; }), [users, fcCache], ); const [fetchedFIDs, setFetchedFIDs] = React.useState<$ReadOnlySet>( new Set(), ); const [farcasterUsernames, setFarcasterUsernames] = React.useState< $ReadOnlyMap, >(new Map()); React.useEffect(() => { if (!fcCache) { return; } const needFetchUsers: $ReadOnlyArray<{ +fid: string, +farcasterUsername?: ?string, }> = cachedInfo .map(user => { if (!user) { return null; } const { fid, cachedResult } = user; if (cachedResult || !fid || fetchedFIDs.has(fid)) { return null; } return { fid }; }) .filter(Boolean); if (needFetchUsers.length === 0) { return; } const needFetchFIDs = needFetchUsers.map(({ fid }) => fid); setFetchedFIDs(oldFetchedFIDs => { const newFetchedFIDs = new Set(oldFetchedFIDs); for (const fid of needFetchFIDs) { newFetchedFIDs.add(fid); } return newFetchedFIDs; }); if (allAtOnce) { void (async () => { const withFarcasterUsernames = await getFCNames( fcCache, needFetchUsers, ); setFarcasterUsernames(oldFarcasterUsernames => { const newFarcasterUsernames = new Map(oldFarcasterUsernames); for (let i = 0; i < withFarcasterUsernames.length; i++) { const fid = needFetchFIDs[i]; const result = withFarcasterUsernames[i].farcasterUsername; if (result) { newFarcasterUsernames.set(fid, result); } } return newFarcasterUsernames; }); })(); return; } for (const fid of needFetchFIDs) { void (async () => { const [result] = await fcCache.getFarcasterUsersForFIDs([fid]); if (!result) { return; } setFarcasterUsernames(oldFarcasterUsernames => { const newFarcasterUsernames = new Map(oldFarcasterUsernames); newFarcasterUsernames.set(fid, result.username); return newFarcasterUsernames; }); })(); } }, [cachedInfo, fetchedFIDs, fcCache, allAtOnce]); return React.useMemo( () => cachedInfo.map(user => { if (!user) { return user; } const { input, fid, cachedResult } = user; if (cachedResult) { return { ...input, farcasterUsername: cachedResult }; } else if (!fid) { return input; } const farcasterUsername = farcasterUsernames.get(fid); if (farcasterUsername) { return { ...input, farcasterUsername }; } return input; }), [cachedInfo, farcasterUsernames], ); } -function useFarcasterAvatarURL(fid: ?string): ?string { +function useFarcasterUserAvatarURL(fid: ?string): ?string { const neynarClientContext = React.useContext(NeynarClientContext); const fcCache = neynarClientContext?.fcCache; const cachedAvatarURL = React.useMemo(() => { if (!fid || !fcCache) { return null; } const cachedUser = fcCache.getCachedFarcasterUserForFID(fid); return cachedUser?.pfpURL ?? null; }, [fcCache, fid]); const [farcasterAvatarURL, setFarcasterAvatarURL] = React.useState(null); React.useEffect(() => { if (!fcCache || !fid || cachedAvatarURL) { return; } void (async () => { const [fetchedUser] = await fcCache.getFarcasterUsersForFIDs([fid]); const avatarURL = fetchedUser?.pfpURL; if (!avatarURL) { return; } setFarcasterAvatarURL(avatarURL); })(); }, [fcCache, cachedAvatarURL, fid]); return React.useMemo(() => { if (!fid) { return null; } else if (cachedAvatarURL) { return cachedAvatarURL; } else { return farcasterAvatarURL; } }, [fid, cachedAvatarURL, farcasterAvatarURL]); } -export { useFCNames, useFarcasterAvatarURL }; +export { useFCNames, useFarcasterUserAvatarURL }; diff --git a/lib/shared/avatar-utils.js b/lib/shared/avatar-utils.js index cda6096a9..038531dc3 100644 --- a/lib/shared/avatar-utils.js +++ b/lib/shared/avatar-utils.js @@ -1,388 +1,388 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import stringHash from 'string-hash'; import { getETHAddressForUserInfo } from './account-utils.js'; import { selectedThreadColors } from './color-utils.js'; import { threadOtherMembers } from './thread-utils.js'; import genesis from '../facts/genesis.js'; import { useENSAvatar } from '../hooks/ens-cache.js'; -import { useFarcasterAvatarURL } from '../hooks/fc-cache.js'; +import { useFarcasterUserAvatarURL } from '../hooks/fc-cache.js'; import type { ClientAvatar, ClientEmojiAvatar, ResolvedClientAvatar, } from '../types/avatar-types.js'; import type { ThreadInfo, RawThreadInfo, } from '../types/minimally-encoded-thread-permissions-types.js'; import { threadTypeIsPersonal, threadTypeIsPrivate, } from '../types/thread-types-enum.js'; import type { UserInfos } from '../types/user-types.js'; const defaultAnonymousUserEmojiAvatar: ClientEmojiAvatar = { color: selectedThreadColors[4], emoji: 'ðŸ‘Ī', type: 'emoji', }; const defaultEmojiAvatars: $ReadOnlyArray = [ { color: selectedThreadColors[0], emoji: '😀', type: 'emoji' }, { color: selectedThreadColors[1], emoji: '😃', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '😄', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '😁', type: 'emoji' }, { color: selectedThreadColors[4], emoji: '😆', type: 'emoji' }, { color: selectedThreadColors[6], emoji: '🙂', type: 'emoji' }, { color: selectedThreadColors[6], emoji: '😉', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '😊', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '😇', type: 'emoji' }, { color: selectedThreadColors[9], emoji: 'ðŸĨ°', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '😍', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸĪĐ', type: 'emoji' }, { color: selectedThreadColors[3], emoji: 'ðŸĨģ', type: 'emoji' }, { color: selectedThreadColors[9], emoji: '😝', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '😎', type: 'emoji' }, { color: selectedThreadColors[4], emoji: '🧐', type: 'emoji' }, { color: selectedThreadColors[7], emoji: 'ðŸĨļ', type: 'emoji' }, { color: selectedThreadColors[3], emoji: 'ðŸĪ—', type: 'emoji' }, { color: selectedThreadColors[0], emoji: 'ðŸ˜Ī', type: 'emoji' }, { color: selectedThreadColors[0], emoji: 'ðŸĪŊ', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸĪ”', type: 'emoji' }, { color: selectedThreadColors[8], emoji: 'ðŸŦĄ', type: 'emoji' }, { color: selectedThreadColors[9], emoji: 'ðŸĪŦ', type: 'emoji' }, { color: selectedThreadColors[3], emoji: 'ðŸ˜Ū', type: 'emoji' }, { color: selectedThreadColors[4], emoji: 'ðŸ˜ē', type: 'emoji' }, { color: selectedThreadColors[5], emoji: 'ðŸĪ ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: 'ðŸĪ‘', type: 'emoji' }, { color: selectedThreadColors[7], emoji: 'ðŸ‘Đ‍🚀', type: 'emoji' }, { color: selectedThreadColors[2], emoji: 'ðŸĨ·', type: 'emoji' }, { color: selectedThreadColors[5], emoji: 'ðŸ‘ŧ', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸ‘ū', type: 'emoji' }, { color: selectedThreadColors[2], emoji: 'ðŸĪ–', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '😚', type: 'emoji' }, { color: selectedThreadColors[4], emoji: 'ðŸ˜ļ', type: 'emoji' }, { color: selectedThreadColors[9], emoji: 'ðŸ˜đ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: 'ðŸ˜ŧ', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸŽĐ', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '👑', type: 'emoji' }, { color: selectedThreadColors[9], emoji: 'ðŸķ', type: 'emoji' }, { color: selectedThreadColors[0], emoji: 'ðŸą', type: 'emoji' }, { color: selectedThreadColors[2], emoji: '🐭', type: 'emoji' }, { color: selectedThreadColors[3], emoji: 'ðŸđ', type: 'emoji' }, { color: selectedThreadColors[4], emoji: '🐰', type: 'emoji' }, { color: selectedThreadColors[5], emoji: 'ðŸŧ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: '🐞', type: 'emoji' }, { color: selectedThreadColors[7], emoji: 'ðŸŧ‍❄ïļ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: 'ðŸĻ', type: 'emoji' }, { color: selectedThreadColors[9], emoji: 'ðŸŊ', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸĶ', type: 'emoji' }, { color: selectedThreadColors[2], emoji: 'ðŸļ', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🐔', type: 'emoji' }, { color: selectedThreadColors[4], emoji: '🐧', type: 'emoji' }, { color: selectedThreadColors[5], emoji: 'ðŸĶ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: 'ðŸĪ', type: 'emoji' }, { color: selectedThreadColors[7], emoji: 'ðŸĶ„', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '🐝', type: 'emoji' }, { color: selectedThreadColors[9], emoji: 'ðŸĶ‹', type: 'emoji' }, { color: selectedThreadColors[5], emoji: '🐎', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸģ', type: 'emoji' }, { color: selectedThreadColors[2], emoji: '🐋', type: 'emoji' }, { color: selectedThreadColors[3], emoji: 'ðŸĶˆ', type: 'emoji' }, { color: selectedThreadColors[4], emoji: 'ðŸĶ­', type: 'emoji' }, { color: selectedThreadColors[5], emoji: '🐘', type: 'emoji' }, { color: selectedThreadColors[6], emoji: 'ðŸĶ›', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🐐', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '🐓', type: 'emoji' }, { color: selectedThreadColors[9], emoji: 'ðŸĶƒ', type: 'emoji' }, { color: selectedThreadColors[0], emoji: 'ðŸĶĐ', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸĶ”', type: 'emoji' }, { color: selectedThreadColors[2], emoji: '🐅', type: 'emoji' }, { color: selectedThreadColors[1], emoji: '🐆', type: 'emoji' }, { color: selectedThreadColors[5], emoji: 'ðŸĶ“', type: 'emoji' }, { color: selectedThreadColors[4], emoji: 'ðŸĶ’', type: 'emoji' }, { color: selectedThreadColors[6], emoji: 'ðŸĶ˜', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🐎', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '🐕', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸĐ', type: 'emoji' }, { color: selectedThreadColors[0], emoji: 'ðŸĶŪ', type: 'emoji' }, { color: selectedThreadColors[1], emoji: '🐈', type: 'emoji' }, { color: selectedThreadColors[2], emoji: 'ðŸĶš', type: 'emoji' }, { color: selectedThreadColors[4], emoji: 'ðŸĶœ', type: 'emoji' }, { color: selectedThreadColors[4], emoji: 'ðŸĶĒ', type: 'emoji' }, { color: selectedThreadColors[5], emoji: '🕊ïļ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: '🐇', type: 'emoji' }, { color: selectedThreadColors[7], emoji: 'ðŸĶĶ', type: 'emoji' }, { color: selectedThreadColors[8], emoji: 'ðŸŋïļ', type: 'emoji' }, { color: selectedThreadColors[9], emoji: '🐉', type: 'emoji' }, { color: selectedThreadColors[0], emoji: 'ðŸŒī', type: 'emoji' }, { color: selectedThreadColors[7], emoji: 'ðŸŒą', type: 'emoji' }, { color: selectedThreadColors[2], emoji: '☘ïļ', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '🍀', type: 'emoji' }, { color: selectedThreadColors[4], emoji: '🍄', type: 'emoji' }, { color: selectedThreadColors[5], emoji: 'ðŸŒŋ', type: 'emoji' }, { color: selectedThreadColors[8], emoji: 'ðŸŠī', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🍁', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '💐', type: 'emoji' }, { color: selectedThreadColors[9], emoji: '🌷', type: 'emoji' }, { color: selectedThreadColors[0], emoji: 'ðŸŒđ', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸŒļ', type: 'emoji' }, { color: selectedThreadColors[7], emoji: 'ðŸŒŧ', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '⭐', type: 'emoji' }, { color: selectedThreadColors[4], emoji: '🌟', type: 'emoji' }, { color: selectedThreadColors[5], emoji: '🍏', type: 'emoji' }, { color: selectedThreadColors[6], emoji: '🍎', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🍐', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '🍊', type: 'emoji' }, { color: selectedThreadColors[9], emoji: '🍋', type: 'emoji' }, { color: selectedThreadColors[0], emoji: '🍓', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸŦ', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '🍈', type: 'emoji' }, { color: selectedThreadColors[2], emoji: '🍒', type: 'emoji' }, { color: selectedThreadColors[4], emoji: 'ðŸĨ­', type: 'emoji' }, { color: selectedThreadColors[9], emoji: '🍍', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸĨ', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🍅', type: 'emoji' }, { color: selectedThreadColors[8], emoji: 'ðŸĨĶ', type: 'emoji' }, { color: selectedThreadColors[9], emoji: 'ðŸĨ•', type: 'emoji' }, { color: selectedThreadColors[0], emoji: 'ðŸĨ', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸĨŊ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: '🍞', type: 'emoji' }, { color: selectedThreadColors[3], emoji: 'ðŸĨ–', type: 'emoji' }, { color: selectedThreadColors[4], emoji: 'ðŸĨĻ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: '🧀', type: 'emoji' }, { color: selectedThreadColors[6], emoji: 'ðŸĨž', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🧇', type: 'emoji' }, { color: selectedThreadColors[8], emoji: 'ðŸĨ“', type: 'emoji' }, { color: selectedThreadColors[9], emoji: '🍔', type: 'emoji' }, { color: selectedThreadColors[0], emoji: '🍟', type: 'emoji' }, { color: selectedThreadColors[1], emoji: '🍕', type: 'emoji' }, { color: selectedThreadColors[2], emoji: 'ðŸĨ—', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '🍝', type: 'emoji' }, { color: selectedThreadColors[4], emoji: '🍜', type: 'emoji' }, { color: selectedThreadColors[5], emoji: 'ðŸē', type: 'emoji' }, { color: selectedThreadColors[6], emoji: '🍛', type: 'emoji' }, { color: selectedThreadColors[7], emoji: 'ðŸĢ', type: 'emoji' }, { color: selectedThreadColors[8], emoji: 'ðŸą', type: 'emoji' }, { color: selectedThreadColors[9], emoji: 'ðŸĨŸ', type: 'emoji' }, { color: selectedThreadColors[0], emoji: 'ðŸĪ', type: 'emoji' }, { color: selectedThreadColors[1], emoji: '🍙', type: 'emoji' }, { color: selectedThreadColors[2], emoji: '🍚', type: 'emoji' }, { color: selectedThreadColors[3], emoji: 'ðŸĨ', type: 'emoji' }, { color: selectedThreadColors[4], emoji: 'ðŸĶ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: '🧁', type: 'emoji' }, { color: selectedThreadColors[5], emoji: '🍭', type: 'emoji' }, { color: selectedThreadColors[7], emoji: 'ðŸĐ', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '🍊', type: 'emoji' }, { color: selectedThreadColors[9], emoji: '☕ïļ', type: 'emoji' }, { color: selectedThreadColors[0], emoji: 'ðŸĩ', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'âš―ïļ', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🏀', type: 'emoji' }, { color: selectedThreadColors[9], emoji: '🏈', type: 'emoji' }, { color: selectedThreadColors[4], emoji: 'âšūïļ', type: 'emoji' }, { color: selectedThreadColors[0], emoji: 'ðŸĨŽ', type: 'emoji' }, { color: selectedThreadColors[8], emoji: 'ðŸŽū', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🏐', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '🏉', type: 'emoji' }, { color: selectedThreadColors[3], emoji: 'ðŸŽą', type: 'emoji' }, { color: selectedThreadColors[0], emoji: '🏆', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸŽĻ', type: 'emoji' }, { color: selectedThreadColors[2], emoji: 'ðŸŽĪ', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '🎧', type: 'emoji' }, { color: selectedThreadColors[4], emoji: '🎞', type: 'emoji' }, { color: selectedThreadColors[5], emoji: 'ðŸŽđ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: 'ðŸĨ', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🎷', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '🎚', type: 'emoji' }, { color: selectedThreadColors[9], emoji: 'ðŸŽļ', type: 'emoji' }, { color: selectedThreadColors[0], emoji: '🊕', type: 'emoji' }, { color: selectedThreadColors[1], emoji: 'ðŸŽŧ', type: 'emoji' }, { color: selectedThreadColors[2], emoji: 'ðŸŽē', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '♟ïļ', type: 'emoji' }, { color: selectedThreadColors[4], emoji: 'ðŸŽŪ', type: 'emoji' }, { color: selectedThreadColors[5], emoji: '🚗', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '🚙', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🚌', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '🏎ïļ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: 'ðŸ›ŧ', type: 'emoji' }, { color: selectedThreadColors[0], emoji: '🚚', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🚛', type: 'emoji' }, { color: selectedThreadColors[2], emoji: '🚘', type: 'emoji' }, { color: selectedThreadColors[0], emoji: '🚀', type: 'emoji' }, { color: selectedThreadColors[4], emoji: '🚁', type: 'emoji' }, { color: selectedThreadColors[5], emoji: 'ðŸ›ķ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: 'â›ĩïļ', type: 'emoji' }, { color: selectedThreadColors[7], emoji: 'ðŸšĪ', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '⚓', type: 'emoji' }, { color: selectedThreadColors[6], emoji: '🏰', type: 'emoji' }, { color: selectedThreadColors[0], emoji: 'ðŸŽĄ', type: 'emoji' }, { color: selectedThreadColors[1], emoji: '💎', type: 'emoji' }, { color: selectedThreadColors[2], emoji: 'ðŸ”Ū', type: 'emoji' }, { color: selectedThreadColors[9], emoji: '💈', type: 'emoji' }, { color: selectedThreadColors[4], emoji: 'ðŸ§ļ', type: 'emoji' }, { color: selectedThreadColors[9], emoji: '🎊', type: 'emoji' }, { color: selectedThreadColors[4], emoji: '🎉', type: 'emoji' }, { color: selectedThreadColors[7], emoji: 'ðŸŠĐ', type: 'emoji' }, { color: selectedThreadColors[8], emoji: '🚂', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '🚆', type: 'emoji' }, { color: selectedThreadColors[1], emoji: '🚊', type: 'emoji' }, { color: selectedThreadColors[1], emoji: '🛰ïļ', type: 'emoji' }, { color: selectedThreadColors[7], emoji: '🏠', type: 'emoji' }, { color: selectedThreadColors[3], emoji: '⛰ïļ', type: 'emoji' }, { color: selectedThreadColors[4], emoji: '🏔ïļ', type: 'emoji' }, { color: selectedThreadColors[5], emoji: 'ðŸ—ŧ', type: 'emoji' }, { color: selectedThreadColors[6], emoji: '🏛ïļ', type: 'emoji' }, { color: selectedThreadColors[7], emoji: 'â›Đïļ', type: 'emoji' }, { color: selectedThreadColors[8], emoji: 'ðŸ§ē', type: 'emoji' }, { color: selectedThreadColors[9], emoji: '🎁', type: 'emoji' }, ]; function getRandomDefaultEmojiAvatar(): ClientEmojiAvatar { const randomIndex = Math.floor(Math.random() * defaultEmojiAvatars.length); return defaultEmojiAvatars[randomIndex]; } function getDefaultAvatar(hashKey: string, color?: string): ClientEmojiAvatar { let key = hashKey; const barPosition = key.indexOf('|'); if (barPosition !== -1) { key = key.slice(barPosition + 1); } const avatarIndex = stringHash(key) % defaultEmojiAvatars.length; return { ...defaultEmojiAvatars[avatarIndex], color: color ? color : defaultEmojiAvatars[avatarIndex].color, }; } function getAvatarForUser( usernameAndAvatar: ?{ +username?: ?string, +avatar?: ?ClientAvatar, ... }, ): ClientAvatar { if (usernameAndAvatar?.avatar) { return usernameAndAvatar.avatar; } if (!usernameAndAvatar?.username) { return defaultAnonymousUserEmojiAvatar; } return getDefaultAvatar(usernameAndAvatar.username); } function getUserAvatarForThread( threadInfo: RawThreadInfo | ThreadInfo, viewerID: ?string, userInfos: UserInfos, ): ClientAvatar { if (threadTypeIsPrivate(threadInfo.type)) { invariant(viewerID, 'viewerID should be set for private threads'); return getAvatarForUser(userInfos[viewerID]); } invariant( threadTypeIsPersonal(threadInfo.type), 'threadInfo should be personal', ); const memberInfos = threadOtherMembers(threadInfo.members, viewerID) .map(member => userInfos[member.id]) .filter(Boolean); if (memberInfos.length === 0) { return defaultAnonymousUserEmojiAvatar; } return getAvatarForUser(memberInfos[0]); } function getAvatarForThread( thread: RawThreadInfo | ThreadInfo, containingThreadInfo: ?ThreadInfo, ): ClientAvatar { if (thread.avatar) { return thread.avatar; } if (containingThreadInfo && containingThreadInfo.id !== genesis().id) { return containingThreadInfo.avatar ? containingThreadInfo.avatar : getDefaultAvatar(containingThreadInfo.id, containingThreadInfo.color); } return getDefaultAvatar(thread.id, thread.color); } function useResolvedUserAvatar( userAvatarInfo: ClientAvatar, usernameAndFID: ?{ +username?: ?string, +farcasterID?: ?string, ... }, ): ResolvedClientAvatar { const ethAddress = React.useMemo( () => getETHAddressForUserInfo(usernameAndFID), [usernameAndFID], ); const ensAvatarURI = useENSAvatar(ethAddress); const fid = usernameAndFID?.farcasterID; - const farcasterAvatarURL = useFarcasterAvatarURL(fid); + const farcasterAvatarURL = useFarcasterUserAvatarURL(fid); const resolvedAvatar = React.useMemo(() => { if (userAvatarInfo.type !== 'ens' && userAvatarInfo.type !== 'farcaster') { return userAvatarInfo; } if (ensAvatarURI) { return { type: 'image', uri: ensAvatarURI, }; } else if (farcasterAvatarURL) { return { type: 'image', uri: farcasterAvatarURL, }; } return defaultAnonymousUserEmojiAvatar; }, [userAvatarInfo, ensAvatarURI, farcasterAvatarURL]); return resolvedAvatar; } function useResolvedThreadAvatar( threadAvatarInfo: ClientAvatar, displayUser: ?{ +username?: ?string, +farcasterID?: ?string, ... }, ): ResolvedClientAvatar { const resolvedUserAvatar = useResolvedUserAvatar( threadAvatarInfo, displayUser, ); if ( threadAvatarInfo.type !== 'ens' && threadAvatarInfo.type !== 'farcaster' ) { return threadAvatarInfo; } return resolvedUserAvatar; } export { defaultAnonymousUserEmojiAvatar, defaultEmojiAvatars, getRandomDefaultEmojiAvatar, getDefaultAvatar, getAvatarForUser, getUserAvatarForThread, getAvatarForThread, useResolvedUserAvatar, useResolvedThreadAvatar, }; diff --git a/native/avatars/edit-user-avatar.react.js b/native/avatars/edit-user-avatar.react.js index fc6fb6b88..6991e9f1f 100644 --- a/native/avatars/edit-user-avatar.react.js +++ b/native/avatars/edit-user-avatar.react.js @@ -1,178 +1,178 @@ // @flow import { useNavigation } from '@react-navigation/native'; import invariant from 'invariant'; import * as React from 'react'; import { ActivityIndicator, TouchableOpacity, View } from 'react-native'; import { EditUserAvatarContext } from 'lib/components/edit-user-avatar-provider.react.js'; import { useENSAvatar } from 'lib/hooks/ens-cache.js'; -import { useFarcasterAvatarURL } from 'lib/hooks/fc-cache.js'; +import { useFarcasterUserAvatarURL } from 'lib/hooks/fc-cache.js'; import { getETHAddressForUserInfo } from 'lib/shared/account-utils.js'; import type { GenericUserInfoWithAvatar } from 'lib/types/avatar-types.js'; import { useNativeSetUserAvatar, useSelectFromGalleryAndUpdateUserAvatar, useShowAvatarActionSheet, } from './avatar-hooks.js'; import EditAvatarBadge from './edit-avatar-badge.react.js'; import UserAvatar from './user-avatar.react.js'; import { EmojiUserAvatarCreationRouteName, UserAvatarCameraModalRouteName, EmojiAvatarSelectionRouteName, RegistrationUserAvatarCameraModalRouteName, } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStyles } from '../themes/colors.js'; type Props = | { +userID: ?string, +disabled?: boolean, +fid: ?string } | { +userInfo: ?GenericUserInfoWithAvatar, +disabled?: boolean, +prefetchedENSAvatarURI: ?string, +prefetchedFarcasterAvatarURL: ?string, +fid: ?string, }; function EditUserAvatar(props: Props): React.Node { const editUserAvatarContext = React.useContext(EditUserAvatarContext); invariant(editUserAvatarContext, 'editUserAvatarContext should be set'); const { userAvatarSaveInProgress, getRegistrationModeEnabled } = editUserAvatarContext; const nativeSetUserAvatar = useNativeSetUserAvatar(); const selectFromGalleryAndUpdateUserAvatar = useSelectFromGalleryAndUpdateUserAvatar(); const currentUserInfo = useSelector(state => state.currentUserInfo); const userInfoProp = props.userInfo; const userInfo: ?GenericUserInfoWithAvatar = userInfoProp ?? currentUserInfo; const ethAddress = React.useMemo( () => getETHAddressForUserInfo(userInfo), [userInfo], ); const fetchedENSAvatarURI = useENSAvatar(ethAddress); const ensAvatarURI = fetchedENSAvatarURI ?? props.prefetchedENSAvatarURI; const fid = props.fid; - const fetchedFarcasterAvatarURL = useFarcasterAvatarURL(fid); + const fetchedFarcasterAvatarURL = useFarcasterUserAvatarURL(fid); const farcasterAvatarURL = fetchedFarcasterAvatarURL ?? props.prefetchedFarcasterAvatarURL; const { navigate } = useNavigation(); const usernameOrEthAddress = userInfo?.username; const navigateToEmojiSelection = React.useCallback(() => { if (!getRegistrationModeEnabled()) { navigate(EmojiUserAvatarCreationRouteName); return; } navigate<'EmojiAvatarSelection'>({ name: EmojiAvatarSelectionRouteName, params: { usernameOrEthAddress }, }); }, [navigate, getRegistrationModeEnabled, usernameOrEthAddress]); const navigateToCamera = React.useCallback(() => { navigate( getRegistrationModeEnabled() ? RegistrationUserAvatarCameraModalRouteName : UserAvatarCameraModalRouteName, ); }, [navigate, getRegistrationModeEnabled]); const setENSUserAvatar = React.useCallback( () => nativeSetUserAvatar({ type: 'ens' }), [nativeSetUserAvatar], ); const setFarcasterUserAvatar = React.useCallback( () => nativeSetUserAvatar({ type: 'farcaster' }), [nativeSetUserAvatar], ); const removeUserAvatar = React.useCallback( () => nativeSetUserAvatar({ type: 'remove' }), [nativeSetUserAvatar], ); const hasCurrentAvatar = !!userInfo?.avatar; const actionSheetConfig = React.useMemo(() => { const configOptions = [ { id: 'emoji', onPress: navigateToEmojiSelection }, { id: 'image', onPress: selectFromGalleryAndUpdateUserAvatar }, { id: 'camera', onPress: navigateToCamera }, ]; if (ensAvatarURI) { configOptions.push({ id: 'ens', onPress: setENSUserAvatar }); } if (farcasterAvatarURL) { configOptions.push({ id: 'farcaster', onPress: setFarcasterUserAvatar }); } if (hasCurrentAvatar) { configOptions.push({ id: 'remove', onPress: removeUserAvatar }); } return configOptions; }, [ navigateToEmojiSelection, selectFromGalleryAndUpdateUserAvatar, navigateToCamera, ensAvatarURI, farcasterAvatarURL, hasCurrentAvatar, setENSUserAvatar, setFarcasterUserAvatar, removeUserAvatar, ]); const showAvatarActionSheet = useShowAvatarActionSheet(actionSheetConfig); const styles = useStyles(unboundStyles); let spinner; if (userAvatarSaveInProgress) { spinner = ( ); } const { userID } = props; const userAvatar = userID ? ( ) : ( ); const { disabled } = props; return ( {userAvatar} {spinner} {!disabled ? : null} ); } const unboundStyles = { spinnerContainer: { position: 'absolute', alignItems: 'center', justifyContent: 'center', top: 0, bottom: 0, left: 0, right: 0, }, }; export default EditUserAvatar; diff --git a/web/avatars/edit-user-avatar-menu.react.js b/web/avatars/edit-user-avatar-menu.react.js index d60e29ee5..db26ddb35 100644 --- a/web/avatars/edit-user-avatar-menu.react.js +++ b/web/avatars/edit-user-avatar-menu.react.js @@ -1,188 +1,188 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { EditUserAvatarContext } from 'lib/components/edit-user-avatar-provider.react.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import SWMansionIcon from 'lib/components/swmansion-icon.react.js'; import { useENSAvatar } from 'lib/hooks/ens-cache.js'; -import { useFarcasterAvatarURL } from 'lib/hooks/fc-cache.js'; +import { useFarcasterUserAvatarURL } from 'lib/hooks/fc-cache.js'; import { getETHAddressForUserInfo } from 'lib/shared/account-utils.js'; import { useCurrentUserFID } from 'lib/utils/farcaster-utils.js'; import { useUploadAvatarMedia } from './avatar-hooks.react.js'; import css from './edit-avatar-menu.css'; import UserEmojiAvatarSelectionModal from './user-emoji-avatar-selection-modal.react.js'; import CommIcon from '../comm-icon.react.js'; import MenuItem from '../components/menu-item.react.js'; import Menu from '../components/menu.react.js'; import { allowedMimeTypeString } from '../media/file-utils.js'; import { useSelector } from '../redux/redux-utils.js'; const editIcon = (
); function EditUserAvatarMenu(): React.Node { const currentUserInfo = useSelector(state => state.currentUserInfo); const currentUserFID = useCurrentUserFID(); const ethAddress: ?string = React.useMemo( () => getETHAddressForUserInfo(currentUserInfo), [currentUserInfo], ); const ensAvatarURI: ?string = useENSAvatar(ethAddress); - const farcasterAvatarURL = useFarcasterAvatarURL(currentUserFID); + const farcasterAvatarURL = useFarcasterUserAvatarURL(currentUserFID); const editUserAvatarContext = React.useContext(EditUserAvatarContext); invariant(editUserAvatarContext, 'editUserAvatarContext should be set'); const { baseSetUserAvatar } = editUserAvatarContext; const removeUserAvatar = React.useCallback( () => baseSetUserAvatar({ type: 'remove' }), [baseSetUserAvatar], ); const { pushModal } = useModalContext(); const openEmojiSelectionModal = React.useCallback( () => pushModal(), [pushModal], ); const emojiMenuItem = React.useMemo( () => ( ), [openEmojiSelectionModal], ); const imageInputRef = React.useRef(); const onImageMenuItemClicked = React.useCallback( () => imageInputRef.current?.click(), [], ); const uploadAvatarMedia = useUploadAvatarMedia(); const onImageSelected = React.useCallback( async (event: SyntheticEvent) => { const { target } = event; invariant(target instanceof HTMLInputElement, 'target not input'); const uploadResult = await uploadAvatarMedia(target.files[0]); await baseSetUserAvatar(uploadResult); }, [baseSetUserAvatar, uploadAvatarMedia], ); const imageMenuItem = React.useMemo( () => ( ), [onImageMenuItemClicked], ); const setENSUserAvatar = React.useCallback( () => baseSetUserAvatar({ type: 'ens' }), [baseSetUserAvatar], ); const ethereumIcon = React.useMemo( () => , [], ); const ensMenuItem = React.useMemo( () => ( ), [ethereumIcon, setENSUserAvatar], ); const setFarcasterUserAvatar = React.useCallback( () => baseSetUserAvatar({ type: 'farcaster' }), [baseSetUserAvatar], ); const farcasterIcon = React.useMemo( () => , [], ); const farcasterMenuItem = React.useMemo( () => ( ), [farcasterIcon, setFarcasterUserAvatar], ); const removeMenuItem = React.useMemo( () => ( ), [removeUserAvatar], ); const menuItems = React.useMemo(() => { const items = [emojiMenuItem, imageMenuItem]; if (ensAvatarURI) { items.push(ensMenuItem); } if (farcasterAvatarURL) { items.push(farcasterMenuItem); } if (currentUserInfo?.avatar) { items.push(removeMenuItem); } return items; }, [ emojiMenuItem, imageMenuItem, ensAvatarURI, farcasterAvatarURL, currentUserInfo?.avatar, ensMenuItem, farcasterMenuItem, removeMenuItem, ]); return (
{menuItems}
); } export default EditUserAvatarMenu;