Skip to content

Commit 683afc3

Browse files
committed
fix: handle react-native-safe-area-context optionally for RN version >=0.81
1 parent 1ba131b commit 683afc3

File tree

8 files changed

+161
-44
lines changed

8 files changed

+161
-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: 13 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,16 @@ import {
2729
VideoType,
2830
} from '../../../native';
2931

32+
import { FileTypes } from '../../../types/types';
33+
import { getReactNativeVersion } from '../../../utils/getReactNativeVersion';
34+
import type { Photo } from '../ImageGallery';
35+
36+
const SafeAreaView = getReactNativeVersion().minor >= 81 ? SafeAreaViewOriginal : RNSafeAreaView;
37+
3038
const ReanimatedSafeAreaView = Animated.createAnimatedComponent
3139
? Animated.createAnimatedComponent(SafeAreaView)
3240
: SafeAreaView;
3341

34-
import { FileTypes } from '../../../types/types';
35-
import type { Photo } from '../ImageGallery';
36-
3742
export type ImageGalleryFooterCustomComponent = ({
3843
openGridView,
3944
photo,
@@ -179,7 +184,10 @@ export const ImageGalleryFooterWithContext = (props: ImageGalleryFooterPropsWith
179184
pointerEvents={'box-none'}
180185
style={styles.wrapper}
181186
>
182-
<ReanimatedSafeAreaView style={[{ backgroundColor: white }, footerStyle, container]}>
187+
<ReanimatedSafeAreaView
188+
edges={['bottom']}
189+
style={[{ backgroundColor: white }, footerStyle, container]}
190+
>
183191
{photo.type === FileTypes.Video ? (
184192
videoControlElement ? (
185193
videoControlElement({ duration, onPlayPause, paused, progress, videoRef })

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
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';
1324
import { Close } from '../../../icons';
1425

26+
import { getReactNativeVersion } from '../../../utils/getReactNativeVersion';
1527
import { getDateString } from '../../../utils/i18n/getDateString';
1628
import type { Photo } from '../ImageGallery';
1729

30+
// This is a workaround to support SafeAreaView on React Native 0.81.0+
31+
const SafeAreaView = getReactNativeVersion().minor >= 81 ? SafeAreaViewOriginal : RNSafeAreaView;
32+
1833
const ReanimatedSafeAreaView = Animated.createAnimatedComponent
1934
? Animated.createAnimatedComponent(SafeAreaView)
2035
: SafeAreaView;
@@ -92,7 +107,10 @@ export const ImageGalleryHeader = (props: Props) => {
92107
onLayout={(event) => setHeight(event.nativeEvent.layout.height)}
93108
pointerEvents={'box-none'}
94109
>
95-
<ReanimatedSafeAreaView style={[{ backgroundColor: white }, headerStyle, container]}>
110+
<ReanimatedSafeAreaView
111+
edges={['top']}
112+
style={[{ backgroundColor: white }, headerStyle, container]}
113+
>
96114
<View style={[styles.innerContainer, innerContainer]}>
97115
{leftElement ? (
98116
leftElement({ hideOverlay, photo })

package/src/components/MessageInput/MessageInput.tsx

Lines changed: 14 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';
@@ -58,10 +67,14 @@ import {
5867
isImageMediaLibraryAvailable,
5968
NativeHandlers,
6069
} from '../../native';
70+
import { getReactNativeVersion } from '../../utils/getReactNativeVersion';
6171
import { AIStates, useAIState } from '../AITypingIndicatorView';
6272
import { AutoCompleteInput } from '../AutoCompleteInput/AutoCompleteInput';
6373
import { CreatePoll } from '../Poll/CreatePollContent';
6474

75+
// This is a workaround to support SafeAreaView on React Native 0.81.0+
76+
const SafeAreaView = getReactNativeVersion().minor >= 81 ? SafeAreaViewOriginal : RNSafeAreaView;
77+
6578
const styles = StyleSheet.create({
6679
attachmentSeparator: {
6780
borderBottomWidth: 1,

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

Lines changed: 64 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';
@@ -9,8 +13,23 @@ import { PollAllOptions } from './PollOption';
913
import { PollResults } from './PollResults';
1014

1115
import { useChatContext, usePollContext, useTheme, useTranslationContext } from '../../../contexts';
16+
import { getReactNativeVersion } from '../../../utils/getReactNativeVersion';
1217
import { usePollState } from '../hooks/usePollState';
1318

19+
// This is a workaround to support SafeAreaView on React Native 0.81.0+
20+
const SafeAreaViewWrapper = ({ children, style }: PropsWithChildren<{ style: ViewStyle }>) => {
21+
if (getReactNativeVersion().minor >= 81) {
22+
return (
23+
<SafeAreaProvider>
24+
<SafeAreaViewOriginal edges={['bottom', 'top']} style={style}>
25+
{children}
26+
</SafeAreaViewOriginal>
27+
</SafeAreaProvider>
28+
);
29+
}
30+
return <RNSafeAreaView style={style}>{children}</RNSafeAreaView>;
31+
};
32+
1433
export const ViewResultsButton = (props: PollButtonProps) => {
1534
const { t } = useTranslationContext();
1635
const { message, poll } = usePollContext();
@@ -32,19 +51,21 @@ export const ViewResultsButton = (props: PollButtonProps) => {
3251
},
3352
} = useTheme();
3453

54+
const onRequestClose = useCallback(() => {
55+
setShowResults(false);
56+
}, []);
57+
3558
return (
3659
<>
3760
<GenericPollButton onPress={onPressHandler} title={t('View Results')} />
3861
{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>
62+
<Modal animationType='slide' onRequestClose={onRequestClose} visible={showResults}>
63+
<SafeAreaProvider>
64+
<SafeAreaViewWrapper style={{ backgroundColor: white, flex: 1 }}>
65+
<PollModalHeader onPress={onRequestClose} title={t('Poll Results')} />
66+
<PollResults message={message} poll={poll} />
67+
</SafeAreaViewWrapper>
68+
</SafeAreaProvider>
4869
</Modal>
4970
) : null}
5071
</>
@@ -67,6 +88,10 @@ export const ShowAllOptionsButton = (props: PollButtonProps) => {
6788
setShowAllOptions(true);
6889
}, [message, onPress, poll]);
6990

91+
const onRequestClose = useCallback(() => {
92+
setShowAllOptions(false);
93+
}, []);
94+
7095
const {
7196
theme: {
7297
colors: { white },
@@ -82,15 +107,13 @@ export const ShowAllOptionsButton = (props: PollButtonProps) => {
82107
/>
83108
) : null}
84109
{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>
110+
<Modal animationType='slide' onRequestClose={onRequestClose} visible={showAllOptions}>
111+
<SafeAreaProvider>
112+
<SafeAreaViewWrapper style={{ backgroundColor: white, flex: 1 }}>
113+
<PollModalHeader onPress={onRequestClose} title={t('Poll Options')} />
114+
<PollAllOptions message={message} poll={poll} />
115+
</SafeAreaViewWrapper>
116+
</SafeAreaProvider>
94117
</Modal>
95118
) : null}
96119
</>
@@ -119,6 +142,10 @@ export const ShowAllCommentsButton = (props: PollButtonProps) => {
119142
},
120143
} = useTheme();
121144

145+
const onRequestClose = useCallback(() => {
146+
setShowAnswers(false);
147+
}, []);
148+
122149
return (
123150
<>
124151
{answersCount && answersCount > 0 ? (
@@ -128,15 +155,13 @@ export const ShowAllCommentsButton = (props: PollButtonProps) => {
128155
/>
129156
) : null}
130157
{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>
158+
<Modal animationType='slide' onRequestClose={onRequestClose} visible={showAnswers}>
159+
<SafeAreaProvider>
160+
<SafeAreaViewWrapper style={{ backgroundColor: white, flex: 1 }}>
161+
<PollModalHeader onPress={onRequestClose} title={t('Poll Comments')} />
162+
<PollAnswersList message={message} poll={poll} />
163+
</SafeAreaViewWrapper>
164+
</SafeAreaProvider>
140165
</Modal>
141166
) : null}
142167
</>
@@ -159,14 +184,18 @@ export const SuggestOptionButton = (props: PollButtonProps) => {
159184
setShowAddOptionDialog(true);
160185
}, [message, onPress, poll]);
161186

187+
const onRequestClose = useCallback(() => {
188+
setShowAddOptionDialog(false);
189+
}, []);
190+
162191
return (
163192
<>
164193
{!isClosed && allowUserSuggestedOptions ? (
165194
<GenericPollButton onPress={onPressHandler} title={t('Suggest an option')} />
166195
) : null}
167196
{showAddOptionDialog ? (
168197
<PollInputDialog
169-
closeDialog={() => setShowAddOptionDialog(false)}
198+
closeDialog={onRequestClose}
170199
onSubmit={addOption}
171200
title={t('Suggest an option')}
172201
visible={showAddOptionDialog}
@@ -192,14 +221,18 @@ export const AddCommentButton = (props: PollButtonProps) => {
192221
setShowAddCommentDialog(true);
193222
}, [message, onPress, poll]);
194223

224+
const onRequestClose = useCallback(() => {
225+
setShowAddCommentDialog(false);
226+
}, []);
227+
195228
return (
196229
<>
197230
{!isClosed && allowAnswers ? (
198231
<GenericPollButton onPress={onPressHandler} title={t('Add a comment')} />
199232
) : null}
200233
{showAddCommentDialog ? (
201234
<PollInputDialog
202-
closeDialog={() => setShowAddCommentDialog(false)}
235+
closeDialog={onRequestClose}
203236
initialValue={ownAnswer?.answer_text ?? ''}
204237
onSubmit={addComment}
205238
title={t('Add a comment')}

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

Lines changed: 31 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

@@ -13,10 +25,25 @@ import {
1325
useTranslationContext,
1426
} from '../../../../contexts';
1527

28+
import { getReactNativeVersion } from '../../../../utils/getReactNativeVersion';
1629
import { usePollState } from '../../hooks/usePollState';
1730
import { GenericPollButton } from '../Button';
1831
import { PollModalHeader } from '../PollModalHeader';
1932

33+
// This is a workaround to support SafeAreaView on React Native 0.81.0+
34+
const SafeAreaViewWrapper = ({ children, style }: PropsWithChildren<{ style: ViewStyle }>) => {
35+
if (getReactNativeVersion().minor >= 81) {
36+
return (
37+
<SafeAreaProvider>
38+
<SafeAreaViewOriginal edges={['bottom', 'top']} style={style}>
39+
{children}
40+
</SafeAreaViewOriginal>
41+
</SafeAreaProvider>
42+
);
43+
}
44+
return <RNSafeAreaView style={style}>{children}</RNSafeAreaView>;
45+
};
46+
2047
export type ShowAllVotesButtonProps = {
2148
option: PollOption;
2249
onPress?: ({
@@ -66,10 +93,10 @@ export const ShowAllVotesButton = (props: ShowAllVotesButtonProps) => {
6693
onRequestClose={() => setShowAllVotes(false)}
6794
visible={showAllVotes}
6895
>
69-
<SafeAreaView style={{ backgroundColor: white, flex: 1 }}>
96+
<SafeAreaViewWrapper style={{ backgroundColor: white, flex: 1 }}>
7097
<PollModalHeader onPress={() => setShowAllVotes(false)} title={option.text} />
7198
<PollOptionFullResults message={message} option={option} poll={poll} />
72-
</SafeAreaView>
99+
</SafeAreaViewWrapper>
73100
</Modal>
74101
) : null}
75102
</>

0 commit comments

Comments
 (0)