diff --git a/examples/SampleApp/ios/Podfile.lock b/examples/SampleApp/ios/Podfile.lock index 8b44306f37..4169819bc6 100644 --- a/examples/SampleApp/ios/Podfile.lock +++ b/examples/SampleApp/ios/Podfile.lock @@ -2476,7 +2476,7 @@ PODS: - ReactNativeDependencies - RNFBApp - Yoga - - RNGestureHandler (3.0.0): + - RNGestureHandler (2.31.2): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3322,7 +3322,7 @@ SPEC CHECKSUMS: RNFastImage: 14580cef91660b889645fb9e87f58a53621db993 RNFBApp: 3b942e786ca88524ba17df665a1a360fb3eee525 RNFBMessaging: b82ba0933288d710f5371f57d3115092abf64903 - RNGestureHandler: ae4b9960c2e7d0fb3991255345bf424cca8e09e4 + RNGestureHandler: a97cc64efbfcb7a53969a38310a189a3d5246c65 RNNotifee: 5e3b271e8ea7456a36eec994085543c9adca9168 RNReactNativeHapticFeedback: 9dc72312c12cb53ee240b5b7aae1e167f3d940a6 RNReanimated: 8aac6baab55e39ca4e02afd69f77fb127b26520c diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx index 57768e813e..aaecdc344d 100644 --- a/package/src/components/Message/Message.tsx +++ b/package/src/components/Message/Message.tsx @@ -67,6 +67,7 @@ import { setOverlayTopH, useIsOverlayActive, } from '../../state-store'; +import { primitives } from '../../theme'; import { FileTypes } from '../../types/types'; import { checkMessageEquality, @@ -830,8 +831,20 @@ const MessageWithContext = (props: MessagePropsWithContext) => { } }, [overlayActive, message]); + const groupKey: 'single' | 'top' | 'middle' | 'bottom' | undefined = + groupStyles?.[0] === 'single' || + groupStyles?.[0] === 'top' || + groupStyles?.[0] === 'middle' || + groupStyles?.[0] === 'bottom' + ? groupStyles[0] + : undefined; + const isVeryLastBubble = + messagesContext.enableMessageGroupingByUser && + channel?.state.messages[channel.state.messages.length - 1]?.id === message.id; const styles = useStyles({ + groupKey, highlightedMessage: (isTargetedMessage || message.pinned) && !isMessageTypeDeleted, + isVeryLastBubble, }); const rect = rectRef.current; const overlayItemsAnchorRect = bubbleRect.current ?? rect; @@ -1147,34 +1160,67 @@ export const Message = (props: MessageProps) => { ); }; -const useStyles = ({ highlightedMessage }: { highlightedMessage?: boolean }) => { +const useStyles = ({ + groupKey, + highlightedMessage, + isVeryLastBubble, +}: { + groupKey: 'single' | 'top' | 'middle' | 'bottom' | undefined; + highlightedMessage?: boolean; + isVeryLastBubble: boolean; +}) => { const { - theme: { - messageItemView: { wrapper, targetedMessageContainer, blockedMessageContainer }, - screenPadding, - semantics, - }, + theme: { messageItemView, screenPadding, semantics }, } = useTheme(); + return useMemo(() => { - return StyleSheet.create({ - wrapper: { - paddingHorizontal: screenPadding, - ...(highlightedMessage - ? { backgroundColor: semantics.backgroundCoreHighlight, ...targetedMessageContainer } - : {}), - ...wrapper, + const groupStylesMap: Record<'single' | 'top' | 'middle' | 'bottom', ViewStyle> = { + single: { + paddingVertical: primitives.spacingXs, + ...messageItemView.messageGroupedSingleStyles, + }, + top: { + paddingTop: primitives.spacingXs, + paddingBottom: primitives.spacingXxs, + ...messageItemView.messageGroupedTopStyles, + }, + middle: { + paddingBottom: primitives.spacingXxs, + ...messageItemView.messageGroupedMiddleStyles, + }, + bottom: { + paddingBottom: primitives.spacingXs, + ...messageItemView.messageGroupedBottomStyles, }, + }; + + let wrapper: ViewStyle = { + paddingHorizontal: screenPadding, + ...(highlightedMessage + ? { + backgroundColor: semantics.backgroundCoreHighlight, + ...messageItemView.targetedMessageContainer, + } + : {}), + ...messageItemView.wrapper, + }; + if (groupKey) { + wrapper = { ...wrapper, ...groupStylesMap[groupKey] }; + } + if (isVeryLastBubble) { + wrapper = { + ...wrapper, + marginBottom: primitives.spacingSm, + ...messageItemView.lastMessageContainer, + }; + } + + return StyleSheet.create({ + wrapper, blockedMessageContainer: { alignItems: 'center', - ...blockedMessageContainer, + ...messageItemView.blockedMessageContainer, }, }); - }, [ - wrapper, - screenPadding, - highlightedMessage, - semantics, - targetedMessageContainer, - blockedMessageContainer, - ]); + }, [messageItemView, screenPadding, semantics, highlightedMessage, groupKey, isVeryLastBubble]); }; diff --git a/package/src/components/Message/MessageItemView/MessageItemView.tsx b/package/src/components/Message/MessageItemView/MessageItemView.tsx index b9a8b42db0..28e7719d3a 100644 --- a/package/src/components/Message/MessageItemView/MessageItemView.tsx +++ b/package/src/components/Message/MessageItemView/MessageItemView.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { Dimensions, StyleSheet, View, ViewStyle } from 'react-native'; +import { Dimensions, StyleSheet, View } from 'react-native'; import { SwipableMessageWrapper } from './MessageBubble'; @@ -22,25 +22,7 @@ import { FileTypes } from '../../../types/types'; import { checkMessageEquality, checkQuotedMessageEquality } from '../../../utils/utils'; import { useMessageData } from '../hooks/useMessageData'; -type GroupType = 'single' | 'top' | 'middle' | 'bottom' | undefined; - -const useStyles = ({ - alignment, - isVeryLastMessage, - messageGroupedSingle, - messageGroupedBottom, - messageGroupedTop, - messageGroupedMiddle, - enableMessageGroupingByUser, -}: { - alignment: Alignment; - isVeryLastMessage: boolean; - messageGroupedSingle: boolean; - messageGroupedBottom: boolean; - messageGroupedTop: boolean; - messageGroupedMiddle: boolean; - enableMessageGroupingByUser: boolean; -}) => { +const useStyles = ({ alignment }: { alignment: Alignment }) => { const { theme: { messageItemView: { @@ -53,24 +35,11 @@ const useStyles = ({ repliesContainer, leftAlignItems, rightAlignItems, - messageGroupedSingleStyles, - messageGroupedBottomStyles, - messageGroupedTopStyles, - messageGroupedMiddleStyles, - lastMessageContainer, }, }, } = useTheme(); - const groupType: GroupType = useMemo(() => { - if (messageGroupedSingle) return 'single'; - if (messageGroupedTop) return 'top'; - if (messageGroupedMiddle) return 'middle'; - if (messageGroupedBottom) return 'bottom'; - return undefined; - }, [messageGroupedSingle, messageGroupedTop, messageGroupedMiddle, messageGroupedBottom]); - - const styles = useMemo( + return useMemo( () => StyleSheet.create({ baseContainer: { @@ -129,73 +98,6 @@ const useStyles = ({ rightAlignItems, ], ); - - const groupStylesMap = useMemo(() => { - return { - single: { - paddingVertical: primitives.spacingXs, - ...messageGroupedSingleStyles, - }, - top: { - paddingTop: primitives.spacingXs, - paddingBottom: primitives.spacingXxs, - ...messageGroupedTopStyles, - }, - middle: { - paddingBottom: primitives.spacingXxs, - ...messageGroupedMiddleStyles, - }, - bottom: { - paddingBottom: primitives.spacingXs, - ...messageGroupedBottomStyles, - }, - }; - }, [ - messageGroupedBottomStyles, - messageGroupedMiddleStyles, - messageGroupedSingleStyles, - messageGroupedTopStyles, - ]); - - const containerStyle = useMemo(() => { - let results: ViewStyle = styles.baseContainer; - - if (groupType) { - results = { - ...results, - ...groupStylesMap[groupType], - }; - } - - if (isVeryLastMessage && enableMessageGroupingByUser) { - results = { - ...results, - marginBottom: primitives.spacingSm, - ...lastMessageContainer, - }; - } - - return results; - }, [ - styles.baseContainer, - groupStylesMap, - groupType, - isVeryLastMessage, - enableMessageGroupingByUser, - lastMessageContainer, - ]); - - return { - container: containerStyle, - bubbleContentContainer: styles.bubbleContentContainer, - bubbleErrorContainer: styles.bubbleErrorContainer, - bubbleReactionListTopContainer: styles.bubbleReactionListTopContainer, - bubbleWrapper: styles.bubbleWrapper, - contentContainer: styles.contentContainer, - repliesContainer: styles.repliesContainer, - leftAlignItems: styles.leftAlignItems, - rightAlignItems: styles.rightAlignItems, - }; }; export type MessageItemViewPropsWithContext = Pick< @@ -232,7 +134,6 @@ const MessageItemViewWithContext = (props: MessageItemViewPropsWithContext) => { channel, contextMenuAnchorRef, customMessageSwipeAction, - enableMessageGroupingByUser, enableSwipeToReply, groupStyles, hasAttachmentActions, @@ -273,22 +174,10 @@ const MessageItemViewWithContext = (props: MessageItemViewPropsWithContext) => { isMessageReceivedOrErrorType, isMessageTypeDeleted, isVeryLastMessage, - messageGroupedSingle, - messageGroupedBottom, - messageGroupedTop, messageGroupedSingleOrBottom, - messageGroupedMiddle, } = useMessageData({}); - const styles = useStyles({ - alignment, - isVeryLastMessage, - messageGroupedSingle, - messageGroupedBottom, - messageGroupedTop, - messageGroupedMiddle, - enableMessageGroupingByUser, - }); + const styles = useStyles({ alignment }); const groupStyle = `${alignment}_${groupStyles?.[0]?.toLowerCase?.()}`; const hasVisibleQuotedReply = !!message.quoted_message && !hasAttachmentActions; @@ -324,7 +213,7 @@ const MessageItemViewWithContext = (props: MessageItemViewPropsWithContext) => { }); const itemViewContent = ( - + {alignment === 'left' ? : null} {isMessageTypeDeleted ? ( diff --git a/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx b/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx index 082fcaaabb..1bbd30bace 100644 --- a/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx +++ b/package/src/components/Message/MessageItemView/__tests__/MessageItemView.test.tsx @@ -223,38 +223,47 @@ describe('MessageItemView', () => { }); }); - it('applies correct styles for when group styles are not single or bottom', async () => { + it('keeps message-item-view-wrapper free of group-positional padding', async () => { const user = generateUser(); const message = generateMessage({ user }); renderMessage({ groupStyles: ['top'], message }); await waitFor(() => { - expect(screen.getByTestId('message-item-view-wrapper').props.style).toMatchObject({ + const innerStyle = screen.getByTestId('message-item-view-wrapper').props.style; + expect(innerStyle).toMatchObject({ alignItems: 'flex-end', gap: 8, flexDirection: 'row', - paddingTop: 8, - paddingBottom: 4, }); + expect(innerStyle.paddingTop).toBeUndefined(); + expect(innerStyle.paddingBottom).toBeUndefined(); }); }); - it('applies correct styles for when group styles are single/bottom and not last message', async () => { + it('hoists the per-group padding delta onto message-wrapper for top messages', async () => { const user = generateUser(); const message = generateMessage({ user }); - renderMessage({ message }); + renderMessage({ groupStyles: ['top'], message }); await waitFor(() => { - const data = screen.getByTestId('message-item-view-wrapper').props.style; + expect(screen.getByTestId('message-wrapper').props.style).toEqual( + expect.arrayContaining([expect.objectContaining({ paddingTop: 8 })]), + ); + }); + }); - expect(data).toMatchObject({ - alignItems: 'flex-end', - gap: 8, - flexDirection: 'row', - paddingBottom: 8, - }); + it('hoists the per-group padding delta onto message-wrapper for bottom messages', async () => { + const user = generateUser(); + const message = generateMessage({ user }); + + renderMessage({ message }); + + await waitFor(() => { + expect(screen.getByTestId('message-wrapper').props.style).toEqual( + expect.arrayContaining([expect.objectContaining({ paddingBottom: 8 })]), + ); }); }); diff --git a/package/src/components/MessageMenu/MessageActionList.tsx b/package/src/components/MessageMenu/MessageActionList.tsx index 4a77f55a50..2296041a3c 100644 --- a/package/src/components/MessageMenu/MessageActionList.tsx +++ b/package/src/components/MessageMenu/MessageActionList.tsx @@ -78,7 +78,7 @@ const useStyles = () => { StyleSheet.create({ container: { borderRadius: primitives.radiusLg, - marginTop: 6, + marginTop: 8, backgroundColor: semantics.backgroundCoreElevation2, borderWidth: 1, borderColor: semantics.borderCoreDefault, diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap index f905a03e95..294d45e49d 100644 --- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap +++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap @@ -337,6 +337,7 @@ exports[`Thread should match thread snapshot 1`] = ` ], { "paddingHorizontal": 16, + "paddingVertical": 8, }, ] } @@ -370,7 +371,6 @@ exports[`Thread should match thread snapshot 1`] = ` "alignItems": "flex-end", "flexDirection": "row", "gap": 8, - "paddingVertical": 8, "width": "100%", } } @@ -671,6 +671,7 @@ exports[`Thread should match thread snapshot 1`] = ` ], { "paddingHorizontal": 16, + "paddingVertical": 8, }, ] } @@ -704,7 +705,6 @@ exports[`Thread should match thread snapshot 1`] = ` "alignItems": "flex-end", "flexDirection": "row", "gap": 8, - "paddingVertical": 8, "width": "100%", } } @@ -1039,6 +1039,7 @@ exports[`Thread should match thread snapshot 1`] = ` ], { "paddingHorizontal": 16, + "paddingVertical": 8, }, ] } @@ -1072,7 +1073,6 @@ exports[`Thread should match thread snapshot 1`] = ` "alignItems": "flex-end", "flexDirection": "row", "gap": 8, - "paddingVertical": 8, "width": "100%", } } @@ -1365,7 +1365,9 @@ exports[`Thread should match thread snapshot 1`] = ` [ undefined, { + "marginBottom": 12, "paddingHorizontal": 16, + "paddingVertical": 8, }, ] } @@ -1399,8 +1401,6 @@ exports[`Thread should match thread snapshot 1`] = ` "alignItems": "flex-end", "flexDirection": "row", "gap": 8, - "marginBottom": 12, - "paddingVertical": 8, "width": "100%", } }