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;
@ -33,16 +34,17 @@ public class MainApplication extends Application implements ReactApplication {
protected List<ReactPackage> getPackages() { protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList( return Arrays.<ReactPackage>asList(
new MainReactPackage(), new MainReactPackage(),
new SvgPackage(), new SvgPackage(),
new ImagePickerPackage(), new ImagePickerPackage(),
new VectorIconsPackage(), new VectorIconsPackage(),
new RNFetchBlobPackage(), new RNFetchBlobPackage(),
new ZeroconfReactPackage(), new ZeroconfReactPackage(),
new RealmReactPackage(), new RealmReactPackage(),
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 emojis={emojis}
key={category} onEmojiSelected={emoji => this.onEmojiSelected(emoji)}
emojis={emojis} style={styles.categoryContainer}
onEmojiSelected={emoji => this.onEmojiSelected(emoji)} size={this.props.emojisPerRow}
finishedLoading={() => { this._timeout = setTimeout(this.loadNextCategory.bind(this), 100); }} 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) => ( {
<ScrollView categories.tabs.map((tab, i) => (
key={i} <ScrollView
tabLabel={tab.tabLabel} key={tab.category}
{...scrollPersistTaps} tabLabel={tab.tabLabel}
> {...scrollProps}
{this.renderCategory(tab.category, i)} >
</ScrollView> {this.renderCategory(tab.category, i)}
)) </ScrollView>
} ))
</ScrollableTabView> }
</View> </ScrollableTabView>
// </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)}
> >
<Avatar <Text style={styles.fixedMentionAvatar}>{item.username}</Text>
style={{ margin: 8 }} <Text>Notify {item.desc} in this room</Text>
text={item.username || item.name}
size={30}
baseUrl={this.props.baseUrl}
/>
<Text>{item.username || item.name }</Text>
</TouchableOpacity> </TouchableOpacity>
) )
renderEmoji() { renderMentionEmoji = (item) => {
const emojiContainer = ( if (item.name) {
<View style={styles.emojiContainer}> return (
<EmojiPicker onEmojiSelected={emoji => this._onEmojiSelected(emoji)} /> <CustomEmoji
</View> key='mention-item-avatar'
); style={styles.mentionItemCustomEmoji}
const { showEmojiContainer, messageboxHeight } = this.state; emoji={item}
return <AnimatedContainer visible={showEmojiContainer} subview={emojiContainer} messageboxHeight={messageboxHeight} />; baseUrl={this.props.baseUrl}
} />
renderMentions() { );
const usersList = ( }
<FlatList
style={styles.mentionList}
data={this.state.mentions}
renderItem={({ item }) => this.renderMentionItem(item)}
keyExtractor={item => item._id}
{...scrollPersistTaps}
/>
);
const { showMentionsContainer, messageboxHeight } = this.state;
return <AnimatedContainer visible={showMentionsContainer} subview={usersList} messageboxHeight={messageboxHeight} />;
}
render() {
const { height } = this.state;
return ( return (
<View> <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
key='mention-item-avatar'
style={{ margin: 8 }}
text={item.username || item.name}
size={30}
baseUrl={this.props.baseUrl}
/>,
<Text key='mention-item-name'>{ item.username || item.name }</Text>
]
}
</TouchableOpacity>
);
}
renderMentions = () => (
<FlatList
key='messagebox-container'
style={styles.mentionList}
data={this.state.mentions}
renderItem={({ item }) => this.renderMentionItem(item)}
keyExtractor={item => item._id || item}
keyboardShouldPersistTaps='always'
/>
);
renderContent() {
return (
[
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 />
<Image {url.image ?
style={styles.image} <Image
source={{ uri: encodeURI(url.image) }} style={styles.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 {
<View style={styles.firstUnread}> componentWillUnmount() {
<View style={styles.firstUnreadLine} /> LayoutAnimation.linear();
<Text style={styles.firstUnreadBadge}>unread messages</Text> }
</View> render() {
); return (
<View style={styles.firstUnread}>
export default UnreadSeparator; <View style={styles.firstUnreadLine} />
<Text style={styles.firstUnreadBadge}>unread messages</Text>
</View>
);
}
}

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 &&
this.state.end !== true
) {
this.setState({
loadingMore: true
});
requestAnimationFrame(() => {
const lastRowData = this.data[this.data.length - 1];
if (!lastRowData) {
return;
}
RocketChat.loadMessagesForRoom(this.rid, lastRowData.ts, ({ end }) => {
this.setState({
loadingMore: false,
end
});
});
});
} }
if (!this.state.loaded) {
alert(2);
return;
}
requestAnimationFrame(() => {
const lastRowData = data[data.length - 1];
if (!lastRowData) {
return;
}
RocketChat.loadMessagesForRoom(this.rid, lastRowData.ts, ({ end }) => end && this.setState({
end
}));
});
} }
updateState = debounce(() => { 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) => {
this.props.setLastOpen(null); RocketChat.sendMessage(this.rid, message).then(() => {
}); 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} onEndReached={this.onEndReached}
renderHeader={typing} renderRow={item => this.renderItem(item)}
onEndReached={this.onEndReached} />
dataSource={this.state.dataSource}
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": {