diff --git a/.flowconfig b/.flowconfig index a359500ee..1ac2a7be5 100644 --- a/.flowconfig +++ b/.flowconfig @@ -37,12 +37,12 @@ suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState suppress_type=$FixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError unsafe.enable_getters_and_setters=true [version] -^0.53.0 +^0.56.0 diff --git a/.gitignore b/.gitignore index 1b88b8c4a..88b401ec7 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,6 @@ ios/sentry.properties # Pods .podinstall ios/Pods/ + +#editor-settings +.vscode \ No newline at end of file diff --git a/Makefile b/Makefile index 5b53a4d83..7e9d62c8c 100644 --- a/Makefile +++ b/Makefile @@ -125,12 +125,11 @@ post-install: @sed -i'' -e 's|"./locale-data/complete.js": false|"./locale-data/complete.js": "./locale-data/complete.js"|g' node_modules/intl/package.json @sed -i'' -e 's|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_FULL_USER);|g' node_modules/react-native-navigation/android/app/src/main/java/com/reactnativenavigation/params/Orientation.java @sed -i'' -e "s|var AndroidTextInput = requireNativeComponent('AndroidTextInput', null);|var AndroidTextInput = requireNativeComponent('CustomTextInput', null);|g" node_modules/react-native/Libraries/Components/TextInput/TextInput.js - @sed -i'' -e 's^getItemLayout || index <= this._highestMeasuredFrameIndex,^!getItemLayout || index !== -1,^g' node_modules/react-native/Libraries/Lists/VirtualizedList.js @if [ $(shell grep "const Platform" node_modules/react-native/Libraries/Lists/VirtualizedList.js | grep -civ grep) -eq 0 ]; then \ sed $ -i'' -e "s|const ReactNative = require('ReactNative');|const ReactNative = require('ReactNative');`echo $\\\\\\r;`const Platform = require('Platform');|g" node_modules/react-native/Libraries/Lists/VirtualizedList.js; \ fi @sed -i'' -e 's|transform: \[{scaleY: -1}\],|...Platform.select({android: {transform: \[{perspective: 1}, {scaleY: -1}\]}, ios: {transform: \[{scaleY: -1}\]}}),|g' node_modules/react-native/Libraries/Lists/VirtualizedList.js - @cd ./node_modules/react-native-svg/ios && rm -rf PerformanceBezier && git clone https://github.com/adamwulf/PerformanceBezier.git + @cd ./node_modules/react-native-svg/ios && rm -rf PerformanceBezier QuartzBookPack && yarn run postinstall @cd ./node_modules/mattermost-redux && yarn run build start-packager: ## Starts the package server diff --git a/app/components/channel_drawer/channels_list/filtered_list/filtered_list.js b/app/components/channel_drawer/channels_list/filtered_list/filtered_list.js index 1f663ec50..bcf7e13ba 100644 --- a/app/components/channel_drawer/channels_list/filtered_list/filtered_list.js +++ b/app/components/channel_drawer/channels_list/filtered_list/filtered_list.js @@ -20,6 +20,9 @@ import {sortChannelsByDisplayName} from 'mattermost-redux/utils/channel_utils'; import {displayUsername} from 'mattermost-redux/utils/user_utils'; import ChannelDrawerItem from 'app/components/channel_drawer/channels_list/channel_item'; +import {ListTypes} from 'app/constants'; + +const VIEWABILITY_CONFIG = ListTypes.VISIBILITY_CONFIG_DEFAULTS; class FilteredList extends Component { static propTypes = { @@ -387,10 +390,7 @@ class FilteredList extends Component { onViewableItemsChanged={this.updateUnreadIndicators} keyboardDismissMode='on-drag' maxToRenderPerBatch={10} - viewabilityConfig={{ - viewAreaCoveragePercentThreshold: 3, - waitForInteraction: false - }} + viewabilityConfig={VIEWABILITY_CONFIG} /> ); diff --git a/app/components/channel_drawer/channels_list/list/list.js b/app/components/channel_drawer/channels_list/list/list.js index 0acfbf46b..4125a5801 100644 --- a/app/components/channel_drawer/channels_list/list/list.js +++ b/app/components/channel_drawer/channels_list/list/list.js @@ -18,9 +18,15 @@ import {debounce} from 'mattermost-redux/actions/helpers'; import ChannelItem from 'app/components/channel_drawer/channels_list/channel_item'; import UnreadIndicator from 'app/components/channel_drawer/channels_list/unread_indicator'; +import {ListTypes} from 'app/constants'; import {wrapWithPreventDoubleTap} from 'app/utils/tap'; import {changeOpacity} from 'app/utils/theme'; +const VIEWABILITY_CONFIG = { + ...ListTypes.VISIBILITY_CONFIG_DEFAULTS, + waitForInteraction: true +}; + export default class List extends PureComponent { static propTypes = { canCreatePrivateChannels: PropTypes.bool.isRequired, @@ -340,10 +346,7 @@ export default class List extends PureComponent { keyboardDismissMode='on-drag' maxToRenderPerBatch={10} stickySectionHeadersEnabled={false} - viewabilityConfig={{ - viewAreaCoveragePercentThreshold: 3, - waitForInteraction: true - }} + viewabilityConfig={VIEWABILITY_CONFIG} /> ); diff --git a/app/components/custom_section_list.js b/app/components/custom_section_list.js index 3831ab407..7bab038e5 100644 --- a/app/components/custom_section_list.js +++ b/app/components/custom_section_list.js @@ -88,13 +88,11 @@ export default class CustomSectionList extends React.PureComponent { initialNumToRender: PropTypes.number }; - static defaultKeyExtractor = (item) => { - return item.id; - }; - static defaultProps = { showNoResults: true, - keyExtractor: CustomSectionList.defaultKeyExtractor, + keyExtractor: (item) => { + return item.id; + }, onListEndReached: () => true, onListEndReachedThreshold: 50, loadingText: null, diff --git a/app/components/edit_channel_info/index.js b/app/components/edit_channel_info/index.js index 6a04059dd..838fe2ef3 100644 --- a/app/components/edit_channel_info/index.js +++ b/app/components/edit_channel_info/index.js @@ -251,6 +251,7 @@ export default class EditChannelInfo extends PureComponent { placeholder={{id: 'channel_modal.nameEx', defaultMessage: 'E.g.: "Bugs", "Marketing", "客户支持"'}} placeholderTextColor={changeOpacity('#000', 0.5)} underlineColorAndroid='transparent' + disableFullscreenUI={true} /> @@ -279,6 +280,7 @@ export default class EditChannelInfo extends PureComponent { placeholder={{id: 'rename_channel.handleHolder', defaultMessage: 'lowercase alphanumeric characters'}} placeholderTextColor={changeOpacity('#000', 0.5)} underlineColorAndroid='transparent' + disableFullscreenUI={true} /> @@ -311,6 +313,7 @@ export default class EditChannelInfo extends PureComponent { blurOnSubmit={false} textAlignVertical='top' underlineColorAndroid='transparent' + disableFullscreenUI={true} /> @@ -349,6 +352,7 @@ export default class EditChannelInfo extends PureComponent { onFocus={this.scrollToEnd} textAlignVertical='top' underlineColorAndroid='transparent' + disableFullscreenUI={true} /> diff --git a/app/components/emoji_picker/emoji_picker.js b/app/components/emoji_picker/emoji_picker.js index fb26992e5..87c782ea8 100644 --- a/app/components/emoji_picker/emoji_picker.js +++ b/app/components/emoji_picker/emoji_picker.js @@ -63,6 +63,7 @@ export default class EmojiPicker extends PureComponent { const emojiSectionIndexByOffset = this.measureEmojiSections(emojis); this.isX = DeviceInfo.getModel() === 'iPhone X'; + this.scrollToSectionTries = 0; this.state = { emojis, emojiSectionIndexByOffset, @@ -264,6 +265,15 @@ export default class EmojiPicker extends PureComponent { }); }; + handleScrollToSectionFailed = ({index}) => { + if (this.scrollToSectionTries < 1) { + setTimeout(() => { + this.scrollToSectionTries++; + this.scrollToSection(index); + }, 200); + } + } + renderSectionHeader = ({section}) => { const {theme} = this.props; const styles = getStyleSheetFromTheme(theme); @@ -282,12 +292,17 @@ export default class EmojiPicker extends PureComponent { ); }; + handleSectionIconPress = (index) => { + this.scrollToSectionTries = 0; + this.scrollToSection(index); + } + renderSectionIcons = () => { const {theme} = this.props; const styles = getStyleSheetFromTheme(theme); return this.state.emojis.map((section, index) => { - const onPress = () => this.scrollToSection(index); + const onPress = () => this.handleSectionIconPress(index); return ( @@ -416,8 +432,7 @@ const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => { left: 0, right: 0, height: 35, - width: '100%', - backgroundColor: 'white' + width: '100%' }, columnStyle: { alignSelf: 'stretch', diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js index 8d1428c83..d095ee57e 100644 --- a/app/components/post_list/post_list.js +++ b/app/components/post_list/post_list.js @@ -46,17 +46,13 @@ export default class PostList extends PureComponent { loadMore: () => true }; - constructor(props) { - super(props); + newMessagesIndex = -1; + scrollToMessageTries = 0; + makeExtraData = makeExtraData(); - this.newMessagesIndex = -1; - - this.makeExtraData = makeExtraData(); - - this.state = { - managedConfig: {} - }; - } + state = { + managedConfig: {} + }; componentWillMount() { this.listenerId = mattermostManaged.addEventListener('change', this.setManagedConfig); @@ -76,6 +72,7 @@ export default class PostList extends PureComponent { componentDidUpdate(prevProps) { const initialPosts = !prevProps.postIds.length && prevProps.postIds !== this.props.postIds; if ((prevProps.channelId !== this.props.channelId || initialPosts || this.props.isSearchResult) && this.refs.list) { + this.scrollToMessageTries = 0; this.scrollList(); } } @@ -86,7 +83,7 @@ export default class PostList extends PureComponent { scrollList = () => { InteractionManager.runAfterInteractions(() => { - if (this.props.postIds.length && this.newMessagesIndex !== -1 && this.newMessagesIndex <= this.props.postIds.length) { + if (this.props.postIds.length && this.newMessagesIndex !== -1) { if (this.refs.list) { this.refs.list.scrollToIndex({ index: this.newMessagesIndex, @@ -95,13 +92,22 @@ export default class PostList extends PureComponent { animated: true }); } - this.newMessagesIndex = -1; } else if (this.refs.list) { this.refs.list.scrollToOffset({y: 0, animated: false}); } }); }; + scrollListFailed = ({index}) => { + if (this.scrollToMessageTries < 3) { + this.scrollToMessageTries++; + setTimeout(() => { + this.newMessagesIndex = index; + this.scrollList(); + }, 300); + } + } + setManagedConfig = async (config) => { let nextConfig = config; if (!nextConfig) { @@ -244,6 +250,7 @@ export default class PostList extends PureComponent { ListFooterComponent={this.renderFooter} onEndReached={loadMore} onEndReachedThreshold={Platform.OS === 'ios' ? 0 : 1} + onScrollToIndexFailed={this.scrollListFailed} {...refreshControl} renderItem={this.renderItem} contentContainerStyle={styles.postListContent} diff --git a/app/components/post_textbox/post_textbox.js b/app/components/post_textbox/post_textbox.js index 1f4d6bb06..e2807e0a3 100644 --- a/app/components/post_textbox/post_textbox.js +++ b/app/components/post_textbox/post_textbox.js @@ -3,8 +3,8 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import {Alert, BackHandler, Keyboard, Platform, Text, TextInput, TouchableOpacity, View} from 'react-native'; -import {injectIntl, intlShape} from 'react-intl'; +import {Alert, BackHandler, Keyboard, Platform, TextInput, TouchableOpacity, View} from 'react-native'; +import {intlShape} from 'react-intl'; import {RequestStatus} from 'mattermost-redux/constants'; import Autocomplete from 'app/components/autocomplete'; @@ -21,7 +21,7 @@ const MAX_MESSAGE_LENGTH = 4000; const MAX_FILE_COUNT = 5; const IS_REACTION_REGEX = /(^\+:([^:\s]*):)$/i; -class PostTextbox extends PureComponent { +export default class PostTextbox extends PureComponent { static propTypes = { actions: PropTypes.shape({ addReactionToLatestPost: PropTypes.func.isRequired, @@ -42,7 +42,6 @@ class PostTextbox extends PureComponent { channelIsLoading: PropTypes.bool.isRequired, currentUserId: PropTypes.string.isRequired, files: PropTypes.array, - intl: intlShape.isRequired, navigator: PropTypes.object, rootId: PropTypes.string, theme: PropTypes.object.isRequired, @@ -56,12 +55,15 @@ class PostTextbox extends PureComponent { value: '' }; + static contextTypes = { + intl: intlShape + }; + constructor(props) { super(props); this.state = { contentHeight: INITIAL_HEIGHT, - inputWidth: null, keyboardType: 'default', value: props.value, showFileMaxWarning: false @@ -131,7 +133,7 @@ class PostTextbox extends PureComponent { }; checkMessageLength = (value) => { - const {intl} = this.props; + const {intl} = this.context; const valueLength = value.trim().length; if (valueLength > MAX_MESSAGE_LENGTH) { @@ -173,9 +175,11 @@ class PostTextbox extends PureComponent { }; handleContentSizeChange = (event) => { - let contentHeight = event.nativeEvent.layout.height; + let contentHeight = event.nativeEvent.contentSize.height; if (contentHeight < INITIAL_HEIGHT) { contentHeight = INITIAL_HEIGHT; + } else if (Platform.OS === 'ios') { + contentHeight += 5; } this.setState({ @@ -197,12 +201,6 @@ class PostTextbox extends PureComponent { } }; - handleInputSizeChange = (event) => { - this.setState({ - inputWidth: event.nativeEvent.layout.width - }); - }; - handlePostDraftSelectionChanged = (event) => { const cursorPosition = event.nativeEvent.selection.end; if (this.props.rootId) { @@ -231,7 +229,7 @@ class PostTextbox extends PureComponent { const hasFailedAttachments = files.some((f) => f.failed); if (hasFailedAttachments) { - const {intl} = this.props; + const {intl} = this.context; Alert.alert( intl.formatMessage({ @@ -316,7 +314,8 @@ class PostTextbox extends PureComponent { }; sendMessage = () => { - const {actions, currentUserId, channelId, files, rootId, intl} = this.props; + const {actions, currentUserId, channelId, files, rootId} = this.props; + const {intl} = this.context; const {value} = this.state; if (files.length === 0 && !value) { @@ -374,7 +373,8 @@ class PostTextbox extends PureComponent { }; sendCommand = async (msg) => { - const {actions, channelId, intl, rootId} = this.props; + const {intl} = this.context; + const {actions, channelId, rootId} = this.props; const {error} = await actions.executeCommand(msg, channelId, rootId); if (error) { @@ -386,7 +386,7 @@ class PostTextbox extends PureComponent { error.message ); } - } + }; sendReaction = (emoji) => { const {actions, rootId} = this.props; @@ -404,12 +404,12 @@ class PostTextbox extends PureComponent { }; render() { + const {intl} = this.context; const { canUploadFiles, channelId, channelIsLoading, files, - intl, navigator, rootId, theme @@ -448,12 +448,6 @@ class PostTextbox extends PureComponent { return ( - - {textValue + ' '} - { } }; }); - -export default injectIntl(PostTextbox, {withRef: true}); diff --git a/app/components/safe_area_view/safe_area_view.ios.js b/app/components/safe_area_view/safe_area_view.ios.js index bd2404476..b02f4d3c4 100644 --- a/app/components/safe_area_view/safe_area_view.ios.js +++ b/app/components/safe_area_view/safe_area_view.ios.js @@ -49,6 +49,7 @@ export default class SafeAreaIos extends PureComponent { componentWillMount() { this.getSafeAreaInsets(); + this.mounted = true; } componentDidMount() { @@ -62,13 +63,16 @@ export default class SafeAreaIos extends PureComponent { Orientation.removeOrientationListener(this.getSafeAreaInsets); this.keyboardDidShowListener.remove(); this.keyboardDidHideListener.remove(); + this.mounted = false; } getStatusBarHeight = () => { try { StatusBarManager.getHeight( (statusBarFrameData) => { - this.setState({statusBarHeight: statusBarFrameData.height}); + if (this.mounted) { + this.setState({statusBarHeight: statusBarFrameData.height}); + } } ); } catch (e) { diff --git a/app/constants/index.js b/app/constants/index.js index 7269a537d..29fbe33b4 100644 --- a/app/constants/index.js +++ b/app/constants/index.js @@ -2,11 +2,13 @@ // See License.txt for license information. import DeviceTypes from './device'; +import ListTypes from './list'; import NavigationTypes from './navigation'; import ViewTypes from './view'; export { DeviceTypes, + ListTypes, NavigationTypes, ViewTypes }; diff --git a/app/constants/list.js b/app/constants/list.js new file mode 100644 index 000000000..1d6638332 --- /dev/null +++ b/app/constants/list.js @@ -0,0 +1,11 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +const VISIBILITY_CONFIG_DEFAULTS = { + viewAreaCoveragePercentThreshold: 3, + waitForInteraction: false +}; + +export default { + VISIBILITY_CONFIG_DEFAULTS +}; \ No newline at end of file diff --git a/app/mattermost.js b/app/mattermost.js index d7331ee6e..43867c528 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -105,7 +105,7 @@ export default class Mattermost { registerScreens(this.store, Provider); setJSExceptionHandler(this.errorHandler, false); - setNativeExceptionHandler(this.nativeErrorHandler); + setNativeExceptionHandler(this.nativeErrorHandler, false); } errorHandler = (e, isFatal) => { diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel.js index 8131c5b07..2a9365579 100644 --- a/app/screens/channel/channel.js +++ b/app/screens/channel/channel.js @@ -102,7 +102,7 @@ class Channel extends PureComponent { }; blurPostTextBox = () => { - this.postTextbox.getWrappedInstance().getWrappedInstance().blur(); + this.postTextbox.getWrappedInstance().blur(); }; channelDrawerRef = (ref) => { diff --git a/app/screens/edit_post/edit_post.js b/app/screens/edit_post/edit_post.js index 5580fbf40..39b9b1911 100644 --- a/app/screens/edit_post/edit_post.js +++ b/app/screens/edit_post/edit_post.js @@ -2,7 +2,7 @@ // See License.txt for license information. import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import {injectIntl, intlShape} from 'react-intl'; +import {intlShape} from 'react-intl'; import { Dimensions, Platform, @@ -17,19 +17,22 @@ import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/ut import {RequestStatus} from 'mattermost-redux/constants'; -class EditPost extends PureComponent { +export default class EditPost extends PureComponent { static propTypes = { actions: PropTypes.shape({ editPost: PropTypes.func.isRequired }), closeButton: PropTypes.object, editPostRequest: PropTypes.object.isRequired, - intl: intlShape.isRequired, navigator: PropTypes.object, post: PropTypes.object.isRequired, theme: PropTypes.object.isRequired }; + static contextTypes = { + intl: intlShape + }; + leftButton = { id: 'close-edit-post' }; @@ -39,11 +42,11 @@ class EditPost extends PureComponent { showAsAction: 'always' }; - constructor(props) { + constructor(props, context) { super(props); this.state = {message: props.post.message}; - this.rightButton.title = props.intl.formatMessage({id: 'edit_post.save', defaultMessage: 'Save'}); + this.rightButton.title = context.intl.formatMessage({id: 'edit_post.save', defaultMessage: 'Save'}); props.navigator.setOnNavigatorEvent(this.onNavigatorEvent); props.navigator.setButtons({ @@ -106,20 +109,6 @@ class EditPost extends PureComponent { this.messageInput.refs.wrappedInstance.focus(); }; - handleSubmit = () => { - // Workaround for android as the multiline is not working - if (Platform.OS === 'android') { - if (this.timeout) { - clearTimeout(this.timeout); - } - this.timeout = setTimeout(() => { - let {message: msg} = this.state; - msg += '\n'; - this.setState({message: msg}); - }, 100); - } - }; - messageRef = (ref) => { this.messageInput = ref; }; @@ -198,7 +187,7 @@ class EditPost extends PureComponent { placeholder={{id: 'edit_post.editPost', defaultMessage: 'Edit the post...'}} placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.4)} underlineColorAndroid='transparent' - onSubmitEditing={this.handleSubmit} + disableFullscreenUI={true} /> @@ -239,5 +228,3 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { } }; }); - -export default injectIntl(EditPost); diff --git a/app/screens/image_preview/image_preview.js b/app/screens/image_preview/image_preview.js index 873d46b3f..3a48ae15f 100644 --- a/app/screens/image_preview/image_preview.js +++ b/app/screens/image_preview/image_preview.js @@ -528,6 +528,7 @@ export default class ImagePreview extends PureComponent { {proceed} diff --git a/app/screens/select_server/select_server.js b/app/screens/select_server/select_server.js index 0b21909b1..12b5d3acf 100644 --- a/app/screens/select_server/select_server.js +++ b/app/screens/select_server/select_server.js @@ -338,6 +338,7 @@ class SelectServer extends PureComponent { placeholder={{id: 'mobile.components.select_server_view.siteUrlPlaceholder', defaultMessage: 'https://mattermost.example.com'}} returnKeyType='go' underlineColorAndroid='transparent' + disableFullscreenUI={true} />