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
command: |
cd ios
fastlane pilot upload --changelog "$(sh ../.circleci/changelog.sh)"
fastlane pilot upload --ipa ios/RocketChatRN.ipa --changelog "$(sh ../.circleci/changelog.sh)"
workflows:
version: 2

View File

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

View File

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

View File

@ -16,6 +16,7 @@ import com.facebook.soloader.SoLoader;
import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage;
import com.brentvatne.react.ReactVideoPackage;
import com.remobile.toast.RCTToastPackage;
import com.wix.reactnativekeyboardinput.KeyboardInputPackage;
import java.util.Arrays;
import java.util.List;
@ -33,16 +34,17 @@ public class MainApplication extends Application implements ReactApplication {
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new SvgPackage(),
new ImagePickerPackage(),
new VectorIconsPackage(),
new RNFetchBlobPackage(),
new ZeroconfReactPackage(),
new RealmReactPackage(),
new ReactNativePushNotificationPackage(),
new ReactVideoPackage(),
new SplashScreenReactPackage(),
new RCTToastPackage()
new SvgPackage(),
new ImagePickerPackage(),
new VectorIconsPackage(),
new RNFetchBlobPackage(),
new ZeroconfReactPackage(),
new RealmReactPackage(),
new ReactNativePushNotificationPackage(),
new ReactVideoPackage(),
new SplashScreenReactPackage(),
new RCTToastPackage(),
new KeyboardInputPackage(MainApplication.this)
);
}
};

View File

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

View File

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

View File

@ -1,4 +1,6 @@
rootProject.name = 'RocketChatRN'
include ':reactnativekeyboardinput'
project(':reactnativekeyboardinput').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keyboard-input/lib/android')
include ':react-native-splash-screen'
project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android')
include ':react-native-video'

View File

@ -28,7 +28,17 @@ export const FORGOT_PASSWORD = createRequestTypes('FORGOT_PASSWORD', [
]);
export const USER = createRequestTypes('USER', ['SET']);
export const ROOMS = createRequestTypes('ROOMS', [...defaultTypes, 'SET_SEARCH']);
export const ROOM = createRequestTypes('ROOM', ['ADD_USER_TYPING', 'REMOVE_USER_TYPING', 'SOMEONE_TYPING', 'OPEN', 'CLOSE', 'USER_TYPING', 'MESSAGE_RECEIVED', 'SET_LAST_OPEN']);
export const ROOM = createRequestTypes('ROOM', [
'ADD_USER_TYPING',
'REMOVE_USER_TYPING',
'SOMEONE_TYPING',
'OPEN',
'CLOSE',
'USER_TYPING',
'MESSAGE_RECEIVED',
'SET_LAST_OPEN',
'LAYOUT_ANIMATION'
]);
export const APP = createRequestTypes('APP', ['READY', 'INIT']);
export const MESSAGES = createRequestTypes('MESSAGES', [
...defaultTypes,
@ -55,7 +65,8 @@ export const MESSAGES = createRequestTypes('MESSAGES', [
'TOGGLE_PIN_SUCCESS',
'TOGGLE_PIN_FAILURE',
'SET_INPUT',
'CLEAR_INPUT'
'CLEAR_INPUT',
'TOGGLE_REACTION_PICKER'
]);
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [
...defaultTypes,
@ -81,4 +92,4 @@ export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'REQUEST'
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const KEYBOARD = createRequestTypes('KEYBOARD', ['OPEN', 'CLOSE']);

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
};
}
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
};
}
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,
{
toValue: finalValue,
duration: 150
duration: 150,
useNativeDriver: true
}
).start();
}

