From 683afc373d9659a9d327fda52231e6ba543039e1 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Mon, 6 Oct 2025 20:29:11 +0530 Subject: [PATCH 1/3] fix: handle react-native-safe-area-context optionally for RN version >=0.81 --- package/package.json | 4 +- .../components/ImageGalleryFooter.tsx | 18 +++- .../components/ImageGalleryHeader.tsx | 22 ++++- .../components/MessageInput/MessageInput.tsx | 15 ++- .../Poll/components/PollButtons.tsx | 95 +++++++++++++------ .../components/PollResults/PollResultItem.tsx | 35 ++++++- package/src/utils/getReactNativeVersion.ts | 11 +++ package/yarn.lock | 5 + 8 files changed, 161 insertions(+), 44 deletions(-) create mode 100644 package/src/utils/getReactNativeVersion.ts diff --git a/package/package.json b/package/package.json index 99ba8839f6..dfe68a4321 100644 --- a/package/package.json +++ b/package/package.json @@ -91,6 +91,7 @@ "react-native": ">=0.73.0", "react-native-gesture-handler": ">=2.18.0", "react-native-reanimated": ">=3.16.0", + "react-native-safe-area-context": ">=5.4.1", "react-native-svg": ">=15.8.0" }, "peerDependenciesMeta": { @@ -111,11 +112,11 @@ "@babel/core": "^7.27.4", "@babel/runtime": "^7.27.6", "@op-engineering/op-sqlite": "^14.0.3", - "@shopify/flash-list": "^2.0.3", "@react-native-community/eslint-config": "3.2.0", "@react-native-community/eslint-plugin": "1.3.0", "@react-native-community/netinfo": "^11.4.1", "@react-native/babel-preset": "0.79.3", + "@shopify/flash-list": "^2.0.3", "@testing-library/jest-native": "^5.4.3", "@testing-library/react-native": "13.2.0", "@types/better-sqlite3": "^7.6.13", @@ -153,6 +154,7 @@ "react-native-builder-bob": "0.40.11", "react-native-gesture-handler": "^2.26.0", "react-native-reanimated": "3.18.0", + "react-native-safe-area-context": "^5.6.1", "react-native-svg": "15.12.0", "react-test-renderer": "19.1.0", "rimraf": "^6.0.1", diff --git a/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx b/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx index 305a9f1a0b..81743a347d 100644 --- a/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx +++ b/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx @@ -1,7 +1,7 @@ import React, { useRef, useState } from 'react'; import { ActivityIndicator, - SafeAreaView, + SafeAreaView as RNSafeAreaView, StyleSheet, Text, TouchableOpacity, @@ -15,6 +15,8 @@ import Animated, { useAnimatedStyle, } from 'react-native-reanimated'; +import { SafeAreaView as SafeAreaViewOriginal } from 'react-native-safe-area-context'; + import { ImageGalleryVideoControl } from './ImageGalleryVideoControl'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; @@ -27,13 +29,16 @@ import { VideoType, } from '../../../native'; +import { FileTypes } from '../../../types/types'; +import { getReactNativeVersion } from '../../../utils/getReactNativeVersion'; +import type { Photo } from '../ImageGallery'; + +const SafeAreaView = getReactNativeVersion().minor >= 81 ? SafeAreaViewOriginal : RNSafeAreaView; + const ReanimatedSafeAreaView = Animated.createAnimatedComponent ? Animated.createAnimatedComponent(SafeAreaView) : SafeAreaView; -import { FileTypes } from '../../../types/types'; -import type { Photo } from '../ImageGallery'; - export type ImageGalleryFooterCustomComponent = ({ openGridView, photo, @@ -179,7 +184,10 @@ export const ImageGalleryFooterWithContext = (props: ImageGalleryFooterPropsWith pointerEvents={'box-none'} style={styles.wrapper} > - + {photo.type === FileTypes.Video ? ( videoControlElement ? ( videoControlElement({ duration, onPlayPause, paused, progress, videoRef }) diff --git a/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx b/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx index 972e08b4d1..a43422c297 100644 --- a/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx +++ b/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx @@ -1,5 +1,14 @@ import React, { useMemo, useState } from 'react'; -import { Pressable, SafeAreaView, StyleSheet, Text, View, ViewStyle } from 'react-native'; + +import { + Pressable, + SafeAreaView as RNSafeAreaView, + StyleSheet, + Text, + View, + ViewStyle, +} from 'react-native'; + import Animated, { Extrapolation, interpolate, @@ -7,14 +16,20 @@ import Animated, { useAnimatedStyle, } from 'react-native-reanimated'; +import { SafeAreaView as SafeAreaViewOriginal } from 'react-native-safe-area-context'; + import { useOverlayContext } from '../../../contexts/overlayContext/OverlayContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { Close } from '../../../icons'; +import { getReactNativeVersion } from '../../../utils/getReactNativeVersion'; import { getDateString } from '../../../utils/i18n/getDateString'; import type { Photo } from '../ImageGallery'; +// This is a workaround to support SafeAreaView on React Native 0.81.0+ +const SafeAreaView = getReactNativeVersion().minor >= 81 ? SafeAreaViewOriginal : RNSafeAreaView; + const ReanimatedSafeAreaView = Animated.createAnimatedComponent ? Animated.createAnimatedComponent(SafeAreaView) : SafeAreaView; @@ -92,7 +107,10 @@ export const ImageGalleryHeader = (props: Props) => { onLayout={(event) => setHeight(event.nativeEvent.layout.height)} pointerEvents={'box-none'} > - + {leftElement ? ( leftElement({ hideOverlay, photo }) diff --git a/package/src/components/MessageInput/MessageInput.tsx b/package/src/components/MessageInput/MessageInput.tsx index 87dc72c642..f3d86ed3a7 100644 --- a/package/src/components/MessageInput/MessageInput.tsx +++ b/package/src/components/MessageInput/MessageInput.tsx @@ -1,5 +1,12 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Modal, SafeAreaView, StyleSheet, TextInput, TextInputProps, View } from 'react-native'; +import { + Modal, + SafeAreaView as RNSafeAreaView, + StyleSheet, + TextInput, + TextInputProps, + View, +} from 'react-native'; import { Gesture, @@ -16,6 +23,8 @@ import Animated, { withSpring, } from 'react-native-reanimated'; +import { SafeAreaView as SafeAreaViewOriginal } from 'react-native-safe-area-context'; + import { type MessageComposerState, type TextComposerState, type UserResponse } from 'stream-chat'; import { useAudioController } from './hooks/useAudioController'; @@ -58,10 +67,14 @@ import { isImageMediaLibraryAvailable, NativeHandlers, } from '../../native'; +import { getReactNativeVersion } from '../../utils/getReactNativeVersion'; import { AIStates, useAIState } from '../AITypingIndicatorView'; import { AutoCompleteInput } from '../AutoCompleteInput/AutoCompleteInput'; import { CreatePoll } from '../Poll/CreatePollContent'; +// This is a workaround to support SafeAreaView on React Native 0.81.0+ +const SafeAreaView = getReactNativeVersion().minor >= 81 ? SafeAreaViewOriginal : RNSafeAreaView; + const styles = StyleSheet.create({ attachmentSeparator: { borderBottomWidth: 1, diff --git a/package/src/components/Poll/components/PollButtons.tsx b/package/src/components/Poll/components/PollButtons.tsx index b88beada1a..aea07903aa 100644 --- a/package/src/components/Poll/components/PollButtons.tsx +++ b/package/src/components/Poll/components/PollButtons.tsx @@ -1,5 +1,9 @@ -import React, { useCallback, useState } from 'react'; -import { Modal, SafeAreaView } from 'react-native'; +import React, { PropsWithChildren, useCallback, useState } from 'react'; +import { Modal, SafeAreaView as RNSafeAreaView, ViewStyle } from 'react-native'; +import { + SafeAreaProvider, + SafeAreaView as SafeAreaViewOriginal, +} from 'react-native-safe-area-context'; import { GenericPollButton, PollButtonProps } from './Button'; import { PollAnswersList } from './PollAnswersList'; @@ -9,8 +13,23 @@ import { PollAllOptions } from './PollOption'; import { PollResults } from './PollResults'; import { useChatContext, usePollContext, useTheme, useTranslationContext } from '../../../contexts'; +import { getReactNativeVersion } from '../../../utils/getReactNativeVersion'; import { usePollState } from '../hooks/usePollState'; +// This is a workaround to support SafeAreaView on React Native 0.81.0+ +const SafeAreaViewWrapper = ({ children, style }: PropsWithChildren<{ style: ViewStyle }>) => { + if (getReactNativeVersion().minor >= 81) { + return ( + + + {children} + + + ); + } + return {children}; +}; + export const ViewResultsButton = (props: PollButtonProps) => { const { t } = useTranslationContext(); const { message, poll } = usePollContext(); @@ -32,19 +51,21 @@ export const ViewResultsButton = (props: PollButtonProps) => { }, } = useTheme(); + const onRequestClose = useCallback(() => { + setShowResults(false); + }, []); + return ( <> {showResults ? ( - setShowResults(false)} - visible={showResults} - > - - setShowResults(false)} title={t('Poll Results')} /> - - + + + + + + + ) : null} @@ -67,6 +88,10 @@ export const ShowAllOptionsButton = (props: PollButtonProps) => { setShowAllOptions(true); }, [message, onPress, poll]); + const onRequestClose = useCallback(() => { + setShowAllOptions(false); + }, []); + const { theme: { colors: { white }, @@ -82,15 +107,13 @@ export const ShowAllOptionsButton = (props: PollButtonProps) => { /> ) : null} {showAllOptions ? ( - setShowAllOptions(false)} - visible={showAllOptions} - > - - setShowAllOptions(false)} title={t('Poll Options')} /> - - + + + + + + + ) : null} @@ -119,6 +142,10 @@ export const ShowAllCommentsButton = (props: PollButtonProps) => { }, } = useTheme(); + const onRequestClose = useCallback(() => { + setShowAnswers(false); + }, []); + return ( <> {answersCount && answersCount > 0 ? ( @@ -128,15 +155,13 @@ export const ShowAllCommentsButton = (props: PollButtonProps) => { /> ) : null} {showAnswers ? ( - setShowAnswers(false)} - visible={showAnswers} - > - - setShowAnswers(false)} title={t('Poll Comments')} /> - - + + + + + + + ) : null} @@ -159,6 +184,10 @@ export const SuggestOptionButton = (props: PollButtonProps) => { setShowAddOptionDialog(true); }, [message, onPress, poll]); + const onRequestClose = useCallback(() => { + setShowAddOptionDialog(false); + }, []); + return ( <> {!isClosed && allowUserSuggestedOptions ? ( @@ -166,7 +195,7 @@ export const SuggestOptionButton = (props: PollButtonProps) => { ) : null} {showAddOptionDialog ? ( setShowAddOptionDialog(false)} + closeDialog={onRequestClose} onSubmit={addOption} title={t('Suggest an option')} visible={showAddOptionDialog} @@ -192,6 +221,10 @@ export const AddCommentButton = (props: PollButtonProps) => { setShowAddCommentDialog(true); }, [message, onPress, poll]); + const onRequestClose = useCallback(() => { + setShowAddCommentDialog(false); + }, []); + return ( <> {!isClosed && allowAnswers ? ( @@ -199,7 +232,7 @@ export const AddCommentButton = (props: PollButtonProps) => { ) : null} {showAddCommentDialog ? ( setShowAddCommentDialog(false)} + closeDialog={onRequestClose} initialValue={ownAnswer?.answer_text ?? ''} onSubmit={addComment} title={t('Add a comment')} diff --git a/package/src/components/Poll/components/PollResults/PollResultItem.tsx b/package/src/components/Poll/components/PollResults/PollResultItem.tsx index 558680a77c..16d5a52811 100644 --- a/package/src/components/Poll/components/PollResults/PollResultItem.tsx +++ b/package/src/components/Poll/components/PollResults/PollResultItem.tsx @@ -1,5 +1,17 @@ -import React, { useCallback, useState } from 'react'; -import { Modal, SafeAreaView, StyleSheet, Text, View } from 'react-native'; +import React, { PropsWithChildren, useCallback, useState } from 'react'; +import { + Modal, + SafeAreaView as RNSafeAreaView, + StyleSheet, + Text, + View, + ViewStyle, +} from 'react-native'; + +import { + SafeAreaProvider, + SafeAreaView as SafeAreaViewOriginal, +} from 'react-native-safe-area-context'; import { LocalMessage, Poll, PollOption, PollVote as PollVoteClass } from 'stream-chat'; @@ -13,10 +25,25 @@ import { useTranslationContext, } from '../../../../contexts'; +import { getReactNativeVersion } from '../../../../utils/getReactNativeVersion'; import { usePollState } from '../../hooks/usePollState'; import { GenericPollButton } from '../Button'; import { PollModalHeader } from '../PollModalHeader'; +// This is a workaround to support SafeAreaView on React Native 0.81.0+ +const SafeAreaViewWrapper = ({ children, style }: PropsWithChildren<{ style: ViewStyle }>) => { + if (getReactNativeVersion().minor >= 81) { + return ( + + + {children} + + + ); + } + return {children}; +}; + export type ShowAllVotesButtonProps = { option: PollOption; onPress?: ({ @@ -66,10 +93,10 @@ export const ShowAllVotesButton = (props: ShowAllVotesButtonProps) => { onRequestClose={() => setShowAllVotes(false)} visible={showAllVotes} > - + setShowAllVotes(false)} title={option.text} /> - + ) : null} diff --git a/package/src/utils/getReactNativeVersion.ts b/package/src/utils/getReactNativeVersion.ts new file mode 100644 index 0000000000..698c149f99 --- /dev/null +++ b/package/src/utils/getReactNativeVersion.ts @@ -0,0 +1,11 @@ +import ReactNativePackageJSON from 'react-native/package.json'; + +export const getReactNativeVersion = () => { + const version = ReactNativePackageJSON.version; + const [major, minor, patch] = version.split('.'); + return { + major: Number(major), + minor: Number(minor), + patch: Number(patch), + }; +}; diff --git a/package/yarn.lock b/package/yarn.lock index b2258ec83a..f905a5a148 100644 --- a/package/yarn.lock +++ b/package/yarn.lock @@ -7756,6 +7756,11 @@ react-native-reanimated@3.18.0: invariant "^2.2.4" react-native-is-edge-to-edge "1.1.7" +react-native-safe-area-context@^5.6.1: + version "5.6.1" + resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz#cb4d249ef1a6f7e8fd0cfdfa9764838dffda26b6" + integrity sha512-/wJE58HLEAkATzhhX1xSr+fostLsK8Q97EfpfMDKo8jlOc1QKESSX/FQrhk7HhQH/2uSaox4Y86sNaI02kteiA== + react-native-svg@15.12.0: version "15.12.0" resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-15.12.0.tgz#0e2d476961e8b07f8c549fe4489c99b5130dc150" From fad96eb6518707b40d50f0471c0e2ac46c1dd27a Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Mon, 6 Oct 2025 21:11:16 +0530 Subject: [PATCH 2/3] fix: deep import issue --- package/src/utils/getReactNativeVersion.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/package/src/utils/getReactNativeVersion.ts b/package/src/utils/getReactNativeVersion.ts index 698c149f99..9f371a7dfc 100644 --- a/package/src/utils/getReactNativeVersion.ts +++ b/package/src/utils/getReactNativeVersion.ts @@ -1,11 +1,6 @@ -import ReactNativePackageJSON from 'react-native/package.json'; +import { Platform } from 'react-native'; export const getReactNativeVersion = () => { - const version = ReactNativePackageJSON.version; - const [major, minor, patch] = version.split('.'); - return { - major: Number(major), - minor: Number(minor), - patch: Number(patch), - }; + const version = Platform.constants.reactNativeVersion; + return version; }; From dcac990596ae051911fadb97e9c36c2301f20265 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 8 Oct 2025 17:51:33 +0530 Subject: [PATCH 3/3] fix: avoid check using version --- .../ImageGallery/components/ImageGalleryFooter.tsx | 3 +-- .../ImageGallery/components/ImageGalleryHeader.tsx | 3 +-- package/src/components/MessageInput/MessageInput.tsx | 3 +-- package/src/components/Poll/components/PollButtons.tsx | 3 +-- .../Poll/components/PollResults/PollResultItem.tsx | 3 +-- package/src/utils/getReactNativeVersion.ts | 6 ------ 6 files changed, 5 insertions(+), 16 deletions(-) delete mode 100644 package/src/utils/getReactNativeVersion.ts diff --git a/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx b/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx index 81743a347d..15c5fbdf48 100644 --- a/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx +++ b/package/src/components/ImageGallery/components/ImageGalleryFooter.tsx @@ -30,10 +30,9 @@ import { } from '../../../native'; import { FileTypes } from '../../../types/types'; -import { getReactNativeVersion } from '../../../utils/getReactNativeVersion'; import type { Photo } from '../ImageGallery'; -const SafeAreaView = getReactNativeVersion().minor >= 81 ? SafeAreaViewOriginal : RNSafeAreaView; +const SafeAreaView = SafeAreaViewOriginal ?? RNSafeAreaView; const ReanimatedSafeAreaView = Animated.createAnimatedComponent ? Animated.createAnimatedComponent(SafeAreaView) diff --git a/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx b/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx index a43422c297..bce630614c 100644 --- a/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx +++ b/package/src/components/ImageGallery/components/ImageGalleryHeader.tsx @@ -23,12 +23,11 @@ import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { Close } from '../../../icons'; -import { getReactNativeVersion } from '../../../utils/getReactNativeVersion'; import { getDateString } from '../../../utils/i18n/getDateString'; import type { Photo } from '../ImageGallery'; // This is a workaround to support SafeAreaView on React Native 0.81.0+ -const SafeAreaView = getReactNativeVersion().minor >= 81 ? SafeAreaViewOriginal : RNSafeAreaView; +const SafeAreaView = SafeAreaViewOriginal ?? RNSafeAreaView; const ReanimatedSafeAreaView = Animated.createAnimatedComponent ? Animated.createAnimatedComponent(SafeAreaView) diff --git a/package/src/components/MessageInput/MessageInput.tsx b/package/src/components/MessageInput/MessageInput.tsx index f3d86ed3a7..2df5981793 100644 --- a/package/src/components/MessageInput/MessageInput.tsx +++ b/package/src/components/MessageInput/MessageInput.tsx @@ -67,13 +67,12 @@ import { isImageMediaLibraryAvailable, NativeHandlers, } from '../../native'; -import { getReactNativeVersion } from '../../utils/getReactNativeVersion'; import { AIStates, useAIState } from '../AITypingIndicatorView'; import { AutoCompleteInput } from '../AutoCompleteInput/AutoCompleteInput'; import { CreatePoll } from '../Poll/CreatePollContent'; // This is a workaround to support SafeAreaView on React Native 0.81.0+ -const SafeAreaView = getReactNativeVersion().minor >= 81 ? SafeAreaViewOriginal : RNSafeAreaView; +const SafeAreaView = SafeAreaViewOriginal ?? RNSafeAreaView; const styles = StyleSheet.create({ attachmentSeparator: { diff --git a/package/src/components/Poll/components/PollButtons.tsx b/package/src/components/Poll/components/PollButtons.tsx index aea07903aa..b2de5f083f 100644 --- a/package/src/components/Poll/components/PollButtons.tsx +++ b/package/src/components/Poll/components/PollButtons.tsx @@ -13,12 +13,11 @@ import { PollAllOptions } from './PollOption'; import { PollResults } from './PollResults'; import { useChatContext, usePollContext, useTheme, useTranslationContext } from '../../../contexts'; -import { getReactNativeVersion } from '../../../utils/getReactNativeVersion'; import { usePollState } from '../hooks/usePollState'; // This is a workaround to support SafeAreaView on React Native 0.81.0+ const SafeAreaViewWrapper = ({ children, style }: PropsWithChildren<{ style: ViewStyle }>) => { - if (getReactNativeVersion().minor >= 81) { + if (SafeAreaViewOriginal) { return ( diff --git a/package/src/components/Poll/components/PollResults/PollResultItem.tsx b/package/src/components/Poll/components/PollResults/PollResultItem.tsx index 16d5a52811..722878905d 100644 --- a/package/src/components/Poll/components/PollResults/PollResultItem.tsx +++ b/package/src/components/Poll/components/PollResults/PollResultItem.tsx @@ -25,14 +25,13 @@ import { useTranslationContext, } from '../../../../contexts'; -import { getReactNativeVersion } from '../../../../utils/getReactNativeVersion'; import { usePollState } from '../../hooks/usePollState'; import { GenericPollButton } from '../Button'; import { PollModalHeader } from '../PollModalHeader'; // This is a workaround to support SafeAreaView on React Native 0.81.0+ const SafeAreaViewWrapper = ({ children, style }: PropsWithChildren<{ style: ViewStyle }>) => { - if (getReactNativeVersion().minor >= 81) { + if (SafeAreaViewOriginal) { return ( diff --git a/package/src/utils/getReactNativeVersion.ts b/package/src/utils/getReactNativeVersion.ts deleted file mode 100644 index 9f371a7dfc..0000000000 --- a/package/src/utils/getReactNativeVersion.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Platform } from 'react-native'; - -export const getReactNativeVersion = () => { - const version = Platform.constants.reactNativeVersion; - return version; -};