Merge pull request #2216 from ecency/sa/add-link-in-post

Insert URL Modal
This commit is contained in:
Feruz M 2022-03-28 21:52:18 +03:00 committed by GitHub
commit bb54274837
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 538 additions and 30 deletions

View File

@ -416,6 +416,8 @@ PODS:
- React-Core
- RNCAsyncStorage (1.12.1):
- React-Core
- RNCClipboard (1.8.5):
- React-Core
- RNCPushNotificationIOS (1.8.0):
- React-Core
- RNFastImage (8.3.4):
@ -529,6 +531,7 @@ DEPENDENCIES:
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- rn-fetch-blob (from `../node_modules/rn-fetch-blob`)
- "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
- "RNCPushNotificationIOS (from `../node_modules/@react-native-community/push-notification-ios`)"
- RNFastImage (from `../node_modules/react-native-fast-image`)
- "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)"
@ -674,6 +677,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/rn-fetch-blob"
RNCAsyncStorage:
:path: "../node_modules/@react-native-community/async-storage"
RNCClipboard:
:path: "../node_modules/@react-native-clipboard/clipboard"
RNCPushNotificationIOS:
:path: "../node_modules/@react-native-community/push-notification-ios"
RNFastImage:
@ -781,6 +786,7 @@ SPEC CHECKSUMS:
ReactCommon: 73d79c7039f473b76db6ff7c6b159c478acbbb3b
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
RNCAsyncStorage: b03032fdbdb725bea0bd9e5ec5a7272865ae7398
RNCClipboard: cc054ad1e8a33d2a74cd13e565588b4ca928d8fd
RNCPushNotificationIOS: 61a7c72bd1ebad3568025957d001e0f0e7b32191
RNFastImage: d4870d58f5936111c56218dbd7fcfc18e65b58ff
RNFBAnalytics: 6414e9fe1f36c3074f39cd6265b3def777dbfbdb

View File

@ -37,6 +37,7 @@
"@hiveio/dhive": "^1.0.1",
"@native-html/iframe-plugin": "^2.6.1",
"@native-html/table-plugin": "^5.3.1",
"@react-native-clipboard/clipboard": "^1.8.5",
"@react-native-community/async-storage": "^1.11.0",
"@react-native-community/cameraroll": "^1.3.0",
"@react-native-community/cli-platform-ios": "^4.10.1",
@ -104,7 +105,7 @@
"react-native-linear-gradient": "^2.4.2",
"react-native-matomo-sdk": "feruzm/react-native-matomo-sdk",
"react-native-media-controls": "^2.3.0",
"react-native-modal": "^11.5.6",
"react-native-modal": "11.5.6",
"react-native-modal-dropdown": "^1.0.2",
"react-native-modal-popover": "^2.1.0",
"react-native-modal-translucent": "^5.0.0",

View File

@ -69,7 +69,7 @@ import { HorizontalIconList } from './horizontalIconList/horizontalIconListView'
import { PopoverWrapper } from './popoverWrapper/popoverWrapperView';
import CommunitiesList from './communitiesList';
import SubscribedCommunitiesList from './subscribedCommunitiesList';
import { InsertLinkModal } from './insertLinkModal/insertLinkModal';
// View
import { Comment } from './comment';
import { Comments } from './comments';
@ -240,6 +240,7 @@ export {
QuickReplyModal,
Tooltip,
VideoPlayer,
InsertLinkModal,
QRModal,
SimpleChart,
};

View File

