diff --git a/examples/SampleApp/ios/Podfile.lock b/examples/SampleApp/ios/Podfile.lock index acfbd0474e..5af66105b1 100644 --- a/examples/SampleApp/ios/Podfile.lock +++ b/examples/SampleApp/ios/Podfile.lock @@ -2036,65 +2036,6 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-keyboard-controller (1.20.2): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-hermes - - React-ImageManager - - React-jsi - - react-native-keyboard-controller/common (= 1.20.2) - - React-NativeModulesApple - - React-RCTFabric - - React-renderercss - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - - react-native-keyboard-controller/common (1.20.2): - - boost - - DoubleConversion - - fast_float - - fmt - - glog - - hermes-engine - - RCT-Folly - - RCT-Folly/Fabric - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-hermes - - React-ImageManager - - React-jsi - - React-NativeModulesApple - - React-RCTFabric - - React-renderercss - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - SocketRocket - - Yoga - react-native-maps (1.20.1): - React-Core - react-native-netinfo (11.4.1): @@ -3338,7 +3279,6 @@ DEPENDENCIES: - "react-native-document-picker (from `../node_modules/@react-native-documents/picker`)" - "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)" - react-native-image-picker (from `../node_modules/react-native-image-picker`) - - react-native-keyboard-controller (from `../node_modules/react-native-keyboard-controller`) - react-native-maps (from `../node_modules/react-native-maps`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) @@ -3514,8 +3454,6 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-community/geolocation" react-native-image-picker: :path: "../node_modules/react-native-image-picker" - react-native-keyboard-controller: - :path: "../node_modules/react-native-keyboard-controller" react-native-maps: :path: "../node_modules/react-native-maps" react-native-netinfo: @@ -3685,7 +3623,6 @@ SPEC CHECKSUMS: react-native-document-picker: c5fa18e9fc47b34cfbab3b0a4447d0df918a5621 react-native-geolocation: eb39c815c9b58ddc3efb552cafdd4b035e4cf682 react-native-image-picker: e479ec8884df9d99a62c1f53f2307055ad43ea85 - react-native-keyboard-controller: 6fe65d5d011d88e651d5279396e95e9c1f9458ca react-native-maps: ee1e65647460c3d41e778071be5eda10e3da6225 react-native-netinfo: f0a9899081c185db1de5bb2fdc1c88c202a059ac react-native-safe-area-context: 7fd4c2c8023da8e18eaa3424cb49d52f626debee diff --git a/examples/SampleApp/ios/SampleApp-tvOS/Info.plist b/examples/SampleApp/ios/SampleApp-tvOS/Info.plist index a8d20ea4c7..5bbd1e0da7 100644 --- a/examples/SampleApp/ios/SampleApp-tvOS/Info.plist +++ b/examples/SampleApp/ios/SampleApp-tvOS/Info.plist @@ -50,4 +50,4 @@ UIViewControllerBasedStatusBarAppearance - \ No newline at end of file + diff --git a/examples/SampleApp/ios/SampleApp/Info.plist b/examples/SampleApp/ios/SampleApp/Info.plist index ae8b0f4d7d..b072573cc4 100644 --- a/examples/SampleApp/ios/SampleApp/Info.plist +++ b/examples/SampleApp/ios/SampleApp/Info.plist @@ -59,4 +59,4 @@ UIViewControllerBasedStatusBarAppearance - \ No newline at end of file + diff --git a/examples/SampleApp/package.json b/examples/SampleApp/package.json index eb08fce2bb..bc555889ab 100644 --- a/examples/SampleApp/package.json +++ b/examples/SampleApp/package.json @@ -51,7 +51,6 @@ "react-native-gesture-handler": "^2.26.0", "react-native-haptic-feedback": "^2.3.3", "react-native-image-picker": "^8.2.1", - "react-native-keyboard-controller": "^1.20.2", "react-native-maps": "1.20.1", "react-native-nitro-modules": "^0.31.3", "react-native-nitro-sound": "^0.2.9", diff --git a/examples/SampleApp/src/screens/ChannelScreen.tsx b/examples/SampleApp/src/screens/ChannelScreen.tsx index c1e35f34f9..333f5d079e 100644 --- a/examples/SampleApp/src/screens/ChannelScreen.tsx +++ b/examples/SampleApp/src/screens/ChannelScreen.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react'; import type { LocalMessage, Channel as StreamChatChannel } from 'stream-chat'; -import { useHeaderHeight } from '@react-navigation/elements'; import { RouteProp, useFocusEffect, useNavigation } from '@react-navigation/native'; import { Channel, @@ -20,7 +19,7 @@ import { ChannelAvatar, useChannelPreviewDisplayPresence, } from 'stream-chat-react-native'; -import { Pressable, StyleSheet, View } from 'react-native'; +import { Platform, Pressable, StyleSheet, View } from 'react-native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useAppContext } from '../context/AppContext'; @@ -230,7 +229,6 @@ export const ChannelScreen: React.FC = ({ }, [chatClient, colors, t, handleMessageInfo], ); - const headerHeight = useHeaderHeight(); if (!channel || !chatClient) { return null; @@ -247,7 +245,7 @@ export const ChannelScreen: React.FC = ({ disableTypingIndicator enforceUniqueReaction initialScrollToFirstUnreadMessage - keyboardVerticalOffset={headerHeight} + keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : -300} messageActions={messageActions} MessageHeader={MessageReminderHeader} MessageLocation={MessageLocation} diff --git a/examples/SampleApp/src/screens/ThreadScreen.tsx b/examples/SampleApp/src/screens/ThreadScreen.tsx index 5abc0b6073..4bea7bc519 100644 --- a/examples/SampleApp/src/screens/ThreadScreen.tsx +++ b/examples/SampleApp/src/screens/ThreadScreen.tsx @@ -1,6 +1,5 @@ import React, { useCallback } from 'react'; -import { StyleSheet, View } from 'react-native'; -import { useHeaderHeight } from '@react-navigation/elements'; +import { Platform, StyleSheet, View } from 'react-native'; import { Channel, @@ -86,8 +85,7 @@ export const ThreadScreen: React.FC = ({ const { client: chatClient } = useChatContext(); const { t } = useTranslationContext(); const { setThread } = useStreamChatContext(); - const { messageInputFloating } = useAppContext(); - const headerHeight = useHeaderHeight(); + const { messageInputFloating, messageListImplementation } = useAppContext(); const onPressMessage: NonNullable['onPressMessage']> = ( payload, @@ -125,7 +123,7 @@ export const ThreadScreen: React.FC = ({ AttachmentPickerSelectionBar={CustomAttachmentPickerSelectionBar} channel={channel} enforceUniqueReaction - keyboardVerticalOffset={headerHeight} + keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : -300} messageActions={messageActions} messageInputFloating={messageInputFloating} MessageHeader={MessageReminderHeader} @@ -135,7 +133,7 @@ export const ThreadScreen: React.FC = ({ threadList > - + ); diff --git a/examples/SampleApp/yarn.lock b/examples/SampleApp/yarn.lock index a93301a6f6..3f241a37a9 100644 --- a/examples/SampleApp/yarn.lock +++ b/examples/SampleApp/yarn.lock @@ -1580,10 +1580,10 @@ "@gorhom/portal" "1.0.14" invariant "^2.2.4" -"@gorhom/bottom-sheet@^5.1.8": - version "5.1.8" - resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-5.1.8.tgz#65547917f5b1dae5a1291dabd4ea8bfee09feba4" - integrity sha512-QuYIVjn3K9bW20n5bgOSjvxBYoWG4YQXiLGOheEAMgISuoT6sMcA270ViSkkb0fenPxcIOwzCnFNuxmr739T9A== +"@gorhom/bottom-sheet@^5.2.8": + version "5.2.8" + resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-5.2.8.tgz#25e49122c30ffe83d3813b3bcf3dec39f3359aeb" + integrity sha512-+N27SMpbBxXZQ/IA2nlEV6RGxL/qSFHKfdFKcygvW+HqPG5jVNb1OqehLQsGfBP+Up42i0gW5ppI+DhpB7UCzA== dependencies: "@gorhom/portal" "1.0.14" invariant "^2.2.4" @@ -7658,13 +7658,6 @@ react-native-is-edge-to-edge@^1.2.1: resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz#64e10851abd9d176cbf2b40562f751622bde3358" integrity sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q== -react-native-keyboard-controller@^1.20.2: - version "1.20.2" - resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.20.2.tgz#2953341f48e25fec20dd732241cb8152251fd4d1" - integrity sha512-3xvPTIfasAbosDxT3Mc6b5Xr/M+yq99ECCM4iGnSAngziIVUZsZuPpfYL7nN1UiN9rQjWKvjdul/jq9E0V1s2w== - dependencies: - react-native-is-edge-to-edge "^1.2.1" - react-native-lightbox@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/react-native-lightbox/-/react-native-lightbox-0.7.0.tgz#e52b4d7fcc141f59d7b23f0180de535e35b20ec9" diff --git a/package/src/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.tsx b/package/src/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.tsx index f75e5853b7..b535c2680f 100644 --- a/package/src/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.tsx +++ b/package/src/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Keyboard, @@ -6,42 +6,40 @@ import { KeyboardAvoidingViewProps as ReactNativeKeyboardAvoidingViewProps, } from 'react-native'; -import { - KeyboardAvoidingView as KeyboardControllerPackageKeyboardAvoidingView, - KeyboardController as KeyboardControllerPackageKeyboardController, - KeyboardEvents, - KeyboardProvider, -} from 'react-native-keyboard-controller'; - import { KeyboardCompatibleView as KeyboardCompatibleViewDefault } from './KeyboardCompatibleView'; type ExtraKeyboardControllerProps = { behavior?: 'translate-with-padding'; }; +type KeyboardControllerModule = typeof import('react-native-keyboard-controller'); + +const optionalRequire = (): T | undefined => { + try { + return require('react-native-keyboard-controller') as T; + } catch { + return undefined; + } +}; + export type KeyboardCompatibleViewProps = ReactNativeKeyboardAvoidingViewProps & ExtraKeyboardControllerProps; -let KeyboardControllerPackage: - | { - KeyboardAvoidingView: typeof KeyboardControllerPackageKeyboardAvoidingView; - KeyboardController: typeof KeyboardControllerPackageKeyboardController; - KeyboardProvider: typeof KeyboardProvider; - KeyboardEvents: typeof KeyboardEvents; - } - | undefined; +const KeyboardControllerPackage = optionalRequire(); -try { - KeyboardControllerPackage = require('react-native-keyboard-controller'); -} catch (e) { - KeyboardControllerPackage = undefined; -} +const { AndroidSoftInputModes, KeyboardController, KeyboardProvider, KeyboardAvoidingView } = + KeyboardControllerPackage ?? {}; export const KeyboardCompatibleView = (props: KeyboardCompatibleViewProps) => { const { behavior = 'translate-with-padding', children, ...rest } = props; - const KeyboardProvider = KeyboardControllerPackage?.KeyboardProvider; - const KeyboardAvoidingView = KeyboardControllerPackage?.KeyboardAvoidingView; + useEffect(() => { + if (AndroidSoftInputModes) { + KeyboardController?.setInputMode(AndroidSoftInputModes.SOFT_INPUT_ADJUST_RESIZE); + } + + return () => KeyboardController?.setDefaultMode(); + }, []); if (KeyboardProvider && KeyboardAvoidingView) { return ( diff --git a/package/src/components/Message/MessageSimple/MessageWrapper.tsx b/package/src/components/Message/MessageSimple/MessageWrapper.tsx index 39a4a36f22..97da35634e 100644 --- a/package/src/components/Message/MessageSimple/MessageWrapper.tsx +++ b/package/src/components/Message/MessageSimple/MessageWrapper.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React from 'react'; import { View } from 'react-native'; @@ -14,7 +14,6 @@ import { ThemeProvider, useTheme } from '../../../contexts/themeContext/ThemeCon import { useStateStore } from '../../../hooks/useStateStore'; import { ChannelUnreadStateStoreType } from '../../../state-store/channel-unread-state'; -import { MessagePreviousAndNextMessageStoreType } from '../../../state-store/message-list-prev-next-state'; const channelUnreadStateSelector = (state: ChannelUnreadStateStoreType) => ({ first_unread_message_id: state.channelUnreadState?.first_unread_message_id, @@ -25,10 +24,12 @@ const channelUnreadStateSelector = (state: ChannelUnreadStateStoreType) => ({ export type MessageWrapperProps = { message: LocalMessage; + previousMessage?: LocalMessage; + nextMessage?: LocalMessage; }; export const MessageWrapper = React.memo((props: MessageWrapperProps) => { - const { message } = props; + const { message, previousMessage, nextMessage } = props; const { client } = useChatContext(); const { channelUnreadStateStore, @@ -47,34 +48,22 @@ export const MessageWrapper = React.memo((props: MessageWrapperProps) => { myMessageTheme, shouldShowUnreadUnderlay, } = useMessagesContext(); - const { - goToMessage, - onThreadSelect, - noGroupByUser, - modifiedTheme, - messageListPreviousAndNextMessageStore, - } = useMessageListItemContext(); + const { goToMessage, onThreadSelect, noGroupByUser, modifiedTheme } = useMessageListItemContext(); const dateSeparatorDate = useMessageDateSeparator({ hideDateSeparators, message, - messageListPreviousAndNextMessageStore, + previousMessage, }); - const selector = useCallback( - (state: MessagePreviousAndNextMessageStoreType) => ({ - nextMessage: state.messageList[message.id]?.nextMessage, - }), - [message.id], - ); - const { nextMessage } = useStateStore(messageListPreviousAndNextMessageStore.state, selector); const isNewestMessage = nextMessage === undefined; const groupStyles = useMessageGroupStyles({ dateSeparatorDate, getMessageGroupStyle, maxTimeBetweenGroupedMessages, message, - messageListPreviousAndNextMessageStore, + previousMessage, + nextMessage, noGroupByUser, }); diff --git a/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageAvatar.test.js.snap b/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageAvatar.test.js.snap index 22a0a2318a..cfea0def75 100644 --- a/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageAvatar.test.js.snap +++ b/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageAvatar.test.js.snap @@ -36,12 +36,16 @@ exports[`MessageAvatar should render message avatar 1`] = ` { "backgroundColor": undefined, }, - undefined, + { + "borderColor": "hsla(0, 0%, 0%, 0.1)", + "borderWidth": 1, + }, ] } testID="avatar-image" > { return 'generic-message'; }; -const renderItem = ({ item: message }: { item: LocalMessage }) => { - return ; -}; - const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => { const LoadingMoreRecentIndicator = props.threadList ? InlineLoadingMoreRecentThreadIndicator @@ -365,17 +361,27 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => [myMessageThemeString, theme], ); - const { - messageListPreviousAndNextMessageStore, - processedMessageList, - rawMessageList, - viewabilityChangedCallback, - } = useMessageList({ + const { processedMessageList, rawMessageList, viewabilityChangedCallback } = useMessageList({ isFlashList: true, isLiveStreaming, threadList, }); + const renderItem = useCallback( + ({ item: message, index }: { item: LocalMessage; index: number }) => { + const previousMessage = processedMessageList[index - 1]; + const nextMessage = processedMessageList[index + 1]; + return ( + + ); + }, + [processedMessageList], + ); + /** * We need topMessage and channelLastRead values to set the initial scroll position. * So these values only get used if `initialScrollToFirstUnreadMessage` prop is true. @@ -742,20 +748,12 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => const messageListItemContextValue: MessageListItemContextValue = useMemo( () => ({ goToMessage, - messageListPreviousAndNextMessageStore, modifiedTheme, noGroupByUser, onThreadSelect, setNativeScrollability, }), - [ - goToMessage, - messageListPreviousAndNextMessageStore, - modifiedTheme, - noGroupByUser, - onThreadSelect, - setNativeScrollability, - ], + [goToMessage, modifiedTheme, noGroupByUser, onThreadSelect, setNativeScrollability], ); /** diff --git a/package/src/components/MessageList/MessageList.tsx b/package/src/components/MessageList/MessageList.tsx index 3cdc53ba94..fbd75727ea 100644 --- a/package/src/components/MessageList/MessageList.tsx +++ b/package/src/components/MessageList/MessageList.tsx @@ -248,10 +248,6 @@ type MessageListPropsWithContext = Pick< isLiveStreaming?: boolean; }; -const renderItem = ({ item: message }: { item: LocalMessage }) => { - return ; -}; - const messageInputHeightStoreSelector = (state: MessageInputHeightState) => ({ height: state.height, }); @@ -349,15 +345,29 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { * NOTE: rawMessageList changes only when messages array state changes * processedMessageList changes on any state change */ - const { - messageListPreviousAndNextMessageStore, - processedMessageList, - rawMessageList, - viewabilityChangedCallback, - } = useMessageList({ + const { processedMessageList, rawMessageList, viewabilityChangedCallback } = useMessageList({ isLiveStreaming, threadList, }); + + const processedMessageListRef = useRef(processedMessageList); + processedMessageListRef.current = processedMessageList; + + const renderItem = useCallback( + ({ item: message, index }: { item: LocalMessage; index: number }) => { + const previousMessage = processedMessageListRef.current[index + 1]; + const nextMessage = processedMessageListRef.current[index - 1]; + return ( + + ); + }, + [processedMessageListRef], + ); + const messageListLengthBeforeUpdate = useRef(0); const messageListLengthAfterUpdate = processedMessageList.length; @@ -805,20 +815,12 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { const messageListItemContextValue: MessageListItemContextValue = useMemo( () => ({ goToMessage, - messageListPreviousAndNextMessageStore, modifiedTheme, noGroupByUser, onThreadSelect, setNativeScrollability, }), - [ - goToMessage, - messageListPreviousAndNextMessageStore, - modifiedTheme, - noGroupByUser, - onThreadSelect, - setNativeScrollability, - ], + [goToMessage, modifiedTheme, noGroupByUser, onThreadSelect, setNativeScrollability], ); /** @@ -1122,6 +1124,7 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { ); } + // TODO: Make sure this is actually overridable as the previous FlatList was. return ( { ) : ( - + // TODO: Consider hiding this behind a feature flag. + layout={LinearTransition.duration(200)} contentContainerStyle={flatListContentContainerStyle} /** Disables the MessageList UI. Which means, message actions, reactions won't work. */ data={processedMessageList} @@ -1159,11 +1164,11 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { onScrollToIndexFailed={onScrollToIndexFailedRef.current} onTouchEnd={dismissImagePicker} onViewableItemsChanged={stableOnViewableItemsChanged} + // @ts-expect-error Safe to do for now ref={refCallback} renderItem={renderItem} scrollEventThrottle={isLiveStreaming ? 16 : undefined} showsVerticalScrollIndicator={false} - // @ts-expect-error react-native internal strictMode={isLiveStreaming} style={flatListStyle} testID='message-flat-list' diff --git a/package/src/components/MessageList/__tests__/useMessageDateSeparator.test.ts b/package/src/components/MessageList/__tests__/useMessageDateSeparator.test.ts index 07203353a6..901ea76a8c 100644 --- a/package/src/components/MessageList/__tests__/useMessageDateSeparator.test.ts +++ b/package/src/components/MessageList/__tests__/useMessageDateSeparator.test.ts @@ -2,15 +2,12 @@ import { renderHook } from '@testing-library/react-native'; import { LocalMessage } from 'stream-chat'; -import { MessagePreviousAndNextMessageStore } from '../../../state-store/message-list-prev-next-state'; import { useMessageDateSeparator } from '../hooks/useMessageDateSeparator'; describe('useMessageDateSeparator', () => { - let messageListPreviousAndNextMessageStore: MessagePreviousAndNextMessageStore; let messages: LocalMessage[]; beforeEach(() => { - messageListPreviousAndNextMessageStore = new MessagePreviousAndNextMessageStore(); messages = [ { created_at: new Date('2020-01-01T00:00:00.000Z'), @@ -28,13 +25,10 @@ describe('useMessageDateSeparator', () => { text: 'Hello World', }, ] as LocalMessage[]; - messageListPreviousAndNextMessageStore.setMessageListPreviousAndNextMessage({ messages }); }); it('should return undefined if no message is passed', () => { - const { result } = renderHook(() => - useMessageDateSeparator({ message: undefined, messageListPreviousAndNextMessageStore }), - ); + const { result } = renderHook(() => useMessageDateSeparator({ message: undefined })); expect(result.current).toBeUndefined(); }); @@ -43,7 +37,7 @@ describe('useMessageDateSeparator', () => { useMessageDateSeparator({ hideDateSeparators: true, message: messages[1], - messageListPreviousAndNextMessageStore, + previousMessage: messages[0], }), ); expect(result.current).toBeUndefined(); @@ -51,7 +45,7 @@ describe('useMessageDateSeparator', () => { it('should return the date separator for a message if previous message is not the same day', () => { const { result } = renderHook(() => - useMessageDateSeparator({ message: messages[1], messageListPreviousAndNextMessageStore }), + useMessageDateSeparator({ message: messages[1], previousMessage: messages[0] }), ); expect(result.current).toBe(messages[1].created_at); }); @@ -69,14 +63,12 @@ describe('useMessageDateSeparator', () => { text: 'World', }, ] as LocalMessage[]; - const messageListPreviousAndNextMessageStore = new MessagePreviousAndNextMessageStore(); - messageListPreviousAndNextMessageStore.setMessageListPreviousAndNextMessage({ messages }); const { result: resultOfFirstMessage } = renderHook(() => - useMessageDateSeparator({ message: messages[0], messageListPreviousAndNextMessageStore }), + useMessageDateSeparator({ message: messages[0], previousMessage: undefined }), ); expect(resultOfFirstMessage.current).toBe(messages[0].created_at); const { result: resultOfSecondMessage } = renderHook(() => - useMessageDateSeparator({ message: messages[1], messageListPreviousAndNextMessageStore }), + useMessageDateSeparator({ message: messages[1], previousMessage: messages[0] }), ); expect(resultOfSecondMessage.current).toBeUndefined(); }); diff --git a/package/src/components/MessageList/hooks/useMessageDateSeparator.ts b/package/src/components/MessageList/hooks/useMessageDateSeparator.ts index 80957b0b1a..32433fa936 100644 --- a/package/src/components/MessageList/hooks/useMessageDateSeparator.ts +++ b/package/src/components/MessageList/hooks/useMessageDateSeparator.ts @@ -1,13 +1,7 @@ -import { useCallback, useMemo } from 'react'; +import { useMemo } from 'react'; import { LocalMessage } from 'stream-chat'; -import { useStateStore } from '../../../hooks/useStateStore'; -import { - MessagePreviousAndNextMessageStore, - MessagePreviousAndNextMessageStoreType, -} from '../../../state-store/message-list-prev-next-state'; - export const getDateSeparatorValue = ({ hideDateSeparators, message, @@ -37,20 +31,12 @@ export const getDateSeparatorValue = ({ export const useMessageDateSeparator = ({ hideDateSeparators, message, - messageListPreviousAndNextMessageStore, + previousMessage, }: { hideDateSeparators?: boolean; message?: LocalMessage; - messageListPreviousAndNextMessageStore: MessagePreviousAndNextMessageStore; + previousMessage?: LocalMessage; }) => { - const selector = useCallback( - (state: MessagePreviousAndNextMessageStoreType) => ({ - previousMessage: message ? state.messageList[message.id]?.previousMessage : undefined, - }), - [message], - ); - const { previousMessage } = useStateStore(messageListPreviousAndNextMessageStore.state, selector); - const dateSeparatorDate = useMemo(() => { if (!message && !previousMessage) { return undefined; diff --git a/package/src/components/MessageList/hooks/useMessageGroupStyles.ts b/package/src/components/MessageList/hooks/useMessageGroupStyles.ts index 4d4d286bc8..43378ff6420 100644 --- a/package/src/components/MessageList/hooks/useMessageGroupStyles.ts +++ b/package/src/components/MessageList/hooks/useMessageGroupStyles.ts @@ -1,15 +1,10 @@ -import { useCallback, useMemo } from 'react'; +import { useMemo } from 'react'; import { LocalMessage } from 'stream-chat'; import { useMessageDateSeparator } from './useMessageDateSeparator'; import { MessagesContextValue } from '../../../contexts/messagesContext/MessagesContext'; -import { useStateStore } from '../../../hooks/useStateStore'; -import { - MessagePreviousAndNextMessageStore, - MessagePreviousAndNextMessageStoreType, -} from '../../../state-store/message-list-prev-next-state'; import { getGroupStyle } from '../utils/getGroupStyles'; /** @@ -20,7 +15,8 @@ export const useMessageGroupStyles = ({ dateSeparatorDate, maxTimeBetweenGroupedMessages, message, - messageListPreviousAndNextMessageStore, + previousMessage, + nextMessage, getMessageGroupStyle = getGroupStyle, }: { noGroupByUser?: boolean; @@ -28,24 +24,13 @@ export const useMessageGroupStyles = ({ dateSeparatorDate?: Date; maxTimeBetweenGroupedMessages?: number; message: LocalMessage; - messageListPreviousAndNextMessageStore: MessagePreviousAndNextMessageStore; + previousMessage?: LocalMessage; + nextMessage?: LocalMessage; }) => { - const selector = useCallback( - (state: MessagePreviousAndNextMessageStoreType) => ({ - nextMessage: state.messageList[message.id]?.nextMessage, - previousMessage: state.messageList[message.id]?.previousMessage, - }), - [message.id], - ); - const { previousMessage, nextMessage } = useStateStore( - messageListPreviousAndNextMessageStore.state, - selector, - ); - // This is needed to calculate the group styles for the next message const nextMessageDateSeparatorDate = useMessageDateSeparator({ message: nextMessage, - messageListPreviousAndNextMessageStore, + previousMessage: message, }); const groupStyles = useMemo(() => { diff --git a/package/src/components/MessageList/hooks/useMessageList.ts b/package/src/components/MessageList/hooks/useMessageList.ts index e5ee25fe65..61d129238c 100644 --- a/package/src/components/MessageList/hooks/useMessageList.ts +++ b/package/src/components/MessageList/hooks/useMessageList.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useMemo } from 'react'; import type { LocalMessage } from 'stream-chat'; @@ -11,7 +11,6 @@ import { usePaginatedMessageListContext } from '../../../contexts/paginatedMessa import { useThreadContext } from '../../../contexts/threadContext/ThreadContext'; import { useRAFCoalescedValue } from '../../../hooks'; -import { MessagePreviousAndNextMessageStore } from '../../../state-store/message-list-prev-next-state'; export type UseMessageListParams = { threadList?: boolean; @@ -60,9 +59,6 @@ export const useMessageList = (params: UseMessageListParams) => { const { messages, viewabilityChangedCallback } = usePaginatedMessageListContext(); const { threadMessages } = useThreadContext(); const messageList = threadList ? threadMessages : messages; - const [messageListPreviousAndNextMessageStore] = useState( - () => new MessagePreviousAndNextMessageStore(), - ); const processedMessageList = useMemo(() => { const newMessageList = []; @@ -84,24 +80,16 @@ export const useMessageList = (params: UseMessageListParams) => { return newMessageList; }, [messageList, deletedMessagesVisibilityType, client.userID, isFlashList]); - useEffect(() => { - messageListPreviousAndNextMessageStore.setMessageListPreviousAndNextMessage({ - isFlashList, - messages: processedMessageList, - }); - }, [processedMessageList, messageListPreviousAndNextMessageStore, isFlashList]); - const data = useRAFCoalescedValue(processedMessageList, isLiveStreaming); return useMemo( () => ({ - messageListPreviousAndNextMessageStore, /** Messages enriched with dates/readby/groups and also reversed in order */ processedMessageList: data, /** Raw messages from the channel state */ rawMessageList: messageList, viewabilityChangedCallback, }), - [data, messageList, messageListPreviousAndNextMessageStore, viewabilityChangedCallback], + [data, messageList, viewabilityChangedCallback], ); }; diff --git a/package/src/components/MessageList/utils/getGroupStyles.ts b/package/src/components/MessageList/utils/getGroupStyles.ts index e3dec9e16a..06e704e678 100644 --- a/package/src/components/MessageList/utils/getGroupStyles.ts +++ b/package/src/components/MessageList/utils/getGroupStyles.ts @@ -4,8 +4,8 @@ import { isEditedMessage } from '../../../utils/utils'; export type MessageGroupStylesParams = { message: LocalMessage; - previousMessage: LocalMessage; - nextMessage: LocalMessage; + previousMessage?: LocalMessage; + nextMessage?: LocalMessage; maxTimeBetweenGroupedMessages?: number; dateSeparatorDate?: Date; nextMessageDateSeparatorDate?: Date; diff --git a/package/src/components/Thread/Thread.tsx b/package/src/components/Thread/Thread.tsx index 93c177ccea..4edc298e1f 100644 --- a/package/src/components/Thread/Thread.tsx +++ b/package/src/components/Thread/Thread.tsx @@ -68,6 +68,7 @@ type ThreadPropsWithContext = Pick & * Call custom function on closing thread if handling thread state elsewhere */ onThreadDismount?: () => void; + shouldUseFlashList?: boolean; }; const ThreadWithContext = (props: ThreadPropsWithContext) => { @@ -86,6 +87,7 @@ const ThreadWithContext = (props: ThreadPropsWithContext) => { parentMessagePreventPress = true, thread, threadInstance, + shouldUseFlashList = false, } = props; useEffect(() => { @@ -119,13 +121,13 @@ const ThreadWithContext = (props: ThreadPropsWithContext) => { [parentMessagePreventPress], ); - if (!thread) { + if (!thread?.id) { return null; } return ( - {FlashList ? ( + {FlashList && shouldUseFlashList ? ( void; - /** - * Store to get the previous and next message in the message list - */ - messageListPreviousAndNextMessageStore: MessagePreviousAndNextMessageStore; /** * Theme to use for the message list item */