Update UX for post long press menu (#2325)

* Update UX for post long press menu

* Post menu feedback review
This commit is contained in:
Elias Nahum 2018-11-14 17:29:09 -03:00 committed by GitHub
parent 17c7d0202b
commit ae629a455c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 845 additions and 770 deletions

View file

@ -2076,41 +2076,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---
## react-native-tooltip
This product contains 'react-native-tooltip' by Chirag Jain.
A react-native wrapper for showing tooltips
* HOMEPAGE:
* https://github.com/chirag04/react-native-tooltip#readme
* LICENSE: MIT
The MIT License (MIT)
Copyright (c) 2015-2016 Chirag Jain
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
## react-native-vector-icons
This product contains 'react-native-vector-icons' by Joel Arvidsson.

View file

@ -8,10 +8,9 @@ import {intlShape} from 'react-intl';
import {displayUsername} from 'mattermost-redux/utils/user_utils';
import {emptyFunction} from 'app/utils/general';
import CustomPropTypes from 'app/constants/custom_prop_types';
import mattermostManaged from 'app/mattermost_managed';
import BottomSheet from 'app/utils/bottom_sheet';
export default class AtMention extends React.PureComponent {
static propTypes = {
@ -19,7 +18,6 @@ export default class AtMention extends React.PureComponent {
mentionName: PropTypes.string.isRequired,
mentionStyle: CustomPropTypes.Style,
navigator: PropTypes.object.isRequired,
onLongPress: PropTypes.func.isRequired,
onPostPress: PropTypes.func,
textStyle: CustomPropTypes.Style,
teammateNameDisplay: PropTypes.string,
@ -27,10 +25,6 @@ export default class AtMention extends React.PureComponent {
usersByUsername: PropTypes.object.isRequired,
};
static defaultProps = {
onLongPress: emptyFunction,
};
static contextTypes = {
intl: intlShape,
};
@ -101,26 +95,33 @@ export default class AtMention extends React.PureComponent {
}
handleLongPress = async () => {
const {intl} = this.context;
const {formatMessage} = this.context.intl;
const config = await mattermostManaged.getLocalConfig();
let action;
if (config.copyAndPasteProtection !== 'false') {
action = {
text: intl.formatMessage({
id: 'mobile.mention.copy_mention',
defaultMessage: 'Copy Mention',
}),
onPress: this.handleCopyMention,
};
}
const cancelText = formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'});
const actionText = formatMessage({id: 'mobile.mention.copy_mention', defaultMessage: 'Copy Mention'});
this.props.onLongPress(action);
BottomSheet.showBottomSheetWithOptions({
options: [actionText, cancelText],
cancelButtonIndex: 1,
}, (value) => {
if (value !== 1) {
this.handleCopyMention();
}
});
}
};
handleCopyMention = () => {
const {username} = this.state;
const {user} = this.state;
const {mentionName} = this.props;
let username = mentionName;
if (user.username) {
username = user.username;
}
Clipboard.setString(`@${username}`);
};

View file

@ -25,6 +25,7 @@ export default class FileAttachment extends PureComponent {
file: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
onCaptureRef: PropTypes.func,
onLongPress: PropTypes.func,
onPreviewPress: PropTypes.func,
theme: PropTypes.object.isRequired,
navigator: PropTypes.object,
@ -92,6 +93,7 @@ export default class FileAttachment extends PureComponent {
file,
theme,
navigator,
onLongPress,
} = this.props;
const {data} = file;
const style = getStyleSheet(theme);
@ -99,7 +101,10 @@ export default class FileAttachment extends PureComponent {
let fileAttachmentComponent;
if ((data && data.has_preview_image) || file.loading || isGif(data)) {
fileAttachmentComponent = (
<TouchableOpacity onPress={this.handlePreviewPress}>
<TouchableOpacity
onPress={this.handlePreviewPress}
onLongPress={onLongPress}
>
<FileAttachmentImage
file={data || {}}
onCaptureRef={this.handleCaptureRef}
@ -114,12 +119,16 @@ export default class FileAttachment extends PureComponent {
canDownloadFiles={canDownloadFiles}
file={file}
navigator={navigator}
onLongPress={onLongPress}
theme={theme}
/>
);
} else {
fileAttachmentComponent = (
<TouchableOpacity onPress={this.handlePreviewPress}>
<TouchableOpacity
onPress={this.handlePreviewPress}
onLongPress={onLongPress}
>
<FileAttachmentIcon
file={data}
onCaptureRef={this.handleCaptureRef}
@ -135,6 +144,7 @@ export default class FileAttachment extends PureComponent {
<View style={[style.fileWrapper, {width}]}>
{fileAttachmentComponent}
<TouchableOpacity
onLongPress={onLongPress}
onPress={this.handlePreviewPress}
style={style.fileInfoContainer}
>

View file

@ -47,6 +47,7 @@ export default class FileAttachmentDocument extends PureComponent {
file: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
navigator: PropTypes.object,
onLongPress: PropTypes.func,
wrapperHeight: PropTypes.number,
wrapperWidth: PropTypes.number,
};
@ -349,7 +350,7 @@ export default class FileAttachmentDocument extends PureComponent {
}
render() {
const {theme, wrapperHeight} = this.props;
const {onLongPress, theme, wrapperHeight} = this.props;
const {downloading, progress} = this.state;
let fileAttachmentComponent;
@ -371,8 +372,11 @@ export default class FileAttachmentDocument extends PureComponent {
}
return (
<TouchableOpacity onPress={this.handlePreviewPress}>
<View style={style.whiteBackgroud}>
<TouchableOpacity
onPress={this.handlePreviewPress}
onLongPress={onLongPress}
>
<View style={style.whiteBackground}>
{fileAttachmentComponent}
</View>
</TouchableOpacity>
@ -389,7 +393,7 @@ const style = StyleSheet.create({
position: 'absolute',
top: 0,
},
whiteBackgroud: {
whiteBackground: {
backgroundColor: '#fff',
},
});

View file

@ -8,7 +8,6 @@ import {
Platform,
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
@ -30,7 +29,6 @@ export default class FileAttachmentList extends Component {
deviceWidth: PropTypes.number.isRequired,
fileIds: PropTypes.array.isRequired,
files: PropTypes.array.isRequired,
hideOptionsContext: PropTypes.func.isRequired,
isFailed: PropTypes.bool,
navigator: PropTypes.object,
onLongPress: PropTypes.func,
@ -121,7 +119,6 @@ export default class FileAttachmentList extends Component {
};
handlePreviewPress = preventDoubleTap((idx) => {
this.props.hideOptionsContext();
Keyboard.dismiss();
previewImageAtIndex(this.props.navigator, this.items, idx, this.galleryFiles);
});
@ -157,23 +154,18 @@ export default class FileAttachmentList extends Component {
};
return (
<TouchableOpacity
<FileAttachment
key={file.id}
canDownloadFiles={canDownloadFiles}
deviceWidth={deviceWidth}
file={f}
index={idx}
navigator={navigator}
onCaptureRef={this.handleCaptureRef}
onPreviewPress={this.handlePreviewPress}
onLongPress={this.props.onLongPress}
onPressIn={this.handlePressIn}
onPressOut={this.handlePressOut}
>
<FileAttachment
canDownloadFiles={canDownloadFiles}
deviceWidth={deviceWidth}
file={f}
index={idx}
navigator={navigator}
onCaptureRef={this.handleCaptureRef}
onPreviewPress={this.handlePreviewPress}
theme={this.props.theme}
/>
</TouchableOpacity>
theme={this.props.theme}
/>
);
});
};

View file

@ -116,7 +116,6 @@ class FormattedMarkdownText extends React.PureComponent {
mentionStyle={this.props.textStyles.mention}
mentionName={mentionName}
navigator={this.props.navigator}
onLongPress={this.props.onPostPress}
onPostPress={this.props.onPostPress}
textStyle={this.computeTextStyle(this.props.baseTextStyle, context)}
/>
@ -145,4 +144,4 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
};
});
export default injectIntl(FormattedMarkdownText);
export default injectIntl(FormattedMarkdownText);

View file

@ -49,7 +49,6 @@ export default class Markdown extends PureComponent {
navigator: PropTypes.object.isRequired,
onChannelLinkPress: PropTypes.func,
onHashtagPress: PropTypes.func,
onLongPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
onPostPress: PropTypes.func,
textStyles: PropTypes.object,
@ -80,13 +79,13 @@ export default class Markdown extends PureComponent {
return new Parser({
urlFilter: this.urlFilter,
});
}
};
urlFilter = (url) => {
const scheme = getScheme(url);
return !scheme || this.props.autolinkedUrlSchemes.indexOf(scheme) !== -1;
}
};
createRenderer = () => {
return new Renderer({
@ -130,7 +129,7 @@ export default class Markdown extends PureComponent {
renderParagraphsInLists: true,
getExtraPropsForNode: this.getExtraPropsForNode,
});
}
};
getExtraPropsForNode = (node) => {
const extraProps = {
@ -144,11 +143,11 @@ export default class Markdown extends PureComponent {
}
return extraProps;
}
};
computeTextStyle = (baseStyle, context) => {
return concatStyles(baseStyle, context.map((type) => this.props.textStyles[type]));
}
};
renderText = ({context, literal}) => {
if (context.indexOf('image') !== -1) {
@ -160,19 +159,17 @@ export default class Markdown extends PureComponent {
const style = this.computeTextStyle(this.props.baseTextStyle, context);
return <Text style={style}>{literal}</Text>;
}
};
renderCodeSpan = ({context, literal}) => {
return <Text style={this.computeTextStyle([this.props.baseTextStyle, this.props.textStyles.code], context)}>{literal}</Text>;
}
};
renderImage = ({linkDestination, reactChildren, context, src}) => {
if (context.indexOf('table') !== -1) {
// We have enough problems rendering images as is, so just render a link inside of a table
return (
<MarkdownTableImage
linkDestination={linkDestination}
onLongPress={this.props.onLongPress}
source={src}
textStyle={[this.computeTextStyle(this.props.baseTextStyle, context), this.props.textStyles.link]}
navigator={this.props.navigator}
@ -187,14 +184,13 @@ export default class Markdown extends PureComponent {
linkDestination={linkDestination}
isReplyPost={this.props.isReplyPost}
navigator={this.props.navigator}
onLongPress={this.props.onLongPress}
source={src}
errorTextStyle={[this.computeTextStyle(this.props.baseTextStyle, context), this.props.textStyles.error]}
>
{reactChildren}
</MarkdownImage>
);
}
};
renderAtMention = ({context, mentionName}) => {
if (this.props.disableAtMentions) {
@ -207,12 +203,11 @@ export default class Markdown extends PureComponent {
textStyle={this.computeTextStyle(this.props.baseTextStyle, context)}
isSearchResult={this.props.isSearchResult}
mentionName={mentionName}
onLongPress={this.props.onLongPress}
onPostPress={this.props.onPostPress}
navigator={this.props.navigator}
/>
);
}
};
renderChannelLink = ({context, channelName}) => {
if (this.props.disableChannelLink) {
@ -227,7 +222,7 @@ export default class Markdown extends PureComponent {
channelName={channelName}
/>
);
}
};
renderEmoji = ({context, emojiName, literal}) => {
return (
@ -237,7 +232,7 @@ export default class Markdown extends PureComponent {
textStyle={this.computeTextStyle(this.props.baseTextStyle, context)}
/>
);
}
};
renderHashtag = ({context, hashtag}) => {
if (this.props.disableHashtags) {
@ -252,7 +247,7 @@ export default class Markdown extends PureComponent {
navigator={this.props.navigator}
/>
);
}
};
renderParagraph = ({children, first}) => {
if (!children || children.length === 0) {
@ -271,7 +266,7 @@ export default class Markdown extends PureComponent {
</Text>
</View>
);
}
};
renderHeading = ({children, level}) => {
const containerStyle = [
@ -286,7 +281,7 @@ export default class Markdown extends PureComponent {
</Text>
</View>
);
}
};
renderCodeBlock = (props) => {
// These sometimes include a trailing newline
@ -298,10 +293,9 @@ export default class Markdown extends PureComponent {
content={content}
language={props.language}
textStyle={this.props.textStyles.codeBlock}
onLongPress={this.props.onLongPress}
/>
);
}
};
renderBlockQuote = ({children, ...otherProps}) => {
return (
@ -312,7 +306,7 @@ export default class Markdown extends PureComponent {
{children}
</MarkdownBlockQuote>
);
}
};
renderList = ({children, start, tight, type}) => {
return (
@ -324,7 +318,7 @@ export default class Markdown extends PureComponent {
{children}
</MarkdownList>
);
}
};
renderListItem = ({children, context, ...otherProps}) => {
const level = context.filter((type) => type === 'list').length;
@ -338,19 +332,19 @@ export default class Markdown extends PureComponent {
{children}
</MarkdownListItem>
);
}
};
renderHardBreak = () => {
return <Text>{'\n'}</Text>;
}
};
renderThematicBreak = () => {
return <View style={this.props.blockStyles.horizontalRule}/>;
}
};
renderSoftBreak = () => {
return <Text>{'\n'}</Text>;
}
};
renderHtml = (props) => {
let rendered = this.renderText(props);
@ -366,7 +360,7 @@ export default class Markdown extends PureComponent {
}
return rendered;
}
};
renderTable = ({children}) => {
return (
@ -374,19 +368,18 @@ export default class Markdown extends PureComponent {
{children}
</MarkdownTable>
);
}
};
renderLink = ({children, href}) => {
return (
<MarkdownLink
href={href}
onLongPress={this.props.onLongPress}
onPermalinkPress={this.props.onPermalinkPress}
>
{children}
</MarkdownLink>
);
}
};
renderEditedIndicator = ({context}) => {
let spacer = '';
@ -411,7 +404,7 @@ export default class Markdown extends PureComponent {
/>
</Text>
);
}
};
render() {
let ast = this.parser.parse(this.props.value);

View file

@ -14,6 +14,7 @@ import {
import CustomPropTypes from 'app/constants/custom_prop_types';
import FormattedText from 'app/components/formatted_text';
import BottomSheet from 'app/utils/bottom_sheet';
import {getDisplayNameForLanguage} from 'app/utils/markdown';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@ -28,7 +29,6 @@ export default class MarkdownCodeBlock extends React.PureComponent {
language: PropTypes.string,
content: PropTypes.string.isRequired,
textStyle: CustomPropTypes.Style,
onLongPress: PropTypes.func.isRequired,
};
static defaultProps = {
@ -84,20 +84,23 @@ export default class MarkdownCodeBlock extends React.PureComponent {
const config = await mattermostManaged.getLocalConfig();
let action;
if (config.copyAndPasteProtection !== 'true') {
action = {
text: formatMessage({id: 'mobile.markdown.code.copy_code', defaultMessage: 'Copy Code'}),
onPress: this.handleCopyCode,
};
const cancelText = formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'});
const actionText = formatMessage({id: 'mobile.markdown.code.copy_code', defaultMessage: 'Copy Code'});
BottomSheet.showBottomSheetWithOptions({
options: [actionText, cancelText],
cancelButtonIndex: 1,
}, (value) => {
if (value !== 1) {
this.handleCopyCode();
}
});
}
this.props.onLongPress(action);
}
};
handleCopyCode = () => {
Clipboard.setString(this.props.content);
}
};
trimContent = (content) => {
const lines = content.split('\n');

View file

@ -19,6 +19,7 @@ import FormattedText from 'app/components/formatted_text';
import ProgressiveImage from 'app/components/progressive_image';
import CustomPropTypes from 'app/constants/custom_prop_types';
import mattermostManaged from 'app/mattermost_managed';
import BottomSheet from 'app/utils/bottom_sheet';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {previewImageAtIndex, calculateDimensions} from 'app/utils/images';
import {normalizeProtocol} from 'app/utils/url';
@ -38,7 +39,6 @@ export default class MarkdownImage extends React.Component {
linkDestination: PropTypes.string,
isReplyPost: PropTypes.bool,
navigator: PropTypes.object.isRequired,
onLongPress: PropTypes.func,
serverURL: PropTypes.string.isRequired,
source: PropTypes.string.isRequired,
errorTextStyle: CustomPropTypes.Style,
@ -138,15 +138,18 @@ export default class MarkdownImage extends React.Component {
const config = await mattermostManaged.getLocalConfig();
let action;
if (config.copyAndPasteProtection !== 'true') {
action = {
text: formatMessage({id: 'mobile.markdown.link.copy_url', defaultMessage: 'Copy URL'}),
onPress: this.handleLinkCopy,
};
const cancelText = formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'});
const actionText = formatMessage({id: 'mobile.markdown.link.copy_url', defaultMessage: 'Copy URL'});
BottomSheet.showBottomSheetWithOptions({
options: [actionText, cancelText],
cancelButtonIndex: 1,
}, (value) => {
if (value !== 1) {
this.handleLinkCopy();
}
});
}
this.props.onLongPress(action);
};
handleLinkCopy = () => {

View file

@ -9,24 +9,22 @@ import {intlShape} from 'react-intl';
import CustomPropTypes from 'app/constants/custom_prop_types';
import mattermostManaged from 'app/mattermost_managed';
import Config from 'assets/config';
import BottomSheet from 'app/utils/bottom_sheet';
import {preventDoubleTap} from 'app/utils/tap';
import {matchPermalink, normalizeProtocol} from 'app/utils/url';
import Config from 'assets/config';
export default class MarkdownLink extends PureComponent {
static propTypes = {
children: CustomPropTypes.Children.isRequired,
href: PropTypes.string.isRequired,
onLongPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
serverURL: PropTypes.string.isRequired,
siteURL: PropTypes.string.isRequired,
};
static defaultProps = {
onLongPress: () => true,
onPermalinkPress: () => true,
};
@ -89,27 +87,30 @@ export default class MarkdownLink extends PureComponent {
...otherChildProps,
};
});
}
};
handleLongPress = async () => {
const {formatMessage} = this.context.intl;
const config = await mattermostManaged.getLocalConfig();
let action;
if (config.copyAndPasteProtection !== 'true') {
action = {
text: formatMessage({id: 'mobile.markdown.link.copy_url', defaultMessage: 'Copy URL'}),
onPress: this.handleCopyURL,
};
const cancelText = formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'});
const actionText = formatMessage({id: 'mobile.markdown.link.copy_url', defaultMessage: 'Copy URL'});
BottomSheet.showBottomSheetWithOptions({
options: [actionText, cancelText],
cancelButtonIndex: 1,
}, (value) => {
if (value !== 1) {
this.handleLinkCopy();
}
});
}
this.props.onLongPress(action);
}
};
handleCopyURL = () => {
Clipboard.setString(this.props.href);
}
};
render() {
const children = Config.ExperimentalNormalizeMarkdownLinks ? this.parseChildren() : this.props.children;

View file

@ -17,7 +17,6 @@ export default class MessageAttachments extends PureComponent {
postId: PropTypes.string.isRequired,
navigator: PropTypes.object.isRequired,
onHashtagPress: PropTypes.func,
onLongPress: PropTypes.func.isRequired,
onPermalinkPress: PropTypes.func,
theme: PropTypes.object,
textStyles: PropTypes.object,
@ -30,7 +29,6 @@ export default class MessageAttachments extends PureComponent {
blockStyles,
navigator,
onHashtagPress,
onLongPress,
onPermalinkPress,
postId,
theme,
@ -47,7 +45,6 @@ export default class MessageAttachments extends PureComponent {
key={'att_' + i}
navigator={navigator}
onHashtagPress={onHashtagPress}
onLongPress={onLongPress}
onPermalinkPress={onPermalinkPress}
postId={postId}
theme={theme}

View file

@ -40,7 +40,6 @@ export default class MessageAttachment extends PureComponent {
blockStyles: PropTypes.object,
navigator: PropTypes.object.isRequired,
postId: PropTypes.string.isRequired,
onLongPress: PropTypes.func.isRequired,
onPermalinkPress: PropTypes.func,
theme: PropTypes.object,
textStyles: PropTypes.object,
@ -194,7 +193,6 @@ export default class MessageAttachment extends PureComponent {
blockStyles={blockStyles}
value={(field.value || '')}
navigator={navigator}
onLongPress={this.props.onLongPress}
onPermalinkPress={onPermalinkPress}
/>
</View>
@ -317,7 +315,6 @@ export default class MessageAttachment extends PureComponent {
blockStyles={blockStyles}
value={attachment.pretext}
navigator={navigator}
onLongPress={this.props.onLongPress}
onPermalinkPress={onPermalinkPress}
/>
</View>
@ -415,7 +412,6 @@ export default class MessageAttachment extends PureComponent {
blockStyles={blockStyles}
value={attachment.text}
navigator={navigator}
onLongPress={this.props.onLongPress}
onPermalinkPress={onPermalinkPress}
/>
</View>

View file

@ -1,6 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Used to leverage the platform specific components
import OptionsContext from './options_context';
export default OptionsContext;

View file

@ -1,71 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {TouchableHighlight, View} from 'react-native';
import RNBottomSheet from 'react-native-bottom-sheet';
export default class OptionsContext extends PureComponent {
static propTypes = {
getPostActions: PropTypes.func,
cancelText: PropTypes.string,
children: PropTypes.node.isRequired,
onPress: PropTypes.func.isRequired,
toggleSelected: PropTypes.func.isRequired,
};
static defaultProps = {
getPostActions: () => [],
cancelText: 'Cancel',
};
show = (additionalAction) => {
const {getPostActions, cancelText} = this.props;
const nextActions = getPostActions();
if (additionalAction && !additionalAction.nativeEvent && additionalAction.text) {
const copyPostIndex = nextActions.findIndex((action) => action.copyPost);
nextActions.splice(copyPostIndex + 1, 0, additionalAction);
}
if (nextActions.length) {
const actionsText = nextActions.map((a) => a.text);
RNBottomSheet.showBottomSheetWithOptions({
options: [...actionsText, cancelText],
cancelButtonIndex: nextActions.length,
}, (value) => {
if (value !== nextActions.length) {
const selectedOption = nextActions[value];
if (selectedOption && selectedOption.onPress) {
selectedOption.onPress();
}
}
});
}
};
handleHideUnderlay = () => {
this.props.toggleSelected(false, this.props.getPostActions().length > 0);
};
handleShowUnderlay = () => {
this.props.toggleSelected(true, this.props.getPostActions().length > 0);
};
render() {
return (
<TouchableHighlight
onHideUnderlay={this.handleHideUnderlay}
onLongPress={this.show}
onPress={this.props.onPress}
onShowUnderlay={this.handleShowUnderlay}
underlayColor='transparent'
style={{flex: 1, flexDirection: 'row'}}
>
<View style={{flex: 1}}>
{this.props.children}
</View>
</TouchableHighlight>
);
}
}

View file

@ -1,108 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import ToolTip from 'app/components/tooltip';
export default class OptionsContext extends PureComponent {
static propTypes = {
getPostActions: PropTypes.func,
children: PropTypes.node.isRequired,
onPress: PropTypes.func.isRequired,
toggleSelected: PropTypes.func.isRequired,
};
static defaultProps = {
getPostActions: () => [],
additionalActions: [],
};
constructor(props) {
super(props);
this.state = {
actions: props.getPostActions(),
};
}
beforeShow = (additionalAction) => {
const nextActions = this.props.getPostActions();
if (additionalAction && additionalAction.text && !additionalAction.nativeEvent) {
const copyPostIndex = nextActions.findIndex((action) => action.copyPost);
nextActions.splice(copyPostIndex + 1, 0, additionalAction);
}
this.setState({
actions: nextActions,
});
};
handleHide = () => {
this.isShowing = false;
this.props.toggleSelected(false);
};
handleHideUnderlay = () => {
if (!this.isShowing) {
this.props.toggleSelected(false);
}
};
handleShow = () => {
this.isShowing = true;
this.props.toggleSelected(true);
}
handleShowUnderlay = () => {
this.beforeShow();
this.props.toggleSelected(true);
};
hide = () => {
this.setState({
actions: this.props.getPostActions(),
});
if (this.refs.toolTip) {
this.refs.toolTip.hideMenu();
}
this.isShowing = false;
this.props.toggleSelected(false);
};
handlePress = () => {
this.props.toggleSelected(false);
this.props.onPress();
};
show = (additionalAction) => {
this.props.toggleSelected(true);
this.beforeShow(additionalAction);
if (this.refs.toolTip) {
this.refs.toolTip.showMenu();
}
};
render() {
return (
<ToolTip
onHideUnderlay={this.handleHideUnderlay}
onShowUnderlay={this.handleShowUnderlay}
ref='toolTip'
actions={this.state.actions}
arrowDirection='down'
longPress={true}
onHide={this.handleHide}
onShow={this.handleShow}
onPress={this.handlePress}
underlayColor='transparent'
>
{this.props.children}
</ToolTip>
);
}
}

View file

@ -4,19 +4,15 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {createPost, deletePost, removePost} from 'mattermost-redux/actions/posts';
import {General, Posts} from 'mattermost-redux/constants';
import {getCurrentChannelId, isCurrentChannelReadOnly} from 'mattermost-redux/selectors/entities/channels';
import {createPost, removePost} from 'mattermost-redux/actions/posts';
import {Posts} from 'mattermost-redux/constants';
import {isCurrentChannelReadOnly} from 'mattermost-redux/selectors/entities/channels';
import {getPost, makeGetCommentCountForPost} from 'mattermost-redux/selectors/entities/posts';
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {getMyPreferences, getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentTeamUrl, getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {canDeletePost, canEditPost, isPostFlagged, isSystemMessage} from 'mattermost-redux/utils/post_utils';
import {isAdmin as checkIsAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {isPostFlagged, isSystemMessage} from 'mattermost-redux/utils/post_utils';
import {insertToDraft, setPostTooltipVisible} from 'app/actions/views/channel';
import {addReaction} from 'app/actions/views/emoji';
import Post from './post';
@ -45,13 +41,8 @@ function makeMapStateToProps() {
const getCommentCountForPost = makeGetCommentCountForPost();
return function mapStateToProps(state, ownProps) {
const post = getPost(state, ownProps.postId);
const config = getConfig(state);
const license = getLicense(state);
const roles = getCurrentUserId(state) ? getCurrentUserRoles(state) : '';
const myPreferences = getMyPreferences(state);
const currentUserId = getCurrentUserId(state);
const currentTeamId = getCurrentTeamId(state);
const currentChannelId = getCurrentChannelId(state);
let isFirstReply = true;
let isLastReply = true;
@ -78,28 +69,8 @@ function makeMapStateToProps() {
}
}
const isAdmin = checkIsAdmin(roles);
const isSystemAdmin = checkIsSystemAdmin(roles);
let canDelete = false;
let canEdit = false;
let canEditUntil = -1;
if (post) {
canDelete = canDeletePost(state, config, license, currentTeamId, currentChannelId, currentUserId, post, isAdmin, isSystemAdmin);
canEdit = canEditPost(state, config, license, currentTeamId, currentChannelId, currentUserId, post);
if (canEdit && license.IsLicensed === 'true' &&
(config.AllowEditPost === General.ALLOW_EDIT_POST_TIME_LIMIT || (config.PostEditTimeLimit !== -1 && config.PostEditTimeLimit !== '-1'))
) {
canEditUntil = post.create_at + (config.PostEditTimeLimit * 1000);
}
}
return {
channelIsReadOnly: isCurrentChannelReadOnly(state),
canDelete,
canEdit,
canEditUntil,
currentTeamUrl: getCurrentTeamUrl(state),
currentUserId,
post,
isFirstReply,
@ -116,9 +87,7 @@ function makeMapStateToProps() {
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
addReaction,
createPost,
deletePost,
removePost,
setPostTooltipVisible,
insertToDraft,

View file

@ -4,15 +4,12 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Alert,
Clipboard,
Platform,
TouchableHighlight,
View,
ViewPropTypes,
} from 'react-native';
import {intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import FlagIcon from 'app/components/flag_icon';
import PostBody from 'app/components/post_body';
@ -22,7 +19,6 @@ import {NavigationTypes} from 'app/constants';
import {fromAutoResponder} from 'app/utils/general';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {getToolTipVisible} from 'app/utils/tooltip';
import {t} from 'app/utils/i18n';
import {Posts} from 'mattermost-redux/constants';
@ -34,14 +30,11 @@ import Config from 'assets/config';
export default class Post extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
addReaction: PropTypes.func.isRequired,
createPost: PropTypes.func.isRequired,
deletePost: PropTypes.func.isRequired,
insertToDraft: PropTypes.func.isRequired,
removePost: PropTypes.func.isRequired,
}).isRequired,
channelIsReadOnly: PropTypes.bool,
currentTeamUrl: PropTypes.string.isRequired,
currentUserId: PropTypes.string.isRequired,
highlight: PropTypes.bool,
style: ViewPropTypes.style,
@ -56,9 +49,6 @@ export default class Post extends PureComponent {
commentedOnPost: PropTypes.object,
managedConfig: PropTypes.object.isRequired,
navigator: PropTypes.object,
canEdit: PropTypes.bool.isRequired,
canEditUntil: PropTypes.number.isRequired,
canDelete: PropTypes.bool.isRequired,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
shouldRenderReplyButton: PropTypes.bool,
@ -112,83 +102,6 @@ export default class Post extends PureComponent {
this.props.actions.insertToDraft(`@${username} `);
};
handlePostDelete = () => {
const {formatMessage} = this.context.intl;
const {actions, currentUserId, post} = this.props;
Alert.alert(
formatMessage({id: 'mobile.post.delete_title', defaultMessage: 'Delete Post'}),
formatMessage({
id: 'mobile.post.delete_question',
defaultMessage: 'Are you sure you want to delete this post?',
}),
[{
text: formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
style: 'cancel',
}, {
text: formatMessage({id: 'post_info.del', defaultMessage: 'Delete'}),
style: 'destructive',
onPress: () => {
actions.deletePost(post);
if (post.user_id === currentUserId) {
actions.removePost(post);
}
},
}]
);
};
handlePostEdit = () => {
const {intl} = this.context;
const {navigator, post, theme} = this.props;
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).then((source) => {
navigator.showModal({
screen: 'EditPost',
title: intl.formatMessage({id: 'mobile.edit_post.title', defaultMessage: 'Editing Message'}),
animated: true,
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
post,
closeButton: source,
},
});
});
};
handleAddReactionToPost = (emoji) => {
const {post} = this.props;
this.props.actions.addReaction(post.id, emoji);
};
handleAddReaction = preventDoubleTap(() => {
const {intl} = this.context;
const {navigator, post, theme} = this.props;
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).then((source) => {
navigator.showModal({
screen: 'AddReaction',
title: intl.formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'}),
animated: true,
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
post,
closeButton: source,
onEmojiPress: this.handleAddReactionToPost,
},
});
});
});
handleFailedPostPress = () => {
const options = {
title: {
@ -246,22 +159,17 @@ export default class Post extends PureComponent {
showLongPost,
} = this.props;
if (!getToolTipVisible()) {
const isValidSystemMessage = fromAutoResponder(post) || !isSystemMessage(post);
if (onPress && post.state !== Posts.POST_DELETED && isValidSystemMessage && !isPostPendingOrFailed(post)) {
onPress(post);
} else if ((isPostEphemeral(post) || post.state === Posts.POST_DELETED) && !showLongPost) {
this.onRemovePost(post);
}
} else if (this.refs.postBody) {
this.refs.postBody.getWrappedInstance().hideOptionsContext();
this.handleHideUnderlay();
const isValidSystemMessage = fromAutoResponder(post) || !isSystemMessage(post);
if (onPress && post.state !== Posts.POST_DELETED && isValidSystemMessage && !isPostPendingOrFailed(post)) {
onPress(post);
} else if ((isPostEphemeral(post) || post.state === Posts.POST_DELETED) && !showLongPost) {
this.onRemovePost(post);
}
});
handleReply = preventDoubleTap(() => {
const {post, onReply} = this.props;
if (!getToolTipVisible() && onReply) {
if (onReply) {
return onReply(post);
}
@ -305,31 +213,13 @@ export default class Post extends PureComponent {
};
viewUserProfile = preventDoubleTap(() => {
if (!getToolTipVisible()) {
this.goToUserProfile();
}
this.goToUserProfile();
});
toggleSelected = (selected) => {
this.setState({selected});
};
handleCopyText = (text) => {
let textToCopy = this.props.post.message;
if (typeof text === 'string') {
textToCopy = text;
}
Clipboard.setString(textToCopy);
};
handleCopyPermalink = () => {
const {currentTeamUrl, postId} = this.props;
const permalink = `${currentTeamUrl}/pl/${postId}`;
Clipboard.setString(permalink);
};
handleHideUnderlay = () => {
this.toggleSelected(false);
};
@ -338,9 +228,9 @@ export default class Post extends PureComponent {
this.toggleSelected(true);
};
showOptionsContext = () => {
showPostOptions = () => {
if (this.refs.postBody) {
this.refs.postBody.getWrappedInstance().showOptionsContext();
this.refs.postBody.getWrappedInstance().showPostOptions();
}
};
@ -405,7 +295,7 @@ export default class Post extends PureComponent {
onPress={this.handlePress}
onHideUnderlay={this.handleHideUnderlay}
onShowUnderlay={this.handleShowUnderlay}
onLongPress={this.showOptionsContext}
onLongPress={this.showPostOptions}
underlayColor='transparent'
>
<PostProfilePicture
@ -440,21 +330,13 @@ export default class Post extends PureComponent {
{postHeader}
<PostBody
ref={'postBody'}
canDelete={this.props.canDelete}
canEdit={this.props.canEdit}
canEditUntil={this.props.canEditUntil}
highlight={highlight}
channelIsReadOnly={channelIsReadOnly}
isSearchResult={isSearchResult}
navigator={this.props.navigator}
onAddReaction={this.handleAddReaction}
onCopyPermalink={this.handleCopyPermalink}
onCopyText={this.handleCopyText}
onFailedPostPress={this.handleFailedPostPress}
onHashtagPress={onHashtagPress}
onPermalinkPress={onPermalinkPress}
onPostDelete={this.handlePostDelete}
onPostEdit={this.handlePostEdit}
onPress={this.handlePress}
postId={post.id}
replyBarStyle={replyBarStyle}

View file

@ -31,7 +31,6 @@ export default class PostAddChannelMember extends React.PureComponent {
userIds: PropTypes.array.isRequired,
usernames: PropTypes.array.isRequired,
navigator: PropTypes.object.isRequired,
onLongPress: PropTypes.func,
onPostPress: PropTypes.func,
textStyles: PropTypes.object,
};
@ -85,7 +84,6 @@ export default class PostAddChannelMember extends React.PureComponent {
<AtMention
mentionStyle={this.props.textStyles.mention}
mentionName={usernames[0]}
onLongPress={this.props.onLongPress}
onPostPress={this.props.onPostPress}
navigator={this.props.navigator}
/>
@ -114,7 +112,6 @@ export default class PostAddChannelMember extends React.PureComponent {
key={username}
mentionStyle={this.props.textStyles.mention}
mentionName={username}
onLongPress={this.props.onLongPress}
onPostPress={this.props.onPostPress}
navigator={this.props.navigator}
/>
@ -192,4 +189,4 @@ export default class PostAddChannelMember extends React.PureComponent {
</Text>
);
}
}
}

View file

@ -2,27 +2,19 @@
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {flagPost, unflagPost} from 'mattermost-redux/actions/posts';
import {
General,
Posts,
} from 'mattermost-redux/constants';
import {General, Posts} from 'mattermost-redux/constants';
import {getChannel, canManageChannelMembers, getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {getPost} from 'mattermost-redux/selectors/entities/posts';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {hasNewPermissions, getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import Permissions from 'mattermost-redux/constants/permissions';
import {
isEdited,
isPostEphemeral,
isSystemMessage,
canEditPost,
canDeletePost,
} from 'mattermost-redux/utils/post_utils';
import {isAdmin as checkIsAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils';
@ -34,19 +26,6 @@ const POST_TIMEOUT = 20000;
function mapStateToProps(state, ownProps) {
const post = getPost(state, ownProps.postId) || {};
const channel = getChannel(state, post.channel_id) || {};
const channelIsArchived = channel ? channel.delete_at !== 0 : false;
const teamId = channel.team_id;
let canAddReaction = true;
if (channelIsArchived) {
canAddReaction = false;
} else if (hasNewPermissions(state)) {
canAddReaction = haveIChannelPermission(state, {
team: teamId,
channel: post.channel_id,
permission: Permissions.ADD_REACTION,
});
}
let isFailed = post.failed;
let isPending = post.id === post.pending_post_id;
@ -69,12 +48,9 @@ function mapStateToProps(state, ownProps) {
const isAdmin = checkIsAdmin(roles);
const isSystemAdmin = checkIsSystemAdmin(roles);
let canDelete = false;
let canEdit = false;
if (post) {
if (!channelIsArchived) {
canDelete = canDeletePost(state, config, license, currentTeamId, currentChannelId, currentUserId, post, isAdmin, isSystemAdmin);
canEdit = canEditPost(state, config, license, currentTeamId, currentChannelId, currentUserId, post);
}
if (post && !ownProps.channelIsArchived) {
canDelete = canDeletePost(state, config, license, currentTeamId, currentChannelId, currentUserId, post, isAdmin, isSystemAdmin);
}
let isPostAddChannelMember = false;
@ -103,19 +79,8 @@ function mapStateToProps(state, ownProps) {
isSystemMessage: isSystemMessage(post),
message: post.message,
theme: getTheme(state),
canAddReaction,
canDelete,
canEdit,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
flagPost,
unflagPost,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps, null, {withRef: true})(PostBody);
export default connect(mapStateToProps, null, null, {withRef: true})(PostBody);

View file

@ -5,7 +5,6 @@ import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Dimensions,
Platform,
TouchableHighlight,
TouchableOpacity,
View,
@ -18,7 +17,6 @@ import {Posts} from 'mattermost-redux/constants';
import CombinedSystemMessage from 'app/components/combined_system_message';
import FormattedText from 'app/components/formatted_text';
import Markdown from 'app/components/markdown';
import OptionsContext from 'app/components/options_context';
import ShowMoreButton from 'app/components/show_more_button';
import {emptyFunction} from 'app/utils/general';
@ -33,14 +31,7 @@ let Reactions;
export default class PostBody extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
flagPost: PropTypes.func.isRequired,
unflagPost: PropTypes.func.isRequired,
}).isRequired,
canAddReaction: PropTypes.bool,
canDelete: PropTypes.bool,
canEdit: PropTypes.bool,
canEditUntil: PropTypes.number.isRequired,
channelIsReadOnly: PropTypes.bool.isRequired,
fileIds: PropTypes.array,
hasBeenDeleted: PropTypes.bool,
@ -58,14 +49,9 @@ export default class PostBody extends PureComponent {
managedConfig: PropTypes.object,
message: PropTypes.string,
navigator: PropTypes.object.isRequired,
onAddReaction: PropTypes.func,
onCopyPermalink: PropTypes.func,
onCopyText: PropTypes.func,
onFailedPostPress: PropTypes.func,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
onPostDelete: PropTypes.func,
onPostEdit: PropTypes.func,
onPress: PropTypes.func,
postId: PropTypes.string.isRequired,
postProps: PropTypes.object,
@ -79,12 +65,7 @@ export default class PostBody extends PureComponent {
static defaultProps = {
fileIds: [],
onAddReaction: emptyFunction,
onCopyPermalink: emptyFunction,
onCopyText: emptyFunction,
onFailedPostPress: emptyFunction,
onPostDelete: emptyFunction,
onPostEdit: emptyFunction,
onPress: emptyFunction,
replyBarStyle: [],
toggleSelected: emptyFunction,
@ -98,11 +79,6 @@ export default class PostBody extends PureComponent {
isLongPost: false,
};
flagPost = () => {
const {actions, postId} = this.props;
actions.flagPost(postId);
};
handleHideUnderlay = () => {
this.props.toggleSelected(false);
};
@ -111,84 +87,6 @@ export default class PostBody extends PureComponent {
this.props.toggleSelected(true);
};
hideOptionsContext = () => {
if (Platform.OS === 'ios' && this.refs.options) {
this.refs.options.hide();
}
};
getPostActions = () => {
const {formatMessage} = this.context.intl;
const {
canEdit,
canEditUntil,
canDelete,
canAddReaction,
channelIsReadOnly,
hasBeenDeleted,
isPending,
isFailed,
isFlagged,
isPostEphemeral,
isSystemMessage,
managedConfig,
message,
onCopyText,
onPostDelete,
onPostEdit,
showAddReaction,
} = this.props;
const actions = [];
const isPendingOrFailedPost = isPending || isFailed;
// we should check for the user roles and permissions
if (!isPendingOrFailedPost && !isSystemMessage && !isPostEphemeral) {
if (showAddReaction && canAddReaction && !channelIsReadOnly) {
actions.push({
text: formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'}),
onPress: this.props.onAddReaction,
});
}
if (managedConfig.copyAndPasteProtection !== 'true' && message) {
actions.push({
text: formatMessage({id: 'mobile.post_info.copy_post', defaultMessage: 'Copy Post'}),
onPress: onCopyText,
copyPost: true,
});
}
if (!channelIsReadOnly) {
if (isFlagged) {
actions.push({
text: formatMessage({id: 'post_info.mobile.unflag', defaultMessage: 'Unflag'}),
onPress: this.unflagPost,
});
} else {
actions.push({
text: formatMessage({id: 'post_info.mobile.flag', defaultMessage: 'Flag'}),
onPress: this.flagPost,
});
}
}
if (canEdit && (canEditUntil === -1 || canEditUntil > Date.now())) {
actions.push({text: formatMessage({id: 'post_info.edit', defaultMessage: 'Edit'}), onPress: onPostEdit});
}
actions.push({
text: formatMessage({id: 'get_post_link_modal.title', defaultMessage: 'Copy Permalink'}),
onPress: this.props.onCopyPermalink,
});
}
if (!isPendingOrFailedPost && !isPostEphemeral && canDelete && !hasBeenDeleted) {
actions.push({text: formatMessage({id: 'post_info.del', defaultMessage: 'Delete'}), onPress: onPostDelete});
}
return actions;
};
measurePost = (event) => {
const {height} = event.nativeEvent.layout;
const {height: deviceHeight} = Dimensions.get('window');
@ -206,7 +104,6 @@ export default class PostBody extends PureComponent {
const {
managedConfig,
navigator,
onAddReaction,
onHashtagPress,
onPermalinkPress,
postId,
@ -225,7 +122,6 @@ export default class PostBody extends PureComponent {
passProps: {
postId,
managedConfig,
onAddReaction,
onHashtagPress,
onPermalinkPress,
},
@ -234,15 +130,51 @@ export default class PostBody extends PureComponent {
navigator.showModal(options);
});
unflagPost = () => {
const {actions, postId} = this.props;
actions.unflagPost(postId);
};
showPostOptions = () => {
const {
canDelete,
channelIsReadOnly,
hasBeenDeleted,
isFailed,
isFlagged,
isPending,
isPostEphemeral,
isSystemMessage,
managedConfig,
navigator,
postId,
showAddReaction,
} = this.props;
showOptionsContext = (additionalAction) => {
if (this.refs.options) {
this.refs.options.show(additionalAction);
if (isSystemMessage && (!canDelete || hasBeenDeleted)) {
return;
}
if (isPending || isFailed || isPostEphemeral) {
return;
}
const options = {
screen: 'PostOptions',
animationType: 'none',
backButtonTitle: '',
navigatorStyle: {
navBarHidden: true,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
},
passProps: {
canDelete,
channelIsReadOnly,
hasBeenDeleted,
isFlagged,
postId,
managedConfig,
showAddReaction,
},
};
navigator.showModal(options);
};
renderAddChannelMember = (style, messageStyle, textStyles) => {
@ -258,7 +190,6 @@ export default class PostBody extends PureComponent {
<PostAddChannelMember
baseTextStyle={messageStyle}
navigator={navigator}
onLongPress={this.showOptionsContext}
onPostPress={onPress}
textStyles={textStyles}
postId={postProps.add_channel_member.post_id}
@ -293,9 +224,8 @@ export default class PostBody extends PureComponent {
attachments = (
<FileAttachmentList
fileIds={fileIds}
hideOptionsContext={this.hideOptionsContext}
isFailed={isFailed}
onLongPress={this.showOptionsContext}
onLongPress={this.showPostOptions}
postId={postId}
toggleSelected={toggleSelected}
navigator={navigator}
@ -323,7 +253,6 @@ export default class PostBody extends PureComponent {
textStyles={textStyles}
isReplyPost={isReplyPost}
onHashtagPress={onHashtagPress}
onLongPress={this.showOptionsContext}
onPermalinkPress={onPermalinkPress}
/>
);
@ -334,7 +263,6 @@ export default class PostBody extends PureComponent {
hasReactions,
isSearchResult,
navigator,
onAddReaction,
postId,
showLongPost,
} = this.props;
@ -350,14 +278,12 @@ export default class PostBody extends PureComponent {
return (
<Reactions
postId={postId}
onAddReaction={onAddReaction}
navigator={navigator}
/>
);
};
render() {
const {formatMessage} = this.context.intl;
const {
hasBeenDeleted,
hasBeenEdited,
@ -378,7 +304,6 @@ export default class PostBody extends PureComponent {
postType,
replyBarStyle,
theme,
toggleSelected,
} = this.props;
const {isLongPost, maxHeight} = this.state;
const style = getStyleSheet(theme);
@ -412,7 +337,7 @@ export default class PostBody extends PureComponent {
} else if (postType === Posts.POST_TYPES.COMBINED_USER_ACTIVITY) {
const {allUserIds, allUsernames, messageData} = postProps.user_activity;
messageComponent = (
<TouchableOpacity onLongPress={this.showOptionsContext}>
<TouchableOpacity onLongPress={this.showPostOptions}>
<View style={style.row}>
<View style={style.flex}>
<CombinedSystemMessage
@ -443,7 +368,6 @@ export default class PostBody extends PureComponent {
isSearchResult={isSearchResult}
navigator={navigator}
onHashtagPress={onHashtagPress}
onLongPress={this.showOptionsContext}
onPermalinkPress={onPermalinkPress}
onPostPress={onPress}
textStyles={textStyles}
@ -456,14 +380,14 @@ export default class PostBody extends PureComponent {
if (!hasBeenDeleted) {
body = (
<View style={style.messageBody}>
<OptionsContext
getPostActions={this.getPostActions}
ref='options'
onPress={onPress}
toggleSelected={toggleSelected}
cancelText={formatMessage({id: 'channel_modal.cancel', defaultMessage: 'Cancel'})}
>
<TouchableHighlight
onHideUnderlay={this.handleHideUnderlay}
onPress={onPress}
onLongPress={this.showPostOptions}
onShowUnderlay={this.handleShowUnderlay}
underlayColor='transparent'
>
<View style={style.messageBody}>
<View onLayout={this.measurePost}>
{messageComponent}
{isLongPost &&
@ -476,8 +400,8 @@ export default class PostBody extends PureComponent {
{this.renderPostAdditionalContent(blockStyles, messageStyle, textStyles)}
{this.renderFileAttachments()}
{this.renderReactions()}
</OptionsContext>
</View>
</View>
</TouchableHighlight>
);
}

View file

@ -19,7 +19,6 @@ import {intlShape} from 'react-intl';
import ProgressiveImage from 'app/components/progressive_image';
import CustomPropTypes from 'app/constants/custom_prop_types';
import {emptyFunction} from 'app/utils/general';
import ImageCacheManager from 'app/utils/image_cache_manager';
import {previewImageAtIndex, calculateDimensions} from 'app/utils/images';
import {getYouTubeVideoId, isImageLink, isYoutubeLink} from 'app/utils/url';
@ -45,7 +44,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
message: PropTypes.string.isRequired,
navigator: PropTypes.object.isRequired,
onHashtagPress: PropTypes.func,
onLongPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
openGraphData: PropTypes.object,
postId: PropTypes.string.isRequired,
@ -55,10 +53,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
theme: PropTypes.object.isRequired,
};
static defaultProps = {
onLongPress: emptyFunction,
};
static contextTypes = {
intl: intlShape.isRequired,
};
@ -311,7 +305,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
textStyles={textStyles}
theme={theme}
onHashtagPress={onHashtagPress}
onLongPress={this.props.onLongPress}
onPermalinkPress={onPermalinkPress}
/>
);

View file

@ -9,7 +9,10 @@ import {
TouchableOpacity,
View,
} from 'react-native';
import {intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import addReactionIcon from 'assets/images/icons/reaction.png';
@ -24,7 +27,6 @@ export default class Reactions extends PureComponent {
}).isRequired,
highlightedReactions: PropTypes.array.isRequired,
navigator: PropTypes.object.isRequired,
onAddReaction: PropTypes.func.isRequired,
position: PropTypes.oneOf(['right', 'left']),
postId: PropTypes.string.isRequired,
reactions: PropTypes.object.isRequired,
@ -37,11 +39,43 @@ export default class Reactions extends PureComponent {
position: 'right',
};
static contextTypes = {
intl: intlShape.isRequired,
};
componentDidMount() {
const {actions, postId} = this.props;
actions.getReactionsForPost(postId);
}
handleAddReaction = preventDoubleTap(() => {
const {formatMessage} = this.context.intl;
const {navigator, theme} = this.props;
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).then((source) => {
navigator.showModal({
screen: 'AddReaction',
title: formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'}),
animated: true,
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
closeButton: source,
onEmojiPress: this.handleAddReactionToPost,
},
});
});
});
handleAddReactionToPost = (emoji) => {
const {postId} = this.props;
this.props.actions.addReaction(postId, emoji);
};
handleReactionPress = (emoji, remove) => {
const {actions, postId} = this.props;
if (remove && this.props.canRemoveReaction) {
@ -104,7 +138,7 @@ export default class Reactions extends PureComponent {
addMoreReactions = (
<TouchableOpacity
key='addReaction'
onPress={this.props.onAddReaction}
onPress={this.handleAddReaction}
style={[styles.reaction]}
>
<Image

View file

@ -15,14 +15,15 @@ import {DeviceTypes} from 'app/constants';
import SlideUpPanelIndicator from './slide_up_panel_indicator';
export const BOTTOM_MARGIN = DeviceTypes.IS_IPHONE_X ? 24 : 0;
const TOP_IOS_MARGIN = DeviceTypes.IS_IPHONE_X ? 84 : 64;
const TOP_ANDROID_MARGIN = 44;
const TOP_MARGIN = Platform.OS === 'ios' ? TOP_IOS_MARGIN : TOP_ANDROID_MARGIN;
const BOTTOM_MARGIN = DeviceTypes.IS_IPHONE_X ? 24 : 0;
const CONTAINER_MARGIN = TOP_MARGIN - 10;
export default class SlideUpPanel extends PureComponent {
static propTypes = {
alwaysCaptureContainerMove: PropTypes.bool,
containerHeight: PropTypes.number,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
@ -45,14 +46,28 @@ export default class SlideUpPanel extends PureComponent {
super(props);
const initialUsedSpace = Math.abs(props.initialPosition);
const initialPosition = ((props.containerHeight - (props.headerHeight + BOTTOM_MARGIN)) * (1 - initialUsedSpace));
let initialPosition;
if (initialUsedSpace <= 1) {
initialPosition = ((props.containerHeight - (props.headerHeight + BOTTOM_MARGIN)) * (1 - initialUsedSpace));
} else {
initialPosition = ((props.containerHeight - (props.headerHeight + BOTTOM_MARGIN)) - initialUsedSpace);
}
this.mainPanGesture = PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => {
onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
if (this.props.alwaysCaptureContainerMove) {
return gestureState.dy !== 0;
}
const isGoingDown = gestureState.y0 < gestureState.dy;
return this.isAValidMovement(gestureState.dx, gestureState.dy, isGoingDown);
},
onPanResponderMove: (evt, gestureState) => {
const isGoingDown = gestureState.dy > 0;
if (this.props.alwaysCaptureContainerMove &&
!this.isAValidMovement(gestureState.dx, gestureState.dy, isGoingDown)) {
return;
}
this.moveStart(gestureState);
},
onPanResponderRelease: (evt, gestureState) => {

View file

@ -46,6 +46,7 @@ export function registerScreens(store, Provider) {
Navigation.registerComponent('NotificationSettingsMobile', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_mobile').default), store, Provider);
Navigation.registerComponent('OptionsModal', () => wrapWithContextProvider(require('app/screens/options_modal').default), store, Provider);
Navigation.registerComponent('Permalink', () => wrapWithContextProvider(require('app/screens/permalink').default), store, Provider);
Navigation.registerComponent('PostOptions', () => wrapWithContextProvider(require('app/screens/post_options').default), store, Provider);
Navigation.registerComponent('ReactionList', () => wrapWithContextProvider(require('app/screens/reaction_list').default), store, Provider);
Navigation.registerComponent('RecentMentions', () => wrapWithContextProvider(require('app/screens/recent_mentions').default), store, Provider);
Navigation.registerComponent('Root', () => require('app/screens/root').default, store, Provider);

View file

@ -51,7 +51,6 @@ export default class LongPost extends PureComponent {
inThreadView: PropTypes.bool,
managedConfig: PropTypes.object,
navigator: PropTypes.object,
onAddReaction: PropTypes.func,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
postId: PropTypes.string.isRequired,
@ -141,7 +140,6 @@ export default class LongPost extends PureComponent {
<View style={style.attachments}>
<FileAttachmentList
fileIds={fileIds}
hideOptionsContext={emptyFunction}
isFailed={false}
onLongPress={emptyFunction}
postId={postId}
@ -155,7 +153,7 @@ export default class LongPost extends PureComponent {
}
renderReactions = (style) => {
const {hasReactions, postId, onAddReaction} = this.props;
const {hasReactions, navigator, postId} = this.props;
if (!hasReactions) {
return null;
@ -164,9 +162,9 @@ export default class LongPost extends PureComponent {
return (
<View style={style.reactions}>
<Reactions
navigator={navigator}
position='left'
postId={postId}
onAddReaction={onAddReaction}
/>
</View>
);

View file

@ -0,0 +1,81 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {deletePost, flagPost, unflagPost, removePost} from 'mattermost-redux/actions/posts';
import {General, Permissions} from 'mattermost-redux/constants';
import {getChannel, getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {getConfig, getLicense, hasNewPermissions} from 'mattermost-redux/selectors/entities/general';
import {getPost} from 'mattermost-redux/selectors/entities/posts';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles';
import {getCurrentTeamId, getCurrentTeamUrl} from 'mattermost-redux/selectors/entities/teams';
import {canEditPost} from 'mattermost-redux/utils/post_utils';
import {addReaction} from 'app/actions/views/emoji';
import {getDimensions} from 'app/selectors/device';
import PostOptions from './post_options';
function mapStateToProps(state, ownProps) {
const post = getPost(state, ownProps.postId);
const channel = getChannel(state, post.channel_id);
const config = getConfig(state);
const license = getLicense(state);
const currentUserId = getCurrentUserId(state);
const currentTeamId = getCurrentTeamId(state);
const currentChannelId = getCurrentChannelId(state);
const channelIsArchived = channel.delete_at !== 0;
let canAddReaction = true;
let canEdit = false;
let canEditUntil = -1;
if (hasNewPermissions(state)) {
canAddReaction = haveIChannelPermission(state, {
team: currentTeamId,
channel: post.channel_id,
permission: Permissions.ADD_REACTION,
});
}
if (channelIsArchived) {
canAddReaction = false;
} else {
canEdit = canEditPost(state, config, license, currentTeamId, currentChannelId, currentUserId, post);
if (canEdit && license.IsLicensed === 'true' &&
(config.AllowEditPost === General.ALLOW_EDIT_POST_TIME_LIMIT || (config.PostEditTimeLimit !== -1 && config.PostEditTimeLimit !== '-1'))
) {
canEditUntil = post.create_at + (config.PostEditTimeLimit * 1000);
}
}
return {
...getDimensions(state),
canAddReaction,
canEdit,
canEditUntil,
currentTeamUrl: getCurrentTeamUrl(state),
isMyPost: currentUserId === post.user_id,
post,
theme: getTheme(state),
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
addReaction,
deletePost,
flagPost,
removePost,
unflagPost,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(PostOptions);

View file

@ -0,0 +1,106 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Image,
StyleSheet,
Text,
TouchableHighlight,
View,
} from 'react-native';
import copy from 'assets/images/post_menu/copy.png';
import edit from 'assets/images/post_menu/edit.png';
import emoji from 'assets/images/post_menu/emoji.png';
import flag from 'assets/images/post_menu/flag.png';
import link from 'assets/images/post_menu/link.png';
import trash from 'assets/images/post_menu/trash.png';
const icons = {
copy,
edit,
emoji,
flag,
link,
trash,
};
export default class PostOption extends PureComponent {
static propTypes = {
destructive: PropTypes.bool,
icon: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
text: PropTypes.string.isRequired,
};
render() {
const {destructive, icon, onPress, text} = this.props;
const image = icons[icon];
return (
<View style={style.container} >
<TouchableHighlight
onPress={onPress}
underlayColor='rgba(0, 0, 0, 0.05)'
style={style.flex}
>
<View style={style.row}>
<View style={style.icon}>
<Image source={image}/>
</View>
<View style={style.textContainer}>
<Text style={[style.text, destructive ? style.destructive : null]}>
{text}
</Text>
</View>
</View>
</TouchableHighlight>
<View style={style.footer}/>
</View>
);
}
}
const style = StyleSheet.create({
container: {
height: 51,
width: '100%',
},
flex: {
flex: 1,
},
destructive: {
color: '#D0021B',
},
row: {
flex: 1,
flexDirection: 'row',
},
icon: {
alignItems: 'center',
height: 50,
justifyContent: 'center',
width: 60,
},
textContainer: {
justifyContent: 'center',
flex: 1,
height: 50,
marginRight: 5,
},
text: {
color: '#000000',
fontSize: 16,
lineHeight: 19,
opacity: 0.9,
letterSpacing: -0.45,
},
footer: {
height: 1,
marginLeft: 60,
borderBottomWidth: 1,
borderBottomColor: 'rgba(0, 0, 0 ,0.2)',
},
});

View file

@ -0,0 +1,358 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Alert, Clipboard, StyleSheet, View} from 'react-native';
import {intlShape} from 'react-intl';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import SlideUpPanel from 'app/components/slide_up_panel';
import {BOTTOM_MARGIN} from 'app/components/slide_up_panel/slide_up_panel';
import PostOption from './post_option';
const OPTION_HEIGHT = 50;
const INITIAL_OPTION_COUNT = 6;
export default class PostOptions extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
addReaction: PropTypes.func.isRequired,
deletePost: PropTypes.func.isRequired,
flagPost: PropTypes.func.isRequired,
removePost: PropTypes.func.isRequired,
unflagPost: PropTypes.func.isRequired,
}).isRequired,
additionalOption: PropTypes.object,
canAddReaction: PropTypes.bool,
canDelete: PropTypes.bool,
canEdit: PropTypes.bool,
canEditUntil: PropTypes.number.isRequired,
channelIsReadOnly: PropTypes.bool,
currentTeamUrl: PropTypes.string.isRequired,
deviceHeight: PropTypes.number.isRequired,
hasBeenDeleted: PropTypes.bool,
isFlagged: PropTypes.bool,
isMyPost: PropTypes.bool,
managedConfig: PropTypes.object.isRequired,
navigator: PropTypes.object.isRequired,
post: PropTypes.object.isRequired,
showAddReaction: PropTypes.bool,
theme: PropTypes.object.isRequired,
};
static contextTypes = {
intl: intlShape.isRequired,
};
close = () => {
this.props.navigator.dismissModal({
animationType: 'none',
});
};
closeWithAnimation = () => {
if (this.slideUpPanel) {
this.slideUpPanel.getWrappedInstance().handleTouchEnd();
} else {
this.close();
}
};
getAddReactionOption = () => {
const {formatMessage} = this.context.intl;
const {canAddReaction, channelIsReadOnly, showAddReaction} = this.props;
if (showAddReaction && canAddReaction && !channelIsReadOnly) {
return (
<PostOption
key='reaction'
icon='emoji'
text={formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'})}
onPress={this.handleAddReaction}
/>
);
}
return null;
};
getCopyPermalink = () => {
const {formatMessage} = this.context.intl;
return (
<PostOption
key='permalink'
icon='link'
text={formatMessage({id: 'get_post_link_modal.title', defaultMessage: 'Copy Permalink'})}
onPress={this.handleCopyPermalink}
/>
);
};
getCopyText = () => {
const {formatMessage} = this.context.intl;
const {managedConfig, post} = this.props;
if (managedConfig.copyAndPasteProtection !== 'true' && post.message) {
return (
<PostOption
key='copy'
icon='copy'
text={formatMessage({id: 'mobile.post_info.copy_text', defaultMessage: 'Copy Text'})}
onPress={this.handleCopyText}
/>
);
}
return null;
};
getDeleteOption = () => {
const {formatMessage} = this.context.intl;
const {canDelete, hasBeenDeleted} = this.props;
if (canDelete && !hasBeenDeleted) {
return (
<PostOption
destructive={true}
key='delete'
icon='trash'
text={formatMessage({id: 'post_info.del', defaultMessage: 'Delete'})}
onPress={this.handlePostDelete}
/>
);
}
return null;
};
getEditOption = () => {
const {formatMessage} = this.context.intl;
const {canEdit, canEditUntil} = this.props;
if (canEdit && (canEditUntil === -1 || canEditUntil > Date.now())) {
return (
<PostOption
key='edit'
icon='edit'
text={formatMessage({id: 'post_info.edit', defaultMessage: 'Edit'})}
onPress={this.handlePostEdit}
/>
);
}
return null;
};
getFlagOption = () => {
const {formatMessage} = this.context.intl;
const {channelIsReadOnly, isFlagged} = this.props;
if (channelIsReadOnly) {
return null;
}
if (isFlagged) {
return (
<PostOption
key='unflag'
icon='flag'
text={formatMessage({id: 'post_info.mobile.unflag', defaultMessage: 'Unflag'})}
onPress={this.handleUnflagPost}
/>
);
}
return (
<PostOption
key='flagged'
icon='flag'
text={formatMessage({id: 'post_info.mobile.flag', defaultMessage: 'Flag'})}
onPress={this.handleFlagPost}
/>
);
};
getMyPostOptions = () => {
const actions = [
this.getEditOption(),
this.getFlagOption(),
this.getAddReactionOption(),
this.getCopyPermalink(),
this.getCopyText(),
this.getDeleteOption(),
];
return actions.filter((a) => a !== null);
};
getOthersPostOptions = () => {
const actions = [
this.getFlagOption(),
this.getAddReactionOption(),
this.getCopyPermalink(),
this.getCopyText(),
this.getEditOption(),
this.getDeleteOption(),
];
return actions.filter((a) => a !== null);
};
getPostOptions = () => {
const {isMyPost} = this.props;
if (isMyPost) {
return this.getMyPostOptions();
}
return this.getOthersPostOptions();
};
handleAddReaction = () => {
const {formatMessage} = this.context.intl;
const {navigator, theme} = this.props;
this.close();
requestAnimationFrame(() => {
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).then((source) => {
navigator.showModal({
screen: 'AddReaction',
title: formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'}),
animated: true,
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
closeButton: source,
onEmojiPress: this.handleAddReactionToPost,
},
});
});
});
};
handleAddReactionToPost = (emoji) => {
const {actions, post} = this.props;
actions.addReaction(post.id, emoji);
};
handleCopyPermalink = () => {
const {currentTeamUrl, post} = this.props;
const permalink = `${currentTeamUrl}/pl/${post.id}`;
Clipboard.setString(permalink);
this.closeWithAnimation();
};
handleCopyText = () => {
const {message} = this.props.post;
Clipboard.setString(message);
this.closeWithAnimation();
};
handleFlagPost = () => {
const {actions, post} = this.props;
actions.flagPost(post.id);
this.closeWithAnimation();
};
handlePostDelete = () => {
const {formatMessage} = this.context.intl;
const {actions, isMyPost, post} = this.props;
Alert.alert(
formatMessage({id: 'mobile.post.delete_title', defaultMessage: 'Delete Post'}),
formatMessage({
id: 'mobile.post.delete_question',
defaultMessage: 'Are you sure you want to delete this post?',
}),
[{
text: formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
style: 'cancel',
}, {
text: formatMessage({id: 'post_info.del', defaultMessage: 'Delete'}),
style: 'destructive',
onPress: () => {
actions.deletePost(post);
if (isMyPost) {
actions.removePost(post);
}
this.closeWithAnimation();
},
}]
);
};
handlePostEdit = () => {
const {intl} = this.context;
const {navigator, post, theme} = this.props;
this.close();
requestAnimationFrame(() => {
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).then((source) => {
navigator.showModal({
screen: 'EditPost',
title: intl.formatMessage({id: 'mobile.edit_post.title', defaultMessage: 'Editing Message'}),
animated: true,
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
post,
closeButton: source,
},
});
});
});
};
handleUnflagPost = () => {
const {actions, post} = this.props;
actions.unflagPost(post.id);
this.closeWithAnimation();
};
refSlideUpPanel = (r) => {
this.slideUpPanel = r;
};
render() {
const {deviceHeight} = this.props;
const options = this.getPostOptions();
const initialPosition = (INITIAL_OPTION_COUNT + 1) * OPTION_HEIGHT;
const marginFromTop = deviceHeight - BOTTOM_MARGIN - ((options.length + 1) * OPTION_HEIGHT);
return (
<View style={style.flex}>
<SlideUpPanel
alwaysCaptureContainerMove={true}
ref={this.refSlideUpPanel}
marginFromTop={marginFromTop}
onRequestClose={this.close}
initialPosition={initialPosition}
>
{options}
</SlideUpPanel>
</View>
);
}
}
const style = StyleSheet.create({
flex: {
flex: 1,
},
});

View file

@ -46,7 +46,6 @@ export default class ReactionList extends PureComponent {
this.contentOffsetY = -1;
this.state = {
canDrag: true,
allUserIds: getUniqueUserIds(reactions),
reactions,
reactionsByName,

View file

@ -0,0 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import RNBottomSheet from 'react-native-bottom-sheet';
export default RNBottomSheet;

View file

@ -0,0 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ActionSheetIOS} from 'react-native';
export default {
showBottomSheetWithOptions: (options, callback) => {
return ActionSheetIOS.showActionSheetWithOptions(options, callback);
},
showShareBottomSheetWithOptions: (options, failureCallback, successCallback) => {
return ActionSheetIOS.showShareActionSheetWithOptions(options, failureCallback, successCallback);
},
};

View file

@ -0,0 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import BottomSheet from './bottom_sheet';
export default BottomSheet;

View file

@ -330,7 +330,7 @@
"mobile.open_gm.error": "We couldn't open a group message with those users. Please check your connection and try again.",
"mobile.open_unknown_channel.error": "Unable to join the channel. Please reset the cache and try again.",
"mobile.post_info.add_reaction": "Add Reaction",
"mobile.post_info.copy_post": "Copy Post",
"mobile.post_info.copy_text": "Copy Text",
"mobile.post_textbox.empty.message": "You are trying to send an empty message.\nPlease make sure you have a message or at least one attached file.",
"mobile.post_textbox.empty.ok": "OK",
"mobile.post_textbox.empty.title": "Empty Message",

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -5,6 +5,7 @@
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
@ -28,9 +29,13 @@
375218501F4B9EE70035444B /* libRCTCameraRoll.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3752184F1F4B9E980035444B /* libRCTCameraRoll.a */; };
37ABD3C81F4CE142001FDE6B /* libART.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37ABD39C1F4CE13B001FDE6B /* libART.a */; };
382D94CF15EE4FC292C3F341 /* Foundation.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 51F5A483EA9F4A9A87ACFB59 /* Foundation.ttf */; };
3C0B333879CE4730843D8584 /* Roboto-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D08F54E10DF14AE4BBF4E2B4 /* Roboto-MediumItalic.ttf */; };
3D38ABA732A34A9BB3294F90 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 349FBA7338E74D9BBD709528 /* EvilIcons.ttf */; };
3D7B4E6EE6B948AAA6A1E4E6 /* Roboto-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C46342B0E5FD4D878AF3A129 /* Roboto-BoldItalic.ttf */; };
3F876A0DC6314E27B16C8A96 /* libRNReactNativeDocViewer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BAF8296411D4657B5A0E8F8 /* libRNReactNativeDocViewer.a */; };
420A7328E12C4B72AEF420CE /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EDC04CBCF81642219D199CBB /* Octicons.ttf */; };
460A48EA5C6C4D8B8D9A2C75 /* Roboto-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 031EF04FB2D14EEFAACBAA1A /* Roboto-Italic.ttf */; };
552835DCC0C24FC691EE6CAB /* Roboto-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8F0B22D2C9924FAFA7FB681C /* Roboto-LightItalic.ttf */; };
55C6561DDBBA45929D88B6D1 /* OpenSans-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 32AC3D4EA79E44738A6E9766 /* OpenSans-BoldItalic.ttf */; };
5A0920184BD344979BCFCD5C /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B89192186C764B9FA473403A /* libRCTVideo.a */; };
5CB86E9DFB94485CAA748DF3 /* YTPlayerView-iframe-player.html in Resources */ = {isa = PBXBuildFile; fileRef = 79CB6EBA24FE4ABFB0C155F0 /* YTPlayerView-iframe-player.html */; };
@ -38,6 +43,7 @@
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
62A8448264674B4D95A5A7C2 /* OpenSans-Semibold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C78A387124874496AD2C1466 /* OpenSans-Semibold.ttf */; };
69AC753E496743BABB7A7124 /* OpenSans-SemiboldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0E617BF0F36D4E738F51D169 /* OpenSans-SemiboldItalic.ttf */; };
71F30A436B5847DF9D319D15 /* Roboto-BlackItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7F54ABFAE6CE4A6DB11D1ED7 /* Roboto-BlackItalic.ttf */; };
74D116AD208A8D5600CF8A79 /* libRNKeychain.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 74D116AA208A8D3100CF8A79 /* libRNKeychain.a */; };
7F2691A11EE1DC6A007574FE /* libRNNotifications.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F2691A01EE1DC51007574FE /* libRNNotifications.a */; };
7F292A711E8AB73400A450A3 /* SplashScreenResource in Resources */ = {isa = PBXBuildFile; fileRef = 7F292A701E8AB73400A450A3 /* SplashScreenResource */; };
@ -62,7 +68,6 @@
7F5CA9A0208FE3B9004F91CE /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F5CA991208FE38F004F91CE /* libRNDocumentPicker.a */; };
7F642DF02093533300F3165E /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F642DED2093530B00F3165E /* libRNDeviceInfo.a */; };
7F642DF1209353A400F3165E /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F642DED2093530B00F3165E /* libRNDeviceInfo.a */; };
7F6877B31E7836070094B63F /* libToolTipMenu.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F6877B01E7835E50094B63F /* libToolTipMenu.a */; };
7F6C47A51FE87E8C00F5A912 /* PerformRequests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F6C47A41FE87E8C00F5A912 /* PerformRequests.m */; };
7F7D7F98201645E100D31155 /* libReactNativePermissions.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F7D7F87201645D300D31155 /* libReactNativePermissions.a */; };
7F95132F208646CE00616E3E /* 0155-keys.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F9512EA208646CD00616E3E /* 0155-keys.png */; };
@ -103,25 +108,20 @@
9358B95F95184EE0A4DCE629 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D4B1B363C2414DA19C1AC521 /* OpenSans-Bold.ttf */; };
A08D512E7ADC40CCAD055A9E /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BC977883E2624E05975CA65B /* OpenSans-Regular.ttf */; };
A9B746D2CFA9CEBFB8AE2B5B /* libPods-Mattermost.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 65FD5EA57EBAE06106094B2F /* libPods-Mattermost.a */; };
AA9605CFDA8E4E7CB8A041BF /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C5BD64DE829E455A997DCAD5 /* Roboto-Regular.ttf */; };
ABF5F93B1D0A47BAACEAC376 /* Roboto-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34B20A903038487E8D7DEA1E /* Roboto-Light.ttf */; };
C035DB50ED2045F09923FFAE /* MaterialCommunityIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 04AA5E8EF3B54735A11E3B95 /* MaterialCommunityIcons.ttf */; };
C337E8708D2845C6A8DBB47E /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2DCFD31D3F4A4154822AB532 /* Ionicons.ttf */; };
C99BB3F4E4564F01A531FBEA /* Roboto-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 09FA716C56874BDDA30FE8C4 /* Roboto-Medium.ttf */; };
D719A67137964F08BE47A5FC /* OpenSans-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3647DF63D6764CF093375861 /* OpenSans-ExtraBold.ttf */; };
DDE492F7425D451884DAA088 /* Roboto-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6EFF13DD24CE4E26953E598A /* Roboto-Black.ttf */; };
DE863F2E1E194A45A5B58FFA /* Roboto-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A734E00E7E184582A877F2B3 /* Roboto-Thin.ttf */; };
DEBCF44F1F46413BB407D316 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8606EB1EB7E349EF8248933E /* libz.tbd */; };
E052494CD6104A65840485E7 /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71E626D4980A4560B26F0E1C /* Roboto-Bold.ttf */; };
EC6AF34E03F647389D377064 /* Roboto-ThinItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 12D0B0E475FD46E29907305E /* Roboto-ThinItalic.ttf */; };
F006936FC2884C24A1321FC0 /* MaterialIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 356C9186FA374641A00EB2EA /* MaterialIcons.ttf */; };
F083DB472349411A8E6E7AAD /* OpenSans-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BE17F630DB5D41FD93F32D22 /* OpenSans-LightItalic.ttf */; };
F23C99AA5FA10E457A76803A /* libPods-MattermostTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4246DC09024BB33A3E491E25 /* libPods-MattermostTests.a */; };
DDE492F7425D451884DAA088 /* Roboto-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6EFF13DD24CE4E26953E598A /* Roboto-Black.ttf */; };
71F30A436B5847DF9D319D15 /* Roboto-BlackItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7F54ABFAE6CE4A6DB11D1ED7 /* Roboto-BlackItalic.ttf */; };
E052494CD6104A65840485E7 /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71E626D4980A4560B26F0E1C /* Roboto-Bold.ttf */; };
3D7B4E6EE6B948AAA6A1E4E6 /* Roboto-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C46342B0E5FD4D878AF3A129 /* Roboto-BoldItalic.ttf */; };
460A48EA5C6C4D8B8D9A2C75 /* Roboto-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 031EF04FB2D14EEFAACBAA1A /* Roboto-Italic.ttf */; };
ABF5F93B1D0A47BAACEAC376 /* Roboto-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34B20A903038487E8D7DEA1E /* Roboto-Light.ttf */; };
552835DCC0C24FC691EE6CAB /* Roboto-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8F0B22D2C9924FAFA7FB681C /* Roboto-LightItalic.ttf */; };
C99BB3F4E4564F01A531FBEA /* Roboto-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 09FA716C56874BDDA30FE8C4 /* Roboto-Medium.ttf */; };
3C0B333879CE4730843D8584 /* Roboto-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D08F54E10DF14AE4BBF4E2B4 /* Roboto-MediumItalic.ttf */; };
AA9605CFDA8E4E7CB8A041BF /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C5BD64DE829E455A997DCAD5 /* Roboto-Regular.ttf */; };
DE863F2E1E194A45A5B58FFA /* Roboto-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A734E00E7E184582A877F2B3 /* Roboto-Thin.ttf */; };
EC6AF34E03F647389D377064 /* Roboto-ThinItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 12D0B0E475FD46E29907305E /* Roboto-ThinItalic.ttf */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -468,20 +468,6 @@
remoteGlobalIDString = E72EC1401F7ABB5A0001BC90;
remoteInfo = "RNDeviceInfo-tvOS";
};
7F6877AF1E7835E50094B63F /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7F6877AA1E7835E50094B63F /* ToolTipMenu.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 4681C0211B05271A004D67D4;
remoteInfo = ToolTipMenu;
};
7F6877B11E7835E50094B63F /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7F6877AA1E7835E50094B63F /* ToolTipMenu.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 4681C02C1B05271A004D67D4;
remoteInfo = ToolTipMenuTests;
};
7F6FF6B4203499F400150948 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
@ -658,9 +644,12 @@
00E356EE1AD99517003FC87E /* MattermostTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MattermostTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* MattermostTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MattermostTests.m; sourceTree = "<group>"; };
031EF04FB2D14EEFAACBAA1A /* Roboto-Italic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Italic.ttf"; path = "../assets/fonts/Roboto-Italic.ttf"; sourceTree = "<group>"; };
04AA5E8EF3B54735A11E3B95 /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialCommunityIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = "<group>"; };
09FA716C56874BDDA30FE8C4 /* Roboto-Medium.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Medium.ttf"; path = "../assets/fonts/Roboto-Medium.ttf"; sourceTree = "<group>"; };
0A091BF1A3D04650AD306A0D /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = "<group>"; };
0E617BF0F36D4E738F51D169 /* OpenSans-SemiboldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-SemiboldItalic.ttf"; path = "../assets/fonts/OpenSans-SemiboldItalic.ttf"; sourceTree = "<group>"; };
12D0B0E475FD46E29907305E /* Roboto-ThinItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-ThinItalic.ttf"; path = "../assets/fonts/Roboto-ThinItalic.ttf"; sourceTree = "<group>"; };
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = "<group>"; };
139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* Mattermost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mattermost.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -678,6 +667,7 @@
2DCFD31D3F4A4154822AB532 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = "<group>"; };
32AC3D4EA79E44738A6E9766 /* OpenSans-BoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-BoldItalic.ttf"; path = "../assets/fonts/OpenSans-BoldItalic.ttf"; sourceTree = "<group>"; };
349FBA7338E74D9BBD709528 /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = "<group>"; };
34B20A903038487E8D7DEA1E /* Roboto-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Light.ttf"; path = "../assets/fonts/Roboto-Light.ttf"; sourceTree = "<group>"; };
356C9186FA374641A00EB2EA /* MaterialIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"; sourceTree = "<group>"; };
3647DF63D6764CF093375861 /* OpenSans-ExtraBold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-ExtraBold.ttf"; path = "../assets/fonts/OpenSans-ExtraBold.ttf"; sourceTree = "<group>"; };
3752184A1F4B9E980035444B /* RCTCameraRoll.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCameraRoll.xcodeproj; path = "../node_modules/react-native/Libraries/CameraRoll/RCTCameraRoll.xcodeproj"; sourceTree = "<group>"; };
@ -697,6 +687,8 @@
6561AEAC21CC40B8A72ABB93 /* OpenSans-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Light.ttf"; path = "../assets/fonts/OpenSans-Light.ttf"; sourceTree = "<group>"; };
65FD5EA57EBAE06106094B2F /* libPods-Mattermost.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Mattermost.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6BAF8296411D4657B5A0E8F8 /* libRNReactNativeDocViewer.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNReactNativeDocViewer.a; sourceTree = "<group>"; };
6EFF13DD24CE4E26953E598A /* Roboto-Black.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Black.ttf"; path = "../assets/fonts/Roboto-Black.ttf"; sourceTree = "<group>"; };
71E626D4980A4560B26F0E1C /* Roboto-Bold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Bold.ttf"; path = "../assets/fonts/Roboto-Bold.ttf"; sourceTree = "<group>"; };
74D116A3208A8C8400CF8A79 /* libRNKeychain.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libRNKeychain.a; sourceTree = BUILT_PRODUCTS_DIR; };
74D116A4208A8D3000CF8A79 /* RNKeychain.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNKeychain.xcodeproj; path = "../node_modules/react-native-keychain/RNKeychain.xcodeproj"; sourceTree = "<group>"; };
7535D128F00C4A47A182627E /* libRNCookieManagerIOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCookieManagerIOS.a; sourceTree = "<group>"; };
@ -713,11 +705,11 @@
7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPods-Mattermost.a"; path = "../../../../../../../Library/Developer/Xcode/DerivedData/Mattermost-czlinsdviifujheezzjvmisotjrm/Build/Products/Debug-iphonesimulator/libPods-Mattermost.a"; sourceTree = "<group>"; };
7F50C968203C6E80007CA374 /* SessionManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionManager.h; sourceTree = "<group>"; };
7F50C969203C6E80007CA374 /* SessionManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SessionManager.m; sourceTree = "<group>"; };
7F54ABFAE6CE4A6DB11D1ED7 /* Roboto-BlackItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-BlackItalic.ttf"; path = "../assets/fonts/Roboto-BlackItalic.ttf"; sourceTree = "<group>"; };
7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNDocumentPicker.xcodeproj; path = "../node_modules/react-native-document-picker/ios/RNDocumentPicker.xcodeproj"; sourceTree = "<group>"; };
7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = "<group>"; };
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = "<group>"; };
7F642DE72093530B00F3165E /* RNDeviceInfo.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNDeviceInfo.xcodeproj; path = "../node_modules/react-native-device-info/ios/RNDeviceInfo.xcodeproj"; sourceTree = "<group>"; };
7F6877AA1E7835E50094B63F /* ToolTipMenu.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ToolTipMenu.xcodeproj; path = "../node_modules/react-native-tooltip/ToolTipMenu.xcodeproj"; sourceTree = "<group>"; };
7F6C47A31FE87E8C00F5A912 /* PerformRequests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PerformRequests.h; sourceTree = "<group>"; };
7F6C47A41FE87E8C00F5A912 /* PerformRequests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PerformRequests.m; sourceTree = "<group>"; };
7F7D7F52201645D300D31155 /* ReactNativePermissions.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativePermissions.xcodeproj; path = "../node_modules/react-native-permissions/ios/ReactNativePermissions.xcodeproj"; sourceTree = "<group>"; };
@ -754,17 +746,22 @@
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
849D881A0372465294DE7315 /* RNSafeArea.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSafeArea.xcodeproj; path = "../node_modules/react-native-safe-area/ios/RNSafeArea.xcodeproj"; sourceTree = "<group>"; };
8606EB1EB7E349EF8248933E /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
8F0B22D2C9924FAFA7FB681C /* Roboto-LightItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-LightItalic.ttf"; path = "../assets/fonts/Roboto-LightItalic.ttf"; sourceTree = "<group>"; };
9263CF9B16054263B13EA23B /* libRNSafeArea.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSafeArea.a; sourceTree = "<group>"; };
9552041247E749308CE7B412 /* RNSentry.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSentry.xcodeproj; path = "../node_modules/react-native-sentry/ios/RNSentry.xcodeproj"; sourceTree = "<group>"; };
A2968536BEC74A4ABBB73BD9 /* RCTYouTube.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RCTYouTube.xcodeproj; path = "../node_modules/react-native-youtube/RCTYouTube.xcodeproj"; sourceTree = "<group>"; };
A734E00E7E184582A877F2B3 /* Roboto-Thin.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Thin.ttf"; path = "../assets/fonts/Roboto-Thin.ttf"; sourceTree = "<group>"; };
AC6EB561E1F64C17A69D2FAD /* Entypo.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Entypo.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"; sourceTree = "<group>"; };
ACC6B9FDC0AD45A6BFA4FBCD /* libBVLinearGradient.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libBVLinearGradient.a; sourceTree = "<group>"; };
B89192186C764B9FA473403A /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTVideo.a; sourceTree = "<group>"; };
BC977883E2624E05975CA65B /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Regular.ttf"; path = "../assets/fonts/OpenSans-Regular.ttf"; sourceTree = "<group>"; };
BE17F630DB5D41FD93F32D22 /* OpenSans-LightItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-LightItalic.ttf"; path = "../assets/fonts/OpenSans-LightItalic.ttf"; sourceTree = "<group>"; };
BFB7025EA936C1B5DC9725C2 /* Pods-Mattermost.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mattermost.release.xcconfig"; path = "Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost.release.xcconfig"; sourceTree = "<group>"; };
C46342B0E5FD4D878AF3A129 /* Roboto-BoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-BoldItalic.ttf"; path = "../assets/fonts/Roboto-BoldItalic.ttf"; sourceTree = "<group>"; };
C5BD64DE829E455A997DCAD5 /* Roboto-Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Regular.ttf"; path = "../assets/fonts/Roboto-Regular.ttf"; sourceTree = "<group>"; };
C78A387124874496AD2C1466 /* OpenSans-Semibold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Semibold.ttf"; path = "../assets/fonts/OpenSans-Semibold.ttf"; sourceTree = "<group>"; };
D0281D64B98143668D6AD42B /* RNLocalAuth.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNLocalAuth.xcodeproj; path = "../node_modules/react-native-local-auth/RNLocalAuth.xcodeproj"; sourceTree = "<group>"; };
D08F54E10DF14AE4BBF4E2B4 /* Roboto-MediumItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-MediumItalic.ttf"; path = "../assets/fonts/Roboto-MediumItalic.ttf"; sourceTree = "<group>"; };
D4B1B363C2414DA19C1AC521 /* OpenSans-Bold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Bold.ttf"; path = "../assets/fonts/OpenSans-Bold.ttf"; sourceTree = "<group>"; };
DF9DAAAA482343F3910A1A4C /* RCTVideo.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RCTVideo.xcodeproj; path = "../node_modules/react-native-video/ios/RCTVideo.xcodeproj"; sourceTree = "<group>"; };
EBA6063A99C141098D40C67A /* RNPasscodeStatus.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNPasscodeStatus.xcodeproj; path = "../node_modules/react-native-passcode-status/ios/RNPasscodeStatus.xcodeproj"; sourceTree = "<group>"; };
@ -773,18 +770,6 @@
EE671DF7637347CD8C069819 /* libRNDeviceInfo.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNDeviceInfo.a; sourceTree = "<group>"; };
F1F071EE85494E269A50AE88 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; };
FBBEC29EE2D3418D9AC33BD5 /* OpenSans-ExtraBoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-ExtraBoldItalic.ttf"; path = "../assets/fonts/OpenSans-ExtraBoldItalic.ttf"; sourceTree = "<group>"; };
6EFF13DD24CE4E26953E598A /* Roboto-Black.ttf */ = {isa = PBXFileReference; name = "Roboto-Black.ttf"; path = "../assets/fonts/Roboto-Black.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
7F54ABFAE6CE4A6DB11D1ED7 /* Roboto-BlackItalic.ttf */ = {isa = PBXFileReference; name = "Roboto-BlackItalic.ttf"; path = "../assets/fonts/Roboto-BlackItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
71E626D4980A4560B26F0E1C /* Roboto-Bold.ttf */ = {isa = PBXFileReference; name = "Roboto-Bold.ttf"; path = "../assets/fonts/Roboto-Bold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
C46342B0E5FD4D878AF3A129 /* Roboto-BoldItalic.ttf */ = {isa = PBXFileReference; name = "Roboto-BoldItalic.ttf"; path = "../assets/fonts/Roboto-BoldItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
031EF04FB2D14EEFAACBAA1A /* Roboto-Italic.ttf */ = {isa = PBXFileReference; name = "Roboto-Italic.ttf"; path = "../assets/fonts/Roboto-Italic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
34B20A903038487E8D7DEA1E /* Roboto-Light.ttf */ = {isa = PBXFileReference; name = "Roboto-Light.ttf"; path = "../assets/fonts/Roboto-Light.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
8F0B22D2C9924FAFA7FB681C /* Roboto-LightItalic.ttf */ = {isa = PBXFileReference; name = "Roboto-LightItalic.ttf"; path = "../assets/fonts/Roboto-LightItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
09FA716C56874BDDA30FE8C4 /* Roboto-Medium.ttf */ = {isa = PBXFileReference; name = "Roboto-Medium.ttf"; path = "../assets/fonts/Roboto-Medium.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
D08F54E10DF14AE4BBF4E2B4 /* Roboto-MediumItalic.ttf */ = {isa = PBXFileReference; name = "Roboto-MediumItalic.ttf"; path = "../assets/fonts/Roboto-MediumItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
C5BD64DE829E455A997DCAD5 /* Roboto-Regular.ttf */ = {isa = PBXFileReference; name = "Roboto-Regular.ttf"; path = "../assets/fonts/Roboto-Regular.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
A734E00E7E184582A877F2B3 /* Roboto-Thin.ttf */ = {isa = PBXFileReference; name = "Roboto-Thin.ttf"; path = "../assets/fonts/Roboto-Thin.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
12D0B0E475FD46E29907305E /* Roboto-ThinItalic.ttf */ = {isa = PBXFileReference; name = "Roboto-ThinItalic.ttf"; path = "../assets/fonts/Roboto-ThinItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -820,7 +805,6 @@
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */,
139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */,
7F43D63F1F6BFA19001FC614 /* libBVLinearGradient.a in Frameworks */,
7F6877B31E7836070094B63F /* libToolTipMenu.a in Frameworks */,
7FC200E81EBB65370099331B /* libReactNativeNavigation.a in Frameworks */,
7F2691A11EE1DC6A007574FE /* libRNNotifications.a in Frameworks */,
7F43D5D61F6BF8C2001FC614 /* libRNLocalAuth.a in Frameworks */,
@ -1194,15 +1178,6 @@
name = Products;
sourceTree = "<group>";
};
7F6877AB1E7835E50094B63F /* Products */ = {
isa = PBXGroup;
children = (
7F6877B01E7835E50094B63F /* libToolTipMenu.a */,
7F6877B21E7835E50094B63F /* ToolTipMenuTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
7F7D7F53201645D300D31155 /* Products */ = {
isa = PBXGroup;
children = (
@ -1355,7 +1330,6 @@
3752184A1F4B9E980035444B /* RCTCameraRoll.xcodeproj */,
7F26919B1EE1DC51007574FE /* RNNotifications.xcodeproj */,
7FC200BC1EBB65100099331B /* ReactNativeNavigation.xcodeproj */,
7F6877AA1E7835E50094B63F /* ToolTipMenu.xcodeproj */,
7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */,
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */,
146833FF1AC3E56700842450 /* React.xcodeproj */,
@ -1694,10 +1668,6 @@
ProductGroup = 7FDF28EE1E1F4B4E00DBBE56 /* Products */;
ProjectRef = 2B25899FDAC149EB96ED3305 /* RNVectorIcons.xcodeproj */;
},
{
ProductGroup = 7F6877AB1E7835E50094B63F /* Products */;
ProjectRef = 7F6877AA1E7835E50094B63F /* ToolTipMenu.xcodeproj */;
},
);
projectRoot = "";
targets = (
@ -2045,20 +2015,6 @@
remoteRef = 7F642DEE2093530B00F3165E /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F6877B01E7835E50094B63F /* libToolTipMenu.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libToolTipMenu.a;
remoteRef = 7F6877AF1E7835E50094B63F /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F6877B21E7835E50094B63F /* ToolTipMenuTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = ToolTipMenuTests.xctest;
remoteRef = 7F6877B11E7835E50094B63F /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F6FF6B5203499F400150948 /* libjsinspector.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;

5
package-lock.json generated
View file

@ -12891,11 +12891,6 @@
"prop-types": "15.6.2"
}
},
"react-native-tooltip": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/react-native-tooltip/-/react-native-tooltip-5.2.0.tgz",
"integrity": "sha512-3mbPGd/VDfb3dUZmdmgg4eE8idJU4OyYM9ItGifnofz+Cz50Aua+sVGo2Ecc7zHV0S+kM0mOyLL758hhTz2gDw=="
},
"react-native-vector-icons": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-5.0.0.tgz",

View file

@ -52,7 +52,6 @@
"react-native-status-bar-size": "0.3.3",
"react-native-svg": "7.0.2",
"react-native-tableview": "2.3.1",
"react-native-tooltip": "5.2.0",
"react-native-vector-icons": "5.0.0",
"react-native-video": "3.2.1",
"react-native-youtube": "github:enahum/react-native-youtube#3f395b620ae4e05a3f1c6bdeef3a1158e78daadc",