@ -0,0 +1,306 @@
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
import {
Platform,
Text,
TouchableOpacity,
View,
ActivityIndicator,
Dimensions,
} from 'react-native';
import { MainButton, PostBody, TextButton } from '..';
import styles from './insertLinkModalStyles';
import ActionSheet from 'react-native-actions-sheet';
import EStyleSheet from 'react-native-extended-stylesheet';
import TextInput from '../textInput';
import { delay } from '../../utils/editor';
import { isStringWebLink } from '../markdownEditor/view/formats/utils';
import { renderPostBody } from '@ecency/render-helper';
import { ScrollView } from 'react-native-gesture-handler';
import applyWebLinkFormat from '../markdownEditor/view/formats/applyWebLinkFormat';
import Clipboard from '@react-native-clipboard/clipboard';
interface InsertLinkModalProps {
handleOnInsertLink: ({
snippetText,
selection,
}: {
snippetText: string;
selection: { start: number; end: number };
}) => void;
handleOnSheetClose: () => void;
}
const screenWidth = Dimensions.get('window').width - 58;
const previewWidth = (10 / 16) * Dimensions.get('window').width;
export const InsertLinkModal = forwardRef(
({ handleOnInsertLink, handleOnSheetClose }: InsertLinkModalProps, ref) => {
const intl = useIntl();
const [isLoading, setIsLoading] = useState(false);
const [label, setLabel] = useState('');
const [url, setUrl] = useState('');
const [isUrlValid, setIsUrlValid] = useState(true);
const [selectedText, setSelectedText] = useState('');
const [formattedText, setFormattedText] = useState('');
const [selection, setSelection] = useState({ start: 0, end: 0 });
const [selectedUrlType, setSelectedUrlType] = useState(0);
const [previewBody, setPreviewBody] = useState('');
const sheetModalRef = useRef<ActionSheet>();
const labelInputRef = useRef(null);
const urlInputRef = useRef(null);
useImperativeHandle(ref, () => ({
showModal: async ({ selectedText, selection }) => {
if (selectedText) {
setSelectedText(selectedText);
setSelection(selection);
if (selection && selection.start !== selection.end) {
if (isStringWebLink(selectedText)) {
setUrl(selectedText);
} else {
setLabel(selectedText);
}
}
} else {
fetchCopiedText();
setSelection(selection);
}
sheetModalRef.current?.setModalVisible(true);
await delay(1500);
labelInputRef.current?.focus();
},
hideModal: () => {
sheetModalRef.current?.setModalVisible(false);
},
}));
useEffect(() => {
if (isStringWebLink(url)) {
setIsUrlValid(true);
}
if (url) {
const labelText =
selectedUrlType === 2 ? url.split('/').pop() : selectedUrlType === 1 ? '' : label;
applyWebLinkFormat({
item: { text: labelText, url: url },
text: '',
selection: { start: 0, end: 0 },
setTextAndSelection: _setFormattedTextAndSelection,
isImage: selectedUrlType === 2,
isVideo: selectedUrlType === 1,
});
} else {
setPreviewBody('');
}
}, [label, url, selectedUrlType]);
const fetchCopiedText = async () => {
const text = await Clipboard.getString();
if (isStringWebLink(text)) {
setUrl(text);
}
};
const _setFormattedTextAndSelection = ({ selection, text }) => {
setPreviewBody(renderPostBody(text, true, Platform.OS === 'ios' ? false : true));
setFormattedText(text);
};
const _handleLabelChange = (text) => {
setLabel(text);
};
const _handleUrlChange = (text) => {
setUrl(text);
};
const _handleOnCloseSheet = () => {
labelInputRef.current?.blur();
setLabel('');
setUrl('');
setSelectedUrlType(0);
setPreviewBody('');
setIsUrlValid(true);
setSelectedText('');
setFormattedText('');
handleOnSheetClose();
};
const _handleInsert = () => {
if (!isStringWebLink(url)) {
setIsUrlValid(false);
return;
}
handleOnInsertLink({ snippetText: formattedText, selection: selection });
setIsUrlValid(true);
};
const _renderFloatingPanel = () => {
return (
<View style={styles.floatingContainer}>
<TextButton
style={styles.cancelButton}
onPress={() => sheetModalRef.current?.setModalVisible(false)}
text={'Cancel'}
/>
<MainButton
style={styles.insertBtn}
onPress={() => _handleInsert()}
iconName="plus"
iconType="MaterialCommunityIcons"
iconColor="white"
text={'Insert Link'}
/>
</View>
);
};
const URL_TYPES = [
{
id: 0,
title: intl.formatMessage({
id: 'editor.plain',
}),
},
{
id: 1,
title: intl.formatMessage({
id: 'editor.video',
}),
},
{
id: 2,
title: intl.formatMessage({
id: 'editor.image',
}),
},
];
const LinkTypeOptions = URL_TYPES.map((item) => {
const selected = item.id === selectedUrlType;
return (
<TouchableOpacity
onPress={() => {
setSelectedUrlType(item.id);
if (item.id === 0) {
labelInputRef.current?.focus();
} else {
labelInputRef.current?.blur();
urlInputRef.current?.focus();
}
}}
style={selected ? styles.optionBtnSelected : styles.optionBtn}
>
<Text style={selected ? styles.optionBtnTextSelected : styles.optionBtnText}>
{item.title}
</Text>
</TouchableOpacity>
);
});
const _renderLabelInput = () => (
<>
<Text style={styles.inputLabel}>
{intl.formatMessage({
id: 'editor.label',
})}
</Text>
<TextInput
style={[styles.input, selectedUrlType !== 0 && styles.disabled]}
value={label}
onChangeText={_handleLabelChange}
placeholder={intl.formatMessage({
id: 'editor.enter_label_placeholder',
})}
placeholderTextColor="#c1c5c7"
autoCapitalize="none"
editable={selectedUrlType === 0}
innerRef={labelInputRef}
/>
</>
);
const _renderInputs = () => (
<View style={styles.inputsContainer}>
<Text style={styles.inputLabel}>
{intl.formatMessage({
id: 'editor.link_type_text',
})}
</Text>
<View style={styles.optionsRow}>{LinkTypeOptions}</View>
{_renderLabelInput()}
<Text style={styles.inputLabel}>
{intl.formatMessage({
id: 'editor.url',
})}
</Text>
<TextInput
style={styles.input}
value={url}
onChangeText={_handleUrlChange}
placeholder={intl.formatMessage({
id: 'editor.enter_url_placeholder',
})}
placeholderTextColor="#c1c5c7"
autoCapitalize="none"
keyboardType="url"
innerRef={urlInputRef}
/>
{!isUrlValid && (
<Text style={styles.validText}>
{intl.formatMessage({
id: 'editor.invalid_url_error',
})}
</Text>
)}
</View>
);
const _renderPreview = () => {
return (
<>
<View style={styles.previewContainer}>
<Text style={styles.previewText}>
{intl.formatMessage({
id: 'editor.preview',
})}
</Text>
<ScrollView
style={styles.previewWrapper}
contentContainerStyle={styles.previewContentContainer}
>
<View style={styles.preview} pointerEvents="none">
{previewBody ? (
<PostBody
body={previewBody}
onLoadEnd={() => setIsLoading(false)}
width={screenWidth}
/>
) : null}
</View>
</ScrollView>
{isLoading && <ActivityIndicator color={'$primaryBlue'} />}
</View>
</>
);
};
const _renderContent = (
<ScrollView style={styles.container} keyboardShouldPersistTaps={'handled'}>
{_renderInputs()}
{_renderPreview()}
{_renderFloatingPanel()}
</ScrollView>
);
return (
<ActionSheet
ref={sheetModalRef}
gestureEnabled={true}
keyboardShouldPersistTaps="handled"
containerStyle={styles.sheetContent}
keyboardHandlerEnabled
indicatorColor={EStyleSheet.value('$primaryWhiteLightBackground')}
onClose={_handleOnCloseSheet}
>
{_renderContent}
</ActionSheet>
);
},
);

