Skip to content

Commit 08bf7f4

Browse files
authored
fix: handle react-native-safe-area-context optionally for RN version >=0.81 (#3239)
* fix: handle react-native-safe-area-context optionally for RN version >=0.81 * fix: deep import issue * fix: avoid check using version
1 parent 0093d7f commit 08bf7f4

File tree

7 files changed

+145
-44
lines changed

7 files changed

+145
-44
lines changed

package/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
"react-native": ">=0.73.0",
9292
"react-native-gesture-handler": ">=2.18.0",
9393
"react-native-reanimated": ">=3.16.0",
94+
"react-native-safe-area-context": ">=5.4.1",
9495
"react-native-svg": ">=15.8.0"
9596
},
9697
"peerDependenciesMeta": {
@@ -111,11 +112,11 @@
111112
"@babel/core": "^7.27.4",
112113
"@babel/runtime": "^7.27.6",
113114
"@op-engineering/op-sqlite": "^14.0.3",
114-
"@shopify/flash-list": "^2.0.3",
115115
"@react-native-community/eslint-config": "3.2.0",
116116
"@react-native-community/eslint-plugin": "1.3.0",
117117
"@react-native-community/netinfo": "^11.4.1",
118118
"@react-native/babel-preset": "0.79.3",
119+
"@shopify/flash-list": "^2.0.3",
119120
"@testing-library/jest-native": "^5.4.3",
120121
"@testing-library/react-native": "13.2.0",
121122
"@types/better-sqlite3": "^7.6.13",
@@ -153,6 +154,7 @@
153154
"react-native-builder-bob": "0.40.11",
154155
"react-native-gesture-handler": "^2.26.0",
155156
"react-native-reanimated": "3.18.0",
157+
"react-native-safe-area-context": "^5.6.1",
156158
"react-native-svg": "15.12.0",
157159
"react-test-renderer": "19.1.0",
158160
"rimraf": "^6.0.1",

package/src/components/ImageGallery/components/ImageGalleryFooter.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useRef, useState } from 'react';
22
import {
33
ActivityIndicator,
4-
SafeAreaView,
4+
SafeAreaView as RNSafeAreaView,
55
StyleSheet,
66
Text,
77
TouchableOpacity,
@@ -15,6 +15,8 @@ import Animated, {
1515
useAnimatedStyle,
1616
} from 'react-native-reanimated';
1717

18+
import { SafeAreaView as SafeAreaViewOriginal } from 'react-native-safe-area-context';
19+
1820
import { ImageGalleryVideoControl } from './ImageGalleryVideoControl';
1921

2022
import { useTheme } from '../../../contexts/themeContext/ThemeContext';
@@ -27,13 +29,15 @@ import {
2729
VideoType,
2830
} from '../../../native';
2931

32+
import { FileTypes } from '../../../types/types';
33+
import type { Photo } from '../ImageGallery';
34+
35+
const SafeAreaView = SafeAreaViewOriginal ?? RNSafeAreaView;
36+
3037
const ReanimatedSafeAreaView = Animated.createAnimatedComponent
3138
? Animated.createAnimatedComponent(SafeAreaView)
3239
: SafeAreaView;
3340

34-
import { FileTypes } from '../../../types/types';
35-
import type { Photo } from '../ImageGallery';
36-
3741
export type ImageGalleryFooterCustomComponent = ({
3842
openGridView,
3943
photo,
@@ -179,7 +183,10 @@ export const ImageGalleryFooterWithContext = (props: ImageGalleryFooterPropsWith
179183
pointerEvents={'box-none'}
180184
style={styles.wrapper}
181185
>
182-
<ReanimatedSafeAreaView style={[{ backgroundColor: white }, footerStyle, container]}>
186+
<ReanimatedSafeAreaView
187+
edges={['bottom']}
188+
style={[{ backgroundColor: white }, footerStyle, container]}
189+
>
183190
{photo.type === FileTypes.Video ? (
184191
videoControlElement ? (
185192
videoControlElement({ duration, onPlayPause, paused, progress, videoRef })

package/src/components/ImageGallery/components/ImageGalleryHeader.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
import React, { useMemo, useState } from 'react';
2-
import { Pressable, SafeAreaView, StyleSheet, Text, View, ViewStyle } from 'react-native';
2+
3+
import {
4+
Pressable,
5+
SafeAreaView as RNSafeAreaView,
6+
StyleSheet,
7+
Text,
8+
View,
9+
ViewStyle,
10+
} from 'react-native';
11+
312
import Animated, {
413
Extrapolation,
514
interpolate,
615
SharedValue,
716
useAnimatedStyle,
817
} from 'react-native-reanimated';
918

19+
import { SafeAreaView as SafeAreaViewOriginal } from 'react-native-safe-area-context';
20+
1021
import { useOverlayContext } from '../../../contexts/overlayContext/OverlayContext';
1122
import { useTheme } from '../../../contexts/themeContext/ThemeContext';
1223
import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext';
@@ -15,6 +26,9 @@ import { Close } from '../../../icons';
1526
import { getDateString } from '../../../utils/i18n/getDateString';
1627
import type { Photo } from '../ImageGallery';
1728

29+
// This is a workaround to support SafeAreaView on React Native 0.81.0+
30+
const SafeAreaView = SafeAreaViewOriginal ?? RNSafeAreaView;
31+
1832
const ReanimatedSafeAreaView = Animated.createAnimatedComponent
1933
? Animated.createAnimatedComponent(SafeAreaView)
2034
: SafeAreaView;
@@ -92,7 +106,10 @@ export const ImageGalleryHeader = (props: Props) => {
92106
onLayout={(event) => setHeight(event.nativeEvent.layout.height)}
93107
pointerEvents={'box-none'}
94108
>
95-
<ReanimatedSafeAreaView style={[{ backgroundColor: white }, headerStyle, container]}>
109+
<ReanimatedSafeAreaView
110+
edges={['top']}
111+
style={[{ backgroundColor: white }, headerStyle, container]}
112+
>
96113
<View style={[styles.innerContainer, innerContainer]}>
97114
{leftElement ? (
98115
leftElement({ hideOverlay, photo })

package/src/components/MessageInput/MessageInput.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import React, { useCallback, useEffect, useMemo, useState } from 'react';
2-
import { Modal, SafeAreaView, StyleSheet, TextInput, TextInputProps, View } from 'react-native';
2+
import {
3+
Modal,
4+
SafeAreaView as RNSafeAreaView,
5+
StyleSheet,
6+
TextInput,
7+
TextInputProps,
8+
View,
9+
} from 'react-native';
310

411
import {
512
Gesture,
@@ -16,6 +23,8 @@ import Animated, {
1623
withSpring,
1724
} from 'react-native-reanimated';
1825

26+
import { SafeAreaView as SafeAreaViewOriginal } from 'react-native-safe-area-context';
27+
1928
import { type MessageComposerState, type TextComposerState, type UserResponse } from 'stream-chat';
2029

2130
import { useAudioController } from './hooks/useAudioController';
@@ -62,6 +71,9 @@ import { AIStates, useAIState } from '../AITypingIndicatorView';
6271
import { AutoCompleteInput } from '../AutoCompleteInput/AutoCompleteInput';
6372
import { CreatePoll } from '../Poll/CreatePollContent';
6473

74+
// This is a workaround to support SafeAreaView on React Native 0.81.0+
75+
const SafeAreaView = SafeAreaViewOriginal ?? RNSafeAreaView;
76+
6577
const styles = StyleSheet.create({
6678
attachmentSeparator: {
6779
borderBottomWidth: 1,

package/src/components/Poll/components/PollButtons.tsx

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import React, { useCallback, useState } from 'react';
2-
import { Modal, SafeAreaView } from 'react-native';
1+
import React, { PropsWithChildren, useCallback, useState } from 'react';
2+
import { Modal, SafeAreaView as RNSafeAreaView, ViewStyle } from 'react-native';
3+
import {
4+
SafeAreaProvider,
5+
SafeAreaView as SafeAreaViewOriginal,
6+
} from 'react-native-safe-area-context';
37

48
import { GenericPollButton, PollButtonProps } from './Button';
59
import { PollAnswersList } from './PollAnswersList';
@@ -11,6 +15,20 @@ import { PollResults } from './PollResults';
1115
import { useChatContext, usePollContext, useTheme, useTranslationContext } from '../../../contexts';
1216
import { usePollState } from '../hooks/usePollState';
1317

18+
// This is a workaround to support SafeAreaView on React Native 0.81.0+
19+
const SafeAreaViewWrapper = ({ children, style }: PropsWithChildren<{ style: ViewStyle }>) => {
20+
if (SafeAreaViewOriginal) {
21+
return (
22+
<SafeAreaProvider>
23+
<SafeAreaViewOriginal edges={['bottom', 'top']} style={style}>
24+
{children}
25+
</SafeAreaViewOriginal>
26+
</SafeAreaProvider>
27+
);
28+
}
29+
return <RNSafeAreaView style={style}>{children}</RNSafeAreaView>;
30+
};
31+
1432
export const ViewResultsButton = (props: PollButtonProps) => {
1533
const { t } = useTranslationContext();
1634
const { message, poll } = usePollContext();
@@ -32,19 +50,21 @@ export const ViewResultsButton = (props: PollButtonProps) => {
3250
},
3351
} = useTheme();
3452

53+
const onRequestClose = useCallback(() => {
54+
setShowResults(false);
55+
}, []);
56+
3557
return (
3658
<>
3759
<GenericPollButton onPress={onPressHandler} title={t('View Results')} />
3860
{showResults ? (
39-
<Modal
40-
animationType='slide'
41-
onRequestClose={() => setShowResults(false)}
42-
visible={showResults}
43-
>
44-
<SafeAreaView style={{ backgroundColor: white, flex: 1 }}>
45-
<PollModalHeader onPress={() => setShowResults(false)} title={t('Poll Results')} />
46-
<PollResults message={message} poll={poll} />
47-
</SafeAreaView>
61+
<Modal animationType='slide' onRequestClose={onRequestClose} visible={showResults}>
62+
<SafeAreaProvider>
63+
<SafeAreaViewWrapper style={{ backgroundColor: white, flex: 1 }}>
64+
<PollModalHeader onPress={onRequestClose} title={t('Poll Results')} />
65+
<PollResults message={message} poll={poll} />
66+
</SafeAreaViewWrapper>
67+
</SafeAreaProvider>
4868
</Modal>
4969
) : null}
5070
</>
@@ -67,6 +87,10 @@ export const ShowAllOptionsButton = (props: PollButtonProps) => {
6787
setShowAllOptions(true);
6888
}, [message, onPress, poll]);
6989

90+
const onRequestClose = useCallback(() => {
91+
setShowAllOptions(false);
92+
}, []);
93+
7094
const {
7195
theme: {
7296
colors: { white },
@@ -82,15 +106,13 @@ export const ShowAllOptionsButton = (props: PollButtonProps) => {
82106
/>
83107
) : null}
84108
{showAllOptions ? (
85-
<Modal
86-
animationType='slide'
87-
onRequestClose={() => setShowAllOptions(false)}
88-
visible={showAllOptions}
89-
>
90-
<SafeAreaView style={{ backgroundColor: white, flex: 1 }}>
91-
<PollModalHeader onPress={() => setShowAllOptions(false)} title={t('Poll Options')} />
92-
<PollAllOptions message={message} poll={poll} />
93-
</SafeAreaView>
109+
<Modal animationType='slide' onRequestClose={onRequestClose} visible={showAllOptions}>
110+
<SafeAreaProvider>
111+
<SafeAreaViewWrapper style={{ backgroundColor: white, flex: 1 }}>
112+
<PollModalHeader onPress={onRequestClose} title={t('Poll Options')} />
113+
<PollAllOptions message={message} poll={poll} />
114+
</SafeAreaViewWrapper>
115+
</SafeAreaProvider>
94116
</Modal>
95117
) : null}
96118
</>
@@ -119,6 +141,10 @@ export const ShowAllCommentsButton = (props: PollButtonProps) => {
119141
},
120142
} = useTheme();
121143

144+
const onRequestClose = useCallback(() => {
145+
setShowAnswers(false);
146+
}, []);
147+
122148
return (
123149
<>
124150
{answersCount && answersCount > 0 ? (
@@ -128,15 +154,13 @@ export const ShowAllCommentsButton = (props: PollButtonProps) => {
128154
/>
129155
) : null}
130156
{showAnswers ? (
131-
<Modal
132-
animationType='slide'
133-
onRequestClose={() => setShowAnswers(false)}
134-
visible={showAnswers}
135-
>
136-
<SafeAreaView style={{ backgroundColor: white, flex: 1 }}>
137-
<PollModalHeader onPress={() => setShowAnswers(false)} title={t('Poll Comments')} />
138-
<PollAnswersList message={message} poll={poll} />
139-
</SafeAreaView>
157+
<Modal animationType='slide' onRequestClose={onRequestClose} visible={showAnswers}>
158+
<SafeAreaProvider>
159+
<SafeAreaViewWrapper style={{ backgroundColor: white, flex: 1 }}>
160+
<PollModalHeader onPress={onRequestClose} title={t('Poll Comments')} />
161+
<PollAnswersList message={message} poll={poll} />
162+
</SafeAreaViewWrapper>
163+
</SafeAreaProvider>
140164
</Modal>
141165
) : null}
142166
</>
@@ -159,14 +183,18 @@ export const SuggestOptionButton = (props: PollButtonProps) => {
159183
setShowAddOptionDialog(true);
160184
}, [message, onPress, poll]);
161185

186+
const onRequestClose = useCallback(() => {
187+
setShowAddOptionDialog(false);
188+
}, []);
189+
162190
return (
163191
<>
164192
{!isClosed && allowUserSuggestedOptions ? (
165193
<GenericPollButton onPress={onPressHandler} title={t('Suggest an option')} />
166194
) : null}
167195
{showAddOptionDialog ? (
168196
<PollInputDialog
169-
closeDialog={() => setShowAddOptionDialog(false)}
197+
closeDialog={onRequestClose}
170198
onSubmit={addOption}
171199
title={t('Suggest an option')}
172200
visible={showAddOptionDialog}
@@ -192,14 +220,18 @@ export const AddCommentButton = (props: PollButtonProps) => {
192220
setShowAddCommentDialog(true);
193221
}, [message, onPress, poll]);
194222

223+
const onRequestClose = useCallback(() => {
224+
setShowAddCommentDialog(false);
225+
}, []);
226+
195227
return (
196228
<>
197229
{!isClosed && allowAnswers ? (
198230
<GenericPollButton onPress={onPressHandler} title={t('Add a comment')} />
199231
) : null}
200232
{showAddCommentDialog ? (
201233
<PollInputDialog
202-
closeDialog={() => setShowAddCommentDialog(false)}
234+
closeDialog={onRequestClose}
203235
initialValue={ownAnswer?.answer_text ?? ''}
204236
onSubmit={addComment}
205237
title={t('Add a comment')}

package/src/components/Poll/components/PollResults/PollResultItem.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1-
import React, { useCallback, useState } from 'react';
2-
import { Modal, SafeAreaView, StyleSheet, Text, View } from 'react-native';
1+
import React, { PropsWithChildren, useCallback, useState } from 'react';
2+
import {
3+
Modal,
4+
SafeAreaView as RNSafeAreaView,
5+
StyleSheet,
6+
Text,
7+
View,
8+
ViewStyle,
9+
} from 'react-native';
10+
11+
import {
12+
SafeAreaProvider,
13+
SafeAreaView as SafeAreaViewOriginal,
14+
} from 'react-native-safe-area-context';
315

416
import { LocalMessage, Poll, PollOption, PollVote as PollVoteClass } from 'stream-chat';
517

@@ -17,6 +29,20 @@ import { usePollState } from '../../hooks/usePollState';
1729
import { GenericPollButton } from '../Button';
1830
import { PollModalHeader } from '../PollModalHeader';
1931

32+
// This is a workaround to support SafeAreaView on React Native 0.81.0+
33+
const SafeAreaViewWrapper = ({ children, style }: PropsWithChildren<{ style: ViewStyle }>) => {
34+
if (SafeAreaViewOriginal) {
35+
return (
36+
<SafeAreaProvider>
37+
<SafeAreaViewOriginal edges={['bottom', 'top']} style={style}>
38+
{children}
39+
</SafeAreaViewOriginal>
40+
</SafeAreaProvider>
41+
);
42+
}
43+
return <RNSafeAreaView style={style}>{children}</RNSafeAreaView>;
44+
};
45+
2046
export type ShowAllVotesButtonProps = {
2147
option: PollOption;
2248
onPress?: ({
@@ -66,10 +92,10 @@ export const ShowAllVotesButton = (props: ShowAllVotesButtonProps) => {
6692
onRequestClose={() => setShowAllVotes(false)}
6793
visible={showAllVotes}
6894
>
69-
<SafeAreaView style={{ backgroundColor: white, flex: 1 }}>
95+
<SafeAreaViewWrapper style={{ backgroundColor: white, flex: 1 }}>
7096
<PollModalHeader onPress={() => setShowAllVotes(false)} title={option.text} />
7197
<PollOptionFullResults message={message} option={option} poll={poll} />
72-
</SafeAreaView>
98+
</SafeAreaViewWrapper>
7399
</Modal>
74100
) : null}
75101
</>

package/yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7756,6 +7756,11 @@ [email protected]:
77567756
invariant "^2.2.4"
77577757
react-native-is-edge-to-edge "1.1.7"
77587758

7759+
react-native-safe-area-context@^5.6.1:
7760+
version "5.6.1"
7761+
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz#cb4d249ef1a6f7e8fd0cfdfa9764838dffda26b6"
7762+
integrity sha512-/wJE58HLEAkATzhhX1xSr+fostLsK8Q97EfpfMDKo8jlOc1QKESSX/FQrhk7HhQH/2uSaox4Y86sNaI02kteiA==
7763+
77597764
77607765
version "15.12.0"
77617766
resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-15.12.0.tgz#0e2d476961e8b07f8c549fe4489c99b5130dc150"

0 commit comments

Comments
 (0)