diff --git a/__mocks__/react-native-device-info.js b/__mocks__/react-native-device-info.js
index 270ca77ea..d3fa5fa66 100644
--- a/__mocks__/react-native-device-info.js
+++ b/__mocks__/react-native-device-info.js
@@ -1,5 +1,6 @@
export default {
getModel: () => '',
getReadableVersion: () => '',
- getBundleId: () => ''
+ getBundleId: () => '',
+ isTablet: () => false
};
diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap
index 83b88dfc3..3009165ff 100644
--- a/__tests__/__snapshots__/Storyshots.test.js.snap
+++ b/__tests__/__snapshots__/Storyshots.test.js.snap
@@ -9953,7 +9953,6 @@ exports[`Storyshots Message list 1`] = `
"borderColor": "#e1e5e8",
"borderRadius": 4,
"borderWidth": 1,
- "maxWidth": 400,
"minHeight": 200,
"width": "100%",
},
@@ -10211,7 +10210,6 @@ exports[`Storyshots Message list 1`] = `
"borderColor": "#e1e5e8",
"borderRadius": 4,
"borderWidth": 1,
- "maxWidth": 400,
"minHeight": 200,
"width": "100%",
},
@@ -11009,17 +11007,20 @@ exports[`Storyshots Message list 1`] = `
View
@@ -11334,17 +11335,20 @@ exports[`Storyshots Message list 1`] = `
View
@@ -11540,17 +11544,20 @@ exports[`Storyshots Message list 1`] = `
View
@@ -11718,17 +11725,20 @@ exports[`Storyshots Message list 1`] = `
View
@@ -12130,7 +12140,7 @@ exports[`Storyshots Message list 1`] = `
style={
Object {
"alignItems": "center",
- "alignSelf": "flex-end",
+ "alignSelf": "flex-start",
"backgroundColor": "#f3f4f5",
"borderColor": "#e1e5e8",
"borderRadius": 4,
@@ -12493,7 +12503,7 @@ exports[`Storyshots Message list 1`] = `
style={
Object {
"alignItems": "center",
- "alignSelf": "flex-end",
+ "alignSelf": "flex-start",
"backgroundColor": "#f3f4f5",
"borderColor": "#e1e5e8",
"borderRadius": 4,
@@ -19491,7 +19501,7 @@ exports[`Storyshots Message list 1`] = `
style={
Object {
"alignItems": "center",
- "alignSelf": "flex-end",
+ "alignSelf": "flex-start",
"backgroundColor": "#f3f4f5",
"borderColor": "#e1e5e8",
"borderRadius": 4,
@@ -20028,7 +20038,7 @@ exports[`Storyshots Message list 1`] = `
style={
Object {
"alignItems": "center",
- "alignSelf": "flex-end",
+ "alignSelf": "flex-start",
"backgroundColor": "#f3f4f5",
"borderColor": "#e1e5e8",
"borderRadius": 4,
@@ -20222,7 +20232,7 @@ exports[`Storyshots Message list 1`] = `
style={
Object {
"alignItems": "center",
- "alignSelf": "flex-end",
+ "alignSelf": "flex-start",
"backgroundColor": "#f3f4f5",
"borderColor": "#e1e5e8",
"borderRadius": 4,
diff --git a/android/app/src/main/res/layout/launch_screen.xml b/android/app/src/main/res/layout/launch_screen.xml
index 7ec70ce1e..97c4ac273 100644
--- a/android/app/src/main/res/layout/launch_screen.xml
+++ b/android/app/src/main/res/layout/launch_screen.xml
@@ -1,6 +1,13 @@
-
+
\ No newline at end of file
diff --git a/app/commands.js b/app/commands.js
new file mode 100644
index 000000000..5c0ffd599
--- /dev/null
+++ b/app/commands.js
@@ -0,0 +1,187 @@
+/* eslint-disable no-bitwise */
+import { constants } from 'react-native-keycommands';
+
+import I18n from './i18n';
+
+const KEY_TYPING = '\t';
+const KEY_PREFERENCES = 'p';
+const KEY_SEARCH = 'f';
+const KEY_PREVIOUS_ROOM = '[';
+const KEY_NEXT_ROOM = ']';
+const KEY_NEW_ROOM = __DEV__ ? 'e' : 'n';
+const KEY_ROOM_ACTIONS = __DEV__ ? 'b' : 'i';
+const KEY_UPLOAD = 'u';
+const KEY_REPLY = ';';
+const KEY_SERVER_SELECTION = __DEV__ ? 'o' : '`';
+const KEY_ADD_SERVER = __DEV__ ? 'l' : 'n';
+const KEY_SEND_MESSAGE = '\r';
+const KEY_SELECT = '123456789';
+
+export const defaultCommands = [
+ {
+ // Focus messageBox
+ input: KEY_TYPING,
+ modifierFlags: 0,
+ discoverabilityTitle: I18n.t('Type_message')
+ },
+ {
+ // Send message on textInput to current room
+ input: KEY_SEND_MESSAGE,
+ modifierFlags: 0,
+ discoverabilityTitle: I18n.t('Send')
+ }
+];
+
+export const keyCommands = [
+ {
+ // Open Preferences Modal
+ input: KEY_PREFERENCES,
+ modifierFlags: constants.keyModifierCommand,
+ discoverabilityTitle: I18n.t('Preferences')
+ },
+ {
+ // Focus Room Search
+ input: KEY_SEARCH,
+ modifierFlags: constants.keyModifierCommand | constants.keyModifierAlternate,
+ discoverabilityTitle: I18n.t('Room_search')
+ },
+ {
+ // Select a room by order using 1-9
+ input: '1...9',
+ modifierFlags: constants.keyModifierCommand,
+ discoverabilityTitle: I18n.t('Room_selection')
+ },
+ {
+ // Change room to next on Rooms List
+ input: KEY_NEXT_ROOM,
+ modifierFlags: constants.keyModifierCommand,
+ discoverabilityTitle: I18n.t('Next_room')
+ },
+ {
+ // Change room to previous on Rooms List
+ input: KEY_PREVIOUS_ROOM,
+ modifierFlags: constants.keyModifierCommand,
+ discoverabilityTitle: I18n.t('Previous_room')
+ },
+ {
+ // Open New Room Modal
+ input: KEY_NEW_ROOM,
+ modifierFlags: constants.keyModifierCommand,
+ discoverabilityTitle: I18n.t('New_room')
+ },
+ {
+ // Open Room Actions
+ input: KEY_ROOM_ACTIONS,
+ modifierFlags: constants.keyModifierCommand,
+ discoverabilityTitle: I18n.t('Room_actions')
+ },
+ {
+ // Upload a file to room
+ input: KEY_UPLOAD,
+ modifierFlags: constants.keyModifierCommand,
+ discoverabilityTitle: I18n.t('Upload_room')
+ },
+ {
+ // Search Messages on current room
+ input: KEY_SEARCH,
+ modifierFlags: constants.keyModifierCommand,
+ discoverabilityTitle: I18n.t('Search_messages')
+ },
+ {
+ // Scroll messages on current room
+ input: '↑ ↓',
+ modifierFlags: constants.keyModifierAlternate,
+ discoverabilityTitle: I18n.t('Scroll_messages')
+ },
+ {
+ // Scroll up messages on current room
+ input: constants.keyInputUpArrow,
+ modifierFlags: constants.keyModifierAlternate
+ },
+ {
+ // Scroll down messages on current room
+ input: constants.keyInputDownArrow,
+ modifierFlags: constants.keyModifierAlternate
+ },
+ {
+ // Reply latest message with Quote
+ input: KEY_REPLY,
+ modifierFlags: constants.keyModifierCommand,
+ discoverabilityTitle: I18n.t('Reply_latest')
+ },
+ {
+ // Open server dropdown
+ input: KEY_SERVER_SELECTION,
+ modifierFlags: constants.keyModifierCommand | constants.keyModifierAlternate,
+ discoverabilityTitle: I18n.t('Server_selection')
+ },
+ {
+ // Select a server by order using 1-9
+ input: '1...9',
+ modifierFlags: constants.keyModifierCommand | constants.keyModifierAlternate,
+ discoverabilityTitle: I18n.t('Server_selection_numbers')
+ },
+ {
+ // Navigate to add new server
+ input: KEY_ADD_SERVER,
+ modifierFlags: constants.keyModifierCommand | constants.keyModifierAlternate,
+ discoverabilityTitle: I18n.t('Add_server')
+ },
+ // Refers to select rooms on list
+ ...([1, 2, 3, 4, 5, 6, 7, 8, 9].map(value => ({
+ input: `${ value }`,
+ modifierFlags: constants.keyModifierCommand
+ }))),
+ // Refers to select servers on list
+ ...([1, 2, 3, 4, 5, 6, 7, 8, 9].map(value => ({
+ input: `${ value }`,
+ modifierFlags: constants.keyModifierCommand | constants.keyModifierAlternate
+ })))
+];
+
+export const KEY_COMMAND = 'KEY_COMMAND';
+
+export const commandHandle = (event, key, flags = []) => {
+ const { input, modifierFlags } = event;
+ let _flags = 0;
+ if (flags.includes('command') && flags.includes('alternate')) {
+ _flags = constants.keyModifierCommand | constants.keyModifierAlternate;
+ } else if (flags.includes('command')) {
+ _flags = constants.keyModifierCommand;
+ } else if (flags.includes('alternate')) {
+ _flags = constants.keyModifierAlternate;
+ }
+ return key.includes(input) && modifierFlags === _flags;
+};
+
+export const handleCommandTyping = event => commandHandle(event, KEY_TYPING);
+
+export const handleCommandSubmit = event => commandHandle(event, KEY_SEND_MESSAGE);
+
+export const handleCommandShowUpload = event => commandHandle(event, KEY_UPLOAD, ['command']);
+
+export const handleCommandScroll = event => commandHandle(event, [constants.keyInputUpArrow, constants.keyInputDownArrow], ['alternate']);
+
+export const handleCommandRoomActions = event => commandHandle(event, KEY_ROOM_ACTIONS, ['command']);
+
+export const handleCommandSearchMessages = event => commandHandle(event, KEY_SEARCH, ['command']);
+
+export const handleCommandReplyLatest = event => commandHandle(event, KEY_REPLY, ['command']);
+
+export const handleCommandSelectServer = event => commandHandle(event, KEY_SELECT, ['command', 'alternate']);
+
+export const handleCommandShowPreferences = event => commandHandle(event, KEY_PREFERENCES, ['command']);
+
+export const handleCommandSearching = event => commandHandle(event, KEY_SEARCH, ['command', 'alternate']);
+
+export const handleCommandSelectRoom = event => commandHandle(event, KEY_SELECT, ['command']);
+
+export const handleCommandPreviousRoom = event => commandHandle(event, KEY_PREVIOUS_ROOM, ['command']);
+
+export const handleCommandNextRoom = event => commandHandle(event, KEY_NEXT_ROOM, ['command']);
+
+export const handleCommandShowNewMessage = event => commandHandle(event, KEY_NEW_ROOM, ['command']);
+
+export const handleCommandAddNewServer = event => commandHandle(event, KEY_ADD_SERVER, ['command', 'alternate']);
+
+export const handleCommandOpenServerDropdown = event => commandHandle(event, KEY_SERVER_SELECTION, ['command', 'alternate']);
diff --git a/app/constants/tablet.js b/app/constants/tablet.js
new file mode 100644
index 000000000..16e62f6d0
--- /dev/null
+++ b/app/constants/tablet.js
@@ -0,0 +1,4 @@
+export const MAX_SIDEBAR_WIDTH = 321;
+export const MAX_CONTENT_WIDTH = '90%';
+export const MAX_SCREEN_CONTENT_WIDTH = '45%';
+export const MIN_WIDTH_SPLIT_LAYOUT = 700;
diff --git a/app/containers/Avatar.js b/app/containers/Avatar.js
index c616788cf..1ad9e93ba 100644
--- a/app/containers/Avatar.js
+++ b/app/containers/Avatar.js
@@ -2,13 +2,14 @@ import React from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import FastImage from 'react-native-fast-image';
+import Touch from '../utils/touch';
const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => (
`${ baseUrl }${ url }?format=png&width=${ uriSize }&height=${ uriSize }${ avatarAuthURLFragment }`
);
const Avatar = React.memo(({
- text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token
+ text, size, baseUrl, borderRadius, style, avatar, type, children, userId, token, onPress
}) => {
const avatarStyle = {
width: size,
@@ -39,7 +40,7 @@ const Avatar = React.memo(({
}
- const image = (
+ let image = (
);
+ if (onPress) {
+ image = (
+
+ {image}
+
+ );
+ }
+
return (
{image}
@@ -67,7 +76,8 @@ Avatar.propTypes = {
type: PropTypes.string,
children: PropTypes.object,
userId: PropTypes.string,
- token: PropTypes.string
+ token: PropTypes.string,
+ onPress: PropTypes.func
};
Avatar.defaultProps = {
diff --git a/app/containers/EmojiPicker/EmojiCategory.js b/app/containers/EmojiPicker/EmojiCategory.js
index e5b572d97..e7a71f513 100644
--- a/app/containers/EmojiPicker/EmojiCategory.js
+++ b/app/containers/EmojiPicker/EmojiCategory.js
@@ -1,20 +1,18 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Text, TouchableOpacity } from 'react-native';
+import { Text, TouchableOpacity, FlatList } from 'react-native';
import { shortnameToUnicode } from 'emoji-toolkit';
import { responsive } from 'react-native-responsive-ui';
-import { OptimizedFlatList } from 'react-native-optimized-flatlist';
import styles from './styles';
import CustomEmoji from './CustomEmoji';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
-import { isIOS } from '../../utils/deviceInfo';
-const EMOJIS_PER_ROW = isIOS ? 8 : 9;
+const EMOJI_SIZE = 50;
const renderEmoji = (emoji, size, baseUrl) => {
- if (emoji.isCustom) {
- return ;
+ if (emoji && emoji.isCustom) {
+ return ;
}
return (
@@ -33,44 +31,41 @@ class EmojiCategory extends React.Component {
width: PropTypes.number
}
- constructor(props) {
- super(props);
- const { window, width, emojisPerRow } = this.props;
- const { width: widthWidth, height: windowHeight } = window;
-
- this.size = Math.min(width || widthWidth, windowHeight) / (emojisPerRow || EMOJIS_PER_ROW);
- this.emojis = props.emojis;
- }
-
- shouldComponentUpdate() {
- return false;
- }
-
- renderItem(emoji, size) {
+ renderItem(emoji) {
const { baseUrl, onEmojiSelected } = this.props;
return (
onEmojiSelected(emoji)}
- testID={`reaction-picker-${ emoji.isCustom ? emoji.content : emoji }`}
+ testID={`reaction-picker-${ emoji && emoji.isCustom ? emoji.content : emoji }`}
>
- {renderEmoji(emoji, size, baseUrl)}
+ {renderEmoji(emoji, EMOJI_SIZE, baseUrl)}
);
}
render() {
- const { emojis } = this.props;
+ const { emojis, width } = this.props;
+
+ if (!width) {
+ return null;
+ }
+
+ const numColumns = Math.trunc(width / EMOJI_SIZE);
+ const marginHorizontal = (width - (numColumns * EMOJI_SIZE)) / 2;
return (
- (item.isCustom && item.content) || item}
+ (item && item.isCustom && item.content) || item}
data={emojis}
- renderItem={({ item }) => this.renderItem(item, this.size)}
- numColumns={EMOJIS_PER_ROW}
+ extraData={this.props}
+ renderItem={({ item }) => this.renderItem(item)}
+ numColumns={numColumns}
initialNumToRender={45}
- getItemLayout={(data, index) => ({ length: this.size, offset: this.size * index, index })}
removeClippedSubviews
{...scrollPersistTaps}
/>
diff --git a/app/containers/EmojiPicker/index.js b/app/containers/EmojiPicker/index.js
index 05ec129c1..8ec587304 100644
--- a/app/containers/EmojiPicker/index.js
+++ b/app/containers/EmojiPicker/index.js
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
+import { View } from 'react-native';
import PropTypes from 'prop-types';
-import { ScrollView } from 'react-native';
import ScrollableTabView from 'react-native-scrollable-tab-view';
import { shortnameToUnicode } from 'emoji-toolkit';
import equal from 'deep-equal';
@@ -27,9 +27,7 @@ class EmojiPicker extends Component {
baseUrl: PropTypes.string.isRequired,
customEmojis: PropTypes.object,
onEmojiSelected: PropTypes.func,
- tabEmojiStyle: PropTypes.object,
- emojisPerRow: PropTypes.number,
- width: PropTypes.number
+ tabEmojiStyle: PropTypes.object
};
constructor(props) {
@@ -44,7 +42,8 @@ class EmojiPicker extends Component {
this.state = {
frequentlyUsed: [],
customEmojis,
- show: false
+ show: false,
+ width: null
};
}
@@ -54,12 +53,11 @@ class EmojiPicker extends Component {
}
shouldComponentUpdate(nextProps, nextState) {
- const { frequentlyUsed, show } = this.state;
- const { width } = this.props;
+ const { frequentlyUsed, show, width } = this.state;
if (nextState.show !== show) {
return true;
}
- if (nextProps.width !== width) {
+ if (nextState.width !== width) {
return true;
}
if (!equal(nextState.frequentlyUsed, frequentlyUsed)) {
@@ -126,11 +124,11 @@ class EmojiPicker extends Component {
this.setState({ frequentlyUsed });
}
- renderCategory(category, i) {
- const { frequentlyUsed, customEmojis } = this.state;
- const {
- emojisPerRow, width, baseUrl
- } = this.props;
+ onLayout = ({ nativeEvent: { layout: { width } } }) => this.setState({ width });
+
+ renderCategory(category, i, label) {
+ const { frequentlyUsed, customEmojis, width } = this.state;
+ const { baseUrl } = this.props;
let emojis = [];
if (i === 0) {
@@ -145,9 +143,9 @@ class EmojiPicker extends Component {
emojis={emojis}
onEmojiSelected={emoji => this.onEmojiSelected(emoji)}
style={styles.categoryContainer}
- size={emojisPerRow}
width={width}
baseUrl={baseUrl}
+ tabLabel={label}
/>
);
}
@@ -160,26 +158,21 @@ class EmojiPicker extends Component {
return null;
}
return (
- }
- contentProps={scrollProps}
- style={styles.background}
- >
- {
- categories.tabs.map((tab, i) => (
- (i === 0 && frequentlyUsed.length === 0) ? null // when no frequentlyUsed don't show the tab
- : (
-
- {this.renderCategory(tab.category, i)}
-
- )))
- }
-
+
+ }
+ contentProps={scrollProps}
+ style={styles.background}
+ >
+ {
+ categories.tabs.map((tab, i) => (
+ (i === 0 && frequentlyUsed.length === 0) ? null // when no frequentlyUsed don't show the tab
+ : (
+ this.renderCategory(tab.category, i, tab.tabLabel)
+ )))
+ }
+
+
);
}
}
diff --git a/app/containers/EmojiPicker/styles.js b/app/containers/EmojiPicker/styles.js
index d4e993f50..2efe1e0ec 100644
--- a/app/containers/EmojiPicker/styles.js
+++ b/app/containers/EmojiPicker/styles.js
@@ -56,6 +56,6 @@ export default StyleSheet.create({
textAlign: 'center'
},
customCategoryEmoji: {
- margin: 4
+ margin: 8
}
});
diff --git a/app/containers/ListItem.js b/app/containers/ListItem.js
index 069329343..0149fe95b 100644
--- a/app/containers/ListItem.js
+++ b/app/containers/ListItem.js
@@ -35,11 +35,11 @@ const styles = StyleSheet.create({
});
const Content = React.memo(({
- title, subtitle, disabled, testID, right
+ title, subtitle, disabled, testID, right, color
}) => (
- {title}
+ {title}
{subtitle
? {subtitle}
: null
@@ -78,6 +78,7 @@ Content.propTypes = {
subtitle: PropTypes.string,
right: PropTypes.func,
disabled: PropTypes.bool,
+ color: PropTypes.string,
testID: PropTypes.string
};
diff --git a/app/containers/MessageBox/EmojiKeyboard.js b/app/containers/MessageBox/EmojiKeyboard.js
index 28e75c97c..a7943a850 100644
--- a/app/containers/MessageBox/EmojiKeyboard.js
+++ b/app/containers/MessageBox/EmojiKeyboard.js
@@ -20,7 +20,7 @@ export default class EmojiKeyboard extends React.PureComponent {
render() {
return (
- this.onEmojiSelected(emoji)} baseUrl={this.baseUrl} />
+
);
}
diff --git a/app/containers/MessageBox/UploadModal.js b/app/containers/MessageBox/UploadModal.js
index 3773391b7..18a43e113 100644
--- a/app/containers/MessageBox/UploadModal.js
+++ b/app/containers/MessageBox/UploadModal.js
@@ -11,17 +11,20 @@ import TextInput from '../TextInput';
import Button from '../Button';
import I18n from '../../i18n';
import sharedStyles from '../../views/Styles';
-import { isIOS } from '../../utils/deviceInfo';
+import { isIOS, isTablet } from '../../utils/deviceInfo';
import {
COLOR_PRIMARY, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE
} from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons';
+import { withSplit } from '../../split';
const cancelButtonColor = COLOR_BACKGROUND_CONTAINER;
const styles = StyleSheet.create({
modal: {
- alignItems: 'center'
+ width: '100%',
+ alignItems: 'center',
+ margin: 0
},
titleContainer: {
flexDirection: 'row',
@@ -48,6 +51,9 @@ const styles = StyleSheet.create({
marginBottom: 16,
resizeMode: 'contain'
},
+ bigPreview: {
+ height: 250
+ },
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
@@ -91,7 +97,8 @@ class UploadModal extends Component {
file: PropTypes.object,
close: PropTypes.func,
submit: PropTypes.func,
- window: PropTypes.object
+ window: PropTypes.object,
+ split: PropTypes.bool
}
state = {
@@ -113,11 +120,14 @@ class UploadModal extends Component {
shouldComponentUpdate(nextProps, nextState) {
const { name, description, file } = this.state;
- const { window, isVisible } = this.props;
+ const { window, isVisible, split } = this.props;
if (nextState.name !== name) {
return true;
}
+ if (nextProps.split !== split) {
+ return true;
+ }
if (nextState.description !== description) {
return true;
}
@@ -184,9 +194,9 @@ class UploadModal extends Component {
}
renderPreview() {
- const { file } = this.props;
+ const { file, split } = this.props;
if (file.mime && file.mime.match(/image/)) {
- return ();
+ return ();
}
if (file.mime && file.mime.match(/video/)) {
return (
@@ -200,7 +210,7 @@ class UploadModal extends Component {
render() {
const {
- window: { width }, isVisible, close
+ window: { width }, isVisible, close, split
} = this.props;
const { name, description } = this.state;
return (
@@ -215,7 +225,7 @@ class UploadModal extends Component {
hideModalContentWhileAnimating
avoidKeyboard
>
-
+
{I18n.t('Upload_file_question_mark')}
@@ -240,4 +250,4 @@ class UploadModal extends Component {
}
}
-export default responsive(UploadModal);
+export default responsive(withSplit(UploadModal));
diff --git a/app/containers/MessageBox/index.js b/app/containers/MessageBox/index.js
index cb7feb555..a4e9ef9f6 100644
--- a/app/containers/MessageBox/index.js
+++ b/app/containers/MessageBox/index.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
- View, TextInput, Alert
+ View, TextInput, Alert, Keyboard
} from 'react-native';
import { connect } from 'react-redux';
import { KeyboardAccessoryView } from 'react-native-keyboard-input';
@@ -25,8 +25,15 @@ import debounce from '../../utils/debounce';
import { COLOR_TEXT_DESCRIPTION } from '../../constants/colors';
import LeftButtons from './LeftButtons';
import RightButtons from './RightButtons';
-import { isAndroid } from '../../utils/deviceInfo';
+import { isAndroid, isTablet } from '../../utils/deviceInfo';
import { canUploadFile } from '../../utils/media';
+import EventEmiter from '../../utils/events';
+import {
+ KEY_COMMAND,
+ handleCommandTyping,
+ handleCommandSubmit,
+ handleCommandShowUpload
+} from '../../commands';
import Mentions from './Mentions';
import MessageboxContext from './Context';
import {
@@ -98,8 +105,8 @@ class MessageBox extends Component {
commandPreview: [],
showCommandPreview: false
};
- this.onEmojiSelected = this.onEmojiSelected.bind(this);
this.text = '';
+ this.focused = false;
this.fileOptions = [
I18n.t('Cancel'),
I18n.t('Take_a_photo'),
@@ -162,6 +169,10 @@ class MessageBox extends Component {
if (isAndroid) {
require('./EmojiKeyboard');
}
+
+ if (isTablet) {
+ EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands);
+ }
}
componentWillReceiveProps(nextProps) {
@@ -239,12 +250,16 @@ class MessageBox extends Component {
if (this.getSlashCommands && this.getSlashCommands.stop) {
this.getSlashCommands.stop();
}
+ if (isTablet) {
+ EventEmiter.removeListener(KEY_COMMAND, this.handleCommands);
+ }
}
onChangeText = (text) => {
const isTextEmpty = text.length === 0;
this.setShowSend(!isTextEmpty);
this.debouncedOnChangeText(text);
+ this.setInput(text);
}
// eslint-disable-next-line react/sort-comp
@@ -253,7 +268,6 @@ class MessageBox extends Component {
const isTextEmpty = text.length === 0;
// this.setShowSend(!isTextEmpty);
this.handleTyping(!isTextEmpty);
- this.setInput(text);
// matches if their is text that stats with '/' and group the command and params so we can use it "/command params"
const slashCommand = text.match(/^\/([a-z0-9._-]+) (.+)/im);
if (slashCommand) {
@@ -453,7 +467,10 @@ class MessageBox extends Component {
}
setShowSend = (showSend) => {
- this.setState({ showSend });
+ const { showSend: prevShowSend } = this.state;
+ if (prevShowSend !== showSend) {
+ this.setState({ showSend });
+ }
}
clearInput = () => {
@@ -624,6 +641,7 @@ class MessageBox extends Component {
const message = this.text;
this.clearInput();
+ this.debouncedOnChangeText.stop();
this.closeEmoji();
this.stopTrackingMention();
this.handleTyping(false);
@@ -725,6 +743,21 @@ class MessageBox extends Component {
});
}
+ handleCommands = ({ event }) => {
+ if (handleCommandTyping(event)) {
+ if (this.focused) {
+ Keyboard.dismiss();
+ } else {
+ this.component.focus();
+ }
+ this.focused = !this.focused;
+ } else if (handleCommandSubmit(event)) {
+ this.submit();
+ } else if (handleCommandShowUpload(event)) {
+ this.showFileActions();
+ }
+ }
+
renderContent = () => {
const {
recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview
@@ -733,6 +766,12 @@ class MessageBox extends Component {
editing, message, replying, replyCancel, user, getCustomEmoji
} = this.props;
+ const isAndroidTablet = isTablet && isAndroid ? {
+ multiline: false,
+ onSubmitEditing: this.submit,
+ returnKeyType: 'send'
+ } : {};
+
if (recording) {
return ;
}
@@ -773,6 +812,7 @@ class MessageBox extends Component {
multiline
placeholderTextColor={COLOR_TEXT_DESCRIPTION}
testID='messagebox-input'
+ {...isAndroidTablet}
/>
(
);
const SearchBox = ({
- onChangeText, onSubmitEditing, testID, hasCancel, onCancelPress, ...props
+ onChangeText, onSubmitEditing, testID, hasCancel, onCancelPress, inputRef, ...props
}) => (
-
+