View File

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

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { StyleSheet, Text, View } from 'react-native';
import { StyleSheet, Text, View, ViewPropTypes } from 'react-native';
import { CachedImage } from 'react-native-img-cache';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
@ -23,8 +23,16 @@ const styles = StyleSheet.create({
@connect(state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
}))
class Avatar extends React.PureComponent {
export default class Avatar extends React.PureComponent {
static propTypes = {
style: ViewPropTypes.style,
baseUrl: PropTypes.string,
text: PropTypes.string.isRequired,
avatar: PropTypes.string,
size: PropTypes.number,
borderRadius: PropTypes.number,
type: PropTypes.string
};
render() {
const {
text = '', size = 25, baseUrl, borderRadius = 4, style, avatar, type = 'd'
@ -76,14 +84,3 @@ class Avatar extends React.PureComponent {
);
}
}
Avatar.propTypes = {
style: PropTypes.object,
baseUrl: PropTypes.string,
text: PropTypes.string.isRequired,
avatar: PropTypes.string,
size: PropTypes.number,
borderRadius: PropTypes.number,
type: PropTypes.string
};
export default Avatar;

View File

@ -1,4 +1,5 @@
import React from 'react';
import { ViewPropTypes } from 'react-native';
import PropTypes from 'prop-types';
import { CachedImage } from 'react-native-img-cache';
import { connect } from 'react-redux';
@ -6,19 +7,21 @@ import { connect } from 'react-redux';
@connect(state => ({
baseUrl: state.settings.Site_Url
}))
export default class extends React.PureComponent {
export default class CustomEmoji extends React.Component {
static propTypes = {
baseUrl: PropTypes.string.isRequired,
emoji: PropTypes.object.isRequired,
style: PropTypes.object
style: ViewPropTypes.style
}
shouldComponentUpdate() {
return false;
}
render() {
const { baseUrl, emoji, style } = this.props;
return (
<CachedImage
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 styles from './styles';
export default class extends React.PureComponent {
export default class TabBar extends React.PureComponent {
static propTypes = {
goToPage: PropTypes.func,
activeTab: PropTypes.number,
tabs: PropTypes.array
tabs: PropTypes.array,
tabEmojiStyle: PropTypes.object
}
render() {
return (
<View style={styles.tabsContainer}>
{this.props.tabs.map((tab, i) => (
<TouchableOpacity activeOpacity={0.7} key={tab} onPress={() => this.props.goToPage(i)} style={styles.tab}>
<Text style={styles.tabEmoji}>{tab}</Text>
<TouchableOpacity
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} />}
</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 = [
{
tabLabel: '🕒',

View File

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

View File

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

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 = {
subview: PropTypes.object.isRequired
}

View File

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

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 PropTypes from 'prop-types';
import { View, TextInput, SafeAreaView, Platform, FlatList, Text, TouchableOpacity, Keyboard } from 'react-native';
import { View, TextInput, SafeAreaView, FlatList, Text, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
import ImagePicker from 'react-native-image-picker';
import { connect } from 'react-redux';
import { userTyping } from '../../actions/room';
import { emojify } from 'react-emojione';
import { KeyboardAccessoryView } from 'react-native-keyboard-input';
import { userTyping, layoutAnimation } from '../../actions/room';
import RocketChat from '../../lib/rocketchat';
import { editRequest, editCancel, clearInput } from '../../actions/messages';
import styles from './style';
import styles from './styles';
import MyIcon from '../icons';
import database from '../../lib/realm';
import Avatar from '../Avatar';
import AnimatedContainer from './AnimatedContainer';
import EmojiPicker from './EmojiPicker';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import CustomEmoji from '../EmojiPicker/CustomEmoji';
import { emojis } from '../../emojis';
import './EmojiKeyboard';
const MENTIONS_TRACKING_TYPE_USERS = '@';
const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
const onlyUnique = function onlyUnique(value, index, self) {
return self.indexOf(({ _id }) => value._id === _id) === index;
@ -25,13 +28,13 @@ const onlyUnique = function onlyUnique(value, index, self) {
room: state.room,
message: state.messages.message,
editing: state.messages.editing,
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
isKeyboardOpen: state.keyboard.isOpen
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
}), dispatch => ({
editCancel: () => dispatch(editCancel()),
editRequest: message => dispatch(editRequest(message)),
typing: status => dispatch(userTyping(status)),
clearInput: () => dispatch(clearInput())
clearInput: () => dispatch(clearInput()),
layoutAnimation: () => dispatch(layoutAnimation())
}))
export default class MessageBox extends React.PureComponent {
static propTypes = {
@ -44,21 +47,23 @@ export default class MessageBox extends React.PureComponent {
editing: PropTypes.bool,
typing: PropTypes.func,
clearInput: PropTypes.func,
isKeyboardOpen: PropTypes.bool
layoutAnimation: PropTypes.func
}
constructor(props) {
super(props);
this.state = {
height: 20,
messageboxHeight: 0,
text: '',
mentions: [],
showMentionsContainer: false,
showEmojiContainer: false
showEmojiKeyboard: false,
trackingType: ''
};
this.users = [];
this.rooms = [];
this.emojis = [];
this.customEmojis = [];
this._onEmojiSelected = this._onEmojiSelected.bind(this);
}
componentWillReceiveProps(nextProps) {
if (this.props.message !== nextProps.message && nextProps.message.msg) {
@ -66,22 +71,22 @@ export default class MessageBox extends React.PureComponent {
this.component.focus();
} else if (!nextProps.message) {
this.setState({ text: '' });
} else if (this.props.isKeyboardOpen !== nextProps.isKeyboardOpen && nextProps.isKeyboardOpen) {
this.closeEmoji();
}
}
onChange() {
onChangeText(text) {
this.setState({ text });
requestAnimationFrame(() => {
const { start, end } = this.component._lastNativeSelection;
const cursor = Math.max(start, end);
const text = this.component._lastNativeText;
const lastNativeText = this.component._lastNativeText;
const regexp = /(#|@)([a-z._-]+)$/im;
const regexp = /(#|@|:)([a-z0-9._-]+)$/im;
const result = text.substr(0, cursor).match(regexp);
const result = lastNativeText.substr(0, cursor).match(regexp);
if (!result) {
return this.stopTrackingMention();
@ -92,9 +97,8 @@ export default class MessageBox extends React.PureComponent {
});
}
onChangeText(text) {
this.setState({ text });
onKeyboardResigned() {
this.closeEmoji();
}
get leftButtons() {
@ -108,7 +112,7 @@ export default class MessageBox extends React.PureComponent {
onPress={() => this.editCancel()}
/>);
}
return !this.state.showEmojiContainer ? (<Icon
return !this.state.showEmojiKeyboard ? (<Icon
style={styles.actionButtons}
onPress={() => this.openEmoji()}
accessibilityLabel='Open emoji selector'
@ -147,10 +151,6 @@ export default class MessageBox extends React.PureComponent {
return icons;
}
updateSize = (height) => {
this.setState({ height: height + (Platform.OS === 'ios' ? 0 : 0) });
}
addFile = () => {
const options = {
maxHeight: 1960,
@ -184,11 +184,12 @@ export default class MessageBox extends React.PureComponent {
this.setState({ text: '' });
}
async openEmoji() {
await this.setState({ showEmojiContainer: !this.state.showEmojiContainer });
Keyboard.dismiss();
await this.setState({
showEmojiKeyboard: true
});
}
closeEmoji() {
this.setState({ showEmojiContainer: false });
this.setState({ showEmojiKeyboard: false });
}
submit(message) {
this.setState({ text: '' });
@ -212,11 +213,21 @@ export default class MessageBox extends React.PureComponent {
});
}
_getFixedMentions(keyword) {
if ('all'.indexOf(keyword) !== -1) {
this.users = [{ _id: -1, username: 'all', desc: 'all' }, ...this.users];
}
if ('here'.indexOf(keyword) !== -1) {
this.users = [{ _id: -2, username: 'here', desc: 'active users' }, ...this.users];
}
}
async _getUsers(keyword) {
this.users = database.objects('users');
if (keyword) {
this.users = this.users.filtered('username CONTAINS[c] $0', keyword);
}
this._getFixedMentions(keyword);
this.setState({ mentions: this.users.slice() });
const usernames = [];
@ -244,8 +255,9 @@ export default class MessageBox extends React.PureComponent {
console.log('spotlight canceled');
} finally {
delete this.oldPromise;
this.users = database.objects('users').filtered('username CONTAINS[c] $0', keyword);
this.setState({ mentions: this.users.slice() });
this.users = database.objects('users').filtered('username CONTAINS[c] $0', keyword).slice();
this._getFixedMentions(keyword);
this.setState({ mentions: this.users });
}
}
@ -289,25 +301,44 @@ export default class MessageBox extends React.PureComponent {
}
}
_getEmojis(keyword) {
if (keyword) {
this.customEmojis = database.objects('customEmojis').filtered('name CONTAINS[c] $0', keyword).slice(0, 4);
this.emojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, 4);
const mergedEmojis = [...this.customEmojis, ...this.emojis];
this.setState({ mentions: mergedEmojis });
}
}
stopTrackingMention() {
this.setState({
showMentionsContainer: false,
mentions: []
mentions: [],
trackingType: ''
});
this.users = [];
this.rooms = [];
this.customEmojis = [];
this.emojis = [];
}
identifyMentionKeyword(keyword, type) {
this.updateMentions(keyword, type);
if (!this.state.showMentionsContainer) {
this.props.layoutAnimation();
}
this.setState({
showMentionsContainer: true
showMentionsContainer: true,
showEmojiKeyboard: false,
trackingType: type
});
this.updateMentions(keyword, type);
}
updateMentions = (keyword, type) => {
if (type === MENTIONS_TRACKING_TYPE_USERS) {
this._getUsers(keyword);
} else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) {
this._getEmojis(keyword);
} else {
this._getRooms(keyword);
}
@ -320,17 +351,20 @@ export default class MessageBox extends React.PureComponent {
const cursor = Math.max(start, end);
const regexp = /([a-z._-]+)$/im;
const regexp = /([a-z0-9._-]+)$/im;
const result = msg.substr(0, cursor).replace(regexp, '');
const text = `${ result }${ item.username || item.name } ${ msg.slice(cursor) }`;
const mentionName = this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ?
`${ item.name || item }:` : (item.username || item.name);
const text = `${ result }${ mentionName } ${ msg.slice(cursor) }`;
this.component.setNativeProps({ text });
this.setState({ text });
this.component.focus();
requestAnimationFrame(() => this.stopTrackingMention());
}
_onEmojiSelected(emoji) {
_onEmojiSelected(keyboardId, params) {
const { text } = this.state;
const { emoji } = params;
let newText = '';
// if messagebox has an active cursor
@ -345,73 +379,116 @@ export default class MessageBox extends React.PureComponent {
this.component.setNativeProps({ text: newText });
this.setState({ text: newText });
}
renderMentionItem = item => (
renderFixedMentionItem = item => (
<TouchableOpacity
style={styles.mentionItem}
onPress={() => this._onPressMention(item)}
>
<Avatar
style={{ margin: 8 }}
text={item.username || item.name}
size={30}
baseUrl={this.props.baseUrl}
/>
<Text>{item.username || item.name }</Text>
<Text style={styles.fixedMentionAvatar}>{item.username}</Text>
<Text>Notify {item.desc} in this room</Text>
</TouchableOpacity>
)
renderEmoji() {
const emojiContainer = (
<View style={styles.emojiContainer}>
<EmojiPicker onEmojiSelected={emoji => this._onEmojiSelected(emoji)} />
</View>
);
const { showEmojiContainer, messageboxHeight } = this.state;
return <AnimatedContainer visible={showEmojiContainer} subview={emojiContainer} messageboxHeight={messageboxHeight} />;
}
renderMentions() {
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;
renderMentionEmoji = (item) => {
if (item.name) {
return (
<CustomEmoji
key='mention-item-avatar'
style={styles.mentionItemCustomEmoji}
emoji={item}
baseUrl={this.props.baseUrl}
/>
);
}
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
key='messagebox'
style={[styles.textBox, (this.props.editing ? styles.editing : null)]}
onLayout={event => this.setState({ messageboxHeight: event.nativeEvent.layout.height })}
>
<View style={styles.textArea}>
{this.leftButtons}
<TextInput
ref={component => this.component = component}
style={[styles.textBoxInput, { height }]}
style={styles.textBoxInput}
returnKeyType='default'
blurOnSubmit={false}
placeholder='New Message'
onChangeText={text => this.onChangeText(text)}
onChange={event => this.onChange(event)}
value={this.state.text}
underlineColorAndroid='transparent'
defaultValue=''
multiline
placeholderTextColor='#9EA2A8'
onContentSizeChange={e => this.updateSize(e.nativeEvent.contentSize.height)}
/>
{this.rightButtons}
</View>
</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;
@ -9,8 +9,6 @@ export default StyleSheet.create({
alignItems: 'center',
borderTopWidth: 1,
borderTopColor: '#D8D8D8',
paddingHorizontal: 15,
paddingVertical: 15,
zIndex: 2
},
safeAreaView: {
@ -23,14 +21,14 @@ export default StyleSheet.create({
flexGrow: 0
},
textBoxInput: {
paddingVertical: 0,
paddingHorizontal: 10,
textAlignVertical: 'top',
textAlignVertical: 'center',
maxHeight: 120,
flexGrow: 1,
width: 1,
paddingTop: 0,
paddingBottom: 0
paddingTop: 15,
paddingBottom: 15,
paddingLeft: 0,
paddingRight: 0
},
editing: {
backgroundColor: '#fff5df'
@ -39,7 +37,8 @@ export default StyleSheet.create({
color: '#2F343D',
fontSize: 20,
textAlign: 'center',
paddingHorizontal: 5,
padding: 15,
paddingHorizontal: 21,
flex: 0
},
actionRow: {
@ -85,5 +84,26 @@ export default StyleSheet.create({
borderTopColor: '#ECECEC',
borderTopWidth: 1,
backgroundColor: '#fff'
},
mentionItemCustomEmoji: {
margin: 8,
width: 30,
height: 30
},
mentionItemEmoji: {
width: 46,
height: 36,
fontSize: Platform.OS === 'ios' ? 30 : 25,
textAlign: 'center'
},
fixedMentionAvatar: {
fontWeight: 'bold',
textAlign: 'center',
width: 46
},
emojiKeyboardContainer: {
flex: 1,
borderTopColor: '#ECECEC',
borderTopWidth: 1
}
});

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text } from 'react-native';
import { StyleSheet, Text, Keyboard } from 'react-native';
import { connect } from 'react-redux';
const styles = StyleSheet.create({
@ -23,12 +23,15 @@ export default class Typing extends React.Component {
shouldComponentUpdate(nextProps) {
return this.props.usersTyping.join() !== nextProps.usersTyping.join();
}
onPress = () => {
Keyboard.dismiss();
}
get usersTyping() {
const users = this.props.usersTyping.filter(_username => this.props.username !== _username);
return users.length ? `${ users.join(' ,') } ${ users.length > 1 ? 'are' : 'is' } typing` : '';
}
render() {
return (<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 = {
file: PropTypes.object.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 { emojify } from 'react-emojione';
import styles from './styles';
import CustomEmoji from '../CustomEmoji';
import CustomEmoji from '../EmojiPicker/CustomEmoji';
const BlockCode = ({ node, state }) => (
<Text
@ -111,9 +111,8 @@ const Markdown = ({ msg, customEmojis }) => {
const emojiExtension = customEmojis[content];
if (emojiExtension) {
const emoji = { extension: emojiExtension, content };
const style = StyleSheet.flatten(styles.customEmoji);
element.props.children = (
<CustomEmoji key={state.key} style={style} emoji={emoji} />
<CustomEmoji key={state.key} style={styles.customEmoji} emoji={emoji} />
);
}
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 = {
title: 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 (
<TouchableOpacity onPress={() => onPress(url.url)} style={styles.button}>
<QuoteMark />
<Image
style={styles.image}
source={{ uri: encodeURI(url.image) }}
/>
{url.image ?
<Image
style={styles.image}
source={{ uri: encodeURI(url.image) }}
/>
: null
}
<View style={styles.textContainer}>
<Text style={styles.title}>{url.title}</Text>
<Text style={styles.description} numberOfLines={1}>{url.description}</Text>

View File

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

View File

@ -5,6 +5,10 @@ export default StyleSheet.create({
flexGrow: 1,
flexShrink: 1
},
flex: {
flexDirection: 'row',
flex: 1
},
message: {
padding: 12,
paddingTop: 6,
@ -33,5 +37,38 @@ export default StyleSheet.create({
borderWidth: 1,
borderRadius: 5,
padding: 5
},
reactionsContainer: {
flexDirection: 'row',
flexWrap: 'wrap'
},
reactionContainer: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
padding: 3,
borderWidth: 1,
borderColor: '#cccccc',
borderRadius: 4,
marginRight: 5,
marginBottom: 5,
height: 23,
width: 35
},
reactionCount: {
fontSize: 12,
marginLeft: 2,
fontWeight: '600',
color: '#aaaaaa'
},
reactionEmoji: {
fontSize: 12
},
reactionCustomEmoji: {
width: 15,
height: 15
},
avatar: {
marginRight: 10
}
});

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 = {
name: 'messagesEditedBy',
@ -174,7 +189,8 @@ const messagesSchema = {
status: { type: 'int', optional: true },
pinned: { type: 'bool', optional: true },
starred: { type: 'bool', optional: true },
editedBy: 'messagesEditedBy'
editedBy: 'messagesEditedBy',
reactions: { type: 'list', objectType: 'messagesReactions' }
}
};
@ -223,7 +239,9 @@ const schema = [
url,
frequentlyUsedEmojiSchema,
customEmojiAliasesSchema,
customEmojisSchema
customEmojisSchema,
messagesReactionsSchema,
messagesReactionsUsernamesSchema
];
class DB {
databases = {

View File

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

View File

@ -2,14 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import { ViewPropTypes } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import { connect } from 'react-redux';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import { setKeyboardOpen, setKeyboardClosed } from '../actions/keyboard';
@connect(null, dispatch => ({
setKeyboardOpen: () => dispatch(setKeyboardOpen()),
setKeyboardClosed: () => dispatch(setKeyboardClosed())
}))
export default class KeyboardView extends React.PureComponent {
static propTypes = {
style: ViewPropTypes.style,
@ -19,9 +13,7 @@ export default class KeyboardView extends React.PureComponent {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
]),
setKeyboardOpen: PropTypes.func,
setKeyboardClosed: PropTypes.func
])
}
render() {
@ -34,8 +26,6 @@ export default class KeyboardView extends React.PureComponent {
alwaysBounceVertical={false}
extraHeight={this.props.keyboardVerticalOffset}
behavior='position'
onKeyboardWillShow={() => this.props.setKeyboardOpen()}
onKeyboardWillHide={() => this.props.setKeyboardClosed()}
>
{this.props.children}
</KeyboardAwareScrollView>

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,20 @@
import { ListView as OldList } from 'realm/react-native';
import React from 'react';
import cloneReferencedElement from 'react-clone-referenced-element';
import { ScrollView, ListView as OldList2 } from 'react-native';
import { ScrollView, ListView as OldList2, LayoutAnimation } from 'react-native';
import moment from 'moment';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import DateSeparator from './DateSeparator';
import UnreadSeparator from './UnreadSeparator';
import styles from './styles';
import debounce from '../../utils/debounce';
import Typing from '../../containers/Typing';
import database from '../../lib/realm';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
const DEFAULT_SCROLL_CALLBACK_THROTTLE = 50;
const DEFAULT_SCROLL_CALLBACK_THROTTLE = 100;
export class DataSource extends OldList.DataSource {
getRowData(sectionIndex: number, rowIndex: number): any {
@ -20,6 +27,56 @@ export class DataSource extends OldList.DataSource {
}
}
const ds = new DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
export class List extends React.Component {
static propTypes = {
onEndReached: PropTypes.func,
renderFooter: PropTypes.func,
renderRow: PropTypes.func,
room: PropTypes.string,
end: PropTypes.bool
};
constructor(props) {
super(props);
this.data = database
.objects('messages')
.filtered('rid = $0', props.room)
.sorted('ts', true);
this.dataSource = ds.cloneWithRows(this.data);
}
componentDidMount() {
this.data.addListener(this.updateState);
}
shouldComponentUpdate(nextProps) {
return this.props.end !== nextProps.end;
}
componentWillUpdate() {
LayoutAnimation.easeInEaseOut();
}
updateState = debounce(() => {
// this.setState({
this.dataSource = this.dataSource.cloneWithRows(this.data);
this.forceUpdate();
// });
}, 100);
render() {
return (<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 => ({
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 { View, StyleSheet, Text } from 'react-native';
import { View, StyleSheet, Text, LayoutAnimation } from 'react-native';
const styles = StyleSheet.create({
firstUnread: {
@ -22,11 +22,16 @@ const styles = StyleSheet.create({
}
});
const UnreadSeparator = () => (
<View style={styles.firstUnread}>
<View style={styles.firstUnreadLine} />
<Text style={styles.firstUnreadBadge}>unread messages</Text>
</View>
);
export default UnreadSeparator;
export default class UnreadSeparator extends React.PureComponent {
componentWillUnmount() {
LayoutAnimation.linear();
}
render() {
return (
<View style={styles.firstUnread}>
<View style={styles.firstUnreadLine} />
<Text style={styles.firstUnreadBadge}>unread messages</Text>
</View>
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

70
package-lock.json generated
View File

@ -1481,6 +1481,16 @@
"babel-helper-is-void-0": "0.2.0"
}
},
"babel-plugin-module-resolver": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-2.7.1.tgz",
"integrity": "sha1-GL48Qt31n3pFbJ4FEs2ROU9uS+E=",
"requires": {
"find-babel-config": "1.1.0",
"glob": "7.1.2",
"resolve": "1.5.0"
}
},
"babel-plugin-react-transform": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/babel-plugin-react-transform/-/babel-plugin-react-transform-3.0.0.tgz",
@ -2172,6 +2182,18 @@
"semver": "5.4.1"
}
},
"babel-preset-expo": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-4.0.0.tgz",
"integrity": "sha512-EWFC6WJzZX5t2zZfLNdJXUkNMusUkxP5V+GrXaSk8pKbWGjE3TD2i33ncpF/4aQM9QGDm+SH6pImZJOqIDlRUw==",
"requires": {
"babel-plugin-module-resolver": "2.7.1",
"babel-plugin-transform-decorators-legacy": "1.3.4",
"babel-plugin-transform-exponentiation-operator": "6.24.1",
"babel-plugin-transform-export-extensions": "6.22.0",
"babel-preset-react-native": "4.0.0"
}
},
"babel-preset-fbjs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.1.4.tgz",
@ -4610,11 +4632,6 @@
"resolved": "https://registry.npmjs.org/email-validator/-/email-validator-1.1.1.tgz",
"integrity": "sha512-vkcJJZEb7JXDY883Nx1Lkmb6noM3j1SfSt8L9tVFhZPnPQiFq+Nkd5evc77+tRVS4ChTUSr34voThsglI/ja/A=="
},
"emoji-datasource": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/emoji-datasource/-/emoji-datasource-4.0.3.tgz",
"integrity": "sha1-1gDnDwVoMnyyjPp79B1T88SGeQE="
},
"emoji-regex": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
@ -5771,6 +5788,15 @@
}
}
},
"find-babel-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.1.0.tgz",
"integrity": "sha1-rMAQQ6Z0n+w0Qpvmtk9ULrtdY1U=",
"requires": {
"json5": "0.5.1",
"path-exists": "3.0.0"
}
},
"find-cache-dir": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz",
@ -12542,6 +12568,21 @@
"react-native-iphone-x-helper": "1.0.1"
}
},
"react-native-keyboard-input": {
"version": "git+https://github.com/RocketChat/react-native-keyboard-input.git#38273b0513f69a5e6e0719f65a675f9f2b5ee883",
"requires": {
"lodash": "4.17.4",
"react-native-keyboard-tracking-view": "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git#3a4084f0a1063e23ae6435facdf1f79152558d15"
},
"dependencies": {
"react-native-keyboard-tracking-view": {
"version": "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git#3a4084f0a1063e23ae6435facdf1f79152558d15"
}
}
},
"react-native-keyboard-tracking-view": {
"version": "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git#3a4084f0a1063e23ae6435facdf1f79152558d15"
},
"react-native-loading-spinner-overlay": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/react-native-loading-spinner-overlay/-/react-native-loading-spinner-overlay-0.5.2.tgz",
@ -12593,9 +12634,9 @@
}
},
"react-native-optimized-flatlist": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/react-native-optimized-flatlist/-/react-native-optimized-flatlist-1.0.3.tgz",
"integrity": "sha1-tFN58lpXu05vhZwZDZmEexgR4Ak=",
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/react-native-optimized-flatlist/-/react-native-optimized-flatlist-1.0.4.tgz",
"integrity": "sha512-PMoZRJAHKzd/ahYKUzt43AJ+kVhHpOSTvBhJdQqooZXw312xADWpR7iDvBAbBiRGkmk0yM4GJacd9TMft6q/Gg==",
"requires": {
"prop-types": "15.6.0"
}
@ -12605,6 +12646,14 @@
"resolved": "https://registry.npmjs.org/react-native-push-notification/-/react-native-push-notification-3.0.1.tgz",
"integrity": "sha1-DiPbMC0Du0o/KNwHLcryqaEXjtg="
},
"react-native-responsive-ui": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/react-native-responsive-ui/-/react-native-responsive-ui-1.1.1.tgz",
"integrity": "sha1-60GDnU85Uf8CVmAYXDapqc4zdZ8=",
"requires": {
"lodash": "4.17.4"
}
},
"react-native-scrollable-tab-view": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/react-native-scrollable-tab-view/-/react-native-scrollable-tab-view-0.8.0.tgz",
@ -14440,11 +14489,6 @@
"strip-ansi": "4.0.0"
}
},
"string.fromcodepoint": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz",
"integrity": "sha1-jZeDM8C8klOPUPOD5IiPPlYZ1lM="
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",

View File

@ -13,7 +13,7 @@
"android": "react-native run-android",
"storybook": "storybook start -p 7007",
"snyk-protect": "snyk protect",
"prepare": "npm run snyk-protect"
"prepare": "exit 0"
},
"rnpm": {
"assets": [
@ -27,9 +27,9 @@
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-remove-console": "^6.8.5",
"babel-polyfill": "^6.26.0",
"babel-preset-expo": "^4.0.0",
"deep-equal": "^1.0.1",
"ejson": "^2.1.2",
"emoji-datasource": "^4.0.3",
"lodash": "^4.17.4",
"moment": "^2.20.1",
"prop-types": "^15.6.0",
@ -45,11 +45,14 @@
"react-native-image-picker": "^0.26.7",
"react-native-img-cache": "^1.5.2",
"react-native-keyboard-aware-scroll-view": "^0.4.1",
"react-native-keyboard-input": "git+https://github.com/RocketChat/react-native-keyboard-input.git",
"react-native-keyboard-tracking-view": "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git",
"react-native-loading-spinner-overlay": "^0.5.2",
"react-native-meteor": "^1.2.0",
"react-native-modal": "^4.1.1",
"react-native-optimized-flatlist": "^1.0.3",
"react-native-optimized-flatlist": "^1.0.4",
"react-native-push-notification": "^3.0.1",
"react-native-responsive-ui": "^1.1.1",
"react-native-scrollable-tab-view": "^0.8.0",
"react-native-slider": "^0.11.0",
"react-native-splash-screen": "^3.0.6",
@ -71,7 +74,6 @@
"remote-redux-devtools": "^0.5.12",
"simple-markdown": "^0.3.1",
"snyk": "^1.61.1",
"string.fromcodepoint": "^0.2.1",
"strip-ansi": "^4.0.0"
},
"devDependencies": {