diff --git a/lib/shared/reaction-utils.js b/lib/shared/reaction-utils.js index 10c2120ae..6506db788 100644 --- a/lib/shared/reaction-utils.js +++ b/lib/shared/reaction-utils.js @@ -1,104 +1,109 @@ // @flow import invariant from 'invariant'; import _sortBy from 'lodash/fp/sortBy'; +import * as React from 'react'; +import { useENSNames } from '../hooks/ens-cache'; import type { MessageReactionInfo } from '../selectors/chat-selectors'; import type { RobotextMessageInfo, ComposableMessageInfo, } from '../types/message-types'; import { threadPermissions, type ThreadInfo } from '../types/thread-types'; import { useSelector } from '../utils/redux-utils'; import { relationshipBlockedInEitherDirection } from './relationship-utils'; import { threadHasPermission } from './thread-utils'; import { stringForUserExplicit } from './user-utils'; function stringForReactionList( reactions: $ReadOnlyMap, ): string { const reactionText = []; for (const reaction of reactions.keys()) { const reactionInfo = reactions.get(reaction); invariant(reactionInfo, 'reactionInfo should be set'); reactionText.push(reaction); const { length: numberOfReacts } = reactionInfo.users; if (numberOfReacts <= 1) { continue; } reactionText.push(numberOfReacts > 9 ? '9+' : numberOfReacts.toString()); } return reactionText.join(' '); } type MessageReactionListInfo = { +id: string, +isViewer: boolean, +reaction: string, +username: string, }; -function createMessageReactionsList( +function useMessageReactionsList( reactions: $ReadOnlyMap, ): $ReadOnlyArray { - const result = []; - - for (const [reaction, reactionInfo] of reactions) { - reactionInfo.users.forEach(user => { - result.push({ - ...user, - username: stringForUserExplicit(user), - reaction, + const withoutENSNames = React.useMemo(() => { + const result = []; + + for (const [reaction, reactionInfo] of reactions) { + reactionInfo.users.forEach(user => { + result.push({ + ...user, + username: stringForUserExplicit(user), + reaction, + }); }); - }); - } - - const sortByNumReactions = (reactionInfo: MessageReactionListInfo) => { - const numOfReactions = reactions.get(reactionInfo.reaction)?.users.length; - return numOfReactions ? -numOfReactions : 0; - }; + } - return _sortBy( - ([sortByNumReactions, 'username']: $ReadOnlyArray< - ((reactionInfo: MessageReactionListInfo) => mixed) | string, - >), - )(result); + const sortByNumReactions = (reactionInfo: MessageReactionListInfo) => { + const numOfReactions = reactions.get(reactionInfo.reaction)?.users.length; + return numOfReactions ? -numOfReactions : 0; + }; + + return _sortBy( + ([sortByNumReactions, 'username']: $ReadOnlyArray< + ((reactionInfo: MessageReactionListInfo) => mixed) | string, + >), + )(result); + }, [reactions]); + return useENSNames(withoutENSNames); } function useCanCreateReactionFromMessage( threadInfo: ThreadInfo, targetMessageInfo: ComposableMessageInfo | RobotextMessageInfo, ): boolean { const targetMessageCreatorRelationship = useSelector( state => state.userStore.userInfos[targetMessageInfo.creator.id] ?.relationshipStatus, ); if ( !targetMessageInfo.id || threadInfo.sourceMessageID === targetMessageInfo.id ) { return false; } const creatorRelationshipHasBlock = targetMessageCreatorRelationship && relationshipBlockedInEitherDirection(targetMessageCreatorRelationship); const hasPermission = threadHasPermission( threadInfo, threadPermissions.VOICED, ); return hasPermission && !creatorRelationshipHasBlock; } export { stringForReactionList, - createMessageReactionsList, + useMessageReactionsList, useCanCreateReactionFromMessage, }; diff --git a/native/chat/message-reactions-modal.react.js b/native/chat/message-reactions-modal.react.js index 0b0a706ad..220249a87 100644 --- a/native/chat/message-reactions-modal.react.js +++ b/native/chat/message-reactions-modal.react.js @@ -1,141 +1,138 @@ // @flow import Icon from '@expo/vector-icons/FontAwesome'; import { useNavigation } from '@react-navigation/native'; import * as React from 'react'; import { View, Text, FlatList, TouchableHighlight } from 'react-native'; import type { MessageReactionInfo } from 'lib/selectors/chat-selectors'; -import { createMessageReactionsList } from 'lib/shared/reaction-utils'; +import { useMessageReactionsList } from 'lib/shared/reaction-utils'; import Modal from '../components/modal.react'; import type { RootNavigationProp } from '../navigation/root-navigator.react'; import type { NavigationRoute } from '../navigation/route-names'; import { useColors, useStyles } from '../themes/colors'; export type MessageReactionsModalParams = { +reactions: $ReadOnlyMap, }; type Props = { +navigation: RootNavigationProp<'MessageReactionsModal'>, +route: NavigationRoute<'MessageReactionsModal'>, }; function MessageReactionsModal(props: Props): React.Node { const { reactions } = props.route.params; const styles = useStyles(unboundStyles); const colors = useColors(); const navigation = useNavigation(); const safeAreaEdges = React.useMemo(() => ['top'], []); const close = React.useCallback(() => navigation.goBack(), [navigation]); - const reactionsListData = React.useMemo( - () => createMessageReactionsList(reactions), - [reactions], - ); + const reactionsListData = useMessageReactionsList(reactions); const renderItem = React.useCallback( ({ item }) => { return ( {item.username} {item.reaction} ); }, [ styles.reactionsListReactionText, styles.reactionsListRowContainer, styles.reactionsListUsernameText, ], ); const itemSeperator = React.useCallback(() => { return ; }, [styles.reactionsListItemSeperator]); return ( Reactions ); } const unboundStyles = { modalStyle: { // we need to set each margin property explicitly to override marginLeft: 0, marginRight: 0, marginBottom: 0, marginTop: 0, justifyContent: 'flex-end', flex: 0, borderWidth: 0, borderTopLeftRadius: 10, borderTopRightRadius: 10, }, modalContainerStyle: { justifyContent: 'flex-end', }, modalContentContainer: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24, marginTop: 8, }, reactionsListContentContainer: { paddingBottom: 16, }, reactionsListTitleText: { color: 'modalForegroundLabel', fontSize: 18, }, reactionsListRowContainer: { flexDirection: 'row', justifyContent: 'space-between', }, reactionsListUsernameText: { color: 'modalForegroundLabel', fontSize: 18, }, reactionsListReactionText: { fontSize: 18, }, reactionsListItemSeperator: { height: 16, }, closeButton: { borderRadius: 4, width: 18, height: 18, alignItems: 'center', }, closeIcon: { color: 'modalBackgroundSecondaryLabel', }, }; export default MessageReactionsModal; diff --git a/web/modals/chat/message-reactions-modal.react.js b/web/modals/chat/message-reactions-modal.react.js index 28c558ddc..24d22ffbf 100644 --- a/web/modals/chat/message-reactions-modal.react.js +++ b/web/modals/chat/message-reactions-modal.react.js @@ -1,37 +1,39 @@ // @flow import * as React from 'react'; import type { MessageReactionInfo } from 'lib/selectors/chat-selectors'; -import { createMessageReactionsList } from 'lib/shared/reaction-utils'; +import { useMessageReactionsList } from 'lib/shared/reaction-utils'; import Modal from '../modal.react'; import css from './message-reactions-modal.css'; type Props = { +onClose: () => void, +reactions: $ReadOnlyMap, }; function MessageReactionsModal(props: Props): React.Node { const { onClose, reactions } = props; - const reactionsList = React.useMemo(() => { - const messageReactionsList = createMessageReactionsList(reactions); - - return messageReactionsList.map(messageReactionUser => ( -
-
{messageReactionUser.username}
-
{messageReactionUser.reaction}
-
- )); - }, [reactions]); + const messageReactionsList = useMessageReactionsList(reactions); + + const reactionsList = React.useMemo( + () => + messageReactionsList.map(messageReactionUser => ( +
+
{messageReactionUser.username}
+
{messageReactionUser.reaction}
+
+ )), + [messageReactionsList], + ); return (
{reactionsList}
); } export default MessageReactionsModal;