View File

@ -0,0 +1,121 @@
import { ViewStyle, Dimensions } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
const previewHeight = (10 / 16) * Dimensions.get('window').width;
export default EStyleSheet.create({
sheetContent: {
backgroundColor: '$primaryBackgroundColor',
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
zIndex: 999,
},
modalStyle: {
flex: 1,
backgroundColor: '$primaryBackgroundColor',
margin: 0,
paddingTop: 32,
paddingBottom: 16,
},
container: {
paddingVertical: 8,
},
bodyWrapper: {
flex: 3,
paddingHorizontal: 16,
},
floatingContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
paddingVertical: 8,
paddingHorizontal: 16,
backgroundColor: '$primaryBackgroundColor',
} as ViewStyle,
insertBtn: {
marginLeft: 16,
width: 170,
},
inputsContainer: {
paddingHorizontal: 16,
},
inputLabel: {
color: '$primaryBlack',
fontWeight: '600',
textAlign: 'left',
},
input: {
borderWidth: 1,
borderColor: '$borderColor',
borderRadius: 8,
paddingHorizontal: 10,
color: '$primaryBlack',
marginVertical: 8,
height: 50,
},
validText: {
color: '$primaryRed',
marginVertical: 4,
},
optionsRow: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 12,
},
optionBtnSelected: {
maxWidth: 75,
borderWidth: 1,
borderColor: '$primaryBlue',
backgroundColor: '$primaryBlue',
borderRadius: 15,
paddingVertical: 4,
paddingHorizontal: 12,
marginRight: 8,
},
optionBtnTextSelected: {
textAlign: 'center',
color: '$white',
textTransform: 'uppercase',
},
optionBtn: {
maxWidth: 75,
borderWidth: 1,
borderColor: '$primaryBlue',
borderRadius: 15,
paddingVertical: 4,
paddingHorizontal: 12,
marginRight: 8,
},
optionBtnText: {
textAlign: 'center',
color: '$primaryBlue',
textTransform: 'uppercase',
},
previewContainer: {},
previewText: {
color: '$primaryBlack',
fontWeight: '600',
textAlign: 'left',
paddingLeft: 16,
marginVertical: 8,
},
previewWrapper: {
height: previewHeight,
marginHorizontal: 16,
borderRadius: 12,
borderWidth: 1,
borderColor: '$borderColor',
},
previewContentContainer: {
flexGrow: 1,
justifyContent: 'center',
},
preview: {
paddingLeft: 12,
paddingRight: 12,
},
disabled: {
backgroundColor: '$modalBackground',
},
});

