Merge remote-tracking branch 'origin/develop' into add-lastmessage

This commit is contained in:
Guilherme Gazzo 2018-02-08 14:38:19 -02:00
commit d01c6c0ffd
No known key found for this signature in database
GPG Key ID: 1F85C9AD922D0829
60 changed files with 3987 additions and 546 deletions

View File

@ -203,7 +203,7 @@ jobs:
name: Fastlane Tesflight Upload name: Fastlane Tesflight Upload
command: | command: |
cd ios cd ios
fastlane pilot upload --changelog "$(sh ../.circleci/changelog.sh)" fastlane pilot upload --ipa ios/RocketChatRN.ipa --changelog "$(sh ../.circleci/changelog.sh)"
workflows: workflows:
version: 2 version: 2

View File

@ -144,6 +144,7 @@ android {
} }
dependencies { dependencies {
compile project(":reactnativekeyboardinput")
compile project(':react-native-splash-screen') compile project(':react-native-splash-screen')
compile project(':react-native-video') compile project(':react-native-video')
compile project(':react-native-push-notification') compile project(':react-native-push-notification')

View File

@ -9,12 +9,12 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE"/>
<permission <permission
android:name="${applicationId}.permission.C2D_MESSAGE" android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" /> android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" /> <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-sdk <uses-sdk
android:minSdkVersion="16" android:minSdkVersion="16"

View File

@ -16,6 +16,7 @@ import com.facebook.soloader.SoLoader;
import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage; import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage;
import com.brentvatne.react.ReactVideoPackage; import com.brentvatne.react.ReactVideoPackage;
import com.remobile.toast.RCTToastPackage; import com.remobile.toast.RCTToastPackage;
import com.wix.reactnativekeyboardinput.KeyboardInputPackage;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -42,7 +43,8 @@ public class MainApplication extends Application implements ReactApplication {
new ReactNativePushNotificationPackage(), new ReactNativePushNotificationPackage(),
new ReactVideoPackage(), new ReactVideoPackage(),
new SplashScreenReactPackage(), new SplashScreenReactPackage(),
new RCTToastPackage() new RCTToastPackage(),
new KeyboardInputPackage(MainApplication.this)
); );
} }
}; };

View File

@ -3,6 +3,7 @@
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="android:colorEdgeEffect">#aaaaaa</item>
</style> </style>
</resources> </resources>

View File

@ -18,3 +18,4 @@
# org.gradle.parallel=true # org.gradle.parallel=true
android.useDeprecatedNdk=true android.useDeprecatedNdk=true
# VERSIONCODE=999999999

View File

@ -1,4 +1,6 @@
rootProject.name = 'RocketChatRN' 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' include ':react-native-splash-screen'
project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android') project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android')
include ':react-native-video' include ':react-native-video'

View File

@ -28,7 +28,17 @@ export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [
]); ]);
export const USER = createRequestTypes('USER', ['SET']); export const USER = createRequestTypes('USER', ['SET']);
export const ROOMS = createRequestTypes('ROOMS', [...defaultTypes, 'SET_SEARCH']); 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 APP = createRequestTypes('APP', ['READY', 'INIT']);
export const MESSAGES = createRequestTypes('MESSAGES', [ export const MESSAGES = createRequestTypes('MESSAGES', [
...defaultTypes, ...defaultTypes,
@ -55,7 +65,8 @@ export const MESSAGES = createRequestTypes('MESSAGES', [
'TOGGLE_PIN_SUCCESS', 'TOGGLE_PIN_SUCCESS',
'TOGGLE_PIN_FAILURE', 'TOGGLE_PIN_FAILURE',
'SET_INPUT', 'SET_INPUT',
'CLEAR_INPUT' 'CLEAR_INPUT',
'TOGGLE_REACTION_PICKER'
]); ]);
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [ export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [
...defaultTypes, ...defaultTypes,
@ -81,4 +92,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']);

View File

@ -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
};
}

View File

@ -176,3 +176,10 @@ export function clearInput() {
type: types.MESSAGES.CLEAR_INPUT type: types.MESSAGES.CLEAR_INPUT
}; };
} }
export function toggleReactionPicker(message) {
return {
type: types.MESSAGES.TOGGLE_REACTION_PICKER,
message
};
}

View File

@ -55,3 +55,9 @@ export function setLastOpen(date = new Date()) {
date date
}; };
} }
export function layoutAnimation() {
return {
type: types.ROOM.LAYOUT_ANIMATION
};
}

View File

@ -42,7 +42,8 @@ export default class Panel extends React.Component {
this.state.animation, this.state.animation,
{ {
toValue: finalValue, toValue: finalValue,
duration: 150 duration: 150,
useNativeDriver: true
} }
).start(); ).start();
} }

View File

