From 2d0f8bf4b6b99edbd42c1488d9361d9e5448ea5a Mon Sep 17 00:00:00 2001 From: Thierry Date: Thu, 24 Oct 2024 14:52:57 -0400 Subject: [PATCH 1/9] refactor and fix modal --- components/Chat/Chat.tsx | 152 +++++----- components/Chat/Message/Message.tsx | 2 +- components/Chat/Message/MessageReactions.tsx | 270 ------------------ .../MessageReactions/MessageReactions.tsx | 106 +++++++ .../MessageReactions.types.ts | 5 + .../MessageReactions.store.tsx | 13 + .../MessageReactionsDrawer.tsx | 94 ++++++ .../MessageReactionsDrawer.utils.ts | 16 ++ .../useMessageReactionsRolledup.ts | 69 +++++ .../BottomSheetBackdropOpacity.tsx | 16 +- .../BottomSheet/BottomSheetModal.tsx | 12 +- ios/Podfile.lock | 2 +- 12 files changed, 410 insertions(+), 347 deletions(-) delete mode 100644 components/Chat/Message/MessageReactions.tsx create mode 100644 components/Chat/Message/MessageReactions/MessageReactions.tsx create mode 100644 components/Chat/Message/MessageReactions/MessageReactions.types.ts create mode 100644 components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx create mode 100644 components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx create mode 100644 components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.utils.ts create mode 100644 components/Chat/Message/MessageReactions/useMessageReactionsRolledup.ts diff --git a/components/Chat/Chat.tsx b/components/Chat/Chat.tsx index fc46d0ec9..556ec532c 100644 --- a/components/Chat/Chat.tsx +++ b/components/Chat/Chat.tsx @@ -52,6 +52,7 @@ import { getProfile, getProfileData } from "../../utils/profile"; import { UUID_REGEX } from "../../utils/regex"; import { isContentType } from "../../utils/xmtpRN/contentTypes"; import { Recommendation } from "../Recommendations/Recommendation"; +import { MessageReactionsDrawer } from "./Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer"; const usePeerSocials = () => { const conversation = useConversationContext("conversation"); @@ -430,81 +431,86 @@ export function Chat() { }, [onReadyToFocus]); return ( - - - {conversation && listArray.length > 0 && !isBlockedPeer && ( - { - if (r) { - messageListRef.current = r; + <> + + + {conversation && listArray.length > 0 && !isBlockedPeer && ( + { + if (r) { + messageListRef.current = r; + } + }} + keyboardDismissMode="interactive" + automaticallyAdjustContentInsets={false} + contentInsetAdjustmentBehavior="never" + // Causes a glitch on Android, no sure we need it for now + // maintainVisibleContentPosition={{ + // minIndexForVisible: 0, + // autoscrollToTopThreshold: 100, + // }} + estimatedListSize={ + isSplitScreen ? undefined : Dimensions.get("screen") } - }} - keyboardDismissMode="interactive" - automaticallyAdjustContentInsets={false} - contentInsetAdjustmentBehavior="never" - // Causes a glitch on Android, no sure we need it for now - // maintainVisibleContentPosition={{ - // minIndexForVisible: 0, - // autoscrollToTopThreshold: 100, - // }} - estimatedListSize={ - isSplitScreen ? undefined : Dimensions.get("screen") - } - inverted - keyExtractor={keyExtractor} - getItemType={getItemType(framesStore)} - keyboardShouldPersistTaps="handled" - estimatedItemSize={80} - // Size glitch on Android - showsVerticalScrollIndicator={Platform.OS === "ios"} - pointerEvents="auto" - ListFooterComponent={ListFooterComponent} - /> + inverted + keyExtractor={keyExtractor} + getItemType={getItemType(framesStore)} + keyboardShouldPersistTaps="handled" + estimatedItemSize={80} + // Size glitch on Android + showsVerticalScrollIndicator={Platform.OS === "ios"} + pointerEvents="auto" + ListFooterComponent={ListFooterComponent} + /> + )} + {showPlaceholder && !conversation?.isGroup && ( + + )} + {showPlaceholder && conversation?.isGroup && ( + + )} + {conversation?.isGroup ? : } + + {showChatInput && ( + <> + + {!transactionMode && } + {transactionMode && } + + + )} - {showPlaceholder && !conversation?.isGroup && ( - - )} - {showPlaceholder && conversation?.isGroup && ( - - )} - {conversation?.isGroup ? : } - - {showChatInput && ( - <> - - {!transactionMode && } - {transactionMode && } - - - - )} - + + + ); } diff --git a/components/Chat/Message/Message.tsx b/components/Chat/Message/Message.tsx index 82979ac00..e498045f2 100644 --- a/components/Chat/Message/Message.tsx +++ b/components/Chat/Message/Message.tsx @@ -29,7 +29,7 @@ import Animated, { } from "react-native-reanimated"; import ChatMessageActions from "./MessageActions"; -import { ChatMessageReactions } from "./MessageReactions"; +import { ChatMessageReactions } from "./MessageReactions/MessageReactions"; import MessageStatus from "./MessageStatus"; import { currentAccount, diff --git a/components/Chat/Message/MessageReactions.tsx b/components/Chat/Message/MessageReactions.tsx deleted file mode 100644 index 3c6364835..000000000 --- a/components/Chat/Message/MessageReactions.tsx +++ /dev/null @@ -1,270 +0,0 @@ -import { useCurrentAccount } from "@data/store/accountsStore"; -import { createBottomSheetModalRef } from "@design-system/BottomSheet/BottomSheet.utils"; -import { BottomSheetContentContainer } from "@design-system/BottomSheet/BottomSheetContentContainer"; -import { BottomSheetHeader } from "@design-system/BottomSheet/BottomSheetHeader"; -import { BottomSheetModal } from "@design-system/BottomSheet/BottomSheetModal"; -import { HStack } from "@design-system/HStack"; -import { ScrollView } from "@design-system/ScrollView"; -import { Text } from "@design-system/Text"; -import { VStack } from "@design-system/VStack"; -import { - BottomSheetBackdrop, - BottomSheetScrollView, -} from "@gorhom/bottom-sheet"; -import { BottomSheetDefaultBackdropProps } from "@gorhom/bottom-sheet/lib/typescript/components/bottomSheetBackdrop/types"; -import { useAppTheme } from "@theme/useAppTheme"; -import { MessageReaction } from "@utils/reactions"; -import { memo, useCallback, useEffect, useMemo, useState } from "react"; -import { StyleSheet, TouchableHighlight } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; - -import { MessageToDisplay } from "./Message"; - -const MAX_REACTION_EMOJIS_SHOWN = 3; - -type Props = { - message: MessageToDisplay; - reactions: { - [senderAddress: string]: MessageReaction[]; - }; -}; - -type ReactionDetails = { - content: string; - count: number; - userReacted: boolean; - reactors: string[]; - firstReactionTime: number; -}; - -type RolledUpReactions = { - emojis: string[]; - totalReactions: number; - userReacted: boolean; - details: { [content: string]: ReactionDetails }; -}; - -export const ChatMessageReactions = memo( - ({ message, reactions }: Props) => { - const styles = useStyles(); - const { top } = useSafeAreaInsets(); - const { theme } = useAppTheme(); - const userAddress = useCurrentAccount(); - const bottomSheetModalRef = createBottomSheetModalRef(); - const [isBottomSheetVisible, setBottomSheetVisible] = useState(false); - - const openReactionsDrawer = () => { - setBottomSheetVisible(true); - }; - - const onReactionsDrawerDismiss = () => { - setBottomSheetVisible(false); - }; - - const onReactionDrawerChange = useCallback( - (index: number) => { - // Dismiss when index 0 (open but not visible) - if (index === 0) { - setBottomSheetVisible(false); - bottomSheetModalRef.current?.dismiss(); - } - }, - [bottomSheetModalRef] - ); - - useEffect(() => { - if (isBottomSheetVisible) { - bottomSheetModalRef.current?.present(); - } - }, [isBottomSheetVisible, bottomSheetModalRef]); - - const renderBackdrop = useCallback( - (props: BottomSheetDefaultBackdropProps) => ( - - ), - [theme] - ); - - const rolledUpReactions: RolledUpReactions = useMemo(() => { - const details: { [content: string]: ReactionDetails } = {}; - let totalReactions = 0; - let userReacted = false; - - Object.values(reactions).forEach((reactionArray) => { - reactionArray.forEach((reaction) => { - if (!details[reaction.content]) { - details[reaction.content] = { - content: reaction.content, - count: 0, - userReacted: false, - reactors: [], - firstReactionTime: reaction.sent, - }; - } - details[reaction.content].count++; - details[reaction.content].reactors.push(reaction.senderAddress); - if ( - reaction.senderAddress.toLowerCase() === userAddress?.toLowerCase() - ) { - details[reaction.content].userReacted = true; - userReacted = true; - } - // Keep track of the earliest reaction time for this emoji - details[reaction.content].firstReactionTime = Math.min( - details[reaction.content].firstReactionTime, - reaction.sent - ); - totalReactions++; - }); - }); - - // Sort by the number of reactors in descending order - const sortedReactions = Object.values(details) - .sort((a, b) => b.reactors.length - a.reactors.length) - .slice(0, MAX_REACTION_EMOJIS_SHOWN) - .map((reaction) => reaction.content); - - return { emojis: sortedReactions, totalReactions, userReacted, details }; - }, [reactions, userAddress]); - - if (rolledUpReactions.totalReactions === 0) return null; - - return ( - - - - - {rolledUpReactions.emojis.map((emoji, index) => ( - {emoji} - ))} - - {rolledUpReactions.totalReactions > 1 && ( - - {rolledUpReactions.totalReactions} - - )} - - - {isBottomSheetVisible && ( - - - - - - All {rolledUpReactions.totalReactions} - {Object.entries(rolledUpReactions.details).map( - ([emoji, details]) => ( - - - {emoji} - {details.count} - - - ) - )} - - - - - - )} - - ); - }, - (prevProps, nextProps) => { - if (prevProps.message.id !== nextProps.message.id) { - return false; - } - if (prevProps.message.lastUpdateAt !== nextProps.message.lastUpdateAt) { - return false; - } - return true; - } -); - -const useStyles = () => { - const { theme } = useAppTheme(); - - return StyleSheet.create({ - reactionsWrapper: { - flexDirection: "row", - flexWrap: "wrap", - }, - reactionButton: { - flexDirection: "row", - alignItems: "center", - paddingHorizontal: theme.spacing.xs, - paddingVertical: theme.spacing.xxs, - borderRadius: theme.borderRadius.sm, - borderWidth: theme.borderWidth.sm, - borderColor: theme.colors.border.subtle, - }, - emojiContainer: { - flexDirection: "row", - flexWrap: "wrap", - gap: theme.spacing.xxxs, - }, - reactorCount: { - marginLeft: theme.spacing.xxxs, - color: theme.colors.text.secondary, - }, - }); -}; diff --git a/components/Chat/Message/MessageReactions/MessageReactions.tsx b/components/Chat/Message/MessageReactions/MessageReactions.tsx new file mode 100644 index 000000000..ce2fabe42 --- /dev/null +++ b/components/Chat/Message/MessageReactions/MessageReactions.tsx @@ -0,0 +1,106 @@ +import { useCurrentAccount } from "@data/store/accountsStore"; +import { HStack } from "@design-system/HStack"; +import { Text } from "@design-system/Text"; +import { VStack } from "@design-system/VStack"; +import { useAppTheme } from "@theme/useAppTheme"; +import { memo, useCallback } from "react"; +import { StyleSheet, TouchableHighlight } from "react-native"; + +import { MessageToDisplay } from "../Message"; +import { MessageReactions } from "./MessageReactions.types"; +import { openMessageReactionsDrawer } from "./MessageReactionsDrawer/MessageReactionsDrawer.utils"; +import { useMessageReactionsRolledup } from "./useMessageReactionsRolledup"; + +type Props = { + message: MessageToDisplay; + reactions: MessageReactions; +}; + +export const ChatMessageReactions = memo( + ({ message, reactions }: Props) => { + const styles = useStyles(); + const { theme } = useAppTheme(); + const userAddress = useCurrentAccount(); + + const rolledUpReactions = useMessageReactionsRolledup({ + reactions, + userAddress: userAddress!, // ! If we are here, the user is logged in + }); + + const handlePress = useCallback(() => { + openMessageReactionsDrawer(message); + }, [message]); + + if (rolledUpReactions.totalReactions === 0) return null; + + return ( + + + + + {rolledUpReactions.emojis.map((emoji, index) => ( + {emoji} + ))} + + {rolledUpReactions.totalReactions > 1 && ( + + {rolledUpReactions.totalReactions} + + )} + + + + ); + }, + (prevProps, nextProps) => { + if (prevProps.message.id !== nextProps.message.id) { + return false; + } + if (prevProps.message.lastUpdateAt !== nextProps.message.lastUpdateAt) { + return false; + } + return true; + } +); + +const useStyles = () => { + const { theme } = useAppTheme(); + + return StyleSheet.create({ + reactionsWrapper: { + flexDirection: "row", + flexWrap: "wrap", + }, + reactionButton: { + flexDirection: "row", + alignItems: "center", + paddingHorizontal: theme.spacing.xs, + paddingVertical: theme.spacing.xxs, + borderRadius: theme.borderRadius.sm, + borderWidth: theme.borderWidth.sm, + borderColor: theme.colors.border.subtle, + }, + emojiContainer: { + flexDirection: "row", + flexWrap: "wrap", + gap: theme.spacing.xxxs, + }, + reactorCount: { + marginLeft: theme.spacing.xxxs, + color: theme.colors.text.secondary, + }, + }); +}; diff --git a/components/Chat/Message/MessageReactions/MessageReactions.types.ts b/components/Chat/Message/MessageReactions/MessageReactions.types.ts new file mode 100644 index 000000000..ffc1b5763 --- /dev/null +++ b/components/Chat/Message/MessageReactions/MessageReactions.types.ts @@ -0,0 +1,5 @@ +import { MessageReaction } from "../../../../utils/reactions"; + +export type MessageReactions = { + [senderAddress: string]: MessageReaction[]; +}; diff --git a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx new file mode 100644 index 000000000..c38f9ea3e --- /dev/null +++ b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx @@ -0,0 +1,13 @@ +import { create } from "zustand"; + +import { MessageToDisplay } from "../../Message"; + +export interface IMessageReactionsStore { + message: MessageToDisplay | null; +} + +export const useMessageReactionsStore = create( + (set, get) => ({ + message: null, + }) +); diff --git a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx new file mode 100644 index 000000000..996f2ffac --- /dev/null +++ b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx @@ -0,0 +1,94 @@ +import { BottomSheetHeader } from "@design-system/BottomSheet/BottomSheetHeader"; +import { BottomSheetModal } from "@design-system/BottomSheet/BottomSheetModal"; +import { HStack } from "@design-system/HStack"; +import { ScrollView } from "@design-system/ScrollView"; +import { Text } from "@design-system/Text"; +import { BottomSheetScrollView } from "@gorhom/bottom-sheet"; +import { useAppTheme } from "@theme/useAppTheme"; +import { getMessageReactions } from "@utils/reactions"; +import { memo, useCallback } from "react"; +import { TouchableHighlight } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +import { useCurrentAccount } from "../../../../../data/store/accountsStore"; +import { BottomSheetContentContainer } from "../../../../../design-system/BottomSheet/BottomSheetContentContainer"; +import { useMessageReactionsRolledup } from "../useMessageReactionsRolledup"; +import { useMessageReactionsStore } from "./MessageReactions.store"; +import { bottomSheetModalRef } from "./MessageReactionsDrawer.utils"; + +export const MessageReactionsDrawer = memo(function MessageReactionsDrawer() { + const { theme } = useAppTheme(); + + const userAddress = useCurrentAccount(); + + const message = useMessageReactionsStore((state) => state.message); + + const insets = useSafeAreaInsets(); + + const reactions = message ? getMessageReactions(message) : {}; + + const rolledUpReactions = useMessageReactionsRolledup({ + reactions, + userAddress: userAddress!, // ! If we are here, the user is logged in + }); + + const handleDismiss = useCallback(() => { + useMessageReactionsStore.setState({ message: null }); + }, []); + + return ( + + + + + + All {rolledUpReactions.totalReactions} + {Object.entries(rolledUpReactions.details).map( + ([emoji, details]) => ( + + + {emoji} + {details.count} + + + ) + )} + + + + + + ); +}); diff --git a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.utils.ts b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.utils.ts new file mode 100644 index 000000000..581878438 --- /dev/null +++ b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.utils.ts @@ -0,0 +1,16 @@ +import { createBottomSheetModalRef } from "@design-system/BottomSheet/BottomSheet.utils"; + +import { useMessageReactionsStore } from "./MessageReactions.store"; +import { MessageToDisplay } from "../../Message"; + +export const bottomSheetModalRef = createBottomSheetModalRef(); + +export function openMessageReactionsDrawer(message: MessageToDisplay) { + bottomSheetModalRef.current?.present(); + useMessageReactionsStore.setState({ message }); +} + +export function closeMessageReactionsDrawer() { + bottomSheetModalRef.current?.dismiss(); + useMessageReactionsStore.setState({ message: null }); +} diff --git a/components/Chat/Message/MessageReactions/useMessageReactionsRolledup.ts b/components/Chat/Message/MessageReactions/useMessageReactionsRolledup.ts new file mode 100644 index 000000000..f1725b24e --- /dev/null +++ b/components/Chat/Message/MessageReactions/useMessageReactionsRolledup.ts @@ -0,0 +1,69 @@ +import { useMemo } from "react"; + +import { MessageReactions } from "./MessageReactions.types"; + +const MAX_REACTION_EMOJIS_SHOWN = 3; + +type ReactionDetails = { + content: string; + count: number; + userReacted: boolean; + reactors: string[]; + firstReactionTime: number; +}; + +type RolledUpReactions = { + emojis: string[]; + totalReactions: number; + userReacted: boolean; + details: { [content: string]: ReactionDetails }; +}; + +export function useMessageReactionsRolledup(arg: { + reactions: MessageReactions; + userAddress: string; +}) { + const { reactions, userAddress } = arg; + + return useMemo((): RolledUpReactions => { + const details: { [content: string]: ReactionDetails } = {}; + let totalReactions = 0; + let userReacted = false; + + Object.values(reactions).forEach((reactionArray) => { + reactionArray.forEach((reaction) => { + if (!details[reaction.content]) { + details[reaction.content] = { + content: reaction.content, + count: 0, + userReacted: false, + reactors: [], + firstReactionTime: reaction.sent, + }; + } + details[reaction.content].count++; + details[reaction.content].reactors.push(reaction.senderAddress); + if ( + reaction.senderAddress.toLowerCase() === userAddress?.toLowerCase() + ) { + details[reaction.content].userReacted = true; + userReacted = true; + } + // Keep track of the earliest reaction time for this emoji + details[reaction.content].firstReactionTime = Math.min( + details[reaction.content].firstReactionTime, + reaction.sent + ); + totalReactions++; + }); + }); + + // Sort by the number of reactors in descending order + const sortedReactions = Object.values(details) + .sort((a, b) => b.reactors.length - a.reactors.length) + .slice(0, MAX_REACTION_EMOJIS_SHOWN) + .map((reaction) => reaction.content); + + return { emojis: sortedReactions, totalReactions, userReacted, details }; + }, [reactions, userAddress]); +} diff --git a/design-system/BottomSheet/BottomSheetBackdropOpacity.tsx b/design-system/BottomSheet/BottomSheetBackdropOpacity.tsx index 891b7d536..b79120584 100644 --- a/design-system/BottomSheet/BottomSheetBackdropOpacity.tsx +++ b/design-system/BottomSheet/BottomSheetBackdropOpacity.tsx @@ -4,15 +4,29 @@ import { } from "@gorhom/bottom-sheet"; import { memo } from "react"; +import { useAppTheme } from "../../theme/useAppTheme"; + export const BottomSheetBackdropOpacity = memo(function BackdropOpacity( props: GorhomBottomSheetBackdropProps ) { + const { style, animatedIndex, animatedPosition, ...rest } = props; + + const { theme } = useAppTheme(); + return ( ); }); diff --git a/design-system/BottomSheet/BottomSheetModal.tsx b/design-system/BottomSheet/BottomSheetModal.tsx index 34bcbf70c..c7488d161 100644 --- a/design-system/BottomSheet/BottomSheetModal.tsx +++ b/design-system/BottomSheet/BottomSheetModal.tsx @@ -10,6 +10,7 @@ import { FullWindowOverlay } from "react-native-screens"; import { BottomSheetBackdropOpacity } from "./BottomSheetBackdropOpacity"; import { BottomSheetHandleBar } from "./BottomSheetHandleBar"; +import { useAppTheme } from "../../theme/useAppTheme"; export type IBottomSheetModalProps = GorhomBottomSheetModalProps & { absoluteHandleBar?: boolean; @@ -18,7 +19,9 @@ export type IBottomSheetModalProps = GorhomBottomSheetModalProps & { export const BottomSheetModal = memo( forwardRef( function BottomSheetModal(props, ref) { - const { absoluteHandleBar = true, ...rest } = props; + const { absoluteHandleBar = true, backgroundStyle, ...rest } = props; + + const { theme } = useAppTheme(); // https://github.com/gorhom/react-native-bottom-sheet/issues/1644#issuecomment-1949019839 const renderContainerComponent = useCallback((props: any) => { @@ -40,8 +43,15 @@ export const BottomSheetModal = memo( containerComponent={ Platform.OS === "ios" ? renderContainerComponent : undefined } + enableDynamicSizing={false} // By default we don't want enable dynamic sizing backdropComponent={BottomSheetBackdropOpacity} handleComponent={renderHandleComponent} + backgroundStyle={[ + { + backgroundColor: theme.colors.background.raised, + }, + backgroundStyle, + ]} {...rest} /> ); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 8bf644dd3..76585d3ee 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2418,7 +2418,7 @@ SPEC CHECKSUMS: web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959 XMTP: 09faa347569b092005997364f7fe787ccc33f3d5 XMTPReactNative: 0c92d55c576ac6ff0775357fa60e90ea8e73afc2 - Yoga: 1ab23c1835475da69cf14e211a560e73aab24cb0 + Yoga: 33622183a85805e12703cd618b2c16bfd18bfffb PODFILE CHECKSUM: 137cb0cd2dafbfb3e5b9343435f9db8e28690806 From 7e53d300eb171e0f83f686de90bdc5f885bc7bd1 Mon Sep 17 00:00:00 2001 From: Thierry Date: Thu, 24 Oct 2024 15:09:32 -0400 Subject: [PATCH 2/9] make cleaner --- .../MessageReactions/MessageReactions.tsx | 66 ++++++++++++++++-- .../MessageReactions.types.ts | 15 ++++ .../MessageReactions.store.tsx | 19 ++++- .../MessageReactionsDrawer.service.ts | 32 +++++++++ .../MessageReactionsDrawer.tsx | 23 ++----- .../MessageReactionsDrawer.utils.ts | 16 ----- .../useMessageReactionsRolledup.ts | 69 ------------------- 7 files changed, 130 insertions(+), 110 deletions(-) create mode 100644 components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.service.ts delete mode 100644 components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.utils.ts delete mode 100644 components/Chat/Message/MessageReactions/useMessageReactionsRolledup.ts diff --git a/components/Chat/Message/MessageReactions/MessageReactions.tsx b/components/Chat/Message/MessageReactions/MessageReactions.tsx index ce2fabe42..cf96b9c11 100644 --- a/components/Chat/Message/MessageReactions/MessageReactions.tsx +++ b/components/Chat/Message/MessageReactions/MessageReactions.tsx @@ -3,13 +3,16 @@ import { HStack } from "@design-system/HStack"; import { Text } from "@design-system/Text"; import { VStack } from "@design-system/VStack"; import { useAppTheme } from "@theme/useAppTheme"; -import { memo, useCallback } from "react"; +import { memo, useCallback, useMemo } from "react"; import { StyleSheet, TouchableHighlight } from "react-native"; import { MessageToDisplay } from "../Message"; -import { MessageReactions } from "./MessageReactions.types"; -import { openMessageReactionsDrawer } from "./MessageReactionsDrawer/MessageReactionsDrawer.utils"; -import { useMessageReactionsRolledup } from "./useMessageReactionsRolledup"; +import { + MessageReactions, + ReactionDetails, + RolledUpReactions, +} from "./MessageReactions.types"; +import { openMessageReactionsDrawer } from "./MessageReactionsDrawer/MessageReactionsDrawer.service"; type Props = { message: MessageToDisplay; @@ -28,8 +31,8 @@ export const ChatMessageReactions = memo( }); const handlePress = useCallback(() => { - openMessageReactionsDrawer(message); - }, [message]); + openMessageReactionsDrawer(rolledUpReactions); + }, [rolledUpReactions]); if (rolledUpReactions.totalReactions === 0) return null; @@ -104,3 +107,54 @@ const useStyles = () => { }, }); }; + +const MAX_REACTION_EMOJIS_SHOWN = 3; + +function useMessageReactionsRolledup(arg: { + reactions: MessageReactions; + userAddress: string; +}) { + const { reactions, userAddress } = arg; + + return useMemo((): RolledUpReactions => { + const details: { [content: string]: ReactionDetails } = {}; + let totalReactions = 0; + let userReacted = false; + + Object.values(reactions).forEach((reactionArray) => { + reactionArray.forEach((reaction) => { + if (!details[reaction.content]) { + details[reaction.content] = { + content: reaction.content, + count: 0, + userReacted: false, + reactors: [], + firstReactionTime: reaction.sent, + }; + } + details[reaction.content].count++; + details[reaction.content].reactors.push(reaction.senderAddress); + if ( + reaction.senderAddress.toLowerCase() === userAddress?.toLowerCase() + ) { + details[reaction.content].userReacted = true; + userReacted = true; + } + // Keep track of the earliest reaction time for this emoji + details[reaction.content].firstReactionTime = Math.min( + details[reaction.content].firstReactionTime, + reaction.sent + ); + totalReactions++; + }); + }); + + // Sort by the number of reactors in descending order + const sortedReactions = Object.values(details) + .sort((a, b) => b.reactors.length - a.reactors.length) + .slice(0, MAX_REACTION_EMOJIS_SHOWN) + .map((reaction) => reaction.content); + + return { emojis: sortedReactions, totalReactions, userReacted, details }; + }, [reactions, userAddress]); +} diff --git a/components/Chat/Message/MessageReactions/MessageReactions.types.ts b/components/Chat/Message/MessageReactions/MessageReactions.types.ts index ffc1b5763..1434aaf8d 100644 --- a/components/Chat/Message/MessageReactions/MessageReactions.types.ts +++ b/components/Chat/Message/MessageReactions/MessageReactions.types.ts @@ -3,3 +3,18 @@ import { MessageReaction } from "../../../../utils/reactions"; export type MessageReactions = { [senderAddress: string]: MessageReaction[]; }; + +export type RolledUpReactions = { + emojis: string[]; + totalReactions: number; + userReacted: boolean; + details: { [content: string]: ReactionDetails }; +}; + +export type ReactionDetails = { + content: string; + count: number; + userReacted: boolean; + reactors: string[]; + firstReactionTime: number; +}; diff --git a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx index c38f9ea3e..b1daf28ab 100644 --- a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx +++ b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx @@ -1,13 +1,26 @@ import { create } from "zustand"; -import { MessageToDisplay } from "../../Message"; +import { RolledUpReactions } from "../MessageReactions.types"; export interface IMessageReactionsStore { - message: MessageToDisplay | null; + rolledUpReactions: RolledUpReactions; } +export const initialMessageReactionsState: IMessageReactionsStore = { + rolledUpReactions: { + emojis: [], + totalReactions: 0, + userReacted: false, + details: {}, + }, +}; + export const useMessageReactionsStore = create( (set, get) => ({ - message: null, + ...initialMessageReactionsState, }) ); + +export const resetMessageReactionsStore = () => { + useMessageReactionsStore.setState(initialMessageReactionsState); +}; diff --git a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.service.ts b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.service.ts new file mode 100644 index 000000000..f61a9ac80 --- /dev/null +++ b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.service.ts @@ -0,0 +1,32 @@ +import { createBottomSheetModalRef } from "@design-system/BottomSheet/BottomSheet.utils"; + +import { RolledUpReactions } from "../MessageReactions.types"; +import { + initialMessageReactionsState, + useMessageReactionsStore, +} from "./MessageReactions.store"; + +export const bottomSheetModalRef = createBottomSheetModalRef(); + +export function openMessageReactionsDrawer( + rolledUpReactions: RolledUpReactions +) { + bottomSheetModalRef.current?.present(); + useMessageReactionsStore.setState({ rolledUpReactions }); +} + +export function closeMessageReactionsDrawer(arg?: { resetStore?: boolean }) { + const { resetStore = true } = arg ?? {}; + bottomSheetModalRef.current?.dismiss(); + if (resetStore) { + resetMessageReactionsDrawer(); + } +} + +export function resetMessageReactionsDrawer() { + useMessageReactionsStore.setState(initialMessageReactionsState); +} + +export function useMessageReactionsRolledUpReactions() { + return useMessageReactionsStore((state) => state.rolledUpReactions); +} diff --git a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx index 996f2ffac..8a55d24d9 100644 --- a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx +++ b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx @@ -5,35 +5,26 @@ import { ScrollView } from "@design-system/ScrollView"; import { Text } from "@design-system/Text"; import { BottomSheetScrollView } from "@gorhom/bottom-sheet"; import { useAppTheme } from "@theme/useAppTheme"; -import { getMessageReactions } from "@utils/reactions"; import { memo, useCallback } from "react"; import { TouchableHighlight } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { useCurrentAccount } from "../../../../../data/store/accountsStore"; +import { + bottomSheetModalRef, + resetMessageReactionsDrawer, + useMessageReactionsRolledUpReactions, +} from "./MessageReactionsDrawer.service"; import { BottomSheetContentContainer } from "../../../../../design-system/BottomSheet/BottomSheetContentContainer"; -import { useMessageReactionsRolledup } from "../useMessageReactionsRolledup"; -import { useMessageReactionsStore } from "./MessageReactions.store"; -import { bottomSheetModalRef } from "./MessageReactionsDrawer.utils"; export const MessageReactionsDrawer = memo(function MessageReactionsDrawer() { const { theme } = useAppTheme(); - const userAddress = useCurrentAccount(); - - const message = useMessageReactionsStore((state) => state.message); - const insets = useSafeAreaInsets(); - const reactions = message ? getMessageReactions(message) : {}; - - const rolledUpReactions = useMessageReactionsRolledup({ - reactions, - userAddress: userAddress!, // ! If we are here, the user is logged in - }); + const rolledUpReactions = useMessageReactionsRolledUpReactions(); const handleDismiss = useCallback(() => { - useMessageReactionsStore.setState({ message: null }); + resetMessageReactionsDrawer(); }, []); return ( diff --git a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.utils.ts b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.utils.ts deleted file mode 100644 index 581878438..000000000 --- a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.utils.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createBottomSheetModalRef } from "@design-system/BottomSheet/BottomSheet.utils"; - -import { useMessageReactionsStore } from "./MessageReactions.store"; -import { MessageToDisplay } from "../../Message"; - -export const bottomSheetModalRef = createBottomSheetModalRef(); - -export function openMessageReactionsDrawer(message: MessageToDisplay) { - bottomSheetModalRef.current?.present(); - useMessageReactionsStore.setState({ message }); -} - -export function closeMessageReactionsDrawer() { - bottomSheetModalRef.current?.dismiss(); - useMessageReactionsStore.setState({ message: null }); -} diff --git a/components/Chat/Message/MessageReactions/useMessageReactionsRolledup.ts b/components/Chat/Message/MessageReactions/useMessageReactionsRolledup.ts deleted file mode 100644 index f1725b24e..000000000 --- a/components/Chat/Message/MessageReactions/useMessageReactionsRolledup.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { useMemo } from "react"; - -import { MessageReactions } from "./MessageReactions.types"; - -const MAX_REACTION_EMOJIS_SHOWN = 3; - -type ReactionDetails = { - content: string; - count: number; - userReacted: boolean; - reactors: string[]; - firstReactionTime: number; -}; - -type RolledUpReactions = { - emojis: string[]; - totalReactions: number; - userReacted: boolean; - details: { [content: string]: ReactionDetails }; -}; - -export function useMessageReactionsRolledup(arg: { - reactions: MessageReactions; - userAddress: string; -}) { - const { reactions, userAddress } = arg; - - return useMemo((): RolledUpReactions => { - const details: { [content: string]: ReactionDetails } = {}; - let totalReactions = 0; - let userReacted = false; - - Object.values(reactions).forEach((reactionArray) => { - reactionArray.forEach((reaction) => { - if (!details[reaction.content]) { - details[reaction.content] = { - content: reaction.content, - count: 0, - userReacted: false, - reactors: [], - firstReactionTime: reaction.sent, - }; - } - details[reaction.content].count++; - details[reaction.content].reactors.push(reaction.senderAddress); - if ( - reaction.senderAddress.toLowerCase() === userAddress?.toLowerCase() - ) { - details[reaction.content].userReacted = true; - userReacted = true; - } - // Keep track of the earliest reaction time for this emoji - details[reaction.content].firstReactionTime = Math.min( - details[reaction.content].firstReactionTime, - reaction.sent - ); - totalReactions++; - }); - }); - - // Sort by the number of reactors in descending order - const sortedReactions = Object.values(details) - .sort((a, b) => b.reactors.length - a.reactors.length) - .slice(0, MAX_REACTION_EMOJIS_SHOWN) - .map((reaction) => reaction.content); - - return { emojis: sortedReactions, totalReactions, userReacted, details }; - }, [reactions, userAddress]); -} From ad9e356270ade136d9efb167ff75ad502d7345f2 Mon Sep 17 00:00:00 2001 From: Thierry Date: Thu, 24 Oct 2024 15:12:48 -0400 Subject: [PATCH 3/9] oop --- .../MessageReactionsDrawer/MessageReactionsDrawer.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx index 8a55d24d9..c2e67c983 100644 --- a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx +++ b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx @@ -34,11 +34,7 @@ export const MessageReactionsDrawer = memo(function MessageReactionsDrawer() { topInset={insets.top} snapPoints={["50%", "100%"]} > - + Date: Thu, 24 Oct 2024 15:13:23 -0400 Subject: [PATCH 4/9] remove background on scrollview since we have on bottom sheeet --- .../MessageReactionsDrawer/MessageReactionsDrawer.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx index c2e67c983..b2e5cba52 100644 --- a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx +++ b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx @@ -36,11 +36,7 @@ export const MessageReactionsDrawer = memo(function MessageReactionsDrawer() { > - + Date: Fri, 25 Oct 2024 10:09:28 +0200 Subject: [PATCH 5/9] Revert because Yoga.podspec has unstable checksum https://github.com/facebook/react-native/issues/43220 --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 76585d3ee..8bf644dd3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2418,7 +2418,7 @@ SPEC CHECKSUMS: web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959 XMTP: 09faa347569b092005997364f7fe787ccc33f3d5 XMTPReactNative: 0c92d55c576ac6ff0775357fa60e90ea8e73afc2 - Yoga: 33622183a85805e12703cd618b2c16bfd18bfffb + Yoga: 1ab23c1835475da69cf14e211a560e73aab24cb0 PODFILE CHECKSUM: 137cb0cd2dafbfb3e5b9343435f9db8e28690806 From 71a918f2a850b6eb563345cf486a9804ead222b2 Mon Sep 17 00:00:00 2001 From: Louis Rouffineau Date: Fri, 25 Oct 2024 10:19:54 +0200 Subject: [PATCH 6/9] Fix typo and add accessibility --- .../Message/MessageReactions/MessageReactions.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/components/Chat/Message/MessageReactions/MessageReactions.tsx b/components/Chat/Message/MessageReactions/MessageReactions.tsx index cf96b9c11..8fd7290f5 100644 --- a/components/Chat/Message/MessageReactions/MessageReactions.tsx +++ b/components/Chat/Message/MessageReactions/MessageReactions.tsx @@ -25,7 +25,7 @@ export const ChatMessageReactions = memo( const { theme } = useAppTheme(); const userAddress = useCurrentAccount(); - const rolledUpReactions = useMessageReactionsRolledup({ + const rolledUpReactions = useMessageReactionsRolledUp({ reactions, userAddress: userAddress!, // ! If we are here, the user is logged in }); @@ -43,7 +43,12 @@ export const ChatMessageReactions = memo( message.fromMe && { justifyContent: "flex-end" }, ]} > - + { const MAX_REACTION_EMOJIS_SHOWN = 3; -function useMessageReactionsRolledup(arg: { +function useMessageReactionsRolledUp(arg: { reactions: MessageReactions; userAddress: string; }) { From 0d21ad11918e8318353391d7ee61a9fbaf679998 Mon Sep 17 00:00:00 2001 From: Louis Rouffineau Date: Fri, 25 Oct 2024 10:20:48 +0200 Subject: [PATCH 7/9] Use path alias, add comment on reaction types for clarity --- .../Message/MessageReactions/MessageReactions.types.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/components/Chat/Message/MessageReactions/MessageReactions.types.ts b/components/Chat/Message/MessageReactions/MessageReactions.types.ts index 1434aaf8d..e521a3d7f 100644 --- a/components/Chat/Message/MessageReactions/MessageReactions.types.ts +++ b/components/Chat/Message/MessageReactions/MessageReactions.types.ts @@ -1,16 +1,22 @@ -import { MessageReaction } from "../../../../utils/reactions"; +import { MessageReaction } from "@utils/reactions"; export type MessageReactions = { [senderAddress: string]: MessageReaction[]; }; +/** + * Aggregated reaction data including top emojis, total count, and detailed breakdown. + */ export type RolledUpReactions = { emojis: string[]; totalReactions: number; userReacted: boolean; - details: { [content: string]: ReactionDetails }; + details: Record; }; +/** + * Details for a specific reaction emoji, including count, reactors, and timing. + */ export type ReactionDetails = { content: string; count: number; From 875e6515eb969e0f642c5114f814cd6531c5b559 Mon Sep 17 00:00:00 2001 From: Louis Rouffineau Date: Fri, 25 Oct 2024 10:38:53 +0200 Subject: [PATCH 8/9] Small fixes, add more accessiblity --- .../MessageReactionsDrawer/MessageReactionsDrawer.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx index b2e5cba52..32e54a4dc 100644 --- a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx +++ b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.tsx @@ -1,3 +1,4 @@ +import { BottomSheetContentContainer } from "@design-system/BottomSheet/BottomSheetContentContainer"; import { BottomSheetHeader } from "@design-system/BottomSheet/BottomSheetHeader"; import { BottomSheetModal } from "@design-system/BottomSheet/BottomSheetModal"; import { HStack } from "@design-system/HStack"; @@ -14,7 +15,6 @@ import { resetMessageReactionsDrawer, useMessageReactionsRolledUpReactions, } from "./MessageReactionsDrawer.service"; -import { BottomSheetContentContainer } from "../../../../../design-system/BottomSheet/BottomSheetContentContainer"; export const MessageReactionsDrawer = memo(function MessageReactionsDrawer() { const { theme } = useAppTheme(); @@ -43,7 +43,6 @@ export const MessageReactionsDrawer = memo(function MessageReactionsDrawer() { style={{ paddingLeft: theme.spacing.lg, flexDirection: "row", - gap: 20, }} > All {rolledUpReactions.totalReactions} @@ -60,6 +59,9 @@ export const MessageReactionsDrawer = memo(function MessageReactionsDrawer() { : theme.colors.background.raised, }} underlayColor={theme.colors.border.subtle} + accessible + accessibilityRole="button" + accessibilityLabel={`${details.count} ${emoji} reactions`} > {emoji} From 49cf6d2ae25d16d443dc1f51e53ac26238d72267 Mon Sep 17 00:00:00 2001 From: Louis Rouffineau Date: Fri, 25 Oct 2024 11:18:18 +0200 Subject: [PATCH 9/9] Zustand logic update --- .../MessageReactions.store.tsx | 29 +++++++++++-------- .../MessageReactionsDrawer.service.ts | 16 +++++++--- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx index b1daf28ab..26367515d 100644 --- a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx +++ b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactions.store.tsx @@ -2,25 +2,30 @@ import { create } from "zustand"; import { RolledUpReactions } from "../MessageReactions.types"; +const initialMessageReactionsState: RolledUpReactions = { + emojis: [], + totalReactions: 0, + userReacted: false, + details: {}, +}; + export interface IMessageReactionsStore { rolledUpReactions: RolledUpReactions; -} + setRolledUpReactions: (reactions: RolledUpReactions) => void; -export const initialMessageReactionsState: IMessageReactionsStore = { - rolledUpReactions: { - emojis: [], - totalReactions: 0, - userReacted: false, - details: {}, - }, -}; + // TODO: update state when new reactions come up and drawer is open + // updateReactions: (updates: Partial) => void; +} export const useMessageReactionsStore = create( - (set, get) => ({ - ...initialMessageReactionsState, + (set) => ({ + rolledUpReactions: initialMessageReactionsState, + setRolledUpReactions: (reactions) => set({ rolledUpReactions: reactions }), }) ); export const resetMessageReactionsStore = () => { - useMessageReactionsStore.setState(initialMessageReactionsState); + useMessageReactionsStore + .getState() + .setRolledUpReactions(initialMessageReactionsState); }; diff --git a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.service.ts b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.service.ts index f61a9ac80..e3276f750 100644 --- a/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.service.ts +++ b/components/Chat/Message/MessageReactions/MessageReactionsDrawer/MessageReactionsDrawer.service.ts @@ -2,7 +2,7 @@ import { createBottomSheetModalRef } from "@design-system/BottomSheet/BottomShee import { RolledUpReactions } from "../MessageReactions.types"; import { - initialMessageReactionsState, + resetMessageReactionsStore, useMessageReactionsStore, } from "./MessageReactions.store"; @@ -11,8 +11,16 @@ export const bottomSheetModalRef = createBottomSheetModalRef(); export function openMessageReactionsDrawer( rolledUpReactions: RolledUpReactions ) { - bottomSheetModalRef.current?.present(); - useMessageReactionsStore.setState({ rolledUpReactions }); + try { + if (!bottomSheetModalRef.current) { + throw new Error("Modal reference not initialized"); + } + useMessageReactionsStore.getState().setRolledUpReactions(rolledUpReactions); + bottomSheetModalRef.current.present(); + } catch (error) { + console.error("Failed to open message reactions drawer:", error); + resetMessageReactionsDrawer(); + } } export function closeMessageReactionsDrawer(arg?: { resetStore?: boolean }) { @@ -24,7 +32,7 @@ export function closeMessageReactionsDrawer(arg?: { resetStore?: boolean }) { } export function resetMessageReactionsDrawer() { - useMessageReactionsStore.setState(initialMessageReactionsState); + resetMessageReactionsStore(); } export function useMessageReactionsRolledUpReactions() {