View File

@ -1,11 +1,10 @@
import {replaceBetween } from './utils';
export default async ({ text, selection, setTextAndSelection, snippetText}) => {
const newText = replaceBetween(text, selection, `\n${snippetText}\n`);
const newText = replaceBetween(text, selection, `${snippetText}`);
const newSelection = {
start: selection.start + 1,
end: selection.start + 1 + (snippetText && snippetText.length),
start: selection.start,
end: selection.start + (snippetText && snippetText.length),
};
setTextAndSelection({ text: newText, selection: newSelection });
};

View File

@ -3,30 +3,52 @@ import { isStringWebLink, replaceBetween } from './utils';
export const writeUrlTextHere = 'https://example.com';
export const writeTextHereString = 'Text here';
export default async ({ text, selection, setTextAndSelection, item, isImage = null }) => {
export default async ({
text,
selection,
setTextAndSelection,
item,
isImage = null,
isVideo = null,
}) => {
const imagePrefix = isImage ? '!' : '';
const itemText = item ? item.text : writeTextHereString;
const itemUrl = item ? item.url : writeUrlTextHere;
const isRawUrl = item && !item.text;
let newText;
let newSelection;
const selectedText = text.substring(selection.start, selection.end);
if (selection.start !== selection.end) {
if (isStringWebLink(selectedText)) {
newText = replaceBetween(text, selection, `\n${imagePrefix}[${itemText}](${selectedText})\n`);
newText = replaceBetween(text, selection, `${imagePrefix}[${itemText}](${selectedText})`);
newSelection = {
start: selection.start + 1,
end: selection.start + 1 + itemText && itemText.length,
// start: selection.start + 1,
// end: selection.start + 1 + itemText && itemText.length,
start: newText.length,
end: newText.length,
};
} else {
newText = replaceBetween(text, selection, `\n${imagePrefix}[${selectedText}](${itemUrl})\n`);
newText = replaceBetween(text, selection, `${imagePrefix}[${selectedText}](${itemUrl})`);
newSelection = {
start: selection.end + 3,
end: selection.end + 3 + itemUrl.length,
// start: selection.end + 3,
// end: selection.end + 3 + itemUrl.length,
start: newText.length,
end: newText.length,
};
}
} else {
newText = replaceBetween(text, selection, `\n${imagePrefix}[${itemText}](${itemUrl})\n`);
newText = replaceBetween(
text,
selection,
isVideo
? `\n${itemUrl}\n`
: isRawUrl
? `${itemUrl}`
: isImage
? `\n${imagePrefix}[${itemText}](${itemUrl})\n`
: `${imagePrefix}[${itemText}](${itemUrl})`,
);
if (isImage) {
const newIndex = newText && newText.indexOf(itemUrl) + 2 + itemUrl.length;
newSelection = {
@ -35,8 +57,10 @@ export default async ({ text, selection, setTextAndSelection, item, isImage = nu
};
} else {
newSelection = {
start: selection.start + 1,
end: selection.start + 1 + (itemText && itemText.length),
start: newText.length,
end: newText.length,
// start: hasLabel ? selection.start + 1 : 0,
// end: hasLabel ? selection.start + 1 + (itemText && itemText.length) : 0,
};
}
}

View File

@ -17,6 +17,7 @@ import { Icon } from '../../icon';
// Utils
import Formats from './formats/formats';
import applyMediaLink from './formats/applyMediaLink';
import applyWebLinkFormat from './formats/applyWebLinkFormat';
// Actions
import { toggleAccountsBottomSheet } from '../../../redux/actions/uiAction';
@ -37,6 +38,7 @@ import {
SnippetsModal,
UploadsGalleryModal,
Tooltip,
InsertLinkModal,
} from '../../index';
import { ThemeContainer } from '../../../containers';
@ -90,6 +92,7 @@ const MarkdownEditorView = ({
const galleryRef = useRef(null);
const clearRef = useRef(null);
const uploadsGalleryModalRef = useRef(null);
const insertLinkModalRef = useRef(null);
const tooltipRef = useRef(null);
const dispatch = useDispatch();
@ -232,6 +235,7 @@ const MarkdownEditorView = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
const _setTextAndSelection = useCallback(({ selection: _selection, text: _text }) => {
console.log('_text : ', _text);
inputRef.current.setNativeProps({
text: _text,
});
@ -255,6 +259,7 @@ const MarkdownEditorView = ({
_changeText(_text);
});
console.log('text : ', text);
const _renderPreview = () => (
<ScrollView style={styles.previewContainer}>
{text ? (
@ -270,7 +275,7 @@ const MarkdownEditorView = ({
text,
selection,
setTextAndSelection: _setTextAndSelection,
snippetText,
snippetText: `\n${snippetText}\n`,
});
};
@ -290,6 +295,26 @@ const MarkdownEditorView = ({
}
};
const _handleOnAddLinkPress = () => {
insertLinkModalRef.current?.showModal({
selectedText: text.slice(selection.start, selection.end),
selection: selection,
});
inputRef.current?.blur();
};
const _handleOnAddLinkSheetClose = () => {
inputRef.current?.focus();
};
const _handleInsertLink = ({ snippetText, selection }) => {
applySnippet({
text,
selection,
setTextAndSelection: _setTextAndSelection,
snippetText,
});
insertLinkModalRef.current?.hideModal();
};
const _renderMarkupButton = ({ item }) => (
<View style={styles.buttonWrapper}>
<IconButton
@ -355,7 +380,8 @@ const MarkdownEditorView = ({
iconType="FontAwesome"
name="link"
onPress={() =>
Formats[3].onPress({ text, selection, setTextAndSelection: _setTextAndSelection })
// Formats[3].onPress({ text, selection, setTextAndSelection: _setTextAndSelection })
_handleOnAddLinkPress()
}
/>
<IconButton
@ -509,6 +535,12 @@ const MarkdownEditorView = ({
uploadedImage={uploadedImage}
/>
<InsertLinkModal
ref={insertLinkModalRef}
handleOnInsertLink={_handleInsertLink}
handleOnSheetClose={_handleOnAddLinkSheetClose}
/>
<OptionsModal
ref={galleryRef}
options={[

View File

@ -1,6 +1,6 @@
import React, { PureComponent } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import ModalBox from 'react-native-modal';
import { default as ModalBox } from 'react-native-modal';
import { IconButton } from '../../iconButton';
import styles from './modalStyles';

View File

@ -22,7 +22,8 @@ import { PostHtmlRenderer, VideoPlayer } from '../../..';
const WIDTH = Dimensions.get('window').width;
const PostBody = ({ navigation, body, dispatch, onLoadEnd }) => {
const PostBody = ({ navigation, body, dispatch, onLoadEnd, width }) => {
console.log('body : ', body);
const [isImageModalOpen, setIsImageModalOpen] = useState(false);
const [postImages, setPostImages] = useState([]);
@ -331,7 +332,7 @@ const PostBody = ({ navigation, body, dispatch, onLoadEnd }) => {
<View>
<PostHtmlRenderer
body={html}
contentWidth={WIDTH - 32}
contentWidth={width ? width : WIDTH - 32}
onLoaded={_handleLoadEnd}
onElementIsImage={_onElementIsImage}
setSelectedImage={_handleSetSelectedImage}
@ -348,7 +349,7 @@ const PostBody = ({ navigation, body, dispatch, onLoadEnd }) => {
};
const areEqual = (prevProps, nextProps) => {
if (prevProps.body !== nextProps.body) {
if (prevProps.body === nextProps.body) {
return true;
}
return false;

View File

@ -13,7 +13,7 @@ export default EStyleSheet.create({
flex: 1,
},
input: {
flex: 1,
minHeight: 50,
// flex: 1,
// minHeight: 50,
},
});

View File

@ -368,7 +368,17 @@
"done":"DONE",
"draft_save_title":"Saving Draft",
"draft_update":"Update current draft",
"draft_save_new":"Save as new draft"
"draft_save_new":"Save as new draft",
"label":"Label",
"enter_label_placeholder":"Enter Label (Optional)",
"url": "URL",
"enter_url_placeholder":"Enter URL",
"link_type_text":"Type of Link",
"preview":"Preview",
"invalid_url_error":"Please insert valid url",
"plain":"Plain",
"video":"Video",
"image":"Image"
},
"snippets":{
"label_no_snippets":"No Snippets Found",

View File

@ -232,3 +232,5 @@ export const createPatch = (text1, text2) => {
return patch;
};
export const delay = ms => new Promise(res => setTimeout(res, ms));

View File

@ -1482,6 +1482,11 @@
dependencies:
merge-options "^3.0.4"
"@react-native-clipboard/clipboard@^1.8.5":
version "1.8.5"
resolved "https://registry.yarnpkg.com/@react-native-clipboard/clipboard/-/clipboard-1.8.5.tgz#b11276e38ef288b0fd70c0a38506e2deecc5fa5a"
integrity sha512-o2RPDwP9JMnLece1Qq6a3Fsz/VxfA9auLckkGOor7WcI82DWaWiJ6Uiyu7H1xpaUyqWc+ypVKRX680GYS36HjA==
"@react-native-community/async-storage@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@react-native-community/async-storage/-/async-storage-1.5.0.tgz#647ffcd832272068b0be57332e08d73036ed391f"
@ -8734,10 +8739,10 @@ react-native-modal-translucent@^5.0.0:
resolved "https://registry.yarnpkg.com/react-native-modal-translucent/-/react-native-modal-translucent-5.0.0.tgz#8b35cfa4189dce776c77a925b00ad19d965bd0a2"
integrity sha512-xhJAlq4uCE7jPEIPxGS1WNiRIm5DCrZEO3SF88moTOm6b4/wfFEANf+lMsVkQf9b9dsQ6Em4nq4uAoejtbHb2A==
react-native-modal@^11.5.6:
version "11.7.0"
resolved "https://registry.yarnpkg.com/react-native-modal/-/react-native-modal-11.7.0.tgz#6637d757eeac6eda85f7017a9dfdee0c0fe3a34c"
integrity sha512-0AeAugUrn12DaJK+k2XGmt8ZIUyWgl1nRdipfwHZDnzFSM8g1oqpf7rHxjOqhimHtmzSj4xJ//ZOn1DWe9aC5Q==
react-native-modal@11.5.6:
version "11.5.6"
resolved "https://registry.yarnpkg.com/react-native-modal/-/react-native-modal-11.5.6.tgz#bb25a78c35a5e24f45de060e5f64284397d38a87"
integrity sha512-APGNfbvgC4hXbJqcSADu79GLoMKIHUmgR3fDQ7rCGZNBypkStSP8imZ4PKK/OzIZZfjGU9aP49jhMgGbhY9KHA==
dependencies:
prop-types "^15.6.2"
react-native-animatable "1.3.3"