diff --git a/app/components/post_body/post_body.js b/app/components/post_body/post_body.js
index c09504003..018905393 100644
--- a/app/components/post_body/post_body.js
+++ b/app/components/post_body/post_body.js
@@ -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) => {
diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js
index 7e7f7cf1e..3cc733f04 100644
--- a/app/components/post_list/post_list.js
+++ b/app/components/post_list/post_list.js
@@ -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) => {
diff --git a/app/components/post_textbox/post_textbox.android.js b/app/components/post_textbox/post_textbox.android.js
new file mode 100644
index 000000000..61f8f622a
--- /dev/null
+++ b/app/components/post_textbox/post_textbox.android.js
@@ -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 (
+
+
+
+
+ {this.renderTextBox()}
+
+ );
+ }
+}
diff --git a/app/components/post_textbox/post_textbox.ios.js b/app/components/post_textbox/post_textbox.ios.js
new file mode 100644
index 000000000..bdf842a71
--- /dev/null
+++ b/app/components/post_textbox/post_textbox.ios.js
@@ -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 (
+
+
+ {this.renderTextBox()}
+
+ );
+ }
+}
diff --git a/app/components/post_textbox/post_textbox.js b/app/components/post_textbox/post_textbox_base.js
similarity index 82%
rename from app/components/post_textbox/post_textbox.js
rename to app/components/post_textbox/post_textbox_base.js
index 3df81ebf3..d9d884743 100644
--- a/app/components/post_textbox/post_textbox.js
+++ b/app/components/post_textbox/post_textbox_base.js
@@ -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 = (
+
+ );
+ }
+
+ 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 (
-
-
- );
+
+
+ );
};
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 (
+
+ {intl.formatMessage({
+ id: 'create_post.deactivated',
+ defaultMessage: 'You are viewing an archived channel with a deactivated user.',
+ })}
+
+ );
+ }
+
+ renderTextBox = () => {
+ const {intl} = this.context;
+ const {channelDisplayName, channelIsArchived, channelIsLoading, channelIsReadOnly, theme} = this.props;
const style = getStyleSheet(theme);
- if (deactivatedChannel) {
- return (
-
- {intl.formatMessage({
- id: 'create_post.deactivated',
- defaultMessage: 'You are viewing an archived channel with a deactivated user.',
- })}
-
- );
+
+ 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 = (
-
- );
- } else {
- inputContainerStyle.push(style.inputContainerWithoutFileUpload);
- }
-
- if (channelIsReadOnly) {
- inputContainerStyle.push(style.readonlyContainer);
- }
+ const placeholder = this.getPlaceHolder();
return (
-
-
- {!channelIsArchived && (
-
- {!channelIsReadOnly && attachmentButton}
-
-
-
-
-
-
-
- )}
- {channelIsArchived && this.archivedView(theme, style)}
-
+
+ {this.getAttachmentButton()}
+
+
+
+
+
+
+
);
- }
+ };
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
diff --git a/app/screens/channel/channel.android.js b/app/screens/channel/channel.android.js
new file mode 100644
index 000000000..14748150e
--- /dev/null
+++ b/app/screens/channel/channel.android.js
@@ -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 = (
+
+
+
+
+
+
+
+
+
+
+
+ {LocalConfig.EnableMobileClientUpgrade && }
+
+ );
+
+ return this.renderChannel(drawerContent);
+ }
+}
diff --git a/app/screens/channel/channel.ios.js b/app/screens/channel/channel.ios.js
new file mode 100644
index 000000000..3e28b1e26
--- /dev/null
+++ b/app/screens/channel/channel.ios.js
@@ -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 = (
+
+
+
+
+
+
+
+
+
+
+
+ {LocalConfig.EnableMobileClientUpgrade && }
+
+
+
+
+
+ );
+
+ return this.renderChannel(drawerContent);
+ }
+}
diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel_base.js
similarity index 76%
rename from app/screens/channel/channel.js
rename to app/screens/channel/channel_base.js
index 27a8aa83b..da40eacda 100644
--- a/app/screens/channel/channel.js
+++ b/app/screens/channel/channel_base.js
@@ -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 (
-
-
-
-
-
-
-
-
- {LocalConfig.EnableMobileClientUpgrade && }
-
-
-
-
+ {drawerContent}
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();
}
diff --git a/app/screens/post_options/post_options.js b/app/screens/post_options/post_options.js
index a4eb50384..cdc701fbe 100644
--- a/app/screens/post_options/post_options.js
+++ b/app/screens/post_options/post_options.js
@@ -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 = () => {
diff --git a/app/screens/thread/__snapshots__/thread.test.js.snap b/app/screens/thread/__snapshots__/thread.ios.test.js.snap
similarity index 88%
rename from app/screens/thread/__snapshots__/thread.test.js.snap
rename to app/screens/thread/__snapshots__/thread.ios.test.js.snap
index 6becaae55..c1773e4e6 100644
--- a/app/screens/thread/__snapshots__/thread.test.js.snap
+++ b/app/screens/thread/__snapshots__/thread.ios.test.js.snap
@@ -4,14 +4,14 @@ exports[`thread should match snapshot, has root post 1`] = `
-
-
+
+
+
+
+ );
+
+ postTextBox = (
+
+ );
+ } else {
+ content = (
+
+ );
+ }
+
+ return (
+
+
+
+ {content}
+ {postTextBox}
+
+
+ );
+ }
+}
diff --git a/app/screens/thread/thread.ios.js b/app/screens/thread/thread.ios.js
new file mode 100644
index 000000000..3b6db17ba
--- /dev/null
+++ b/app/screens/thread/thread.ios.js
@@ -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 = (
+
+
+
+
+
+
+
+ );
+
+ postTextBox = (
+
+
+
+ );
+ } else {
+ content = (
+
+ );
+ }
+
+ return (
+
+
+
+ {content}
+
+ {postTextBox}
+
+ );
+ }
+}
diff --git a/app/screens/thread/thread.test.js b/app/screens/thread/thread.ios.test.js
similarity index 95%
rename from app/screens/thread/thread.test.js
rename to app/screens/thread/thread.ios.test.js
index bad3a0235..309b71088 100644
--- a/app/screens/thread/thread.test.js
+++ b/app/screens/thread/thread.ios.test.js
@@ -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(
- ,
+ ,
{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(
- ,
@@ -75,7 +75,7 @@ describe('thread', () => {
};
const newNavigator = {...navigator};
const wrapper = shallow(
- ,
@@ -88,7 +88,7 @@ describe('thread', () => {
test('should match snapshot, render footer', () => {
const wrapper = shallow(
- ,
+ ,
{context: {intl: {formatMessage: jest.fn()}}},
);
diff --git a/app/screens/thread/thread.js b/app/screens/thread/thread_base.js
similarity index 53%
rename from app/screens/thread/thread.js
rename to app/screens/thread/thread_base.js
index 5fb8a1396..95718d020 100644
--- a/app/screens/thread/thread.js
+++ b/app/screens/thread/thread_base.js
@@ -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 = (
-
-
-
-
-
- );
-
- postTextBox = (
-
-
-
- );
- } else {
- content = (
-
- );
- }
-
- return (
-
-
-
- {content}
-
- {postTextBox}
-
- );
- }
}
diff --git a/package-lock.json b/package-lock.json
index f83783d42..780b99c5b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 409c5394e..2ebbf9603 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/packager/moduleNames.js b/packager/moduleNames.js
index 2fd530343..2b780a989 100644
--- a/packager/moduleNames.js
+++ b/packager/moduleNames.js
@@ -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',
diff --git a/packager/modulePaths.js b/packager/modulePaths.js
index dafe7f76a..0f657849a 100644
--- a/packager/modulePaths.js
+++ b/packager/modulePaths.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',