diff --git a/examples/SampleApp/App.tsx b/examples/SampleApp/App.tsx index 6f427f6604..e52d0495ca 100644 --- a/examples/SampleApp/App.tsx +++ b/examples/SampleApp/App.tsx @@ -59,8 +59,6 @@ Geolocation.setRNConfiguration({ }); import type { LocalMessage, StreamChat, TextComposerMiddleware } from 'stream-chat'; -import { Toast } from './src/components/ToastComponent/Toast'; -import { useClientNotificationsToastHandler } from './src/hooks/useClientNotificationsToastHandler'; import AsyncStore from './src/utils/AsyncStore.ts'; import { MessageInputFloatingConfigItem, @@ -377,7 +375,6 @@ const DrawerNavigatorWrapper: React.FC<{ - @@ -403,7 +400,6 @@ const UserSelector = () => ( // TODO: Split the stack into multiple stacks - ChannelStack, CreateChannelStack etc. const HomeScreen = () => { const { overlay } = useOverlayContext(); - useClientNotificationsToastHandler(); return ( , + () => , [semantics.textTertiary], ); diff --git a/examples/SampleApp/src/components/ScreenHeader.tsx b/examples/SampleApp/src/components/ScreenHeader.tsx index 9d7ef3d4ca..ab1b5393a0 100644 --- a/examples/SampleApp/src/components/ScreenHeader.tsx +++ b/examples/SampleApp/src/components/ScreenHeader.tsx @@ -122,7 +122,7 @@ export const ScreenHeader: React.FC = (props) => { const { theme: { semantics }, } = useTheme(); - const { black, grey, white } = useLegacyColors(); + const { black, grey } = useLegacyColors(); const insets = useSafeAreaInsets(); return ( @@ -130,7 +130,7 @@ export const ScreenHeader: React.FC = (props) => { style={[ styles.safeAreaContainer, { - backgroundColor: white, + backgroundColor: semantics.backgroundCoreElevation1, borderBottomColor: semantics.borderCoreSubtle, height: HEADER_CONTENT_HEIGHT + (inSafeArea ? 0 : insets.top), }, diff --git a/examples/SampleApp/src/icons/ShareLocationIcon.tsx b/examples/SampleApp/src/icons/ShareLocationIcon.tsx index 2680f19261..fd290834d0 100644 --- a/examples/SampleApp/src/icons/ShareLocationIcon.tsx +++ b/examples/SampleApp/src/icons/ShareLocationIcon.tsx @@ -2,9 +2,17 @@ import Svg, { Path } from 'react-native-svg'; import { ColorValue } from 'react-native'; // Icon for "Share Location" button, next to input box. -export const ShareLocationIcon = ({ stroke }: { stroke: ColorValue }) => { +export const ShareLocationIcon = ({ + width, + height, + stroke, +}: { + stroke: ColorValue; + width: number; + height: number; +}) => { return ( - + & Pick & { attachment: Attachment; @@ -34,6 +34,7 @@ const GiphyWithContext = (props: GiphyPropsWithContext) => { attachment, giphyVersion, handleAction, + isMyMessage, onLongPress, onPress, onPressIn, @@ -53,7 +54,7 @@ const GiphyWithContext = (props: GiphyPropsWithContext) => { }, } = useTheme(); - const styles = useStyles(); + const styles = useStyles({ isMyMessage }); const uri = image_url || thumb_url; @@ -122,7 +123,7 @@ const GiphyWithContext = (props: GiphyPropsWithContext) => { } }} testID='giphy-attachment' - style={styles.container} + style={[styles.container, container]} {...additionalPressableProps} > @@ -134,10 +135,12 @@ const areEqual = (prevProps: GiphyPropsWithContext, nextProps: GiphyPropsWithCon const { attachment: { actions: prevActions, image_url: prevImageUrl, thumb_url: prevThumbUrl }, giphyVersion: prevGiphyVersion, + isMyMessage: prevIsMyMessage, } = prevProps; const { attachment: { actions: nextActions, image_url: nextImageUrl, thumb_url: nextThumbUrl }, giphyVersion: nextGiphyVersion, + isMyMessage: nextIsMyMessage, } = nextProps; const imageUrlEqual = prevImageUrl === nextImageUrl; @@ -165,6 +168,11 @@ const areEqual = (prevProps: GiphyPropsWithContext, nextProps: GiphyPropsWithCon return false; } + const isMyMessageEqual = prevIsMyMessage === nextIsMyMessage; + if (!isMyMessageEqual) { + return false; + } + return true; }; @@ -178,7 +186,8 @@ export type GiphyProps = Partial & { * UI component for card in attachments. */ export const Giphy = (props: GiphyProps) => { - const { handleAction, onLongPress, onPress, onPressIn, preventPress } = useMessageContext(); + const { handleAction, isMyMessage, onLongPress, onPress, onPressIn, preventPress } = + useMessageContext(); const { additionalPressableProps, giphyVersion } = useMessagesContext(); return ( @@ -187,6 +196,7 @@ export const Giphy = (props: GiphyProps) => { additionalPressableProps, giphyVersion, handleAction, + isMyMessage, onLongPress, onPress, onPressIn, @@ -199,14 +209,16 @@ export const Giphy = (props: GiphyProps) => { Giphy.displayName = 'Giphy{messageItemView{giphy}}'; -const useStyles = () => { +const useStyles = ({ isMyMessage }: Pick) => { const { theme: { semantics }, } = useTheme(); return useMemo(() => { return StyleSheet.create({ container: { - backgroundColor: semantics.chatBgOutgoing, + backgroundColor: isMyMessage + ? semantics.chatBgAttachmentOutgoing + : semantics.chatBgAttachmentIncoming, borderRadius: primitives.radiusLg, maxWidth: 256, // TODO: Not sure how to fix this overflow: 'hidden', @@ -240,5 +252,5 @@ const useStyles = () => { lineHeight: primitives.typographyLineHeightTight, }, }); - }, [semantics]); + }, [isMyMessage, semantics]); }; diff --git a/package/src/components/Attachment/__tests__/Giphy.test.js b/package/src/components/Attachment/__tests__/Giphy.test.js index d35e5839bd..dc430c7031 100644 --- a/package/src/components/Attachment/__tests__/Giphy.test.js +++ b/package/src/components/Attachment/__tests__/Giphy.test.js @@ -14,7 +14,7 @@ import { MessageProvider } from '../../../contexts/messageContext/MessageContext import { MessagesProvider } from '../../../contexts/messagesContext/MessagesContext'; import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider'; -import { ThemeProvider } from '../../../contexts/themeContext/ThemeContext'; +import { mergeThemes, ThemeProvider } from '../../../contexts/themeContext/ThemeContext'; import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel'; import { useMockedApis } from '../../../mock-builders/api/useMockedApis'; import { generateGiphyAttachment } from '../../../mock-builders/generator/attachment'; @@ -36,6 +36,8 @@ const streami18n = new Streami18n({ }); describe('Giphy', () => { + const lightTheme = mergeThemes({ scheme: 'light' }); + const getAttachmentComponent = (props, messageContextValue = {}) => { const message = generateMessage(); return ( @@ -115,6 +117,36 @@ describe('Giphy', () => { }); }); + it('uses the outgoing attachment background for outgoing giphys', async () => { + render(getAttachmentComponent({ attachment }, { isMyMessage: true })); + + await waitFor(() => { + const style = screen.getByTestId('giphy-attachment').props.style; + expect(style).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + backgroundColor: lightTheme.semantics.chatBgAttachmentOutgoing, + }), + ]), + ); + }); + }); + + it('uses the incoming attachment background for incoming giphys', async () => { + render(getAttachmentComponent({ attachment }, { isMyMessage: false })); + + await waitFor(() => { + const style = screen.getByTestId('giphy-attachment').props.style; + expect(style).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + backgroundColor: lightTheme.semantics.chatBgAttachmentIncoming, + }), + ]), + ); + }); + }); + it('"giphy" attachment size should be customisable', async () => { attachment.giphy = giphy; render(getAttachmentComponent({ attachment, giphyVersion: 'fixed_height' })); diff --git a/package/src/components/AttachmentPicker/components/AttachmentPickerContent.tsx b/package/src/components/AttachmentPicker/components/AttachmentPickerContent.tsx index 59ee68645b..e42d2cd7d2 100644 --- a/package/src/components/AttachmentPicker/components/AttachmentPickerContent.tsx +++ b/package/src/components/AttachmentPicker/components/AttachmentPickerContent.tsx @@ -201,7 +201,7 @@ export const AttachmentCameraPicker = ( onPress={openCameraPicker} height={height} buttonText={t('Open Camera')} - description={t('Take a video and share')} + description={t(videoOnly ? 'Take a video and share' : 'Take a photo and share')} /> ); }; @@ -216,8 +216,8 @@ export const AttachmentFilePicker = (props: AttachmentPickerContentProps) => { Icon={FilePickerIcon} onPress={pickFile} height={height} - buttonText={t('Pick document')} - description={t('Pick a document to share it with everyone')} + buttonText={t('Open Files')} + description={t('Select files to share')} /> ); }; diff --git a/package/src/components/AttachmentPicker/components/AttachmentPickerGenericContent.tsx b/package/src/components/AttachmentPicker/components/AttachmentPickerGenericContent.tsx index 768680a576..5731a3b68a 100644 --- a/package/src/components/AttachmentPicker/components/AttachmentPickerGenericContent.tsx +++ b/package/src/components/AttachmentPicker/components/AttachmentPickerGenericContent.tsx @@ -21,12 +21,20 @@ const useStyles = () => { paddingHorizontal: primitives.spacing2xl, paddingBottom: primitives.spacing3xl, }, - infoContainer: { flex: 1, alignItems: 'center', justifyContent: 'center' }, + infoContainer: { + flex: 1, + gap: primitives.spacingSm, + alignItems: 'center', + justifyContent: 'center', + }, + actionContainer: { + gap: primitives.spacingMd, + alignItems: 'center', + justifyContent: 'center', + }, text: { fontSize: primitives.typographyFontSizeMd, color: semantics.textSecondary, - marginTop: 8, - marginHorizontal: 24, textAlign: 'center', maxWidth: 200, }, @@ -54,13 +62,13 @@ export const AttachmentPickerGenericContent = (props: AttachmentPickerGenericCon theme: { semantics, attachmentPicker: { - content: { container, text, infoContainer }, + content: { actionContainer, container, text, infoContainer }, }, }, } = useTheme(); const ThemedIcon = useCallback( - () => , + () => , [Icon, semantics.textTertiary], ); @@ -76,15 +84,17 @@ export const AttachmentPickerGenericContent = (props: AttachmentPickerGenericCon > - {description} + + {description} + + - ); }; diff --git a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx index 07988eaa55..897bf2c271 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteSuggestionList.tsx @@ -1,5 +1,7 @@ import React, { useCallback, useMemo } from 'react'; -import { FlatList, StyleSheet } from 'react-native'; +import { StyleSheet } from 'react-native'; + +import { FlatList } from 'react-native-gesture-handler'; import Animated, { LinearTransition, ZoomIn, ZoomOut } from 'react-native-reanimated'; diff --git a/package/src/components/ChannelPreview/ChannelDetailsBottomSheet.tsx b/package/src/components/ChannelPreview/ChannelDetailsBottomSheet.tsx index 670fd55393..c139e05bfd 100644 --- a/package/src/components/ChannelPreview/ChannelDetailsBottomSheet.tsx +++ b/package/src/components/ChannelPreview/ChannelDetailsBottomSheet.tsx @@ -141,7 +141,7 @@ const useStyles = () => { flexDirection: 'row', padding: primitives.spacingSm, gap: primitives.spacingSm, - backgroundColor: semantics.backgroundCoreElevation1, + backgroundColor: 'transparent', ...header.container, }, headerMeta: { diff --git a/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx b/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx index 98509ee6c9..ca7e17fdde 100644 --- a/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx +++ b/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx @@ -104,7 +104,9 @@ export const ChannelPreviewMessage = (props: ChannelPreviewMessageProps) => { return ( - {pollLabel} + + {pollLabel} + ); } diff --git a/package/src/components/ImageGallery/components/ImageGrid.tsx b/package/src/components/ImageGallery/components/ImageGrid.tsx index 9e7df0a1f2..28c7165e27 100644 --- a/package/src/components/ImageGallery/components/ImageGrid.tsx +++ b/package/src/components/ImageGallery/components/ImageGrid.tsx @@ -12,6 +12,7 @@ import type { ImageGalleryAsset, ImageGalleryState, } from '../../../state-store/image-gallery-state-store'; +import { primitives } from '../../../theme'; import { FileTypes } from '../../../types/types'; import { StreamBottomSheetModalFlatList } from '../../UIComponents/StreamBottomSheetModalFlatList'; @@ -108,8 +109,9 @@ const useStyles = () => { return StyleSheet.create({ contentContainer: { flexGrow: 1, - ...contentContainer, backgroundColor: semantics.backgroundCoreApp, + marginTop: primitives.spacingSm, + ...contentContainer, }, image: { margin: 1, ...gridImage }, }); diff --git a/package/src/components/Indicators/LoadingIndicator.tsx b/package/src/components/Indicators/LoadingIndicator.tsx index 1e6593345d..4f8dc88020 100644 --- a/package/src/components/Indicators/LoadingIndicator.tsx +++ b/package/src/components/Indicators/LoadingIndicator.tsx @@ -6,7 +6,7 @@ import { useTranslationContext } from '../../contexts/translationContext/Transla import { primitives } from '../../theme'; import { Spinner } from '../UIComponents/Spinner'; -type LoadingIndicatorWrapperProps = { text: string }; +type LoadingIndicatorWrapperProps = { text: string | undefined }; const LoadingIndicatorWrapper = ({ text }: LoadingIndicatorWrapperProps) => { const styles = useStyles(); @@ -14,9 +14,11 @@ const LoadingIndicatorWrapper = ({ text }: LoadingIndicatorWrapperProps) => { return ( - - {text} - + {text ? ( + + {text} + + ) : null} ); }; @@ -44,7 +46,7 @@ export const LoadingIndicator = (props: LoadingProps) => { case 'message': return ; case 'threads': - return ; + return ; default: return ; } diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx index 786e097696..dbca30c0e4 100644 --- a/package/src/components/Message/Message.tsx +++ b/package/src/components/Message/Message.tsx @@ -381,21 +381,12 @@ const MessageWithContext = (props: MessagePropsWithContext) => { } }; - const onPressQuotedMessage = (quotedMessage: LocalMessage) => { - if (!goToMessage) { - return; - } - - goToMessage(quotedMessage.id); - }; - const errorOrFailed = message.type === 'error' || message.status === MessageStatusTypes.FAILED; const onPress = (error = errorOrFailed) => { if (dismissKeyboardOnMessageTouch) { dismissKeyboard(); } - const quotedMessage = message.quoted_message; if (error) { /** * If its a Blocked message, we don't do anything as per specs. @@ -411,8 +402,6 @@ const MessageWithContext = (props: MessagePropsWithContext) => { return; } showMessageOverlay(); - } else if (quotedMessage) { - onPressQuotedMessage(quotedMessage); } }; diff --git a/package/src/components/Message/MessageItemView/MessageContent.tsx b/package/src/components/Message/MessageItemView/MessageContent.tsx index cb65156006..429e299e03 100644 --- a/package/src/components/Message/MessageItemView/MessageContent.tsx +++ b/package/src/components/Message/MessageItemView/MessageContent.tsx @@ -121,6 +121,7 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { FileAttachmentGroup, Gallery, groupStyles, + goToMessage, isMessageAIGenerated, isMyMessage, isVeryLastMessage, @@ -221,7 +222,6 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { const { setNativeScrollability } = useMessageListItemContext(); const hasContentSideViews = !!(MessageContentLeadingView || MessageContentTrailingView); - const contentBody = ( <> { case 'quoted_reply': return ( message.quoted_message && ( - { + if (onLongPress) { + onLongPress({ + emitter: 'messageContent', + event, + }); + } + }} + onPress={(event) => { + if (!message.quoted_message || !goToMessage) { + return; + } + + if (onPress) { + onPress({ + defaultHandler: () => goToMessage(message.quoted_message!.id), + emitter: 'messageContent', + event, + }); + return; + } + + goToMessage(message.quoted_message.id); + }} + onPressIn={(event) => { + if (onPressIn) { + onPressIn({ + emitter: 'messageContent', + event, + }); + } + }} style={[styles.replyContainer, replyContainer]} > - + ) ); case 'attachments': @@ -584,7 +617,8 @@ export const MessageContent = (props: MessageContentProps) => { const messageHasSingleFile = messageContentOrder.length === 1 && messageContentOrder[0] === 'files' && isSingleFile; const messageHasOnlyText = messageContentOrder.length === 1 && messageContentOrder[0] === 'text'; - const messageHasGiphyOrImgur = + const messageHasStandaloneGiphyOrImgur = + !message.quoted_message && otherAttachments.filter( (file) => file.type === FileTypes.Giphy || file.type === FileTypes.Imgur, ).length > 0; @@ -594,17 +628,20 @@ export const MessageContent = (props: MessageContentProps) => { messageHasSingleMedia || messageHasSingleFile || messageHasOnlyText || - messageHasGiphyOrImgur; + messageHasStandaloneGiphyOrImgur; const hidePaddingHorizontal = - messageHasPoll || messageHasSingleMedia || messageHasSingleFile || messageHasGiphyOrImgur; + messageHasPoll || + messageHasSingleMedia || + messageHasSingleFile || + messageHasStandaloneGiphyOrImgur; const hidePaddingBottom = messageHasPoll || messageHasSingleMedia || messageHasSingleFile || messageHasOnlyText || - messageHasGiphyOrImgur || + messageHasStandaloneGiphyOrImgur || (messageContentOrder.length > 1 && messageContentOrder[messageContentOrder.length - 1] === 'text'); diff --git a/package/src/components/Message/MessageItemView/MessageItemView.tsx b/package/src/components/Message/MessageItemView/MessageItemView.tsx index b21f5a477f..fb2b658f59 100644 --- a/package/src/components/Message/MessageItemView/MessageItemView.tsx +++ b/package/src/components/Message/MessageItemView/MessageItemView.tsx @@ -17,6 +17,7 @@ import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useStableCallback } from '../../../hooks/useStableCallback'; import { primitives } from '../../../theme'; +import { FileTypes } from '../../../types/types'; import { checkMessageEquality, checkQuotedMessageEquality } from '../../../utils/utils'; import { useMessageData } from '../hooks/useMessageData'; @@ -293,10 +294,14 @@ const MessageItemViewWithContext = (props: MessageItemViewPropsWithContext) => { }); const groupStyle = `${alignment}_${groupStyles?.[0]?.toLowerCase?.()}`; + const hasStandaloneGiphyOrImgur = + !message.quoted_message && + otherAttachments.length > 0 && + (otherAttachments[0].type === FileTypes.Giphy || otherAttachments[0].type === FileTypes.Imgur); let noBorder = onlyEmojis && !message.quoted_message; if (otherAttachments.length) { - if (otherAttachments[0].type === 'giphy' && !isMyMessage) { + if (hasStandaloneGiphyOrImgur && !isMyMessage) { noBorder = false; } else { noBorder = true; @@ -306,10 +311,8 @@ const MessageItemViewWithContext = (props: MessageItemViewPropsWithContext) => { let backgroundColor = semantics.chatBgOutgoing; if (onlyEmojis && !message.quoted_message) { backgroundColor = 'transparent'; - } else if (otherAttachments.length) { - if (otherAttachments[0].type === 'giphy') { - backgroundColor = 'transparent'; - } + } else if (hasStandaloneGiphyOrImgur) { + backgroundColor = 'transparent'; } else if (isMessageReceivedOrErrorType) { backgroundColor = semantics.chatBgIncoming; } diff --git a/package/src/components/Message/MessageItemView/MessageReplies.tsx b/package/src/components/Message/MessageItemView/MessageReplies.tsx index 42649c1a47..d44e0aba05 100644 --- a/package/src/components/Message/MessageItemView/MessageReplies.tsx +++ b/package/src/components/Message/MessageItemView/MessageReplies.tsx @@ -235,7 +235,7 @@ const useStyles = () => { gap: primitives.spacingXs, }, messageRepliesText: { - color: shouldUseOverlayStyles ? semantics.textOnAccent : semantics.textPrimary, + color: shouldUseOverlayStyles ? semantics.textOnAccent : semantics.textLink, fontSize: primitives.typographyFontSizeSm, fontWeight: primitives.typographyFontWeightSemiBold, lineHeight: primitives.typographyLineHeightTight, diff --git a/package/src/components/Message/MessageItemView/MessageStatus.tsx b/package/src/components/Message/MessageItemView/MessageStatus.tsx index a4a4ea650a..5046ac7ba7 100644 --- a/package/src/components/Message/MessageItemView/MessageStatus.tsx +++ b/package/src/components/Message/MessageItemView/MessageStatus.tsx @@ -29,7 +29,6 @@ const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => { messageItemView: { status: { checkAllIcon, checkIcon, container, timeIcon }, }, - semantics, }, } = useTheme(); @@ -54,14 +53,14 @@ const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => { {read ? ( ) : delivered ? ( { /> ) : sending ? (