Fix autocomplete showing behind the keyboard on iOS and not working on Android (#2830)
* Fix autocomplete showing behind the keyboard on iOS and not working on Android * Unbundle config for Android * Dismiss keyboard on post long press, fix scroll to bottom on new message and update tests * Add a timeout before scrolling to give time to render the last post * Fix crash on Android
This commit is contained in:
parent
03c3b91c1c
commit
2c7116bc7f
19 changed files with 644 additions and 326 deletions
|
|
@ -4,6 +4,7 @@
|
|||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Keyboard,
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
|
|
@ -204,7 +205,10 @@ export default class PostBody extends PureComponent {
|
|||
},
|
||||
};
|
||||
|
||||
navigator.showModal(options);
|
||||
Keyboard.dismiss();
|
||||
requestAnimationFrame(() => {
|
||||
navigator.showModal(options);
|
||||
});
|
||||
};
|
||||
|
||||
renderAddChannelMember = (style, messageStyle, textStyles) => {
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ export default class PostList extends PureComponent {
|
|||
|
||||
this.hasDoneInitialScroll = false;
|
||||
this.contentOffsetY = 0;
|
||||
this.shouldScrollToBottom = false;
|
||||
this.makeExtraData = makeExtraData();
|
||||
this.flatListRef = React.createRef();
|
||||
|
||||
|
|
@ -92,7 +93,7 @@ export default class PostList extends PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
EventEmitter.on('scroll-to-bottom', this.scrollToBottom);
|
||||
EventEmitter.on('scroll-to-bottom', this.handleSetScrollToBottom);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
|
@ -102,15 +103,22 @@ export default class PostList extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.deepLinkURL) {
|
||||
this.handleDeepLink(this.props.deepLinkURL);
|
||||
this.props.actions.setDeepLinkURL('');
|
||||
componentDidUpdate(prevProps) {
|
||||
const {actions, channelId, deepLinkURL, postIds} = this.props;
|
||||
|
||||
if (deepLinkURL && deepLinkURL !== prevProps.deepLinkURL) {
|
||||
this.handleDeepLink(deepLinkURL);
|
||||
actions.setDeepLinkURL('');
|
||||
}
|
||||
|
||||
if (this.shouldScrollToBottom && prevProps.channelId === channelId && prevProps.postIds.length === postIds.length) {
|
||||
this.scrollToBottom();
|
||||
this.shouldScrollToBottom = false;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('scroll-to-bottom', this.scrollToBottom);
|
||||
EventEmitter.off('scroll-to-bottom', this.handleSetScrollToBottom);
|
||||
}
|
||||
|
||||
handleClosePermalink = () => {
|
||||
|
|
@ -199,6 +207,10 @@ export default class PostList extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
handleSetScrollToBottom = () => {
|
||||
this.shouldScrollToBottom = true;
|
||||
}
|
||||
|
||||
keyExtractor = (item) => {
|
||||
// All keys are strings (either post IDs or special keys)
|
||||
return item;
|
||||
|
|
@ -268,7 +280,9 @@ export default class PostList extends PureComponent {
|
|||
};
|
||||
|
||||
scrollToBottom = () => {
|
||||
this.flatListRef.current.scrollToOffset({offset: 0, animated: true});
|
||||
setTimeout(() => {
|
||||
this.flatListRef.current.scrollToOffset({offset: 0, animated: true});
|
||||
}, 250);
|
||||
};
|
||||
|
||||
scrollToInitialIndexIfNeeded = (width, height) => {
|
||||
|
|
|
|||
49
app/components/post_textbox/post_textbox.android.js
Normal file
49
app/components/post_textbox/post_textbox.android.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Autocomplete from 'app/components/autocomplete';
|
||||
import FileUploadPreview from 'app/components/file_upload_preview';
|
||||
|
||||
import Typing from './components/typing';
|
||||
import PostTextBoxBase from './post_textbox_base';
|
||||
|
||||
const AUTOCOMPLETE_MARGIN = 20;
|
||||
const AUTOCOMPLETE_MAX_HEIGHT = 200;
|
||||
|
||||
export default class PostTextBoxAndroid extends PostTextBoxBase {
|
||||
render() {
|
||||
const {
|
||||
channelId,
|
||||
deactivatedChannel,
|
||||
files,
|
||||
rootId,
|
||||
} = this.props;
|
||||
|
||||
if (deactivatedChannel) {
|
||||
return this.renderDeactivatedChannel();
|
||||
}
|
||||
|
||||
const {cursorPosition, top} = this.state;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Typing/>
|
||||
<FileUploadPreview
|
||||
channelId={channelId}
|
||||
files={files}
|
||||
rootId={rootId}
|
||||
/>
|
||||
<Autocomplete
|
||||
cursorPosition={cursorPosition}
|
||||
maxHeight={Math.min(top - AUTOCOMPLETE_MARGIN, AUTOCOMPLETE_MAX_HEIGHT)}
|
||||
onChangeText={this.handleTextChange}
|
||||
value={this.state.value}
|
||||
rootId={rootId}
|
||||
/>
|
||||
{this.renderTextBox()}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
24
app/components/post_textbox/post_textbox.ios.js
Normal file
24
app/components/post_textbox/post_textbox.ios.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Typing from './components/typing';
|
||||
import PostTextBoxBase from './post_textbox_base';
|
||||
|
||||
export default class PostTextBoxIOS extends PostTextBoxBase {
|
||||
render() {
|
||||
const {deactivatedChannel} = this.props;
|
||||
|
||||
if (deactivatedChannel) {
|
||||
return this.renderDeactivatedChannel();
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Typing/>
|
||||
{this.renderTextBox()}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,17 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Alert, BackHandler, findNodeHandle, Keyboard, NativeModules, Platform, Text, TextInput, View} from 'react-native';
|
||||
import {
|
||||
Alert,
|
||||
BackHandler,
|
||||
findNodeHandle,
|
||||
Keyboard,
|
||||
NativeModules,
|
||||
Platform,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import Button from 'react-native-button';
|
||||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
|
|
@ -12,20 +22,19 @@ import {getFormattedFileSize} from 'mattermost-redux/utils/file_utils';
|
|||
|
||||
import AttachmentButton from 'app/components/attachment_button';
|
||||
import Fade from 'app/components/fade';
|
||||
import {INSERT_TO_COMMENT, INSERT_TO_DRAFT, IS_REACTION_REGEX, MAX_CONTENT_HEIGHT, MAX_FILE_COUNT} from 'app/constants/post_textbox';
|
||||
import {confirmOutOfOfficeDisabled} from 'app/utils/status';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import FormattedMarkdownText from 'app/components/formatted_markdown_text';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import SendButton from 'app/components/send_button';
|
||||
|
||||
import Typing from './components/typing';
|
||||
import {INSERT_TO_COMMENT, INSERT_TO_DRAFT, IS_REACTION_REGEX, MAX_CONTENT_HEIGHT, MAX_FILE_COUNT} from 'app/constants/post_textbox';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {confirmOutOfOfficeDisabled} from 'app/utils/status';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const PLACEHOLDER_COLOR = changeOpacity('#000', 0.5);
|
||||
const {RNTextInputReset} = NativeModules;
|
||||
|
||||
export default class PostTextbox extends PureComponent {
|
||||
export default class PostTextBoxBase extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
addReactionToLatestPost: PropTypes.func.isRequired,
|
||||
|
|
@ -172,6 +181,60 @@ export default class PostTextbox extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
getAttachmentButton = () => {
|
||||
const {canUploadFiles, channelIsReadOnly, files, maxFileSize, navigator, theme} = this.props;
|
||||
let attachmentButton = null;
|
||||
|
||||
if (canUploadFiles && !channelIsReadOnly) {
|
||||
attachmentButton = (
|
||||
<AttachmentButton
|
||||
blurTextBox={this.blur}
|
||||
theme={theme}
|
||||
navigator={navigator}
|
||||
fileCount={files.length}
|
||||
maxFileSize={maxFileSize}
|
||||
maxFileCount={MAX_FILE_COUNT}
|
||||
onShowFileMaxWarning={this.onShowFileMaxWarning}
|
||||
onShowFileSizeWarning={this.onShowFileSizeWarning}
|
||||
uploadFiles={this.handleUploadFiles}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return attachmentButton;
|
||||
};
|
||||
|
||||
getInputContainerStyle = () => {
|
||||
const {canUploadFiles, channelIsReadOnly, theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
const inputContainerStyle = [style.inputContainer];
|
||||
|
||||
if (!canUploadFiles) {
|
||||
inputContainerStyle.push(style.inputContainerWithoutFileUpload);
|
||||
}
|
||||
|
||||
if (channelIsReadOnly) {
|
||||
inputContainerStyle.push(style.readonlyContainer);
|
||||
}
|
||||
|
||||
return inputContainerStyle;
|
||||
};
|
||||
|
||||
getPlaceHolder = () => {
|
||||
const {channelIsReadOnly, rootId} = this.props;
|
||||
let placeholder;
|
||||
|
||||
if (channelIsReadOnly) {
|
||||
placeholder = {id: t('mobile.create_post.read_only'), defaultMessage: 'This channel is read-only.'};
|
||||
} else if (rootId) {
|
||||
placeholder = {id: t('create_comment.addComment'), defaultMessage: 'Add a comment...'};
|
||||
} else {
|
||||
placeholder = {id: t('create_post.write'), defaultMessage: 'Write to {channelDisplayName}'};
|
||||
}
|
||||
|
||||
return placeholder;
|
||||
};
|
||||
|
||||
handleAndroidKeyboard = () => {
|
||||
this.blur();
|
||||
};
|
||||
|
|
@ -270,20 +333,31 @@ export default class PostTextbox extends PureComponent {
|
|||
actions,
|
||||
channelId,
|
||||
rootId,
|
||||
cursorPositionEvent,
|
||||
valueEvent,
|
||||
} = this.props;
|
||||
|
||||
// Workaround for some Android keyboards that don't play well with cursors (e.g. Samsung keyboards)
|
||||
if (autocomplete && Platform.OS === 'android' && this.input?.current) {
|
||||
RNTextInputReset.resetKeyboardInput(findNodeHandle(this.input.current));
|
||||
}
|
||||
|
||||
this.checkMessageLength(value);
|
||||
if (!autocomplete && valueEvent) {
|
||||
if (valueEvent) {
|
||||
EventEmitter.emit(valueEvent, value);
|
||||
}
|
||||
|
||||
this.setState({value});
|
||||
const nextState = {value};
|
||||
|
||||
// Workaround for some Android keyboards that don't play well with cursors (e.g. Samsung keyboards)
|
||||
if (autocomplete && this.input?.current) {
|
||||
if (Platform.OS === 'android') {
|
||||
RNTextInputReset.resetKeyboardInput(findNodeHandle(this.input.current));
|
||||
} else {
|
||||
nextState.cursorPosition = value.length;
|
||||
if (cursorPositionEvent) {
|
||||
EventEmitter.emit(cursorPositionEvent, nextState.cursorPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.checkMessageLength(value);
|
||||
|
||||
this.setState(nextState);
|
||||
|
||||
if (value) {
|
||||
actions.userTyping(channelId, rootId);
|
||||
|
|
@ -464,24 +538,26 @@ export default class PostTextbox extends PureComponent {
|
|||
};
|
||||
|
||||
archivedView = (theme, style) => {
|
||||
return (<View style={style.archivedWrapper}>
|
||||
<FormattedMarkdownText
|
||||
id='archivedChannelMessage'
|
||||
defaultMessage='You are viewing an **archived channel**. New messages cannot be posted.'
|
||||
theme={theme}
|
||||
style={style.archivedText}
|
||||
/>
|
||||
<Button
|
||||
containerStyle={style.closeButton}
|
||||
onPress={this.onCloseChannelPress}
|
||||
>
|
||||
<FormattedText
|
||||
id='center_panel.archived.closeChannel'
|
||||
defaultMessage='Close Channel'
|
||||
style={style.closeButtonText}
|
||||
return (
|
||||
<View style={style.archivedWrapper}>
|
||||
<FormattedMarkdownText
|
||||
id='archivedChannelMessage'
|
||||
defaultMessage='You are viewing an **archived channel**. New messages cannot be posted.'
|
||||
theme={theme}
|
||||
style={style.archivedText}
|
||||
/>
|
||||
</Button>
|
||||
</View>);
|
||||
<Button
|
||||
containerStyle={style.closeButton}
|
||||
onPress={this.onCloseChannelPress}
|
||||
>
|
||||
<FormattedText
|
||||
id='center_panel.archived.closeChannel'
|
||||
defaultMessage='Close Channel'
|
||||
style={style.closeButtonText}
|
||||
/>
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
handleLayout = (e) => {
|
||||
|
|
@ -490,110 +566,67 @@ export default class PostTextbox extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
renderDeactivatedChannel = () => {
|
||||
const {intl} = this.context;
|
||||
const {
|
||||
canUploadFiles,
|
||||
channelDisplayName,
|
||||
channelIsLoading,
|
||||
channelIsReadOnly,
|
||||
deactivatedChannel,
|
||||
files,
|
||||
maxFileSize,
|
||||
navigator,
|
||||
rootId,
|
||||
theme,
|
||||
channelIsArchived,
|
||||
} = this.props;
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return (
|
||||
<Text style={style.deactivatedMessage}>
|
||||
{intl.formatMessage({
|
||||
id: 'create_post.deactivated',
|
||||
defaultMessage: 'You are viewing an archived channel with a deactivated user.',
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
renderTextBox = () => {
|
||||
const {intl} = this.context;
|
||||
const {channelDisplayName, channelIsArchived, channelIsLoading, channelIsReadOnly, theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
if (deactivatedChannel) {
|
||||
return (
|
||||
<Text style={style.deactivatedMessage}>
|
||||
{intl.formatMessage({
|
||||
id: 'create_post.deactivated',
|
||||
defaultMessage: 'You are viewing an archived channel with a deactivated user.',
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
|
||||
if (channelIsArchived) {
|
||||
return this.archivedView(theme, style);
|
||||
}
|
||||
|
||||
const {value} = this.state;
|
||||
const textValue = channelIsLoading ? '' : value;
|
||||
|
||||
let placeholder;
|
||||
if (channelIsReadOnly) {
|
||||
placeholder = {id: t('mobile.create_post.read_only'), defaultMessage: 'This channel is read-only.'};
|
||||
} else if (rootId) {
|
||||
placeholder = {id: t('create_comment.addComment'), defaultMessage: 'Add a comment...'};
|
||||
} else {
|
||||
placeholder = {id: t('create_post.write'), defaultMessage: 'Write to {channelDisplayName}'};
|
||||
}
|
||||
|
||||
let attachmentButton = null;
|
||||
const inputContainerStyle = [style.inputContainer];
|
||||
if (canUploadFiles) {
|
||||
attachmentButton = (
|
||||
<AttachmentButton
|
||||
blurTextBox={this.blur}
|
||||
theme={theme}
|
||||
navigator={navigator}
|
||||
fileCount={files.length}
|
||||
maxFileSize={maxFileSize}
|
||||
maxFileCount={MAX_FILE_COUNT}
|
||||
onShowFileMaxWarning={this.onShowFileMaxWarning}
|
||||
onShowFileSizeWarning={this.onShowFileSizeWarning}
|
||||
uploadFiles={this.handleUploadFiles}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
inputContainerStyle.push(style.inputContainerWithoutFileUpload);
|
||||
}
|
||||
|
||||
if (channelIsReadOnly) {
|
||||
inputContainerStyle.push(style.readonlyContainer);
|
||||
}
|
||||
const placeholder = this.getPlaceHolder();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Typing/>
|
||||
{!channelIsArchived && (
|
||||
<View
|
||||
style={style.inputWrapper}
|
||||
onLayout={this.handleLayout}
|
||||
>
|
||||
{!channelIsReadOnly && attachmentButton}
|
||||
<View style={inputContainerStyle}>
|
||||
<TextInput
|
||||
ref={this.input}
|
||||
value={textValue}
|
||||
onChangeText={this.handleTextChange}
|
||||
onSelectionChange={this.handlePostDraftSelectionChanged}
|
||||
placeholder={intl.formatMessage(placeholder, {channelDisplayName})}
|
||||
placeholderTextColor={PLACEHOLDER_COLOR}
|
||||
multiline={true}
|
||||
blurOnSubmit={false}
|
||||
underlineColorAndroid='transparent'
|
||||
style={style.input}
|
||||
keyboardType={this.state.keyboardType}
|
||||
onEndEditing={this.handleEndEditing}
|
||||
disableFullscreenUI={true}
|
||||
editable={!channelIsReadOnly}
|
||||
/>
|
||||
<Fade visible={this.isSendButtonVisible()}>
|
||||
<SendButton
|
||||
disabled={this.isFileLoading()}
|
||||
handleSendMessage={this.handleSendMessage}
|
||||
theme={theme}
|
||||
/>
|
||||
</Fade>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
{channelIsArchived && this.archivedView(theme, style)}
|
||||
</React.Fragment>
|
||||
<View
|
||||
style={style.inputWrapper}
|
||||
onLayout={this.handleLayout}
|
||||
>
|
||||
{this.getAttachmentButton()}
|
||||
<View style={this.getInputContainerStyle()}>
|
||||
<TextInput
|
||||
ref={this.input}
|
||||
value={textValue}
|
||||
onChangeText={this.handleTextChange}
|
||||
onSelectionChange={this.handlePostDraftSelectionChanged}
|
||||
placeholder={intl.formatMessage(placeholder, {channelDisplayName})}
|
||||
placeholderTextColor={PLACEHOLDER_COLOR}
|
||||
multiline={true}
|
||||
blurOnSubmit={false}
|
||||
underlineColorAndroid='transparent'
|
||||
style={style.input}
|
||||
keyboardType={this.state.keyboardType}
|
||||
onEndEditing={this.handleEndEditing}
|
||||
disableFullscreenUI={true}
|
||||
editable={!channelIsReadOnly}
|
||||
/>
|
||||
<Fade visible={this.isSendButtonVisible()}>
|
||||
<SendButton
|
||||
disabled={this.isFileLoading()}
|
||||
handleSendMessage={this.handleSendMessage}
|
||||
theme={theme}
|
||||
/>
|
||||
</Fade>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
57
app/screens/channel/channel.android.js
Normal file
57
app/screens/channel/channel.android.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Dimensions, View} from 'react-native';
|
||||
|
||||
import ChannelLoader from 'app/components/channel_loader';
|
||||
import KeyboardLayout from 'app/components/layout/keyboard_layout';
|
||||
import NetworkIndicator from 'app/components/network_indicator';
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import PostTextbox from 'app/components/post_textbox';
|
||||
import LocalConfig from 'assets/config';
|
||||
|
||||
import ChannelNavBar from './channel_nav_bar';
|
||||
import ChannelPostList from './channel_post_list';
|
||||
|
||||
import ChannelBase, {ClientUpgradeListener, style} from './channel_base';
|
||||
|
||||
export default class ChannelAndroid extends ChannelBase {
|
||||
render() {
|
||||
const {height} = Dimensions.get('window');
|
||||
const {
|
||||
navigator,
|
||||
} = this.props;
|
||||
|
||||
const channelLoaderStyle = [style.channelLoader, {height}];
|
||||
const drawerContent = (
|
||||
<SafeAreaView navigator={navigator}>
|
||||
<StatusBar/>
|
||||
<NetworkIndicator/>
|
||||
<ChannelNavBar
|
||||
navigator={navigator}
|
||||
openChannelDrawer={this.openChannelSidebar}
|
||||
openSettingsDrawer={this.openSettingsSidebar}
|
||||
onPress={this.goToChannelInfo}
|
||||
/>
|
||||
<KeyboardLayout>
|
||||
<View style={style.flex}>
|
||||
<ChannelPostList navigator={navigator}/>
|
||||
</View>
|
||||
<PostTextbox
|
||||
ref={this.postTextbox}
|
||||
navigator={navigator}
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
<ChannelLoader
|
||||
height={height}
|
||||
style={channelLoaderStyle}
|
||||
/>
|
||||
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener navigator={navigator}/>}
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
||||
return this.renderChannel(drawerContent);
|
||||
}
|
||||
}
|
||||
87
app/screens/channel/channel.ios.js
Normal file
87
app/screens/channel/channel.ios.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Dimensions, View} from 'react-native';
|
||||
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
|
||||
|
||||
import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete';
|
||||
import ChannelLoader from 'app/components/channel_loader';
|
||||
import FileUploadPreview from 'app/components/file_upload_preview';
|
||||
import NetworkIndicator from 'app/components/network_indicator';
|
||||
import PostTextbox from 'app/components/post_textbox';
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
|
||||
import LocalConfig from 'assets/config';
|
||||
|
||||
import ChannelBase, {ClientUpgradeListener, style} from './channel_base';
|
||||
import ChannelNavBar from './channel_nav_bar';
|
||||
import ChannelPostList from './channel_post_list';
|
||||
|
||||
const ACCESSORIES_CONTAINER_NATIVE_ID = 'channelAccessoriesContainer';
|
||||
const CHANNEL_POST_TEXTBOX_CURSOR_CHANGE = 'onChannelTextBoxCursorChange';
|
||||
const CHANNEL_POST_TEXTBOX_VALUE_CHANGE = 'onChannelTextBoxValueChange';
|
||||
|
||||
export default class ChannelIOS extends ChannelBase {
|
||||
render() {
|
||||
const {height} = Dimensions.get('window');
|
||||
const {
|
||||
currentChannelId,
|
||||
navigator,
|
||||
} = this.props;
|
||||
|
||||
const channelLoaderStyle = [style.channelLoader, {height}];
|
||||
if ((DeviceTypes.IS_IPHONE_X || DeviceTypes.IS_TABLET)) {
|
||||
channelLoaderStyle.push(style.iOSHomeIndicator);
|
||||
}
|
||||
|
||||
const drawerContent = (
|
||||
<React.Fragment>
|
||||
<SafeAreaView navigator={navigator}>
|
||||
<StatusBar/>
|
||||
<NetworkIndicator/>
|
||||
<ChannelNavBar
|
||||
navigator={navigator}
|
||||
openChannelDrawer={this.openChannelSidebar}
|
||||
openSettingsDrawer={this.openSettingsSidebar}
|
||||
onPress={this.goToChannelInfo}
|
||||
/>
|
||||
<ChannelPostList
|
||||
navigator={navigator}
|
||||
updateNativeScrollView={this.updateNativeScrollView}
|
||||
/>
|
||||
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
|
||||
<FileUploadPreview channelId={currentChannelId}/>
|
||||
<Autocomplete
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.handleAutoComplete}
|
||||
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
|
||||
/>
|
||||
</View>
|
||||
<ChannelLoader
|
||||
height={height}
|
||||
style={channelLoaderStyle}
|
||||
/>
|
||||
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener navigator={navigator}/>}
|
||||
</SafeAreaView>
|
||||
<KeyboardTrackingView
|
||||
ref={this.keyboardTracker}
|
||||
scrollViewNativeID={currentChannelId}
|
||||
accessoriesContainerID={ACCESSORIES_CONTAINER_NATIVE_ID}
|
||||
>
|
||||
<PostTextbox
|
||||
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
|
||||
ref={this.postTextbox}
|
||||
navigator={navigator}
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
return this.renderChannel(drawerContent);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,47 +5,33 @@ import React, {PureComponent} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Dimensions,
|
||||
Keyboard,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
|
||||
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import {app} from 'app/mattermost';
|
||||
|
||||
import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete';
|
||||
import InteractiveDialogController from 'app/components/interactive_dialog_controller';
|
||||
import ChannelLoader from 'app/components/channel_loader';
|
||||
import EmptyToolbar from 'app/components/start/empty_toolbar';
|
||||
import FileUploadPreview from 'app/components/file_upload_preview';
|
||||
import InteractiveDialogController from 'app/components/interactive_dialog_controller';
|
||||
import MainSidebar from 'app/components/sidebars/main';
|
||||
import SettingsSidebar from 'app/components/sidebars/settings';
|
||||
import NetworkIndicator from 'app/components/network_indicator';
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import SettingsSidebar from 'app/components/sidebars/settings';
|
||||
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import PostTextbox from 'app/components/post_textbox';
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
import tracker from 'app/utils/time_tracker';
|
||||
import LocalConfig from 'assets/config';
|
||||
|
||||
import telemetry from 'app/telemetry';
|
||||
|
||||
import ChannelNavBar from './channel_nav_bar';
|
||||
import ChannelPostList from './channel_post_list';
|
||||
import LocalConfig from 'assets/config';
|
||||
|
||||
const CHANNEL_POST_TEXTBOX_CURSOR_CHANGE = 'onChannelTextBoxCursorChange';
|
||||
const CHANNEL_POST_TEXTBOX_VALUE_CHANGE = 'onChannelTextBoxValueChange';
|
||||
export let ClientUpgradeListener;
|
||||
|
||||
let ClientUpgradeListener;
|
||||
|
||||
export default class Channel extends PureComponent {
|
||||
export default class ChannelBase extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
loadChannelsIfNecessary: PropTypes.func.isRequired,
|
||||
|
|
@ -273,7 +259,7 @@ export default class Channel extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
renderChannel(drawerContent) {
|
||||
const {
|
||||
channelsRequestFailed,
|
||||
currentChannelId,
|
||||
|
|
@ -282,8 +268,6 @@ export default class Channel extends PureComponent {
|
|||
theme,
|
||||
} = this.props;
|
||||
|
||||
const {height} = Dimensions.get('window');
|
||||
|
||||
if (!currentChannelId) {
|
||||
if (channelsRequestFailed) {
|
||||
const PostListRetry = require('app/components/post_list_retry').default;
|
||||
|
|
@ -309,11 +293,6 @@ export default class Channel extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const channelLoaderStyle = [style.channelLoader, {height}];
|
||||
if (Platform.OS === 'ios' && (DeviceTypes.IS_IPHONE_X || DeviceTypes.IS_TABLET)) {
|
||||
channelLoaderStyle.push(style.iOSHomeIndicator);
|
||||
}
|
||||
|
||||
return (
|
||||
<MainSidebar
|
||||
ref={this.channelSidebarRef}
|
||||
|
|
@ -325,43 +304,7 @@ export default class Channel extends PureComponent {
|
|||
blurPostTextBox={this.blurPostTextBox}
|
||||
navigator={navigator}
|
||||
>
|
||||
<SafeAreaView navigator={navigator}>
|
||||
<StatusBar/>
|
||||
<NetworkIndicator/>
|
||||
<ChannelNavBar
|
||||
navigator={navigator}
|
||||
openChannelDrawer={this.openChannelSidebar}
|
||||
openSettingsDrawer={this.openSettingsSidebar}
|
||||
onPress={this.goToChannelInfo}
|
||||
/>
|
||||
<ChannelPostList
|
||||
navigator={navigator}
|
||||
updateNativeScrollView={this.updateNativeScrollView}
|
||||
/>
|
||||
<FileUploadPreview channelId={currentChannelId}/>
|
||||
<Autocomplete
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.handleAutoComplete}
|
||||
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
|
||||
/>
|
||||
<ChannelLoader
|
||||
height={height}
|
||||
style={channelLoaderStyle}
|
||||
/>
|
||||
{LocalConfig.EnableMobileClientUpgrade && <ClientUpgradeListener navigator={navigator}/>}
|
||||
</SafeAreaView>
|
||||
<KeyboardTrackingView
|
||||
ref={this.keyboardTracker}
|
||||
scrollViewNativeID={currentChannelId}
|
||||
>
|
||||
<PostTextbox
|
||||
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
|
||||
ref={this.postTextbox}
|
||||
navigator={navigator}
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
{drawerContent}
|
||||
</SettingsSidebar>
|
||||
<InteractiveDialogController
|
||||
navigator={navigator}
|
||||
|
|
@ -372,7 +315,7 @@ export default class Channel extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
export const style = StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
|
|
@ -89,7 +89,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
this.props.actions.recordLoadTime('Switch Channel', 'channelSwitch');
|
||||
}
|
||||
|
||||
if (!prevProps.postIds?.length && this.props.postIds?.length > 0) {
|
||||
if (!prevProps.postIds?.length && this.props.postIds?.length > 0 && this.props.updateNativeScrollView) {
|
||||
// This is needed to re-bind the scrollview natively when getting the first posts
|
||||
this.props.updateNativeScrollView();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ export default class PostOptions extends PureComponent {
|
|||
const {navigator, theme} = this.props;
|
||||
|
||||
this.close();
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).then((source) => {
|
||||
navigator.showModal({
|
||||
screen: 'AddReaction',
|
||||
|
|
@ -288,7 +288,7 @@ export default class PostOptions extends PureComponent {
|
|||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 300);
|
||||
};
|
||||
|
||||
handleReply = () => {
|
||||
|
|
@ -368,7 +368,7 @@ export default class PostOptions extends PureComponent {
|
|||
const {navigator, post, theme} = this.props;
|
||||
|
||||
this.close();
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).then((source) => {
|
||||
navigator.showModal({
|
||||
screen: 'EditPost',
|
||||
|
|
@ -386,7 +386,7 @@ export default class PostOptions extends PureComponent {
|
|||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 300);
|
||||
};
|
||||
|
||||
handleUnflagPost = () => {
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ exports[`thread should match snapshot, has root post 1`] = `
|
|||
<React.Fragment>
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<React.Fragment>
|
||||
<Connect(PostList)
|
||||
currentUserId="member_user_id"
|
||||
indicateNewMessages={false}
|
||||
lastPostIndex={-1}
|
||||
lastPostIndex={2}
|
||||
lastViewedAt={0}
|
||||
location="thread"
|
||||
navigator={
|
||||
Object {
|
||||
|
|
@ -52,20 +52,25 @@ exports[`thread should match snapshot, has root post 1`] = `
|
|||
}
|
||||
scrollViewNativeID="threadPostList"
|
||||
/>
|
||||
<Connect(FileUploadPreview)
|
||||
channelId="channel_id"
|
||||
rootId="root_id"
|
||||
/>
|
||||
<ForwardRef(forwardConnectRef)
|
||||
cursorPositionEvent="onThreadTextBoxCursorChange"
|
||||
maxHeight={200}
|
||||
onChangeText={[Function]}
|
||||
rootId="root_id"
|
||||
valueEvent="onThreadTextBoxValueChange"
|
||||
/>
|
||||
<View
|
||||
nativeID="threadAccessoriesContainer"
|
||||
>
|
||||
<Connect(FileUploadPreview)
|
||||
channelId="channel_id"
|
||||
rootId="root_id"
|
||||
/>
|
||||
<ForwardRef(forwardConnectRef)
|
||||
cursorPositionEvent="onThreadTextBoxCursorChange"
|
||||
maxHeight={200}
|
||||
onChangeText={[Function]}
|
||||
rootId="root_id"
|
||||
valueEvent="onThreadTextBoxValueChange"
|
||||
/>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
</Connect(SafeAreaIos)>
|
||||
<KeyboardTrackingView
|
||||
accessoriesContainerID="threadAccessoriesContainer"
|
||||
scrollViewNativeID="threadPostList"
|
||||
>
|
||||
<ForwardRef(forwardConnectRef)
|
||||
|
|
@ -106,7 +111,6 @@ exports[`thread should match snapshot, no root post, loading 1`] = `
|
|||
<React.Fragment>
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<Loading
|
||||
|
|
@ -122,7 +126,8 @@ exports[`thread should match snapshot, render footer 1`] = `
|
|||
<Connect(PostList)
|
||||
currentUserId="member_user_id"
|
||||
indicateNewMessages={false}
|
||||
lastPostIndex={-1}
|
||||
lastPostIndex={2}
|
||||
lastViewedAt={0}
|
||||
location="thread"
|
||||
navigator={
|
||||
Object {
|
||||
|
|
@ -169,7 +174,7 @@ exports[`thread should match snapshot, render footer 2`] = `
|
|||
<Connect(PostList)
|
||||
currentUserId="member_user_id"
|
||||
indicateNewMessages={false}
|
||||
lastPostIndex={-1}
|
||||
lastPostIndex={2}
|
||||
lastViewedAt={0}
|
||||
location="thread"
|
||||
navigator={
|
||||
|
|
@ -211,7 +216,6 @@ exports[`thread should match snapshot, render footer 3`] = `
|
|||
<React.Fragment>
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<Loading
|
||||
70
app/screens/thread/thread.android.js
Normal file
70
app/screens/thread/thread.android.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {THREAD} from 'app/constants/screen';
|
||||
|
||||
import Loading from 'app/components/loading';
|
||||
import KeyboardLayout from 'app/components/layout/keyboard_layout';
|
||||
import PostList from 'app/components/post_list';
|
||||
import PostTextbox from 'app/components/post_textbox';
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
|
||||
import ThreadBase from './thread_base';
|
||||
|
||||
export default class ThreadAndroid extends ThreadBase {
|
||||
render() {
|
||||
const {
|
||||
channelId,
|
||||
myMember,
|
||||
navigator,
|
||||
postIds,
|
||||
rootId,
|
||||
channelIsArchived,
|
||||
} = this.props;
|
||||
|
||||
let content;
|
||||
let postTextBox;
|
||||
if (this.hasRootPost()) {
|
||||
content = (
|
||||
<PostList
|
||||
renderFooter={this.renderFooter()}
|
||||
indicateNewMessages={false}
|
||||
postIds={postIds}
|
||||
currentUserId={myMember && myMember.user_id}
|
||||
lastViewedAt={this.state.lastViewedAt}
|
||||
lastPostIndex={-1}
|
||||
navigator={navigator}
|
||||
onPostPress={this.hideKeyboard}
|
||||
location={THREAD}
|
||||
/>
|
||||
);
|
||||
|
||||
postTextBox = (
|
||||
<PostTextbox
|
||||
channelIsArchived={channelIsArchived}
|
||||
rootId={rootId}
|
||||
channelId={channelId}
|
||||
navigator={navigator}
|
||||
onCloseChannel={this.onCloseChannel}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<Loading/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<StatusBar/>
|
||||
<KeyboardLayout>
|
||||
{content}
|
||||
{postTextBox}
|
||||
</KeyboardLayout>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
104
app/screens/thread/thread.ios.js
Normal file
104
app/screens/thread/thread.ios.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {View} from 'react-native';
|
||||
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
|
||||
|
||||
import {getLastPostIndex} from 'mattermost-redux/utils/post_list';
|
||||
|
||||
import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete';
|
||||
import FileUploadPreview from 'app/components/file_upload_preview';
|
||||
import Loading from 'app/components/loading';
|
||||
import PostList from 'app/components/post_list';
|
||||
import PostTextbox from 'app/components/post_textbox';
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {THREAD} from 'app/constants/screen';
|
||||
|
||||
import ThreadBase from './thread_base';
|
||||
|
||||
const ACCESSORIES_CONTAINER_NATIVE_ID = 'threadAccessoriesContainer';
|
||||
const THREAD_POST_TEXTBOX_CURSOR_CHANGE = 'onThreadTextBoxCursorChange';
|
||||
const THREAD_POST_TEXTBOX_VALUE_CHANGE = 'onThreadTextBoxValueChange';
|
||||
const SCROLLVIEW_NATIVE_ID = 'threadPostList';
|
||||
|
||||
export default class ThreadIOS extends ThreadBase {
|
||||
render() {
|
||||
const {
|
||||
channelId,
|
||||
myMember,
|
||||
navigator,
|
||||
postIds,
|
||||
rootId,
|
||||
channelIsArchived,
|
||||
} = this.props;
|
||||
|
||||
let content;
|
||||
let postTextBox;
|
||||
if (this.hasRootPost()) {
|
||||
content = (
|
||||
<React.Fragment>
|
||||
<PostList
|
||||
renderFooter={this.renderFooter()}
|
||||
indicateNewMessages={false}
|
||||
postIds={postIds}
|
||||
lastPostIndex={getLastPostIndex(postIds)}
|
||||
currentUserId={myMember && myMember.user_id}
|
||||
lastViewedAt={this.state.lastViewedAt}
|
||||
navigator={navigator}
|
||||
onPostPress={this.hideKeyboard}
|
||||
location={THREAD}
|
||||
scrollViewNativeID={SCROLLVIEW_NATIVE_ID}
|
||||
/>
|
||||
<View nativeID={ACCESSORIES_CONTAINER_NATIVE_ID}>
|
||||
<FileUploadPreview
|
||||
channelId={channelId}
|
||||
rootId={rootId}
|
||||
/>
|
||||
<Autocomplete
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.handleAutoComplete}
|
||||
cursorPositionEvent={THREAD_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={THREAD_POST_TEXTBOX_VALUE_CHANGE}
|
||||
rootId={rootId}
|
||||
/>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
postTextBox = (
|
||||
<KeyboardTrackingView
|
||||
scrollViewNativeID={SCROLLVIEW_NATIVE_ID}
|
||||
accessoriesContainerID={ACCESSORIES_CONTAINER_NATIVE_ID}
|
||||
>
|
||||
<PostTextbox
|
||||
ref={this.postTextbox}
|
||||
channelIsArchived={channelIsArchived}
|
||||
rootId={rootId}
|
||||
channelId={channelId}
|
||||
navigator={navigator}
|
||||
onCloseChannel={this.onCloseChannel}
|
||||
cursorPositionEvent={THREAD_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={THREAD_POST_TEXTBOX_VALUE_CHANGE}
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<Loading/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SafeAreaView excludeHeader={true}>
|
||||
<StatusBar/>
|
||||
{content}
|
||||
</SafeAreaView>
|
||||
{postTextBox}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import Preferences from 'mattermost-redux/constants/preferences';
|
|||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
import PostList from 'app/components/post_list';
|
||||
|
||||
import Thread from './thread.js';
|
||||
import ThreadIOS from './thread.ios';
|
||||
|
||||
jest.mock('react-intl');
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ describe('thread', () => {
|
|||
|
||||
test('should match snapshot, has root post', () => {
|
||||
const wrapper = shallow(
|
||||
<Thread {...baseProps}/>,
|
||||
<ThreadIOS {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
|
|
@ -46,7 +46,7 @@ describe('thread', () => {
|
|||
test('should match snapshot, no root post, loading', () => {
|
||||
const newPostIds = ['post_id_1', 'post_id_2'];
|
||||
const wrapper = shallow(
|
||||
<Thread
|
||||
<ThreadIOS
|
||||
{...baseProps}
|
||||
postIds={newPostIds}
|
||||
/>,
|
||||
|
|
@ -75,7 +75,7 @@ describe('thread', () => {
|
|||
};
|
||||
const newNavigator = {...navigator};
|
||||
const wrapper = shallow(
|
||||
<Thread
|
||||
<ThreadIOS
|
||||
{...baseProps}
|
||||
navigator={newNavigator}
|
||||
/>,
|
||||
|
|
@ -88,7 +88,7 @@ describe('thread', () => {
|
|||
|
||||
test('should match snapshot, render footer', () => {
|
||||
const wrapper = shallow(
|
||||
<Thread {...baseProps}/>,
|
||||
<ThreadIOS {...baseProps}/>,
|
||||
{context: {intl: {formatMessage: jest.fn()}}},
|
||||
);
|
||||
|
||||
|
|
@ -5,28 +5,14 @@ import React, {PureComponent} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {Keyboard, Platform} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
|
||||
|
||||
import {General, RequestStatus} from 'mattermost-redux/constants';
|
||||
import {getLastPostIndex} from 'mattermost-redux/utils/post_list';
|
||||
|
||||
import {THREAD} from 'app/constants/screen';
|
||||
|
||||
import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete';
|
||||
import FileUploadPreview from 'app/components/file_upload_preview';
|
||||
import Loading from 'app/components/loading';
|
||||
import PostList from 'app/components/post_list';
|
||||
import PostTextbox from 'app/components/post_textbox';
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {setNavigatorStyles} from 'app/utils/theme';
|
||||
import DeletedPost from 'app/components/deleted_post';
|
||||
|
||||
const THREAD_POST_TEXTBOX_CURSOR_CHANGE = 'onThreadTextBoxCursorChange';
|
||||
const THREAD_POST_TEXTBOX_VALUE_CHANGE = 'onThreadTextBoxValueChange';
|
||||
const SCROLLVIEW_NATIVE_ID = 'threadPostList';
|
||||
|
||||
export default class Thread extends PureComponent {
|
||||
export default class ThreadBase extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
selectPost: PropTypes.func.isRequired,
|
||||
|
|
@ -47,8 +33,6 @@ export default class Thread extends PureComponent {
|
|||
postIds: [],
|
||||
};
|
||||
|
||||
state = {};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
};
|
||||
|
|
@ -71,6 +55,10 @@ export default class Thread extends PureComponent {
|
|||
this.props.navigator.setTitle({
|
||||
title,
|
||||
});
|
||||
|
||||
this.state = {
|
||||
lastViewedAt: props.myMember && props.myMember.last_viewed_at,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
|
@ -153,79 +141,4 @@ export default class Thread extends PureComponent {
|
|||
},
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
channelId,
|
||||
myMember,
|
||||
navigator,
|
||||
postIds,
|
||||
rootId,
|
||||
channelIsArchived,
|
||||
} = this.props;
|
||||
|
||||
let content;
|
||||
let postTextBox;
|
||||
if (this.hasRootPost()) {
|
||||
content = (
|
||||
<React.Fragment>
|
||||
<PostList
|
||||
renderFooter={this.renderFooter()}
|
||||
indicateNewMessages={false}
|
||||
postIds={postIds}
|
||||
lastPostIndex={Platform.OS === 'android' ? getLastPostIndex(postIds) : -1}
|
||||
currentUserId={myMember && myMember.user_id}
|
||||
lastViewedAt={this.state.lastViewedAt}
|
||||
navigator={navigator}
|
||||
onPostPress={this.hideKeyboard}
|
||||
location={THREAD}
|
||||
scrollViewNativeID={SCROLLVIEW_NATIVE_ID}
|
||||
/>
|
||||
<FileUploadPreview
|
||||
channelId={channelId}
|
||||
rootId={rootId}
|
||||
/>
|
||||
<Autocomplete
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.handleAutoComplete}
|
||||
cursorPositionEvent={THREAD_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={THREAD_POST_TEXTBOX_VALUE_CHANGE}
|
||||
rootId={rootId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
postTextBox = (
|
||||
<KeyboardTrackingView scrollViewNativeID={SCROLLVIEW_NATIVE_ID}>
|
||||
<PostTextbox
|
||||
ref={this.postTextbox}
|
||||
channelIsArchived={channelIsArchived}
|
||||
rootId={rootId}
|
||||
channelId={channelId}
|
||||
navigator={navigator}
|
||||
onCloseChannel={this.onCloseChannel}
|
||||
cursorPositionEvent={THREAD_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={THREAD_POST_TEXTBOX_VALUE_CHANGE}
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<Loading/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SafeAreaView
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<StatusBar/>
|
||||
{content}
|
||||
</SafeAreaView>
|
||||
{postTextBox}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -15552,8 +15552,8 @@
|
|||
}
|
||||
},
|
||||
"react-native-keyboard-tracking-view": {
|
||||
"version": "github:enahum/react-native-keyboard-tracking-view#60c8a81e3ff2ad5a17d969f235e5990c88d465d6",
|
||||
"from": "github:enahum/react-native-keyboard-tracking-view#60c8a81e3ff2ad5a17d969f235e5990c88d465d6"
|
||||
"version": "github:enahum/react-native-keyboard-tracking-view#66979a750e42fefe06366f0c924b29368bb586f2",
|
||||
"from": "github:enahum/react-native-keyboard-tracking-view#66979a750e42fefe06366f0c924b29368bb586f2"
|
||||
},
|
||||
"react-native-keychain": {
|
||||
"version": "3.1.3",
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
"react-native-image-gallery": "github:mattermost/react-native-image-gallery#c1a9f7118e90cc87d47620bc0584c9cac4b0cf38",
|
||||
"react-native-image-picker": "0.28.1",
|
||||
"react-native-keyboard-aware-scroll-view": "0.8.0",
|
||||
"react-native-keyboard-tracking-view": "github:enahum/react-native-keyboard-tracking-view#60c8a81e3ff2ad5a17d969f235e5990c88d465d6",
|
||||
"react-native-keyboard-tracking-view": "github:enahum/react-native-keyboard-tracking-view#66979a750e42fefe06366f0c924b29368bb586f2",
|
||||
"react-native-keychain": "3.1.3",
|
||||
"react-native-linear-gradient": "2.5.4",
|
||||
"react-native-local-auth": "github:mattermost/react-native-local-auth#cc9ce2f468fbf7b431dfad3191a31aaa9227a6ab",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ module.exports = [
|
|||
'app/components/formatted_markdown_text.js',
|
||||
'app/components/formatted_text.js',
|
||||
'app/components/formatted_time.js',
|
||||
'app/components/interactive_dialog_controller/index.js',
|
||||
'app/components/interactive_dialog_controller/interactive_dialog_controller.js',
|
||||
'app/components/layout/keyboard_layout/index.js',
|
||||
'app/components/layout/keyboard_layout/keyboard_layout.js',
|
||||
'app/components/loading.js',
|
||||
|
|
@ -45,6 +47,7 @@ module.exports = [
|
|||
'app/components/markdown/markdown_table_row/markdown_table_row.js',
|
||||
'app/components/network_indicator/index.js',
|
||||
'app/components/network_indicator/network_indicator.js',
|
||||
'app/components/paper_plane.js',
|
||||
'app/components/post/index.js',
|
||||
'app/components/post/post.js',
|
||||
'app/components/post_body/index.js',
|
||||
|
|
@ -60,8 +63,11 @@ module.exports = [
|
|||
'app/components/post_list_retry.js',
|
||||
'app/components/post_profile_picture/index.js',
|
||||
'app/components/post_profile_picture/post_profile_picture.js',
|
||||
'app/components/post_textbox/components/typing/index.js',
|
||||
'app/components/post_textbox/components/typing/typing.js',
|
||||
'app/components/post_textbox/index.js',
|
||||
'app/components/post_textbox/post_textbox.js',
|
||||
'app/components/post_textbox/post_textbox.android.js',
|
||||
'app/components/post_textbox/post_textbox_base.js',
|
||||
'app/components/profile_picture/index.js',
|
||||
'app/components/profile_picture/profile_picture.js',
|
||||
'app/components/progressive_image/index.js',
|
||||
|
|
@ -69,6 +75,7 @@ module.exports = [
|
|||
'app/components/reply_icon.js',
|
||||
'app/components/safe_area_view/index.js',
|
||||
'app/components/safe_area_view/safe_area_view.android.js',
|
||||
'app/components/send_button.js',
|
||||
'app/components/show_more_button/index.js',
|
||||
'app/components/show_more_button/show_more_button.js',
|
||||
'app/components/start/empty_toolbar.js',
|
||||
|
|
@ -121,7 +128,8 @@ module.exports = [
|
|||
'app/reducers/views/team.js',
|
||||
'app/reducers/views/thread.js',
|
||||
'app/reducers/views/user.js',
|
||||
'app/screens/channel/channel.js',
|
||||
'app/screens/channel/channel.android.js',
|
||||
'app/screens/channel/channel_base.js',
|
||||
'app/screens/channel/channel_nav_bar/channel_drawer_button.js',
|
||||
'app/screens/channel/channel_nav_bar/channel_nav_bar.js',
|
||||
'app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/app/components/formatted_markdown_text.js',
|
||||
'./node_modules/app/components/formatted_text.js',
|
||||
'./node_modules/app/components/formatted_time.js',
|
||||
'./node_modules/app/components/interactive_dialog_controller/index.js',
|
||||
'./node_modules/app/components/interactive_dialog_controller/interactive_dialog_controller.js',
|
||||
'./node_modules/app/components/layout/keyboard_layout/index.js',
|
||||
'./node_modules/app/components/layout/keyboard_layout/keyboard_layout.js',
|
||||
'./node_modules/app/components/loading.js',
|
||||
|
|
@ -43,6 +45,7 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/app/components/markdown/markdown_table_row/markdown_table_row.js',
|
||||
'./node_modules/app/components/network_indicator/index.js',
|
||||
'./node_modules/app/components/network_indicator/network_indicator.js',
|
||||
'./node_modules/app/components/paper_plane.js',
|
||||
'./node_modules/app/components/post/index.js',
|
||||
'./node_modules/app/components/post/post.js',
|
||||
'./node_modules/app/components/post_body/index.js',
|
||||
|
|
@ -58,8 +61,11 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/app/components/post_list_retry.js',
|
||||
'./node_modules/app/components/post_profile_picture/index.js',
|
||||
'./node_modules/app/components/post_profile_picture/post_profile_picture.js',
|
||||
'./node_modules/app/components/post_textbox/components/typing/index.js',
|
||||
'./node_modules/app/components/post_textbox/components/typing/typing.js',
|
||||
'./node_modules/app/components/post_textbox/index.js',
|
||||
'./node_modules/app/components/post_textbox/post_textbox.js',
|
||||
'./node_modules/app/components/post_textbox/post_textbox.android.js',
|
||||
'./node_modules/app/components/post_textbox/post_textbox_base.js',
|
||||
'./node_modules/app/components/profile_picture/index.js',
|
||||
'./node_modules/app/components/profile_picture/profile_picture.js',
|
||||
'./node_modules/app/components/progressive_image/index.js',
|
||||
|
|
@ -67,6 +73,7 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/app/components/reply_icon.js',
|
||||
'./node_modules/app/components/safe_area_view/index.js',
|
||||
'./node_modules/app/components/safe_area_view/safe_area_view.android.js',
|
||||
'./node_modules/app/components/send_button.js',
|
||||
'./node_modules/app/components/show_more_button/index.js',
|
||||
'./node_modules/app/components/show_more_button/show_more_button.js',
|
||||
'./node_modules/app/components/start/empty_toolbar.js',
|
||||
|
|
@ -119,7 +126,8 @@ module.exports = ['./node_modules/app/app.js',
|
|||
'./node_modules/app/reducers/views/team.js',
|
||||
'./node_modules/app/reducers/views/thread.js',
|
||||
'./node_modules/app/reducers/views/user.js',
|
||||
'./node_modules/app/screens/channel/channel.js',
|
||||
'./node_modules/app/screens/channel/channel.android.js',
|
||||
'./node_modules/app/screens/channel/channel_base.js',
|
||||
'./node_modules/app/screens/channel/channel_nav_bar/channel_drawer_button.js',
|
||||
'./node_modules/app/screens/channel/channel_nav_bar/channel_nav_bar.js',
|
||||
'./node_modules/app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js',
|
||||
|
|
|
|||
Loading…
Reference in a new issue