parent
a6b525b09e
commit
0636fd0266
|
@ -158,6 +158,8 @@ dependencies {
|
||||||
compile "com.android.support:appcompat-v7:23.0.1"
|
compile "com.android.support:appcompat-v7:23.0.1"
|
||||||
compile 'com.android.support:customtabs:23.0.1'
|
compile 'com.android.support:customtabs:23.0.1'
|
||||||
compile "com.facebook.react:react-native:+" // From node_modules
|
compile "com.facebook.react:react-native:+" // From node_modules
|
||||||
|
compile 'com.facebook.fresco:fresco:1.7.1'
|
||||||
|
compile 'com.facebook.fresco:animated-gif:1.7.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run this once to be able to run the application with BUCK
|
// Run this once to be able to run the application with BUCK
|
||||||
|
|
|
@ -81,3 +81,4 @@ export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'REQUEST'
|
||||||
|
|
||||||
export const INCREMENT = 'INCREMENT';
|
export const INCREMENT = 'INCREMENT';
|
||||||
export const DECREMENT = 'DECREMENT';
|
export const DECREMENT = 'DECREMENT';
|
||||||
|
export const KEYBOARD = createRequestTypes('KEYBOARD', ['OPEN', 'CLOSE']);
|
||||||
|
|
|
@ -39,6 +39,13 @@ export function setAllPermissions(permissions) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setCustomEmojis(emojis) {
|
||||||
|
return {
|
||||||
|
type: types.SET_CUSTOM_EMOJIS,
|
||||||
|
payload: emojis
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function login() {
|
export function login() {
|
||||||
return {
|
return {
|
||||||
type: 'LOGIN'
|
type: 'LOGIN'
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
|
export function setKeyboardOpen() {
|
||||||
|
return {
|
||||||
|
type: types.KEYBOARD.OPEN
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setKeyboardClosed() {
|
||||||
|
return {
|
||||||
|
type: types.KEYBOARD.CLOSE
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
export const SET_CURRENT_SERVER = 'SET_CURRENT_SERVER';
|
export const SET_CURRENT_SERVER = 'SET_CURRENT_SERVER';
|
||||||
export const SET_ALL_SETTINGS = 'SET_ALL_SETTINGS';
|
export const SET_ALL_SETTINGS = 'SET_ALL_SETTINGS';
|
||||||
export const SET_ALL_PERMISSIONS = 'SET_ALL_PERMISSIONS';
|
export const SET_ALL_PERMISSIONS = 'SET_ALL_PERMISSIONS';
|
||||||
|
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
|
||||||
export const ADD_SETTINGS = 'ADD_SETTINGS';
|
export const ADD_SETTINGS = 'ADD_SETTINGS';
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { CachedImage } from 'react-native-img-cache';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
@connect(state => ({
|
||||||
|
baseUrl: state.settings.Site_Url
|
||||||
|
}))
|
||||||
|
export default class extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
emoji: PropTypes.object.isRequired,
|
||||||
|
style: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { baseUrl, emoji, style } = this.props;
|
||||||
|
return (
|
||||||
|
<CachedImage
|
||||||
|
style={style}
|
||||||
|
source={{ uri: `${ baseUrl }/emoji-custom/${ encodeURIComponent(emoji.content) }.${ emoji.extension }` }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
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 <CustomEmoji style={style} emoji={emoji} />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Text style={styles.categoryEmoji}>
|
||||||
|
{emoji}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { emojis } = this.props;
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<View style={styles.categoryInner}>
|
||||||
|
{emojis.map(emoji =>
|
||||||
|
(
|
||||||
|
<TouchableOpacity
|
||||||
|
activeOpacity={0.7}
|
||||||
|
key={emoji.isCustom ? emoji.content : emoji}
|
||||||
|
onPress={() => this.props.onEmojiSelected(emoji)}
|
||||||
|
>
|
||||||
|
{this.renderEmoji(emoji)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { View, TouchableOpacity, Text } from 'react-native';
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
export default class extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
goToPage: PropTypes.func,
|
||||||
|
activeTab: PropTypes.number,
|
||||||
|
tabs: PropTypes.array
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={styles.tabsContainer}>
|
||||||
|
{this.props.tabs.map((tab, i) => (
|
||||||
|
<TouchableOpacity activeOpacity={0.7} key={tab} onPress={() => this.props.goToPage(i)} style={styles.tab}>
|
||||||
|
<Text style={styles.tabEmoji}>{tab}</Text>
|
||||||
|
{this.props.activeTab === i ? <View style={styles.activeTabLine} /> : <View style={styles.tabLine} />}
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
const list = ['Frequently Used', 'Custom', 'Smileys & People', 'Animals & Nature', 'Food & Drink', 'Activities', 'Travel & Places', 'Objects', 'Symbols', 'Flags'];
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
tabLabel: '🕒',
|
||||||
|
category: list[0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabLabel: '🚀',
|
||||||
|
category: list[1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabLabel: '😃',
|
||||||
|
category: list[2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabLabel: '🐶',
|
||||||
|
category: list[3]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabLabel: '🍔',
|
||||||
|
category: list[4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabLabel: '⚽',
|
||||||
|
category: list[5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabLabel: '🚌',
|
||||||
|
category: list[6]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabLabel: '💡',
|
||||||
|
category: list[7]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabLabel: '💛',
|
||||||
|
category: list[8]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tabLabel: '🏁',
|
||||||
|
category: list[9]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
export default { list, tabs };
|
|
@ -0,0 +1,143 @@
|
||||||
|
import 'string.fromcodepoint';
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { ScrollView, View } 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 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';
|
||||||
|
|
||||||
|
const charFromUtf16 = utf16 => String.fromCodePoint(...utf16.split('-').map(u => `0x${ u }`));
|
||||||
|
const charFromEmojiObj = obj => charFromUtf16(obj.unified);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
static propTypes = {
|
||||||
|
onEmojiSelected: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
categories: categories.list.slice(0, 1),
|
||||||
|
frequentlyUsed: [],
|
||||||
|
customEmojis: []
|
||||||
|
};
|
||||||
|
this.frequentlyUsed = database.objects('frequentlyUsedEmoji').sorted('count', true);
|
||||||
|
this.customEmojis = database.objects('customEmojis');
|
||||||
|
this.updateFrequentlyUsed = this.updateFrequentlyUsed.bind(this);
|
||||||
|
this.updateCustomEmojis = this.updateCustomEmojis.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.frequentlyUsed.addListener(this.updateFrequentlyUsed);
|
||||||
|
this.customEmojis.addListener(this.updateCustomEmojis);
|
||||||
|
this.updateFrequentlyUsed();
|
||||||
|
this.updateCustomEmojis();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearTimeout(this._timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEmojiSelected(emoji) {
|
||||||
|
if (emoji.isCustom) {
|
||||||
|
const count = this._getFrequentlyUsedCount(emoji.content);
|
||||||
|
this._addFrequentlyUsed({
|
||||||
|
content: emoji.content, extension: emoji.extension, count, isCustom: true
|
||||||
|
});
|
||||||
|
this.props.onEmojiSelected(`:${ emoji.content }:`);
|
||||||
|
} else {
|
||||||
|
const content = emoji.codePointAt(0).toString();
|
||||||
|
const count = this._getFrequentlyUsedCount(content);
|
||||||
|
this._addFrequentlyUsed({ content, count, isCustom: false });
|
||||||
|
this.props.onEmojiSelected(emoji);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_addFrequentlyUsed = (emoji) => {
|
||||||
|
database.write(() => {
|
||||||
|
database.create('frequentlyUsedEmoji', emoji, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_getFrequentlyUsedCount = (content) => {
|
||||||
|
const emojiRow = this.frequentlyUsed.filtered('content == $0', content);
|
||||||
|
return emojiRow.length ? emojiRow[0].count + 1 : 1;
|
||||||
|
}
|
||||||
|
updateFrequentlyUsed() {
|
||||||
|
const frequentlyUsed = _.map(this.frequentlyUsed.slice(), (item) => {
|
||||||
|
if (item.isCustom) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
return String.fromCodePoint(item.content);
|
||||||
|
});
|
||||||
|
this.setState({ frequentlyUsed });
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCustomEmojis() {
|
||||||
|
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) {
|
||||||
|
emojis = this.state.frequentlyUsed;
|
||||||
|
} else if (i === 1) {
|
||||||
|
emojis = this.state.customEmojis;
|
||||||
|
} else {
|
||||||
|
emojis = emojisByCategory[category];
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={styles.categoryContainer}>
|
||||||
|
<EmojiCategory
|
||||||
|
key={category}
|
||||||
|
emojis={emojis}
|
||||||
|
onEmojiSelected={emoji => this.onEmojiSelected(emoji)}
|
||||||
|
finishedLoading={() => { this._timeout = setTimeout(this.loadNextCategory.bind(this), 100); }}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const scrollProps = {
|
||||||
|
keyboardShouldPersistTaps: 'always'
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<ScrollableTabView
|
||||||
|
renderTabBar={() => <TabBar />}
|
||||||
|
contentProps={scrollProps}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
_.map(categories.tabs, (tab, i) => (
|
||||||
|
<ScrollView
|
||||||
|
key={i}
|
||||||
|
tabLabel={tab.tabLabel}
|
||||||
|
{...scrollPersistTaps}
|
||||||
|
>
|
||||||
|
{this.renderCategory(tab.category, i)}
|
||||||
|
</ScrollView>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ScrollableTabView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { StyleSheet, Dimensions, Platform } from 'react-native';
|
||||||
|
|
||||||
|
const { width } = Dimensions.get('window');
|
||||||
|
const EMOJI_SIZE = width / (Platform.OS === 'ios' ? 8 : 9);
|
||||||
|
|
||||||
|
export default StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
tabsContainer: {
|
||||||
|
height: 45,
|
||||||
|
flexDirection: 'row',
|
||||||
|
paddingTop: 5
|
||||||
|
},
|
||||||
|
tab: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingBottom: 10
|
||||||
|
},
|
||||||
|
tabEmoji: {
|
||||||
|
fontSize: 20,
|
||||||
|
color: 'black'
|
||||||
|
},
|
||||||
|
activeTabLine: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 2,
|
||||||
|
backgroundColor: '#007aff',
|
||||||
|
bottom: 0
|
||||||
|
},
|
||||||
|
tabLine: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 2,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.05)',
|
||||||
|
bottom: 0
|
||||||
|
},
|
||||||
|
categoryContainer: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'flex-start'
|
||||||
|
},
|
||||||
|
categoryInner: {
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
categoryEmoji: {
|
||||||
|
fontSize: EMOJI_SIZE - 14,
|
||||||
|
color: 'black',
|
||||||
|
height: EMOJI_SIZE,
|
||||||
|
width: EMOJI_SIZE,
|
||||||
|
textAlign: 'center'
|
||||||
|
},
|
||||||
|
customCategoryEmoji: {
|
||||||
|
height: EMOJI_SIZE - 8,
|
||||||
|
width: EMOJI_SIZE - 8,
|
||||||
|
margin: 4
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, TextInput, SafeAreaView, Platform, FlatList, Text, TouchableOpacity } from 'react-native';
|
import { View, TextInput, SafeAreaView, Platform, FlatList, Text, TouchableOpacity, Keyboard } from 'react-native';
|
||||||
import Icon from 'react-native-vector-icons/Ionicons';
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||||
import ImagePicker from 'react-native-image-picker';
|
import ImagePicker from 'react-native-image-picker';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { userTyping } from '../../actions/room';
|
import { userTyping } from '../../actions/room';
|
||||||
|
@ -12,6 +12,8 @@ import MyIcon from '../icons';
|
||||||
import database from '../../lib/realm';
|
import database from '../../lib/realm';
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
import AnimatedContainer from './AnimatedContainer';
|
import AnimatedContainer from './AnimatedContainer';
|
||||||
|
import EmojiPicker from './EmojiPicker';
|
||||||
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
|
|
||||||
const MENTIONS_TRACKING_TYPE_USERS = '@';
|
const MENTIONS_TRACKING_TYPE_USERS = '@';
|
||||||
|
|
||||||
|
@ -23,7 +25,8 @@ const onlyUnique = function onlyUnique(value, index, self) {
|
||||||
room: state.room,
|
room: state.room,
|
||||||
message: state.messages.message,
|
message: state.messages.message,
|
||||||
editing: state.messages.editing,
|
editing: state.messages.editing,
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
|
isKeyboardOpen: state.keyboard.isOpen
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
editCancel: () => dispatch(editCancel()),
|
editCancel: () => dispatch(editCancel()),
|
||||||
editRequest: message => dispatch(editRequest(message)),
|
editRequest: message => dispatch(editRequest(message)),
|
||||||
|
@ -40,7 +43,8 @@ export default class MessageBox extends React.PureComponent {
|
||||||
message: PropTypes.object,
|
message: PropTypes.object,
|
||||||
editing: PropTypes.bool,
|
editing: PropTypes.bool,
|
||||||
typing: PropTypes.func,
|
typing: PropTypes.func,
|
||||||
clearInput: PropTypes.func
|
clearInput: PropTypes.func,
|
||||||
|
isKeyboardOpen: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -50,7 +54,8 @@ export default class MessageBox extends React.PureComponent {
|
||||||
messageboxHeight: 0,
|
messageboxHeight: 0,
|
||||||
text: '',
|
text: '',
|
||||||
mentions: [],
|
mentions: [],
|
||||||
showAnimatedContainer: false
|
showMentionsContainer: false,
|
||||||
|
showEmojiContainer: false
|
||||||
};
|
};
|
||||||
this.users = [];
|
this.users = [];
|
||||||
this.rooms = [];
|
this.rooms = [];
|
||||||
|
@ -61,6 +66,8 @@ export default class MessageBox extends React.PureComponent {
|
||||||
this.component.focus();
|
this.component.focus();
|
||||||
} else if (!nextProps.message) {
|
} else if (!nextProps.message) {
|
||||||
this.setState({ text: '' });
|
this.setState({ text: '' });
|
||||||
|
} else if (this.props.isKeyboardOpen !== nextProps.isKeyboardOpen && nextProps.isKeyboardOpen) {
|
||||||
|
this.closeEmoji();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,24 +102,24 @@ export default class MessageBox extends React.PureComponent {
|
||||||
if (editing) {
|
if (editing) {
|
||||||
return (<Icon
|
return (<Icon
|
||||||
style={styles.actionButtons}
|
style={styles.actionButtons}
|
||||||
name='ios-close'
|
name='close'
|
||||||
accessibilityLabel='Cancel editing'
|
accessibilityLabel='Cancel editing'
|
||||||
accessibilityTraits='button'
|
accessibilityTraits='button'
|
||||||
onPress={() => this.editCancel()}
|
onPress={() => this.editCancel()}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
return !this.state.emoji ? (<Icon
|
return !this.state.showEmojiContainer ? (<Icon
|
||||||
style={styles.actionButtons}
|
style={styles.actionButtons}
|
||||||
onPress={() => this.openEmoji()}
|
onPress={() => this.openEmoji()}
|
||||||
accessibilityLabel='Open emoji selector'
|
accessibilityLabel='Open emoji selector'
|
||||||
accessibilityTraits='button'
|
accessibilityTraits='button'
|
||||||
name='md-happy'
|
name='mood'
|
||||||
/>) : (<Icon
|
/>) : (<Icon
|
||||||
onPress={() => this.openEmoji()}
|
onPress={() => this.closeEmoji()}
|
||||||
style={styles.actionButtons}
|
style={styles.actionButtons}
|
||||||
accessibilityLabel='Close emoji selector'
|
accessibilityLabel='Close emoji selector'
|
||||||
accessibilityTraits='button'
|
accessibilityTraits='button'
|
||||||
name='md-sad'
|
name='keyboard'
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
get rightButtons() {
|
get rightButtons() {
|
||||||
|
@ -176,11 +183,16 @@ export default class MessageBox extends React.PureComponent {
|
||||||
this.props.editCancel();
|
this.props.editCancel();
|
||||||
this.setState({ text: '' });
|
this.setState({ text: '' });
|
||||||
}
|
}
|
||||||
openEmoji() {
|
async openEmoji() {
|
||||||
this.setState({ emoji: !this.state.emoji });
|
await this.setState({ showEmojiContainer: !this.state.showEmojiContainer });
|
||||||
|
Keyboard.dismiss();
|
||||||
|
}
|
||||||
|
closeEmoji() {
|
||||||
|
this.setState({ showEmojiContainer: false });
|
||||||
}
|
}
|
||||||
submit(message) {
|
submit(message) {
|
||||||
this.setState({ text: '' });
|
this.setState({ text: '' });
|
||||||
|
this.closeEmoji();
|
||||||
this.stopTrackingMention();
|
this.stopTrackingMention();
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.props.typing(false);
|
this.props.typing(false);
|
||||||
|
@ -279,7 +291,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
|
|
||||||
stopTrackingMention() {
|
stopTrackingMention() {
|
||||||
this.setState({
|
this.setState({
|
||||||
showAnimatedContainer: false,
|
showMentionsContainer: false,
|
||||||
mentions: []
|
mentions: []
|
||||||
});
|
});
|
||||||
this.users = [];
|
this.users = [];
|
||||||
|
@ -289,7 +301,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
identifyMentionKeyword(keyword, type) {
|
identifyMentionKeyword(keyword, type) {
|
||||||
this.updateMentions(keyword, type);
|
this.updateMentions(keyword, type);
|
||||||
this.setState({
|
this.setState({
|
||||||
showAnimatedContainer: true
|
showMentionsContainer: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,6 +329,22 @@ export default class MessageBox extends React.PureComponent {
|
||||||
this.component.focus();
|
this.component.focus();
|
||||||
requestAnimationFrame(() => this.stopTrackingMention());
|
requestAnimationFrame(() => this.stopTrackingMention());
|
||||||
}
|
}
|
||||||
|
_onEmojiSelected(emoji) {
|
||||||
|
const { text } = this.state;
|
||||||
|
let newText = '';
|
||||||
|
|
||||||
|
// if messagebox has an active cursor
|
||||||
|
if (this.component._lastNativeSelection) {
|
||||||
|
const { start, end } = this.component._lastNativeSelection;
|
||||||
|
const cursor = Math.max(start, end);
|
||||||
|
newText = `${ text.substr(0, cursor) }${ emoji }${ text.substr(cursor) }`;
|
||||||
|
} else {
|
||||||
|
// if messagebox doesn't have a cursor, just append selected emoji
|
||||||
|
newText = `${ text }${ emoji }`;
|
||||||
|
}
|
||||||
|
this.component.setNativeProps({ text: newText });
|
||||||
|
this.setState({ text: newText });
|
||||||
|
}
|
||||||
renderMentionItem = item => (
|
renderMentionItem = item => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.mentionItem}
|
style={styles.mentionItem}
|
||||||
|
@ -331,6 +359,15 @@ export default class MessageBox extends React.PureComponent {
|
||||||
<Text>{item.username || item.name }</Text>
|
<Text>{item.username || item.name }</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)
|
)
|
||||||
|
renderEmoji() {
|
||||||
|
const emojiContainer = (
|
||||||
|
<View style={styles.emojiContainer}>
|
||||||
|
<EmojiPicker onEmojiSelected={emoji => this._onEmojiSelected(emoji)} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
const { showEmojiContainer, messageboxHeight } = this.state;
|
||||||
|
return <AnimatedContainer visible={showEmojiContainer} subview={emojiContainer} messageboxHeight={messageboxHeight} />;
|
||||||
|
}
|
||||||
renderMentions() {
|
renderMentions() {
|
||||||
const usersList = (
|
const usersList = (
|
||||||
<FlatList
|
<FlatList
|
||||||
|
@ -338,12 +375,11 @@ export default class MessageBox extends React.PureComponent {
|
||||||
data={this.state.mentions}
|
data={this.state.mentions}
|
||||||
renderItem={({ item }) => this.renderMentionItem(item)}
|
renderItem={({ item }) => this.renderMentionItem(item)}
|
||||||
keyExtractor={item => item._id}
|
keyExtractor={item => item._id}
|
||||||
keyboardShouldPersistTaps='always'
|
{...scrollPersistTaps}
|
||||||
keyboardDismissMode='interactive'
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { showAnimatedContainer, messageboxHeight } = this.state;
|
const { showMentionsContainer, messageboxHeight } = this.state;
|
||||||
return <AnimatedContainer visible={showAnimatedContainer} subview={usersList} messageboxHeight={messageboxHeight} />;
|
return <AnimatedContainer visible={showMentionsContainer} subview={usersList} messageboxHeight={messageboxHeight} />;
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { height } = this.state;
|
const { height } = this.state;
|
||||||
|
@ -374,6 +410,7 @@ export default class MessageBox extends React.PureComponent {
|
||||||
</View>
|
</View>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
{this.renderMentions()}
|
{this.renderMentions()}
|
||||||
|
{this.renderEmoji()}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,5 +79,11 @@ export default StyleSheet.create({
|
||||||
borderBottomColor: '#ECECEC',
|
borderBottomColor: '#ECECEC',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
emojiContainer: {
|
||||||
|
height: 200,
|
||||||
|
borderTopColor: '#ECECEC',
|
||||||
|
borderTopWidth: 1,
|
||||||
|
backgroundColor: '#fff'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,32 +1,29 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, Platform } from 'react-native';
|
import { Text, StyleSheet } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import EasyMarkdown from 'react-native-easy-markdown'; // eslint-disable-line
|
import EasyMarkdown from 'react-native-easy-markdown'; // eslint-disable-line
|
||||||
import SimpleMarkdown from 'simple-markdown';
|
import SimpleMarkdown from 'simple-markdown';
|
||||||
import { emojify } from 'react-emojione';
|
import { emojify } from 'react-emojione';
|
||||||
|
import styles from './styles';
|
||||||
const codeStyle = {
|
import CustomEmoji from '../CustomEmoji';
|
||||||
...Platform.select({
|
|
||||||
ios: { fontFamily: 'Courier New' },
|
|
||||||
android: { fontFamily: 'monospace' }
|
|
||||||
}),
|
|
||||||
backgroundColor: '#f8f8f8',
|
|
||||||
borderColor: '#cccccc',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 5,
|
|
||||||
padding: 5
|
|
||||||
};
|
|
||||||
|
|
||||||
const BlockCode = ({ node, state }) => (
|
const BlockCode = ({ node, state }) => (
|
||||||
<Text
|
<Text
|
||||||
key={state.key}
|
key={state.key}
|
||||||
style={codeStyle}
|
style={styles.codeStyle}
|
||||||
>
|
>
|
||||||
{node.content}
|
{node.content}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
const mentionStyle = { color: '#13679a' };
|
const mentionStyle = { color: '#13679a' };
|
||||||
const rules = {
|
|
||||||
|
const Markdown = ({ msg, customEmojis }) => {
|
||||||
|
if (!msg) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
msg = emojify(msg, { output: 'unicode' });
|
||||||
|
|
||||||
|
const rules = {
|
||||||
username: {
|
username: {
|
||||||
order: -1,
|
order: -1,
|
||||||
match: SimpleMarkdown.inlineRegex(/^@[0-9a-zA-Z-_.]+/),
|
match: SimpleMarkdown.inlineRegex(/^@[0-9a-zA-Z-_.]+/),
|
||||||
|
@ -68,7 +65,7 @@ const rules = {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
fence: {
|
fence: {
|
||||||
order: -5,
|
order: -3,
|
||||||
match: SimpleMarkdown.blockRegex(/^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n *)+\n/),
|
match: SimpleMarkdown.blockRegex(/^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n *)+\n/),
|
||||||
parse: capture => ({
|
parse: capture => ({
|
||||||
lang: capture[2] || undefined,
|
lang: capture[2] || undefined,
|
||||||
|
@ -85,7 +82,7 @@ const rules = {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
blockCode: {
|
blockCode: {
|
||||||
order: -6,
|
order: -4,
|
||||||
match: SimpleMarkdown.blockRegex(/^(```)\s*([\s\S]*?[^`])\s*\1(?!```)/),
|
match: SimpleMarkdown.blockRegex(/^(```)\s*([\s\S]*?[^`])\s*\1(?!```)/),
|
||||||
parse: capture => ({ content: capture[2] }),
|
parse: capture => ({ content: capture[2] }),
|
||||||
react: (node, output, state) => ({
|
react: (node, output, state) => ({
|
||||||
|
@ -97,14 +94,34 @@ const rules = {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
customEmoji: {
|
||||||
|
order: -5,
|
||||||
|
match: SimpleMarkdown.inlineRegex(/^:([0-9a-zA-Z-_.]+):/),
|
||||||
|
parse: capture => ({ content: capture }),
|
||||||
|
react: (node, output, state) => {
|
||||||
|
const element = {
|
||||||
|
type: 'custom',
|
||||||
|
key: state.key,
|
||||||
|
props: {
|
||||||
|
children: <Text key={state.key}>{node.content[0]}</Text>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const content = node.content[1];
|
||||||
|
const emojiExtension = customEmojis[content];
|
||||||
|
if (emojiExtension) {
|
||||||
|
const emoji = { extension: emojiExtension, content };
|
||||||
|
const style = StyleSheet.flatten(styles.customEmoji);
|
||||||
|
element.props.children = (
|
||||||
|
<CustomEmoji key={state.key} style={style} emoji={emoji} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const Markdown = ({ msg }) => {
|
const codeStyle = StyleSheet.flatten(styles.codeStyle);
|
||||||
if (!msg) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
msg = emojify(msg, { output: 'unicode' });
|
|
||||||
return (
|
return (
|
||||||
<EasyMarkdown
|
<EasyMarkdown
|
||||||
style={{ marginBottom: 0 }}
|
style={{ marginBottom: 0 }}
|
||||||
|
@ -116,7 +133,8 @@ const Markdown = ({ msg }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
Markdown.propTypes = {
|
Markdown.propTypes = {
|
||||||
msg: PropTypes.string.isRequired
|
msg: PropTypes.string.isRequired,
|
||||||
|
customEmojis: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
BlockCode.propTypes = {
|
BlockCode.propTypes = {
|
||||||
|
|
|
@ -23,7 +23,8 @@ const flex = { flexDirection: 'row', flex: 1 };
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
message: state.messages.message,
|
message: state.messages.message,
|
||||||
editing: state.messages.editing
|
editing: state.messages.editing,
|
||||||
|
customEmojis: state.customEmojis
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
actionsShow: actionMessage => dispatch(actionsShow(actionMessage)),
|
actionsShow: actionMessage => dispatch(actionsShow(actionMessage)),
|
||||||
errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage))
|
errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage))
|
||||||
|
@ -38,7 +39,8 @@ export default class Message extends React.Component {
|
||||||
editing: PropTypes.bool,
|
editing: PropTypes.bool,
|
||||||
actionsShow: PropTypes.func,
|
actionsShow: PropTypes.func,
|
||||||
errorActionsShow: PropTypes.func,
|
errorActionsShow: PropTypes.func,
|
||||||
animate: PropTypes.bool
|
animate: PropTypes.bool,
|
||||||
|
customEmojis: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
@ -135,7 +137,8 @@ export default class Message extends React.Component {
|
||||||
if (this.isInfoMessage()) {
|
if (this.isInfoMessage()) {
|
||||||
return <Text style={styles.textInfo}>{this.getInfoMessage()}</Text>;
|
return <Text style={styles.textInfo}>{this.getInfoMessage()}</Text>;
|
||||||
}
|
}
|
||||||
return <Markdown msg={this.props.item.msg} />;
|
const { item, customEmojis, baseUrl } = this.props;
|
||||||
|
return <Markdown msg={item.msg} customEmojis={customEmojis} baseUrl={baseUrl} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUrl() {
|
renderUrl() {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet, Platform } from 'react-native';
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
content: {
|
content: {
|
||||||
|
@ -18,5 +18,20 @@ export default StyleSheet.create({
|
||||||
},
|
},
|
||||||
editing: {
|
editing: {
|
||||||
backgroundColor: '#fff5df'
|
backgroundColor: '#fff5df'
|
||||||
|
},
|
||||||
|
customEmoji: {
|
||||||
|
width: 16,
|
||||||
|
height: 16
|
||||||
|
},
|
||||||
|
codeStyle: {
|
||||||
|
...Platform.select({
|
||||||
|
ios: { fontFamily: 'Courier New' },
|
||||||
|
android: { fontFamily: 'monospace' }
|
||||||
|
}),
|
||||||
|
backgroundColor: '#f8f8f8',
|
||||||
|
borderColor: '#cccccc',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 5,
|
||||||
|
padding: 5
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -175,6 +175,37 @@ const messagesSchema = {
|
||||||
editedBy: 'messagesEditedBy'
|
editedBy: 'messagesEditedBy'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const frequentlyUsedEmojiSchema = {
|
||||||
|
name: 'frequentlyUsedEmoji',
|
||||||
|
primaryKey: 'content',
|
||||||
|
properties: {
|
||||||
|
content: { type: 'string', optional: true },
|
||||||
|
extension: { type: 'string', optional: true },
|
||||||
|
isCustom: 'bool',
|
||||||
|
count: 'int'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const customEmojiAliasesSchema = {
|
||||||
|
name: 'customEmojiAliases',
|
||||||
|
properties: {
|
||||||
|
value: 'string'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const customEmojisSchema = {
|
||||||
|
name: 'customEmojis',
|
||||||
|
primaryKey: '_id',
|
||||||
|
properties: {
|
||||||
|
_id: 'string',
|
||||||
|
name: 'string',
|
||||||
|
aliases: { type: 'list', objectType: 'customEmojiAliases' },
|
||||||
|
extension: 'string',
|
||||||
|
_updatedAt: { type: 'date', optional: true }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const schema = [
|
const schema = [
|
||||||
settingsSchema,
|
settingsSchema,
|
||||||
subscriptionSchema,
|
subscriptionSchema,
|
||||||
|
@ -187,7 +218,10 @@ const schema = [
|
||||||
messagesEditedBySchema,
|
messagesEditedBySchema,
|
||||||
permissionsSchema,
|
permissionsSchema,
|
||||||
permissionsRolesSchema,
|
permissionsRolesSchema,
|
||||||
url
|
url,
|
||||||
|
frequentlyUsedEmojiSchema,
|
||||||
|
customEmojiAliasesSchema,
|
||||||
|
customEmojisSchema
|
||||||
];
|
];
|
||||||
class DB {
|
class DB {
|
||||||
databases = {
|
databases = {
|
||||||
|
|
|
@ -83,6 +83,7 @@ const RocketChat = {
|
||||||
this.ddp.on('connected', () => {
|
this.ddp.on('connected', () => {
|
||||||
RocketChat.getSettings();
|
RocketChat.getSettings();
|
||||||
RocketChat.getPermissions();
|
RocketChat.getPermissions();
|
||||||
|
RocketChat.getCustomEmoji();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ddp.on('error', (err) => {
|
this.ddp.on('error', (err) => {
|
||||||
|
@ -513,6 +514,29 @@ const RocketChat = {
|
||||||
});
|
});
|
||||||
return permissions;
|
return permissions;
|
||||||
},
|
},
|
||||||
|
async getCustomEmoji() {
|
||||||
|
const temp = database.objects('customEmojis').sorted('_updatedAt', true)[0];
|
||||||
|
let emojis = await call('listEmojiCustom');
|
||||||
|
emojis = emojis.filter(emoji => !temp || emoji._updatedAt > temp._updatedAt);
|
||||||
|
emojis = RocketChat._prepareEmojis(emojis);
|
||||||
|
database.write(() => {
|
||||||
|
emojis.forEach(emoji => database.create('customEmojis', emoji, true));
|
||||||
|
});
|
||||||
|
reduxStore.dispatch(actions.setCustomEmojis(RocketChat.parseEmojis(emojis)));
|
||||||
|
},
|
||||||
|
parseEmojis: emojis => emojis.reduce((ret, item) => {
|
||||||
|
ret[item.name] = item.extension;
|
||||||
|
item.aliases.forEach((alias) => {
|
||||||
|
ret[alias.value] = item.extension;
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}, {}),
|
||||||
|
_prepareEmojis(emojis) {
|
||||||
|
emojis.forEach((emoji) => {
|
||||||
|
emoji.aliases = emoji.aliases.map(alias => ({ value: alias }));
|
||||||
|
});
|
||||||
|
return emojis;
|
||||||
|
},
|
||||||
deleteMessage(message) {
|
deleteMessage(message) {
|
||||||
return call('deleteMessage', { _id: message._id });
|
return call('deleteMessage', { _id: message._id });
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,7 +2,14 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ViewPropTypes } from 'react-native';
|
import { ViewPropTypes } from 'react-native';
|
||||||
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
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 {
|
export default class KeyboardView extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
style: ViewPropTypes.style,
|
style: ViewPropTypes.style,
|
||||||
|
@ -12,20 +19,23 @@ export default class KeyboardView extends React.PureComponent {
|
||||||
children: PropTypes.oneOfType([
|
children: PropTypes.oneOfType([
|
||||||
PropTypes.arrayOf(PropTypes.node),
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
PropTypes.node
|
PropTypes.node
|
||||||
])
|
]),
|
||||||
|
setKeyboardOpen: PropTypes.func,
|
||||||
|
setKeyboardClosed: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<KeyboardAwareScrollView
|
<KeyboardAwareScrollView
|
||||||
keyboardDismissMode='interactive'
|
{...scrollPersistTaps}
|
||||||
keyboardShouldPersistTaps='always'
|
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
contentContainerStyle={this.props.contentContainerStyle}
|
contentContainerStyle={this.props.contentContainerStyle}
|
||||||
scrollEnabled={this.props.scrollEnabled}
|
scrollEnabled={this.props.scrollEnabled}
|
||||||
alwaysBounceVertical={false}
|
alwaysBounceVertical={false}
|
||||||
extraHeight={this.props.keyboardVerticalOffset}
|
extraHeight={this.props.keyboardVerticalOffset}
|
||||||
behavior='position'
|
behavior='position'
|
||||||
|
onKeyboardWillShow={() => this.props.setKeyboardOpen()}
|
||||||
|
onKeyboardWillHide={() => this.props.setKeyboardClosed()}
|
||||||
>
|
>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</KeyboardAwareScrollView>
|
</KeyboardAwareScrollView>
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import * as types from '../constants/types';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
customEmojis: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default function customEmojis(state = initialState.customEmojis, action) {
|
||||||
|
if (action.type === types.SET_CUSTOM_EMOJIS) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...action.payload
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
|
@ -10,8 +10,23 @@ import navigator from './navigator';
|
||||||
import createChannel from './createChannel';
|
import createChannel from './createChannel';
|
||||||
import app from './app';
|
import app from './app';
|
||||||
import permissions from './permissions';
|
import permissions from './permissions';
|
||||||
|
import customEmojis from './customEmojis';
|
||||||
import activeUsers from './activeUsers';
|
import activeUsers from './activeUsers';
|
||||||
|
import keyboard from './keyboard';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
settings, login, meteor, messages, server, navigator, createChannel, app, room, rooms, permissions, activeUsers
|
settings,
|
||||||
|
login,
|
||||||
|
meteor,
|
||||||
|
messages,
|
||||||
|
server,
|
||||||
|
navigator,
|
||||||
|
createChannel,
|
||||||
|
app,
|
||||||
|
room,
|
||||||
|
rooms,
|
||||||
|
permissions,
|
||||||
|
customEmojis,
|
||||||
|
activeUsers,
|
||||||
|
keyboard
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,8 @@ const restore = function* restore() {
|
||||||
yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length))));
|
yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length))));
|
||||||
const permissions = database.objects('permissions');
|
const permissions = database.objects('permissions');
|
||||||
yield put(actions.setAllPermissions(RocketChat.parsePermissions(permissions.slice(0, permissions.length))));
|
yield put(actions.setAllPermissions(RocketChat.parsePermissions(permissions.slice(0, permissions.length))));
|
||||||
|
const emojis = database.objects('customEmojis');
|
||||||
|
yield put(actions.setCustomEmojis(RocketChat.parseEmojis(emojis.slice(0, emojis.length))));
|
||||||
}
|
}
|
||||||
yield put(actions.appReady({}));
|
yield put(actions.appReady({}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
keyboardShouldPersistTaps: 'always',
|
||||||
|
keyboardDismissMode: 'interactive'
|
||||||
|
};
|
|
@ -8,6 +8,7 @@ import * as loginActions from '../actions/login';
|
||||||
import KeyboardView from '../presentation/KeyboardView';
|
import KeyboardView from '../presentation/KeyboardView';
|
||||||
|
|
||||||
import styles from './Styles';
|
import styles from './Styles';
|
||||||
|
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||||
import { showToast } from '../utils/info';
|
import { showToast } from '../utils/info';
|
||||||
|
|
||||||
class LoginView extends React.Component {
|
class LoginView extends React.Component {
|
||||||
|
@ -87,8 +88,7 @@ class LoginView extends React.Component {
|
||||||
>
|
>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.loginView}
|
style={styles.loginView}
|
||||||
keyboardDismissMode='interactive'
|
{...scrollPersistTaps}
|
||||||
keyboardShouldPersistTaps='always'
|
|
||||||
>
|
>
|
||||||
<SafeAreaView>
|
<SafeAreaView>
|
||||||
<View style={styles.formContainer}>
|
<View style={styles.formContainer}>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { connect } from 'react-redux';
|
||||||
import { serverRequest, addServer } from '../actions/server';
|
import { serverRequest, addServer } from '../actions/server';
|
||||||
import KeyboardView from '../presentation/KeyboardView';
|
import KeyboardView from '../presentation/KeyboardView';
|
||||||
import styles from './Styles';
|
import styles from './Styles';
|
||||||
|
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
validInstance: !state.server.failure && !state.server.connecting,
|
validInstance: !state.server.failure && !state.server.connecting,
|
||||||
|
@ -115,8 +116,7 @@ export default class NewServerView extends React.Component {
|
||||||
>
|
>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.loginView}
|
style={styles.loginView}
|
||||||
keyboardDismissMode='interactive'
|
{...scrollPersistTaps}
|
||||||
keyboardShouldPersistTaps='always'
|
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={ref => this.inputElement = ref}
|
ref={ref => this.inputElement = ref}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import Banner from './banner';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
|
|
||||||
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
|
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
|
||||||
|
|
||||||
|
@ -187,8 +188,7 @@ export default class RoomView extends React.Component {
|
||||||
dataSource={this.state.dataSource}
|
dataSource={this.state.dataSource}
|
||||||
renderRow={item => this.renderItem(item)}
|
renderRow={item => this.renderItem(item)}
|
||||||
initialListSize={10}
|
initialListSize={10}
|
||||||
keyboardShouldPersistTaps='always'
|
{...scrollPersistTaps}
|
||||||
keyboardDismissMode='interactive'
|
|
||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
{this.renderFooter()}
|
{this.renderFooter()}
|
||||||
|
|
|
@ -4610,6 +4610,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/email-validator/-/email-validator-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/email-validator/-/email-validator-1.1.1.tgz",
|
||||||
"integrity": "sha512-vkcJJZEb7JXDY883Nx1Lkmb6noM3j1SfSt8L9tVFhZPnPQiFq+Nkd5evc77+tRVS4ChTUSr34voThsglI/ja/A=="
|
"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": {
|
"emoji-regex": {
|
||||||
"version": "6.5.1",
|
"version": "6.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
|
||||||
|
@ -12600,6 +12605,16 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-native-push-notification/-/react-native-push-notification-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-push-notification/-/react-native-push-notification-3.0.1.tgz",
|
||||||
"integrity": "sha1-DiPbMC0Du0o/KNwHLcryqaEXjtg="
|
"integrity": "sha1-DiPbMC0Du0o/KNwHLcryqaEXjtg="
|
||||||
},
|
},
|
||||||
|
"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",
|
||||||
|
"integrity": "sha512-8Q7v4f1WyV5cKqvV3QHxnLFRWV8gi24JW2T+Cfx++b3ctHxtJCkGg5Zs15ufYMxaN4W68iDkJrftVVAq0tqb8w==",
|
||||||
|
"requires": {
|
||||||
|
"create-react-class": "15.6.2",
|
||||||
|
"prop-types": "15.6.0",
|
||||||
|
"react-timer-mixin": "0.13.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-native-slider": {
|
"react-native-slider": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-slider/-/react-native-slider-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-slider/-/react-native-slider-0.11.0.tgz",
|
||||||
|
@ -14425,6 +14440,11 @@
|
||||||
"strip-ansi": "4.0.0"
|
"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": {
|
"string_decoder": {
|
||||||
"version": "0.10.31",
|
"version": "0.10.31",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
"babel-polyfill": "^6.26.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
"deep-equal": "^1.0.1",
|
"deep-equal": "^1.0.1",
|
||||||
"ejson": "^2.1.2",
|
"ejson": "^2.1.2",
|
||||||
|
"emoji-datasource": "^4.0.3",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
"moment": "^2.20.1",
|
"moment": "^2.20.1",
|
||||||
"prop-types": "^15.6.0",
|
"prop-types": "^15.6.0",
|
||||||
"react": "^16.2.0",
|
"react": "^16.2.0",
|
||||||
|
@ -48,6 +50,7 @@
|
||||||
"react-native-modal": "^4.1.1",
|
"react-native-modal": "^4.1.1",
|
||||||
"react-native-optimized-flatlist": "^1.0.3",
|
"react-native-optimized-flatlist": "^1.0.3",
|
||||||
"react-native-push-notification": "^3.0.1",
|
"react-native-push-notification": "^3.0.1",
|
||||||
|
"react-native-scrollable-tab-view": "^0.8.0",
|
||||||
"react-native-slider": "^0.11.0",
|
"react-native-slider": "^0.11.0",
|
||||||
"react-native-splash-screen": "^3.0.6",
|
"react-native-splash-screen": "^3.0.6",
|
||||||
"react-native-svg": "^6.0.0",
|
"react-native-svg": "^6.0.0",
|
||||||
|
@ -68,6 +71,7 @@
|
||||||
"remote-redux-devtools": "^0.5.12",
|
"remote-redux-devtools": "^0.5.12",
|
||||||
"simple-markdown": "^0.3.1",
|
"simple-markdown": "^0.3.1",
|
||||||
"snyk": "^1.61.1",
|
"snyk": "^1.61.1",
|
||||||
|
"string.fromcodepoint": "^0.2.1",
|
||||||
"strip-ansi": "^4.0.0"
|
"strip-ansi": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
Loading…
Reference in New Issue