diff --git a/.gitignore b/.gitignore index 3895765f2f..ca66e76e98 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ index.android.bundle *.DSYM.zip **/metrics/ package/shared-native/.sync-state/ + +.claude/worktrees diff --git a/README.md b/README.md index 5622f90803..a010d8eb45 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![NPM](https://img.shields.io/npm/v/stream-chat-react-native.svg)](https://www.npmjs.com/package/stream-chat-react-native) [![Build Status](https://github.com/GetStream/stream-chat-react-native/actions/workflows/release.yml/badge.svg)](https://github.com/GetStream/stream-chat-react-native/actions) [![Component Reference](https://img.shields.io/badge/docs-component%20reference-blue.svg)](https://getstream.io/chat/docs/sdk/reactnative) -![JS Bundle Size](https://img.shields.io/badge/js_bundle_size-360%20KB-blue) +![JS Bundle Size](https://img.shields.io/badge/js_bundle_size-368%20KB-blue) diff --git a/examples/ExpoMessaging/app/channel/[cid]/index.tsx b/examples/ExpoMessaging/app/channel/[cid]/index.tsx index 0410bec2f6..fa8be06f86 100644 --- a/examples/ExpoMessaging/app/channel/[cid]/index.tsx +++ b/examples/ExpoMessaging/app/channel/[cid]/index.tsx @@ -73,6 +73,7 @@ export default function ChannelScreen() { channel={channel} onPressMessage={onPressMessage} keyboardVerticalOffset={headerHeight} + topInset={headerHeight} thread={thread} > diff --git a/examples/ExpoMessaging/package.json b/examples/ExpoMessaging/package.json index 1664d6fd02..b57ba6ed16 100644 --- a/examples/ExpoMessaging/package.json +++ b/examples/ExpoMessaging/package.json @@ -47,13 +47,14 @@ "react-dom": "19.2.0", "react-native": "0.83.2", "react-native-gesture-handler": "~2.30.0", + "react-native-keyboard-controller": "1.20.7", "react-native-maps": "1.26.20", "react-native-reanimated": "4.2.1", "react-native-safe-area-context": "~5.6.2", "react-native-screens": "~4.23.0", "react-native-svg": "15.15.3", - "react-native-web": "^0.21.0", "react-native-teleport": "^1.0.2", + "react-native-web": "^0.21.0", "react-native-worklets": "0.7.2", "stream-chat-expo": "link:../../package/expo-package", "stream-chat-react-native-core": "link:../../package" diff --git a/examples/ExpoMessaging/yarn.lock b/examples/ExpoMessaging/yarn.lock index edfa2b6f3b..d307548819 100644 --- a/examples/ExpoMessaging/yarn.lock +++ b/examples/ExpoMessaging/yarn.lock @@ -5526,6 +5526,13 @@ react-native-is-edge-to-edge@1.2.1, 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.7: + version "1.20.7" + resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.20.7.tgz#e1be1c15a5eb10b96a40a0812d8472e6e4bd8f29" + integrity sha512-G8S5jz1FufPrcL1vPtReATx+jJhT/j+sTqxMIb30b1z7cYEfMlkIzOCyaHgf6IMB2KA9uBmnA5M6ve2A9Ou4kw== + 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" @@ -6073,10 +6080,10 @@ stream-chat-react-native-core@8.1.0: version "0.0.0" uid "" -stream-chat@^9.42.1: - version "9.42.1" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.42.1.tgz#8b6aa4e3e73a39ed07bb2a4f2a6829ba9354567a" - integrity sha512-o+9wQO4Ruu1A48T0IrX9ZH8+9F5xPgGLPvflaswaPeLyIZXcy8bsQdcT/HSrPmT7gs0WGD3qcbXaAJU5lMQezQ== +stream-chat@^9.44.2: + version "9.44.2" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.44.2.tgz#97d23ae4ac356b352bb0f20a31a29dc63d3ea6f5" + integrity sha512-TXALWeHyWnSn1KlGYEF0sltEHB26vFd26l5m1qlE9Q1XHo9RPPSyLb5mfXqTEY8b2FAv57Ei3hrT8nSXVWacDQ== dependencies: "@types/jsonwebtoken" "^9.0.8" "@types/ws" "^8.5.14" diff --git a/package/package.json b/package/package.json index 4cc0500837..96eee94d36 100644 --- a/package/package.json +++ b/package/package.json @@ -94,7 +94,6 @@ "emoji-mart": ">=5.4.0", "react-native": ">=0.73.0", "react-native-gesture-handler": ">=2.18.0", - "react-native-keyboard-controller": ">=1.20.2", "react-native-reanimated": ">=3.16.0", "react-native-safe-area-context": ">=5.4.1", "react-native-svg": ">=15.8.0", @@ -112,9 +111,6 @@ }, "@emoji-mart/data": { "optional": true - }, - "react-native-keyboard-controller": { - "optional": true } }, "devDependencies": { @@ -163,7 +159,6 @@ "react-native": "0.80.2", "react-native-builder-bob": "0.40.11", "react-native-gesture-handler": "^2.26.0", - "react-native-keyboard-controller": "^1.20.2", "react-native-reanimated": "3.18.0", "react-native-safe-area-context": "^5.6.1", "react-native-svg": "15.12.0", diff --git a/package/src/components/AttachmentPicker/AttachmentPicker.tsx b/package/src/components/AttachmentPicker/AttachmentPicker.tsx index 3b8586e82f..cd4d786930 100644 --- a/package/src/components/AttachmentPicker/AttachmentPicker.tsx +++ b/package/src/components/AttachmentPicker/AttachmentPicker.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { BackHandler, - EmitterSubscription, Keyboard, Platform, View, @@ -21,7 +20,6 @@ import { useComponentsContext } from '../../contexts/componentsContext/Component import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useStableCallback } from '../../hooks'; import { BottomSheet } from '../BottomSheetCompatibility/BottomSheet'; -import { KeyboardControllerPackage } from '../KeyboardCompatibleView/KeyboardControllerAvoidingView'; dayjs.extend(duration); @@ -41,6 +39,7 @@ export const AttachmentPicker = () => { attachmentPickerBottomSheetHeight, bottomSheetRef: ref, bottomInset, + topInset, disableAttachmentPicker, } = useAttachmentPickerContext(); const { AttachmentPickerContent, AttachmentPickerSelectionBar } = useComponentsContext(); @@ -78,18 +77,10 @@ export const AttachmentPicker = () => { } closePicker(); }; - let keyboardSubscription: EmitterSubscription | null = null; - if (KeyboardControllerPackage?.KeyboardEvents) { - keyboardSubscription = KeyboardControllerPackage.KeyboardEvents.addListener( - 'keyboardWillShow', - onKeyboardOpenHandler, - ); - } else { - const keyboardShowEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'; - keyboardSubscription = Keyboard.addListener(keyboardShowEvent, onKeyboardOpenHandler); - } + const keyboardShowEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'; + const keyboardSubscription = Keyboard.addListener(keyboardShowEvent, onKeyboardOpenHandler); return () => { - keyboardSubscription?.remove(); + keyboardSubscription.remove(); }; }, [attachmentPickerStore, closePicker]); @@ -104,7 +95,7 @@ export const AttachmentPicker = () => { const initialSnapPoint = attachmentPickerBottomSheetHeight; const pickerTopInset = Math.max( 0, - windowHeight - attachmentPickerBottomSheetHeight - bottomInset, + windowHeight - topInset - attachmentPickerBottomSheetHeight - bottomInset, ); /** diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 3645e6dc8f..52070c39b8 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -114,7 +114,7 @@ import { } from '../../utils/utils'; import { NotificationAnnouncer } from '../Accessibility/NotificationAnnouncer'; import { AttachmentPicker } from '../AttachmentPicker/AttachmentPicker'; -import type { KeyboardCompatibleViewProps } from '../KeyboardCompatibleView/KeyboardControllerAvoidingView'; +import type { KeyboardCompatibleViewProps } from '../KeyboardCompatibleView/KeyboardCompatibleView'; import { Emoji } from '../MessageMenu/EmojiPickerList'; import { emojis } from '../MessageMenu/emojis'; import { toUnicodeScalarString } from '../MessageMenu/utils/toUnicodeScalarString'; @@ -496,7 +496,7 @@ const ChannelWithContext = (props: PropsWithChildren) = thread: threadFromProps, threadList, threadMessages, - topInset, + topInset = 0, isOnline, maximumMessageLimit, initializeOnMount = true, @@ -567,6 +567,18 @@ const ChannelWithContext = (props: PropsWithChildren) = channel, }); + const shouldLoadInitialChannelAtFirstUnreadMessage = useStableCallback((unreadCount?: number) => { + if (messageId || !initialScrollToFirstUnreadMessage || !client.user) { + return false; + } + + return (unreadCount ?? channel.countUnread()) > scrollToFirstUnreadThreshold; + }); + + const hasPendingInitialTargetLoad = useStableCallback(() => { + return !!messageId || shouldLoadInitialChannelAtFirstUnreadMessage(); + }); + const { setMessages: copyMessagesStateFromChannel, viewabilityChangedCallback } = usePrunableMessageList({ maximumMessageLimit, setMessages: rawCopyMessagesStateFromChannel }); @@ -693,6 +705,7 @@ const ChannelWithContext = (props: PropsWithChildren) = const initChannel = async () => { setLastRead(new Date()); const unreadCount = channel.countUnread(); + const shouldLoadAtFirstUnread = shouldLoadInitialChannelAtFirstUnreadMessage(unreadCount); if (!channel || !shouldSyncChannel) { return; } @@ -722,13 +735,14 @@ const ChannelWithContext = (props: PropsWithChildren) = if (messageId) { await loadChannelAroundMessage({ messageId, setTargetedMessage }); - } else if ( - initialScrollToFirstUnreadMessage && - client.user && - unreadCount > scrollToFirstUnreadThreshold - ) { + } else if (shouldLoadAtFirstUnread) { + const clientUserId = client.user?.id; + if (!clientUserId) { + return; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { user, ...ownReadState } = channel.state.read[client.user.id]; + const { user, ...ownReadState } = channel.state.read[clientUserId]; await loadChannelAtFirstUnreadMessage({ channelUnreadState: ownReadState, @@ -1578,6 +1592,7 @@ const ChannelWithContext = (props: PropsWithChildren) = setChannelUnreadState, setLastRead, setTargetedMessage, + hasPendingInitialTargetLoad, targetedMessage, threadList, uploadAbortControllerRef, diff --git a/package/src/components/Channel/hooks/useCreateChannelContext.ts b/package/src/components/Channel/hooks/useCreateChannelContext.ts index 5c7c9e33ca..8e8870707c 100644 --- a/package/src/components/Channel/hooks/useCreateChannelContext.ts +++ b/package/src/components/Channel/hooks/useCreateChannelContext.ts @@ -27,6 +27,7 @@ export const useCreateChannelContext = ({ setChannelUnreadState, setLastRead, setTargetedMessage, + hasPendingInitialTargetLoad, targetedMessage, threadList, uploadAbortControllerRef, @@ -69,6 +70,7 @@ export const useCreateChannelContext = ({ setChannelUnreadState, setLastRead, setTargetedMessage, + hasPendingInitialTargetLoad, targetedMessage, threadList, uploadAbortControllerRef, diff --git a/package/src/components/ImageGallery/ImageGallery.tsx b/package/src/components/ImageGallery/ImageGallery.tsx index d3af58ec2d..a07076bc59 100644 --- a/package/src/components/ImageGallery/ImageGallery.tsx +++ b/package/src/components/ImageGallery/ImageGallery.tsx @@ -36,7 +36,7 @@ import { useViewport } from '../../hooks/useViewport'; import { IconProps } from '../../icons/utils/base'; import { ImageGalleryState } from '../../state-store/image-gallery-state-store'; import { FileTypes } from '../../types/types'; -import { dismissKeyboard } from '../KeyboardCompatibleView/KeyboardControllerAvoidingView'; +import { dismissKeyboard } from '../KeyboardCompatibleView/KeyboardCompatibleView'; import { BottomSheetModal } from '../UIComponents'; export type ImageGalleryActionHandler = () => Promise | void; diff --git a/package/src/components/KeyboardCompatibleView/KeyboardCompatibleView.tsx b/package/src/components/KeyboardCompatibleView/KeyboardCompatibleView.tsx index 52f599ea16..2371c6e364 100644 --- a/package/src/components/KeyboardCompatibleView/KeyboardCompatibleView.tsx +++ b/package/src/components/KeyboardCompatibleView/KeyboardCompatibleView.tsx @@ -22,6 +22,12 @@ import { import { KeyboardProvider } from '../../contexts/keyboardContext/KeyboardContext'; +export type KeyboardCompatibleViewProps = KeyboardAvoidingViewProps; + +export const dismissKeyboard = () => { + Keyboard.dismiss(); +}; + type State = { bottom: number; }; diff --git a/package/src/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.tsx b/package/src/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.tsx deleted file mode 100644 index fd31080d70..0000000000 --- a/package/src/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useEffect } from 'react'; - -import { - Keyboard, - KeyboardAvoidingViewProps as ReactNativeKeyboardAvoidingViewProps, -} from 'react-native'; - -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; - -const KeyboardControllerPackage = optionalRequire(); - -const { AndroidSoftInputModes, KeyboardController, KeyboardProvider, KeyboardAvoidingView } = - KeyboardControllerPackage ?? {}; - -export const KeyboardCompatibleView = (props: KeyboardCompatibleViewProps) => { - const { behavior = 'translate-with-padding', children, ...rest } = props; - - useEffect(() => { - if (AndroidSoftInputModes) { - KeyboardController?.setInputMode(AndroidSoftInputModes.SOFT_INPUT_ADJUST_RESIZE); - } - - return () => KeyboardController?.setDefaultMode(); - }, []); - - if (KeyboardProvider && KeyboardAvoidingView) { - return ( - - {/* @ts-expect-error - The reason is that react-native-keyboard-controller's KeyboardAvoidingViewProps is a discriminated union, not a simple behavior union so it complains about the `position` value passed. */} - - {children} - - - ); - } - const compatibleBehavior = behavior === 'translate-with-padding' ? 'padding' : behavior; - - return ( - - {children} - - ); -}; - -export const dismissKeyboard = () => { - if (KeyboardControllerPackage?.KeyboardController) { - KeyboardControllerPackage?.KeyboardController.dismiss(); - } - Keyboard.dismiss(); -}; - -export { KeyboardControllerPackage }; diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx index 316979be3f..57768e813e 100644 --- a/package/src/components/Message/Message.tsx +++ b/package/src/components/Message/Message.tsx @@ -77,7 +77,7 @@ import { MessageStatusTypes, } from '../../utils/utils'; import type { Thumbnail } from '../Attachment/utils/buildGallery/types'; -import { dismissKeyboard } from '../KeyboardCompatibleView/KeyboardControllerAvoidingView'; +import { dismissKeyboard } from '../KeyboardCompatibleView/KeyboardCompatibleView'; import { BottomSheetModal } from '../UIComponents'; const createMessageOverlayId = (messageId?: string) => diff --git a/package/src/components/Message/MessageItemView/MessageContent.tsx b/package/src/components/Message/MessageItemView/MessageContent.tsx index 4f94ad2383..fc1f92bc18 100644 --- a/package/src/components/Message/MessageItemView/MessageContent.tsx +++ b/package/src/components/Message/MessageItemView/MessageContent.tsx @@ -656,7 +656,6 @@ const styles = StyleSheet.create({ containerInner: { borderTopLeftRadius: components.messageBubbleRadiusGroupBottom, borderTopRightRadius: components.messageBubbleRadiusGroupBottom, - overflow: 'hidden', }, contentBody: { flexShrink: 1, diff --git a/package/src/components/MessageList/MessageFlashList.tsx b/package/src/components/MessageList/MessageFlashList.tsx index 05a033b949..e9f8fb7bd7 100644 --- a/package/src/components/MessageList/MessageFlashList.tsx +++ b/package/src/components/MessageList/MessageFlashList.tsx @@ -132,6 +132,7 @@ type MessageFlashListPropsWithContext = Pick< | 'scrollToFirstUnreadThreshold' | 'setChannelUnreadState' | 'setTargetedMessage' + | 'hasPendingInitialTargetLoad' | 'targetedMessage' | 'threadList' | 'maximumMessageLimit' @@ -289,6 +290,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => setChannelUnreadState, setFlatListRef, setTargetedMessage, + hasPendingInitialTargetLoad, targetedMessage, thread, threadInstance, @@ -388,11 +390,15 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => useEffect(() => { if (autoscrollToRecent && flashListRef.current) { + if (hasPendingInitialTargetLoad?.()) { + return; + } + flashListRef.current.scrollToEnd({ animated: true, }); } - }, [autoscrollToRecent]); + }, [autoscrollToRecent, hasPendingInitialTargetLoad]); const maintainVisibleContentPosition = useMemo(() => { return { @@ -408,18 +414,6 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => } }, [disabled]); - const indexToScrollToRef = useRef(undefined); - - const initialIndexToScrollTo = useMemo(() => { - return targetedMessage - ? processedMessageList.findIndex((message) => message?.id === targetedMessage) - : -1; - }, [processedMessageList, targetedMessage]); - - useEffect(() => { - indexToScrollToRef.current = initialIndexToScrollTo; - }, [initialIndexToScrollTo]); - /** * Check if a messageId needs to be scrolled to after list loads, and scroll to it * Note: This effect fires on every list change with a small debounce so that scrolling isnt abrupted by an immediate rerender @@ -440,13 +434,29 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => scrollToDebounceTimeoutRef.current = setTimeout(() => { clearTimeout(scrollToDebounceTimeoutRef.current); - // now scroll to it - flashListRef.current?.scrollToIndex({ - animated: true, - index: indexOfParentInMessageList, - viewPosition: 0.5, + const scrollToIndex = async () => { + const list = flashListRef.current; + + if (!list) { + return false; + } + + await list.scrollToIndex({ + index: indexOfParentInMessageList, + animated: true, + viewPosition: 0.5, + }); + + return true; + }; + + requestAnimationFrame(async () => { + await scrollToIndex(); + requestAnimationFrame(async () => { + await scrollToIndex(); + setTargetedMessage(undefined); + }); }); - setTargetedMessage(undefined); }, WAIT_FOR_SCROLL_TIMEOUT); } }, [loadChannelAroundMessage, processedMessageList, setTargetedMessage, targetedMessage]); @@ -456,8 +466,6 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => (message) => message?.id === messageId, ); - indexToScrollToRef.current = indexOfParentInMessageList; - try { if (indexOfParentInMessageList === -1) { clearTimeout(scrollToDebounceTimeoutRef.current); @@ -529,7 +537,6 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => setScrollToBottomButtonVisible(true); return; } else { - indexToScrollToRef.current = undefined; setAutoscrollToRecent(true); } const latestNonCurrentMessageBeforeUpdate = latestNonCurrentMessageBeforeUpdateRef.current; @@ -1064,9 +1071,6 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => data={processedMessageList} drawDistance={800} getItemType={getItemTypeInternal} - initialScrollIndex={ - indexToScrollToRef.current === -1 ? undefined : indexToScrollToRef.current - } keyboardShouldPersistTaps='handled' keyExtractor={keyExtractor} ListFooterComponent={ListFooterComponent} @@ -1203,6 +1207,7 @@ export const MessageFlashList = (props: MessageFlashListProps) => { scrollToFirstUnreadThreshold, setChannelUnreadState, setTargetedMessage, + hasPendingInitialTargetLoad, targetedMessage, threadList, } = useChannelContext(); @@ -1246,6 +1251,7 @@ export const MessageFlashList = (props: MessageFlashListProps) => { scrollToFirstUnreadThreshold, setChannelUnreadState, setTargetedMessage, + hasPendingInitialTargetLoad, shouldShowUnreadUnderlay, targetedMessage, thread, 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 a40eb0ce5e..f41411a83b 100644 --- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap +++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.tsx.snap @@ -506,7 +506,6 @@ exports[`Thread should match thread snapshot 1`] = ` { "borderTopLeftRadius": 20, "borderTopRightRadius": 20, - "overflow": "hidden", }, { "backgroundColor": "#ebeef1", @@ -849,7 +848,6 @@ exports[`Thread should match thread snapshot 1`] = ` { "borderTopLeftRadius": 20, "borderTopRightRadius": 20, - "overflow": "hidden", }, { "backgroundColor": "#ebeef1", @@ -1225,7 +1223,6 @@ exports[`Thread should match thread snapshot 1`] = ` { "borderTopLeftRadius": 20, "borderTopRightRadius": 20, - "overflow": "hidden", }, { "backgroundColor": "#ebeef1", @@ -1559,7 +1556,6 @@ exports[`Thread should match thread snapshot 1`] = ` { "borderTopLeftRadius": 20, "borderTopRightRadius": 20, - "overflow": "hidden", }, { "backgroundColor": "#ebeef1", diff --git a/package/src/components/UIComponents/BottomSheetModal.tsx b/package/src/components/UIComponents/BottomSheetModal.tsx index 843aec89b8..337f4d3f2a 100644 --- a/package/src/components/UIComponents/BottomSheetModal.tsx +++ b/package/src/components/UIComponents/BottomSheetModal.tsx @@ -19,7 +19,6 @@ import { View, } from 'react-native'; import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'; -import type { KeyboardEventData } from 'react-native-keyboard-controller'; import Animated, { Easing, FadeIn, @@ -43,7 +42,6 @@ import { BottomSheetProvider } from '../../contexts/bottomSheetContext/BottomShe import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useStableCallback } from '../../hooks'; import { primitives } from '../../theme'; -import { KeyboardControllerPackage } from '../KeyboardCompatibleView/KeyboardControllerAvoidingView'; export type BottomSheetModalProps = { /** @@ -377,18 +375,11 @@ const BottomSheetModalInner = (props: PropsWithChildren) const listeners: EventSubscription[] = []; - if (KeyboardControllerPackage?.KeyboardEvents) { - const keyboardDidShowKC = (event: KeyboardEventData) => { - animateKeyboardOffset(event.height); - }; - + if (Platform.OS === 'ios') { listeners.push( - KeyboardControllerPackage.KeyboardEvents.addListener('keyboardDidShow', keyboardDidShowKC), - KeyboardControllerPackage.KeyboardEvents.addListener('keyboardDidHide', keyboardDidHide), + Keyboard.addListener('keyboardWillShow', keyboardDidShowRN), + Keyboard.addListener('keyboardWillHide', keyboardDidHide), ); - } else if (Platform.OS === 'ios') { - listeners.push(Keyboard.addListener('keyboardWillShow', keyboardDidShowRN)); - listeners.push(Keyboard.addListener('keyboardWillHide', keyboardDidHide)); } return () => listeners.forEach((l) => l.remove()); diff --git a/package/src/contexts/channelContext/ChannelContext.tsx b/package/src/contexts/channelContext/ChannelContext.tsx index 6167626f5d..a14119ac0e 100644 --- a/package/src/contexts/channelContext/ChannelContext.tsx +++ b/package/src/contexts/channelContext/ChannelContext.tsx @@ -112,6 +112,12 @@ export type ChannelContextValue = { setChannelUnreadState: (data: ChannelUnreadStateStoreType['channelUnreadState']) => void; setLastRead: React.Dispatch>; setTargetedMessage: (messageId?: string) => void; + /** + * Returns true when Channel is about to load an initial targeted message. + * + * @internal + */ + hasPendingInitialTargetLoad?: () => boolean; /** * Abort controller for cancelling async requests made for uploading images/files * Its a map of filename and AbortController diff --git a/package/src/contexts/componentsContext/defaultComponents.ts b/package/src/contexts/componentsContext/defaultComponents.ts index 6735fba586..4aac65dee5 100644 --- a/package/src/contexts/componentsContext/defaultComponents.ts +++ b/package/src/contexts/componentsContext/defaultComponents.ts @@ -50,7 +50,7 @@ import { ImageGalleryGrid } from '../../components/ImageGallery/components/Image import { EmptyStateIndicator } from '../../components/Indicators/EmptyStateIndicator'; import { LoadingErrorIndicator } from '../../components/Indicators/LoadingErrorIndicator'; import { LoadingIndicator } from '../../components/Indicators/LoadingIndicator'; -import { KeyboardCompatibleView } from '../../components/KeyboardCompatibleView/KeyboardControllerAvoidingView'; +import { KeyboardCompatibleView } from '../../components/KeyboardCompatibleView/KeyboardCompatibleView'; import { Message } from '../../components/Message/Message'; import { MessagePinnedHeader } from '../../components/Message/MessageItemView/Headers/MessagePinnedHeader'; import { MessageReminderHeader } from '../../components/Message/MessageItemView/Headers/MessageReminderHeader'; diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index af240756a0..dd6ad459ac 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -23,7 +23,7 @@ import { import { useCreateMessageInputContext } from './hooks/useCreateMessageInputContext'; import { useMessageComposer } from './hooks/useMessageComposer'; -import { dismissKeyboard } from '../../components/KeyboardCompatibleView/KeyboardControllerAvoidingView'; +import { dismissKeyboard } from '../../components/KeyboardCompatibleView/KeyboardCompatibleView'; import { parseLinksFromText } from '../../components/Message/MessageItemView/utils/parseLinks'; import { useAudioRecorder } from '../../components/MessageInput/hooks/useAudioRecorder'; import { useNotificationApi } from '../../components/Notifications'; diff --git a/package/src/hooks/useAfterKeyboardOpenCallback.ts b/package/src/hooks/useAfterKeyboardOpenCallback.ts index 199bd92cc4..9ac6a78a8e 100644 --- a/package/src/hooks/useAfterKeyboardOpenCallback.ts +++ b/package/src/hooks/useAfterKeyboardOpenCallback.ts @@ -3,7 +3,6 @@ import { EventSubscription, Keyboard, Platform } from 'react-native'; import { useStableCallback } from './useStableCallback'; -import { KeyboardControllerPackage } from '../components/KeyboardCompatibleView/KeyboardControllerAvoidingView'; import { useMessageInputContext } from '../contexts/messageInputContext/MessageInputContext'; /** @@ -50,9 +49,7 @@ export const useAfterKeyboardOpenCallback = ( const keyboardEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'; - keyboardSubscriptionRef.current = KeyboardControllerPackage?.KeyboardEvents - ? KeyboardControllerPackage.KeyboardEvents.addListener(keyboardEvent, runCallback) - : Keyboard.addListener(keyboardEvent, runCallback); + keyboardSubscriptionRef.current = Keyboard.addListener(keyboardEvent, runCallback); inputBoxRef.current.focus(); }); diff --git a/package/src/hooks/useKeyboardVisibility.ts b/package/src/hooks/useKeyboardVisibility.ts index cffd977b31..2218f6321a 100644 --- a/package/src/hooks/useKeyboardVisibility.ts +++ b/package/src/hooks/useKeyboardVisibility.ts @@ -1,7 +1,5 @@ import { useEffect, useState } from 'react'; -import { EventSubscription, Keyboard, Platform } from 'react-native'; - -import { KeyboardControllerPackage } from '../components/KeyboardCompatibleView/KeyboardControllerAvoidingView'; +import { Keyboard, Platform } from 'react-native'; /** * A custom hook that provides a boolean value indicating whether the keyboard is visible. @@ -11,30 +9,14 @@ export const useKeyboardVisibility = () => { const [isKeyboardVisible, setIsKeyboardVisible] = useState(false); useEffect(() => { - const listeners: EventSubscription[] = []; - if (KeyboardControllerPackage?.KeyboardEvents) { - listeners.push( - KeyboardControllerPackage.KeyboardEvents.addListener('keyboardWillShow', () => - setIsKeyboardVisible(true), - ), - ); - listeners.push( - KeyboardControllerPackage.KeyboardEvents.addListener('keyboardWillHide', () => - setIsKeyboardVisible(false), - ), - ); - } else { - listeners.push( - Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', () => - setIsKeyboardVisible(true), - ), - ); - listeners.push( - Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', () => - setIsKeyboardVisible(false), - ), - ); - } + const listeners = [ + Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', () => + setIsKeyboardVisible(true), + ), + Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', () => + setIsKeyboardVisible(false), + ), + ]; return () => listeners.forEach((listener) => listener.remove()); }, []); diff --git a/package/src/state-store/audio-player.ts b/package/src/state-store/audio-player.ts index 8eb2a4394b..bd0a021937 100644 --- a/package/src/state-store/audio-player.ts +++ b/package/src/state-store/audio-player.ts @@ -425,17 +425,10 @@ export class AudioPlayer { } this.position = positionInMillis; if (this.isExpoCLI) { - if (positionInMillis === 0) { - // If currentTime is 0, we should replay the video from 0th position. - if (this.playerRef?.replayAsync) { - await this.playerRef.replayAsync({}); - } + if (this.playerRef?.setPositionAsync) { + await this.playerRef.setPositionAsync(positionInMillis); } else { - if (this.playerRef?.setPositionAsync) { - await this.playerRef.setPositionAsync(positionInMillis); - } else { - this.notifyError('seek-not-supported'); - } + this.notifyError('seek-not-supported'); } } else { if (this.playerRef?.seek) {