@ -29,7 +29,8 @@ export default class Fade extends React.Component {
} }
Animated.timing(this._visibility, { Animated.timing(this._visibility, {
toValue: nextProps.visible ? 1 : 0, toValue: nextProps.visible ? 1 : 0,
duration: 300 duration: 300,
useNativeDriver: true
}).start(() => { }).start(() => {
this.setState({ visible: nextProps.visible }); this.setState({ visible: nextProps.visible });
}); });

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; 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 { CachedImage } from 'react-native-img-cache';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor'; import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
@ -23,8 +23,16 @@ const styles = StyleSheet.create({
@connect(state => ({ @connect(state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
})) }))
export default class Avatar extends React.PureComponent {
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() { render() {
const { const {
text = '', size = 25, baseUrl, borderRadius = 4, style, avatar, type = 'd' 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;

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { ViewPropTypes } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { CachedImage } from 'react-native-img-cache'; import { CachedImage } from 'react-native-img-cache';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -6,19 +7,21 @@ import { connect } from 'react-redux';
@connect(state => ({ @connect(state => ({
baseUrl: state.settings.Site_Url baseUrl: state.settings.Site_Url
})) }))
export default class extends React.PureComponent { export default class CustomEmoji extends React.Component {
static propTypes = { static propTypes = {
baseUrl: PropTypes.string.isRequired, baseUrl: PropTypes.string.isRequired,
emoji: PropTypes.object.isRequired, emoji: PropTypes.object.isRequired,
style: PropTypes.object style: ViewPropTypes.style
}
shouldComponentUpdate() {
return false;
} }
render() { render() {
const { baseUrl, emoji, style } = this.props; const { baseUrl, emoji, style } = this.props;
return ( return (
<CachedImage <CachedImage
style={style} style={style}
source={{ uri: `${ baseUrl }/emoji-custom/${ encodeURIComponent(emoji.content) }.${ emoji.extension }` }} source={{ uri: `${ baseUrl }/emoji-custom/${ encodeURIComponent(emoji.content || emoji.name) }.${ emoji.extension }` }}
/> />
); );
} }

View File

@ -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 <CustomEmoji style={[styles.customCategoryEmoji, { height: size - 8, width: size - 8 }]} emoji={emoji} />;
}
return (
<Text style={[styles.categoryEmoji, { height: size, width: size, fontSize: size - 14 }]}>
{emojify(`:${ emoji }:`, { output: 'unicode' })}
</Text>
);
};
@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 (
<TouchableOpacity
activeOpacity={0.7}
key={emoji.isCustom ? emoji.content : emoji}
onPress={() => this.props.onEmojiSelected(emoji)}
>
{renderEmoji(emoji, size)}
</TouchableOpacity>);
}
render() {
return (
<OptimizedFlatList
keyExtractor={item => (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}
/>
);
}
}

View File

@ -3,19 +3,25 @@ import PropTypes from 'prop-types';
import { View, TouchableOpacity, Text } from 'react-native'; import { View, TouchableOpacity, Text } from 'react-native';
import styles from './styles'; import styles from './styles';
export default class extends React.PureComponent { export default class TabBar extends React.PureComponent {
static propTypes = { static propTypes = {
goToPage: PropTypes.func, goToPage: PropTypes.func,
activeTab: PropTypes.number, activeTab: PropTypes.number,
tabs: PropTypes.array tabs: PropTypes.array,
tabEmojiStyle: PropTypes.object
} }
render() { render() {
return ( return (
<View style={styles.tabsContainer}> <View style={styles.tabsContainer}>
{this.props.tabs.map((tab, i) => ( {this.props.tabs.map((tab, i) => (
<TouchableOpacity activeOpacity={0.7} key={tab} onPress={() => this.props.goToPage(i)} style={styles.tab}> <TouchableOpacity
<Text style={styles.tabEmoji}>{tab}</Text> activeOpacity={0.7}
key={tab}
onPress={() => this.props.goToPage(i)}
style={styles.tab}
>
<Text style={[styles.tabEmoji, this.props.tabEmojiStyle]}>{tab}</Text>
{this.props.activeTab === i ? <View style={styles.activeTabLine} /> : <View style={styles.tabLine} />} {this.props.activeTab === i ? <View style={styles.activeTabLine} /> : <View style={styles.tabLine} />}
</TouchableOpacity> </TouchableOpacity>
))} ))}

View File

@ -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 = [ const tabs = [
{ {
tabLabel: '🕒', tabLabel: '🕒',

View File

@ -1,35 +1,32 @@
import 'string.fromcodepoint'; import React, { Component } from 'react';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types'; 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 ScrollableTabView from 'react-native-scrollable-tab-view';
import emojiDatasource from 'emoji-datasource/emoji.json';
import _ from 'lodash'; import _ from 'lodash';
import { groupBy, orderBy } from 'lodash/collection'; import { emojify } from 'react-emojione';
import { mapValues } from 'lodash/object';
import TabBar from './TabBar'; import TabBar from './TabBar';
import EmojiCategory from './EmojiCategory'; import EmojiCategory from './EmojiCategory';
import styles from './styles'; import styles from './styles';
import categories from './categories'; 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 scrollProps = {
const charFromEmojiObj = obj => charFromUtf16(obj.unified); keyboardShouldPersistTaps: 'always',
keyboardDismissMode: 'none'
};
const filteredEmojis = emojiDatasource.filter(e => parseFloat(e.added_in) < 10.0); export default class EmojiPicker extends Component {
const groupedAndSorted = groupBy(orderBy(filteredEmojis, 'sort_order'), 'category');
const emojisByCategory = mapValues(groupedAndSorted, group => group.map(charFromEmojiObj));
export default class extends PureComponent {
static propTypes = { static propTypes = {
onEmojiSelected: PropTypes.func onEmojiSelected: PropTypes.func,
tabEmojiStyle: PropTypes.object,
emojisPerRow: PropTypes.number,
width: PropTypes.number
}; };
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
categories: categories.list.slice(0, 1),
frequentlyUsed: [], frequentlyUsed: [],
customEmojis: [] customEmojis: []
}; };
@ -38,6 +35,10 @@ export default class extends PureComponent {
this.updateFrequentlyUsed = this.updateFrequentlyUsed.bind(this); this.updateFrequentlyUsed = this.updateFrequentlyUsed.bind(this);
this.updateCustomEmojis = this.updateCustomEmojis.bind(this); this.updateCustomEmojis = this.updateCustomEmojis.bind(this);
} }
//
// shouldComponentUpdate(nextProps) {
// return false;
// }
componentWillMount() { componentWillMount() {
this.frequentlyUsed.addListener(this.updateFrequentlyUsed); this.frequentlyUsed.addListener(this.updateFrequentlyUsed);
@ -45,9 +46,12 @@ export default class extends PureComponent {
this.updateFrequentlyUsed(); this.updateFrequentlyUsed();
this.updateCustomEmojis(); this.updateCustomEmojis();
} }
componentDidMount() {
requestAnimationFrame(() => this.setState({ show: true }));
}
componentWillUnmount() { componentWillUnmount() {
clearTimeout(this._timeout); this.frequentlyUsed.removeAllListeners();
this.customEmojis.removeAllListeners();
} }
onEmojiSelected(emoji) { onEmojiSelected(emoji) {
@ -58,10 +62,11 @@ export default class extends PureComponent {
}); });
this.props.onEmojiSelected(`:${ emoji.content }:`); this.props.onEmojiSelected(`:${ emoji.content }:`);
} else { } else {
const content = emoji.codePointAt(0).toString(); const content = emoji;
const count = this._getFrequentlyUsedCount(content); const count = this._getFrequentlyUsedCount(content);
this._addFrequentlyUsed({ content, count, isCustom: false }); this._addFrequentlyUsed({ content, count, isCustom: false });
this.props.onEmojiSelected(emoji); const shortname = `:${ emoji }:`;
this.props.onEmojiSelected(emojify(shortname, { output: 'unicode' }), shortname);
} }
} }
_addFrequentlyUsed = (emoji) => { _addFrequentlyUsed = (emoji) => {
@ -78,22 +83,17 @@ export default class extends PureComponent {
if (item.isCustom) { if (item.isCustom) {
return item; return item;
} }
return String.fromCodePoint(item.content); return emojify(`${ item.content }`, { output: 'unicode' });
}); });
this.setState({ frequentlyUsed }); this.setState({ frequentlyUsed });
} }
updateCustomEmojis() { 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 }); 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) { renderCategory(category, i) {
let emojis = []; let emojis = [];
if (i === 0) { if (i === 0) {
@ -104,40 +104,40 @@ export default class extends PureComponent {
emojis = emojisByCategory[category]; emojis = emojisByCategory[category];
} }
return ( return (
<View style={styles.categoryContainer}>
<EmojiCategory <EmojiCategory
key={category}
emojis={emojis} emojis={emojis}
onEmojiSelected={emoji => this.onEmojiSelected(emoji)} onEmojiSelected={emoji => this.onEmojiSelected(emoji)}
finishedLoading={() => { this._timeout = setTimeout(this.loadNextCategory.bind(this), 100); }} style={styles.categoryContainer}
size={this.props.emojisPerRow}
width={this.props.width}
/> />
</View>
); );
} }
render() { render() {
const scrollProps = { if (!this.state.show) {
keyboardShouldPersistTaps: 'always' return null;
}; }
return ( return (
<View style={styles.container}> // <View style={styles.container}>
<ScrollableTabView <ScrollableTabView
renderTabBar={() => <TabBar />} renderTabBar={() => <TabBar tabEmojiStyle={this.props.tabEmojiStyle} />}
contentProps={scrollProps} contentProps={scrollProps}
// prerenderingSiblingsNumber={1}
> >
{ {
_.map(categories.tabs, (tab, i) => ( categories.tabs.map((tab, i) => (
<ScrollView <ScrollView
key={i} key={tab.category}
tabLabel={tab.tabLabel} tabLabel={tab.tabLabel}
{...scrollPersistTaps} {...scrollProps}
> >
{this.renderCategory(tab.category, i)} {this.renderCategory(tab.category, i)}
</ScrollView> </ScrollView>
)) ))
} }
</ScrollableTabView> </ScrollableTabView>
</View> // </View>
); );
} }
} }

View File

@ -1,7 +1,4 @@
import { StyleSheet, Dimensions, Platform } from 'react-native'; import { StyleSheet } from 'react-native';
const { width } = Dimensions.get('window');
const EMOJI_SIZE = width / (Platform.OS === 'ios' ? 8 : 9);
export default StyleSheet.create({ export default StyleSheet.create({
container: { container: {
@ -45,18 +42,16 @@ export default StyleSheet.create({
categoryInner: { categoryInner: {
flexWrap: 'wrap', flexWrap: 'wrap',
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center' alignItems: 'center',
justifyContent: 'flex-start',
flex: 1
}, },
categoryEmoji: { categoryEmoji: {
fontSize: EMOJI_SIZE - 14,
color: 'black', color: 'black',
height: EMOJI_SIZE, backgroundColor: 'transparent',
width: EMOJI_SIZE,
textAlign: 'center' textAlign: 'center'
}, },
customCategoryEmoji: { customCategoryEmoji: {
height: EMOJI_SIZE - 8,
width: EMOJI_SIZE - 8,
margin: 4 margin: 4
} }
}); });

View File

@ -33,7 +33,7 @@ const styles = StyleSheet.create({
} }
}); });
export default class extends React.PureComponent { export default class Header extends React.PureComponent {
static propTypes = { static propTypes = {
subview: PropTypes.object.isRequired subview: PropTypes.object.isRequired
} }

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; 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 { connect } from 'react-redux';
import ActionSheet from 'react-native-actionsheet'; import ActionSheet from 'react-native-actionsheet';
import * as moment from 'moment'; import * as moment from 'moment';
@ -13,7 +13,8 @@ import {
permalinkClear, permalinkClear,
togglePinRequest, togglePinRequest,
setInput, setInput,
actionsHide actionsHide,
toggleReactionPicker
} from '../actions/messages'; } from '../actions/messages';
import { showToast } from '../utils/info'; import { showToast } from '../utils/info';
@ -39,7 +40,8 @@ import { showToast } from '../utils/info';
permalinkRequest: message => dispatch(permalinkRequest(message)), permalinkRequest: message => dispatch(permalinkRequest(message)),
permalinkClear: () => dispatch(permalinkClear()), permalinkClear: () => dispatch(permalinkClear()),
togglePinRequest: message => dispatch(togglePinRequest(message)), 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 { export default class MessageActions extends React.Component {
@ -58,6 +60,7 @@ export default class MessageActions extends React.Component {
togglePinRequest: PropTypes.func.isRequired, togglePinRequest: PropTypes.func.isRequired,
setInput: PropTypes.func.isRequired, setInput: PropTypes.func.isRequired,
permalink: PropTypes.string, permalink: PropTypes.string,
toggleReactionPicker: PropTypes.func.isRequired,
Message_AllowDeleting: PropTypes.bool, Message_AllowDeleting: PropTypes.bool,
Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number, Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number,
Message_AllowEditing: PropTypes.bool, Message_AllowEditing: PropTypes.bool,
@ -104,6 +107,9 @@ export default class MessageActions extends React.Component {
// Copy // Copy
this.options.push('Copy Message'); this.options.push('Copy Message');
this.COPY_INDEX = this.options.length - 1; this.COPY_INDEX = this.options.length - 1;
// Share
this.options.push('Share Message');
this.SHARE_INDEX = this.options.length - 1;
// Quote // Quote
if (!this.isRoomReadOnly()) { if (!this.isRoomReadOnly()) {
this.options.push('Quote'); this.options.push('Quote');
@ -119,6 +125,11 @@ export default class MessageActions extends React.Component {
this.options.push(actionMessage.pinned ? 'Unpin' : 'Pin'); this.options.push(actionMessage.pinned ? 'Unpin' : 'Pin');
this.PIN_INDEX = this.options.length - 1; this.PIN_INDEX = this.options.length - 1;
} }
// Reaction
if (!this.isRoomReadOnly()) {
this.options.push('Add Reaction');
this.REACTION_INDEX = this.options.length - 1;
}
// Delete // Delete
if (this.allowDelete(nextProps)) { if (this.allowDelete(nextProps)) {
this.options.push('Delete'); this.options.push('Delete');
@ -126,6 +137,7 @@ export default class MessageActions extends React.Component {
} }
setTimeout(() => { setTimeout(() => {
this.ActionSheet.show(); this.ActionSheet.show();
Vibration.vibrate(50);
}); });
} else if (this.props.permalink !== nextProps.permalink && nextProps.permalink) { } else if (this.props.permalink !== nextProps.permalink && nextProps.permalink) {
// copy permalink // copy permalink
@ -251,6 +263,12 @@ export default class MessageActions extends React.Component {
showToast('Copied to clipboard!'); showToast('Copied to clipboard!');
} }
handleShare = async() => {
Share.share({
message: this.props.actionMessage.msg.content.replace(/<(?:.|\n)*?>/gm, '')
});
};
handleStar() { handleStar() {
this.props.toggleStarRequest(this.props.actionMessage); this.props.toggleStarRequest(this.props.actionMessage);
} }
@ -274,6 +292,10 @@ export default class MessageActions extends React.Component {
this.props.permalinkRequest(this.props.actionMessage); this.props.permalinkRequest(this.props.actionMessage);
} }
handleReaction() {
this.props.toggleReactionPicker(this.props.actionMessage);
}
handleActionPress = (actionIndex) => { handleActionPress = (actionIndex) => {
switch (actionIndex) { switch (actionIndex) {
case this.REPLY_INDEX: case this.REPLY_INDEX:
@ -288,6 +310,9 @@ export default class MessageActions extends React.Component {
case this.COPY_INDEX: case this.COPY_INDEX:
this.handleCopy(); this.handleCopy();
break; break;
case this.SHARE_INDEX:
this.handleShare();
break;
case this.QUOTE_INDEX: case this.QUOTE_INDEX:
this.handleQuote(); this.handleQuote();
break; break;
@ -297,6 +322,9 @@ export default class MessageActions extends React.Component {
case this.PIN_INDEX: case this.PIN_INDEX:
this.handlePin(); this.handlePin();
break; break;
case this.REACTION_INDEX:
this.handleReaction();
break;
case this.DELETE_INDEX: case this.DELETE_INDEX:
this.handleDelete(); this.handleDelete();
break; break;

View File

@ -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 (
<Animated.View
style={{
position: 'absolute',
left: 0,
right: 0,
bottom: -200,
zIndex: 1,
transform: [{ translateY: bottom }]
}}
>
{this.props.subview}
</Animated.View>
);
}
}

View File

@ -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 (
<Provider store={store}>
<View style={styles.emojiKeyboardContainer}>
<EmojiPicker onEmojiSelected={emoji => this.onEmojiSelected(emoji)} />
</View>
</Provider>
);
}
}
KeyboardRegistry.registerKeyboard('EmojiKeyboard', () => EmojiKeyboard);

View File

@ -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 <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>
);
}
}

View File

@ -1,21 +1,24 @@
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, Keyboard } from 'react-native'; import { View, TextInput, SafeAreaView, FlatList, Text, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons'; 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 { emojify } from 'react-emojione';
import { KeyboardAccessoryView } from 'react-native-keyboard-input';
import { userTyping, layoutAnimation } from '../../actions/room';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import { editRequest, editCancel, clearInput } from '../../actions/messages'; import { editRequest, editCancel, clearInput } from '../../actions/messages';
import styles from './style'; import styles from './styles';
import MyIcon from '../icons'; 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 CustomEmoji from '../EmojiPicker/CustomEmoji';
import EmojiPicker from './EmojiPicker'; import { emojis } from '../../emojis';
import scrollPersistTaps from '../../utils/scrollPersistTaps'; import './EmojiKeyboard';
const MENTIONS_TRACKING_TYPE_USERS = '@'; const MENTIONS_TRACKING_TYPE_USERS = '@';
const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
const onlyUnique = function onlyUnique(value, index, self) { const onlyUnique = function onlyUnique(value, index, self) {
return self.indexOf(({ _id }) => value._id === _id) === index; return self.indexOf(({ _id }) => value._id === _id) === index;
@ -25,13 +28,13 @@ 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)),
typing: status => dispatch(userTyping(status)), typing: status => dispatch(userTyping(status)),
clearInput: () => dispatch(clearInput()) clearInput: () => dispatch(clearInput()),
layoutAnimation: () => dispatch(layoutAnimation())
})) }))
export default class MessageBox extends React.PureComponent { export default class MessageBox extends React.PureComponent {
static propTypes = { static propTypes = {
@ -44,21 +47,23 @@ export default class MessageBox extends React.PureComponent {
editing: PropTypes.bool, editing: PropTypes.bool,
typing: PropTypes.func, typing: PropTypes.func,
clearInput: PropTypes.func, clearInput: PropTypes.func,
isKeyboardOpen: PropTypes.bool layoutAnimation: PropTypes.func
} }
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
height: 20,
messageboxHeight: 0,
text: '', text: '',
mentions: [], mentions: [],
showMentionsContainer: false, showMentionsContainer: false,
showEmojiContainer: false showEmojiKeyboard: false,
trackingType: ''
}; };
this.users = []; this.users = [];
this.rooms = []; this.rooms = [];
this.emojis = [];
this.customEmojis = [];
this._onEmojiSelected = this._onEmojiSelected.bind(this);
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (this.props.message !== nextProps.message && nextProps.message.msg) { if (this.props.message !== nextProps.message && nextProps.message.msg) {
@ -66,22 +71,22 @@ 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();
} }
} }
onChange() { onChangeText(text) {
this.setState({ text });
requestAnimationFrame(() => { requestAnimationFrame(() => {
const { start, end } = this.component._lastNativeSelection; const { start, end } = this.component._lastNativeSelection;
const cursor = Math.max(start, end); 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) { if (!result) {
return this.stopTrackingMention(); return this.stopTrackingMention();
@ -92,9 +97,8 @@ export default class MessageBox extends React.PureComponent {
}); });
} }
onKeyboardResigned() {
onChangeText(text) { this.closeEmoji();
this.setState({ text });
} }
get leftButtons() { get leftButtons() {
@ -108,7 +112,7 @@ export default class MessageBox extends React.PureComponent {
onPress={() => this.editCancel()} onPress={() => this.editCancel()}
/>); />);
} }
return !this.state.showEmojiContainer ? (<Icon return !this.state.showEmojiKeyboard ? (<Icon
style={styles.actionButtons} style={styles.actionButtons}
onPress={() => this.openEmoji()} onPress={() => this.openEmoji()}
accessibilityLabel='Open emoji selector' accessibilityLabel='Open emoji selector'
@ -147,10 +151,6 @@ export default class MessageBox extends React.PureComponent {
return icons; return icons;
} }
updateSize = (height) => {
this.setState({ height: height + (Platform.OS === 'ios' ? 0 : 0) });
}
addFile = () => { addFile = () => {
const options = { const options = {
maxHeight: 1960, maxHeight: 1960,
@ -184,11 +184,12 @@ export default class MessageBox extends React.PureComponent {
this.setState({ text: '' }); this.setState({ text: '' });
} }
async openEmoji() { async openEmoji() {
await this.setState({ showEmojiContainer: !this.state.showEmojiContainer }); await this.setState({
Keyboard.dismiss(); showEmojiKeyboard: true
});
} }
closeEmoji() { closeEmoji() {
this.setState({ showEmojiContainer: false }); this.setState({ showEmojiKeyboard: false });
} }
submit(message) { submit(message) {
this.setState({ text: '' }); 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) { async _getUsers(keyword) {
this.users = database.objects('users'); this.users = database.objects('users');
if (keyword) { if (keyword) {
this.users = this.users.filtered('username CONTAINS[c] $0', keyword); this.users = this.users.filtered('username CONTAINS[c] $0', keyword);
} }
this._getFixedMentions(keyword);
this.setState({ mentions: this.users.slice() }); this.setState({ mentions: this.users.slice() });
const usernames = []; const usernames = [];
@ -244,8 +255,9 @@ export default class MessageBox extends React.PureComponent {
console.log('spotlight canceled'); console.log('spotlight canceled');
} finally { } finally {
delete this.oldPromise; delete this.oldPromise;
this.users = database.objects('users').filtered('username CONTAINS[c] $0', keyword); this.users = database.objects('users').filtered('username CONTAINS[c] $0', keyword).slice();
this.setState({ mentions: this.users.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() { stopTrackingMention() {
this.setState({ this.setState({
showMentionsContainer: false, showMentionsContainer: false,
mentions: [] mentions: [],
trackingType: ''
}); });
this.users = []; this.users = [];
this.rooms = []; this.rooms = [];
this.customEmojis = [];
this.emojis = [];
} }
identifyMentionKeyword(keyword, type) { identifyMentionKeyword(keyword, type) {
this.updateMentions(keyword, type); if (!this.state.showMentionsContainer) {
this.props.layoutAnimation();
}
this.setState({ this.setState({
showMentionsContainer: true showMentionsContainer: true,
showEmojiKeyboard: false,
trackingType: type
}); });
this.updateMentions(keyword, type);
} }
updateMentions = (keyword, type) => { updateMentions = (keyword, type) => {
if (type === MENTIONS_TRACKING_TYPE_USERS) { if (type === MENTIONS_TRACKING_TYPE_USERS) {
this._getUsers(keyword); this._getUsers(keyword);
} else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) {
this._getEmojis(keyword);
} else { } else {
this._getRooms(keyword); this._getRooms(keyword);
} }
@ -320,17 +351,20 @@ export default class MessageBox extends React.PureComponent {
const cursor = Math.max(start, end); 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 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.component.setNativeProps({ text });
this.setState({ text }); this.setState({ text });
this.component.focus(); this.component.focus();
requestAnimationFrame(() => this.stopTrackingMention()); requestAnimationFrame(() => this.stopTrackingMention());
} }
_onEmojiSelected(emoji) { _onEmojiSelected(keyboardId, params) {
const { text } = this.state; const { text } = this.state;
const { emoji } = params;
let newText = ''; let newText = '';
// if messagebox has an active cursor // if messagebox has an active cursor
@ -345,73 +379,116 @@ export default class MessageBox extends React.PureComponent {
this.component.setNativeProps({ text: newText }); this.component.setNativeProps({ text: newText });
this.setState({ text: newText }); this.setState({ text: newText });
} }
renderMentionItem = item => ( renderFixedMentionItem = item => (
<TouchableOpacity <TouchableOpacity
style={styles.mentionItem} style={styles.mentionItem}
onPress={() => this._onPressMention(item)} onPress={() => this._onPressMention(item)}
> >
<Text style={styles.fixedMentionAvatar}>{item.username}</Text>
<Text>Notify {item.desc} in this room</Text>
</TouchableOpacity>
)
renderMentionEmoji = (item) => {
if (item.name) {
return (
<CustomEmoji
key='mention-item-avatar'
style={styles.mentionItemCustomEmoji}
emoji={item}
baseUrl={this.props.baseUrl}
/>
);
}
return (
<Text
key='mention-item-avatar'
style={styles.mentionItemEmoji}
>
{emojify(`:${ item }:`, { output: 'unicode' })}
</Text>
);
}
renderMentionItem = (item) => {
if (item.username === 'all' || item.username === 'here') {
return this.renderFixedMentionItem(item);
}
return (
<TouchableOpacity
style={styles.mentionItem}
onPress={() => this._onPressMention(item)}
>
{this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ?
[
this.renderMentionEmoji(item),
<Text key='mention-item-name'>:{ item.name || item }:</Text>
]
: [
<Avatar <Avatar
key='mention-item-avatar'
style={{ margin: 8 }} style={{ margin: 8 }}
text={item.username || item.name} text={item.username || item.name}
size={30} size={30}
baseUrl={this.props.baseUrl} baseUrl={this.props.baseUrl}
/> />,
<Text>{item.username || item.name }</Text> <Text key='mention-item-name'>{ item.username || item.name }</Text>
</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() { </TouchableOpacity>
const usersList = ( );
}
renderMentions = () => (
<FlatList <FlatList
key='messagebox-container'
style={styles.mentionList} style={styles.mentionList}
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 || item}
{...scrollPersistTaps} keyboardShouldPersistTaps='always'
/> />
); );
const { showMentionsContainer, messageboxHeight } = this.state;
return <AnimatedContainer visible={showMentionsContainer} subview={usersList} messageboxHeight={messageboxHeight} />; renderContent() {
}
render() {
const { height } = this.state;
return ( return (
<View> [
this.renderMentions(),
<SafeAreaView <SafeAreaView
key='messagebox'
style={[styles.textBox, (this.props.editing ? styles.editing : null)]} style={[styles.textBox, (this.props.editing ? styles.editing : null)]}
onLayout={event => this.setState({ messageboxHeight: event.nativeEvent.layout.height })}
> >
<View style={styles.textArea}> <View style={styles.textArea}>
{this.leftButtons} {this.leftButtons}
<TextInput <TextInput
ref={component => this.component = component} ref={component => this.component = component}
style={[styles.textBoxInput, { height }]} style={styles.textBoxInput}
returnKeyType='default' returnKeyType='default'
blurOnSubmit={false} blurOnSubmit={false}
placeholder='New Message' placeholder='New Message'
onChangeText={text => this.onChangeText(text)} onChangeText={text => this.onChangeText(text)}
onChange={event => this.onChange(event)}
value={this.state.text} value={this.state.text}
underlineColorAndroid='transparent' underlineColorAndroid='transparent'
defaultValue='' defaultValue=''
multiline multiline
placeholderTextColor='#9EA2A8' placeholderTextColor='#9EA2A8'
onContentSizeChange={e => this.updateSize(e.nativeEvent.contentSize.height)}
/> />
{this.rightButtons} {this.rightButtons}
</View> </View>
</SafeAreaView> </SafeAreaView>
{this.renderMentions()} ]
{this.renderEmoji()} );
</View> }
render() {
return (
<KeyboardAccessoryView
renderContent={() => this.renderContent()}
kbInputRef={this.component}
kbComponent={this.state.showEmojiKeyboard ? 'EmojiKeyboard' : null}
onKeyboardResigned={() => this.onKeyboardResigned()}
onItemSelected={this._onEmojiSelected}
trackInteractive
revealKeyboardInteractive
requiresSameParentToManageScrollView
/>
); );
} }
} }

View File

@ -1,4 +1,4 @@
import { StyleSheet } from 'react-native'; import { StyleSheet, Platform } from 'react-native';
const MENTION_HEIGHT = 50; const MENTION_HEIGHT = 50;
@ -9,8 +9,6 @@ export default StyleSheet.create({
alignItems: 'center', alignItems: 'center',
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: '#D8D8D8', borderTopColor: '#D8D8D8',
paddingHorizontal: 15,
paddingVertical: 15,
zIndex: 2 zIndex: 2
}, },
safeAreaView: { safeAreaView: {
@ -23,14 +21,14 @@ export default StyleSheet.create({
flexGrow: 0 flexGrow: 0
}, },
textBoxInput: { textBoxInput: {
paddingVertical: 0, textAlignVertical: 'center',
paddingHorizontal: 10,
textAlignVertical: 'top',
maxHeight: 120, maxHeight: 120,
flexGrow: 1, flexGrow: 1,
width: 1, width: 1,
paddingTop: 0, paddingTop: 15,
paddingBottom: 0 paddingBottom: 15,
paddingLeft: 0,
paddingRight: 0
}, },
editing: { editing: {
backgroundColor: '#fff5df' backgroundColor: '#fff5df'
@ -39,7 +37,8 @@ export default StyleSheet.create({
color: '#2F343D', color: '#2F343D',
fontSize: 20, fontSize: 20,
textAlign: 'center', textAlign: 'center',
paddingHorizontal: 5, padding: 15,
paddingHorizontal: 21,
flex: 0 flex: 0
}, },
actionRow: { actionRow: {
@ -85,5 +84,26 @@ export default StyleSheet.create({
borderTopColor: '#ECECEC', borderTopColor: '#ECECEC',
borderTopWidth: 1, borderTopWidth: 1,
backgroundColor: '#fff' 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
} }
}); });

View File

@ -16,7 +16,7 @@ import database from '../lib/realm';
errorActionsHide: () => dispatch(errorActionsHide()) errorActionsHide: () => dispatch(errorActionsHide())
}) })
) )
export default class MessageActions extends React.Component { export default class MessageErrorActions extends React.Component {
static propTypes = { static propTypes = {
errorActionsHide: PropTypes.func.isRequired, errorActionsHide: PropTypes.func.isRequired,
showErrorActions: PropTypes.bool.isRequired, showErrorActions: PropTypes.bool.isRequired,

View File

@ -1,7 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ScrollView, Text, View, StyleSheet, FlatList, TouchableHighlight } from 'react-native'; import { ScrollView, Text, View, StyleSheet, FlatList, TouchableHighlight } from 'react-native';
import { DrawerItems } from 'react-navigation';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import database from '../lib/realm'; import database from '../lib/realm';
@ -101,10 +100,6 @@ export default class Sidebar extends Component {
return ( return (
<ScrollView style={styles.scrollView}> <ScrollView style={styles.scrollView}>
<View style={{ paddingBottom: 20 }}> <View style={{ paddingBottom: 20 }}>
<DrawerItems
{...this.props}
onItemPress={this.onItemPress}
/>
<FlatList <FlatList
data={this.state.servers} data={this.state.servers}
renderItem={this.renderItem} renderItem={this.renderItem}

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { StyleSheet, Text } from 'react-native'; import { StyleSheet, Text, Keyboard } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -23,12 +23,15 @@ export default class Typing extends React.Component {
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
return this.props.usersTyping.join() !== nextProps.usersTyping.join(); return this.props.usersTyping.join() !== nextProps.usersTyping.join();
} }
onPress = () => {
Keyboard.dismiss();
}
get usersTyping() { get usersTyping() {
const users = this.props.usersTyping.filter(_username => this.props.username !== _username); const users = this.props.usersTyping.filter(_username => this.props.username !== _username);
return users.length ? `${ users.join(' ,') } ${ users.length > 1 ? 'are' : 'is' } typing` : ''; return users.length ? `${ users.join(' ,') } ${ users.length > 1 ? 'are' : 'is' } typing` : '';
} }
render() { render() {
return (<Text style={styles.typing}>{this.usersTyping}</Text>); return (<Text style={styles.typing} onPress={() => this.onPress()}>{this.usersTyping}</Text>);
} }
} }

View File

@ -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 <CustomEmoji key={content} style={customEmojiStyle} emoji={emoji} />;
}
return <Text style={standardEmojiStyle}>{ emojify(`${ content }`, { output: 'unicode' }) }</Text>;
}
}

View File

@ -39,7 +39,7 @@ const styles = StyleSheet.create({
} }
}); });
export default class extends React.PureComponent { export default class Image extends React.PureComponent {
static propTypes = { static propTypes = {
file: PropTypes.object.isRequired, file: PropTypes.object.isRequired,
baseUrl: PropTypes.string.isRequired, baseUrl: PropTypes.string.isRequired,

View File

@ -5,7 +5,7 @@ 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'; import styles from './styles';
import CustomEmoji from '../CustomEmoji'; import CustomEmoji from '../EmojiPicker/CustomEmoji';
const BlockCode = ({ node, state }) => ( const BlockCode = ({ node, state }) => (
<Text <Text
@ -111,9 +111,8 @@ const Markdown = ({ msg, customEmojis }) => {
const emojiExtension = customEmojis[content]; const emojiExtension = customEmojis[content];
if (emojiExtension) { if (emojiExtension) {
const emoji = { extension: emojiExtension, content }; const emoji = { extension: emojiExtension, content };
const style = StyleSheet.flatten(styles.customEmoji);
element.props.children = ( element.props.children = (
<CustomEmoji key={state.key} style={style} emoji={emoji} /> <CustomEmoji key={state.key} style={styles.customEmoji} emoji={emoji} />
); );
} }
return element; return element;

View File

@ -25,7 +25,7 @@ const styles = {
} }
}; };
export default class extends React.PureComponent { export default class PhotoModal extends React.PureComponent {
static propTypes = { static propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
image: PropTypes.string.isRequired, image: PropTypes.string.isRequired,

View File

@ -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 (
<View style={styles.itemContainer}>
<View style={styles.emojiContainer}>
<Emoji
content={item.emoji}
standardEmojiStyle={standardEmojiStyle}
customEmojiStyle={customEmojiStyle}
customEmojis={this.props.customEmojis}
/>
</View>
<View style={styles.peopleItemContainer}>
<Text style={styles.reactCount}>
{count === 1 ? '1 person' : `${ count } people`} reacted
</Text>
<Text style={styles.peopleReacted}>{ usernames }</Text>
</View>
</View>
);
}
render() {
const {
isVisible, onClose, reactions
} = this.props;
return (
<Modal
isVisible={isVisible}
onBackdropPress={onClose}
onBackButtonPress={onClose}
backdropOpacity={0.9}
>
<TouchableWithoutFeedback onPress={onClose}>
<View style={styles.titleContainer}>
<Icon
style={styles.closeButton}
name='close'
size={20}
onPress={onClose}
/>
<Text style={styles.title}>Reactions</Text>
</View>
</TouchableWithoutFeedback>
<View style={styles.listContainer}>
<FlatList
data={reactions}
renderItem={({ item }) => this.renderItem(item)}
keyExtractor={item => item.emoji}
/>
</View>
</Modal>
);
}
}

View File

@ -52,10 +52,13 @@ const Url = ({ url }) => {
return ( return (
<TouchableOpacity onPress={() => onPress(url.url)} style={styles.button}> <TouchableOpacity onPress={() => onPress(url.url)} style={styles.button}>
<QuoteMark /> <QuoteMark />
{url.image ?
<Image <Image
style={styles.image} style={styles.image}
source={{ uri: encodeURI(url.image) }} source={{ uri: encodeURI(url.image) }}
/> />
: null
}
<View style={styles.textContainer}> <View style={styles.textContainer}>
<Text style={styles.title}>{url.title}</Text> <Text style={styles.title}>{url.title}</Text>
<Text style={styles.description} numberOfLines={1}>{url.description}</Text> <Text style={styles.description} numberOfLines={1}>{url.description}</Text>

View File

@ -1,11 +1,13 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; 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 { connect } from 'react-redux';
import Icon from 'react-native-vector-icons/MaterialIcons'; import Icon from 'react-native-vector-icons/MaterialIcons';
import moment from 'moment'; 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 Image from './Image';
import User from './User'; import User from './User';
import Avatar from '../Avatar'; import Avatar from '../Avatar';
@ -14,23 +16,24 @@ import Video from './Video';
import Markdown from './Markdown'; import Markdown from './Markdown';
import Url from './Url'; import Url from './Url';
import Reply from './Reply'; import Reply from './Reply';
import ReactionsModal from './ReactionsModal';
import Emoji from './Emoji';
import messageStatus from '../../constants/messagesStatus'; import messageStatus from '../../constants/messagesStatus';
import styles from './styles'; import styles from './styles';
const avatar = { marginRight: 10 };
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 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)),
toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
})) }))
export default class Message extends React.Component { export default class Message extends React.Component {
static propTypes = { static propTypes = {
item: PropTypes.object.isRequired, item: PropTypes.object.isRequired,
reactions: PropTypes.object.isRequired,
baseUrl: PropTypes.string.isRequired, baseUrl: PropTypes.string.isRequired,
Message_TimeFormat: PropTypes.string.isRequired, Message_TimeFormat: PropTypes.string.isRequired,
message: PropTypes.object.isRequired, message: PropTypes.object.isRequired,
@ -38,20 +41,15 @@ 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, customEmojis: PropTypes.object,
customEmojis: PropTypes.object toggleReactionPicker: PropTypes.func,
onReactionPress: PropTypes.func
} }
componentWillMount() { constructor(props) {
this._visibility = new Animated.Value(this.props.animate ? 0 : 1); super(props);
} this.state = { reactionsModal: false };
componentDidMount() { this.onClose = this.onClose.bind(this);
if (this.props.animate) {
Animated.timing(this._visibility, {
toValue: 1,
duration: 300
}).start();
}
} }
componentWillReceiveProps() { componentWillReceiveProps() {
this.extraStyle = this.extraStyle || {}; 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; return this.props.item._updatedAt.toGMTString() !== nextProps.item._updatedAt.toGMTString() || this.props.item.status !== nextProps.item.status;
} }
onPress = () => {
KeyboardUtils.dismiss();
}
onLongPress() { onLongPress() {
const { item } = this.props; this.props.actionsShow(this.parseMessage());
this.props.actionsShow(JSON.parse(JSON.stringify(item)));
} }
onErrorPress() { onErrorPress() {
const { item } = this.props; this.props.errorActionsShow(this.parseMessage());
this.props.errorActionsShow(JSON.parse(JSON.stringify(item))); }
onReactionPress(emoji) {
this.props.onReactionPress(emoji, this.props.item._id);
}
onClose() {
this.setState({ reactionsModal: false });
}
onReactionLongPress() {
this.setState({ reactionsModal: true });
Vibration.vibrate(50);
} }
getInfoMessage() { getInfoMessage() {
@ -101,11 +118,12 @@ export default class Message extends React.Component {
return message; return message;
} }
parseMessage = () => JSON.parse(JSON.stringify(this.props.item));
isInfoMessage() { isInfoMessage() {
return ['r', 'au', 'ru', 'ul', 'uj', 'rm', 'user-muted', 'user-unmuted', 'message_pinned'].includes(this.props.item.t); return ['r', 'au', 'ru', 'ul', 'uj', 'rm', 'user-muted', 'user-unmuted', 'message_pinned'].includes(this.props.item.t);
} }
isDeleted() { isDeleted() {
return this.props.item.t === 'rm'; 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 (
<TouchableOpacity
onPress={() => this.onReactionPress(reaction.emoji)}
onLongPress={() => this.onReactionLongPress()}
key={reaction.emoji}
>
<View style={[styles.reactionContainer, reactedContainerStyle]}>
<Emoji
content={reaction.emoji}
standardEmojiStyle={styles.reactionEmoji}
customEmojiStyle={styles.reactionCustomEmoji}
customEmojis={this.props.customEmojis}
/>
<Text style={[styles.reactionCount, reactedCount]}>{ reaction.usernames.length }</Text>
</View>
</TouchableOpacity>
);
}
renderReactions() {
if (this.props.item.reactions.length === 0) {
return null;
}
return (
<View style={styles.reactionsContainer}>
{this.props.item.reactions.map(reaction => this.renderReaction(reaction))}
<TouchableOpacity
onPress={() => this.props.toggleReactionPicker(this.parseMessage())}
key='add-reaction'
style={styles.reactionContainer}
>
<Icon name='insert-emoticon' color='#aaaaaa' size={15} />
</TouchableOpacity>
</View>
);
}
render() { render() {
const { const {
item, message, editing, baseUrl item, message, editing, baseUrl, customEmojis
} = this.props; } = 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 username = item.alias || item.u.username;
const isEditing = message._id === item._id && editing; const isEditing = message._id === item._id && editing;
const accessibilityLabel = `Message from ${ username } at ${ moment(item.ts).format(this.props.Message_TimeFormat) }, ${ this.props.item.msg }`;
const accessibilityLabel = `Message from ${ item.alias || item.u.username } at ${ moment(item.ts).format(this.props.Message_TimeFormat) }, ${ this.props.item.msg }`;
return ( return (
<TouchableHighlight <TouchableHighlight
onPress={() => this.onPress()}
onLongPress={() => this.onLongPress()} onLongPress={() => this.onLongPress()}
disabled={this.isDeleted() || this.hasError()} disabled={this.isDeleted() || this.hasError()}
underlayColor='#FFFFFF' underlayColor='#FFFFFF'
@ -188,11 +238,11 @@ export default class Message extends React.Component {
style={[styles.message, isEditing ? styles.editing : null]} style={[styles.message, isEditing ? styles.editing : null]}
accessibilityLabel={accessibilityLabel} accessibilityLabel={accessibilityLabel}
> >
<Animated.View style={[flex, { opacity, marginLeft }]}> <View style={styles.flex}>
{this.renderError()} {this.renderError()}
<View style={[this.extraStyle, flex]}> <View style={[this.extraStyle, styles.flex]}>
<Avatar <Avatar
style={avatar} style={styles.avatar}
text={item.avatar ? '' : username} text={item.avatar ? '' : username}
size={40} size={40}
baseUrl={baseUrl} baseUrl={baseUrl}
@ -208,9 +258,20 @@ export default class Message extends React.Component {
{this.renderMessageContent()} {this.renderMessageContent()}
{this.attachments()} {this.attachments()}
{this.renderUrl()} {this.renderUrl()}
{this.renderReactions()}
</View> </View>
</View> </View>
</Animated.View> {this.state.reactionsModal ?
<ReactionsModal
isVisible={this.state.reactionsModal}
onClose={this.onClose}
reactions={item.reactions}
user={this.props.user}
customEmojis={customEmojis}
/>
: null
}
</View>
</TouchableHighlight> </TouchableHighlight>
); );
} }

View File

@ -5,6 +5,10 @@ export default StyleSheet.create({
flexGrow: 1, flexGrow: 1,
flexShrink: 1 flexShrink: 1
}, },
flex: {
flexDirection: 'row',
flex: 1
},
message: { message: {
padding: 12, padding: 12,
paddingTop: 6, paddingTop: 6,
@ -33,5 +37,38 @@ export default StyleSheet.create({
borderWidth: 1, borderWidth: 1,
borderRadius: 5, borderRadius: 5,
padding: 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
} }
}); });

2833
app/emojis.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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 = { const messagesEditedBySchema = {
name: 'messagesEditedBy', name: 'messagesEditedBy',
@ -174,7 +189,8 @@ const messagesSchema = {
status: { type: 'int', optional: true }, status: { type: 'int', optional: true },
pinned: { type: 'bool', optional: true }, pinned: { type: 'bool', optional: true },
starred: { 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, url,
frequentlyUsedEmojiSchema, frequentlyUsedEmojiSchema,
customEmojiAliasesSchema, customEmojiAliasesSchema,
customEmojisSchema customEmojisSchema,
messagesReactionsSchema,
messagesReactionsUsernamesSchema
]; ];
class DB { class DB {
databases = { databases = {

View File

@ -1,6 +1,7 @@
import Random from 'react-native-meteor/lib/Random'; import Random from 'react-native-meteor/lib/Random';
import { AsyncStorage, Platform } from 'react-native'; import { AsyncStorage, Platform } from 'react-native';
import { hashPassword } from 'react-native-meteor/lib/utils'; import { hashPassword } from 'react-native-meteor/lib/utils';
import _ from 'lodash';
import RNFetchBlob from 'react-native-fetch-blob'; import RNFetchBlob from 'react-native-fetch-blob';
import reduxStore from './createStore'; import reduxStore from './createStore';
@ -264,6 +265,8 @@ const RocketChat = {
// loadHistory returns message.starred as object // loadHistory returns message.starred as object
// stream-room-messages returns message.starred as an array // stream-room-messages returns message.starred as an array
message.starred = message.starred && (Array.isArray(message.starred) ? message.starred.length > 0 : !!message.starred); 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; return message;
}, },
loadMessagesForRoom(rid, end, cb) { loadMessagesForRoom(rid, end, cb) {
@ -590,6 +593,9 @@ const RocketChat = {
}, },
setUserPresenceDefaultStatus(status) { setUserPresenceDefaultStatus(status) {
return call('UserPresence:setDefaultStatus', status); return call('UserPresence:setDefaultStatus', status);
},
setReaction(emoji, messageId) {
return call('setReaction', emoji, messageId);
} }
}; };

View File

@ -2,14 +2,8 @@ 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 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,
@ -19,9 +13,7 @@ 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() {
@ -34,8 +26,6 @@ export default class KeyboardView extends React.PureComponent {
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>

View File

@ -4,11 +4,13 @@ import EJSON from 'ejson';
import { goRoom } from './containers/routes/NavigationService'; import { goRoom } from './containers/routes/NavigationService';
const handleNotification = (notification) => { const handleNotification = (notification) => {
if (notification.usernInteraction) { if (!notification.userInteraction) {
return; return;
} }
const { rid, name } = EJSON.parse(notification.ejson); const {
return rid && name && goRoom({ rid, name }); rid, name, sender, type
} = EJSON.parse(notification.ejson || notification.data.ejson);
return rid && goRoom({ rid, name: type === 'd' ? sender.username : name });
}; };
PushNotification.configure({ PushNotification.configure({

View File

@ -12,7 +12,6 @@ import app from './app';
import permissions from './permissions'; import permissions from './permissions';
import customEmojis from './customEmojis'; import customEmojis from './customEmojis';
import activeUsers from './activeUsers'; import activeUsers from './activeUsers';
import keyboard from './keyboard';
export default combineReducers({ export default combineReducers({
settings, settings,
@ -27,6 +26,5 @@ export default combineReducers({
rooms, rooms,
permissions, permissions,
customEmojis, customEmojis,
activeUsers, activeUsers
keyboard
}); });

View File

@ -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;
}
}

View File

@ -8,7 +8,8 @@ const initialState = {
editing: false, editing: false,
permalink: '', permalink: '',
showActions: false, showActions: false,
showErrorActions: false showErrorActions: false,
showReactionPicker: false
}; };
export default function messages(state = initialState, action) { export default function messages(state = initialState, action) {
@ -96,6 +97,12 @@ export default function messages(state = initialState, action) {
...state, ...state,
message: {} message: {}
}; };
case types.MESSAGES.TOGGLE_REACTION_PICKER:
return {
...state,
showReactionPicker: !state.showReactionPicker,
actionMessage: action.message
};
default: default:
return state; return state;
} }

View File

@ -1,7 +1,8 @@
import * as types from '../actions/actionsTypes'; import * as types from '../actions/actionsTypes';
const initialState = { const initialState = {
usersTyping: [] usersTyping: [],
layoutAnimation: new Date()
}; };
export default function room(state = initialState, action) { export default function room(state = initialState, action) {
@ -31,6 +32,11 @@ export default function room(state = initialState, action) {
...state, ...state,
usersTyping: [...state.usersTyping.filter(user => user !== action.username)] usersTyping: [...state.usersTyping.filter(user => user !== action.username)]
}; };
case types.ROOM.LAYOUT_ANIMATION:
return {
...state,
layoutAnimation: new Date()
};
default: default:
return state; return state;
} }

View File

@ -14,7 +14,7 @@ const styles = {
} }
}; };
export default class extends React.PureComponent { export default class Photo extends React.PureComponent {
static propTypes = { static propTypes = {
navigation: PropTypes.object.isRequired navigation: PropTypes.object.isRequired
} }

View File

@ -18,7 +18,7 @@ import { closeRoom } from '../../../actions/room';
}), dispatch => ({ }), dispatch => ({
close: () => dispatch(closeRoom()) close: () => dispatch(closeRoom())
})) }))
export default class extends React.PureComponent { export default class RoomHeaderView extends React.PureComponent {
static propTypes = { static propTypes = {
close: PropTypes.func.isRequired, close: PropTypes.func.isRequired,
navigation: PropTypes.object.isRequired, navigation: PropTypes.object.isRequired,

View File

@ -1,13 +1,20 @@
import { ListView as OldList } from 'realm/react-native'; import { ListView as OldList } from 'realm/react-native';
import React from 'react'; import React from 'react';
import cloneReferencedElement from 'react-clone-referenced-element'; 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 moment from 'moment';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import DateSeparator from './DateSeparator'; import DateSeparator from './DateSeparator';
import UnreadSeparator from './UnreadSeparator'; 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 { export class DataSource extends OldList.DataSource {
getRowData(sectionIndex: number, rowIndex: number): any { 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 (<ListView
enableEmptySections
style={styles.list}
onEndReachedThreshold={0.5}
renderFooter={this.props.renderFooter}
renderHeader={() => <Typing />}
onEndReached={() => this.props.onEndReached(this.data)}
dataSource={this.dataSource}
renderRow={item => this.props.renderRow(item)}
initialListSize={10}
{...scrollPersistTaps}
/>);
}
}
@connect(state => ({ @connect(state => ({
lastOpen: state.room.lastOpen lastOpen: state.room.lastOpen
})) }))

View File

@ -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 (
<Modal
isVisible={this.props.showReactionPicker}
style={{ alignItems: 'center' }}
onBackdropPress={() => this.props.toggleReactionPicker()}
onBackButtonPress={() => this.props.toggleReactionPicker()}
animationIn='fadeIn'
animationOut='fadeOut'
>
<View style={[styles.reactionPickerContainer, { width: width - margin, height: Math.min(width, height) - (margin * 2) }]}>
<EmojiPicker
tabEmojiStyle={tabEmojiStyle}
width={Math.min(width, height) - margin}
onEmojiSelected={(emoji, shortname) => this.onEmojiSelected(emoji, shortname)}
/>
</View>
</Modal>
);
}
}

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { View, StyleSheet, Text } from 'react-native'; import { View, StyleSheet, Text, LayoutAnimation } from 'react-native';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
firstUnread: { firstUnread: {
@ -22,11 +22,16 @@ const styles = StyleSheet.create({
} }
}); });
const UnreadSeparator = () => ( export default class UnreadSeparator extends React.PureComponent {
componentWillUnmount() {
LayoutAnimation.linear();
}
render() {
return (
<View style={styles.firstUnread}> <View style={styles.firstUnread}>
<View style={styles.firstUnreadLine} /> <View style={styles.firstUnreadLine} />
<Text style={styles.firstUnreadBadge}>unread messages</Text> <Text style={styles.firstUnreadBadge}>unread messages</Text>
</View> </View>
); );
}
export default UnreadSeparator; }

View File

@ -1,45 +1,41 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; 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 { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import equal from 'deep-equal'; import equal from 'deep-equal';
import { ListView } from './ListView'; import { List } from './ListView';
import * as actions from '../../actions'; import * as actions from '../../actions';
import { openRoom, setLastOpen } from '../../actions/room'; import { openRoom, setLastOpen } from '../../actions/room';
import { editCancel } from '../../actions/messages'; import { editCancel, toggleReactionPicker } from '../../actions/messages';
import database from '../../lib/realm'; import database from '../../lib/realm';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import Message from '../../containers/message'; import Message from '../../containers/message';
import MessageActions from '../../containers/MessageActions'; import MessageActions from '../../containers/MessageActions';
import MessageErrorActions from '../../containers/MessageErrorActions'; import MessageErrorActions from '../../containers/MessageErrorActions';
import MessageBox from '../../containers/MessageBox'; import MessageBox from '../../containers/MessageBox';
import Typing from '../../containers/Typing';
import KeyboardView from '../../presentation/KeyboardView';
import Header from '../../containers/Header'; import Header from '../../containers/Header';
import RoomsHeader from './Header'; import RoomsHeader from './Header';
import ReactionPicker from './ReactionPicker';
import Banner from './banner'; import Banner from './banner';
import styles from './styles'; 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 = () => <Typing />;
@connect( @connect(
state => ({ state => ({
Site_Url: state.settings.Site_Url || state.server ? state.server.server : '', Site_Url: state.settings.Site_Url || state.server ? state.server.server : '',
Message_TimeFormat: state.settings.Message_TimeFormat, Message_TimeFormat: state.settings.Message_TimeFormat,
loading: state.messages.isFetching, loading: state.messages.isFetching,
user: state.login.user user: state.login.user,
actionMessage: state.messages.actionMessage,
layoutAnimation: state.room.layoutAnimation
}), }),
dispatch => ({ dispatch => ({
actions: bindActionCreators(actions, dispatch), actions: bindActionCreators(actions, dispatch),
openRoom: room => dispatch(openRoom(room)), openRoom: room => dispatch(openRoom(room)),
editCancel: () => dispatch(editCancel()), 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 { export default class RoomView extends React.Component {
@ -52,7 +48,11 @@ export default class RoomView extends React.Component {
rid: PropTypes.string, rid: PropTypes.string,
name: PropTypes.string, name: PropTypes.string,
Site_Url: 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 }) => ({ static navigationOptions = ({ navigation }) => ({
@ -64,87 +64,83 @@ export default class RoomView extends React.Component {
this.rid = this.rid =
props.rid || props.rid ||
props.navigation.state.params.room.rid; props.navigation.state.params.room.rid;
this.name = this.props.name || this.name = props.name ||
this.props.navigation.state.params.name || props.navigation.state.params.name ||
this.props.navigation.state.params.room.name; 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.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid); this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
this.state = { this.state = {
dataSource: ds.cloneWithRows(this.data, rowIds),
loaded: true, loaded: true,
joined: typeof props.rid === 'undefined', joined: typeof props.rid === 'undefined',
readOnly: false room: {}
}; };
this.onReactionPress = this.onReactionPress.bind(this);
} }
componentWillMount() { async componentWillMount() {
this.props.navigation.setParams({ this.props.navigation.setParams({
title: this.name title: this.name
}); });
this.updateRoom(); this.updateRoom();
this.props.openRoom({ rid: this.rid, name: this.name, ls: this.room.ls }); await this.props.openRoom({ rid: this.rid, name: this.name, ls: this.state.room.ls });
if (this.room.alert || this.room.unread || this.room.userMentions) { if (this.state.room.alert || this.state.room.unread || this.state.room.userMentions) {
this.props.setLastOpen(this.room.ls); this.props.setLastOpen(this.state.room.ls);
} else { } else {
this.props.setLastOpen(null); this.props.setLastOpen(null);
} }
this.data.addListener(this.updateState);
this.rooms.addListener(this.updateRoom); this.rooms.addListener(this.updateRoom);
} }
componentWillReceiveProps(nextProps) {
if (this.props.layoutAnimation !== nextProps.layoutAnimation) {
LayoutAnimation.spring();
}
}
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return !(equal(this.props, nextProps) && equal(this.state, nextState)); return !(equal(this.props, nextProps) && equal(this.state, nextState));
} }
componentWillUnmount() { componentWillUnmount() {
clearTimeout(this.timer); clearTimeout(this.timer);
this.data.removeAllListeners(); this.rooms.removeAllListeners();
this.props.editCancel(); this.props.editCancel();
} }
onEndReached = () => { onEndReached = (data) => {
if ( if (this.props.loading || this.state.end) {
// rowCount && return;
this.state.loaded && }
this.state.loadingMore !== true && if (!this.state.loaded) {
this.state.end !== true alert(2);
) { return;
this.setState({ }
loadingMore: true
});
requestAnimationFrame(() => { requestAnimationFrame(() => {
const lastRowData = this.data[this.data.length - 1]; const lastRowData = data[data.length - 1];
if (!lastRowData) { if (!lastRowData) {
return; return;
} }
RocketChat.loadMessagesForRoom(this.rid, lastRowData.ts, ({ end }) => { RocketChat.loadMessagesForRoom(this.rid, lastRowData.ts, ({ end }) => end && this.setState({
this.setState({
loadingMore: false,
end end
}));
}); });
});
});
}
} }
updateState = debounce(() => { onReactionPress = (shortname, messageId) => {
const rowIds = this.data.map((row, index) => index); if (!messageId) {
this.setState({ RocketChat.setReaction(shortname, this.props.actionMessage._id);
dataSource: this.state.dataSource.cloneWithRows(this.data, rowIds) return this.props.toggleReactionPicker();
}); }
}, 50); RocketChat.setReaction(shortname, messageId);
};
updateRoom = () => { updateRoom = () => {
[this.room] = this.rooms; this.setState({ room: this.rooms[0] });
this.setState({ readOnly: this.room.ro });
} }
sendMessage = message => RocketChat.sendMessage(this.rid, message).then(() => { sendMessage = (message) => {
RocketChat.sendMessage(this.rid, message).then(() => {
this.props.setLastOpen(null); this.props.setLastOpen(null);
}); });
};
joinRoom = async() => { joinRoom = async() => {
await RocketChat.joinRoom(this.props.rid); await RocketChat.joinRoom(this.props.rid);
@ -157,10 +153,11 @@ export default class RoomView extends React.Component {
<Message <Message
key={item._id} key={item._id}
item={item} item={item}
animate={this.opened.toISOString() < item.ts.toISOString()} reactions={JSON.parse(JSON.stringify(item.reactions))}
baseUrl={this.props.Site_Url} baseUrl={this.props.Site_Url}
Message_TimeFormat={this.props.Message_TimeFormat} Message_TimeFormat={this.props.Message_TimeFormat}
user={this.props.user} user={this.props.user}
onReactionPress={this.onReactionPress}
/> />
); );
@ -175,7 +172,7 @@ export default class RoomView extends React.Component {
</View> </View>
); );
} }
if (this.state.readOnly) { if (this.state.room.ro) {
return ( return (
<View style={styles.readOnly}> <View style={styles.readOnly}>
<Text>This room is read only</Text> <Text>This room is read only</Text>
@ -186,37 +183,28 @@ export default class RoomView extends React.Component {
}; };
renderHeader = () => { renderHeader = () => {
if (this.state.loadingMore) {
return <Text style={styles.loadingMore}>Loading more messages...</Text>;
}
if (this.state.end) { if (this.state.end) {
return <Text style={styles.loadingMore}>Start of conversation</Text>; return <Text style={styles.loadingMore}>Start of conversation</Text>;
} }
return <Text style={styles.loadingMore}>Loading more messages...</Text>;
} }
render() { render() {
return ( return (
<KeyboardView contentContainerStyle={styles.container} keyboardVerticalOffset={64}> <View style={styles.container}>
<Banner /> <Banner />
<SafeAreaView style={styles.safeAreaView}> <List
<ListView key='room-view-messages'
enableEmptySections end={this.state.end}
style={styles.list} room={this.rid}
onEndReachedThreshold={500}
renderFooter={this.renderHeader} renderFooter={this.renderHeader}
renderHeader={typing}
onEndReached={this.onEndReached} onEndReached={this.onEndReached}
dataSource={this.state.dataSource}
renderRow={item => this.renderItem(item)} renderRow={item => this.renderItem(item)}
initialListSize={10}
{...scrollPersistTaps}
/> />
</SafeAreaView>
{this.renderFooter()} {this.renderFooter()}
<MessageActions room={this.room} /> {this.state.room._id ? <MessageActions room={this.state.room} /> : null}
<MessageErrorActions /> <MessageErrorActions />
</KeyboardView> <ReactionPicker onEmojiSelected={this.onReactionPress} />
</View>
); );
} }
} }

View File

@ -33,5 +33,13 @@ export default StyleSheet.create({
}, },
readOnly: { readOnly: {
padding: 10 padding: 10
},
reactionPickerContainer: {
// width: width - 20,
// height: width - 20,
// paddingHorizontal: Platform.OS === 'android' ? 11 : 10,
backgroundColor: '#F7F7F7',
borderRadius: 4,
flexDirection: 'column'
} }
}); });

View File

@ -20,7 +20,7 @@ import styles from './styles';
}), dispatch => ({ }), dispatch => ({
setSearch: searchText => dispatch(setSearch(searchText)) setSearch: searchText => dispatch(setSearch(searchText))
})) }))
export default class extends React.Component { export default class RoomsListHeaderView extends React.Component {
static propTypes = { static propTypes = {
navigation: PropTypes.object.isRequired, navigation: PropTypes.object.isRequired,
user: PropTypes.object.isRequired, user: PropTypes.object.isRequired,

View File

@ -69,7 +69,7 @@ const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
resetCreateChannel: () => dispatch(createChannelActions.reset()) resetCreateChannel: () => dispatch(createChannelActions.reset())
}) })
) )
export default class RoomsListView extends React.Component { export default class SelectUsersView extends React.Component {
static propTypes = { static propTypes = {
navigation: PropTypes.object.isRequired, navigation: PropTypes.object.isRequired,
Site_Url: PropTypes.string, Site_Url: PropTypes.string,

View File

@ -5,6 +5,8 @@ import { AppRegistry } from 'react-native';
import './app/push'; import './app/push';
import RocketChat from './app/index'; import RocketChat from './app/index';
// UIManager.setLayoutAnimationEnabledExperimental(true);
// import './app/ReactotronConfig'; // import './app/ReactotronConfig';
// import { AppRegistry } from 'react-native'; // import { AppRegistry } from 'react-native';
// import Routes from './app/routes'; // import Routes from './app/routes';

View File

@ -47,11 +47,13 @@
647660C6B6A340C7BD4D1099 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A18EFC3B0CFE40E0918A8F0C /* EvilIcons.ttf */; }; 647660C6B6A340C7BD4D1099 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A18EFC3B0CFE40E0918A8F0C /* EvilIcons.ttf */; };
70A8D9B456894EFFAF027CAB /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7A30DA4B2D474348824CD05B /* FontAwesome.ttf */; }; 70A8D9B456894EFFAF027CAB /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7A30DA4B2D474348824CD05B /* FontAwesome.ttf */; };
77C35F50C01C43668188886C /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A0EEFAF8AB14F5B9E796CDD /* libRNVectorIcons.a */; }; 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 */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
8A159EDB97C44E52AF62D69C /* libRNSVG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA50CE47374C4C35BE6D9D58 /* libRNSVG.a */; }; 8A159EDB97C44E52AF62D69C /* libRNSVG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA50CE47374C4C35BE6D9D58 /* libRNSVG.a */; };
8ECBD927DDAC4987B98E102E /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20CE3E407E0D4D9E8C9885F2 /* libRCTVideo.a */; }; 8ECBD927DDAC4987B98E102E /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20CE3E407E0D4D9E8C9885F2 /* libRCTVideo.a */; };
AE5D35882AE04CC29630FB3D /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = DC6EE17B5550465E98C70FF0 /* Entypo.ttf */; }; AE5D35882AE04CC29630FB3D /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = DC6EE17B5550465E98C70FF0 /* Entypo.ttf */; };
B88F586F1FBF57F600B352B8 /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B88F58461FBF55E200B352B8 /* libRCTPushNotification.a */; }; 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 */; }; B8C682A81FD850F4003A12C8 /* icomoon.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B8C682611FD84CEF003A12C8 /* icomoon.ttf */; };
B8C682AC1FD8511D003A12C8 /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1B0746E708284151B8AD1198 /* Ionicons.ttf */; }; B8C682AC1FD8511D003A12C8 /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1B0746E708284151B8AD1198 /* Ionicons.ttf */; };
B8C682AD1FD8511E003A12C8 /* 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; remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTLinking; remoteInfo = RCTLinking;
}; };
7A430E1D20238C02008F55BC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 39DF4FE71E00394E00F5B4B2;
remoteInfo = RCTCustomInputController;
};
7A7F5C981FCC982500024129 /* PBXContainerItemProxy */ = { 7A7F5C981FCC982500024129 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = AD0379F2BCE84C968538CDAF /* RCTVideo.xcodeproj */; containerPortal = AD0379F2BCE84C968538CDAF /* RCTVideo.xcodeproj */;
@ -366,6 +375,13 @@
remoteGlobalIDString = 9936F32F1F5F2E5B0010BF04; remoteGlobalIDString = 9936F32F1F5F2E5B0010BF04;
remoteInfo = "privatedata-tvOS"; remoteInfo = "privatedata-tvOS";
}; };
B8971BB0202A091D0000D245 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = D834CED81CC64F2400FA5668;
remoteInfo = KeyboardTrackingView;
};
B8E79A8D1F3CCC6D005B464F /* PBXContainerItemProxy */ = { B8E79A8D1F3CCC6D005B464F /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = 4CD38E4891ED4601B7481448 /* RNFetchBlob.xcodeproj */; 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 = "<group>"; }; 6533FB90166345D29F1B91C0 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFetchBlob.a; sourceTree = "<group>"; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; }; 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCustomInputController.xcodeproj; path = "../node_modules/react-native-keyboard-input/lib/ios/RCTCustomInputController.xcodeproj"; sourceTree = "<group>"; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
@ -444,6 +461,7 @@
B2607FA180F14E6584301101 /* libSplashScreen.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libSplashScreen.a; sourceTree = "<group>"; }; B2607FA180F14E6584301101 /* libSplashScreen.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libSplashScreen.a; sourceTree = "<group>"; };
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; }; 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 = "<group>"; }; B88F58361FBF55E200B352B8 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = "<group>"; };
B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = KeyboardTrackingView.xcodeproj; path = "../node_modules/react-native-keyboard-tracking-view/lib/KeyboardTrackingView.xcodeproj"; sourceTree = "<group>"; };
B8C682611FD84CEF003A12C8 /* icomoon.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = icomoon.ttf; path = ../resources/fonts/icomoon.ttf; sourceTree = "<group>"; }; B8C682611FD84CEF003A12C8 /* icomoon.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = icomoon.ttf; path = ../resources/fonts/icomoon.ttf; sourceTree = "<group>"; };
BAAE4B947F5D44959F0A9D5A /* libRNZeroconf.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNZeroconf.a; sourceTree = "<group>"; }; BAAE4B947F5D44959F0A9D5A /* libRNZeroconf.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNZeroconf.a; sourceTree = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
@ -467,6 +485,8 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
B8971BB2202A093B0000D245 /* libKeyboardTrackingView.a in Frameworks */,
7A430E4F20238C46008F55BC /* libRCTCustomInputController.a in Frameworks */,
146834051AC3E58100842450 /* libReact.a in Frameworks */, 146834051AC3E58100842450 /* libReact.a in Frameworks */,
B88F586F1FBF57F600B352B8 /* libRCTPushNotification.a in Frameworks */, B88F586F1FBF57F600B352B8 /* libRCTPushNotification.a in Frameworks */,
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */, 5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */,
@ -674,6 +694,14 @@
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
7A430E1720238C01008F55BC /* Products */ = {
isa = PBXGroup;
children = (
7A430E1E20238C02008F55BC /* libRCTCustomInputController.a */,
);
name = Products;
sourceTree = "<group>";
};
7A7F5C831FCC982500024129 /* Products */ = { 7A7F5C831FCC982500024129 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -694,6 +722,8 @@
832341AE1AAA6A7D00B99B32 /* Libraries */ = { 832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */,
7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */,
B88F58361FBF55E200B352B8 /* RCTPushNotification.xcodeproj */, B88F58361FBF55E200B352B8 /* RCTPushNotification.xcodeproj */,
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */, 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */,
146833FF1AC3E56700842450 /* React.xcodeproj */, 146833FF1AC3E56700842450 /* React.xcodeproj */,
@ -780,6 +810,14 @@
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B8971BAD202A091D0000D245 /* Products */ = {
isa = PBXGroup;
children = (
B8971BB1202A091D0000D245 /* libKeyboardTrackingView.a */,
);
name = Products;
sourceTree = "<group>";
};
B8E79A681F3CCC69005B464F /* Recovered References */ = { B8E79A681F3CCC69005B464F /* Recovered References */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -952,6 +990,10 @@
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = ""; projectDirPath = "";
projectReferences = ( projectReferences = (
{
ProductGroup = B8971BAD202A091D0000D245 /* Products */;
ProjectRef = B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */;
},
{ {
ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */; ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */;
ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */;
@ -960,6 +1002,10 @@
ProductGroup = 5E91572E1DD0AC6500FF2AA8 /* Products */; ProductGroup = 5E91572E1DD0AC6500FF2AA8 /* Products */;
ProjectRef = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */; ProjectRef = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */;
}, },
{
ProductGroup = 7A430E1720238C01008F55BC /* Products */;
ProjectRef = 7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */;
},
{ {
ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */; ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */;
ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */;
@ -1261,6 +1307,13 @@
remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */; remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR; 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 */ = { 7A7F5C991FCC982500024129 /* libRCTVideo.a */ = {
isa = PBXReferenceProxy; isa = PBXReferenceProxy;
fileType = archive.ar; fileType = archive.ar;
@ -1331,6 +1384,13 @@
remoteRef = B88F58661FBF55E200B352B8 /* PBXContainerItemProxy */; remoteRef = B88F58661FBF55E200B352B8 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR; 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 */ = { B8E79A8E1F3CCC6D005B464F /* libRNFetchBlob.a */ = {
isa = PBXReferenceProxy; isa = PBXReferenceProxy;
fileType = archive.ar; fileType = archive.ar;

70
package-lock.json generated
View File

@ -1481,6 +1481,16 @@
"babel-helper-is-void-0": "0.2.0" "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": { "babel-plugin-react-transform": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/babel-plugin-react-transform/-/babel-plugin-react-transform-3.0.0.tgz", "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" "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": { "babel-preset-fbjs": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.1.4.tgz", "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", "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",
@ -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": { "find-cache-dir": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", "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-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": { "react-native-loading-spinner-overlay": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/react-native-loading-spinner-overlay/-/react-native-loading-spinner-overlay-0.5.2.tgz", "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": { "react-native-optimized-flatlist": {
"version": "1.0.3", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/react-native-optimized-flatlist/-/react-native-optimized-flatlist-1.0.3.tgz", "resolved": "https://registry.npmjs.org/react-native-optimized-flatlist/-/react-native-optimized-flatlist-1.0.4.tgz",
"integrity": "sha1-tFN58lpXu05vhZwZDZmEexgR4Ak=", "integrity": "sha512-PMoZRJAHKzd/ahYKUzt43AJ+kVhHpOSTvBhJdQqooZXw312xADWpR7iDvBAbBiRGkmk0yM4GJacd9TMft6q/Gg==",
"requires": { "requires": {
"prop-types": "15.6.0" "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", "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-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": { "react-native-scrollable-tab-view": {
"version": "0.8.0", "version": "0.8.0",
"resolved": "https://registry.npmjs.org/react-native-scrollable-tab-view/-/react-native-scrollable-tab-view-0.8.0.tgz", "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" "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",

View File

@ -13,7 +13,7 @@
"android": "react-native run-android", "android": "react-native run-android",
"storybook": "storybook start -p 7007", "storybook": "storybook start -p 7007",
"snyk-protect": "snyk protect", "snyk-protect": "snyk protect",
"prepare": "npm run snyk-protect" "prepare": "exit 0"
}, },
"rnpm": { "rnpm": {
"assets": [ "assets": [
@ -27,9 +27,9 @@
"babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-remove-console": "^6.8.5", "babel-plugin-transform-remove-console": "^6.8.5",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"babel-preset-expo": "^4.0.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", "lodash": "^4.17.4",
"moment": "^2.20.1", "moment": "^2.20.1",
"prop-types": "^15.6.0", "prop-types": "^15.6.0",
@ -45,11 +45,14 @@
"react-native-image-picker": "^0.26.7", "react-native-image-picker": "^0.26.7",
"react-native-img-cache": "^1.5.2", "react-native-img-cache": "^1.5.2",
"react-native-keyboard-aware-scroll-view": "^0.4.1", "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-loading-spinner-overlay": "^0.5.2",
"react-native-meteor": "^1.2.0", "react-native-meteor": "^1.2.0",
"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.4",
"react-native-push-notification": "^3.0.1", "react-native-push-notification": "^3.0.1",
"react-native-responsive-ui": "^1.1.1",
"react-native-scrollable-tab-view": "^0.8.0", "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",
@ -71,7 +74,6 @@
"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": {