MM-9494 & MM-13888 Tapping execute actions & interactive keyboard dismissal (#2799)
* MM-9494 & MM-13888 Tapping with the keyboard opened executes the action & iOS iteractive keyboard * Fix tests * feedback review * add new line at the end of file * feedback review and added todo list * Track interactive dismiss keyboard and set scrollview bounds natively * Fix snapshots * Fastlane default to current branch when no BRANCH_TO_BUILD is set * Set NODE_OPTIONS in ios build script * Rebind scrollview when channel gets first set of posts * Keep scrolling momentum on keyboard close * Update react-native-keyboard-tracking-view * Fix ScrollView offset with keyboard-tracking * Fix offset while dragging the keyboard * Allow action on channel drawer on tablets * Fix typo Co-Authored-By: Saturnino Abril <saturnino.abril@gmail.com> * Fix indentation
This commit is contained in:
parent
ff4374f59f
commit
f7f56e958b
37 changed files with 1268 additions and 979 deletions
35
NOTICE.txt
35
NOTICE.txt
|
|
@ -1595,6 +1595,41 @@ SOFTWARE.
|
|||
|
||||
---
|
||||
|
||||
## react-native-keyboard-tracking-view
|
||||
|
||||
This product contains a modified version of 'react-native-keyboard-tracking-view' by Wix.com.
|
||||
|
||||
a strongly specified, highly compatible variant of Markdown
|
||||
|
||||
* HOMEPAGE:
|
||||
* wix/react-native-keyboard-tracking-view
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Wix.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-keychain
|
||||
|
||||
This product contains 'react-native-keychain' by Joel Arvidsson.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
|
|
@ -20,7 +22,7 @@ import DateSuggestion from './date_suggestion';
|
|||
|
||||
export default class Autocomplete extends PureComponent {
|
||||
static propTypes = {
|
||||
cursorPosition: PropTypes.number.isRequired,
|
||||
cursorPosition: PropTypes.number,
|
||||
deviceHeight: PropTypes.number,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
maxHeight: PropTypes.number,
|
||||
|
|
@ -29,6 +31,8 @@ export default class Autocomplete extends PureComponent {
|
|||
theme: PropTypes.object.isRequired,
|
||||
value: PropTypes.string,
|
||||
enableDateSuggestion: PropTypes.bool.isRequired,
|
||||
valueEvent: PropTypes.string,
|
||||
cursorPositionEvent: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -37,18 +41,67 @@ export default class Autocomplete extends PureComponent {
|
|||
enableDateSuggestion: false,
|
||||
};
|
||||
|
||||
state = {
|
||||
atMentionCount: 0,
|
||||
channelMentionCount: 0,
|
||||
emojiCount: 0,
|
||||
commandCount: 0,
|
||||
dateCount: 0,
|
||||
keyboardOffset: 0,
|
||||
};
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
const nextState = {};
|
||||
let updated = false;
|
||||
|
||||
if (props.cursorPosition !== state.cursorPosition && !props.cursorPositionEvent) {
|
||||
nextState.cursorPosition = props.cursorPosition;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (props.value !== state.value && !props.valueEvent) {
|
||||
nextState.value = props.value;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
return updated ? nextState : null;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
atMentionCount: 0,
|
||||
channelMentionCount: 0,
|
||||
cursorPosition: props.cursorPosition,
|
||||
emojiCount: 0,
|
||||
commandCount: 0,
|
||||
dateCount: 0,
|
||||
keyboardOffset: 0,
|
||||
value: props.value,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow);
|
||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide);
|
||||
|
||||
if (this.props.valueEvent) {
|
||||
EventEmitter.on(this.props.valueEvent, this.handleValueChange);
|
||||
}
|
||||
|
||||
if (this.props.cursorPositionEvent) {
|
||||
EventEmitter.on(this.props.cursorPositionEvent, this.handleCursorPositionChange);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.keyboardDidShowListener.remove();
|
||||
this.keyboardDidHideListener.remove();
|
||||
|
||||
if (this.props.valueEvent) {
|
||||
EventEmitter.off(this.props.valueEvent, this.handleValueChange);
|
||||
}
|
||||
|
||||
if (this.props.cursorPositionEvent) {
|
||||
EventEmitter.off(this.props.cursorPositionEvent, this.handleCursorPositionChange);
|
||||
}
|
||||
}
|
||||
|
||||
onChangeText = (value) => {
|
||||
this.props.onChangeText(value, true);
|
||||
}
|
||||
};
|
||||
|
||||
handleAtMentionCountChange = (atMentionCount) => {
|
||||
this.setState({atMentionCount});
|
||||
|
|
@ -58,6 +111,10 @@ export default class Autocomplete extends PureComponent {
|
|||
this.setState({channelMentionCount});
|
||||
};
|
||||
|
||||
handleCursorPositionChange = (cursorPosition) => {
|
||||
this.setState({cursorPosition});
|
||||
};
|
||||
|
||||
handleEmojiCountChange = (emojiCount) => {
|
||||
this.setState({emojiCount});
|
||||
};
|
||||
|
|
@ -70,15 +127,9 @@ export default class Autocomplete extends PureComponent {
|
|||
this.setState({dateCount});
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow);
|
||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.keyboardDidShowListener.remove();
|
||||
this.keyboardDidHideListener.remove();
|
||||
}
|
||||
handleValueChange = (value) => {
|
||||
this.setState({value});
|
||||
};
|
||||
|
||||
keyboardDidShow = (e) => {
|
||||
const {height} = e.endCoordinates;
|
||||
|
|
@ -119,7 +170,7 @@ export default class Autocomplete extends PureComponent {
|
|||
}
|
||||
|
||||
// We always need to render something, but we only draw the borders when we have results to show
|
||||
const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount} = this.state;
|
||||
const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount, cursorPosition, value} = this.state;
|
||||
if (atMentionCount + channelMentionCount + emojiCount + commandCount + dateCount > 0) {
|
||||
if (this.props.isSearch) {
|
||||
wrapperStyle.push(style.bordersSearch);
|
||||
|
|
@ -134,34 +185,43 @@ export default class Autocomplete extends PureComponent {
|
|||
<View style={wrapperStyle}>
|
||||
<View style={containerStyle}>
|
||||
<AtMention
|
||||
maxListHeight={maxListHeight}
|
||||
onResultCountChange={this.handleAtMentionCountChange}
|
||||
{...this.props}
|
||||
cursorPosition={cursorPosition}
|
||||
maxListHeight={maxListHeight}
|
||||
onChangeText={this.onChangeText}
|
||||
onResultCountChange={this.handleAtMentionCountChange}
|
||||
value={value || ''}
|
||||
/>
|
||||
<ChannelMention
|
||||
maxListHeight={maxListHeight}
|
||||
onResultCountChange={this.handleChannelMentionCountChange}
|
||||
{...this.props}
|
||||
cursorPosition={cursorPosition}
|
||||
maxListHeight={maxListHeight}
|
||||
onChangeText={this.onChangeText}
|
||||
onResultCountChange={this.handleChannelMentionCountChange}
|
||||
value={value || ''}
|
||||
/>
|
||||
<EmojiSuggestion
|
||||
maxListHeight={maxListHeight}
|
||||
onResultCountChange={this.handleEmojiCountChange}
|
||||
{...this.props}
|
||||
cursorPosition={cursorPosition}
|
||||
maxListHeight={maxListHeight}
|
||||
onChangeText={this.onChangeText}
|
||||
onResultCountChange={this.handleEmojiCountChange}
|
||||
value={value || ''}
|
||||
/>
|
||||
<SlashSuggestion
|
||||
maxListHeight={maxListHeight}
|
||||
onResultCountChange={this.handleCommandCountChange}
|
||||
{...this.props}
|
||||
maxListHeight={maxListHeight}
|
||||
onChangeText={this.onChangeText}
|
||||
onResultCountChange={this.handleCommandCountChange}
|
||||
value={value || ''}
|
||||
/>
|
||||
{(this.props.isSearch && this.props.enableDateSuggestion) &&
|
||||
<DateSuggestion
|
||||
onResultCountChange={this.handleIsDateFilterChange}
|
||||
{...this.props}
|
||||
cursorPosition={cursorPosition}
|
||||
onChangeText={this.onChangeText}
|
||||
onResultCountChange={this.handleIsDateFilterChange}
|
||||
value={value || ''}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -17,4 +17,6 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
export const AUTOCOMPLETE_MAX_HEIGHT = 200;
|
||||
|
||||
export default connect(mapStateToProps, null, null, {forwardRef: true})(Autocomplete);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ exports[`CustomList should match snapshot with FlatList 1`] = `
|
|||
horizontal={false}
|
||||
initialNumToRender={15}
|
||||
keyExtractor={[Function]}
|
||||
keyboardDismissMode="interactive"
|
||||
keyboardDismissMode="on-drag"
|
||||
keyboardShouldPersistTaps="always"
|
||||
maxToRenderPerBatch={16}
|
||||
numColumns={1}
|
||||
|
|
@ -62,7 +62,7 @@ exports[`CustomList should match snapshot with SectionList 1`] = `
|
|||
horizontal={false}
|
||||
initialNumToRender={15}
|
||||
keyExtractor={[Function]}
|
||||
keyboardDismissMode="interactive"
|
||||
keyboardDismissMode="on-drag"
|
||||
keyboardShouldPersistTaps="always"
|
||||
maxToRenderPerBatch={16}
|
||||
onEndReachedThreshold={2}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {FlatList, Platform, SectionList, Text, View} from 'react-native';
|
||||
import {FlatList, Keyboard, Platform, SectionList, Text, View} from 'react-native';
|
||||
|
||||
import {ListTypes} from 'app/constants';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
|
|
@ -26,10 +26,7 @@ export default class CustomList extends PureComponent {
|
|||
onLoadMore: PropTypes.func,
|
||||
onRowPress: PropTypes.func,
|
||||
onRowSelect: PropTypes.func,
|
||||
renderItem: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.element,
|
||||
]).isRequired,
|
||||
renderItem: PropTypes.func.isRequired,
|
||||
selectable: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
shouldRenderSeparator: PropTypes.bool,
|
||||
|
|
@ -45,6 +42,14 @@ export default class CustomList extends PureComponent {
|
|||
super(props);
|
||||
|
||||
this.contentOffsetY = 0;
|
||||
this.keyboardDismissProp = Platform.select({
|
||||
android: {
|
||||
onScrollBeginDrag: Keyboard.dismiss,
|
||||
},
|
||||
ios: {
|
||||
keyboardDismissMode: 'on-drag',
|
||||
},
|
||||
});
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
|
|
@ -98,12 +103,6 @@ export default class CustomList extends PureComponent {
|
|||
props.onPress = onRowPress;
|
||||
}
|
||||
|
||||
// Allow passing in a component like UserListRow or ChannelListRow
|
||||
if (this.props.renderItem.prototype.isReactComponent) {
|
||||
const RowComponent = this.props.renderItem;
|
||||
return <RowComponent {...props}/>;
|
||||
}
|
||||
|
||||
return this.props.renderItem(props);
|
||||
};
|
||||
|
||||
|
|
@ -117,7 +116,7 @@ export default class CustomList extends PureComponent {
|
|||
data={data}
|
||||
extraData={extraData}
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyboardDismissMode='interactive'
|
||||
{...this.keyboardDismissProp}
|
||||
keyExtractor={this.keyExtractor}
|
||||
initialNumToRender={INITIAL_BATCH_TO_RENDER}
|
||||
ItemSeparatorComponent={this.renderSeparator}
|
||||
|
|
@ -169,7 +168,7 @@ export default class CustomList extends PureComponent {
|
|||
contentContainerStyle={style.container}
|
||||
extraData={loading}
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyboardDismissMode='interactive'
|
||||
{...this.keyboardDismissProp}
|
||||
keyExtractor={this.keyExtractor}
|
||||
initialNumToRender={INITIAL_BATCH_TO_RENDER}
|
||||
ItemSeparatorComponent={this.renderSeparator}
|
||||
|
|
|
|||
|
|
@ -53,42 +53,39 @@ export default class EditChannelInfo extends PureComponent {
|
|||
editing: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.nameInput = React.createRef();
|
||||
this.urlInput = React.createRef();
|
||||
this.purposeInput = React.createRef();
|
||||
this.headerInput = React.createRef();
|
||||
this.lastText = React.createRef();
|
||||
this.scroll = React.createRef();
|
||||
}
|
||||
|
||||
blur = () => {
|
||||
if (this.nameInput) {
|
||||
this.nameInput.blur();
|
||||
if (this.nameInput?.current) {
|
||||
this.nameInput.current.blur();
|
||||
}
|
||||
|
||||
// TODO: uncomment below once the channel URL field is added
|
||||
// if (this.urlInput) {
|
||||
// this.urlInput.blur();
|
||||
// if (this.urlInput?.current) {
|
||||
// this.urlInput.current.blur();
|
||||
// }
|
||||
if (this.purposeInput) {
|
||||
this.purposeInput.blur();
|
||||
|
||||
if (this.purposeInput?.current) {
|
||||
this.purposeInput.current.blur();
|
||||
}
|
||||
if (this.headerInput) {
|
||||
this.headerInput.blur();
|
||||
if (this.headerInput?.current) {
|
||||
this.headerInput.current.blur();
|
||||
}
|
||||
if (this.scroll) {
|
||||
this.scroll.scrollToPosition(0, 0, true);
|
||||
|
||||
if (this.scroll?.current) {
|
||||
this.scroll.current.scrollToPosition(0, 0, true);
|
||||
}
|
||||
};
|
||||
|
||||
channelNameRef = (ref) => {
|
||||
this.nameInput = ref;
|
||||
};
|
||||
|
||||
channelURLRef = (ref) => {
|
||||
this.urlInput = ref;
|
||||
};
|
||||
|
||||
channelPurposeRef = (ref) => {
|
||||
this.purposeInput = ref;
|
||||
};
|
||||
|
||||
channelHeaderRef = (ref) => {
|
||||
this.headerInput = ref;
|
||||
};
|
||||
|
||||
close = (goBack = false) => {
|
||||
if (goBack) {
|
||||
this.props.navigator.pop({animated: true});
|
||||
|
|
@ -99,10 +96,6 @@ export default class EditChannelInfo extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
lastTextRef = (ref) => {
|
||||
this.lastText = ref;
|
||||
};
|
||||
|
||||
canUpdate = (displayName, channelURL, purpose, header) => {
|
||||
const {
|
||||
oldDisplayName,
|
||||
|
|
@ -167,13 +160,9 @@ export default class EditChannelInfo extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
scrollRef = (ref) => {
|
||||
this.scroll = ref;
|
||||
};
|
||||
|
||||
scrollToEnd = () => {
|
||||
if (this.scroll && this.lastText) {
|
||||
this.scroll.scrollToFocusedInput(findNodeHandle(this.lastText));
|
||||
if (this.scroll?.current && this.lastText?.current) {
|
||||
this.scroll.current.scrollToFocusedInput(findNodeHandle(this.lastText.current));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -223,7 +212,7 @@ export default class EditChannelInfo extends PureComponent {
|
|||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
<KeyboardAwareScrollView
|
||||
ref={this.scrollRef}
|
||||
ref={this.scroll}
|
||||
style={style.container}
|
||||
>
|
||||
{displayError}
|
||||
|
|
@ -240,7 +229,7 @@ export default class EditChannelInfo extends PureComponent {
|
|||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
ref={this.channelNameRef}
|
||||
ref={this.nameInput}
|
||||
value={displayName}
|
||||
onChangeText={this.onDisplayNameChangeText}
|
||||
style={style.input}
|
||||
|
|
@ -269,7 +258,7 @@ export default class EditChannelInfo extends PureComponent {
|
|||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
ref={this.channelURLRef}
|
||||
ref={this.urlInput}
|
||||
value={channelURL}
|
||||
onChangeText={this.onDisplayURLChangeText}
|
||||
style={style.input}
|
||||
|
|
@ -299,7 +288,7 @@ export default class EditChannelInfo extends PureComponent {
|
|||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
ref={this.channelPurposeRef}
|
||||
ref={this.purposeInput}
|
||||
value={purpose}
|
||||
onChangeText={this.onPurposeChangeText}
|
||||
style={[style.input, {height: 110}]}
|
||||
|
|
@ -337,7 +326,7 @@ export default class EditChannelInfo extends PureComponent {
|
|||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
ref={this.channelHeaderRef}
|
||||
ref={this.headerInput}
|
||||
value={header}
|
||||
onChangeText={this.onHeaderChangeText}
|
||||
style={[style.input, {height: 110}]}
|
||||
|
|
@ -353,7 +342,7 @@ export default class EditChannelInfo extends PureComponent {
|
|||
disableFullscreenUI={true}
|
||||
/>
|
||||
</View>
|
||||
<View ref={this.lastTextRef}>
|
||||
<View ref={this.lastText}>
|
||||
<FormattedText
|
||||
style={style.helpText}
|
||||
id='channel_modal.headerHelp'
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export default class Emoji extends React.PureComponent {
|
|||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {displayTextOnly, emojiName, imageUrl} = nextProps;
|
||||
if (emojiName !== this.props.emojiName) {
|
||||
if (emojiName !== this.props.emojiName && this.mounted) {
|
||||
this.setState({
|
||||
imageUrl: null,
|
||||
});
|
||||
|
|
@ -82,9 +82,11 @@ export default class Emoji extends React.PureComponent {
|
|||
}
|
||||
|
||||
setImageUrl = (imageUrl) => {
|
||||
this.setState({
|
||||
imageUrl,
|
||||
});
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
imageUrl,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
exports[`PostAttachmentOpenGraph should match snapshot with a single image file 1`] = `
|
||||
<ScrollViewMock
|
||||
horizontal={true}
|
||||
keyboardShouldPersistTaps="always"
|
||||
scrollEnabled={false}
|
||||
style={
|
||||
Array [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Keyboard,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
|
|
@ -123,7 +122,6 @@ export default class FileAttachmentList extends Component {
|
|||
};
|
||||
|
||||
handlePreviewPress = preventDoubleTap((idx) => {
|
||||
Keyboard.dismiss();
|
||||
previewImageAtIndex(this.props.navigator, this.items, idx, this.galleryFiles);
|
||||
});
|
||||
|
||||
|
|
@ -176,6 +174,7 @@ export default class FileAttachmentList extends Component {
|
|||
horizontal={true}
|
||||
scrollEnabled={fileIds.length > 1}
|
||||
style={[(isFailed && styles.failed)]}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
>
|
||||
{this.renderItems()}
|
||||
</ScrollView>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
import FileUploadItem from './file_upload_item';
|
||||
|
|
@ -21,9 +23,7 @@ export default class FileUploadPreview extends PureComponent {
|
|||
deviceHeight: PropTypes.number.isRequired,
|
||||
files: PropTypes.array.isRequired,
|
||||
filesUploadingForCurrentChannel: PropTypes.bool.isRequired,
|
||||
fileSizeWarning: PropTypes.string,
|
||||
rootId: PropTypes.string,
|
||||
showFileMaxWarning: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
|
@ -31,6 +31,21 @@ export default class FileUploadPreview extends PureComponent {
|
|||
files: [],
|
||||
};
|
||||
|
||||
state = {
|
||||
fileSizeWarning: null,
|
||||
showFileMaxWarning: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
EventEmitter.on('fileMaxWarning', this.handleFileMaxWarning);
|
||||
EventEmitter.on('fileSizeWarning', this.handleFileSizeWarning);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('fileMaxWarning', this.handleFileMaxWarning);
|
||||
EventEmitter.off('fileSizeWarning', this.handleFileSizeWarning);
|
||||
}
|
||||
|
||||
buildFilePreviews = () => {
|
||||
return this.props.files.map((file) => {
|
||||
return (
|
||||
|
|
@ -45,15 +60,25 @@ export default class FileUploadPreview extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
handleFileMaxWarning = () => {
|
||||
this.setState({showFileMaxWarning: true});
|
||||
setTimeout(() => {
|
||||
this.setState({showFileMaxWarning: false});
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
handleFileSizeWarning = (message) => {
|
||||
this.setState({fileSizeWarning: message});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
showFileMaxWarning,
|
||||
fileSizeWarning,
|
||||
channelIsLoading,
|
||||
filesUploadingForCurrentChannel,
|
||||
deviceHeight,
|
||||
files,
|
||||
} = this.props;
|
||||
const {fileSizeWarning, showFileMaxWarning} = this.state;
|
||||
|
||||
if (
|
||||
!fileSizeWarning && !showFileMaxWarning &&
|
||||
|
|
@ -69,6 +94,7 @@ export default class FileUploadPreview extends PureComponent {
|
|||
horizontal={true}
|
||||
style={style.scrollView}
|
||||
contentContainerStyle={style.scrollViewContent}
|
||||
keyboardShouldPersistTaps={'handled'}
|
||||
>
|
||||
{this.buildFilePreviews()}
|
||||
</ScrollView>
|
||||
|
|
|
|||
|
|
@ -6,15 +6,18 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
|||
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
import {checkForFileUploadingInChannel} from 'app/selectors/file';
|
||||
import {getCurrentChannelDraft, getThreadDraft} from 'app/selectors/views';
|
||||
|
||||
import FileUploadPreview from './file_upload_preview';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {deviceHeight} = getDimensions(state);
|
||||
const currentDraft = ownProps.rootId ? getThreadDraft(state, ownProps.rootId) : getCurrentChannelDraft(state);
|
||||
|
||||
return {
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
deviceHeight,
|
||||
files: currentDraft.files,
|
||||
filesUploadingForCurrentChannel: checkForFileUploadingInChannel(state, ownProps.channelId, ownProps.rootId),
|
||||
theme: getTheme(state),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
import KeyboardLayout from './keyboard_layout';
|
||||
|
||||
export default KeyboardLayout;
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null, null, {forwardRef: true})(KeyboardLayout);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Keyboard,
|
||||
Platform,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
|
|
@ -104,11 +105,14 @@ export default class Post extends PureComponent {
|
|||
},
|
||||
};
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
navigator.push(options);
|
||||
} else {
|
||||
navigator.showModal(options);
|
||||
}
|
||||
Keyboard.dismiss();
|
||||
requestAnimationFrame(() => {
|
||||
if (Platform.OS === 'ios') {
|
||||
navigator.push(options);
|
||||
} else {
|
||||
navigator.showModal(options);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
autofillUserMention = (username) => {
|
||||
|
|
|
|||
|
|
@ -439,6 +439,7 @@ export default class PostBody extends PureComponent {
|
|||
scrollEnabled={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
>
|
||||
{messageComponent}
|
||||
</ScrollView>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ exports[`PostList setting channel deep link 1`] = `
|
|||
initialNumToRender={15}
|
||||
inverted={true}
|
||||
keyExtractor={[Function]}
|
||||
keyboardDismissMode="interactive"
|
||||
keyboardShouldPersistTaps="handled"
|
||||
maintainVisibleContentPosition={
|
||||
Object {
|
||||
"autoscrollToTopThreshold": 60,
|
||||
|
|
@ -74,6 +76,8 @@ exports[`PostList setting permalink deep link 1`] = `
|
|||
initialNumToRender={15}
|
||||
inverted={true}
|
||||
keyExtractor={[Function]}
|
||||
keyboardDismissMode="interactive"
|
||||
keyboardShouldPersistTaps="handled"
|
||||
maintainVisibleContentPosition={
|
||||
Object {
|
||||
"autoscrollToTopThreshold": 60,
|
||||
|
|
@ -122,6 +126,8 @@ exports[`PostList should match snapshot 1`] = `
|
|||
initialNumToRender={15}
|
||||
inverted={true}
|
||||
keyExtractor={[Function]}
|
||||
keyboardDismissMode="interactive"
|
||||
keyboardShouldPersistTaps="handled"
|
||||
maintainVisibleContentPosition={
|
||||
Object {
|
||||
"autoscrollToTopThreshold": 60,
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export default class PostList extends PureComponent {
|
|||
siteURL: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
location: PropTypes.string,
|
||||
scrollViewNativeID: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -320,6 +321,7 @@ export default class PostList extends PureComponent {
|
|||
highlightPostId,
|
||||
postIds,
|
||||
refreshing,
|
||||
scrollViewNativeID,
|
||||
} = this.props;
|
||||
|
||||
const refreshControl = {refreshing};
|
||||
|
|
@ -339,6 +341,8 @@ export default class PostList extends PureComponent {
|
|||
extraData={this.makeExtraData(channelId, highlightPostId, this.props.extraData)}
|
||||
initialNumToRender={INITIAL_BATCH_TO_RENDER}
|
||||
inverted={true}
|
||||
keyboardDismissMode={'interactive'}
|
||||
keyboardShouldPersistTaps={'handled'}
|
||||
keyExtractor={this.keyExtractor}
|
||||
ListFooterComponent={this.props.renderFooter}
|
||||
maintainVisibleContentPosition={SCROLL_POSITION_CONFIG}
|
||||
|
|
@ -351,6 +355,7 @@ export default class PostList extends PureComponent {
|
|||
renderItem={this.renderItem}
|
||||
scrollEventThrottle={60}
|
||||
{...refreshControl}
|
||||
nativeID={scrollViewNativeID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
|||
import {getFormattedFileSize} from 'mattermost-redux/utils/file_utils';
|
||||
|
||||
import AttachmentButton from 'app/components/attachment_button';
|
||||
import Autocomplete from 'app/components/autocomplete';
|
||||
import Fade from 'app/components/fade';
|
||||
import FileUploadPreview from 'app/components/file_upload_preview';
|
||||
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';
|
||||
|
|
@ -27,9 +25,6 @@ import Typing from './components/typing';
|
|||
const PLACEHOLDER_COLOR = changeOpacity('#000', 0.5);
|
||||
const {RNTextInputReset} = NativeModules;
|
||||
|
||||
const AUTOCOMPLETE_MARGIN = 20;
|
||||
const AUTOCOMPLETE_MAX_HEIGHT = 200;
|
||||
|
||||
export default class PostTextbox extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
|
|
@ -66,6 +61,8 @@ export default class PostTextbox extends PureComponent {
|
|||
userIsOutOfOffice: PropTypes.bool.isRequired,
|
||||
channelIsArchived: PropTypes.bool,
|
||||
onCloseChannel: PropTypes.func,
|
||||
cursorPositionEvent: PropTypes.string,
|
||||
valueEvent: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -86,10 +83,8 @@ export default class PostTextbox extends PureComponent {
|
|||
this.state = {
|
||||
cursorPosition: 0,
|
||||
keyboardType: 'default',
|
||||
fileSizeWarning: null,
|
||||
top: 0,
|
||||
value: props.value,
|
||||
showFileMaxWarning: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +113,7 @@ export default class PostTextbox extends PureComponent {
|
|||
}
|
||||
|
||||
blur = () => {
|
||||
if (this.input?.current) {
|
||||
if (this.input.current) {
|
||||
this.input.current.blur();
|
||||
}
|
||||
};
|
||||
|
|
@ -198,6 +193,12 @@ export default class PostTextbox extends PureComponent {
|
|||
|
||||
handlePostDraftSelectionChanged = (event) => {
|
||||
const cursorPosition = event.nativeEvent.selection.end;
|
||||
const {cursorPositionEvent} = this.props;
|
||||
|
||||
if (cursorPositionEvent) {
|
||||
EventEmitter.emit(cursorPositionEvent, cursorPosition);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
cursorPosition,
|
||||
});
|
||||
|
|
@ -269,14 +270,19 @@ export default class PostTextbox extends PureComponent {
|
|||
actions,
|
||||
channelId,
|
||||
rootId,
|
||||
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) {
|
||||
if (autocomplete && Platform.OS === 'android' && this.input?.current) {
|
||||
RNTextInputReset.resetKeyboardInput(findNodeHandle(this.input.current));
|
||||
}
|
||||
|
||||
this.checkMessageLength(value);
|
||||
if (!autocomplete && valueEvent) {
|
||||
EventEmitter.emit(valueEvent, value);
|
||||
}
|
||||
|
||||
this.setState({value});
|
||||
|
||||
if (value) {
|
||||
|
|
@ -430,11 +436,7 @@ export default class PostTextbox extends PureComponent {
|
|||
};
|
||||
|
||||
onShowFileMaxWarning = () => {
|
||||
this.setState({showFileMaxWarning: true}, () => {
|
||||
setTimeout(() => {
|
||||
this.setState({showFileMaxWarning: false});
|
||||
}, 3000);
|
||||
});
|
||||
EventEmitter.emit('fileMaxWarning');
|
||||
};
|
||||
|
||||
onShowFileSizeWarning = (filename) => {
|
||||
|
|
@ -447,11 +449,10 @@ export default class PostTextbox extends PureComponent {
|
|||
filename,
|
||||
});
|
||||
|
||||
this.setState({fileSizeWarning}, () => {
|
||||
setTimeout(() => {
|
||||
this.setState({fileSizeWarning: null});
|
||||
}, 3000);
|
||||
});
|
||||
EventEmitter.emit('fileSizeWarning', fileSizeWarning);
|
||||
setTimeout(() => {
|
||||
EventEmitter.emit('fileSizeWarning', null);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
onCloseChannelPress = () => {
|
||||
|
|
@ -493,7 +494,6 @@ export default class PostTextbox extends PureComponent {
|
|||
const {intl} = this.context;
|
||||
const {
|
||||
canUploadFiles,
|
||||
channelId,
|
||||
channelDisplayName,
|
||||
channelIsLoading,
|
||||
channelIsReadOnly,
|
||||
|
|
@ -518,14 +518,7 @@ export default class PostTextbox extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const {
|
||||
cursorPosition,
|
||||
fileSizeWarning,
|
||||
showFileMaxWarning,
|
||||
top,
|
||||
value,
|
||||
} = this.state;
|
||||
|
||||
const {value} = this.state;
|
||||
const textValue = channelIsLoading ? '' : value;
|
||||
|
||||
let placeholder;
|
||||
|
|
@ -564,20 +557,6 @@ export default class PostTextbox extends PureComponent {
|
|||
return (
|
||||
<React.Fragment>
|
||||
<Typing/>
|
||||
<FileUploadPreview
|
||||
channelId={channelId}
|
||||
files={files}
|
||||
fileSizeWarning={fileSizeWarning}
|
||||
rootId={rootId}
|
||||
showFileMaxWarning={showFileMaxWarning}
|
||||
/>
|
||||
<Autocomplete
|
||||
cursorPosition={cursorPosition}
|
||||
maxHeight={Math.min(top - AUTOCOMPLETE_MARGIN, AUTOCOMPLETE_MAX_HEIGHT)}
|
||||
onChangeText={this.handleTextChange}
|
||||
value={this.state.value}
|
||||
rootId={rootId}
|
||||
/>
|
||||
{!channelIsArchived && (
|
||||
<View
|
||||
style={style.inputWrapper}
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ export default class Reactions extends PureComponent {
|
|||
alwaysBounceHorizontal={false}
|
||||
horizontal={true}
|
||||
overScrollMode='never'
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
>
|
||||
{reactionElements}
|
||||
</ScrollView>
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ export default class DrawerLayout extends Component {
|
|||
this.setState({ drawerShown, accessibilityViewIsModal });
|
||||
}
|
||||
|
||||
if (this.props.keyboardDismissMode === 'on-drag') {
|
||||
if (this.props.keyboardDismissMode === 'on-drag' || drawerShown) {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ import React, {Component} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
FlatList,
|
||||
Keyboard,
|
||||
Platform,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
|
@ -61,6 +62,12 @@ class FilteredList extends Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.keyboardDismissProp = {
|
||||
keyboardDismissMode: Platform.OS === 'ios' ? 'interactive' : 'none',
|
||||
onScrollBeginDrag: Keyboard.dismiss,
|
||||
};
|
||||
|
||||
this.state = {
|
||||
dataSource: this.buildData(props),
|
||||
};
|
||||
|
|
@ -112,7 +119,6 @@ class FilteredList extends Component {
|
|||
createChannelElement = (channel) => {
|
||||
return (
|
||||
<ChannelItem
|
||||
ref={channel.id}
|
||||
channelId={channel.id}
|
||||
channel={channel}
|
||||
isSearchResult={true}
|
||||
|
|
@ -386,7 +392,6 @@ class FilteredList extends Component {
|
|||
|
||||
render() {
|
||||
const {styles} = this.props;
|
||||
|
||||
const {dataSource} = this.state;
|
||||
|
||||
return (
|
||||
|
|
@ -394,13 +399,12 @@ class FilteredList extends Component {
|
|||
style={styles.container}
|
||||
>
|
||||
<FlatList
|
||||
ref='list'
|
||||
data={dataSource}
|
||||
renderItem={this.renderItem}
|
||||
keyExtractor={(item) => item.id}
|
||||
onViewableItemsChanged={this.updateUnreadIndicators}
|
||||
keyboardDismissMode={Platform.OS === 'ios' ? 'interactive' : 'on-drag'}
|
||||
keyboardShouldPersistTaps='always'
|
||||
{...this.keyboardDismissProp}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
maxToRenderPerBatch={10}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import React, {PureComponent} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
Platform,
|
||||
SectionList,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
|
|
@ -17,7 +19,7 @@ import {General} from 'mattermost-redux/constants';
|
|||
import {debounce} from 'mattermost-redux/actions/helpers';
|
||||
|
||||
import ChannelItem from 'app/components/sidebars/main/channels_list/channel_item';
|
||||
import {ListTypes} from 'app/constants';
|
||||
import {DeviceTypes, ListTypes} from 'app/constants';
|
||||
import {SidebarSectionTypes} from 'app/constants/view';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
|
|
@ -44,7 +46,6 @@ export default class List extends PureComponent {
|
|||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
unreadChannelIds: [],
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -56,6 +57,11 @@ export default class List extends PureComponent {
|
|||
width: 0,
|
||||
};
|
||||
|
||||
this.keyboardDismissProp = {
|
||||
keyboardDismissMode: Platform.OS === 'ios' ? 'interactive' : 'none',
|
||||
onScrollBeginDrag: this.scrollBeginDrag,
|
||||
};
|
||||
|
||||
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => {
|
||||
this.closeButton = source;
|
||||
});
|
||||
|
|
@ -135,7 +141,7 @@ export default class List extends PureComponent {
|
|||
defaultMessage: 'CHANNELS',
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
buildSections = (props) => {
|
||||
const {
|
||||
|
|
@ -209,7 +215,7 @@ export default class List extends PureComponent {
|
|||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
goToCreatePublicChannel = preventDoubleTap(() => {
|
||||
const {navigator, theme} = this.props;
|
||||
|
|
@ -308,6 +314,9 @@ export default class List extends PureComponent {
|
|||
|
||||
onSelectChannel = (channel, currentChannelId) => {
|
||||
const {onSelectChannel} = this.props;
|
||||
if (DeviceTypes.IS_TABLET) {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
onSelectChannel(channel, currentChannelId);
|
||||
};
|
||||
|
||||
|
|
@ -409,6 +418,12 @@ export default class List extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
scrollBeginDrag = () => {
|
||||
if (DeviceTypes.IS_TABLET) {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {styles, theme} = this.props;
|
||||
const {sections, width, showIndicator} = this.state;
|
||||
|
|
@ -425,10 +440,11 @@ export default class List extends PureComponent {
|
|||
renderSectionHeader={this.renderSectionHeader}
|
||||
keyExtractor={this.keyExtractor}
|
||||
onViewableItemsChanged={this.updateUnreadIndicators}
|
||||
keyboardDismissMode='on-drag'
|
||||
maxToRenderPerBatch={10}
|
||||
stickySectionHeadersEnabled={false}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
{...this.keyboardDismissProp}
|
||||
/>
|
||||
{showIndicator &&
|
||||
<UnreadIndicator
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {
|
|||
AppState,
|
||||
Dimensions,
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
NativeModules,
|
||||
Platform,
|
||||
YellowBox,
|
||||
|
|
@ -426,7 +425,6 @@ const handleAppActive = async () => {
|
|||
}
|
||||
|
||||
app.setInBackgroundSince(null);
|
||||
Keyboard.dismiss();
|
||||
};
|
||||
|
||||
const handleAppInActive = () => {
|
||||
|
|
|
|||
|
|
@ -6,22 +6,25 @@ 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 EmptyToolbar from 'app/components/start/empty_toolbar';
|
||||
import ChannelLoader from 'app/components/channel_loader';
|
||||
import EmptyToolbar from 'app/components/start/empty_toolbar';
|
||||
import FileUploadPreview from 'app/components/file_upload_preview';
|
||||
import MainSidebar from 'app/components/sidebars/main';
|
||||
import SettingsSidebar from 'app/components/sidebars/settings';
|
||||
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';
|
||||
|
|
@ -45,6 +48,9 @@ const {
|
|||
IOSX_TOP_PORTRAIT,
|
||||
} = ViewTypes;
|
||||
|
||||
const CHANNEL_POST_TEXTBOX_CURSOR_CHANGE = 'onChannelTextBoxCursorChange';
|
||||
const CHANNEL_POST_TEXTBOX_VALUE_CHANGE = 'onChannelTextBoxValueChange';
|
||||
|
||||
let ClientUpgradeListener;
|
||||
|
||||
export default class Channel extends PureComponent {
|
||||
|
|
@ -77,6 +83,13 @@ export default class Channel extends PureComponent {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.postTextbox = React.createRef();
|
||||
this.keyboardTracker = React.createRef();
|
||||
|
||||
props.navigator.setStyle({
|
||||
screenBackgroundColor: props.theme.centerChannelBg,
|
||||
});
|
||||
|
||||
if (LocalConfig.EnableMobileClientUpgrade && !ClientUpgradeListener) {
|
||||
ClientUpgradeListener = require('app/components/client_upgrade_listener').default;
|
||||
}
|
||||
|
|
@ -140,19 +153,19 @@ export default class Channel extends PureComponent {
|
|||
if (this.props.currentChannelId && !prevProps.currentChannelId) {
|
||||
EventEmitter.emit('renderDrawer');
|
||||
}
|
||||
|
||||
if (this.props.currentChannelId && this.props.currentChannelId !== prevProps.currentChannelId) {
|
||||
this.updateNativeScrollView();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('leave_team', this.handleLeaveTeam);
|
||||
}
|
||||
|
||||
attachPostTextBox = (ref) => {
|
||||
this.postTextbox = ref;
|
||||
};
|
||||
|
||||
blurPostTextBox = () => {
|
||||
if (this.postTextbox) {
|
||||
this.postTextbox.blur();
|
||||
if (this.postTextbox?.current) {
|
||||
this.postTextbox.current.blur();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -233,13 +246,23 @@ export default class Channel extends PureComponent {
|
|||
},
|
||||
};
|
||||
|
||||
Keyboard.dismiss();
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
navigator.showModal(options);
|
||||
} else {
|
||||
navigator.push(options);
|
||||
requestAnimationFrame(() => {
|
||||
navigator.push(options);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
handleAutoComplete = (value) => {
|
||||
if (this.postTextbox?.current) {
|
||||
this.postTextbox.current.handleTextChange(value, true);
|
||||
}
|
||||
};
|
||||
|
||||
handleLeaveTeam = () => {
|
||||
this.props.actions.selectDefaultTeam();
|
||||
};
|
||||
|
|
@ -278,6 +301,12 @@ export default class Channel extends PureComponent {
|
|||
this.loadChannels(this.props.currentTeamId);
|
||||
};
|
||||
|
||||
updateNativeScrollView = () => {
|
||||
if (this.keyboardTracker?.current) {
|
||||
this.keyboardTracker.current.resetScrollView(this.props.currentChannelId);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
channelsRequestFailed,
|
||||
|
|
@ -334,21 +363,34 @@ export default class Channel extends PureComponent {
|
|||
openSettingsDrawer={this.openSettingsSidebar}
|
||||
onPress={this.goToChannelInfo}
|
||||
/>
|
||||
<KeyboardLayout>
|
||||
<View style={style.flex}>
|
||||
<ChannelPostList navigator={navigator}/>
|
||||
</View>
|
||||
<PostTextbox
|
||||
ref={this.attachPostTextBox}
|
||||
navigator={navigator}
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
<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
|
||||
style={[style.channelLoader, loaderDimensions]}
|
||||
maxRows={isLandscape ? 4 : 6}
|
||||
/>
|
||||
{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>
|
||||
</SettingsSidebar>
|
||||
<InteractiveDialogController
|
||||
navigator={navigator}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Keyboard,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
|
@ -25,6 +26,7 @@ export default class ChannelSearchButton extends PureComponent {
|
|||
handlePress = preventDoubleTap(async () => {
|
||||
const {actions, navigator} = this.props;
|
||||
|
||||
Keyboard.dismiss();
|
||||
await actions.clearSearch();
|
||||
await actions.showSearchModal(navigator);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, {PureComponent} from 'react';
|
||||
import {
|
||||
Keyboard,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View,
|
||||
|
|
@ -43,6 +44,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
postVisibility: PropTypes.number,
|
||||
refreshing: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
updateNativeScrollView: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -86,6 +88,11 @@ export default class ChannelPostList extends PureComponent {
|
|||
if (prevProps.channelId !== this.props.channelId && tracker.channelSwitch) {
|
||||
this.props.actions.recordLoadTime('Switch Channel', 'channelSwitch');
|
||||
}
|
||||
|
||||
if (!prevProps.postIds?.length && this.props.postIds?.length > 0) {
|
||||
// This is needed to re-bind the scrollview natively when getting the first posts
|
||||
this.props.updateNativeScrollView();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
@ -101,6 +108,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
const {actions, channelId, navigator, theme} = this.props;
|
||||
const rootId = (post.root_id || post.id);
|
||||
|
||||
Keyboard.dismiss();
|
||||
actions.loadThreadIfNecessary(rootId);
|
||||
actions.selectPost(rootId);
|
||||
|
||||
|
|
@ -123,7 +131,9 @@ export default class ChannelPostList extends PureComponent {
|
|||
if (Platform.OS === 'android') {
|
||||
navigator.showModal(options);
|
||||
} else {
|
||||
navigator.push(options);
|
||||
requestAnimationFrame(() => {
|
||||
navigator.push(options);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -216,6 +226,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
navigator={navigator}
|
||||
renderFooter={this.renderFooter}
|
||||
refreshing={refreshing}
|
||||
scrollViewNativeID={channelId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MoreChannels should match snapshot 1`] = `
|
||||
<KeyboardLayout>
|
||||
<ForwardRef(forwardConnectRef)>
|
||||
<Connect(StatusBar) />
|
||||
<React.Fragment>
|
||||
<View
|
||||
|
|
@ -110,5 +110,5 @@ exports[`MoreChannels should match snapshot 1`] = `
|
|||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</KeyboardLayout>
|
||||
</ForwardRef(forwardConnectRef)>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -292,6 +292,12 @@ export default class MoreChannels extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
renderItem = (props) => {
|
||||
return (
|
||||
<ChannelListRow {...props}/>
|
||||
);
|
||||
}
|
||||
|
||||
searchChannels = (text) => {
|
||||
const {actions, channels, currentTeamId} = this.props;
|
||||
|
||||
|
|
@ -363,7 +369,7 @@ export default class MoreChannels extends PureComponent {
|
|||
noResults={this.renderNoResults()}
|
||||
onLoadMore={more}
|
||||
onRowPress={this.onSelectChannel}
|
||||
renderItem={ChannelListRow}
|
||||
renderItem={this.renderItem}
|
||||
theme={theme}
|
||||
/>
|
||||
</React.Fragment>
|
||||
|
|
|
|||
|
|
@ -275,6 +275,18 @@ export default class SelectorScreen extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
renderChannelItem = (props) => {
|
||||
return <ChannelListRow {...props}/>;
|
||||
};
|
||||
|
||||
renderOptionItem = (props) => {
|
||||
return <OptionListRow {...props}/>;
|
||||
};
|
||||
|
||||
renderUserItem = (props) => {
|
||||
return <UserListRow {...props}/>;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {theme, dataSource} = this.props;
|
||||
|
|
@ -294,11 +306,11 @@ export default class SelectorScreen extends PureComponent {
|
|||
|
||||
let rowComponent;
|
||||
if (dataSource === ViewTypes.DATA_SOURCE_USERS) {
|
||||
rowComponent = UserListRow;
|
||||
rowComponent = this.renderUserItem;
|
||||
} else if (dataSource === ViewTypes.DATA_SOURCE_CHANNELS) {
|
||||
rowComponent = ChannelListRow;
|
||||
rowComponent = this.renderChannelItem;
|
||||
} else {
|
||||
rowComponent = OptionListRow;
|
||||
rowComponent = this.renderOptionItem;
|
||||
}
|
||||
|
||||
const {data, listType} = this.getDataResults();
|
||||
|
|
|
|||
|
|
@ -1,64 +1,77 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`thread should match snapshot, has root post 1`] = `
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<KeyboardLayout
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
<React.Fragment>
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<Connect(PostList)
|
||||
currentUserId="member_user_id"
|
||||
indicateNewMessages={false}
|
||||
lastPostIndex={-1}
|
||||
location="thread"
|
||||
navigator={
|
||||
Object {
|
||||
"dismissModal": [MockFunction],
|
||||
"pop": [MockFunction],
|
||||
"resetTo": [MockFunction],
|
||||
"setTitle": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
<Connect(StatusBar) />
|
||||
<React.Fragment>
|
||||
<Connect(PostList)
|
||||
currentUserId="member_user_id"
|
||||
indicateNewMessages={false}
|
||||
lastPostIndex={-1}
|
||||
location="thread"
|
||||
navigator={
|
||||
Object {
|
||||
"dismissModal": [MockFunction],
|
||||
"pop": [MockFunction],
|
||||
"resetTo": [MockFunction],
|
||||
"setTitle": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
Object {
|
||||
"title": undefined,
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"title": undefined,
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
postIds={
|
||||
Array [
|
||||
"root_id",
|
||||
"post_id_1",
|
||||
"post_id_2",
|
||||
]
|
||||
}
|
||||
renderFooter={
|
||||
<Loading
|
||||
color="grey"
|
||||
size="large"
|
||||
style={Object {}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
onPostPress={[Function]}
|
||||
postIds={
|
||||
Array [
|
||||
"root_id",
|
||||
"post_id_1",
|
||||
"post_id_2",
|
||||
]
|
||||
}
|
||||
renderFooter={
|
||||
<Loading
|
||||
color="grey"
|
||||
size="large"
|
||||
style={Object {}}
|
||||
/>
|
||||
}
|
||||
scrollViewNativeID="threadPostList"
|
||||
/>
|
||||
<Connect(FileUploadPreview)
|
||||
channelId="channel_id"
|
||||
rootId="root_id"
|
||||
/>
|
||||
<ForwardRef(forwardConnectRef)
|
||||
cursorPositionEvent="onThreadTextBoxCursorChange"
|
||||
maxHeight={200}
|
||||
onChangeText={[Function]}
|
||||
rootId="root_id"
|
||||
valueEvent="onThreadTextBoxValueChange"
|
||||
/>
|
||||
</React.Fragment>
|
||||
</Connect(SafeAreaIos)>
|
||||
<KeyboardTrackingView
|
||||
scrollViewNativeID="threadPostList"
|
||||
>
|
||||
<ForwardRef(forwardConnectRef)
|
||||
channelId="channel_id"
|
||||
channelIsArchived={false}
|
||||
cursorPositionEvent="onThreadTextBoxCursorChange"
|
||||
navigator={
|
||||
Object {
|
||||
"dismissModal": [MockFunction],
|
||||
|
|
@ -83,32 +96,26 @@ exports[`thread should match snapshot, has root post 1`] = `
|
|||
}
|
||||
onCloseChannel={[Function]}
|
||||
rootId="root_id"
|
||||
valueEvent="onThreadTextBoxValueChange"
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
</Connect(SafeAreaIos)>
|
||||
</KeyboardTrackingView>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
exports[`thread should match snapshot, no root post, loading 1`] = `
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<KeyboardLayout
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
<React.Fragment>
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<Loading
|
||||
color="grey"
|
||||
size="large"
|
||||
style={Object {}}
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
</Connect(SafeAreaIos)>
|
||||
</Connect(SafeAreaIos)>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
exports[`thread should match snapshot, render footer 1`] = `
|
||||
|
|
@ -139,6 +146,7 @@ exports[`thread should match snapshot, render footer 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
onPostPress={[Function]}
|
||||
postIds={
|
||||
Array [
|
||||
"root_id",
|
||||
|
|
@ -153,6 +161,7 @@ exports[`thread should match snapshot, render footer 1`] = `
|
|||
style={Object {}}
|
||||
/>
|
||||
}
|
||||
scrollViewNativeID="threadPostList"
|
||||
/>
|
||||
`;
|
||||
|
||||
|
|
@ -185,6 +194,7 @@ exports[`thread should match snapshot, render footer 2`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
onPostPress={[Function]}
|
||||
postIds={
|
||||
Array [
|
||||
"root_id",
|
||||
|
|
@ -193,28 +203,22 @@ exports[`thread should match snapshot, render footer 2`] = `
|
|||
]
|
||||
}
|
||||
renderFooter={null}
|
||||
scrollViewNativeID="threadPostList"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`thread should match snapshot, render footer 3`] = `
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<KeyboardLayout
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
<React.Fragment>
|
||||
<Connect(SafeAreaIos)
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<Loading
|
||||
color="grey"
|
||||
size="large"
|
||||
style={Object {}}
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
</Connect(SafeAreaIos)>
|
||||
</Connect(SafeAreaIos)>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -3,23 +3,29 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Platform} from 'react-native';
|
||||
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 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 {makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme';
|
||||
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 {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
|
|
@ -47,17 +53,21 @@ export default class Thread extends PureComponent {
|
|||
intl: intlShape,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
const {channelType, displayName} = this.props;
|
||||
const {intl} = this.context;
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
const {channelType, displayName} = props;
|
||||
const {formatMessage} = context.intl;
|
||||
let title;
|
||||
|
||||
if (channelType === General.DM_CHANNEL) {
|
||||
title = intl.formatMessage({id: 'mobile.routes.thread_dm', defaultMessage: 'Direct Message Thread'});
|
||||
title = formatMessage({id: 'mobile.routes.thread_dm', defaultMessage: 'Direct Message Thread'});
|
||||
} else {
|
||||
title = intl.formatMessage({id: 'mobile.routes.thread', defaultMessage: '{channelName} Thread'}, {channelName: displayName});
|
||||
title = formatMessage({id: 'mobile.routes.thread', defaultMessage: '{channelName} Thread'}, {channelName: displayName});
|
||||
}
|
||||
|
||||
this.postTextbox = React.createRef();
|
||||
|
||||
this.props.navigator.setTitle({
|
||||
title,
|
||||
});
|
||||
|
|
@ -96,10 +106,20 @@ export default class Thread extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleAutoComplete = (value) => {
|
||||
if (this.postTextbox?.current) {
|
||||
this.postTextbox.current.handleTextChange(value, true);
|
||||
}
|
||||
};
|
||||
|
||||
hasRootPost = () => {
|
||||
return this.props.postIds.includes(this.props.rootId);
|
||||
};
|
||||
|
||||
hideKeyboard = () => {
|
||||
Keyboard.dismiss();
|
||||
};
|
||||
|
||||
renderFooter = () => {
|
||||
if (!this.hasRootPost() && this.props.threadLoadingStatus.status !== RequestStatus.STARTED) {
|
||||
return (
|
||||
|
|
@ -141,34 +161,53 @@ export default class Thread extends PureComponent {
|
|||
navigator,
|
||||
postIds,
|
||||
rootId,
|
||||
theme,
|
||||
channelIsArchived,
|
||||
} = this.props;
|
||||
const style = getStyle(theme);
|
||||
|
||||
let content;
|
||||
let postTextBox;
|
||||
if (this.hasRootPost()) {
|
||||
content = (
|
||||
<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}
|
||||
location={THREAD}
|
||||
/>
|
||||
<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 = (
|
||||
<PostTextbox
|
||||
channelIsArchived={channelIsArchived}
|
||||
rootId={rootId}
|
||||
channelId={channelId}
|
||||
navigator={navigator}
|
||||
onCloseChannel={this.onCloseChannel}
|
||||
/>
|
||||
<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 = (
|
||||
|
|
@ -177,25 +216,16 @@ export default class Thread extends PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<StatusBar/>
|
||||
<KeyboardLayout style={style.container}>
|
||||
<React.Fragment>
|
||||
<SafeAreaView
|
||||
excludeHeader={true}
|
||||
keyboardOffset={20}
|
||||
>
|
||||
<StatusBar/>
|
||||
{content}
|
||||
{postTextBox}
|
||||
</KeyboardLayout>
|
||||
</SafeAreaView>
|
||||
</SafeAreaView>
|
||||
{postTextBox}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyle = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Keyboard} from 'react-native';
|
||||
|
||||
import {
|
||||
IMAGE_MAX_HEIGHT,
|
||||
IMAGE_MIN_DIMENSION,
|
||||
|
|
@ -75,18 +77,21 @@ export function previewImageAtIndex(navigator, components, index, files) {
|
|||
}
|
||||
|
||||
function goToImagePreview(navigator, passProps) {
|
||||
navigator.showModal({
|
||||
screen: 'ImagePreview',
|
||||
title: '',
|
||||
animationType: 'none',
|
||||
passProps,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: 'transparent',
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
Keyboard.dismiss();
|
||||
requestAnimationFrame(() => {
|
||||
navigator.showModal({
|
||||
screen: 'ImagePreview',
|
||||
title: '',
|
||||
animationType: 'none',
|
||||
passProps,
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
screenBackgroundColor: 'transparent',
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -555,4 +555,4 @@
|
|||
"user.settings.push_notification.offline": "Offline",
|
||||
"user.settings.push_notification.online": "Online, away or offline",
|
||||
"web.root.signup_info": "All team communication in one place, searchable and accessible anywhere"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ before_all do |lane, options|
|
|||
end
|
||||
|
||||
# Block to ensure we are on the right branch
|
||||
branch = ENV['BRANCH_TO_BUILD'] || 'master'
|
||||
branch = ENV['BRANCH_TO_BUILD'] || ENV['GIT_BRANCH']
|
||||
begin
|
||||
ensure_git_branch(
|
||||
branch: branch
|
||||
|
|
@ -693,7 +693,7 @@ lane :upload_file_to_s3 do |options|
|
|||
filename_plist = "#{app_name}.plist"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
build_folder_path = Dir[File.expand_path('..')].first
|
||||
file_path = "#{build_folder_path}/#{filename}"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
|
||||
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
|
||||
|
|
@ -51,6 +52,7 @@
|
|||
7BD159C40A68467FB5A17141 /* FontAwesome5_Solid.ttf in Resources */ = {isa = PBXBuildFile; fileRef = DC1D660B55BE462A9C3B8028 /* FontAwesome5_Solid.ttf */; };
|
||||
7F151D3E221B062700FAD8F3 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F151D3D221B062700FAD8F3 /* RuntimeUtils.swift */; };
|
||||
7F151D41221B069200FAD8F3 /* 0155-keys.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F151D40221B069200FAD8F3 /* 0155-keys.png */; };
|
||||
7F1967FF228E379000D19270 /* libKeyboardTrackingView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F11AA10228848D8001C9540 /* libKeyboardTrackingView.a */; };
|
||||
7F1A56B4227E38B600EF7A90 /* libRNCAsyncStorage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F1A569B227E389600EF7A90 /* libRNCAsyncStorage.a */; };
|
||||
7F240A1C220D3A2300637665 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F240A1B220D3A2300637665 /* ShareViewController.swift */; };
|
||||
7F240A1F220D3A2300637665 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7F240A1D220D3A2300637665 /* MainInterface.storyboard */; };
|
||||
|
|
@ -366,6 +368,13 @@
|
|||
remoteGlobalIDString = 134814201AA4EA6300B7C361;
|
||||
remoteInfo = RCTLinking;
|
||||
};
|
||||
7F11AA0F228848D8001C9540 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 7F11AA06228848D8001C9540 /* KeyboardTrackingView.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = D834CED81CC64F2400FA5668;
|
||||
remoteInfo = KeyboardTrackingView;
|
||||
};
|
||||
7F1A569A227E389600EF7A90 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 7F1A5663227E389600EF7A90 /* RNCAsyncStorage.xcodeproj */;
|
||||
|
|
@ -779,6 +788,7 @@
|
|||
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
|
||||
79CB6EBA24FE4ABFB0C155F0 /* YTPlayerView-iframe-player.html */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "YTPlayerView-iframe-player.html"; path = "../node_modules/react-native-youtube/assets/YTPlayerView-iframe-player.html"; sourceTree = "<group>"; };
|
||||
7DCC3D826CE640AF8F491692 /* BVLinearGradient.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = BVLinearGradient.xcodeproj; path = "../node_modules/react-native-linear-gradient/BVLinearGradient.xcodeproj"; sourceTree = "<group>"; };
|
||||
7F11AA06228848D8001C9540 /* KeyboardTrackingView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = KeyboardTrackingView.xcodeproj; path = "../node_modules/react-native-keyboard-tracking-view/lib/KeyboardTrackingView.xcodeproj"; sourceTree = "<group>"; };
|
||||
7F151D3D221B062700FAD8F3 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RuntimeUtils.swift; path = Mattermost/RuntimeUtils.swift; sourceTree = "<group>"; };
|
||||
7F151D40221B069200FAD8F3 /* 0155-keys.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "0155-keys.png"; path = "Mattermost/0155-keys.png"; sourceTree = "<group>"; };
|
||||
7F151D42221B07F700FAD8F3 /* MattermostShare-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MattermostShare-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
|
|
@ -889,6 +899,7 @@
|
|||
files = (
|
||||
7FABE04622137F5C00D0F595 /* libUploadAttachments.a in Frameworks */,
|
||||
74D116AD208A8D5600CF8A79 /* libRNKeychain.a in Frameworks */,
|
||||
7F1967FF228E379000D19270 /* libKeyboardTrackingView.a in Frameworks */,
|
||||
37ABD3C81F4CE142001FDE6B /* libART.a in Frameworks */,
|
||||
375218501F4B9EE70035444B /* libRCTCameraRoll.a in Frameworks */,
|
||||
7F43D5E01F6BF994001FC614 /* libRNSVG.a in Frameworks */,
|
||||
|
|
@ -1241,6 +1252,14 @@
|
|||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7F11AA07228848D8001C9540 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F11AA10228848D8001C9540 /* libKeyboardTrackingView.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7F1A5664227E389600EF7A90 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -1484,6 +1503,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
7FABE04022137F2900D0F595 /* UploadAttachments.xcodeproj */,
|
||||
7F11AA06228848D8001C9540 /* KeyboardTrackingView.xcodeproj */,
|
||||
7F1A5663227E389600EF7A90 /* RNCAsyncStorage.xcodeproj */,
|
||||
7F4C258F227E3AE9009144EF /* RNCNetInfo.xcodeproj */,
|
||||
7F26C1C6219C463300FEB42D /* RNCWebView.xcodeproj */,
|
||||
|
|
@ -1732,6 +1752,10 @@
|
|||
ProductGroup = 7F8C4A5D1F3E21FB003A22BA /* Products */;
|
||||
ProjectRef = 27A6EA89298440439DA9F98D /* JailMonkey.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 7F11AA07228848D8001C9540 /* Products */;
|
||||
ProjectRef = 7F11AA06228848D8001C9540 /* KeyboardTrackingView.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */;
|
||||
ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */;
|
||||
|
|
@ -2130,6 +2154,13 @@
|
|||
remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
7F11AA10228848D8001C9540 /* libKeyboardTrackingView.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libKeyboardTrackingView.a;
|
||||
remoteRef = 7F11AA0F228848D8001C9540 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
7F1A569B227E389600EF7A90 /* libRNCAsyncStorage.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
export NODE_OPTIONS=--max_old_space_size=12000
|
||||
export NODE_BINARY=node
|
||||
|
||||
if [[ "${SENTRY_ENABLED}" = "true" ]]; then
|
||||
|
|
|
|||
1316
package-lock.json
generated
1316
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -40,6 +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-keychain": "3.1.3",
|
||||
"react-native-linear-gradient": "2.5.4",
|
||||
"react-native-local-auth": "github:mattermost/react-native-local-auth#cc9ce2f468fbf7b431dfad3191a31aaa9227a6ab",
|
||||
|
|
|
|||
Loading…
Reference in a new issue