Merge remote-tracking branch 'origin/develop' into add-lastmessage
This commit is contained in:
commit
d01c6c0ffd
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -18,3 +18,4 @@
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
|
|
||||||
android.useDeprecatedNdk=true
|
android.useDeprecatedNdk=true
|
||||||
|
# VERSIONCODE=999999999
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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']);
|
|
||||||
|
|
|
@ -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
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -55,3 +55,9 @@ export function setLastOpen(date = new Date()) {
|
||||||
date
|
date
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function layoutAnimation() {
|
||||||
|
return {
|
||||||
|
type: types.ROOM.LAYOUT_ANIMATION
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 });
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
|
@ -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 }` }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
))}
|
))}
|
|
@ -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: '🕒',
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -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,
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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 = {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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({
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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",
|
||||||
|
|
10
package.json
10
package.json
|
@ -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": {
|
||||||
|
|
Loading…
Reference in New Issue