diff --git a/.circleci/config.yml b/.circleci/config.yml
index 9541943c0..cea7b0274 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -203,7 +203,7 @@ jobs:
name: Fastlane Tesflight Upload
command: |
cd ios
- fastlane pilot upload --changelog "$(sh ../.circleci/changelog.sh)"
+ fastlane pilot upload --ipa ios/RocketChatRN.ipa --changelog "$(sh ../.circleci/changelog.sh)"
workflows:
version: 2
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 1d5816d81..e6c65008c 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -144,6 +144,7 @@ android {
}
dependencies {
+ compile project(":reactnativekeyboardinput")
compile project(':react-native-splash-screen')
compile project(':react-native-video')
compile project(':react-native-push-notification')
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 67e24ad00..494507756 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -9,12 +9,12 @@
+
-
getPackages() {
return Arrays.asList(
new MainReactPackage(),
- new SvgPackage(),
- new ImagePickerPackage(),
- new VectorIconsPackage(),
- new RNFetchBlobPackage(),
- new ZeroconfReactPackage(),
- new RealmReactPackage(),
- new ReactNativePushNotificationPackage(),
- new ReactVideoPackage(),
- new SplashScreenReactPackage(),
- new RCTToastPackage()
+ new SvgPackage(),
+ new ImagePickerPackage(),
+ new VectorIconsPackage(),
+ new RNFetchBlobPackage(),
+ new ZeroconfReactPackage(),
+ new RealmReactPackage(),
+ new ReactNativePushNotificationPackage(),
+ new ReactVideoPackage(),
+ new SplashScreenReactPackage(),
+ new RCTToastPackage(),
+ new KeyboardInputPackage(MainApplication.this)
);
}
};
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index 319eb0ca1..654ec9502 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -3,6 +3,7 @@
diff --git a/android/gradle.properties b/android/gradle.properties
index 1fd964e90..732c56e3e 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -18,3 +18,4 @@
# org.gradle.parallel=true
android.useDeprecatedNdk=true
+# VERSIONCODE=999999999
diff --git a/android/settings.gradle b/android/settings.gradle
index 5455aadb5..236de741b 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -1,4 +1,6 @@
rootProject.name = 'RocketChatRN'
+include ':reactnativekeyboardinput'
+project(':reactnativekeyboardinput').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keyboard-input/lib/android')
include ':react-native-splash-screen'
project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android')
include ':react-native-video'
diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js
index b4123946b..2422a207a 100644
--- a/app/actions/actionsTypes.js
+++ b/app/actions/actionsTypes.js
@@ -28,7 +28,17 @@ export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [
]);
export const USER = createRequestTypes('USER', ['SET']);
export const ROOMS = createRequestTypes('ROOMS', [...defaultTypes, 'SET_SEARCH']);
-export const ROOM = createRequestTypes('ROOM', ['ADD_USER_TYPING', 'REMOVE_USER_TYPING', 'SOMEONE_TYPING', 'OPEN', 'CLOSE', 'USER_TYPING', 'MESSAGE_RECEIVED', 'SET_LAST_OPEN']);
+export const ROOM = createRequestTypes('ROOM', [
+ 'ADD_USER_TYPING',
+ 'REMOVE_USER_TYPING',
+ 'SOMEONE_TYPING',
+ 'OPEN',
+ 'CLOSE',
+ 'USER_TYPING',
+ 'MESSAGE_RECEIVED',
+ 'SET_LAST_OPEN',
+ 'LAYOUT_ANIMATION'
+]);
export const APP = createRequestTypes('APP', ['READY', 'INIT']);
export const MESSAGES = createRequestTypes('MESSAGES', [
...defaultTypes,
@@ -55,7 +65,8 @@ export const MESSAGES = createRequestTypes('MESSAGES', [
'TOGGLE_PIN_SUCCESS',
'TOGGLE_PIN_FAILURE',
'SET_INPUT',
- 'CLEAR_INPUT'
+ 'CLEAR_INPUT',
+ 'TOGGLE_REACTION_PICKER'
]);
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [
...defaultTypes,
@@ -81,4 +92,4 @@ export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'REQUEST'
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
-export const KEYBOARD = createRequestTypes('KEYBOARD', ['OPEN', 'CLOSE']);
+
diff --git a/app/actions/keyboard.js b/app/actions/keyboard.js
deleted file mode 100644
index 6c598d024..000000000
--- a/app/actions/keyboard.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as types from './actionsTypes';
-
-export function setKeyboardOpen() {
- return {
- type: types.KEYBOARD.OPEN
- };
-}
-
-export function setKeyboardClosed() {
- return {
- type: types.KEYBOARD.CLOSE
- };
-}
diff --git a/app/actions/messages.js b/app/actions/messages.js
index 29c1b4ca9..ed9bf4971 100644
--- a/app/actions/messages.js
+++ b/app/actions/messages.js
@@ -176,3 +176,10 @@ export function clearInput() {
type: types.MESSAGES.CLEAR_INPUT
};
}
+
+export function toggleReactionPicker(message) {
+ return {
+ type: types.MESSAGES.TOGGLE_REACTION_PICKER,
+ message
+ };
+}
diff --git a/app/actions/room.js b/app/actions/room.js
index ebae2bd6c..1b3a67e1f 100644
--- a/app/actions/room.js
+++ b/app/actions/room.js
@@ -55,3 +55,9 @@ export function setLastOpen(date = new Date()) {
date
};
}
+
+export function layoutAnimation() {
+ return {
+ type: types.ROOM.LAYOUT_ANIMATION
+ };
+}
diff --git a/app/animations/collapse.js b/app/animations/collapse.js
index 35bca751d..05cfce52a 100644
--- a/app/animations/collapse.js
+++ b/app/animations/collapse.js
@@ -42,7 +42,8 @@ export default class Panel extends React.Component {
this.state.animation,
{
toValue: finalValue,
- duration: 150
+ duration: 150,
+ useNativeDriver: true
}
).start();
}
diff --git a/app/animations/fade.js b/app/animations/fade.js
index b9f276903..36da8da33 100644
--- a/app/animations/fade.js
+++ b/app/animations/fade.js
@@ -29,7 +29,8 @@ export default class Fade extends React.Component {
}
Animated.timing(this._visibility, {
toValue: nextProps.visible ? 1 : 0,
- duration: 300
+ duration: 300,
+ useNativeDriver: true
}).start(() => {
this.setState({ visible: nextProps.visible });
});
diff --git a/app/containers/Avatar.js b/app/containers/Avatar.js
index 8ff05e6d0..f547b7788 100644
--- a/app/containers/Avatar.js
+++ b/app/containers/Avatar.js
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
-import { StyleSheet, Text, View } from 'react-native';
+import { StyleSheet, Text, View, ViewPropTypes } from 'react-native';
import { CachedImage } from 'react-native-img-cache';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
@@ -23,8 +23,16 @@ const styles = StyleSheet.create({
@connect(state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
}))
-
-class Avatar extends React.PureComponent {
+export default class Avatar extends React.PureComponent {
+ static propTypes = {
+ style: ViewPropTypes.style,
+ baseUrl: PropTypes.string,
+ text: PropTypes.string.isRequired,
+ avatar: PropTypes.string,
+ size: PropTypes.number,
+ borderRadius: PropTypes.number,
+ type: PropTypes.string
+ };
render() {
const {
text = '', size = 25, baseUrl, borderRadius = 4, style, avatar, type = 'd'
@@ -76,14 +84,3 @@ class Avatar extends React.PureComponent {
);
}
}
-
-Avatar.propTypes = {
- style: PropTypes.object,
- baseUrl: PropTypes.string,
- text: PropTypes.string.isRequired,
- avatar: PropTypes.string,
- size: PropTypes.number,
- borderRadius: PropTypes.number,
- type: PropTypes.string
-};
-export default Avatar;
diff --git a/app/containers/CustomEmoji.js b/app/containers/EmojiPicker/CustomEmoji.js
similarity index 67%
rename from app/containers/CustomEmoji.js
rename to app/containers/EmojiPicker/CustomEmoji.js
index 166e065ab..8215f0ea6 100644
--- a/app/containers/CustomEmoji.js
+++ b/app/containers/EmojiPicker/CustomEmoji.js
@@ -1,4 +1,5 @@
import React from 'react';
+import { ViewPropTypes } from 'react-native';
import PropTypes from 'prop-types';
import { CachedImage } from 'react-native-img-cache';
import { connect } from 'react-redux';
@@ -6,19 +7,21 @@ import { connect } from 'react-redux';
@connect(state => ({
baseUrl: state.settings.Site_Url
}))
-export default class extends React.PureComponent {
+export default class CustomEmoji extends React.Component {
static propTypes = {
baseUrl: PropTypes.string.isRequired,
emoji: PropTypes.object.isRequired,
- style: PropTypes.object
+ style: ViewPropTypes.style
+ }
+ shouldComponentUpdate() {
+ return false;
}
-
render() {
const { baseUrl, emoji, style } = this.props;
return (
);
}
diff --git a/app/containers/EmojiPicker/EmojiCategory.js b/app/containers/EmojiPicker/EmojiCategory.js
new file mode 100644
index 000000000..09e15fde3
--- /dev/null
+++ b/app/containers/EmojiPicker/EmojiCategory.js
@@ -0,0 +1,74 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Text, TouchableOpacity, Platform } from 'react-native';
+import { emojify } from 'react-emojione';
+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';
+
+const emojisPerRow = Platform.OS === 'ios' ? 8 : 9;
+
+const renderEmoji = (emoji, size) => {
+ if (emoji.isCustom) {
+ return ;
+ }
+ return (
+
+ {emojify(`:${ emoji }:`, { output: 'unicode' })}
+
+ );
+};
+
+
+@responsive
+export default class EmojiCategory extends React.Component {
+ static propTypes = {
+ emojis: PropTypes.any,
+ window: PropTypes.any,
+ onEmojiSelected: PropTypes.func,
+ emojisPerRow: PropTypes.number,
+ width: PropTypes.number
+ };
+ constructor(props) {
+ super(props);
+ const { width, height } = this.props.window;
+
+ this.size = Math.min(this.props.width || width, height) / (this.props.emojisPerRow || emojisPerRow);
+ this.emojis = [];
+ }
+ componentWillMount() {
+ this.emojis = this.props.emojis;
+ }
+
+ shouldComponentUpdate() {
+ return false;
+ }
+
+ renderItem(emoji, size) {
+ return (
+ this.props.onEmojiSelected(emoji)}
+ >
+ {renderEmoji(emoji, size)}
+ );
+ }
+
+ render() {
+ return (
+ (item.isCustom && item.content) || item}
+ data={this.props.emojis}
+ renderItem={({ item }) => this.renderItem(item, this.size)}
+ numColumns={emojisPerRow}
+ initialNumToRender={45}
+ getItemLayout={(data, index) => ({ length: this.size, offset: this.size * index, index })}
+ removeClippedSubviews
+ {...scrollPersistTaps}
+ />
+ );
+ }
+}
diff --git a/app/containers/MessageBox/EmojiPicker/TabBar.js b/app/containers/EmojiPicker/TabBar.js
similarity index 60%
rename from app/containers/MessageBox/EmojiPicker/TabBar.js
rename to app/containers/EmojiPicker/TabBar.js
index 9bc131a46..4886006a6 100644
--- a/app/containers/MessageBox/EmojiPicker/TabBar.js
+++ b/app/containers/EmojiPicker/TabBar.js
@@ -3,19 +3,25 @@ import PropTypes from 'prop-types';
import { View, TouchableOpacity, Text } from 'react-native';
import styles from './styles';
-export default class extends React.PureComponent {
+export default class TabBar extends React.PureComponent {
static propTypes = {
goToPage: PropTypes.func,
activeTab: PropTypes.number,
- tabs: PropTypes.array
+ tabs: PropTypes.array,
+ tabEmojiStyle: PropTypes.object
}
render() {
return (
{this.props.tabs.map((tab, i) => (
- this.props.goToPage(i)} style={styles.tab}>
- {tab}
+ this.props.goToPage(i)}
+ style={styles.tab}
+ >
+ {tab}
{this.props.activeTab === i ? : }
))}
diff --git a/app/containers/MessageBox/EmojiPicker/categories.js b/app/containers/EmojiPicker/categories.js
similarity index 75%
rename from app/containers/MessageBox/EmojiPicker/categories.js
rename to app/containers/EmojiPicker/categories.js
index 341c6a83e..a95f67cf6 100644
--- a/app/containers/MessageBox/EmojiPicker/categories.js
+++ b/app/containers/EmojiPicker/categories.js
@@ -1,4 +1,4 @@
-const list = ['Frequently Used', 'Custom', 'Smileys & People', 'Animals & Nature', 'Food & Drink', 'Activities', 'Travel & Places', 'Objects', 'Symbols', 'Flags'];
+const list = ['frequentlyUsed', 'custom', 'people', 'nature', 'food', 'activity', 'travel', 'objects', 'symbols', 'flags'];
const tabs = [
{
tabLabel: '🕒',
diff --git a/app/containers/MessageBox/EmojiPicker/index.js b/app/containers/EmojiPicker/index.js
similarity index 50%
rename from app/containers/MessageBox/EmojiPicker/index.js
rename to app/containers/EmojiPicker/index.js
index fbddf4c78..c722da0ed 100644
--- a/app/containers/MessageBox/EmojiPicker/index.js
+++ b/app/containers/EmojiPicker/index.js
@@ -1,35 +1,32 @@
-import 'string.fromcodepoint';
-import React, { PureComponent } from 'react';
+import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { ScrollView, View } from 'react-native';
+import { ScrollView } from 'react-native';
import ScrollableTabView from 'react-native-scrollable-tab-view';
-import emojiDatasource from 'emoji-datasource/emoji.json';
import _ from 'lodash';
-import { groupBy, orderBy } from 'lodash/collection';
-import { mapValues } from 'lodash/object';
+import { emojify } from 'react-emojione';
import TabBar from './TabBar';
import EmojiCategory from './EmojiCategory';
import styles from './styles';
import categories from './categories';
-import scrollPersistTaps from '../../../utils/scrollPersistTaps';
-import database from '../../../lib/realm';
+import database from '../../lib/realm';
+import { emojisByCategory } from '../../emojis';
-const charFromUtf16 = utf16 => String.fromCodePoint(...utf16.split('-').map(u => `0x${ u }`));
-const charFromEmojiObj = obj => charFromUtf16(obj.unified);
+const scrollProps = {
+ keyboardShouldPersistTaps: 'always',
+ keyboardDismissMode: 'none'
+};
-const filteredEmojis = emojiDatasource.filter(e => parseFloat(e.added_in) < 10.0);
-const groupedAndSorted = groupBy(orderBy(filteredEmojis, 'sort_order'), 'category');
-const emojisByCategory = mapValues(groupedAndSorted, group => group.map(charFromEmojiObj));
-
-export default class extends PureComponent {
+export default class EmojiPicker extends Component {
static propTypes = {
- onEmojiSelected: PropTypes.func
+ onEmojiSelected: PropTypes.func,
+ tabEmojiStyle: PropTypes.object,
+ emojisPerRow: PropTypes.number,
+ width: PropTypes.number
};
constructor(props) {
super(props);
this.state = {
- categories: categories.list.slice(0, 1),
frequentlyUsed: [],
customEmojis: []
};
@@ -38,6 +35,10 @@ export default class extends PureComponent {
this.updateFrequentlyUsed = this.updateFrequentlyUsed.bind(this);
this.updateCustomEmojis = this.updateCustomEmojis.bind(this);
}
+ //
+ // shouldComponentUpdate(nextProps) {
+ // return false;
+ // }
componentWillMount() {
this.frequentlyUsed.addListener(this.updateFrequentlyUsed);
@@ -45,9 +46,12 @@ export default class extends PureComponent {
this.updateFrequentlyUsed();
this.updateCustomEmojis();
}
-
+ componentDidMount() {
+ requestAnimationFrame(() => this.setState({ show: true }));
+ }
componentWillUnmount() {
- clearTimeout(this._timeout);
+ this.frequentlyUsed.removeAllListeners();
+ this.customEmojis.removeAllListeners();
}
onEmojiSelected(emoji) {
@@ -58,10 +62,11 @@ export default class extends PureComponent {
});
this.props.onEmojiSelected(`:${ emoji.content }:`);
} else {
- const content = emoji.codePointAt(0).toString();
+ const content = emoji;
const count = this._getFrequentlyUsedCount(content);
this._addFrequentlyUsed({ content, count, isCustom: false });
- this.props.onEmojiSelected(emoji);
+ const shortname = `:${ emoji }:`;
+ this.props.onEmojiSelected(emojify(shortname, { output: 'unicode' }), shortname);
}
}
_addFrequentlyUsed = (emoji) => {
@@ -78,22 +83,17 @@ export default class extends PureComponent {
if (item.isCustom) {
return item;
}
- return String.fromCodePoint(item.content);
+ return emojify(`${ item.content }`, { output: 'unicode' });
});
this.setState({ frequentlyUsed });
}
updateCustomEmojis() {
- const customEmojis = _.map(this.customEmojis.slice(), item => ({ content: item.name, extension: item.extension, isCustom: true }));
+ const customEmojis = _.map(this.customEmojis.slice(), item =>
+ ({ content: item.name, extension: item.extension, isCustom: true }));
this.setState({ customEmojis });
}
- loadNextCategory() {
- if (this.state.categories.length < categories.list.length) {
- this.setState({ categories: categories.list.slice(0, this.state.categories.length + 1) });
- }
- }
-
renderCategory(category, i) {
let emojis = [];
if (i === 0) {
@@ -104,40 +104,40 @@ export default class extends PureComponent {
emojis = emojisByCategory[category];
}
return (
-
- this.onEmojiSelected(emoji)}
- finishedLoading={() => { this._timeout = setTimeout(this.loadNextCategory.bind(this), 100); }}
- />
-
+ this.onEmojiSelected(emoji)}
+ style={styles.categoryContainer}
+ size={this.props.emojisPerRow}
+ width={this.props.width}
+ />
);
}
render() {
- const scrollProps = {
- keyboardShouldPersistTaps: 'always'
- };
+ if (!this.state.show) {
+ return null;
+ }
return (
-
- }
- contentProps={scrollProps}
- >
- {
- _.map(categories.tabs, (tab, i) => (
-
- {this.renderCategory(tab.category, i)}
-
- ))
- }
-
-
+ //
+ }
+ contentProps={scrollProps}
+ // prerenderingSiblingsNumber={1}
+ >
+ {
+ categories.tabs.map((tab, i) => (
+
+ {this.renderCategory(tab.category, i)}
+
+ ))
+ }
+
+ //
);
}
}
diff --git a/app/containers/MessageBox/EmojiPicker/styles.js b/app/containers/EmojiPicker/styles.js
similarity index 70%
rename from app/containers/MessageBox/EmojiPicker/styles.js
rename to app/containers/EmojiPicker/styles.js
index a4ffdb598..038d11b20 100644
--- a/app/containers/MessageBox/EmojiPicker/styles.js
+++ b/app/containers/EmojiPicker/styles.js
@@ -1,7 +1,4 @@
-import { StyleSheet, Dimensions, Platform } from 'react-native';
-
-const { width } = Dimensions.get('window');
-const EMOJI_SIZE = width / (Platform.OS === 'ios' ? 8 : 9);
+import { StyleSheet } from 'react-native';
export default StyleSheet.create({
container: {
@@ -45,18 +42,16 @@ export default StyleSheet.create({
categoryInner: {
flexWrap: 'wrap',
flexDirection: 'row',
- alignItems: 'center'
+ alignItems: 'center',
+ justifyContent: 'flex-start',
+ flex: 1
},
categoryEmoji: {
- fontSize: EMOJI_SIZE - 14,
color: 'black',
- height: EMOJI_SIZE,
- width: EMOJI_SIZE,
+ backgroundColor: 'transparent',
textAlign: 'center'
},
customCategoryEmoji: {
- height: EMOJI_SIZE - 8,
- width: EMOJI_SIZE - 8,
margin: 4
}
});
diff --git a/app/containers/Header.js b/app/containers/Header.js
index b285d4121..7dfa46f1b 100644
--- a/app/containers/Header.js
+++ b/app/containers/Header.js
@@ -33,7 +33,7 @@ const styles = StyleSheet.create({
}
});
-export default class extends React.PureComponent {
+export default class Header extends React.PureComponent {
static propTypes = {
subview: PropTypes.object.isRequired
}
diff --git a/app/containers/MessageActions.js b/app/containers/MessageActions.js
index 12be855e8..e362c69d8 100644
--- a/app/containers/MessageActions.js
+++ b/app/containers/MessageActions.js
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Alert, Clipboard } from 'react-native';
+import { Alert, Clipboard, Vibration, Share } from 'react-native';
import { connect } from 'react-redux';
import ActionSheet from 'react-native-actionsheet';
import * as moment from 'moment';
@@ -13,7 +13,8 @@ import {
permalinkClear,
togglePinRequest,
setInput,
- actionsHide
+ actionsHide,
+ toggleReactionPicker
} from '../actions/messages';
import { showToast } from '../utils/info';
@@ -39,7 +40,8 @@ import { showToast } from '../utils/info';
permalinkRequest: message => dispatch(permalinkRequest(message)),
permalinkClear: () => dispatch(permalinkClear()),
togglePinRequest: message => dispatch(togglePinRequest(message)),
- setInput: message => dispatch(setInput(message))
+ setInput: message => dispatch(setInput(message)),
+ toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
})
)
export default class MessageActions extends React.Component {
@@ -58,6 +60,7 @@ export default class MessageActions extends React.Component {
togglePinRequest: PropTypes.func.isRequired,
setInput: PropTypes.func.isRequired,
permalink: PropTypes.string,
+ toggleReactionPicker: PropTypes.func.isRequired,
Message_AllowDeleting: PropTypes.bool,
Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number,
Message_AllowEditing: PropTypes.bool,
@@ -104,6 +107,9 @@ export default class MessageActions extends React.Component {
// Copy
this.options.push('Copy Message');
this.COPY_INDEX = this.options.length - 1;
+ // Share
+ this.options.push('Share Message');
+ this.SHARE_INDEX = this.options.length - 1;
// Quote
if (!this.isRoomReadOnly()) {
this.options.push('Quote');
@@ -119,6 +125,11 @@ export default class MessageActions extends React.Component {
this.options.push(actionMessage.pinned ? 'Unpin' : 'Pin');
this.PIN_INDEX = this.options.length - 1;
}
+ // Reaction
+ if (!this.isRoomReadOnly()) {
+ this.options.push('Add Reaction');
+ this.REACTION_INDEX = this.options.length - 1;
+ }
// Delete
if (this.allowDelete(nextProps)) {
this.options.push('Delete');
@@ -126,6 +137,7 @@ export default class MessageActions extends React.Component {
}
setTimeout(() => {
this.ActionSheet.show();
+ Vibration.vibrate(50);
});
} else if (this.props.permalink !== nextProps.permalink && nextProps.permalink) {
// copy permalink
@@ -251,6 +263,12 @@ export default class MessageActions extends React.Component {
showToast('Copied to clipboard!');
}
+ handleShare = async() => {
+ Share.share({
+ message: this.props.actionMessage.msg.content.replace(/<(?:.|\n)*?>/gm, '')
+ });
+ };
+
handleStar() {
this.props.toggleStarRequest(this.props.actionMessage);
}
@@ -274,6 +292,10 @@ export default class MessageActions extends React.Component {
this.props.permalinkRequest(this.props.actionMessage);
}
+ handleReaction() {
+ this.props.toggleReactionPicker(this.props.actionMessage);
+ }
+
handleActionPress = (actionIndex) => {
switch (actionIndex) {
case this.REPLY_INDEX:
@@ -288,6 +310,9 @@ export default class MessageActions extends React.Component {
case this.COPY_INDEX:
this.handleCopy();
break;
+ case this.SHARE_INDEX:
+ this.handleShare();
+ break;
case this.QUOTE_INDEX:
this.handleQuote();
break;
@@ -297,6 +322,9 @@ export default class MessageActions extends React.Component {
case this.PIN_INDEX:
this.handlePin();
break;
+ case this.REACTION_INDEX:
+ this.handleReaction();
+ break;
case this.DELETE_INDEX:
this.handleDelete();
break;
diff --git a/app/containers/MessageBox/AnimatedContainer.js b/app/containers/MessageBox/AnimatedContainer.js
deleted file mode 100644
index eb5cbbe47..000000000
--- a/app/containers/MessageBox/AnimatedContainer.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { Animated } from 'react-native';
-
-export default class MessageBox extends React.PureComponent {
- static propTypes = {
- subview: PropTypes.object.isRequired,
- visible: PropTypes.bool.isRequired,
- messageboxHeight: PropTypes.number.isRequired
- }
-
- constructor(props) {
- super(props);
- this.animatedBottom = new Animated.Value(0);
- }
-
- componentWillReceiveProps(nextProps) {
- if (this.props.visible === nextProps.visible) {
- return;
- }
- if (nextProps.visible) {
- return this.show();
- }
- this.hide();
- }
-
- show() {
- this.animatedBottom.setValue(0);
- Animated.timing(this.animatedBottom, {
- toValue: 1,
- duration: 300,
- useNativeDriver: true
- }).start();
- }
-
- hide() {
- Animated.timing(this.animatedBottom, {
- toValue: 0,
- duration: 300,
- useNativeDriver: true
- }).start();
- }
-
- render() {
- const bottom = this.animatedBottom.interpolate({
- inputRange: [0, 1],
- outputRange: [0, -this.props.messageboxHeight - 200]
- });
-
- return (
-
- {this.props.subview}
-
- );
- }
-}
diff --git a/app/containers/MessageBox/EmojiKeyboard.js b/app/containers/MessageBox/EmojiKeyboard.js
new file mode 100644
index 000000000..9a6cb8b91
--- /dev/null
+++ b/app/containers/MessageBox/EmojiKeyboard.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import { View } from 'react-native';
+import { KeyboardRegistry } from 'react-native-keyboard-input';
+import { Provider } from 'react-redux';
+import store from '../../lib/createStore';
+import EmojiPicker from '../EmojiPicker';
+import styles from './styles';
+
+export default class EmojiKeyboard extends React.PureComponent {
+ onEmojiSelected = (emoji) => {
+ KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
+ }
+ render() {
+ return (
+
+
+ this.onEmojiSelected(emoji)} />
+
+
+ );
+ }
+}
+KeyboardRegistry.registerKeyboard('EmojiKeyboard', () => EmojiKeyboard);
diff --git a/app/containers/MessageBox/EmojiPicker/EmojiCategory.js b/app/containers/MessageBox/EmojiPicker/EmojiCategory.js
deleted file mode 100644
index e4c648b1b..000000000
--- a/app/containers/MessageBox/EmojiPicker/EmojiCategory.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { Text, View, TouchableOpacity, StyleSheet } from 'react-native';
-import styles from './styles';
-import CustomEmoji from '../../CustomEmoji';
-
-export default class extends React.PureComponent {
- static propTypes = {
- emojis: PropTypes.any,
- finishedLoading: PropTypes.func,
- onEmojiSelected: PropTypes.func
- };
-
- componentDidMount() {
- this.props.finishedLoading();
- }
-
- renderEmoji = (emoji) => {
- if (emoji.isCustom) {
- const style = StyleSheet.flatten(styles.customCategoryEmoji);
- return ;
- }
- return (
-
- {emoji}
-
- );
- }
-
- render() {
- const { emojis } = this.props;
- return (
-
-
- {emojis.map(emoji =>
- (
- this.props.onEmojiSelected(emoji)}
- >
- {this.renderEmoji(emoji)}
-
- ))}
-
-
- );
- }
-}
diff --git a/app/containers/MessageBox/index.js b/app/containers/MessageBox/index.js
index af1a196d5..db4a0a061 100644
--- a/app/containers/MessageBox/index.js
+++ b/app/containers/MessageBox/index.js
@@ -1,21 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { View, TextInput, SafeAreaView, Platform, FlatList, Text, TouchableOpacity, Keyboard } from 'react-native';
+import { View, TextInput, SafeAreaView, FlatList, Text, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
import ImagePicker from 'react-native-image-picker';
import { connect } from 'react-redux';
-import { userTyping } from '../../actions/room';
+import { emojify } from 'react-emojione';
+import { KeyboardAccessoryView } from 'react-native-keyboard-input';
+import { userTyping, layoutAnimation } from '../../actions/room';
import RocketChat from '../../lib/rocketchat';
import { editRequest, editCancel, clearInput } from '../../actions/messages';
-import styles from './style';
+import styles from './styles';
import MyIcon from '../icons';
import database from '../../lib/realm';
import Avatar from '../Avatar';
-import AnimatedContainer from './AnimatedContainer';
-import EmojiPicker from './EmojiPicker';
-import scrollPersistTaps from '../../utils/scrollPersistTaps';
+import CustomEmoji from '../EmojiPicker/CustomEmoji';
+import { emojis } from '../../emojis';
+import './EmojiKeyboard';
const MENTIONS_TRACKING_TYPE_USERS = '@';
+const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
const onlyUnique = function onlyUnique(value, index, self) {
return self.indexOf(({ _id }) => value._id === _id) === index;
@@ -25,13 +28,13 @@ const onlyUnique = function onlyUnique(value, index, self) {
room: state.room,
message: state.messages.message,
editing: state.messages.editing,
- baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
- isKeyboardOpen: state.keyboard.isOpen
+ baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
}), dispatch => ({
editCancel: () => dispatch(editCancel()),
editRequest: message => dispatch(editRequest(message)),
typing: status => dispatch(userTyping(status)),
- clearInput: () => dispatch(clearInput())
+ clearInput: () => dispatch(clearInput()),
+ layoutAnimation: () => dispatch(layoutAnimation())
}))
export default class MessageBox extends React.PureComponent {
static propTypes = {
@@ -44,21 +47,23 @@ export default class MessageBox extends React.PureComponent {
editing: PropTypes.bool,
typing: PropTypes.func,
clearInput: PropTypes.func,
- isKeyboardOpen: PropTypes.bool
+ layoutAnimation: PropTypes.func
}
constructor(props) {
super(props);
this.state = {
- height: 20,
- messageboxHeight: 0,
text: '',
mentions: [],
showMentionsContainer: false,
- showEmojiContainer: false
+ showEmojiKeyboard: false,
+ trackingType: ''
};
this.users = [];
this.rooms = [];
+ this.emojis = [];
+ this.customEmojis = [];
+ this._onEmojiSelected = this._onEmojiSelected.bind(this);
}
componentWillReceiveProps(nextProps) {
if (this.props.message !== nextProps.message && nextProps.message.msg) {
@@ -66,22 +71,22 @@ export default class MessageBox extends React.PureComponent {
this.component.focus();
} else if (!nextProps.message) {
this.setState({ text: '' });
- } else if (this.props.isKeyboardOpen !== nextProps.isKeyboardOpen && nextProps.isKeyboardOpen) {
- this.closeEmoji();
}
}
- onChange() {
+ onChangeText(text) {
+ this.setState({ text });
+
requestAnimationFrame(() => {
const { start, end } = this.component._lastNativeSelection;
const cursor = Math.max(start, end);
- const text = this.component._lastNativeText;
+ const lastNativeText = this.component._lastNativeText;
- const regexp = /(#|@)([a-z._-]+)$/im;
+ const regexp = /(#|@|:)([a-z0-9._-]+)$/im;
- const result = text.substr(0, cursor).match(regexp);
+ const result = lastNativeText.substr(0, cursor).match(regexp);
if (!result) {
return this.stopTrackingMention();
@@ -92,9 +97,8 @@ export default class MessageBox extends React.PureComponent {
});
}
-
- onChangeText(text) {
- this.setState({ text });
+ onKeyboardResigned() {
+ this.closeEmoji();
}
get leftButtons() {
@@ -108,7 +112,7 @@ export default class MessageBox extends React.PureComponent {
onPress={() => this.editCancel()}
/>);
}
- return !this.state.showEmojiContainer ? ( this.openEmoji()}
accessibilityLabel='Open emoji selector'
@@ -147,10 +151,6 @@ export default class MessageBox extends React.PureComponent {
return icons;
}
- updateSize = (height) => {
- this.setState({ height: height + (Platform.OS === 'ios' ? 0 : 0) });
- }
-
addFile = () => {
const options = {
maxHeight: 1960,
@@ -184,11 +184,12 @@ export default class MessageBox extends React.PureComponent {
this.setState({ text: '' });
}
async openEmoji() {
- await this.setState({ showEmojiContainer: !this.state.showEmojiContainer });
- Keyboard.dismiss();
+ await this.setState({
+ showEmojiKeyboard: true
+ });
}
closeEmoji() {
- this.setState({ showEmojiContainer: false });
+ this.setState({ showEmojiKeyboard: false });
}
submit(message) {
this.setState({ text: '' });
@@ -212,11 +213,21 @@ export default class MessageBox extends React.PureComponent {
});
}
+ _getFixedMentions(keyword) {
+ if ('all'.indexOf(keyword) !== -1) {
+ this.users = [{ _id: -1, username: 'all', desc: 'all' }, ...this.users];
+ }
+ if ('here'.indexOf(keyword) !== -1) {
+ this.users = [{ _id: -2, username: 'here', desc: 'active users' }, ...this.users];
+ }
+ }
+
async _getUsers(keyword) {
this.users = database.objects('users');
if (keyword) {
this.users = this.users.filtered('username CONTAINS[c] $0', keyword);
}
+ this._getFixedMentions(keyword);
this.setState({ mentions: this.users.slice() });
const usernames = [];
@@ -244,8 +255,9 @@ export default class MessageBox extends React.PureComponent {
console.log('spotlight canceled');
} finally {
delete this.oldPromise;
- this.users = database.objects('users').filtered('username CONTAINS[c] $0', keyword);
- this.setState({ mentions: this.users.slice() });
+ this.users = database.objects('users').filtered('username CONTAINS[c] $0', keyword).slice();
+ this._getFixedMentions(keyword);
+ this.setState({ mentions: this.users });
}
}
@@ -289,25 +301,44 @@ export default class MessageBox extends React.PureComponent {
}
}
+ _getEmojis(keyword) {
+ if (keyword) {
+ this.customEmojis = database.objects('customEmojis').filtered('name CONTAINS[c] $0', keyword).slice(0, 4);
+ this.emojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, 4);
+ const mergedEmojis = [...this.customEmojis, ...this.emojis];
+ this.setState({ mentions: mergedEmojis });
+ }
+ }
+
stopTrackingMention() {
this.setState({
showMentionsContainer: false,
- mentions: []
+ mentions: [],
+ trackingType: ''
});
this.users = [];
this.rooms = [];
+ this.customEmojis = [];
+ this.emojis = [];
}
identifyMentionKeyword(keyword, type) {
- this.updateMentions(keyword, type);
+ if (!this.state.showMentionsContainer) {
+ this.props.layoutAnimation();
+ }
this.setState({
- showMentionsContainer: true
+ showMentionsContainer: true,
+ showEmojiKeyboard: false,
+ trackingType: type
});
+ this.updateMentions(keyword, type);
}
updateMentions = (keyword, type) => {
if (type === MENTIONS_TRACKING_TYPE_USERS) {
this._getUsers(keyword);
+ } else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) {
+ this._getEmojis(keyword);
} else {
this._getRooms(keyword);
}
@@ -320,17 +351,20 @@ export default class MessageBox extends React.PureComponent {
const cursor = Math.max(start, end);
- const regexp = /([a-z._-]+)$/im;
+ const regexp = /([a-z0-9._-]+)$/im;
const result = msg.substr(0, cursor).replace(regexp, '');
- const text = `${ result }${ item.username || item.name } ${ msg.slice(cursor) }`;
+ const mentionName = this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ?
+ `${ item.name || item }:` : (item.username || item.name);
+ const text = `${ result }${ mentionName } ${ msg.slice(cursor) }`;
this.component.setNativeProps({ text });
this.setState({ text });
this.component.focus();
requestAnimationFrame(() => this.stopTrackingMention());
}
- _onEmojiSelected(emoji) {
+ _onEmojiSelected(keyboardId, params) {
const { text } = this.state;
+ const { emoji } = params;
let newText = '';
// if messagebox has an active cursor
@@ -345,73 +379,116 @@ export default class MessageBox extends React.PureComponent {
this.component.setNativeProps({ text: newText });
this.setState({ text: newText });
}
- renderMentionItem = item => (
+ renderFixedMentionItem = item => (
this._onPressMention(item)}
>
-
- {item.username || item.name }
+ {item.username}
+ Notify {item.desc} in this room
)
- renderEmoji() {
- const emojiContainer = (
-
- this._onEmojiSelected(emoji)} />
-
- );
- const { showEmojiContainer, messageboxHeight } = this.state;
- return ;
- }
- renderMentions() {
- const usersList = (
- this.renderMentionItem(item)}
- keyExtractor={item => item._id}
- {...scrollPersistTaps}
- />
- );
- const { showMentionsContainer, messageboxHeight } = this.state;
- return ;
- }
- render() {
- const { height } = this.state;
+ renderMentionEmoji = (item) => {
+ if (item.name) {
+ return (
+
+ );
+ }
return (
-
+
+ {emojify(`:${ item }:`, { output: 'unicode' })}
+
+ );
+ }
+ renderMentionItem = (item) => {
+ if (item.username === 'all' || item.username === 'here') {
+ return this.renderFixedMentionItem(item);
+ }
+ return (
+ this._onPressMention(item)}
+ >
+ {this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ?
+ [
+ this.renderMentionEmoji(item),
+ :{ item.name || item }:
+ ]
+ : [
+ ,
+ { item.username || item.name }
+ ]
+ }
+
+ );
+ }
+ renderMentions = () => (
+ this.renderMentionItem(item)}
+ keyExtractor={item => item._id || item}
+ keyboardShouldPersistTaps='always'
+ />
+ );
+
+ renderContent() {
+ return (
+ [
+ this.renderMentions(),
this.setState({ messageboxHeight: event.nativeEvent.layout.height })}
>
{this.leftButtons}
this.component = component}
- style={[styles.textBoxInput, { height }]}
+ style={styles.textBoxInput}
returnKeyType='default'
blurOnSubmit={false}
placeholder='New Message'
onChangeText={text => this.onChangeText(text)}
- onChange={event => this.onChange(event)}
value={this.state.text}
underlineColorAndroid='transparent'
defaultValue=''
multiline
placeholderTextColor='#9EA2A8'
- onContentSizeChange={e => this.updateSize(e.nativeEvent.contentSize.height)}
/>
{this.rightButtons}
- {this.renderMentions()}
- {this.renderEmoji()}
-
+ ]
+ );
+ }
+
+ render() {
+ return (
+ this.renderContent()}
+ kbInputRef={this.component}
+ kbComponent={this.state.showEmojiKeyboard ? 'EmojiKeyboard' : null}
+ onKeyboardResigned={() => this.onKeyboardResigned()}
+ onItemSelected={this._onEmojiSelected}
+ trackInteractive
+ revealKeyboardInteractive
+ requiresSameParentToManageScrollView
+ />
);
}
}
diff --git a/app/containers/MessageBox/style.js b/app/containers/MessageBox/styles.js
similarity index 71%
rename from app/containers/MessageBox/style.js
rename to app/containers/MessageBox/styles.js
index bee61609e..fed138d6b 100644
--- a/app/containers/MessageBox/style.js
+++ b/app/containers/MessageBox/styles.js
@@ -1,4 +1,4 @@
-import { StyleSheet } from 'react-native';
+import { StyleSheet, Platform } from 'react-native';
const MENTION_HEIGHT = 50;
@@ -9,8 +9,6 @@ export default StyleSheet.create({
alignItems: 'center',
borderTopWidth: 1,
borderTopColor: '#D8D8D8',
- paddingHorizontal: 15,
- paddingVertical: 15,
zIndex: 2
},
safeAreaView: {
@@ -23,14 +21,14 @@ export default StyleSheet.create({
flexGrow: 0
},
textBoxInput: {
- paddingVertical: 0,
- paddingHorizontal: 10,
- textAlignVertical: 'top',
+ textAlignVertical: 'center',
maxHeight: 120,
flexGrow: 1,
width: 1,
- paddingTop: 0,
- paddingBottom: 0
+ paddingTop: 15,
+ paddingBottom: 15,
+ paddingLeft: 0,
+ paddingRight: 0
},
editing: {
backgroundColor: '#fff5df'
@@ -39,7 +37,8 @@ export default StyleSheet.create({
color: '#2F343D',
fontSize: 20,
textAlign: 'center',
- paddingHorizontal: 5,
+ padding: 15,
+ paddingHorizontal: 21,
flex: 0
},
actionRow: {
@@ -85,5 +84,26 @@ export default StyleSheet.create({
borderTopColor: '#ECECEC',
borderTopWidth: 1,
backgroundColor: '#fff'
+ },
+ mentionItemCustomEmoji: {
+ margin: 8,
+ width: 30,
+ height: 30
+ },
+ mentionItemEmoji: {
+ width: 46,
+ height: 36,
+ fontSize: Platform.OS === 'ios' ? 30 : 25,
+ textAlign: 'center'
+ },
+ fixedMentionAvatar: {
+ fontWeight: 'bold',
+ textAlign: 'center',
+ width: 46
+ },
+ emojiKeyboardContainer: {
+ flex: 1,
+ borderTopColor: '#ECECEC',
+ borderTopWidth: 1
}
});
diff --git a/app/containers/MessageErrorActions.js b/app/containers/MessageErrorActions.js
index b96ca79aa..4f3439fae 100644
--- a/app/containers/MessageErrorActions.js
+++ b/app/containers/MessageErrorActions.js
@@ -16,7 +16,7 @@ import database from '../lib/realm';
errorActionsHide: () => dispatch(errorActionsHide())
})
)
-export default class MessageActions extends React.Component {
+export default class MessageErrorActions extends React.Component {
static propTypes = {
errorActionsHide: PropTypes.func.isRequired,
showErrorActions: PropTypes.bool.isRequired,
diff --git a/app/containers/Sidebar.js b/app/containers/Sidebar.js
index c93f5857b..ae76794f8 100644
--- a/app/containers/Sidebar.js
+++ b/app/containers/Sidebar.js
@@ -1,7 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { ScrollView, Text, View, StyleSheet, FlatList, TouchableHighlight } from 'react-native';
-import { DrawerItems } from 'react-navigation';
import { connect } from 'react-redux';
import database from '../lib/realm';
@@ -101,10 +100,6 @@ export default class Sidebar extends Component {
return (
-
{
+ Keyboard.dismiss();
+ }
get usersTyping() {
const users = this.props.usersTyping.filter(_username => this.props.username !== _username);
return users.length ? `${ users.join(' ,') } ${ users.length > 1 ? 'are' : 'is' } typing` : '';
}
render() {
- return ({this.usersTyping});
+ return ( this.onPress()}>{this.usersTyping});
}
}
diff --git a/app/containers/message/Emoji.js b/app/containers/message/Emoji.js
new file mode 100644
index 000000000..edbcd470c
--- /dev/null
+++ b/app/containers/message/Emoji.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import { Text, ViewPropTypes } from 'react-native';
+import PropTypes from 'prop-types';
+import { emojify } from 'react-emojione';
+import CustomEmoji from '../EmojiPicker/CustomEmoji';
+
+export default class Emoji extends React.PureComponent {
+ static propTypes = {
+ content: PropTypes.string,
+ standardEmojiStyle: Text.propTypes.style,
+ customEmojiStyle: ViewPropTypes.style,
+ customEmojis: PropTypes.object.isRequired
+ };
+ render() {
+ const {
+ content, standardEmojiStyle, customEmojiStyle, customEmojis
+ } = this.props;
+ const parsedContent = content.replace(/^:|:$/g, '');
+ const emojiExtension = customEmojis[parsedContent];
+ if (emojiExtension) {
+ const emoji = { extension: emojiExtension, content: parsedContent };
+ return ;
+ }
+ return { emojify(`${ content }`, { output: 'unicode' }) };
+ }
+}
diff --git a/app/containers/message/Image.js b/app/containers/message/Image.js
index c7b3bdbc6..4a88d1f82 100644
--- a/app/containers/message/Image.js
+++ b/app/containers/message/Image.js
@@ -39,7 +39,7 @@ const styles = StyleSheet.create({
}
});
-export default class extends React.PureComponent {
+export default class Image extends React.PureComponent {
static propTypes = {
file: PropTypes.object.isRequired,
baseUrl: PropTypes.string.isRequired,
diff --git a/app/containers/message/Markdown.js b/app/containers/message/Markdown.js
index 3fab10a45..805c915f8 100644
--- a/app/containers/message/Markdown.js
+++ b/app/containers/message/Markdown.js
@@ -5,7 +5,7 @@ import EasyMarkdown from 'react-native-easy-markdown'; // eslint-disable-line
import SimpleMarkdown from 'simple-markdown';
import { emojify } from 'react-emojione';
import styles from './styles';
-import CustomEmoji from '../CustomEmoji';
+import CustomEmoji from '../EmojiPicker/CustomEmoji';
const BlockCode = ({ node, state }) => (
{
const emojiExtension = customEmojis[content];
if (emojiExtension) {
const emoji = { extension: emojiExtension, content };
- const style = StyleSheet.flatten(styles.customEmoji);
element.props.children = (
-
+
);
}
return element;
diff --git a/app/containers/message/PhotoModal.js b/app/containers/message/PhotoModal.js
index 694015b01..7d378794e 100644
--- a/app/containers/message/PhotoModal.js
+++ b/app/containers/message/PhotoModal.js
@@ -25,7 +25,7 @@ const styles = {
}
};
-export default class extends React.PureComponent {
+export default class PhotoModal extends React.PureComponent {
static propTypes = {
title: PropTypes.string.isRequired,
image: PropTypes.string.isRequired,
diff --git a/app/containers/message/ReactionsModal.js b/app/containers/message/ReactionsModal.js
new file mode 100644
index 000000000..08491518f
--- /dev/null
+++ b/app/containers/message/ReactionsModal.js
@@ -0,0 +1,124 @@
+import React from 'react';
+import { View, Text, TouchableWithoutFeedback, FlatList, StyleSheet } from 'react-native';
+import PropTypes from 'prop-types';
+import Modal from 'react-native-modal';
+import Icon from 'react-native-vector-icons/MaterialIcons';
+import Emoji from './Emoji';
+
+const styles = StyleSheet.create({
+ titleContainer: {
+ width: '100%',
+ alignItems: 'center',
+ paddingVertical: 10
+ },
+ title: {
+ color: '#ffffff',
+ textAlign: 'center',
+ fontSize: 16,
+ fontWeight: '600'
+ },
+ reactCount: {
+ color: '#dddddd',
+ fontSize: 10
+ },
+ peopleReacted: {
+ color: '#ffffff',
+ fontWeight: '500'
+ },
+ peopleItemContainer: {
+ flex: 1,
+ flexDirection: 'column',
+ justifyContent: 'center'
+ },
+ emojiContainer: {
+ width: 50,
+ height: 50,
+ alignItems: 'center',
+ justifyContent: 'center'
+ },
+ itemContainer: {
+ height: 50,
+ flexDirection: 'row'
+ },
+ listContainer: {
+ flex: 1
+ },
+ closeButton: {
+ position: 'absolute',
+ left: 0,
+ top: 10,
+ color: '#ffffff'
+ }
+});
+const standardEmojiStyle = { fontSize: 20 };
+const customEmojiStyle = { width: 20, height: 20 };
+export default class ReactionsModal extends React.PureComponent {
+ static propTypes = {
+ isVisible: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+ reactions: PropTypes.object.isRequired,
+ user: PropTypes.object.isRequired,
+ customEmojis: PropTypes.object.isRequired
+ }
+ renderItem = (item) => {
+ const count = item.usernames.length;
+ let usernames = item.usernames.slice(0, 3)
+ .map(username => (username.value === this.props.user.username ? 'you' : username.value)).join(', ');
+ if (count > 3) {
+ usernames = `${ usernames } and more ${ count - 3 }`;
+ } else {
+ usernames = usernames.replace(/,(?=[^,]*$)/, ' and');
+ }
+ return (
+
+
+
+
+
+
+ {count === 1 ? '1 person' : `${ count } people`} reacted
+
+ { usernames }
+
+
+ );
+ }
+
+ render() {
+ const {
+ isVisible, onClose, reactions
+ } = this.props;
+ return (
+
+
+
+
+ Reactions
+
+
+
+ this.renderItem(item)}
+ keyExtractor={item => item.emoji}
+ />
+
+
+ );
+ }
+}
diff --git a/app/containers/message/Url.js b/app/containers/message/Url.js
index 6fe6cdddb..5ec48a999 100644
--- a/app/containers/message/Url.js
+++ b/app/containers/message/Url.js
@@ -52,10 +52,13 @@ const Url = ({ url }) => {
return (
onPress(url.url)} style={styles.button}>
-
+ {url.image ?
+
+ : null
+ }
{url.title}
{url.description}
diff --git a/app/containers/message/index.js b/app/containers/message/index.js
index 7ab119870..a361ef57c 100644
--- a/app/containers/message/index.js
+++ b/app/containers/message/index.js
@@ -1,11 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { View, TouchableHighlight, Text, TouchableOpacity, Animated } from 'react-native';
+import { View, TouchableHighlight, Text, TouchableOpacity, Vibration } from 'react-native';
import { connect } from 'react-redux';
import Icon from 'react-native-vector-icons/MaterialIcons';
import moment from 'moment';
+import equal from 'deep-equal';
+import { KeyboardUtils } from 'react-native-keyboard-input';
-import { actionsShow, errorActionsShow } from '../../actions/messages';
+import { actionsShow, errorActionsShow, toggleReactionPicker } from '../../actions/messages';
import Image from './Image';
import User from './User';
import Avatar from '../Avatar';
@@ -14,23 +16,24 @@ import Video from './Video';
import Markdown from './Markdown';
import Url from './Url';
import Reply from './Reply';
+import ReactionsModal from './ReactionsModal';
+import Emoji from './Emoji';
import messageStatus from '../../constants/messagesStatus';
import styles from './styles';
-const avatar = { marginRight: 10 };
-const flex = { flexDirection: 'row', flex: 1 };
-
@connect(state => ({
message: state.messages.message,
editing: state.messages.editing,
customEmojis: state.customEmojis
}), dispatch => ({
actionsShow: actionMessage => dispatch(actionsShow(actionMessage)),
- errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage))
+ errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage)),
+ toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
}))
export default class Message extends React.Component {
static propTypes = {
item: PropTypes.object.isRequired,
+ reactions: PropTypes.object.isRequired,
baseUrl: PropTypes.string.isRequired,
Message_TimeFormat: PropTypes.string.isRequired,
message: PropTypes.object.isRequired,
@@ -38,20 +41,15 @@ export default class Message extends React.Component {
editing: PropTypes.bool,
actionsShow: PropTypes.func,
errorActionsShow: PropTypes.func,
- animate: PropTypes.bool,
- customEmojis: PropTypes.object
+ customEmojis: PropTypes.object,
+ toggleReactionPicker: PropTypes.func,
+ onReactionPress: PropTypes.func
}
- componentWillMount() {
- this._visibility = new Animated.Value(this.props.animate ? 0 : 1);
- }
- componentDidMount() {
- if (this.props.animate) {
- Animated.timing(this._visibility, {
- toValue: 1,
- duration: 300
- }).start();
- }
+ constructor(props) {
+ super(props);
+ this.state = { reactionsModal: false };
+ this.onClose = this.onClose.bind(this);
}
componentWillReceiveProps() {
this.extraStyle = this.extraStyle || {};
@@ -60,18 +58,37 @@ export default class Message extends React.Component {
}
}
- shouldComponentUpdate(nextProps) {
+ shouldComponentUpdate(nextProps, nextState) {
+ if (!equal(this.props.reactions, nextProps.reactions)) {
+ return true;
+ }
+ if (this.state.reactionsModal !== nextState.reactionsModal) {
+ return true;
+ }
return this.props.item._updatedAt.toGMTString() !== nextProps.item._updatedAt.toGMTString() || this.props.item.status !== nextProps.item.status;
}
+ onPress = () => {
+ KeyboardUtils.dismiss();
+ }
+
onLongPress() {
- const { item } = this.props;
- this.props.actionsShow(JSON.parse(JSON.stringify(item)));
+ this.props.actionsShow(this.parseMessage());
}
onErrorPress() {
- const { item } = this.props;
- this.props.errorActionsShow(JSON.parse(JSON.stringify(item)));
+ this.props.errorActionsShow(this.parseMessage());
+ }
+
+ onReactionPress(emoji) {
+ this.props.onReactionPress(emoji, this.props.item._id);
+ }
+ onClose() {
+ this.setState({ reactionsModal: false });
+ }
+ onReactionLongPress() {
+ this.setState({ reactionsModal: true });
+ Vibration.vibrate(50);
}
getInfoMessage() {
@@ -101,11 +118,12 @@ export default class Message extends React.Component {
return message;
}
+ parseMessage = () => JSON.parse(JSON.stringify(this.props.item));
+
isInfoMessage() {
return ['r', 'au', 'ru', 'ul', 'uj', 'rm', 'user-muted', 'user-unmuted', 'message_pinned'].includes(this.props.item.t);
}
-
isDeleted() {
return this.props.item.t === 'rm';
}
@@ -161,26 +179,58 @@ export default class Message extends React.Component {
);
}
+ renderReaction(reaction) {
+ const reacted = reaction.usernames.findIndex(item => item.value === this.props.user.username) !== -1;
+ const reactedContainerStyle = reacted ? { borderColor: '#bde1fe', backgroundColor: '#f3f9ff' } : {};
+ const reactedCount = reacted ? { color: '#4fb0fc' } : {};
+ return (
+ this.onReactionPress(reaction.emoji)}
+ onLongPress={() => this.onReactionLongPress()}
+ key={reaction.emoji}
+ >
+
+
+ { reaction.usernames.length }
+
+
+ );
+ }
+
+ renderReactions() {
+ if (this.props.item.reactions.length === 0) {
+ return null;
+ }
+ return (
+
+ {this.props.item.reactions.map(reaction => this.renderReaction(reaction))}
+ this.props.toggleReactionPicker(this.parseMessage())}
+ key='add-reaction'
+ style={styles.reactionContainer}
+ >
+
+
+
+ );
+ }
+
render() {
const {
- item, message, editing, baseUrl
+ item, message, editing, baseUrl, customEmojis
} = this.props;
-
- const marginLeft = this._visibility.interpolate({
- inputRange: [0, 1],
- outputRange: [-30, 0]
- });
- const opacity = this._visibility.interpolate({
- inputRange: [0, 1],
- outputRange: [0, 1]
- });
const username = item.alias || item.u.username;
const isEditing = message._id === item._id && editing;
-
- const accessibilityLabel = `Message from ${ item.alias || item.u.username } at ${ moment(item.ts).format(this.props.Message_TimeFormat) }, ${ this.props.item.msg }`;
+ const accessibilityLabel = `Message from ${ username } at ${ moment(item.ts).format(this.props.Message_TimeFormat) }, ${ this.props.item.msg }`;
return (
this.onPress()}
onLongPress={() => this.onLongPress()}
disabled={this.isDeleted() || this.hasError()}
underlayColor='#FFFFFF'
@@ -188,11 +238,11 @@ export default class Message extends React.Component {
style={[styles.message, isEditing ? styles.editing : null]}
accessibilityLabel={accessibilityLabel}
>
-
+
{this.renderError()}
-
+
-
+ {this.state.reactionsModal ?
+
+ : null
+ }
+
);
}
diff --git a/app/containers/message/styles.js b/app/containers/message/styles.js
index 6236adf06..23911cc48 100644
--- a/app/containers/message/styles.js
+++ b/app/containers/message/styles.js
@@ -5,6 +5,10 @@ export default StyleSheet.create({
flexGrow: 1,
flexShrink: 1
},
+ flex: {
+ flexDirection: 'row',
+ flex: 1
+ },
message: {
padding: 12,
paddingTop: 6,
@@ -33,5 +37,38 @@ export default StyleSheet.create({
borderWidth: 1,
borderRadius: 5,
padding: 5
+ },
+ reactionsContainer: {
+ flexDirection: 'row',
+ flexWrap: 'wrap'
+ },
+ reactionContainer: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: 3,
+ borderWidth: 1,
+ borderColor: '#cccccc',
+ borderRadius: 4,
+ marginRight: 5,
+ marginBottom: 5,
+ height: 23,
+ width: 35
+ },
+ reactionCount: {
+ fontSize: 12,
+ marginLeft: 2,
+ fontWeight: '600',
+ color: '#aaaaaa'
+ },
+ reactionEmoji: {
+ fontSize: 12
+ },
+ reactionCustomEmoji: {
+ width: 15,
+ height: 15
+ },
+ avatar: {
+ marginRight: 10
}
});
diff --git a/app/emojis.js b/app/emojis.js
new file mode 100644
index 000000000..b7bff1957
--- /dev/null
+++ b/app/emojis.js
@@ -0,0 +1,2833 @@
+export const emojisByCategory = {
+ people: [
+ 'grinning',
+ 'grimacing',
+ 'grin',
+ 'joy',
+ 'smiley',
+ 'smile',
+ 'sweat_smile',
+ 'laughing',
+ 'innocent',
+ 'wink',
+ 'blush',
+ 'slight_smile',
+ 'upside_down',
+ 'relaxed',
+ 'yum',
+ 'relieved',
+ 'heart_eyes',
+ 'kissing_heart',
+ 'kissing',
+ 'kissing_smiling_eyes',
+ 'kissing_closed_eyes',
+ 'stuck_out_tongue_winking_eye',
+ 'stuck_out_tongue_closed_eyes',
+ 'stuck_out_tongue',
+ 'money_mouth',
+ 'nerd',
+ 'sunglasses',
+ 'hugging',
+ 'smirk',
+ 'no_mouth',
+ 'neutral_face',
+ 'expressionless',
+ 'unamused',
+ 'rolling_eyes',
+ 'thinking',
+ 'flushed',
+ 'disappointed',
+ 'worried',
+ 'angry',
+ 'rage',
+ 'pensive',
+ 'confused',
+ 'slight_frown',
+ 'frowning2',
+ 'persevere',
+ 'confounded',
+ 'tired_face',
+ 'weary',
+ 'triumph',
+ 'open_mouth',
+ 'scream',
+ 'fearful',
+ 'cold_sweat',
+ 'hushed',
+ 'frowning',
+ 'anguished',
+ 'cry',
+ 'disappointed_relieved',
+ 'sleepy',
+ 'sweat',
+ 'sob',
+ 'dizzy_face',
+ 'astonished',
+ 'zipper_mouth',
+ 'mask',
+ 'thermometer_face',
+ 'head_bandage',
+ 'sleeping',
+ 'zzz',
+ 'poop',
+ 'smiling_imp',
+ 'imp',
+ 'japanese_ogre',
+ 'japanese_goblin',
+ 'skull',
+ 'ghost',
+ 'alien',
+ 'robot',
+ 'smiley_cat',
+ 'smile_cat',
+ 'joy_cat',
+ 'heart_eyes_cat',
+ 'smirk_cat',
+ 'kissing_cat',
+ 'scream_cat',
+ 'crying_cat_face',
+ 'pouting_cat',
+ 'raised_hands',
+ 'clap',
+ 'wave',
+ 'thumbsup',
+ 'thumbsdown',
+ 'punch',
+ 'fist',
+ 'v',
+ 'ok_hand',
+ 'raised_hand',
+ 'open_hands',
+ 'muscle',
+ 'pray',
+ 'point_up',
+ 'point_up_2',
+ 'point_down',
+ 'point_left',
+ 'point_right',
+ 'middle_finger',
+ 'hand_splayed',
+ 'metal',
+ 'vulcan',
+ 'writing_hand',
+ 'nail_care',
+ 'lips',
+ 'tongue',
+ 'ear',
+ 'nose',
+ 'eye',
+ 'eyes',
+ 'bust_in_silhouette',
+ 'busts_in_silhouette',
+ 'speaking_head',
+ 'baby',
+ 'boy',
+ 'girl',
+ 'man',
+ 'woman',
+ 'person_with_blond_hair',
+ 'older_man',
+ 'older_woman',
+ 'man_with_gua_pi_mao',
+ 'man_with_turban',
+ 'cop',
+ 'construction_worker',
+ 'guardsman',
+ 'spy',
+ 'santa',
+ 'angel',
+ 'princess',
+ 'bride_with_veil',
+ 'walking',
+ 'runner',
+ 'dancer',
+ 'dancers',
+ 'couple',
+ 'two_men_holding_hands',
+ 'two_women_holding_hands',
+ 'bow',
+ 'information_desk_person',
+ 'no_good',
+ 'ok_woman',
+ 'raising_hand',
+ 'person_with_pouting_face',
+ 'person_frowning',
+ 'haircut',
+ 'massage',
+ 'couple_with_heart',
+ 'couple_ww',
+ 'couple_mm',
+ 'couplekiss',
+ 'kiss_ww',
+ 'kiss_mm',
+ 'family',
+ 'family_mwg',
+ 'family_mwgb',
+ 'family_mwbb',
+ 'family_mwgg',
+ 'family_wwb',
+ 'family_wwg',
+ 'family_wwgb',
+ 'family_wwbb',
+ 'family_wwgg',
+ 'family_mmb',
+ 'family_mmg',
+ 'family_mmgb',
+ 'family_mmbb',
+ 'family_mmgg',
+ 'womans_clothes',
+ 'shirt',
+ 'jeans',
+ 'necktie',
+ 'dress',
+ 'bikini',
+ 'kimono',
+ 'lipstick',
+ 'kiss',
+ 'footprints',
+ 'high_heel',
+ 'sandal',
+ 'boot',
+ 'mans_shoe',
+ 'athletic_shoe',
+ 'womans_hat',
+ 'tophat',
+ 'helmet_with_cross',
+ 'mortar_board',
+ 'crown',
+ 'school_satchel',
+ 'pouch',
+ 'purse',
+ 'handbag',
+ 'briefcase',
+ 'eyeglasses',
+ 'dark_sunglasses',
+ 'ring',
+ 'closed_umbrella',
+ 'cowboy',
+ 'clown',
+ 'nauseated_face',
+ 'rofl',
+ 'drooling_face',
+ 'lying_face',
+ 'sneezing_face',
+ 'prince',
+ 'man_in_tuxedo',
+ 'mrs_claus',
+ 'face_palm',
+ 'shrug',
+ 'selfie',
+ 'man_dancing',
+ 'call_me',
+ 'raised_back_of_hand',
+ 'left_facing_fist',
+ 'right_facing_fist',
+ 'handshake',
+ 'fingers_crossed',
+ 'pregnant_woman'
+ ],
+ nature: [
+ 'dog',
+ 'cat',
+ 'mouse',
+ 'hamster',
+ 'rabbit',
+ 'bear',
+ 'panda_face',
+ 'koala',
+ 'tiger',
+ 'lion_face',
+ 'cow',
+ 'pig',
+ 'pig_nose',
+ 'frog',
+ 'octopus',
+ 'monkey_face',
+ 'see_no_evil',
+ 'hear_no_evil',
+ 'speak_no_evil',
+ 'monkey',
+ 'chicken',
+ 'penguin',
+ 'bird',
+ 'baby_chick',
+ 'hatching_chick',
+ 'hatched_chick',
+ 'wolf',
+ 'boar',
+ 'horse',
+ 'unicorn',
+ 'bee',
+ 'bug',
+ 'snail',
+ 'beetle',
+ 'ant',
+ 'spider',
+ 'scorpion',
+ 'crab',
+ 'snake',
+ 'turtle',
+ 'tropical_fish',
+ 'fish',
+ 'blowfish',
+ 'dolphin',
+ 'whale',
+ 'whale2',
+ 'crocodile',
+ 'leopard',
+ 'tiger2',
+ 'water_buffalo',
+ 'ox',
+ 'cow2',
+ 'dromedary_camel',
+ 'camel',
+ 'elephant',
+ 'goat',
+ 'ram',
+ 'sheep',
+ 'racehorse',
+ 'pig2',
+ 'rat',
+ 'mouse2',
+ 'rooster',
+ 'turkey',
+ 'dove',
+ 'dog2',
+ 'poodle',
+ 'cat2',
+ 'rabbit2',
+ 'chipmunk',
+ 'feet',
+ 'dragon',
+ 'dragon_face',
+ 'cactus',
+ 'christmas_tree',
+ 'evergreen_tree',
+ 'deciduous_tree',
+ 'palm_tree',
+ 'seedling',
+ 'herb',
+ 'shamrock',
+ 'four_leaf_clover',
+ 'bamboo',
+ 'tanabata_tree',
+ 'leaves',
+ 'fallen_leaf',
+ 'maple_leaf',
+ 'ear_of_rice',
+ 'hibiscus',
+ 'sunflower',
+ 'rose',
+ 'tulip',
+ 'blossom',
+ 'cherry_blossom',
+ 'bouquet',
+ 'mushroom',
+ 'chestnut',
+ 'jack_o_lantern',
+ 'shell',
+ 'spider_web',
+ 'earth_americas',
+ 'earth_africa',
+ 'earth_asia',
+ 'full_moon',
+ 'waning_gibbous_moon',
+ 'last_quarter_moon',
+ 'waning_crescent_moon',
+ 'new_moon',
+ 'waxing_crescent_moon',
+ 'first_quarter_moon',
+ 'waxing_gibbous_moon',
+ 'new_moon_with_face',
+ 'full_moon_with_face',
+ 'first_quarter_moon_with_face',
+ 'last_quarter_moon_with_face',
+ 'sun_with_face',
+ 'crescent_moon',
+ 'star',
+ 'star2',
+ 'dizzy',
+ 'sparkles',
+ 'comet',
+ 'sunny',
+ 'white_sun_small_cloud',
+ 'partly_sunny',
+ 'white_sun_cloud',
+ 'white_sun_rain_cloud',
+ 'cloud',
+ 'cloud_rain',
+ 'thunder_cloud_rain',
+ 'cloud_lightning',
+ 'zap',
+ 'fire',
+ 'boom',
+ 'snowflake',
+ 'cloud_snow',
+ 'snowman2',
+ 'snowman',
+ 'wind_blowing_face',
+ 'dash',
+ 'cloud_tornado',
+ 'fog',
+ 'umbrella2',
+ 'umbrella',
+ 'droplet',
+ 'sweat_drops',
+ 'ocean',
+ 'eagle',
+ 'duck',
+ 'bat',
+ 'shark',
+ 'owl',
+ 'fox',
+ 'butterfly',
+ 'deer',
+ 'gorilla',
+ 'lizard',
+ 'rhino',
+ 'wilted_rose',
+ 'shrimp',
+ 'squid'
+ ],
+ food: [
+ 'green_apple',
+ 'apple',
+ 'pear',
+ 'tangerine',
+ 'lemon',
+ 'banana',
+ 'watermelon',
+ 'grapes',
+ 'strawberry',
+ 'melon',
+ 'cherries',
+ 'peach',
+ 'pineapple',
+ 'tomato',
+ 'eggplant',
+ 'hot_pepper',
+ 'corn',
+ 'sweet_potato',
+ 'honey_pot',
+ 'bread',
+ 'cheese',
+ 'poultry_leg',
+ 'meat_on_bone',
+ 'fried_shrimp',
+ 'cooking',
+ 'hamburger',
+ 'fries',
+ 'hotdog',
+ 'pizza',
+ 'spaghetti',
+ 'taco',
+ 'burrito',
+ 'ramen',
+ 'stew',
+ 'fish_cake',
+ 'sushi',
+ 'bento',
+ 'curry',
+ 'rice_ball',
+ 'rice',
+ 'rice_cracker',
+ 'oden',
+ 'dango',
+ 'shaved_ice',
+ 'ice_cream',
+ 'icecream',
+ 'cake',
+ 'birthday',
+ 'custard',
+ 'candy',
+ 'lollipop',
+ 'chocolate_bar',
+ 'popcorn',
+ 'doughnut',
+ 'cookie',
+ 'beer',
+ 'beers',
+ 'wine_glass',
+ 'cocktail',
+ 'tropical_drink',
+ 'champagne',
+ 'sake',
+ 'tea',
+ 'coffee',
+ 'baby_bottle',
+ 'fork_and_knife',
+ 'fork_knife_plate',
+ 'croissant',
+ 'avocado',
+ 'cucumber',
+ 'bacon',
+ 'potato',
+ 'carrot',
+ 'french_bread',
+ 'salad',
+ 'shallow_pan_of_food',
+ 'stuffed_flatbread',
+ 'champagne_glass',
+ 'tumbler_glass',
+ 'spoon',
+ 'egg',
+ 'milk',
+ 'peanuts',
+ 'kiwi',
+ 'pancakes'
+ ],
+ activity: [
+ 'soccer',
+ 'basketball',
+ 'football',
+ 'baseball',
+ 'tennis',
+ 'volleyball',
+ 'rugby_football',
+ '8ball',
+ 'golf',
+ 'golfer',
+ 'ping_pong',
+ 'badminton',
+ 'hockey',
+ 'field_hockey',
+ 'cricket',
+ 'ski',
+ 'skier',
+ 'snowboarder',
+ 'ice_skate',
+ 'bow_and_arrow',
+ 'fishing_pole_and_fish',
+ 'rowboat',
+ 'swimmer',
+ 'surfer',
+ 'bath',
+ 'basketball_player',
+ 'lifter',
+ 'bicyclist',
+ 'mountain_bicyclist',
+ 'horse_racing',
+ 'levitate',
+ 'trophy',
+ 'running_shirt_with_sash',
+ 'medal',
+ 'military_medal',
+ 'reminder_ribbon',
+ 'rosette',
+ 'ticket',
+ 'tickets',
+ 'performing_arts',
+ 'art',
+ 'circus_tent',
+ 'microphone',
+ 'headphones',
+ 'musical_score',
+ 'musical_keyboard',
+ 'saxophone',
+ 'trumpet',
+ 'guitar',
+ 'violin',
+ 'clapper',
+ 'video_game',
+ 'space_invader',
+ 'dart',
+ 'game_die',
+ 'slot_machine',
+ 'bowling',
+ 'cartwheel',
+ 'juggling',
+ 'wrestlers',
+ 'boxing_glove',
+ 'martial_arts_uniform',
+ 'water_polo',
+ 'handball',
+ 'goal',
+ 'fencer',
+ 'first_place',
+ 'second_place',
+ 'third_place',
+ 'drum'
+ ],
+ travel: [
+ 'red_car',
+ 'taxi',
+ 'blue_car',
+ 'bus',
+ 'trolleybus',
+ 'race_car',
+ 'police_car',
+ 'ambulance',
+ 'fire_engine',
+ 'minibus',
+ 'truck',
+ 'articulated_lorry',
+ 'tractor',
+ 'motorcycle',
+ 'bike',
+ 'rotating_light',
+ 'oncoming_police_car',
+ 'oncoming_bus',
+ 'oncoming_automobile',
+ 'oncoming_taxi',
+ 'aerial_tramway',
+ 'mountain_cableway',
+ 'suspension_railway',
+ 'railway_car',
+ 'train',
+ 'monorail',
+ 'bullettrain_side',
+ 'bullettrain_front',
+ 'light_rail',
+ 'mountain_railway',
+ 'steam_locomotive',
+ 'train2',
+ 'metro',
+ 'tram',
+ 'station',
+ 'helicopter',
+ 'airplane_small',
+ 'airplane',
+ 'airplane_departure',
+ 'airplane_arriving',
+ 'sailboat',
+ 'motorboat',
+ 'speedboat',
+ 'ferry',
+ 'cruise_ship',
+ 'rocket',
+ 'satellite_orbital',
+ 'seat',
+ 'anchor',
+ 'construction',
+ 'fuelpump',
+ 'busstop',
+ 'vertical_traffic_light',
+ 'traffic_light',
+ 'checkered_flag',
+ 'ship',
+ 'ferris_wheel',
+ 'roller_coaster',
+ 'carousel_horse',
+ 'construction_site',
+ 'foggy',
+ 'tokyo_tower',
+ 'factory',
+ 'fountain',
+ 'rice_scene',
+ 'mountain',
+ 'mountain_snow',
+ 'mount_fuji',
+ 'volcano',
+ 'japan',
+ 'camping',
+ 'tent',
+ 'park',
+ 'motorway',
+ 'railway_track',
+ 'sunrise',
+ 'sunrise_over_mountains',
+ 'desert',
+ 'beach',
+ 'island',
+ 'city_sunset',
+ 'city_dusk',
+ 'cityscape',
+ 'night_with_stars',
+ 'bridge_at_night',
+ 'milky_way',
+ 'stars',
+ 'sparkler',
+ 'fireworks',
+ 'rainbow',
+ 'homes',
+ 'european_castle',
+ 'japanese_castle',
+ 'stadium',
+ 'statue_of_liberty',
+ 'house',
+ 'house_with_garden',
+ 'house_abandoned',
+ 'office',
+ 'department_store',
+ 'post_office',
+ 'european_post_office',
+ 'hospital',
+ 'bank',
+ 'hotel',
+ 'convenience_store',
+ 'school',
+ 'love_hotel',
+ 'wedding',
+ 'classical_building',
+ 'church',
+ 'mosque',
+ 'synagogue',
+ 'kaaba',
+ 'shinto_shrine',
+ 'shopping_cart',
+ 'scooter',
+ 'motor_scooter',
+ 'canoe'
+ ],
+ objects: [
+ 'watch',
+ 'iphone',
+ 'calling',
+ 'computer',
+ 'keyboard',
+ 'desktop',
+ 'printer',
+ 'mouse_three_button',
+ 'trackball',
+ 'joystick',
+ 'compression',
+ 'minidisc',
+ 'floppy_disk',
+ 'cd',
+ 'dvd',
+ 'vhs',
+ 'camera',
+ 'camera_with_flash',
+ 'video_camera',
+ 'movie_camera',
+ 'projector',
+ 'film_frames',
+ 'telephone_receiver',
+ 'telephone',
+ 'pager',
+ 'fax',
+ 'tv',
+ 'radio',
+ 'microphone2',
+ 'level_slider',
+ 'control_knobs',
+ 'stopwatch',
+ 'timer',
+ 'alarm_clock',
+ 'clock',
+ 'hourglass_flowing_sand',
+ 'hourglass',
+ 'satellite',
+ 'battery',
+ 'electric_plug',
+ 'bulb',
+ 'flashlight',
+ 'candle',
+ 'wastebasket',
+ 'oil',
+ 'money_with_wings',
+ 'dollar',
+ 'yen',
+ 'euro',
+ 'pound',
+ 'moneybag',
+ 'credit_card',
+ 'gem',
+ 'scales',
+ 'wrench',
+ 'hammer',
+ 'hammer_pick',
+ 'tools',
+ 'pick',
+ 'nut_and_bolt',
+ 'gear',
+ 'chains',
+ 'gun',
+ 'bomb',
+ 'knife',
+ 'dagger',
+ 'crossed_swords',
+ 'shield',
+ 'smoking',
+ 'skull_crossbones',
+ 'coffin',
+ 'urn',
+ 'amphora',
+ 'crystal_ball',
+ 'prayer_beads',
+ 'barber',
+ 'alembic',
+ 'telescope',
+ 'microscope',
+ 'hole',
+ 'pill',
+ 'syringe',
+ 'thermometer',
+ 'label',
+ 'bookmark',
+ 'toilet',
+ 'shower',
+ 'bathtub',
+ 'key',
+ 'key2',
+ 'couch',
+ 'sleeping_accommodation',
+ 'bed',
+ 'door',
+ 'bellhop',
+ 'frame_photo',
+ 'map',
+ 'beach_umbrella',
+ 'moyai',
+ 'shopping_bags',
+ 'balloon',
+ 'flags',
+ 'ribbon',
+ 'gift',
+ 'confetti_ball',
+ 'tada',
+ 'dolls',
+ 'wind_chime',
+ 'crossed_flags',
+ 'izakaya_lantern',
+ 'envelope',
+ 'envelope_with_arrow',
+ 'incoming_envelope',
+ 'e-mail',
+ 'love_letter',
+ 'postbox',
+ 'mailbox_closed',
+ 'mailbox',
+ 'mailbox_with_mail',
+ 'mailbox_with_no_mail',
+ 'package',
+ 'postal_horn',
+ 'inbox_tray',
+ 'outbox_tray',
+ 'scroll',
+ 'page_with_curl',
+ 'bookmark_tabs',
+ 'bar_chart',
+ 'chart_with_upwards_trend',
+ 'chart_with_downwards_trend',
+ 'page_facing_up',
+ 'date',
+ 'calendar',
+ 'calendar_spiral',
+ 'card_index',
+ 'card_box',
+ 'ballot_box',
+ 'file_cabinet',
+ 'clipboard',
+ 'notepad_spiral',
+ 'file_folder',
+ 'open_file_folder',
+ 'dividers',
+ 'newspaper2',
+ 'newspaper',
+ 'notebook',
+ 'closed_book',
+ 'green_book',
+ 'blue_book',
+ 'orange_book',
+ 'notebook_with_decorative_cover',
+ 'ledger',
+ 'books',
+ 'book',
+ 'link',
+ 'paperclip',
+ 'paperclips',
+ 'scissors',
+ 'triangular_ruler',
+ 'straight_ruler',
+ 'pushpin',
+ 'round_pushpin',
+ 'triangular_flag_on_post',
+ 'flag_white',
+ 'flag_black',
+ 'closed_lock_with_key',
+ 'lock',
+ 'unlock',
+ 'lock_with_ink_pen',
+ 'pen_ballpoint',
+ 'pen_fountain',
+ 'black_nib',
+ 'pencil',
+ 'pencil2',
+ 'crayon',
+ 'paintbrush',
+ 'mag',
+ 'mag_right'
+ ],
+ symbols: [
+ '100',
+ '1234',
+ 'heart',
+ 'yellow_heart',
+ 'green_heart',
+ 'blue_heart',
+ 'purple_heart',
+ 'broken_heart',
+ 'heart_exclamation',
+ 'two_hearts',
+ 'revolving_hearts',
+ 'heartbeat',
+ 'heartpulse',
+ 'sparkling_heart',
+ 'cupid',
+ 'gift_heart',
+ 'heart_decoration',
+ 'peace',
+ 'cross',
+ 'star_and_crescent',
+ 'om_symbol',
+ 'wheel_of_dharma',
+ 'star_of_david',
+ 'six_pointed_star',
+ 'menorah',
+ 'yin_yang',
+ 'orthodox_cross',
+ 'place_of_worship',
+ 'ophiuchus',
+ 'aries',
+ 'taurus',
+ 'gemini',
+ 'cancer',
+ 'leo',
+ 'virgo',
+ 'libra',
+ 'scorpius',
+ 'sagittarius',
+ 'capricorn',
+ 'aquarius',
+ 'pisces',
+ 'id',
+ 'atom',
+ 'u7a7a',
+ 'u5272',
+ 'radioactive',
+ 'biohazard',
+ 'mobile_phone_off',
+ 'vibration_mode',
+ 'u6709',
+ 'u7121',
+ 'u7533',
+ 'u55b6',
+ 'u6708',
+ 'eight_pointed_black_star',
+ 'vs',
+ 'accept',
+ 'white_flower',
+ 'ideograph_advantage',
+ 'secret',
+ 'congratulations',
+ 'u5408',
+ 'u6e80',
+ 'u7981',
+ 'a',
+ 'b',
+ 'ab',
+ 'cl',
+ 'o2',
+ 'sos',
+ 'no_entry',
+ 'name_badge',
+ 'no_entry_sign',
+ 'x',
+ 'o',
+ 'anger',
+ 'hotsprings',
+ 'no_pedestrians',
+ 'do_not_litter',
+ 'no_bicycles',
+ 'non-potable_water',
+ 'underage',
+ 'no_mobile_phones',
+ 'exclamation',
+ 'grey_exclamation',
+ 'question',
+ 'grey_question',
+ 'bangbang',
+ 'interrobang',
+ 'low_brightness',
+ 'high_brightness',
+ 'trident',
+ 'fleur-de-lis',
+ 'part_alternation_mark',
+ 'warning',
+ 'children_crossing',
+ 'beginner',
+ 'recycle',
+ 'u6307',
+ 'chart',
+ 'sparkle',
+ 'eight_spoked_asterisk',
+ 'negative_squared_cross_mark',
+ 'white_check_mark',
+ 'diamond_shape_with_a_dot_inside',
+ 'cyclone',
+ 'loop',
+ 'globe_with_meridians',
+ 'm',
+ 'atm',
+ 'sa',
+ 'passport_control',
+ 'customs',
+ 'baggage_claim',
+ 'left_luggage',
+ 'wheelchair',
+ 'no_smoking',
+ 'wc',
+ 'parking',
+ 'potable_water',
+ 'mens',
+ 'womens',
+ 'baby_symbol',
+ 'restroom',
+ 'put_litter_in_its_place',
+ 'cinema',
+ 'signal_strength',
+ 'koko',
+ 'ng',
+ 'ok',
+ 'up',
+ 'cool',
+ 'new',
+ 'free',
+ 'zero',
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five',
+ 'six',
+ 'seven',
+ 'eight',
+ 'nine',
+ 'keycap_ten',
+ 'arrow_forward',
+ 'pause_button',
+ 'play_pause',
+ 'stop_button',
+ 'record_button',
+ 'track_next',
+ 'track_previous',
+ 'fast_forward',
+ 'rewind',
+ 'twisted_rightwards_arrows',
+ 'repeat',
+ 'repeat_one',
+ 'arrow_backward',
+ 'arrow_up_small',
+ 'arrow_down_small',
+ 'arrow_double_up',
+ 'arrow_double_down',
+ 'arrow_right',
+ 'arrow_left',
+ 'arrow_up',
+ 'arrow_down',
+ 'arrow_upper_right',
+ 'arrow_lower_right',
+ 'arrow_lower_left',
+ 'arrow_upper_left',
+ 'arrow_up_down',
+ 'left_right_arrow',
+ 'arrows_counterclockwise',
+ 'arrow_right_hook',
+ 'leftwards_arrow_with_hook',
+ 'arrow_heading_up',
+ 'arrow_heading_down',
+ 'hash',
+ 'asterisk',
+ 'information_source',
+ 'abc',
+ 'abcd',
+ 'capital_abcd',
+ 'symbols',
+ 'musical_note',
+ 'notes',
+ 'wavy_dash',
+ 'curly_loop',
+ 'heavy_check_mark',
+ 'arrows_clockwise',
+ 'heavy_plus_sign',
+ 'heavy_minus_sign',
+ 'heavy_division_sign',
+ 'heavy_multiplication_x',
+ 'heavy_dollar_sign',
+ 'currency_exchange',
+ 'copyright',
+ 'registered',
+ 'tm',
+ 'end',
+ 'back',
+ 'on',
+ 'top',
+ 'soon',
+ 'ballot_box_with_check',
+ 'radio_button',
+ 'white_circle',
+ 'black_circle',
+ 'red_circle',
+ 'large_blue_circle',
+ 'small_orange_diamond',
+ 'small_blue_diamond',
+ 'large_orange_diamond',
+ 'large_blue_diamond',
+ 'small_red_triangle',
+ 'black_small_square',
+ 'white_small_square',
+ 'black_large_square',
+ 'white_large_square',
+ 'small_red_triangle_down',
+ 'black_medium_square',
+ 'white_medium_square',
+ 'black_medium_small_square',
+ 'white_medium_small_square',
+ 'black_square_button',
+ 'white_square_button',
+ 'speaker',
+ 'sound',
+ 'loud_sound',
+ 'mute',
+ 'mega',
+ 'loudspeaker',
+ 'bell',
+ 'no_bell',
+ 'black_joker',
+ 'mahjong',
+ 'spades',
+ 'clubs',
+ 'hearts',
+ 'diamonds',
+ 'flower_playing_cards',
+ 'thought_balloon',
+ 'anger_right',
+ 'speech_balloon',
+ 'clock1',
+ 'clock2',
+ 'clock3',
+ 'clock4',
+ 'clock5',
+ 'clock6',
+ 'clock7',
+ 'clock8',
+ 'clock9',
+ 'clock10',
+ 'clock11',
+ 'clock12',
+ 'clock130',
+ 'clock230',
+ 'clock330',
+ 'clock430',
+ 'clock530',
+ 'clock630',
+ 'clock730',
+ 'clock830',
+ 'clock930',
+ 'clock1030',
+ 'clock1130',
+ 'clock1230',
+ 'eye_in_speech_bubble',
+ 'speech_left',
+ 'eject',
+ 'black_heart',
+ 'octagonal_sign',
+ 'asterisk_symbol',
+ 'pound_symbol',
+ 'digit_nine',
+ 'digit_eight',
+ 'digit_seven',
+ 'digit_six',
+ 'digit_five',
+ 'digit_four',
+ 'digit_three',
+ 'digit_two',
+ 'digit_one',
+ 'digit_zero',
+ 'regional_indicator_z',
+ 'regional_indicator_y',
+ 'regional_indicator_x',
+ 'regional_indicator_w',
+ 'regional_indicator_v',
+ 'regional_indicator_u',
+ 'regional_indicator_t',
+ 'regional_indicator_s',
+ 'regional_indicator_r',
+ 'regional_indicator_q',
+ 'regional_indicator_p',
+ 'regional_indicator_o',
+ 'regional_indicator_n',
+ 'regional_indicator_m',
+ 'regional_indicator_l',
+ 'regional_indicator_k',
+ 'regional_indicator_j',
+ 'regional_indicator_i',
+ 'regional_indicator_h',
+ 'regional_indicator_g',
+ 'regional_indicator_f',
+ 'regional_indicator_e',
+ 'regional_indicator_d',
+ 'regional_indicator_c',
+ 'regional_indicator_b',
+ 'regional_indicator_a'
+ ],
+ flags: [
+ 'flag_ac',
+ 'flag_af',
+ 'flag_al',
+ 'flag_dz',
+ 'flag_ad',
+ 'flag_ao',
+ 'flag_ai',
+ 'flag_ag',
+ 'flag_ar',
+ 'flag_am',
+ 'flag_aw',
+ 'flag_au',
+ 'flag_at',
+ 'flag_az',
+ 'flag_bs',
+ 'flag_bh',
+ 'flag_bd',
+ 'flag_bb',
+ 'flag_by',
+ 'flag_be',
+ 'flag_bz',
+ 'flag_bj',
+ 'flag_bm',
+ 'flag_bt',
+ 'flag_bo',
+ 'flag_ba',
+ 'flag_bw',
+ 'flag_br',
+ 'flag_bn',
+ 'flag_bg',
+ 'flag_bf',
+ 'flag_bi',
+ 'flag_cv',
+ 'flag_kh',
+ 'flag_cm',
+ 'flag_ca',
+ 'flag_ky',
+ 'flag_cf',
+ 'flag_td',
+ 'flag_cl',
+ 'flag_cn',
+ 'flag_co',
+ 'flag_km',
+ 'flag_cg',
+ 'flag_cd',
+ 'flag_cr',
+ 'flag_hr',
+ 'flag_cu',
+ 'flag_cy',
+ 'flag_cz',
+ 'flag_dk',
+ 'flag_dj',
+ 'flag_dm',
+ 'flag_do',
+ 'flag_ec',
+ 'flag_eg',
+ 'flag_sv',
+ 'flag_gq',
+ 'flag_er',
+ 'flag_ee',
+ 'flag_et',
+ 'flag_fk',
+ 'flag_fo',
+ 'flag_fj',
+ 'flag_fi',
+ 'flag_fr',
+ 'flag_pf',
+ 'flag_ga',
+ 'flag_gm',
+ 'flag_ge',
+ 'flag_de',
+ 'flag_gh',
+ 'flag_gi',
+ 'flag_gr',
+ 'flag_gl',
+ 'flag_gd',
+ 'flag_gu',
+ 'flag_gt',
+ 'flag_gn',
+ 'flag_gw',
+ 'flag_gy',
+ 'flag_ht',
+ 'flag_hn',
+ 'flag_hk',
+ 'flag_hu',
+ 'flag_is',
+ 'flag_in',
+ 'flag_id',
+ 'flag_ir',
+ 'flag_iq',
+ 'flag_ie',
+ 'flag_il',
+ 'flag_it',
+ 'flag_ci',
+ 'flag_jm',
+ 'flag_jp',
+ 'flag_je',
+ 'flag_jo',
+ 'flag_kz',
+ 'flag_ke',
+ 'flag_ki',
+ 'flag_xk',
+ 'flag_kw',
+ 'flag_kg',
+ 'flag_la',
+ 'flag_lv',
+ 'flag_lb',
+ 'flag_ls',
+ 'flag_lr',
+ 'flag_ly',
+ 'flag_li',
+ 'flag_lt',
+ 'flag_lu',
+ 'flag_mo',
+ 'flag_mk',
+ 'flag_mg',
+ 'flag_mw',
+ 'flag_my',
+ 'flag_mv',
+ 'flag_ml',
+ 'flag_mt',
+ 'flag_mh',
+ 'flag_mr',
+ 'flag_mu',
+ 'flag_mx',
+ 'flag_fm',
+ 'flag_md',
+ 'flag_mc',
+ 'flag_mn',
+ 'flag_me',
+ 'flag_ms',
+ 'flag_ma',
+ 'flag_mz',
+ 'flag_mm',
+ 'flag_na',
+ 'flag_nr',
+ 'flag_np',
+ 'flag_nl',
+ 'flag_nc',
+ 'flag_nz',
+ 'flag_ni',
+ 'flag_ne',
+ 'flag_ng',
+ 'flag_nu',
+ 'flag_kp',
+ 'flag_no',
+ 'flag_om',
+ 'flag_pk',
+ 'flag_pw',
+ 'flag_ps',
+ 'flag_pa',
+ 'flag_pg',
+ 'flag_py',
+ 'flag_pe',
+ 'flag_ph',
+ 'flag_pl',
+ 'flag_pt',
+ 'flag_pr',
+ 'flag_qa',
+ 'flag_ro',
+ 'flag_ru',
+ 'flag_rw',
+ 'flag_sh',
+ 'flag_kn',
+ 'flag_lc',
+ 'flag_vc',
+ 'flag_ws',
+ 'flag_sm',
+ 'flag_st',
+ 'flag_sa',
+ 'flag_sn',
+ 'flag_rs',
+ 'flag_sc',
+ 'flag_sl',
+ 'flag_sg',
+ 'flag_sk',
+ 'flag_si',
+ 'flag_sb',
+ 'flag_so',
+ 'flag_za',
+ 'flag_kr',
+ 'flag_es',
+ 'flag_lk',
+ 'flag_sd',
+ 'flag_sr',
+ 'flag_sz',
+ 'flag_se',
+ 'flag_ch',
+ 'flag_sy',
+ 'flag_tw',
+ 'flag_tj',
+ 'flag_tz',
+ 'flag_th',
+ 'flag_tl',
+ 'flag_tg',
+ 'flag_to',
+ 'flag_tt',
+ 'flag_tn',
+ 'flag_tr',
+ 'flag_tm',
+ 'flag_tv',
+ 'flag_ug',
+ 'flag_ua',
+ 'flag_ae',
+ 'flag_gb',
+ 'flag_us',
+ 'flag_vi',
+ 'flag_uy',
+ 'flag_uz',
+ 'flag_vu',
+ 'flag_va',
+ 'flag_ve',
+ 'flag_vn',
+ 'flag_wf',
+ 'flag_eh',
+ 'flag_ye',
+ 'flag_zm',
+ 'flag_zw',
+ 'flag_re',
+ 'flag_ax',
+ 'flag_ta',
+ 'flag_io',
+ 'flag_bq',
+ 'flag_cx',
+ 'flag_cc',
+ 'flag_gg',
+ 'flag_im',
+ 'flag_yt',
+ 'flag_nf',
+ 'flag_pn',
+ 'flag_bl',
+ 'flag_pm',
+ 'flag_gs',
+ 'flag_tk',
+ 'flag_bv',
+ 'flag_hm',
+ 'flag_sj',
+ 'flag_um',
+ 'flag_ic',
+ 'flag_ea',
+ 'flag_cp',
+ 'flag_dg',
+ 'flag_as',
+ 'flag_aq',
+ 'flag_vg',
+ 'flag_ck',
+ 'flag_cw',
+ 'flag_eu',
+ 'flag_gf',
+ 'flag_tf',
+ 'flag_gp',
+ 'flag_mq',
+ 'flag_mp',
+ 'flag_sx',
+ 'flag_ss',
+ 'flag_tc',
+ 'flag_mf'
+ ]
+};
+
+export const emojis = [
+ 'grinning',
+ 'grimacing',
+ 'grin',
+ 'joy',
+ 'smiley',
+ 'smile',
+ 'sweat_smile',
+ 'laughing',
+ 'innocent',
+ 'wink',
+ 'blush',
+ 'slight_smile',
+ 'upside_down',
+ 'relaxed',
+ 'yum',
+ 'relieved',
+ 'heart_eyes',
+ 'kissing_heart',
+ 'kissing',
+ 'kissing_smiling_eyes',
+ 'kissing_closed_eyes',
+ 'stuck_out_tongue_winking_eye',
+ 'stuck_out_tongue_closed_eyes',
+ 'stuck_out_tongue',
+ 'money_mouth',
+ 'nerd',
+ 'sunglasses',
+ 'hugging',
+ 'smirk',
+ 'no_mouth',
+ 'neutral_face',
+ 'expressionless',
+ 'unamused',
+ 'rolling_eyes',
+ 'thinking',
+ 'flushed',
+ 'disappointed',
+ 'worried',
+ 'angry',
+ 'rage',
+ 'pensive',
+ 'confused',
+ 'slight_frown',
+ 'frowning2',
+ 'persevere',
+ 'confounded',
+ 'tired_face',
+ 'weary',
+ 'triumph',
+ 'open_mouth',
+ 'scream',
+ 'fearful',
+ 'cold_sweat',
+ 'hushed',
+ 'frowning',
+ 'anguished',
+ 'cry',
+ 'disappointed_relieved',
+ 'sleepy',
+ 'sweat',
+ 'sob',
+ 'dizzy_face',
+ 'astonished',
+ 'zipper_mouth',
+ 'mask',
+ 'thermometer_face',
+ 'head_bandage',
+ 'sleeping',
+ 'zzz',
+ 'poop',
+ 'smiling_imp',
+ 'imp',
+ 'japanese_ogre',
+ 'japanese_goblin',
+ 'skull',
+ 'ghost',
+ 'alien',
+ 'robot',
+ 'smiley_cat',
+ 'smile_cat',
+ 'joy_cat',
+ 'heart_eyes_cat',
+ 'smirk_cat',
+ 'kissing_cat',
+ 'scream_cat',
+ 'crying_cat_face',
+ 'pouting_cat',
+ 'raised_hands',
+ 'clap',
+ 'wave',
+ 'thumbsup',
+ 'thumbsdown',
+ 'punch',
+ 'fist',
+ 'v',
+ 'ok_hand',
+ 'raised_hand',
+ 'open_hands',
+ 'muscle',
+ 'pray',
+ 'point_up',
+ 'point_up_2',
+ 'point_down',
+ 'point_left',
+ 'point_right',
+ 'middle_finger',
+ 'hand_splayed',
+ 'metal',
+ 'vulcan',
+ 'writing_hand',
+ 'nail_care',
+ 'lips',
+ 'tongue',
+ 'ear',
+ 'nose',
+ 'eye',
+ 'eyes',
+ 'bust_in_silhouette',
+ 'busts_in_silhouette',
+ 'speaking_head',
+ 'baby',
+ 'boy',
+ 'girl',
+ 'man',
+ 'woman',
+ 'person_with_blond_hair',
+ 'older_man',
+ 'older_woman',
+ 'man_with_gua_pi_mao',
+ 'man_with_turban',
+ 'cop',
+ 'construction_worker',
+ 'guardsman',
+ 'spy',
+ 'santa',
+ 'angel',
+ 'princess',
+ 'bride_with_veil',
+ 'walking',
+ 'runner',
+ 'dancer',
+ 'dancers',
+ 'couple',
+ 'two_men_holding_hands',
+ 'two_women_holding_hands',
+ 'bow',
+ 'information_desk_person',
+ 'no_good',
+ 'ok_woman',
+ 'raising_hand',
+ 'person_with_pouting_face',
+ 'person_frowning',
+ 'haircut',
+ 'massage',
+ 'couple_with_heart',
+ 'couple_ww',
+ 'couple_mm',
+ 'couplekiss',
+ 'kiss_ww',
+ 'kiss_mm',
+ 'family',
+ 'family_mwg',
+ 'family_mwgb',
+ 'family_mwbb',
+ 'family_mwgg',
+ 'family_wwb',
+ 'family_wwg',
+ 'family_wwgb',
+ 'family_wwbb',
+ 'family_wwgg',
+ 'family_mmb',
+ 'family_mmg',
+ 'family_mmgb',
+ 'family_mmbb',
+ 'family_mmgg',
+ 'womans_clothes',
+ 'shirt',
+ 'jeans',
+ 'necktie',
+ 'dress',
+ 'bikini',
+ 'kimono',
+ 'lipstick',
+ 'kiss',
+ 'footprints',
+ 'high_heel',
+ 'sandal',
+ 'boot',
+ 'mans_shoe',
+ 'athletic_shoe',
+ 'womans_hat',
+ 'tophat',
+ 'helmet_with_cross',
+ 'mortar_board',
+ 'crown',
+ 'school_satchel',
+ 'pouch',
+ 'purse',
+ 'handbag',
+ 'briefcase',
+ 'eyeglasses',
+ 'dark_sunglasses',
+ 'ring',
+ 'closed_umbrella',
+ 'cowboy',
+ 'clown',
+ 'nauseated_face',
+ 'rofl',
+ 'drooling_face',
+ 'lying_face',
+ 'sneezing_face',
+ 'prince',
+ 'man_in_tuxedo',
+ 'mrs_claus',
+ 'face_palm',
+ 'shrug',
+ 'selfie',
+ 'man_dancing',
+ 'call_me',
+ 'raised_back_of_hand',
+ 'left_facing_fist',
+ 'right_facing_fist',
+ 'handshake',
+ 'fingers_crossed',
+ 'pregnant_woman',
+ 'dog',
+ 'cat',
+ 'mouse',
+ 'hamster',
+ 'rabbit',
+ 'bear',
+ 'panda_face',
+ 'koala',
+ 'tiger',
+ 'lion_face',
+ 'cow',
+ 'pig',
+ 'pig_nose',
+ 'frog',
+ 'octopus',
+ 'monkey_face',
+ 'see_no_evil',
+ 'hear_no_evil',
+ 'speak_no_evil',
+ 'monkey',
+ 'chicken',
+ 'penguin',
+ 'bird',
+ 'baby_chick',
+ 'hatching_chick',
+ 'hatched_chick',
+ 'wolf',
+ 'boar',
+ 'horse',
+ 'unicorn',
+ 'bee',
+ 'bug',
+ 'snail',
+ 'beetle',
+ 'ant',
+ 'spider',
+ 'scorpion',
+ 'crab',
+ 'snake',
+ 'turtle',
+ 'tropical_fish',
+ 'fish',
+ 'blowfish',
+ 'dolphin',
+ 'whale',
+ 'whale2',
+ 'crocodile',
+ 'leopard',
+ 'tiger2',
+ 'water_buffalo',
+ 'ox',
+ 'cow2',
+ 'dromedary_camel',
+ 'camel',
+ 'elephant',
+ 'goat',
+ 'ram',
+ 'sheep',
+ 'racehorse',
+ 'pig2',
+ 'rat',
+ 'mouse2',
+ 'rooster',
+ 'turkey',
+ 'dove',
+ 'dog2',
+ 'poodle',
+ 'cat2',
+ 'rabbit2',
+ 'chipmunk',
+ 'feet',
+ 'dragon',
+ 'dragon_face',
+ 'cactus',
+ 'christmas_tree',
+ 'evergreen_tree',
+ 'deciduous_tree',
+ 'palm_tree',
+ 'seedling',
+ 'herb',
+ 'shamrock',
+ 'four_leaf_clover',
+ 'bamboo',
+ 'tanabata_tree',
+ 'leaves',
+ 'fallen_leaf',
+ 'maple_leaf',
+ 'ear_of_rice',
+ 'hibiscus',
+ 'sunflower',
+ 'rose',
+ 'tulip',
+ 'blossom',
+ 'cherry_blossom',
+ 'bouquet',
+ 'mushroom',
+ 'chestnut',
+ 'jack_o_lantern',
+ 'shell',
+ 'spider_web',
+ 'earth_americas',
+ 'earth_africa',
+ 'earth_asia',
+ 'full_moon',
+ 'waning_gibbous_moon',
+ 'last_quarter_moon',
+ 'waning_crescent_moon',
+ 'new_moon',
+ 'waxing_crescent_moon',
+ 'first_quarter_moon',
+ 'waxing_gibbous_moon',
+ 'new_moon_with_face',
+ 'full_moon_with_face',
+ 'first_quarter_moon_with_face',
+ 'last_quarter_moon_with_face',
+ 'sun_with_face',
+ 'crescent_moon',
+ 'star',
+ 'star2',
+ 'dizzy',
+ 'sparkles',
+ 'comet',
+ 'sunny',
+ 'white_sun_small_cloud',
+ 'partly_sunny',
+ 'white_sun_cloud',
+ 'white_sun_rain_cloud',
+ 'cloud',
+ 'cloud_rain',
+ 'thunder_cloud_rain',
+ 'cloud_lightning',
+ 'zap',
+ 'fire',
+ 'boom',
+ 'snowflake',
+ 'cloud_snow',
+ 'snowman2',
+ 'snowman',
+ 'wind_blowing_face',
+ 'dash',
+ 'cloud_tornado',
+ 'fog',
+ 'umbrella2',
+ 'umbrella',
+ 'droplet',
+ 'sweat_drops',
+ 'ocean',
+ 'eagle',
+ 'duck',
+ 'bat',
+ 'shark',
+ 'owl',
+ 'fox',
+ 'butterfly',
+ 'deer',
+ 'gorilla',
+ 'lizard',
+ 'rhino',
+ 'wilted_rose',
+ 'shrimp',
+ 'squid',
+ 'green_apple',
+ 'apple',
+ 'pear',
+ 'tangerine',
+ 'lemon',
+ 'banana',
+ 'watermelon',
+ 'grapes',
+ 'strawberry',
+ 'melon',
+ 'cherries',
+ 'peach',
+ 'pineapple',
+ 'tomato',
+ 'eggplant',
+ 'hot_pepper',
+ 'corn',
+ 'sweet_potato',
+ 'honey_pot',
+ 'bread',
+ 'cheese',
+ 'poultry_leg',
+ 'meat_on_bone',
+ 'fried_shrimp',
+ 'cooking',
+ 'hamburger',
+ 'fries',
+ 'hotdog',
+ 'pizza',
+ 'spaghetti',
+ 'taco',
+ 'burrito',
+ 'ramen',
+ 'stew',
+ 'fish_cake',
+ 'sushi',
+ 'bento',
+ 'curry',
+ 'rice_ball',
+ 'rice',
+ 'rice_cracker',
+ 'oden',
+ 'dango',
+ 'shaved_ice',
+ 'ice_cream',
+ 'icecream',
+ 'cake',
+ 'birthday',
+ 'custard',
+ 'candy',
+ 'lollipop',
+ 'chocolate_bar',
+ 'popcorn',
+ 'doughnut',
+ 'cookie',
+ 'beer',
+ 'beers',
+ 'wine_glass',
+ 'cocktail',
+ 'tropical_drink',
+ 'champagne',
+ 'sake',
+ 'tea',
+ 'coffee',
+ 'baby_bottle',
+ 'fork_and_knife',
+ 'fork_knife_plate',
+ 'croissant',
+ 'avocado',
+ 'cucumber',
+ 'bacon',
+ 'potato',
+ 'carrot',
+ 'french_bread',
+ 'salad',
+ 'shallow_pan_of_food',
+ 'stuffed_flatbread',
+ 'champagne_glass',
+ 'tumbler_glass',
+ 'spoon',
+ 'egg',
+ 'milk',
+ 'peanuts',
+ 'kiwi',
+ 'pancakes',
+ 'soccer',
+ 'basketball',
+ 'football',
+ 'baseball',
+ 'tennis',
+ 'volleyball',
+ 'rugby_football',
+ '8ball',
+ 'golf',
+ 'golfer',
+ 'ping_pong',
+ 'badminton',
+ 'hockey',
+ 'field_hockey',
+ 'cricket',
+ 'ski',
+ 'skier',
+ 'snowboarder',
+ 'ice_skate',
+ 'bow_and_arrow',
+ 'fishing_pole_and_fish',
+ 'rowboat',
+ 'swimmer',
+ 'surfer',
+ 'bath',
+ 'basketball_player',
+ 'lifter',
+ 'bicyclist',
+ 'mountain_bicyclist',
+ 'horse_racing',
+ 'levitate',
+ 'trophy',
+ 'running_shirt_with_sash',
+ 'medal',
+ 'military_medal',
+ 'reminder_ribbon',
+ 'rosette',
+ 'ticket',
+ 'tickets',
+ 'performing_arts',
+ 'art',
+ 'circus_tent',
+ 'microphone',
+ 'headphones',
+ 'musical_score',
+ 'musical_keyboard',
+ 'saxophone',
+ 'trumpet',
+ 'guitar',
+ 'violin',
+ 'clapper',
+ 'video_game',
+ 'space_invader',
+ 'dart',
+ 'game_die',
+ 'slot_machine',
+ 'bowling',
+ 'cartwheel',
+ 'juggling',
+ 'wrestlers',
+ 'boxing_glove',
+ 'martial_arts_uniform',
+ 'water_polo',
+ 'handball',
+ 'goal',
+ 'fencer',
+ 'first_place',
+ 'second_place',
+ 'third_place',
+ 'drum',
+ 'red_car',
+ 'taxi',
+ 'blue_car',
+ 'bus',
+ 'trolleybus',
+ 'race_car',
+ 'police_car',
+ 'ambulance',
+ 'fire_engine',
+ 'minibus',
+ 'truck',
+ 'articulated_lorry',
+ 'tractor',
+ 'motorcycle',
+ 'bike',
+ 'rotating_light',
+ 'oncoming_police_car',
+ 'oncoming_bus',
+ 'oncoming_automobile',
+ 'oncoming_taxi',
+ 'aerial_tramway',
+ 'mountain_cableway',
+ 'suspension_railway',
+ 'railway_car',
+ 'train',
+ 'monorail',
+ 'bullettrain_side',
+ 'bullettrain_front',
+ 'light_rail',
+ 'mountain_railway',
+ 'steam_locomotive',
+ 'train2',
+ 'metro',
+ 'tram',
+ 'station',
+ 'helicopter',
+ 'airplane_small',
+ 'airplane',
+ 'airplane_departure',
+ 'airplane_arriving',
+ 'sailboat',
+ 'motorboat',
+ 'speedboat',
+ 'ferry',
+ 'cruise_ship',
+ 'rocket',
+ 'satellite_orbital',
+ 'seat',
+ 'anchor',
+ 'construction',
+ 'fuelpump',
+ 'busstop',
+ 'vertical_traffic_light',
+ 'traffic_light',
+ 'checkered_flag',
+ 'ship',
+ 'ferris_wheel',
+ 'roller_coaster',
+ 'carousel_horse',
+ 'construction_site',
+ 'foggy',
+ 'tokyo_tower',
+ 'factory',
+ 'fountain',
+ 'rice_scene',
+ 'mountain',
+ 'mountain_snow',
+ 'mount_fuji',
+ 'volcano',
+ 'japan',
+ 'camping',
+ 'tent',
+ 'park',
+ 'motorway',
+ 'railway_track',
+ 'sunrise',
+ 'sunrise_over_mountains',
+ 'desert',
+ 'beach',
+ 'island',
+ 'city_sunset',
+ 'city_dusk',
+ 'cityscape',
+ 'night_with_stars',
+ 'bridge_at_night',
+ 'milky_way',
+ 'stars',
+ 'sparkler',
+ 'fireworks',
+ 'rainbow',
+ 'homes',
+ 'european_castle',
+ 'japanese_castle',
+ 'stadium',
+ 'statue_of_liberty',
+ 'house',
+ 'house_with_garden',
+ 'house_abandoned',
+ 'office',
+ 'department_store',
+ 'post_office',
+ 'european_post_office',
+ 'hospital',
+ 'bank',
+ 'hotel',
+ 'convenience_store',
+ 'school',
+ 'love_hotel',
+ 'wedding',
+ 'classical_building',
+ 'church',
+ 'mosque',
+ 'synagogue',
+ 'kaaba',
+ 'shinto_shrine',
+ 'shopping_cart',
+ 'scooter',
+ 'motor_scooter',
+ 'canoe',
+ 'watch',
+ 'iphone',
+ 'calling',
+ 'computer',
+ 'keyboard',
+ 'desktop',
+ 'printer',
+ 'mouse_three_button',
+ 'trackball',
+ 'joystick',
+ 'compression',
+ 'minidisc',
+ 'floppy_disk',
+ 'cd',
+ 'dvd',
+ 'vhs',
+ 'camera',
+ 'camera_with_flash',
+ 'video_camera',
+ 'movie_camera',
+ 'projector',
+ 'film_frames',
+ 'telephone_receiver',
+ 'telephone',
+ 'pager',
+ 'fax',
+ 'tv',
+ 'radio',
+ 'microphone2',
+ 'level_slider',
+ 'control_knobs',
+ 'stopwatch',
+ 'timer',
+ 'alarm_clock',
+ 'clock',
+ 'hourglass_flowing_sand',
+ 'hourglass',
+ 'satellite',
+ 'battery',
+ 'electric_plug',
+ 'bulb',
+ 'flashlight',
+ 'candle',
+ 'wastebasket',
+ 'oil',
+ 'money_with_wings',
+ 'dollar',
+ 'yen',
+ 'euro',
+ 'pound',
+ 'moneybag',
+ 'credit_card',
+ 'gem',
+ 'scales',
+ 'wrench',
+ 'hammer',
+ 'hammer_pick',
+ 'tools',
+ 'pick',
+ 'nut_and_bolt',
+ 'gear',
+ 'chains',
+ 'gun',
+ 'bomb',
+ 'knife',
+ 'dagger',
+ 'crossed_swords',
+ 'shield',
+ 'smoking',
+ 'skull_crossbones',
+ 'coffin',
+ 'urn',
+ 'amphora',
+ 'crystal_ball',
+ 'prayer_beads',
+ 'barber',
+ 'alembic',
+ 'telescope',
+ 'microscope',
+ 'hole',
+ 'pill',
+ 'syringe',
+ 'thermometer',
+ 'label',
+ 'bookmark',
+ 'toilet',
+ 'shower',
+ 'bathtub',
+ 'key',
+ 'key2',
+ 'couch',
+ 'sleeping_accommodation',
+ 'bed',
+ 'door',
+ 'bellhop',
+ 'frame_photo',
+ 'map',
+ 'beach_umbrella',
+ 'moyai',
+ 'shopping_bags',
+ 'balloon',
+ 'flags',
+ 'ribbon',
+ 'gift',
+ 'confetti_ball',
+ 'tada',
+ 'dolls',
+ 'wind_chime',
+ 'crossed_flags',
+ 'izakaya_lantern',
+ 'envelope',
+ 'envelope_with_arrow',
+ 'incoming_envelope',
+ 'e-mail',
+ 'love_letter',
+ 'postbox',
+ 'mailbox_closed',
+ 'mailbox',
+ 'mailbox_with_mail',
+ 'mailbox_with_no_mail',
+ 'package',
+ 'postal_horn',
+ 'inbox_tray',
+ 'outbox_tray',
+ 'scroll',
+ 'page_with_curl',
+ 'bookmark_tabs',
+ 'bar_chart',
+ 'chart_with_upwards_trend',
+ 'chart_with_downwards_trend',
+ 'page_facing_up',
+ 'date',
+ 'calendar',
+ 'calendar_spiral',
+ 'card_index',
+ 'card_box',
+ 'ballot_box',
+ 'file_cabinet',
+ 'clipboard',
+ 'notepad_spiral',
+ 'file_folder',
+ 'open_file_folder',
+ 'dividers',
+ 'newspaper2',
+ 'newspaper',
+ 'notebook',
+ 'closed_book',
+ 'green_book',
+ 'blue_book',
+ 'orange_book',
+ 'notebook_with_decorative_cover',
+ 'ledger',
+ 'books',
+ 'book',
+ 'link',
+ 'paperclip',
+ 'paperclips',
+ 'scissors',
+ 'triangular_ruler',
+ 'straight_ruler',
+ 'pushpin',
+ 'round_pushpin',
+ 'triangular_flag_on_post',
+ 'flag_white',
+ 'flag_black',
+ 'closed_lock_with_key',
+ 'lock',
+ 'unlock',
+ 'lock_with_ink_pen',
+ 'pen_ballpoint',
+ 'pen_fountain',
+ 'black_nib',
+ 'pencil',
+ 'pencil2',
+ 'crayon',
+ 'paintbrush',
+ 'mag',
+ 'mag_right',
+ '100',
+ '1234',
+ 'heart',
+ 'yellow_heart',
+ 'green_heart',
+ 'blue_heart',
+ 'purple_heart',
+ 'broken_heart',
+ 'heart_exclamation',
+ 'two_hearts',
+ 'revolving_hearts',
+ 'heartbeat',
+ 'heartpulse',
+ 'sparkling_heart',
+ 'cupid',
+ 'gift_heart',
+ 'heart_decoration',
+ 'peace',
+ 'cross',
+ 'star_and_crescent',
+ 'om_symbol',
+ 'wheel_of_dharma',
+ 'star_of_david',
+ 'six_pointed_star',
+ 'menorah',
+ 'yin_yang',
+ 'orthodox_cross',
+ 'place_of_worship',
+ 'ophiuchus',
+ 'aries',
+ 'taurus',
+ 'gemini',
+ 'cancer',
+ 'leo',
+ 'virgo',
+ 'libra',
+ 'scorpius',
+ 'sagittarius',
+ 'capricorn',
+ 'aquarius',
+ 'pisces',
+ 'id',
+ 'atom',
+ 'u7a7a',
+ 'u5272',
+ 'radioactive',
+ 'biohazard',
+ 'mobile_phone_off',
+ 'vibration_mode',
+ 'u6709',
+ 'u7121',
+ 'u7533',
+ 'u55b6',
+ 'u6708',
+ 'eight_pointed_black_star',
+ 'vs',
+ 'accept',
+ 'white_flower',
+ 'ideograph_advantage',
+ 'secret',
+ 'congratulations',
+ 'u5408',
+ 'u6e80',
+ 'u7981',
+ 'a',
+ 'b',
+ 'ab',
+ 'cl',
+ 'o2',
+ 'sos',
+ 'no_entry',
+ 'name_badge',
+ 'no_entry_sign',
+ 'x',
+ 'o',
+ 'anger',
+ 'hotsprings',
+ 'no_pedestrians',
+ 'do_not_litter',
+ 'no_bicycles',
+ 'non-potable_water',
+ 'underage',
+ 'no_mobile_phones',
+ 'exclamation',
+ 'grey_exclamation',
+ 'question',
+ 'grey_question',
+ 'bangbang',
+ 'interrobang',
+ 'low_brightness',
+ 'high_brightness',
+ 'trident',
+ 'fleur-de-lis',
+ 'part_alternation_mark',
+ 'warning',
+ 'children_crossing',
+ 'beginner',
+ 'recycle',
+ 'u6307',
+ 'chart',
+ 'sparkle',
+ 'eight_spoked_asterisk',
+ 'negative_squared_cross_mark',
+ 'white_check_mark',
+ 'diamond_shape_with_a_dot_inside',
+ 'cyclone',
+ 'loop',
+ 'globe_with_meridians',
+ 'm',
+ 'atm',
+ 'sa',
+ 'passport_control',
+ 'customs',
+ 'baggage_claim',
+ 'left_luggage',
+ 'wheelchair',
+ 'no_smoking',
+ 'wc',
+ 'parking',
+ 'potable_water',
+ 'mens',
+ 'womens',
+ 'baby_symbol',
+ 'restroom',
+ 'put_litter_in_its_place',
+ 'cinema',
+ 'signal_strength',
+ 'koko',
+ 'ng',
+ 'ok',
+ 'up',
+ 'cool',
+ 'new',
+ 'free',
+ 'zero',
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five',
+ 'six',
+ 'seven',
+ 'eight',
+ 'nine',
+ 'keycap_ten',
+ 'arrow_forward',
+ 'pause_button',
+ 'play_pause',
+ 'stop_button',
+ 'record_button',
+ 'track_next',
+ 'track_previous',
+ 'fast_forward',
+ 'rewind',
+ 'twisted_rightwards_arrows',
+ 'repeat',
+ 'repeat_one',
+ 'arrow_backward',
+ 'arrow_up_small',
+ 'arrow_down_small',
+ 'arrow_double_up',
+ 'arrow_double_down',
+ 'arrow_right',
+ 'arrow_left',
+ 'arrow_up',
+ 'arrow_down',
+ 'arrow_upper_right',
+ 'arrow_lower_right',
+ 'arrow_lower_left',
+ 'arrow_upper_left',
+ 'arrow_up_down',
+ 'left_right_arrow',
+ 'arrows_counterclockwise',
+ 'arrow_right_hook',
+ 'leftwards_arrow_with_hook',
+ 'arrow_heading_up',
+ 'arrow_heading_down',
+ 'hash',
+ 'asterisk',
+ 'information_source',
+ 'abc',
+ 'abcd',
+ 'capital_abcd',
+ 'symbols',
+ 'musical_note',
+ 'notes',
+ 'wavy_dash',
+ 'curly_loop',
+ 'heavy_check_mark',
+ 'arrows_clockwise',
+ 'heavy_plus_sign',
+ 'heavy_minus_sign',
+ 'heavy_division_sign',
+ 'heavy_multiplication_x',
+ 'heavy_dollar_sign',
+ 'currency_exchange',
+ 'copyright',
+ 'registered',
+ 'tm',
+ 'end',
+ 'back',
+ 'on',
+ 'top',
+ 'soon',
+ 'ballot_box_with_check',
+ 'radio_button',
+ 'white_circle',
+ 'black_circle',
+ 'red_circle',
+ 'large_blue_circle',
+ 'small_orange_diamond',
+ 'small_blue_diamond',
+ 'large_orange_diamond',
+ 'large_blue_diamond',
+ 'small_red_triangle',
+ 'black_small_square',
+ 'white_small_square',
+ 'black_large_square',
+ 'white_large_square',
+ 'small_red_triangle_down',
+ 'black_medium_square',
+ 'white_medium_square',
+ 'black_medium_small_square',
+ 'white_medium_small_square',
+ 'black_square_button',
+ 'white_square_button',
+ 'speaker',
+ 'sound',
+ 'loud_sound',
+ 'mute',
+ 'mega',
+ 'loudspeaker',
+ 'bell',
+ 'no_bell',
+ 'black_joker',
+ 'mahjong',
+ 'spades',
+ 'clubs',
+ 'hearts',
+ 'diamonds',
+ 'flower_playing_cards',
+ 'thought_balloon',
+ 'anger_right',
+ 'speech_balloon',
+ 'clock1',
+ 'clock2',
+ 'clock3',
+ 'clock4',
+ 'clock5',
+ 'clock6',
+ 'clock7',
+ 'clock8',
+ 'clock9',
+ 'clock10',
+ 'clock11',
+ 'clock12',
+ 'clock130',
+ 'clock230',
+ 'clock330',
+ 'clock430',
+ 'clock530',
+ 'clock630',
+ 'clock730',
+ 'clock830',
+ 'clock930',
+ 'clock1030',
+ 'clock1130',
+ 'clock1230',
+ 'eye_in_speech_bubble',
+ 'speech_left',
+ 'eject',
+ 'black_heart',
+ 'octagonal_sign',
+ 'asterisk_symbol',
+ 'pound_symbol',
+ 'digit_nine',
+ 'digit_eight',
+ 'digit_seven',
+ 'digit_six',
+ 'digit_five',
+ 'digit_four',
+ 'digit_three',
+ 'digit_two',
+ 'digit_one',
+ 'digit_zero',
+ 'regional_indicator_z',
+ 'regional_indicator_y',
+ 'regional_indicator_x',
+ 'regional_indicator_w',
+ 'regional_indicator_v',
+ 'regional_indicator_u',
+ 'regional_indicator_t',
+ 'regional_indicator_s',
+ 'regional_indicator_r',
+ 'regional_indicator_q',
+ 'regional_indicator_p',
+ 'regional_indicator_o',
+ 'regional_indicator_n',
+ 'regional_indicator_m',
+ 'regional_indicator_l',
+ 'regional_indicator_k',
+ 'regional_indicator_j',
+ 'regional_indicator_i',
+ 'regional_indicator_h',
+ 'regional_indicator_g',
+ 'regional_indicator_f',
+ 'regional_indicator_e',
+ 'regional_indicator_d',
+ 'regional_indicator_c',
+ 'regional_indicator_b',
+ 'regional_indicator_a',
+ 'flag_ac',
+ 'flag_af',
+ 'flag_al',
+ 'flag_dz',
+ 'flag_ad',
+ 'flag_ao',
+ 'flag_ai',
+ 'flag_ag',
+ 'flag_ar',
+ 'flag_am',
+ 'flag_aw',
+ 'flag_au',
+ 'flag_at',
+ 'flag_az',
+ 'flag_bs',
+ 'flag_bh',
+ 'flag_bd',
+ 'flag_bb',
+ 'flag_by',
+ 'flag_be',
+ 'flag_bz',
+ 'flag_bj',
+ 'flag_bm',
+ 'flag_bt',
+ 'flag_bo',
+ 'flag_ba',
+ 'flag_bw',
+ 'flag_br',
+ 'flag_bn',
+ 'flag_bg',
+ 'flag_bf',
+ 'flag_bi',
+ 'flag_cv',
+ 'flag_kh',
+ 'flag_cm',
+ 'flag_ca',
+ 'flag_ky',
+ 'flag_cf',
+ 'flag_td',
+ 'flag_cl',
+ 'flag_cn',
+ 'flag_co',
+ 'flag_km',
+ 'flag_cg',
+ 'flag_cd',
+ 'flag_cr',
+ 'flag_hr',
+ 'flag_cu',
+ 'flag_cy',
+ 'flag_cz',
+ 'flag_dk',
+ 'flag_dj',
+ 'flag_dm',
+ 'flag_do',
+ 'flag_ec',
+ 'flag_eg',
+ 'flag_sv',
+ 'flag_gq',
+ 'flag_er',
+ 'flag_ee',
+ 'flag_et',
+ 'flag_fk',
+ 'flag_fo',
+ 'flag_fj',
+ 'flag_fi',
+ 'flag_fr',
+ 'flag_pf',
+ 'flag_ga',
+ 'flag_gm',
+ 'flag_ge',
+ 'flag_de',
+ 'flag_gh',
+ 'flag_gi',
+ 'flag_gr',
+ 'flag_gl',
+ 'flag_gd',
+ 'flag_gu',
+ 'flag_gt',
+ 'flag_gn',
+ 'flag_gw',
+ 'flag_gy',
+ 'flag_ht',
+ 'flag_hn',
+ 'flag_hk',
+ 'flag_hu',
+ 'flag_is',
+ 'flag_in',
+ 'flag_id',
+ 'flag_ir',
+ 'flag_iq',
+ 'flag_ie',
+ 'flag_il',
+ 'flag_it',
+ 'flag_ci',
+ 'flag_jm',
+ 'flag_jp',
+ 'flag_je',
+ 'flag_jo',
+ 'flag_kz',
+ 'flag_ke',
+ 'flag_ki',
+ 'flag_xk',
+ 'flag_kw',
+ 'flag_kg',
+ 'flag_la',
+ 'flag_lv',
+ 'flag_lb',
+ 'flag_ls',
+ 'flag_lr',
+ 'flag_ly',
+ 'flag_li',
+ 'flag_lt',
+ 'flag_lu',
+ 'flag_mo',
+ 'flag_mk',
+ 'flag_mg',
+ 'flag_mw',
+ 'flag_my',
+ 'flag_mv',
+ 'flag_ml',
+ 'flag_mt',
+ 'flag_mh',
+ 'flag_mr',
+ 'flag_mu',
+ 'flag_mx',
+ 'flag_fm',
+ 'flag_md',
+ 'flag_mc',
+ 'flag_mn',
+ 'flag_me',
+ 'flag_ms',
+ 'flag_ma',
+ 'flag_mz',
+ 'flag_mm',
+ 'flag_na',
+ 'flag_nr',
+ 'flag_np',
+ 'flag_nl',
+ 'flag_nc',
+ 'flag_nz',
+ 'flag_ni',
+ 'flag_ne',
+ 'flag_ng',
+ 'flag_nu',
+ 'flag_kp',
+ 'flag_no',
+ 'flag_om',
+ 'flag_pk',
+ 'flag_pw',
+ 'flag_ps',
+ 'flag_pa',
+ 'flag_pg',
+ 'flag_py',
+ 'flag_pe',
+ 'flag_ph',
+ 'flag_pl',
+ 'flag_pt',
+ 'flag_pr',
+ 'flag_qa',
+ 'flag_ro',
+ 'flag_ru',
+ 'flag_rw',
+ 'flag_sh',
+ 'flag_kn',
+ 'flag_lc',
+ 'flag_vc',
+ 'flag_ws',
+ 'flag_sm',
+ 'flag_st',
+ 'flag_sa',
+ 'flag_sn',
+ 'flag_rs',
+ 'flag_sc',
+ 'flag_sl',
+ 'flag_sg',
+ 'flag_sk',
+ 'flag_si',
+ 'flag_sb',
+ 'flag_so',
+ 'flag_za',
+ 'flag_kr',
+ 'flag_es',
+ 'flag_lk',
+ 'flag_sd',
+ 'flag_sr',
+ 'flag_sz',
+ 'flag_se',
+ 'flag_ch',
+ 'flag_sy',
+ 'flag_tw',
+ 'flag_tj',
+ 'flag_tz',
+ 'flag_th',
+ 'flag_tl',
+ 'flag_tg',
+ 'flag_to',
+ 'flag_tt',
+ 'flag_tn',
+ 'flag_tr',
+ 'flag_tm',
+ 'flag_tv',
+ 'flag_ug',
+ 'flag_ua',
+ 'flag_ae',
+ 'flag_gb',
+ 'flag_us',
+ 'flag_vi',
+ 'flag_uy',
+ 'flag_uz',
+ 'flag_vu',
+ 'flag_va',
+ 'flag_ve',
+ 'flag_vn',
+ 'flag_wf',
+ 'flag_eh',
+ 'flag_ye',
+ 'flag_zm',
+ 'flag_zw',
+ 'flag_re',
+ 'flag_ax',
+ 'flag_ta',
+ 'flag_io',
+ 'flag_bq',
+ 'flag_cx',
+ 'flag_cc',
+ 'flag_gg',
+ 'flag_im',
+ 'flag_yt',
+ 'flag_nf',
+ 'flag_pn',
+ 'flag_bl',
+ 'flag_pm',
+ 'flag_gs',
+ 'flag_tk',
+ 'flag_bv',
+ 'flag_hm',
+ 'flag_sj',
+ 'flag_um',
+ 'flag_ic',
+ 'flag_ea',
+ 'flag_cp',
+ 'flag_dg',
+ 'flag_as',
+ 'flag_aq',
+ 'flag_vg',
+ 'flag_ck',
+ 'flag_cw',
+ 'flag_eu',
+ 'flag_gf',
+ 'flag_tf',
+ 'flag_gp',
+ 'flag_mq',
+ 'flag_mp',
+ 'flag_sx',
+ 'flag_ss',
+ 'flag_tc',
+ 'flag_mf'
+];
diff --git a/app/lib/realm.js b/app/lib/realm.js
index b67661669..7087ac26e 100644
--- a/app/lib/realm.js
+++ b/app/lib/realm.js
@@ -143,6 +143,21 @@ const url = {
}
};
+const messagesReactionsUsernamesSchema = {
+ name: 'messagesReactionsUsernames',
+ properties: {
+ value: 'string'
+ }
+};
+
+const messagesReactionsSchema = {
+ name: 'messagesReactions',
+ primaryKey: 'emoji',
+ properties: {
+ emoji: 'string',
+ usernames: { type: 'list', objectType: 'messagesReactionsUsernames' }
+ }
+};
const messagesEditedBySchema = {
name: 'messagesEditedBy',
@@ -174,7 +189,8 @@ const messagesSchema = {
status: { type: 'int', optional: true },
pinned: { type: 'bool', optional: true },
starred: { type: 'bool', optional: true },
- editedBy: 'messagesEditedBy'
+ editedBy: 'messagesEditedBy',
+ reactions: { type: 'list', objectType: 'messagesReactions' }
}
};
@@ -223,7 +239,9 @@ const schema = [
url,
frequentlyUsedEmojiSchema,
customEmojiAliasesSchema,
- customEmojisSchema
+ customEmojisSchema,
+ messagesReactionsSchema,
+ messagesReactionsUsernamesSchema
];
class DB {
databases = {
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index 781ed2112..d561e5e09 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -1,6 +1,7 @@
import Random from 'react-native-meteor/lib/Random';
import { AsyncStorage, Platform } from 'react-native';
import { hashPassword } from 'react-native-meteor/lib/utils';
+import _ from 'lodash';
import RNFetchBlob from 'react-native-fetch-blob';
import reduxStore from './createStore';
@@ -264,6 +265,8 @@ const RocketChat = {
// loadHistory returns message.starred as object
// stream-room-messages returns message.starred as an array
message.starred = message.starred && (Array.isArray(message.starred) ? message.starred.length > 0 : !!message.starred);
+ message.reactions = _.map(message.reactions, (value, key) =>
+ ({ emoji: key, usernames: value.usernames.map(username => ({ value: username })) }));
return message;
},
loadMessagesForRoom(rid, end, cb) {
@@ -590,6 +593,9 @@ const RocketChat = {
},
setUserPresenceDefaultStatus(status) {
return call('UserPresence:setDefaultStatus', status);
+ },
+ setReaction(emoji, messageId) {
+ return call('setReaction', emoji, messageId);
}
};
diff --git a/app/presentation/KeyboardView.js b/app/presentation/KeyboardView.js
index 6e405c664..82a5e1100 100644
--- a/app/presentation/KeyboardView.js
+++ b/app/presentation/KeyboardView.js
@@ -2,14 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import { ViewPropTypes } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
-import { connect } from 'react-redux';
import scrollPersistTaps from '../utils/scrollPersistTaps';
-import { setKeyboardOpen, setKeyboardClosed } from '../actions/keyboard';
-@connect(null, dispatch => ({
- setKeyboardOpen: () => dispatch(setKeyboardOpen()),
- setKeyboardClosed: () => dispatch(setKeyboardClosed())
-}))
export default class KeyboardView extends React.PureComponent {
static propTypes = {
style: ViewPropTypes.style,
@@ -19,9 +13,7 @@ export default class KeyboardView extends React.PureComponent {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
- ]),
- setKeyboardOpen: PropTypes.func,
- setKeyboardClosed: PropTypes.func
+ ])
}
render() {
@@ -34,8 +26,6 @@ export default class KeyboardView extends React.PureComponent {
alwaysBounceVertical={false}
extraHeight={this.props.keyboardVerticalOffset}
behavior='position'
- onKeyboardWillShow={() => this.props.setKeyboardOpen()}
- onKeyboardWillHide={() => this.props.setKeyboardClosed()}
>
{this.props.children}
diff --git a/app/push.js b/app/push.js
index 6c0687d8b..2a8de1ff6 100644
--- a/app/push.js
+++ b/app/push.js
@@ -4,11 +4,13 @@ import EJSON from 'ejson';
import { goRoom } from './containers/routes/NavigationService';
const handleNotification = (notification) => {
- if (notification.usernInteraction) {
+ if (!notification.userInteraction) {
return;
}
- const { rid, name } = EJSON.parse(notification.ejson);
- return rid && name && goRoom({ rid, name });
+ const {
+ rid, name, sender, type
+ } = EJSON.parse(notification.ejson || notification.data.ejson);
+ return rid && goRoom({ rid, name: type === 'd' ? sender.username : name });
};
PushNotification.configure({
diff --git a/app/reducers/index.js b/app/reducers/index.js
index 60c2be016..f7872a539 100644
--- a/app/reducers/index.js
+++ b/app/reducers/index.js
@@ -12,7 +12,6 @@ import app from './app';
import permissions from './permissions';
import customEmojis from './customEmojis';
import activeUsers from './activeUsers';
-import keyboard from './keyboard';
export default combineReducers({
settings,
@@ -27,6 +26,5 @@ export default combineReducers({
rooms,
permissions,
customEmojis,
- activeUsers,
- keyboard
+ activeUsers
});
diff --git a/app/reducers/keyboard.js b/app/reducers/keyboard.js
deleted file mode 100644
index 0885be777..000000000
--- a/app/reducers/keyboard.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import * as types from '../actions/actionsTypes';
-
-const initialState = {
- isOpen: false
-};
-
-export default function messages(state = initialState, action) {
- switch (action.type) {
- case types.KEYBOARD.OPEN:
- return {
- ...state,
- isOpen: true
- };
- case types.KEYBOARD.CLOSE:
- return {
- ...state,
- isOpen: false
- };
- default:
- return state;
- }
-}
diff --git a/app/reducers/messages.js b/app/reducers/messages.js
index 7c4022375..ae17f395e 100644
--- a/app/reducers/messages.js
+++ b/app/reducers/messages.js
@@ -8,7 +8,8 @@ const initialState = {
editing: false,
permalink: '',
showActions: false,
- showErrorActions: false
+ showErrorActions: false,
+ showReactionPicker: false
};
export default function messages(state = initialState, action) {
@@ -96,6 +97,12 @@ export default function messages(state = initialState, action) {
...state,
message: {}
};
+ case types.MESSAGES.TOGGLE_REACTION_PICKER:
+ return {
+ ...state,
+ showReactionPicker: !state.showReactionPicker,
+ actionMessage: action.message
+ };
default:
return state;
}
diff --git a/app/reducers/room.js b/app/reducers/room.js
index 03b1fa898..4ed7c921b 100644
--- a/app/reducers/room.js
+++ b/app/reducers/room.js
@@ -1,7 +1,8 @@
import * as types from '../actions/actionsTypes';
const initialState = {
- usersTyping: []
+ usersTyping: [],
+ layoutAnimation: new Date()
};
export default function room(state = initialState, action) {
@@ -31,6 +32,11 @@ export default function room(state = initialState, action) {
...state,
usersTyping: [...state.usersTyping.filter(user => user !== action.username)]
};
+ case types.ROOM.LAYOUT_ANIMATION:
+ return {
+ ...state,
+ layoutAnimation: new Date()
+ };
default:
return state;
}
diff --git a/app/views/Photo.js b/app/views/Photo.js
index e68597543..a13dd4adb 100644
--- a/app/views/Photo.js
+++ b/app/views/Photo.js
@@ -14,7 +14,7 @@ const styles = {
}
};
-export default class extends React.PureComponent {
+export default class Photo extends React.PureComponent {
static propTypes = {
navigation: PropTypes.object.isRequired
}
diff --git a/app/views/RoomView/Header/index.js b/app/views/RoomView/Header/index.js
index 5cf03f123..1402712f4 100644
--- a/app/views/RoomView/Header/index.js
+++ b/app/views/RoomView/Header/index.js
@@ -18,7 +18,7 @@ import { closeRoom } from '../../../actions/room';
}), dispatch => ({
close: () => dispatch(closeRoom())
}))
-export default class extends React.PureComponent {
+export default class RoomHeaderView extends React.PureComponent {
static propTypes = {
close: PropTypes.func.isRequired,
navigation: PropTypes.object.isRequired,
diff --git a/app/views/RoomView/ListView.js b/app/views/RoomView/ListView.js
index 3eacad9cb..500e693bb 100644
--- a/app/views/RoomView/ListView.js
+++ b/app/views/RoomView/ListView.js
@@ -1,13 +1,20 @@
import { ListView as OldList } from 'realm/react-native';
import React from 'react';
import cloneReferencedElement from 'react-clone-referenced-element';
-import { ScrollView, ListView as OldList2 } from 'react-native';
+import { ScrollView, ListView as OldList2, LayoutAnimation } from 'react-native';
import moment from 'moment';
import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+
import DateSeparator from './DateSeparator';
import UnreadSeparator from './UnreadSeparator';
+import styles from './styles';
+import debounce from '../../utils/debounce';
+import Typing from '../../containers/Typing';
+import database from '../../lib/realm';
+import scrollPersistTaps from '../../utils/scrollPersistTaps';
-const DEFAULT_SCROLL_CALLBACK_THROTTLE = 50;
+const DEFAULT_SCROLL_CALLBACK_THROTTLE = 100;
export class DataSource extends OldList.DataSource {
getRowData(sectionIndex: number, rowIndex: number): any {
@@ -20,6 +27,56 @@ export class DataSource extends OldList.DataSource {
}
}
+const ds = new DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
+
+export class List extends React.Component {
+ static propTypes = {
+ onEndReached: PropTypes.func,
+ renderFooter: PropTypes.func,
+ renderRow: PropTypes.func,
+ room: PropTypes.string,
+ end: PropTypes.bool
+ };
+ constructor(props) {
+ super(props);
+ this.data = database
+ .objects('messages')
+ .filtered('rid = $0', props.room)
+ .sorted('ts', true);
+ this.dataSource = ds.cloneWithRows(this.data);
+ }
+ componentDidMount() {
+ this.data.addListener(this.updateState);
+ }
+ shouldComponentUpdate(nextProps) {
+ return this.props.end !== nextProps.end;
+ }
+ componentWillUpdate() {
+ LayoutAnimation.easeInEaseOut();
+ }
+ updateState = debounce(() => {
+ // this.setState({
+ this.dataSource = this.dataSource.cloneWithRows(this.data);
+ this.forceUpdate();
+ // });
+ }, 100);
+
+ render() {
+ return ( }
+ onEndReached={() => this.props.onEndReached(this.data)}
+ dataSource={this.dataSource}
+ renderRow={item => this.props.renderRow(item)}
+ initialListSize={10}
+ {...scrollPersistTaps}
+ />);
+ }
+}
+
@connect(state => ({
lastOpen: state.room.lastOpen
}))
diff --git a/app/views/RoomView/ReactionPicker.js b/app/views/RoomView/ReactionPicker.js
new file mode 100644
index 000000000..7c5592cb1
--- /dev/null
+++ b/app/views/RoomView/ReactionPicker.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { View, Platform } from 'react-native';
+import { connect } from 'react-redux';
+import Modal from 'react-native-modal';
+import { responsive } from 'react-native-responsive-ui';
+import EmojiPicker from '../../containers/EmojiPicker';
+import { toggleReactionPicker } from '../../actions/messages';
+import styles from './styles';
+
+const margin = Platform.OS === 'android' ? 40 : 20;
+const tabEmojiStyle = { fontSize: 15 };
+
+@connect(state => ({
+ showReactionPicker: state.messages.showReactionPicker
+}), dispatch => ({
+ toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
+}))
+@responsive
+export default class ReactionPicker extends React.Component {
+ static propTypes = {
+ window: PropTypes.any,
+ showReactionPicker: PropTypes.bool,
+ toggleReactionPicker: PropTypes.func,
+ onEmojiSelected: PropTypes.func
+ };
+
+ shouldComponentUpdate(nextProps) {
+ return nextProps.showReactionPicker !== this.props.showReactionPicker || this.props.window.width !== nextProps.window.width;
+ }
+
+ onEmojiSelected(emoji, shortname) {
+ // standard emojis: `emoji` is unicode and `shortname` is :joy:
+ // custom emojis: only `emoji` is returned with shortname type (:joy:)
+ // to set reactions, we need shortname type
+ this.props.onEmojiSelected(shortname || emoji);
+ }
+
+ render() {
+ const { width, height } = this.props.window;
+ return (
+ this.props.toggleReactionPicker()}
+ onBackButtonPress={() => this.props.toggleReactionPicker()}
+ animationIn='fadeIn'
+ animationOut='fadeOut'
+ >
+
+ this.onEmojiSelected(emoji, shortname)}
+ />
+
+
+ );
+ }
+}
diff --git a/app/views/RoomView/UnreadSeparator.js b/app/views/RoomView/UnreadSeparator.js
index c2856e677..f5384fc29 100644
--- a/app/views/RoomView/UnreadSeparator.js
+++ b/app/views/RoomView/UnreadSeparator.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { View, StyleSheet, Text } from 'react-native';
+import { View, StyleSheet, Text, LayoutAnimation } from 'react-native';
const styles = StyleSheet.create({
firstUnread: {
@@ -22,11 +22,16 @@ const styles = StyleSheet.create({
}
});
-const UnreadSeparator = () => (
-
-
- unread messages
-
-);
-
-export default UnreadSeparator;
+export default class UnreadSeparator extends React.PureComponent {
+ componentWillUnmount() {
+ LayoutAnimation.linear();
+ }
+ render() {
+ return (
+
+
+ unread messages
+
+ );
+ }
+}
diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js
index c960c74c4..4d32ca07d 100644
--- a/app/views/RoomView/index.js
+++ b/app/views/RoomView/index.js
@@ -1,45 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Text, View, Button, SafeAreaView } from 'react-native';
+import { Text, View, Button, LayoutAnimation } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import equal from 'deep-equal';
-import { ListView } from './ListView';
+import { List } from './ListView';
import * as actions from '../../actions';
import { openRoom, setLastOpen } from '../../actions/room';
-import { editCancel } from '../../actions/messages';
+import { editCancel, toggleReactionPicker } from '../../actions/messages';
import database from '../../lib/realm';
import RocketChat from '../../lib/rocketchat';
import Message from '../../containers/message';
import MessageActions from '../../containers/MessageActions';
import MessageErrorActions from '../../containers/MessageErrorActions';
import MessageBox from '../../containers/MessageBox';
-import Typing from '../../containers/Typing';
-import KeyboardView from '../../presentation/KeyboardView';
import Header from '../../containers/Header';
import RoomsHeader from './Header';
+import ReactionPicker from './ReactionPicker';
import Banner from './banner';
import styles from './styles';
-import debounce from '../../utils/debounce';
-import scrollPersistTaps from '../../utils/scrollPersistTaps';
-
-const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
-
-const typing = () => ;
@connect(
state => ({
Site_Url: state.settings.Site_Url || state.server ? state.server.server : '',
Message_TimeFormat: state.settings.Message_TimeFormat,
loading: state.messages.isFetching,
- user: state.login.user
+ user: state.login.user,
+ actionMessage: state.messages.actionMessage,
+ layoutAnimation: state.room.layoutAnimation
}),
dispatch => ({
actions: bindActionCreators(actions, dispatch),
openRoom: room => dispatch(openRoom(room)),
editCancel: () => dispatch(editCancel()),
- setLastOpen: date => dispatch(setLastOpen(date))
+ setLastOpen: date => dispatch(setLastOpen(date)),
+ toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
})
)
export default class RoomView extends React.Component {
@@ -52,7 +48,11 @@ export default class RoomView extends React.Component {
rid: PropTypes.string,
name: PropTypes.string,
Site_Url: PropTypes.string,
- Message_TimeFormat: PropTypes.string
+ Message_TimeFormat: PropTypes.string,
+ loading: PropTypes.bool,
+ actionMessage: PropTypes.object,
+ toggleReactionPicker: PropTypes.func.isRequired,
+ layoutAnimation: PropTypes.instanceOf(Date)
};
static navigationOptions = ({ navigation }) => ({
@@ -64,87 +64,83 @@ export default class RoomView extends React.Component {
this.rid =
props.rid ||
props.navigation.state.params.room.rid;
- this.name = this.props.name ||
- this.props.navigation.state.params.name ||
- this.props.navigation.state.params.room.name;
- this.opened = new Date();
- this.data = database
- .objects('messages')
- .filtered('rid = $0', this.rid)
- .sorted('ts', true);
- const rowIds = this.data.map((row, index) => index);
+ this.name = props.name ||
+ props.navigation.state.params.name ||
+ props.navigation.state.params.room.name;
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
this.state = {
- dataSource: ds.cloneWithRows(this.data, rowIds),
loaded: true,
joined: typeof props.rid === 'undefined',
- readOnly: false
+ room: {}
};
+ this.onReactionPress = this.onReactionPress.bind(this);
}
- componentWillMount() {
+ async componentWillMount() {
this.props.navigation.setParams({
title: this.name
});
this.updateRoom();
- this.props.openRoom({ rid: this.rid, name: this.name, ls: this.room.ls });
- if (this.room.alert || this.room.unread || this.room.userMentions) {
- this.props.setLastOpen(this.room.ls);
+ await this.props.openRoom({ rid: this.rid, name: this.name, ls: this.state.room.ls });
+ if (this.state.room.alert || this.state.room.unread || this.state.room.userMentions) {
+ this.props.setLastOpen(this.state.room.ls);
} else {
this.props.setLastOpen(null);
}
- this.data.addListener(this.updateState);
+
this.rooms.addListener(this.updateRoom);
}
+ componentWillReceiveProps(nextProps) {
+ if (this.props.layoutAnimation !== nextProps.layoutAnimation) {
+ LayoutAnimation.spring();
+ }
+ }
shouldComponentUpdate(nextProps, nextState) {
return !(equal(this.props, nextProps) && equal(this.state, nextState));
}
componentWillUnmount() {
clearTimeout(this.timer);
- this.data.removeAllListeners();
+ this.rooms.removeAllListeners();
this.props.editCancel();
}
- onEndReached = () => {
- if (
- // rowCount &&
- this.state.loaded &&
- this.state.loadingMore !== true &&
- this.state.end !== true
- ) {
- this.setState({
- loadingMore: true
- });
- requestAnimationFrame(() => {
- const lastRowData = this.data[this.data.length - 1];
- if (!lastRowData) {
- return;
- }
- RocketChat.loadMessagesForRoom(this.rid, lastRowData.ts, ({ end }) => {
- this.setState({
- loadingMore: false,
- end
- });
- });
- });
+ onEndReached = (data) => {
+ if (this.props.loading || this.state.end) {
+ return;
}
+ if (!this.state.loaded) {
+ alert(2);
+ return;
+ }
+
+ requestAnimationFrame(() => {
+ const lastRowData = data[data.length - 1];
+ if (!lastRowData) {
+ return;
+ }
+ RocketChat.loadMessagesForRoom(this.rid, lastRowData.ts, ({ end }) => end && this.setState({
+ end
+ }));
+ });
}
- updateState = debounce(() => {
- const rowIds = this.data.map((row, index) => index);
- this.setState({
- dataSource: this.state.dataSource.cloneWithRows(this.data, rowIds)
- });
- }, 50);
+ onReactionPress = (shortname, messageId) => {
+ if (!messageId) {
+ RocketChat.setReaction(shortname, this.props.actionMessage._id);
+ return this.props.toggleReactionPicker();
+ }
+ RocketChat.setReaction(shortname, messageId);
+ };
updateRoom = () => {
- [this.room] = this.rooms;
- this.setState({ readOnly: this.room.ro });
+ this.setState({ room: this.rooms[0] });
}
- sendMessage = message => RocketChat.sendMessage(this.rid, message).then(() => {
- this.props.setLastOpen(null);
- });
+ sendMessage = (message) => {
+ RocketChat.sendMessage(this.rid, message).then(() => {
+ this.props.setLastOpen(null);
+ });
+ };
joinRoom = async() => {
await RocketChat.joinRoom(this.props.rid);
@@ -157,10 +153,11 @@ export default class RoomView extends React.Component {
);
@@ -175,7 +172,7 @@ export default class RoomView extends React.Component {
);
}
- if (this.state.readOnly) {
+ if (this.state.room.ro) {
return (
This room is read only
@@ -186,37 +183,28 @@ export default class RoomView extends React.Component {
};
renderHeader = () => {
- if (this.state.loadingMore) {
- return Loading more messages...;
- }
-
if (this.state.end) {
return Start of conversation;
}
+ return Loading more messages...;
}
render() {
return (
-
-
+
-
- this.renderItem(item)}
- initialListSize={10}
- {...scrollPersistTaps}
- />
-
+ this.renderItem(item)}
+ />
{this.renderFooter()}
-
+ {this.state.room._id ? : null}
-
+
+
);
}
}
diff --git a/app/views/RoomView/styles.js b/app/views/RoomView/styles.js
index 2e2713d05..089c56fc8 100644
--- a/app/views/RoomView/styles.js
+++ b/app/views/RoomView/styles.js
@@ -33,5 +33,13 @@ export default StyleSheet.create({
},
readOnly: {
padding: 10
+ },
+ reactionPickerContainer: {
+ // width: width - 20,
+ // height: width - 20,
+ // paddingHorizontal: Platform.OS === 'android' ? 11 : 10,
+ backgroundColor: '#F7F7F7',
+ borderRadius: 4,
+ flexDirection: 'column'
}
});
diff --git a/app/views/RoomsListView/Header/index.js b/app/views/RoomsListView/Header/index.js
index 52b4d5064..af91e2e85 100644
--- a/app/views/RoomsListView/Header/index.js
+++ b/app/views/RoomsListView/Header/index.js
@@ -20,7 +20,7 @@ import styles from './styles';
}), dispatch => ({
setSearch: searchText => dispatch(setSearch(searchText))
}))
-export default class extends React.Component {
+export default class RoomsListHeaderView extends React.Component {
static propTypes = {
navigation: PropTypes.object.isRequired,
user: PropTypes.object.isRequired,
diff --git a/app/views/SelectUsersView.js b/app/views/SelectUsersView.js
index 667ecccf0..17b908899 100644
--- a/app/views/SelectUsersView.js
+++ b/app/views/SelectUsersView.js
@@ -69,7 +69,7 @@ const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
resetCreateChannel: () => dispatch(createChannelActions.reset())
})
)
-export default class RoomsListView extends React.Component {
+export default class SelectUsersView extends React.Component {
static propTypes = {
navigation: PropTypes.object.isRequired,
Site_Url: PropTypes.string,
diff --git a/index.android.js b/index.android.js
index c7a8b8bc9..4fade3c08 100644
--- a/index.android.js
+++ b/index.android.js
@@ -5,6 +5,8 @@ import { AppRegistry } from 'react-native';
import './app/push';
import RocketChat from './app/index';
+// UIManager.setLayoutAnimationEnabledExperimental(true);
+
// import './app/ReactotronConfig';
// import { AppRegistry } from 'react-native';
// import Routes from './app/routes';
diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj
index 167166fe6..23a4c8279 100644
--- a/ios/RocketChatRN.xcodeproj/project.pbxproj
+++ b/ios/RocketChatRN.xcodeproj/project.pbxproj
@@ -47,11 +47,13 @@
647660C6B6A340C7BD4D1099 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A18EFC3B0CFE40E0918A8F0C /* EvilIcons.ttf */; };
70A8D9B456894EFFAF027CAB /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7A30DA4B2D474348824CD05B /* FontAwesome.ttf */; };
77C35F50C01C43668188886C /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A0EEFAF8AB14F5B9E796CDD /* libRNVectorIcons.a */; };
+ 7A430E4F20238C46008F55BC /* libRCTCustomInputController.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A430E1E20238C02008F55BC /* libRCTCustomInputController.a */; };
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
8A159EDB97C44E52AF62D69C /* libRNSVG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA50CE47374C4C35BE6D9D58 /* libRNSVG.a */; };
8ECBD927DDAC4987B98E102E /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20CE3E407E0D4D9E8C9885F2 /* libRCTVideo.a */; };
AE5D35882AE04CC29630FB3D /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = DC6EE17B5550465E98C70FF0 /* Entypo.ttf */; };
B88F586F1FBF57F600B352B8 /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B88F58461FBF55E200B352B8 /* libRCTPushNotification.a */; };
+ B8971BB2202A093B0000D245 /* libKeyboardTrackingView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B8971BB1202A091D0000D245 /* libKeyboardTrackingView.a */; };
B8C682A81FD850F4003A12C8 /* icomoon.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B8C682611FD84CEF003A12C8 /* icomoon.ttf */; };
B8C682AC1FD8511D003A12C8 /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1B0746E708284151B8AD1198 /* Ionicons.ttf */; };
B8C682AD1FD8511E003A12C8 /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1B0746E708284151B8AD1198 /* Ionicons.ttf */; };
@@ -296,6 +298,13 @@
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTLinking;
};
+ 7A430E1D20238C02008F55BC /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 39DF4FE71E00394E00F5B4B2;
+ remoteInfo = RCTCustomInputController;
+ };
7A7F5C981FCC982500024129 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = AD0379F2BCE84C968538CDAF /* RCTVideo.xcodeproj */;
@@ -366,6 +375,13 @@
remoteGlobalIDString = 9936F32F1F5F2E5B0010BF04;
remoteInfo = "privatedata-tvOS";
};
+ B8971BB0202A091D0000D245 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = D834CED81CC64F2400FA5668;
+ remoteInfo = KeyboardTrackingView;
+ };
B8E79A8D1F3CCC6D005B464F /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4CD38E4891ED4601B7481448 /* RNFetchBlob.xcodeproj */;
@@ -436,6 +452,7 @@
6533FB90166345D29F1B91C0 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFetchBlob.a; sourceTree = ""; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; };
7A30DA4B2D474348824CD05B /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = ""; };
+ 7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCustomInputController.xcodeproj; path = "../node_modules/react-native-keyboard-input/lib/ios/RCTCustomInputController.xcodeproj"; sourceTree = ""; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; };
8A2DD67ADD954AD9873F45FC /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = ""; };
9A1E1766CCB84C91A62BD5A6 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = ""; };
@@ -444,6 +461,7 @@
B2607FA180F14E6584301101 /* libSplashScreen.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libSplashScreen.a; sourceTree = ""; };
B37C79D9BD0742CE936B6982 /* libc++.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
B88F58361FBF55E200B352B8 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = ""; };
+ B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = KeyboardTrackingView.xcodeproj; path = "../node_modules/react-native-keyboard-tracking-view/lib/KeyboardTrackingView.xcodeproj"; sourceTree = ""; };
B8C682611FD84CEF003A12C8 /* icomoon.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = icomoon.ttf; path = ../resources/fonts/icomoon.ttf; sourceTree = ""; };
BAAE4B947F5D44959F0A9D5A /* libRNZeroconf.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNZeroconf.a; sourceTree = ""; };
C23AEF1D9EBE4A38A1A6B97B /* RNSVG.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSVG.xcodeproj; path = "../node_modules/react-native-svg/ios/RNSVG.xcodeproj"; sourceTree = ""; };
@@ -467,6 +485,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ B8971BB2202A093B0000D245 /* libKeyboardTrackingView.a in Frameworks */,
+ 7A430E4F20238C46008F55BC /* libRCTCustomInputController.a in Frameworks */,
146834051AC3E58100842450 /* libReact.a in Frameworks */,
B88F586F1FBF57F600B352B8 /* libRCTPushNotification.a in Frameworks */,
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */,
@@ -674,6 +694,14 @@
name = Products;
sourceTree = "";
};
+ 7A430E1720238C01008F55BC /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 7A430E1E20238C02008F55BC /* libRCTCustomInputController.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
7A7F5C831FCC982500024129 /* Products */ = {
isa = PBXGroup;
children = (
@@ -694,6 +722,8 @@
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
+ B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */,
+ 7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */,
B88F58361FBF55E200B352B8 /* RCTPushNotification.xcodeproj */,
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */,
146833FF1AC3E56700842450 /* React.xcodeproj */,
@@ -780,6 +810,14 @@
name = Products;
sourceTree = "";
};
+ B8971BAD202A091D0000D245 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ B8971BB1202A091D0000D245 /* libKeyboardTrackingView.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
B8E79A681F3CCC69005B464F /* Recovered References */ = {
isa = PBXGroup;
children = (
@@ -952,6 +990,10 @@
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectReferences = (
+ {
+ ProductGroup = B8971BAD202A091D0000D245 /* Products */;
+ ProjectRef = B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */;
+ },
{
ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */;
ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */;
@@ -960,6 +1002,10 @@
ProductGroup = 5E91572E1DD0AC6500FF2AA8 /* Products */;
ProjectRef = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */;
},
+ {
+ ProductGroup = 7A430E1720238C01008F55BC /* Products */;
+ ProjectRef = 7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */;
+ },
{
ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */;
ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */;
@@ -1261,6 +1307,13 @@
remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
+ 7A430E1E20238C02008F55BC /* libRCTCustomInputController.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libRCTCustomInputController.a;
+ remoteRef = 7A430E1D20238C02008F55BC /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
7A7F5C991FCC982500024129 /* libRCTVideo.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -1331,6 +1384,13 @@
remoteRef = B88F58661FBF55E200B352B8 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
+ B8971BB1202A091D0000D245 /* libKeyboardTrackingView.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libKeyboardTrackingView.a;
+ remoteRef = B8971BB0202A091D0000D245 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
B8E79A8E1F3CCC6D005B464F /* libRNFetchBlob.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
diff --git a/package-lock.json b/package-lock.json
index c2a1dc120..57ab9160a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1481,6 +1481,16 @@
"babel-helper-is-void-0": "0.2.0"
}
},
+ "babel-plugin-module-resolver": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-2.7.1.tgz",
+ "integrity": "sha1-GL48Qt31n3pFbJ4FEs2ROU9uS+E=",
+ "requires": {
+ "find-babel-config": "1.1.0",
+ "glob": "7.1.2",
+ "resolve": "1.5.0"
+ }
+ },
"babel-plugin-react-transform": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/babel-plugin-react-transform/-/babel-plugin-react-transform-3.0.0.tgz",
@@ -2172,6 +2182,18 @@
"semver": "5.4.1"
}
},
+ "babel-preset-expo": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-4.0.0.tgz",
+ "integrity": "sha512-EWFC6WJzZX5t2zZfLNdJXUkNMusUkxP5V+GrXaSk8pKbWGjE3TD2i33ncpF/4aQM9QGDm+SH6pImZJOqIDlRUw==",
+ "requires": {
+ "babel-plugin-module-resolver": "2.7.1",
+ "babel-plugin-transform-decorators-legacy": "1.3.4",
+ "babel-plugin-transform-exponentiation-operator": "6.24.1",
+ "babel-plugin-transform-export-extensions": "6.22.0",
+ "babel-preset-react-native": "4.0.0"
+ }
+ },
"babel-preset-fbjs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.1.4.tgz",
@@ -4610,11 +4632,6 @@
"resolved": "https://registry.npmjs.org/email-validator/-/email-validator-1.1.1.tgz",
"integrity": "sha512-vkcJJZEb7JXDY883Nx1Lkmb6noM3j1SfSt8L9tVFhZPnPQiFq+Nkd5evc77+tRVS4ChTUSr34voThsglI/ja/A=="
},
- "emoji-datasource": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/emoji-datasource/-/emoji-datasource-4.0.3.tgz",
- "integrity": "sha1-1gDnDwVoMnyyjPp79B1T88SGeQE="
- },
"emoji-regex": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
@@ -5771,6 +5788,15 @@
}
}
},
+ "find-babel-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.1.0.tgz",
+ "integrity": "sha1-rMAQQ6Z0n+w0Qpvmtk9ULrtdY1U=",
+ "requires": {
+ "json5": "0.5.1",
+ "path-exists": "3.0.0"
+ }
+ },
"find-cache-dir": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz",
@@ -12542,6 +12568,21 @@
"react-native-iphone-x-helper": "1.0.1"
}
},
+ "react-native-keyboard-input": {
+ "version": "git+https://github.com/RocketChat/react-native-keyboard-input.git#38273b0513f69a5e6e0719f65a675f9f2b5ee883",
+ "requires": {
+ "lodash": "4.17.4",
+ "react-native-keyboard-tracking-view": "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git#3a4084f0a1063e23ae6435facdf1f79152558d15"
+ },
+ "dependencies": {
+ "react-native-keyboard-tracking-view": {
+ "version": "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git#3a4084f0a1063e23ae6435facdf1f79152558d15"
+ }
+ }
+ },
+ "react-native-keyboard-tracking-view": {
+ "version": "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git#3a4084f0a1063e23ae6435facdf1f79152558d15"
+ },
"react-native-loading-spinner-overlay": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/react-native-loading-spinner-overlay/-/react-native-loading-spinner-overlay-0.5.2.tgz",
@@ -12593,9 +12634,9 @@
}
},
"react-native-optimized-flatlist": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/react-native-optimized-flatlist/-/react-native-optimized-flatlist-1.0.3.tgz",
- "integrity": "sha1-tFN58lpXu05vhZwZDZmEexgR4Ak=",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/react-native-optimized-flatlist/-/react-native-optimized-flatlist-1.0.4.tgz",
+ "integrity": "sha512-PMoZRJAHKzd/ahYKUzt43AJ+kVhHpOSTvBhJdQqooZXw312xADWpR7iDvBAbBiRGkmk0yM4GJacd9TMft6q/Gg==",
"requires": {
"prop-types": "15.6.0"
}
@@ -12605,6 +12646,14 @@
"resolved": "https://registry.npmjs.org/react-native-push-notification/-/react-native-push-notification-3.0.1.tgz",
"integrity": "sha1-DiPbMC0Du0o/KNwHLcryqaEXjtg="
},
+ "react-native-responsive-ui": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/react-native-responsive-ui/-/react-native-responsive-ui-1.1.1.tgz",
+ "integrity": "sha1-60GDnU85Uf8CVmAYXDapqc4zdZ8=",
+ "requires": {
+ "lodash": "4.17.4"
+ }
+ },
"react-native-scrollable-tab-view": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/react-native-scrollable-tab-view/-/react-native-scrollable-tab-view-0.8.0.tgz",
@@ -14440,11 +14489,6 @@
"strip-ansi": "4.0.0"
}
},
- "string.fromcodepoint": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz",
- "integrity": "sha1-jZeDM8C8klOPUPOD5IiPPlYZ1lM="
- },
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
diff --git a/package.json b/package.json
index 688bc0f10..8cf7d9a1a 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"android": "react-native run-android",
"storybook": "storybook start -p 7007",
"snyk-protect": "snyk protect",
- "prepare": "npm run snyk-protect"
+ "prepare": "exit 0"
},
"rnpm": {
"assets": [
@@ -27,9 +27,9 @@
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-remove-console": "^6.8.5",
"babel-polyfill": "^6.26.0",
+ "babel-preset-expo": "^4.0.0",
"deep-equal": "^1.0.1",
"ejson": "^2.1.2",
- "emoji-datasource": "^4.0.3",
"lodash": "^4.17.4",
"moment": "^2.20.1",
"prop-types": "^15.6.0",
@@ -45,11 +45,14 @@
"react-native-image-picker": "^0.26.7",
"react-native-img-cache": "^1.5.2",
"react-native-keyboard-aware-scroll-view": "^0.4.1",
+ "react-native-keyboard-input": "git+https://github.com/RocketChat/react-native-keyboard-input.git",
+ "react-native-keyboard-tracking-view": "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git",
"react-native-loading-spinner-overlay": "^0.5.2",
"react-native-meteor": "^1.2.0",
"react-native-modal": "^4.1.1",
- "react-native-optimized-flatlist": "^1.0.3",
+ "react-native-optimized-flatlist": "^1.0.4",
"react-native-push-notification": "^3.0.1",
+ "react-native-responsive-ui": "^1.1.1",
"react-native-scrollable-tab-view": "^0.8.0",
"react-native-slider": "^0.11.0",
"react-native-splash-screen": "^3.0.6",
@@ -71,7 +74,6 @@
"remote-redux-devtools": "^0.5.12",
"simple-markdown": "^0.3.1",
"snyk": "^1.61.1",
- "string.fromcodepoint": "^0.2.1",
"strip-ansi": "^4.0.0"
},
"devDependencies": {