diff --git a/app/components/emoji/emoji.js b/app/components/emoji/emoji.js index c2112dbe2..dcb6b2126 100644 --- a/app/components/emoji/emoji.js +++ b/app/components/emoji/emoji.js @@ -14,13 +14,6 @@ import { import CustomPropTypes from 'app/constants/custom_prop_types'; import ImageCacheManager from 'app/utils/image_cache_manager'; -const scaleEmojiBasedOnDevice = (size) => { - if (Platform.OS === 'ios') { - return size * 1.1; // slightly larger emojis look better on ios - } - return size * PixelRatio.get(); -}; - export default class Emoji extends React.PureComponent { static propTypes = { @@ -60,30 +53,28 @@ export default class Emoji extends React.PureComponent { this.state = { imageUrl: null, - originalWidth: 0, - originalHeight: 0, }; } componentWillMount() { + const {displayTextOnly, imageUrl} = this.props; this.mounted = true; - if (!this.props.displayTextOnly && this.props.imageUrl) { - ImageCacheManager.cache(this.props.imageUrl, this.props.imageUrl, this.updateImageHeight); + if (!displayTextOnly && imageUrl) { + ImageCacheManager.cache(imageUrl, imageUrl, this.setImageUrl); } } componentWillReceiveProps(nextProps) { - if (nextProps.emojiName !== this.props.emojiName) { + const {displayTextOnly, emojiName, imageUrl} = nextProps; + if (emojiName !== this.props.emojiName) { this.setState({ imageUrl: null, - originalWidth: 0, - originalHeight: 0, }); } - if (!nextProps.displayTextOnly && nextProps.imageUrl && - nextProps.imageUrl !== this.props.imageUrl) { - ImageCacheManager.cache(nextProps.imageUrl, nextProps.imageUrl, this.updateImageHeight); + if (!displayTextOnly && imageUrl && + imageUrl !== this.props.imageUrl) { + ImageCacheManager.cache(imageUrl, imageUrl, this.setImageUrl); } } @@ -91,21 +82,15 @@ export default class Emoji extends React.PureComponent { this.mounted = false; } - updateImageHeight = (imageUrl) => { + setImageUrl = (imageUrl) => { let prefix = ''; if (Platform.OS === 'android') { prefix = 'file://'; } const uri = `${prefix}${imageUrl}`; - Image.getSize(uri, (originalWidth, originalHeight) => { - if (this.mounted) { - this.setState({ - imageUrl: uri, - originalWidth, - originalHeight, - }); - } + this.setState({ + imageUrl: uri, }); }; @@ -122,26 +107,15 @@ export default class Emoji extends React.PureComponent { if (!size && textStyle) { const flatten = StyleSheet.flatten(textStyle); fontSize = flatten.fontSize; - size = scaleEmojiBasedOnDevice(fontSize); + size = fontSize * (Platform.OS === 'android' ? PixelRatio.get() : 1); } if (displayTextOnly) { return {literal}; } - let width = size; - let height = size; - let {originalHeight, originalWidth} = this.state; - originalHeight = scaleEmojiBasedOnDevice(originalHeight); - originalWidth = scaleEmojiBasedOnDevice(originalWidth); - if (originalHeight && originalWidth) { - if (originalWidth > originalHeight) { - height = (size * originalHeight) / originalWidth; - } else if (originalWidth < originalHeight) { - // This may cause text to reflow, but its impossible to add a horizontal margin - width = (size * originalWidth) / originalHeight; - } - } + const width = size; + const height = size; let marginTop = 0; if (textStyle) { diff --git a/app/components/emoji_picker/emoji_picker.js b/app/components/emoji_picker/emoji_picker.js index 10a391c0a..509cf781d 100644 --- a/app/components/emoji_picker/emoji_picker.js +++ b/app/components/emoji_picker/emoji_picker.js @@ -407,6 +407,7 @@ export default class EmojiPicker extends PureComponent { renderItem={this.flatListRenderItem} pageSize={10} initialListSize={10} + removeClippedSubviews={true} /> ); } else { @@ -558,6 +559,7 @@ const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => { borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2), borderRightWidth: 1, borderRightColor: changeOpacity(theme.centerChannelColor, 0.2), + overflow: 'hidden', }, listView: { backgroundColor: theme.centerChannelBg, diff --git a/app/components/emoji_picker/emoji_picker_row.js b/app/components/emoji_picker/emoji_picker_row.js index 0c1937bab..4f69eccac 100644 --- a/app/components/emoji_picker/emoji_picker_row.js +++ b/app/components/emoji_picker/emoji_picker_row.js @@ -64,7 +64,7 @@ export default class EmojiPickerRow extends Component { /> ); - } + }; render() { const {emojiGutter, items} = this.props; @@ -86,6 +86,7 @@ const styles = StyleSheet.create({ emoji: { alignItems: 'center', justifyContent: 'center', + overflow: 'hidden', }, emojiLeft: { marginLeft: 0, diff --git a/app/components/options_context/options_context.android.js b/app/components/options_context/options_context.android.js index 2f1c63a93..f0249d87c 100644 --- a/app/components/options_context/options_context.android.js +++ b/app/components/options_context/options_context.android.js @@ -8,7 +8,7 @@ import RNBottomSheet from 'react-native-bottom-sheet'; export default class OptionsContext extends PureComponent { static propTypes = { - actions: PropTypes.array, + getPostActions: PropTypes.func, cancelText: PropTypes.string, children: PropTypes.node.isRequired, onPress: PropTypes.func.isRequired, @@ -16,13 +16,13 @@ export default class OptionsContext extends PureComponent { }; static defaultProps = { - actions: [], + getPostActions: () => [], cancelText: 'Cancel', }; show = (additionalAction) => { - const {actions, cancelText} = this.props; - const nextActions = [...actions]; + 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); @@ -45,11 +45,11 @@ export default class OptionsContext extends PureComponent { }; handleHideUnderlay = () => { - this.props.toggleSelected(false, this.props.actions.length > 0); + this.props.toggleSelected(false, this.props.getPostActions().length > 0); }; handleShowUnderlay = () => { - this.props.toggleSelected(true, this.props.actions.length > 0); + this.props.toggleSelected(true, this.props.getPostActions().length > 0); }; render() { diff --git a/app/components/options_context/options_context.ios.js b/app/components/options_context/options_context.ios.js index 9c3fe3896..0e89b3f2d 100644 --- a/app/components/options_context/options_context.ios.js +++ b/app/components/options_context/options_context.ios.js @@ -8,14 +8,14 @@ import ToolTip from 'app/components/tooltip'; export default class OptionsContext extends PureComponent { static propTypes = { - actions: PropTypes.array, + getPostActions: PropTypes.func, children: PropTypes.node.isRequired, onPress: PropTypes.func.isRequired, toggleSelected: PropTypes.func.isRequired, }; static defaultProps = { - actions: [], + getPostActions: () => [], additionalActions: [], }; @@ -23,16 +23,10 @@ export default class OptionsContext extends PureComponent { super(props); this.state = { - actions: props.actions, + actions: props.getPostActions(), }; } - componentWillReceiveProps(nextProps) { - if (this.props.actions !== nextProps.actions) { - this.setState({actions: nextProps.actions}); - } - } - handleHideUnderlay = () => { if (!this.isShowing) { this.props.toggleSelected(false, false); @@ -45,11 +39,11 @@ export default class OptionsContext extends PureComponent { handleHide = () => { this.isShowing = false; - this.props.toggleSelected(false, this.props.actions.length > 0); + this.props.toggleSelected(false, this.props.getPostActions().length > 0); }; handleShow = () => { - this.isShowing = this.props.actions.length > 0; + this.isShowing = this.props.getPostActions().length > 0; this.props.toggleSelected(true, this.isShowing); }; @@ -59,12 +53,12 @@ export default class OptionsContext extends PureComponent { } this.setState({ - actions: this.props.actions, + actions: this.props.getPostActions(), }); }; show = (additionalAction) => { - const nextActions = [...this.props.actions]; + const nextActions = this.props.getPostActions(); if (additionalAction && additionalAction.text && !additionalAction.nativeEvent) { const copyPostIndex = nextActions.findIndex((action) => action.copyPost); nextActions.splice(copyPostIndex + 1, 0, additionalAction); @@ -80,7 +74,7 @@ export default class OptionsContext extends PureComponent { }; handlePress = () => { - this.props.toggleSelected(false, this.props.actions.length > 0); + this.props.toggleSelected(false, this.props.getPostActions().length > 0); this.props.onPress(); }; diff --git a/app/components/post/index.js b/app/components/post/index.js index d719c3bd5..97d74b1cf 100644 --- a/app/components/post/index.js +++ b/app/components/post/index.js @@ -5,6 +5,7 @@ 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 {getPost, makeGetCommentCountForPost} from 'mattermost-redux/selectors/entities/posts'; import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users'; @@ -16,8 +17,6 @@ import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general import {insertToDraft, setPostTooltipVisible} from 'app/actions/views/channel'; import {addReaction} from 'app/actions/views/emoji'; -import {getDimensions} from 'app/selectors/device'; -import {Posts} from 'mattermost-redux/constants'; import Post from './post'; @@ -78,33 +77,36 @@ function makeMapStateToProps() { } } } - const {deviceWidth} = getDimensions(state); 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), - config, canDelete, canEdit, + canEditUntil, currentTeamUrl: getCurrentTeamUrl(state), currentUserId, - deviceWidth, post, isFirstReply, isLastReply, consecutivePost: isConsecutivePost(state, ownProps), hasComments: getCommentCountForPost(state, {post}) > 0, commentedOnPost, - license, theme: getTheme(state), isFlagged: isPostFlagged(post.id, myPreferences), }; diff --git a/app/components/post/post.js b/app/components/post/post.js index 2dbfa03bc..37bea8265 100644 --- a/app/components/post/post.js +++ b/app/components/post/post.js @@ -25,9 +25,8 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import {getToolTipVisible} from 'app/utils/tooltip'; import {Posts} from 'mattermost-redux/constants'; -import DelayedAction from 'mattermost-redux/utils/delayed_action'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; -import {editDisable, isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils'; +import {isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils'; import Config from 'assets/config'; @@ -41,10 +40,8 @@ export default class Post extends PureComponent { removePost: PropTypes.func.isRequired, }).isRequired, channelIsReadOnly: PropTypes.bool, - config: PropTypes.object.isRequired, currentTeamUrl: PropTypes.string.isRequired, currentUserId: PropTypes.string.isRequired, - deviceWidth: PropTypes.number.isRequired, highlight: PropTypes.bool, style: ViewPropTypes.style, post: PropTypes.object, @@ -56,10 +53,10 @@ export default class Post extends PureComponent { hasComments: PropTypes.bool, isSearchResult: PropTypes.bool, commentedOnPost: PropTypes.object, - license: PropTypes.object.isRequired, managedConfig: PropTypes.object.isRequired, navigator: PropTypes.object, canEdit: PropTypes.bool.isRequired, + canEditUntil: PropTypes.number.isRequired, canDelete: PropTypes.bool.isRequired, onPermalinkPress: PropTypes.func, shouldRenderReplyButton: PropTypes.bool, @@ -83,37 +80,6 @@ export default class Post extends PureComponent { intl: intlShape.isRequired, }; - constructor(props) { - super(props); - - const {config, license, currentUserId, post} = props; - this.editDisableAction = new DelayedAction(this.handleEditDisable); - if (post) { - editDisable(config, license, currentUserId, post, this.editDisableAction); - } - this.state = { - canEdit: this.props.canEdit, - }; - } - - componentWillReceiveProps(nextProps) { - if (nextProps.config !== this.props.config || - nextProps.license !== this.props.license || - nextProps.currentUserId !== this.props.currentUserId || - nextProps.post !== this.props.post) { - const {config, license, currentUserId, post} = nextProps; - - editDisable(config, license, currentUserId, post, this.editDisableAction); - this.setState({ - canEdit: nextProps.canEdit, - }); - } - } - - componentWillUnmount() { - this.editDisableAction.cancel(); - } - goToUserProfile = () => { const {intl} = this.context; const {navigator, post, theme} = this.props; @@ -165,7 +131,6 @@ export default class Post extends PureComponent { text: formatMessage({id: 'post_info.del', defaultMessage: 'Delete'}), style: 'destructive', onPress: () => { - this.editDisableAction.cancel(); actions.deletePost(post); if (post.user_id === currentUserId) { actions.removePost(post); @@ -479,7 +444,8 @@ export default class Post extends PureComponent { Date.now())) { actions.push({text: formatMessage({id: 'post_info.edit', defaultMessage: 'Edit'}), onPress: onPostEdit}); } @@ -435,7 +437,7 @@ export default class PostBody extends PureComponent { body = ( + {this.getDisplayName(style)} @@ -253,7 +253,7 @@ export default class PostHeader extends PureComponent { {this.renderCommentedOnMessage(style)} } - + ); } } diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js index af86fad6d..c933abc5b 100644 --- a/app/components/post_list/post_list.js +++ b/app/components/post_list/post_list.js @@ -234,9 +234,11 @@ export default class PostList extends PureComponent { nextConfig = await mattermostManaged.getLocalConfig(); } - this.setState({ - managedConfig: nextConfig, - }); + if (Object.keys(nextConfig).length) { + this.setState({ + managedConfig: nextConfig, + }); + } }; keyExtractor = (item) => { diff --git a/app/components/post_list/with_layout.js b/app/components/post_list/with_layout.js index dcef92225..5ed73e34a 100644 --- a/app/components/post_list/with_layout.js +++ b/app/components/post_list/with_layout.js @@ -28,9 +28,10 @@ function withLayout(WrappedComponent) { }; render() { + const {index, onLayoutCalled, shouldCallOnLayout, ...otherProps} = this.props; //eslint-disable-line no-unused-vars return ( - + ); } diff --git a/app/screens/entry/entry.js b/app/screens/entry/entry.js index 640396b19..8f61e1884 100644 --- a/app/screens/entry/entry.js +++ b/app/screens/entry/entry.js @@ -11,6 +11,7 @@ import { import DeviceInfo from 'react-native-device-info'; +import {setSystemEmojis} from 'mattermost-redux/actions/emojis'; import {Client4} from 'mattermost-redux/client'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; @@ -56,15 +57,14 @@ const lazyLoadReplyPushNotifications = () => { */ export default class Entry extends PureComponent { static propTypes = { - config: PropTypes.object, theme: PropTypes.object, navigator: PropTypes.object, isLandscape: PropTypes.bool, - hydrationComplete: PropTypes.bool, enableTimezone: PropTypes.bool, deviceTimezone: PropTypes.string, initializeModules: PropTypes.func.isRequired, actions: PropTypes.shape({ + autoUpdateTimezone: PropTypes.func.isRequired, setDeviceToken: PropTypes.func.isRequired, }).isRequired, }; @@ -132,6 +132,7 @@ export default class Entry extends PureComponent { this.setAppCredentials(); this.setStartupThemes(); this.handleNotification(); + this.loadSystemEmojis(); if (Platform.OS === 'android') { this.launchForAndroid(); @@ -226,6 +227,11 @@ export default class Entry extends PureComponent { } }; + loadSystemEmojis = () => { + const EmojiIndicesByAlias = require('app/utils/emojis').EmojiIndicesByAlias; + setSystemEmojis(EmojiIndicesByAlias); + }; + renderLogin = () => { const SelectServer = lazyLoadSelectServer(); const props = { diff --git a/app/screens/entry/index.js b/app/screens/entry/index.js index 5048a5537..dab3808b8 100644 --- a/app/screens/entry/index.js +++ b/app/screens/entry/index.js @@ -6,7 +6,6 @@ import {connect} from 'react-redux'; import {setDeviceToken} from 'mattermost-redux/actions/general'; import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {isLandscape} from 'app/selectors/device'; import {getDeviceTimezone, isTimezoneEnabled} from 'app/utils/timezone'; @@ -16,16 +15,12 @@ const lazyLoadEntry = () => { }; function mapStateToProps(state) { - const config = getConfig(state); - const enableTimezone = isTimezoneEnabled(state); const deviceTimezone = getDeviceTimezone(); return { - config, theme: getTheme(state), isLandscape: isLandscape(state), - hydrationComplete: state.views.root.hydrationComplete, enableTimezone, deviceTimezone, }; @@ -34,8 +29,8 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ - setDeviceToken, autoUpdateTimezone, + setDeviceToken, }, dispatch), }; } diff --git a/app/utils/why_did_you_update.js b/app/utils/why_did_you_update.js index 891a0aff7..973476d37 100644 --- a/app/utils/why_did_you_update.js +++ b/app/utils/why_did_you_update.js @@ -40,10 +40,10 @@ function deepDiff(o1, o2, p) { } } -function whyDidYouUpdate(prevProps, prevState) { +function whyDidYouUpdate(theClass, prevProps, prevState) { deepDiff({props: prevProps, state: prevState}, - {props: this.props, state: this.state}, - this.constructor.name); + {props: theClass.props, state: theClass.state}, + theClass.constructor.name); } export default whyDidYouUpdate;