Update dependencies (#431)
* Update dependencies * Lint and test * Added react-native fork * rn 57 * Lint and tests updated * Update xcode on circleci * Use legacy build system * Update tests
2
.babelrc
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"presets": ["react-native"],
|
||||
"presets": ["module:metro-react-native-babel-preset"],
|
||||
"plugins": [["@babel/plugin-proposal-decorators", { "legacy": true }]],
|
||||
"env": {
|
||||
"production": {
|
||||
|
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
|
||||
e2e-test:
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
xcode: "10.0.0"
|
||||
|
||||
environment:
|
||||
BASH_ENV: "~/.nvm/nvm.sh"
|
||||
|
@ -87,7 +87,8 @@ jobs:
|
|||
- image: circleci/android:api-27-node8-alpha
|
||||
|
||||
environment:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError"
|
||||
# GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError"
|
||||
GRADLE_OPTS: -Xmx2048m -Dorg.gradle.daemon=false
|
||||
JVM_OPTS: -Xmx4096m
|
||||
TERM: dumb
|
||||
BASH_ENV: "~/.nvm/nvm.sh"
|
||||
|
@ -170,7 +171,7 @@ jobs:
|
|||
|
||||
ios-build:
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
xcode: "10.0.0"
|
||||
|
||||
environment:
|
||||
BASH_ENV: "~/.nvm/nvm.sh"
|
||||
|
@ -236,7 +237,7 @@ jobs:
|
|||
|
||||
ios-testflight:
|
||||
macos:
|
||||
xcode: "9.0"
|
||||
xcode: "10.0.0"
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
|
|
@ -13,7 +13,8 @@ module.exports = {
|
|||
"ecmaVersion": 2017,
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread" : true,
|
||||
"jsx": true
|
||||
"jsx": true,
|
||||
"legacyDecorators": true
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
|
@ -65,6 +66,7 @@ module.exports = {
|
|||
"no-dupe-args": 2,
|
||||
"no-dupe-class-members": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-else-return": [0, {allowElseIf: true}],
|
||||
"no-empty": 2,
|
||||
"no-empty-character-class": 2,
|
||||
"no-ex-assign": 2,
|
||||
|
@ -125,7 +127,8 @@ module.exports = {
|
|||
"object-shorthand": 2,
|
||||
"consistent-return": 0,
|
||||
"global-require": "off",
|
||||
"react-native/no-unused-styles": 2
|
||||
"react-native/no-unused-styles": 2,
|
||||
"react/jsx-one-expression-per-line": 0
|
||||
},
|
||||
"globals": {
|
||||
"__DEV__": true
|
||||
|
|
|
@ -4,7 +4,6 @@ exports[`render channel 1`] = `
|
|||
<View>
|
||||
<View
|
||||
accessibilityLabel="general, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -200,7 +199,6 @@ exports[`render no icon 1`] = `
|
|||
<View>
|
||||
<View
|
||||
accessibilityLabel="name, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -396,7 +394,6 @@ exports[`render private group 1`] = `
|
|||
<View>
|
||||
<View
|
||||
accessibilityLabel="private-group, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -593,7 +590,6 @@ exports[`render unread +999 1`] = `
|
|||
<View>
|
||||
<View
|
||||
accessibilityLabel="name, 1000 alerts, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -856,7 +852,6 @@ exports[`render unread 1`] = `
|
|||
<View>
|
||||
<View
|
||||
accessibilityLabel="name, 1 alert, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -1119,7 +1114,6 @@ exports[`renders correctly 1`] = `
|
|||
<View>
|
||||
<View
|
||||
accessibilityLabel="name, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
|
|
@ -300,7 +300,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
<View>
|
||||
<View
|
||||
accessibilityLabel="rocket.cat, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -532,7 +531,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
</View>
|
||||
<View
|
||||
accessibilityLabel="rocket.cat, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -768,7 +766,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
</View>
|
||||
<View
|
||||
accessibilityLabel="rocket.cat, 1 alert, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -1026,7 +1023,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
</View>
|
||||
<View
|
||||
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 9 alerts, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -1288,7 +1284,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
</View>
|
||||
<View
|
||||
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 99 alerts, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -1546,7 +1541,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
</View>
|
||||
<View
|
||||
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 100 alerts, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -1804,7 +1798,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
</View>
|
||||
<View
|
||||
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 100000 alerts, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -2062,7 +2055,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
</View>
|
||||
<View
|
||||
accessibilityLabel="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, 100000 alerts, you were mentioned, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -2320,7 +2312,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
</View>
|
||||
<View
|
||||
accessibilityLabel="W, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -2552,7 +2543,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
</View>
|
||||
<View
|
||||
accessibilityLabel="WW, last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
@ -2784,7 +2774,6 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
|
|||
</View>
|
||||
<View
|
||||
accessibilityLabel=", last message Nov 10"
|
||||
accessibilityTraits="selected"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
|
|
|
@ -198,24 +198,23 @@ dependencies {
|
|||
implementation project(':react-native-audio')
|
||||
implementation project(":reactnativekeyboardinput")
|
||||
implementation project(':react-native-video')
|
||||
implementation project(':react-native-svg')
|
||||
implementation project(':react-native-vector-icons')
|
||||
implementation project(':rn-fetch-blob')
|
||||
implementation project(':react-native-zeroconf')
|
||||
implementation project(':@remobile/react-native-toast')
|
||||
implementation project(':react-native-fast-image')
|
||||
implementation project(':realm')
|
||||
implementation project(':react-native-navigation')
|
||||
implementation project(':reactnativenotifications')
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "com.android.support:appcompat-v7:27.1.0"
|
||||
implementation "com.android.support:support-v4:27.1.+"
|
||||
implementation 'com.android.support:customtabs:27.1.0'
|
||||
implementation 'com.android.support:design:27.1.0'
|
||||
implementation "com.android.support:appcompat-v7:27.1.1"
|
||||
implementation "com.android.support:support-v4:27.1.1"
|
||||
implementation 'com.android.support:customtabs:27.1.1'
|
||||
implementation 'com.android.support:design:27.1.1'
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
implementation 'com.facebook.fresco:animated-gif:1.9.0'
|
||||
implementation 'com.facebook.fresco:animated-webp:1.9.0'
|
||||
implementation 'com.facebook.fresco:webpsupport:1.9.0'
|
||||
implementation 'com.facebook.fresco:fresco:1.10.0'
|
||||
implementation 'com.facebook.fresco:animated-gif:1.10.0'
|
||||
implementation 'com.facebook.fresco:animated-webp:1.10.0'
|
||||
implementation 'com.facebook.fresco:webpsupport:1.10.0'
|
||||
implementation('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') {
|
||||
transitive = true;
|
||||
}
|
||||
|
|
|
@ -6,13 +6,11 @@ import android.os.Bundle;
|
|||
import com.AlexanderZaytsev.RNI18n.RNI18nPackage;
|
||||
import com.reactnative.ivpusic.imagepicker.PickerPackage;
|
||||
import com.RNFetchBlob.RNFetchBlobPackage;
|
||||
import com.balthazargronon.RCTZeroconf.ZeroconfReactPackage;
|
||||
import com.brentvatne.react.ReactVideoPackage;
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.dylanvann.fastimage.FastImageViewPackage;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import com.horcrux.svg.SvgPackage;
|
||||
import com.oblador.vectoricons.VectorIconsPackage;
|
||||
import com.reactnativenavigation.NavigationApplication;
|
||||
import com.remobile.toast.RCTToastPackage;
|
||||
|
@ -62,10 +60,8 @@ public class MainApplication extends NavigationApplication implements INotificat
|
|||
new RNDeviceInfo(),
|
||||
new RNGestureHandlerPackage(),
|
||||
new PickerPackage(),
|
||||
new SvgPackage(),
|
||||
new VectorIconsPackage(),
|
||||
new RNFetchBlobPackage(),
|
||||
new ZeroconfReactPackage(),
|
||||
new RealmReactPackage(),
|
||||
new ReactVideoPackage(),
|
||||
new RCTToastPackage(),
|
||||
|
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 33 KiB |
|
@ -21,12 +21,8 @@ include ':reactnativekeyboardinput'
|
|||
project(':reactnativekeyboardinput').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keyboard-input/lib/android')
|
||||
include ':react-native-video'
|
||||
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android')
|
||||
include ':react-native-svg'
|
||||
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
|
||||
include ':react-native-vector-icons'
|
||||
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
||||
include ':react-native-zeroconf'
|
||||
project(':react-native-zeroconf').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-zeroconf/android')
|
||||
include ':realm'
|
||||
project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
|
||||
include ':react-native-navigation'
|
||||
|
|
|
@ -3,12 +3,17 @@ class NavigationActionsClass {
|
|||
this.navigator = navigator;
|
||||
}
|
||||
|
||||
push = params => this.navigator && this.navigator.push(params);
|
||||
pop = params => this.navigator && this.navigator.pop(params);
|
||||
popToRoot = params => this.navigator && this.navigator.popToRoot(params);
|
||||
resetTo = params => this.navigator && this.navigator.resetTo(params);
|
||||
toggleDrawer = params => this.navigator && this.navigator.toggleDrawer(params);
|
||||
dismissModal = params => this.navigator && this.navigator.dismissModal(params);
|
||||
push = params => this.navigator && this.navigator.push(params)
|
||||
|
||||
pop = params => this.navigator && this.navigator.pop(params)
|
||||
|
||||
popToRoot = params => this.navigator && this.navigator.popToRoot(params)
|
||||
|
||||
resetTo = params => this.navigator && this.navigator.resetTo(params)
|
||||
|
||||
toggleDrawer = params => this.navigator && this.navigator.toggleDrawer(params)
|
||||
|
||||
dismissModal = params => this.navigator && this.navigator.dismissModal(params)
|
||||
}
|
||||
|
||||
export const NavigationActions = new NavigationActionsClass();
|
||||
|
|
|
@ -103,4 +103,3 @@ export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL
|
|||
|
||||
export const INCREMENT = 'INCREMENT';
|
||||
export const DECREMENT = 'DECREMENT';
|
||||
|
||||
|
|
|
@ -26,4 +26,3 @@ export function setLoading(loading) {
|
|||
loading
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ export default class Panel extends React.Component {
|
|||
children: PropTypes.node.isRequired,
|
||||
style: PropTypes.object
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
@ -18,15 +19,22 @@ export default class Panel extends React.Component {
|
|||
this.open = false;
|
||||
this.opacity = 0;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const initialValue = !this.props.open ? this.height : 0;
|
||||
this.state.animation.setValue(initialValue);
|
||||
const { animation } = this.state;
|
||||
const { open } = this.props;
|
||||
const initialValue = !open ? this.height : 0;
|
||||
animation.setValue(initialValue);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { animation } = this.state;
|
||||
const { open } = this.props;
|
||||
|
||||
if (this.first) {
|
||||
this.first = false;
|
||||
if (!this.props.open) {
|
||||
this.state.animation.setValue(0);
|
||||
if (!open) {
|
||||
animation.setValue(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -37,9 +45,9 @@ export default class Panel extends React.Component {
|
|||
const initialValue = !nextProps.open ? this.height : 0;
|
||||
const finalValue = !nextProps.open ? 0 : this.height;
|
||||
|
||||
this.state.animation.setValue(initialValue);
|
||||
animation.setValue(initialValue);
|
||||
Animated.timing(
|
||||
this.state.animation,
|
||||
animation,
|
||||
{
|
||||
toValue: finalValue,
|
||||
duration: 150,
|
||||
|
@ -47,16 +55,21 @@ export default class Panel extends React.Component {
|
|||
}
|
||||
).start();
|
||||
}
|
||||
|
||||
set _height(h) {
|
||||
this.height = h || this.height;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { animation } = this.state;
|
||||
const { style, children } = this.props;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[{ height: this.state.animation }, this.props.style]}
|
||||
style={[{ height: animation }, style]}
|
||||
>
|
||||
<View onLayout={({ nativeEvent }) => this._height = nativeEvent.layout.height} style={{ position: !this.first ? 'relative' : 'absolute' }}>
|
||||
{this.props.children}
|
||||
{children}
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
|
|
|
@ -14,10 +14,11 @@ export default class Fade extends React.Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { visible } = this.props;
|
||||
this.state = {
|
||||
visible: props.visible
|
||||
visible
|
||||
};
|
||||
this._visibility = new Animated.Value(this.props.visible ? 1 : 0);
|
||||
this._visibility = new Animated.Value(visible ? 1 : 0);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
@ -34,6 +35,7 @@ export default class Fade extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { visible } = this.state;
|
||||
const { style, children, ...rest } = this.props;
|
||||
|
||||
const containerStyle = {
|
||||
|
@ -53,8 +55,8 @@ export default class Fade extends React.Component {
|
|||
|
||||
const combinedStyle = [containerStyle, style];
|
||||
return (
|
||||
<Animated.View style={this.state.visible ? combinedStyle : containerStyle} {...rest}>
|
||||
<Text>{this.state.visible ? children : null}</Text>
|
||||
<Animated.View style={visible ? combinedStyle : containerStyle} {...rest}>
|
||||
<Text>{visible ? children : null}</Text>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StyleSheet, Text, View, ViewPropTypes } from 'react-native';
|
||||
import {
|
||||
StyleSheet, Text, View, ViewPropTypes
|
||||
} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
|
||||
|
||||
|
@ -29,14 +31,16 @@ export default class Avatar extends React.PureComponent {
|
|||
type: PropTypes.string,
|
||||
children: PropTypes.object,
|
||||
forceInitials: PropTypes.bool
|
||||
};
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
text: '',
|
||||
size: 25,
|
||||
type: 'd',
|
||||
borderRadius: 4,
|
||||
forceInitials: false
|
||||
};
|
||||
}
|
||||
|
||||
state = { showInitials: true };
|
||||
|
||||
// componentDidMount() {
|
||||
|
@ -93,8 +97,9 @@ export default class Avatar extends React.PureComponent {
|
|||
// }
|
||||
|
||||
render() {
|
||||
const { showInitials } = this.state;
|
||||
const {
|
||||
text, size, baseUrl, borderRadius, style, avatar, type, forceInitials
|
||||
text, size, baseUrl, borderRadius, style, avatar, type, forceInitials, children
|
||||
} = this.props;
|
||||
const { initials, color } = avatarInitialsAndColor(`${ text }`);
|
||||
|
||||
|
@ -133,17 +138,19 @@ export default class Avatar extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<View style={[styles.iconContainer, iconContainerStyle, style]}>
|
||||
{this.state.showInitials ?
|
||||
{showInitials
|
||||
? (
|
||||
<Text
|
||||
style={[styles.avatarInitials, avatarInitialsStyle]}
|
||||
allowFontScaling={false}
|
||||
>
|
||||
{initials}
|
||||
</Text>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{image}
|
||||
{this.props.children}
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StyleSheet, View, Text, Platform, ActivityIndicator } from 'react-native';
|
||||
import {
|
||||
StyleSheet, View, Text, Platform, ActivityIndicator
|
||||
} from 'react-native';
|
||||
|
||||
import { COLOR_BUTTON_PRIMARY, COLOR_TEXT } from '../../constants/colors';
|
||||
import Touch from '../../utils/touch';
|
||||
|
@ -92,9 +94,9 @@ export default class Button extends React.PureComponent {
|
|||
]}
|
||||
>
|
||||
{
|
||||
loading ?
|
||||
<ActivityIndicator color={colors[`text_color_${ type }`]} /> :
|
||||
<Text style={[styles.text, styles[`text_color_${ type }`]]}>{title}</Text>
|
||||
loading
|
||||
? <ActivityIndicator color={colors[`text_color_${ type }`]} />
|
||||
: <Text style={[styles.text, styles[`text_color_${ type }`]]}>{title}</Text>
|
||||
}
|
||||
</View>
|
||||
</Touch>
|
||||
|
|
|
@ -8,9 +8,11 @@ export default class CustomEmoji extends React.Component {
|
|||
emoji: PropTypes.object.isRequired,
|
||||
style: ViewPropTypes.style
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { baseUrl, emoji, style } = this.props;
|
||||
return (
|
||||
|
|
|
@ -8,7 +8,7 @@ import styles from './styles';
|
|||
import CustomEmoji from './CustomEmoji';
|
||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
|
||||
const emojisPerRow = Platform.OS === 'ios' ? 8 : 9;
|
||||
const EMOJIS_PER_ROW = Platform.OS === 'ios' ? 8 : 9;
|
||||
|
||||
const renderEmoji = (emoji, size, baseUrl) => {
|
||||
if (emoji.isCustom) {
|
||||
|
@ -31,12 +31,14 @@ export default class EmojiCategory extends React.Component {
|
|||
onEmojiSelected: PropTypes.func,
|
||||
emojisPerRow: PropTypes.number,
|
||||
width: PropTypes.number
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { width, height } = this.props.window;
|
||||
const { window, width, emojisPerRow } = this.props;
|
||||
const { width: widthWidth, height: windowHeight } = window;
|
||||
|
||||
this.size = Math.min(this.props.width || width, height) / (this.props.emojisPerRow || emojisPerRow);
|
||||
this.size = Math.min(width || widthWidth, windowHeight) / (emojisPerRow || EMOJIS_PER_ROW);
|
||||
this.emojis = props.emojis;
|
||||
}
|
||||
|
||||
|
@ -45,12 +47,12 @@ export default class EmojiCategory extends React.Component {
|
|||
}
|
||||
|
||||
renderItem(emoji, size) {
|
||||
const { baseUrl } = this.props;
|
||||
const { baseUrl, onEmojiSelected } = this.props;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
key={emoji.isCustom ? emoji.content : emoji}
|
||||
onPress={() => this.props.onEmojiSelected(emoji)}
|
||||
onPress={() => onEmojiSelected(emoji)}
|
||||
testID={`reaction-picker-${ emoji.isCustom ? emoji.content : emoji }`}
|
||||
>
|
||||
{renderEmoji(emoji, size, baseUrl)}
|
||||
|
@ -58,12 +60,14 @@ export default class EmojiCategory extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { emojis } = this.props;
|
||||
|
||||
return (
|
||||
<OptimizedFlatList
|
||||
keyExtractor={item => (item.isCustom && item.content) || item}
|
||||
data={this.props.emojis}
|
||||
data={emojis}
|
||||
renderItem={({ item }) => this.renderItem(item, this.size)}
|
||||
numColumns={emojisPerRow}
|
||||
numColumns={EMOJIS_PER_ROW}
|
||||
initialNumToRender={45}
|
||||
getItemLayout={(data, index) => ({ length: this.size, offset: this.size * index, index })}
|
||||
removeClippedSubviews
|
||||
|
|
|
@ -12,18 +12,22 @@ export default class TabBar extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
tabs, goToPage, tabEmojiStyle, activeTab
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<View style={styles.tabsContainer}>
|
||||
{this.props.tabs.map((tab, i) => (
|
||||
{tabs.map((tab, i) => (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
key={tab}
|
||||
onPress={() => this.props.goToPage(i)}
|
||||
onPress={() => goToPage(i)}
|
||||
style={styles.tab}
|
||||
testID={`reaction-picker-${ tab }`}
|
||||
>
|
||||
<Text style={[styles.tabEmoji, this.props.tabEmojiStyle]}>{tab}</Text>
|
||||
{this.props.activeTab === i ? <View style={styles.activeTabLine} /> : <View style={styles.tabLine} />}
|
||||
<Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text>
|
||||
{activeTab === i ? <View style={styles.activeTabLine} /> : <View style={styles.tabLine} />}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
|
|
|
@ -49,36 +49,41 @@ export default class EmojiPicker extends Component {
|
|||
this.updateFrequentlyUsed();
|
||||
this.updateCustomEmojis();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.frequentlyUsed.removeAllListeners();
|
||||
this.customEmojis.removeAllListeners();
|
||||
}
|
||||
|
||||
onEmojiSelected(emoji) {
|
||||
const { onEmojiSelected } = this.props;
|
||||
if (emoji.isCustom) {
|
||||
const count = this._getFrequentlyUsedCount(emoji.content);
|
||||
this._addFrequentlyUsed({
|
||||
content: emoji.content, extension: emoji.extension, count, isCustom: true
|
||||
});
|
||||
this.props.onEmojiSelected(`:${ emoji.content }:`);
|
||||
onEmojiSelected(`:${ emoji.content }:`);
|
||||
} else {
|
||||
const content = emoji;
|
||||
const count = this._getFrequentlyUsedCount(content);
|
||||
this._addFrequentlyUsed({ content, count, isCustom: false });
|
||||
const shortname = `:${ emoji }:`;
|
||||
this.props.onEmojiSelected(emojify(shortname, { output: 'unicode' }), shortname);
|
||||
onEmojiSelected(emojify(shortname, { output: 'unicode' }), shortname);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
_addFrequentlyUsed = protectedFunction((emoji) => {
|
||||
database.write(() => {
|
||||
database.create('frequentlyUsedEmoji', emoji, true);
|
||||
});
|
||||
})
|
||||
|
||||
_getFrequentlyUsedCount = (content) => {
|
||||
const emojiRow = this.frequentlyUsed.filtered('content == $0', content);
|
||||
return emojiRow.length ? emojiRow[0].count + 1 : 1;
|
||||
}
|
||||
|
||||
updateFrequentlyUsed() {
|
||||
const frequentlyUsed = map(this.frequentlyUsed.slice(), (item) => {
|
||||
if (item.isCustom) {
|
||||
|
@ -90,17 +95,19 @@ export default class EmojiPicker extends Component {
|
|||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
renderCategory(category, i) {
|
||||
const { frequentlyUsed, customEmojis } = this.state;
|
||||
const { emojisPerRow, width, baseUrl } = this.props;
|
||||
|
||||
let emojis = [];
|
||||
if (i === 0) {
|
||||
emojis = this.state.frequentlyUsed;
|
||||
emojis = frequentlyUsed;
|
||||
} else if (i === 1) {
|
||||
emojis = this.state.customEmojis;
|
||||
emojis = customEmojis;
|
||||
} else {
|
||||
emojis = emojisByCategory[category];
|
||||
}
|
||||
|
@ -109,21 +116,23 @@ export default class EmojiPicker extends Component {
|
|||
emojis={emojis}
|
||||
onEmojiSelected={emoji => this.onEmojiSelected(emoji)}
|
||||
style={styles.categoryContainer}
|
||||
size={this.props.emojisPerRow}
|
||||
width={this.props.width}
|
||||
baseUrl={this.props.baseUrl}
|
||||
size={emojisPerRow}
|
||||
width={width}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.show) {
|
||||
const { show } = this.state;
|
||||
const { tabEmojiStyle } = this.props;
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
// <View style={styles.container}>
|
||||
<ScrollableTabView
|
||||
renderTabBar={() => <TabBar tabEmojiStyle={this.props.tabEmojiStyle} />}
|
||||
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} />}
|
||||
contentProps={scrollProps}
|
||||
style={styles.background}
|
||||
>
|
||||
|
@ -140,7 +149,6 @@ export default class EmojiPicker extends Component {
|
|||
))
|
||||
}
|
||||
</ScrollableTabView>
|
||||
// </View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StyleSheet, View, Modal, Animated } from 'react-native';
|
||||
import {
|
||||
StyleSheet, View, Modal, Animated
|
||||
} from 'react-native';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -27,8 +29,11 @@ export default class Loading extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { opacity, scale } = this.state;
|
||||
const { visible } = this.props;
|
||||
|
||||
this.opacityAnimation = Animated.timing(
|
||||
this.state.opacity,
|
||||
opacity,
|
||||
{
|
||||
toValue: 1,
|
||||
duration: 1000,
|
||||
|
@ -37,7 +42,7 @@ export default class Loading extends React.PureComponent {
|
|||
);
|
||||
this.scaleAnimation = Animated.loop(Animated.sequence([
|
||||
Animated.timing(
|
||||
this.state.scale,
|
||||
scale,
|
||||
{
|
||||
toValue: 0,
|
||||
duration: 1000,
|
||||
|
@ -45,7 +50,7 @@ export default class Loading extends React.PureComponent {
|
|||
}
|
||||
),
|
||||
Animated.timing(
|
||||
this.state.scale,
|
||||
scale,
|
||||
{
|
||||
toValue: 1,
|
||||
duration: 1000,
|
||||
|
@ -54,13 +59,14 @@ export default class Loading extends React.PureComponent {
|
|||
)
|
||||
]));
|
||||
|
||||
if (this.props.visible) {
|
||||
if (visible) {
|
||||
this.startAnimations();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.visible && this.props.visible !== prevProps.visible) {
|
||||
const { visible } = this.props;
|
||||
if (visible && visible !== prevProps.visible) {
|
||||
this.startAnimations();
|
||||
}
|
||||
}
|
||||
|
@ -84,13 +90,16 @@ export default class Loading extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const scale = this.state.scale.interpolate({
|
||||
const { opacity, scale } = this.state;
|
||||
const { visible } = this.props;
|
||||
|
||||
const scaleAnimation = scale.interpolate({
|
||||
inputRange: [0, 0.5, 1],
|
||||
outputRange: [1, 1.1, 1]
|
||||
});
|
||||
return (
|
||||
<Modal
|
||||
visible={this.props.visible}
|
||||
visible={visible}
|
||||
transparent
|
||||
onRequestClose={() => {}}
|
||||
>
|
||||
|
@ -98,9 +107,9 @@ export default class Loading extends React.PureComponent {
|
|||
<Animated.Image
|
||||
source={require('../static/images/logo.png')}
|
||||
style={[styles.image, {
|
||||
opacity: this.state.opacity,
|
||||
opacity,
|
||||
transform: [{
|
||||
scale
|
||||
scale: scaleAnimation
|
||||
}]
|
||||
}]}
|
||||
/>
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Alert, Clipboard, Vibration, Share } from 'react-native';
|
||||
import {
|
||||
Alert, Clipboard, Vibration, Share
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import * as moment from 'moment';
|
||||
|
||||
import {
|
||||
deleteRequest,
|
||||
editInit,
|
||||
toggleStarRequest,
|
||||
togglePinRequest,
|
||||
actionsHide,
|
||||
toggleReactionPicker,
|
||||
replyInit
|
||||
actionsHide as actionsHideAction,
|
||||
deleteRequest as deleteRequestAction,
|
||||
editInit as editInitAction,
|
||||
replyInit as replyInitAction,
|
||||
togglePinRequest as togglePinRequestAction,
|
||||
toggleReactionPicker as toggleReactionPickerAction,
|
||||
toggleStarRequest as toggleStarRequestAction
|
||||
} from '../actions/messages';
|
||||
import { showToast } from '../utils/info';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
@ -29,13 +31,13 @@ import I18n from '../i18n';
|
|||
Message_AllowStarring: state.settings.Message_AllowStarring
|
||||
}),
|
||||
dispatch => ({
|
||||
actionsHide: () => dispatch(actionsHide()),
|
||||
deleteRequest: message => dispatch(deleteRequest(message)),
|
||||
editInit: message => dispatch(editInit(message)),
|
||||
toggleStarRequest: message => dispatch(toggleStarRequest(message)),
|
||||
togglePinRequest: message => dispatch(togglePinRequest(message)),
|
||||
toggleReactionPicker: message => dispatch(toggleReactionPicker(message)),
|
||||
replyInit: (message, mention) => dispatch(replyInit(message, mention))
|
||||
actionsHide: () => dispatch(actionsHideAction()),
|
||||
deleteRequest: message => dispatch(deleteRequestAction(message)),
|
||||
editInit: message => dispatch(editInitAction(message)),
|
||||
toggleStarRequest: message => dispatch(toggleStarRequestAction(message)),
|
||||
togglePinRequest: message => dispatch(togglePinRequestAction(message)),
|
||||
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message)),
|
||||
replyInit: (message, mention) => dispatch(replyInitAction(message, mention))
|
||||
})
|
||||
)
|
||||
export default class MessageActions extends React.Component {
|
||||
|
@ -63,6 +65,8 @@ export default class MessageActions extends React.Component {
|
|||
this.handleActionPress = this.handleActionPress.bind(this);
|
||||
this.setPermissions();
|
||||
|
||||
const { Message_AllowStarring, Message_AllowPinning } = this.props;
|
||||
|
||||
// Cancel
|
||||
this.options = [I18n.t('Cancel')];
|
||||
this.CANCEL_INDEX = 0;
|
||||
|
@ -98,13 +102,13 @@ export default class MessageActions extends React.Component {
|
|||
}
|
||||
|
||||
// Star
|
||||
if (this.props.Message_AllowStarring) {
|
||||
if (Message_AllowStarring) {
|
||||
this.options.push(I18n.t(props.actionMessage.starred ? 'Unstar' : 'Star'));
|
||||
this.STAR_INDEX = this.options.length - 1;
|
||||
}
|
||||
|
||||
// Pin
|
||||
if (this.props.Message_AllowPinning) {
|
||||
if (Message_AllowPinning) {
|
||||
this.options.push(I18n.t(props.actionMessage.pinned ? 'Unpin' : 'Pin'));
|
||||
this.PIN_INDEX = this.options.length - 1;
|
||||
}
|
||||
|
@ -129,8 +133,9 @@ export default class MessageActions extends React.Component {
|
|||
}
|
||||
|
||||
setPermissions() {
|
||||
const { room } = this.props;
|
||||
const permissions = ['edit-message', 'delete-message', 'force-delete-message'];
|
||||
const result = RocketChat.hasPermission(permissions, this.props.room.rid);
|
||||
const result = RocketChat.hasPermission(permissions, room.rid);
|
||||
this.hasEditPermission = result[permissions[0]];
|
||||
this.hasDeletePermission = result[permissions[1]];
|
||||
this.hasForceDeletePermission = result[permissions[2]];
|
||||
|
@ -146,20 +151,27 @@ export default class MessageActions extends React.Component {
|
|||
|
||||
isOwn = props => props.actionMessage.u && props.actionMessage.u._id === props.user.id;
|
||||
|
||||
isRoomReadOnly = () => this.props.room.ro;
|
||||
isRoomReadOnly = () => {
|
||||
const { room } = this.props;
|
||||
return room.ro;
|
||||
}
|
||||
|
||||
canReactWhenReadOnly = () => this.props.room.reactWhenReadOnly;
|
||||
canReactWhenReadOnly = () => {
|
||||
const { room } = this.props;
|
||||
return room.reactWhenReadOnly;
|
||||
}
|
||||
|
||||
allowEdit = (props) => {
|
||||
if (this.isRoomReadOnly()) {
|
||||
return false;
|
||||
}
|
||||
const editOwn = this.isOwn(props);
|
||||
const { Message_AllowEditing: isEditAllowed } = this.props;
|
||||
const { Message_AllowEditing: isEditAllowed, Message_AllowEditing_BlockEditInMinutes } = this.props;
|
||||
|
||||
if (!(this.hasEditPermission || (isEditAllowed && editOwn))) {
|
||||
return false;
|
||||
}
|
||||
const blockEditInMinutes = this.props.Message_AllowEditing_BlockEditInMinutes;
|
||||
const blockEditInMinutes = Message_AllowEditing_BlockEditInMinutes;
|
||||
if (blockEditInMinutes) {
|
||||
let msgTs;
|
||||
if (props.actionMessage.ts != null) {
|
||||
|
@ -179,14 +191,14 @@ export default class MessageActions extends React.Component {
|
|||
return false;
|
||||
}
|
||||
const deleteOwn = this.isOwn(props);
|
||||
const { Message_AllowDeleting: isDeleteAllowed } = this.props;
|
||||
const { Message_AllowDeleting: isDeleteAllowed, Message_AllowDeleting_BlockDeleteInMinutes } = this.props;
|
||||
if (!(this.hasDeletePermission || (isDeleteAllowed && deleteOwn) || this.hasForceDeletePermission)) {
|
||||
return false;
|
||||
}
|
||||
if (this.hasForceDeletePermission) {
|
||||
return true;
|
||||
}
|
||||
const blockDeleteInMinutes = this.props.Message_AllowDeleting_BlockDeleteInMinutes;
|
||||
const blockDeleteInMinutes = Message_AllowDeleting_BlockDeleteInMinutes;
|
||||
if (blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) {
|
||||
let msgTs;
|
||||
if (props.actionMessage.ts != null) {
|
||||
|
@ -201,7 +213,8 @@ export default class MessageActions extends React.Component {
|
|||
return true;
|
||||
}
|
||||
|
||||
handleDelete() {
|
||||
handleDelete = () => {
|
||||
const { deleteRequest, actionMessage } = this.props;
|
||||
Alert.alert(
|
||||
I18n.t('Are_you_sure_question_mark'),
|
||||
I18n.t('You_will_not_be_able_to_recover_this_message'),
|
||||
|
@ -213,56 +226,66 @@ export default class MessageActions extends React.Component {
|
|||
{
|
||||
text: I18n.t('Yes_action_it', { action: 'delete' }),
|
||||
style: 'destructive',
|
||||
onPress: () => this.props.deleteRequest(this.props.actionMessage)
|
||||
onPress: () => deleteRequest(actionMessage)
|
||||
}
|
||||
],
|
||||
{ cancelable: false }
|
||||
);
|
||||
}
|
||||
|
||||
handleEdit() {
|
||||
const { _id, msg, rid } = this.props.actionMessage;
|
||||
this.props.editInit({ _id, msg, rid });
|
||||
handleEdit = () => {
|
||||
const { actionMessage, editInit } = this.props;
|
||||
const { _id, msg, rid } = actionMessage;
|
||||
editInit({ _id, msg, rid });
|
||||
}
|
||||
|
||||
handleCopy = async() => {
|
||||
await Clipboard.setString(this.props.actionMessage.msg);
|
||||
const { actionMessage } = this.props;
|
||||
await Clipboard.setString(actionMessage.msg);
|
||||
showToast(I18n.t('Copied_to_clipboard'));
|
||||
}
|
||||
|
||||
handleShare = async() => {
|
||||
const { actionMessage } = this.props;
|
||||
Share.share({
|
||||
message: this.props.actionMessage.msg.content.replace(/<(?:.|\n)*?>/gm, '')
|
||||
message: actionMessage.msg.content.replace(/<(?:.|\n)*?>/gm, '')
|
||||
});
|
||||
};
|
||||
|
||||
handleStar() {
|
||||
this.props.toggleStarRequest(this.props.actionMessage);
|
||||
handleStar = () => {
|
||||
const { actionMessage, toggleStarRequest } = this.props;
|
||||
toggleStarRequest(actionMessage);
|
||||
}
|
||||
|
||||
async handlePermalink() {
|
||||
const permalink = await this.getPermalink(this.props.actionMessage);
|
||||
handlePermalink = async() => {
|
||||
const { actionMessage } = this.props;
|
||||
const permalink = await this.getPermalink(actionMessage);
|
||||
Clipboard.setString(permalink);
|
||||
showToast(I18n.t('Permalink_copied_to_clipboard'));
|
||||
}
|
||||
|
||||
handlePin() {
|
||||
this.props.togglePinRequest(this.props.actionMessage);
|
||||
handlePin = () => {
|
||||
const { actionMessage, togglePinRequest } = this.props;
|
||||
togglePinRequest(actionMessage);
|
||||
}
|
||||
|
||||
handleReply() {
|
||||
this.props.replyInit(this.props.actionMessage, true);
|
||||
handleReply = () => {
|
||||
const { actionMessage, replyInit } = this.props;
|
||||
replyInit(actionMessage, true);
|
||||
}
|
||||
|
||||
handleQuote() {
|
||||
this.props.replyInit(this.props.actionMessage, false);
|
||||
handleQuote = () => {
|
||||
const { actionMessage, replyInit } = this.props;
|
||||
replyInit(actionMessage, false);
|
||||
}
|
||||
|
||||
handleReaction() {
|
||||
this.props.toggleReactionPicker(this.props.actionMessage);
|
||||
handleReaction = () => {
|
||||
const { actionMessage, toggleReactionPicker } = this.props;
|
||||
toggleReactionPicker(actionMessage);
|
||||
}
|
||||
|
||||
handleActionPress = (actionIndex) => {
|
||||
const { actionsHide } = this.props;
|
||||
switch (actionIndex) {
|
||||
case this.REPLY_INDEX:
|
||||
this.handleReply();
|
||||
|
@ -297,7 +320,7 @@ export default class MessageActions extends React.Component {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
this.props.actionsHide();
|
||||
actionsHide();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -12,9 +12,11 @@ export default class EmojiKeyboard extends React.PureComponent {
|
|||
const state = store.getState();
|
||||
this.baseUrl = state.settings.Site_Url || state.server ? state.server.server : '';
|
||||
}
|
||||
|
||||
onEmojiSelected = (emoji) => {
|
||||
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.emojiKeyboardContainer} testID='messagebox-keyboard-emoji'>
|
||||
|
|
|
@ -34,7 +34,7 @@ export default class FilesActions extends Component {
|
|||
}
|
||||
|
||||
handleActionPress = (actionIndex) => {
|
||||
const { takePhoto, chooseFromLibrary } = this.props;
|
||||
const { takePhoto, chooseFromLibrary, hideActions } = this.props;
|
||||
switch (actionIndex) {
|
||||
case this.PHOTO_INDEX:
|
||||
takePhoto();
|
||||
|
@ -45,7 +45,7 @@ export default class FilesActions extends Component {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
this.props.hideActions();
|
||||
hideActions();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, SafeAreaView, Platform, PermissionsAndroid, Text } from 'react-native';
|
||||
import {
|
||||
View, SafeAreaView, Platform, PermissionsAndroid, Text
|
||||
} from 'react-native';
|
||||
import { AudioRecorder, AudioUtils } from 'react-native-audio';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import styles from './styles';
|
||||
|
@ -61,7 +63,7 @@ export default class extends React.PureComponent {
|
|||
//
|
||||
AudioRecorder.onFinished = (data) => {
|
||||
if (!this.recordingCanceled && Platform.OS === 'ios') {
|
||||
this._finishRecording(data.status === 'OK', data.audioFileURL);
|
||||
this.finishRecording(data.status === 'OK', data.audioFileURL);
|
||||
}
|
||||
};
|
||||
AudioRecorder.startRecording();
|
||||
|
@ -73,9 +75,10 @@ export default class extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
_finishRecording(didSucceed, filePath) {
|
||||
finishRecording = (didSucceed, filePath) => {
|
||||
const { onFinish } = this.props;
|
||||
if (!didSucceed) {
|
||||
return this.props.onFinish && this.props.onFinish(didSucceed);
|
||||
return onFinish && onFinish(didSucceed);
|
||||
}
|
||||
|
||||
const path = filePath.startsWith('file://') ? filePath.split('file://')[1] : filePath;
|
||||
|
@ -84,7 +87,7 @@ export default class extends React.PureComponent {
|
|||
store: 'Uploads',
|
||||
path
|
||||
};
|
||||
return this.props.onFinish && this.props.onFinish(fileInfo);
|
||||
return onFinish && onFinish(fileInfo);
|
||||
}
|
||||
|
||||
finishAudioMessage = async() => {
|
||||
|
@ -92,10 +95,10 @@ export default class extends React.PureComponent {
|
|||
this.recording = false;
|
||||
const filePath = await AudioRecorder.stopRecording();
|
||||
if (Platform.OS === 'android') {
|
||||
this._finishRecording(true, filePath);
|
||||
this.finishRecording(true, filePath);
|
||||
}
|
||||
} catch (err) {
|
||||
this._finishRecording(false);
|
||||
this.finishRecording(false);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
@ -104,10 +107,12 @@ export default class extends React.PureComponent {
|
|||
this.recording = false;
|
||||
this.recordingCanceled = true;
|
||||
await AudioRecorder.stopRecording();
|
||||
return this._finishRecording(false);
|
||||
return this.finishRecording(false);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { currentTime } = this.state;
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
key='messagebox-recording'
|
||||
|
@ -123,7 +128,7 @@ export default class extends React.PureComponent {
|
|||
accessibilityTraits='button'
|
||||
onPress={this.cancelAudioMessage}
|
||||
/>
|
||||
<Text key='currentTime' style={styles.textBoxInput}>{this.state.currentTime}</Text>
|
||||
<Text key='currentTime' style={styles.textBoxInput}>{currentTime}</Text>
|
||||
<Icon
|
||||
style={[styles.actionButtons, { color: 'green' }]}
|
||||
name='check'
|
||||
|
|
|
@ -57,7 +57,8 @@ export default class ReplyPreview extends Component {
|
|||
}
|
||||
|
||||
close = () => {
|
||||
this.props.close();
|
||||
const { close } = this.props;
|
||||
close();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React, { Component } from 'react';
|
||||
import { View, Text, StyleSheet, Image, ScrollView, Platform } from 'react-native';
|
||||
import {
|
||||
View, Text, StyleSheet, Image, ScrollView, Platform
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Modal from 'react-native-modal';
|
||||
import { responsive } from 'react-native-responsive-ui';
|
||||
|
@ -85,9 +87,9 @@ export default class UploadModal extends Component {
|
|||
return (
|
||||
<Modal
|
||||
isVisible={isVisible}
|
||||
style={{ alignItems: 'center' }} // TODO: need this?
|
||||
onBackdropPress={() => this.props.close()}
|
||||
onBackButtonPress={() => this.props.close()}
|
||||
style={{ alignItems: 'center' }}
|
||||
onBackdropPress={() => close()}
|
||||
onBackButtonPress={() => close()}
|
||||
animationIn='fadeIn'
|
||||
animationOut='fadeOut'
|
||||
useNativeDriver
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, TextInput, FlatList, Text, TouchableOpacity, Alert } from 'react-native';
|
||||
import {
|
||||
View, TextInput, FlatList, Text, TouchableOpacity, Alert
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { connect } from 'react-redux';
|
||||
import { emojify } from 'react-emojione';
|
||||
import { KeyboardAccessoryView } from 'react-native-keyboard-input';
|
||||
import ImagePicker from 'react-native-image-crop-picker';
|
||||
|
||||
import { userTyping } from '../../actions/room';
|
||||
import { userTyping as userTypingAction } from '../../actions/room';
|
||||
import {
|
||||
editRequest as editRequestAction,
|
||||
editCancel as editCancelAction,
|
||||
replyCancel as replyCancelAction
|
||||
} from '../../actions/messages';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import { editRequest, editCancel, replyCancel } from '../../actions/messages';
|
||||
import styles from './styles';
|
||||
import MyIcon from '../icons';
|
||||
import database from '../../lib/realm';
|
||||
|
@ -48,10 +54,10 @@ const imagePickerConfig = {
|
|||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
username: state.login.user && state.login.user.username
|
||||
}), dispatch => ({
|
||||
editCancel: () => dispatch(editCancel()),
|
||||
editRequest: message => dispatch(editRequest(message)),
|
||||
typing: status => dispatch(userTyping(status)),
|
||||
closeReply: () => dispatch(replyCancel())
|
||||
editCancel: () => dispatch(editCancelAction()),
|
||||
editRequest: message => dispatch(editRequestAction(message)),
|
||||
typing: status => dispatch(userTypingAction(status)),
|
||||
closeReply: () => dispatch(replyCancelAction())
|
||||
}))
|
||||
export default class MessageBox extends React.PureComponent {
|
||||
static propTypes = {
|
||||
|
@ -86,13 +92,15 @@ export default class MessageBox extends React.PureComponent {
|
|||
this.rooms = [];
|
||||
this.emojis = [];
|
||||
this.customEmojis = [];
|
||||
this._onEmojiSelected = this._onEmojiSelected.bind(this);
|
||||
this.onEmojiSelected = this.onEmojiSelected.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.message !== nextProps.message && nextProps.message.msg) {
|
||||
const { message, replyMessage } = this.props;
|
||||
if (message !== nextProps.message && nextProps.message.msg) {
|
||||
this.setState({ text: nextProps.message.msg });
|
||||
this.component.focus();
|
||||
} else if (this.props.replyMessage !== nextProps.replyMessage && nextProps.replyMessage.msg) {
|
||||
} else if (replyMessage !== nextProps.replyMessage && nextProps.replyMessage.msg) {
|
||||
this.component.focus();
|
||||
} else if (!nextProps.message) {
|
||||
this.setState({ text: '' });
|
||||
|
@ -100,8 +108,10 @@ export default class MessageBox extends React.PureComponent {
|
|||
}
|
||||
|
||||
onChangeText(text) {
|
||||
const { typing } = this.props;
|
||||
|
||||
this.setState({ text });
|
||||
this.props.typing(text.length > 0);
|
||||
typing(text.length > 0);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const { start, end } = this.component._lastNativeSelection;
|
||||
|
@ -126,26 +136,74 @@ export default class MessageBox extends React.PureComponent {
|
|||
this.closeEmoji();
|
||||
}
|
||||
|
||||
onPressMention(item) {
|
||||
const { trackingType } = this.state;
|
||||
|
||||
const msg = this.component._lastNativeText;
|
||||
|
||||
const { start, end } = this.component._lastNativeSelection;
|
||||
|
||||
const cursor = Math.max(start, end);
|
||||
|
||||
const regexp = /([a-z0-9._-]+)$/im;
|
||||
|
||||
const result = msg.substr(0, cursor).replace(regexp, '');
|
||||
const mentionName = trackingType === MENTIONS_TRACKING_TYPE_EMOJIS
|
||||
? `${ item.name || item }:`
|
||||
: (item.username || item.name);
|
||||
const text = `${ result }${ mentionName } ${ msg.slice(cursor) }`;
|
||||
this.component.setNativeProps({ text });
|
||||
this.setState({ text });
|
||||
this.component.focus();
|
||||
requestAnimationFrame(() => this.stopTrackingMention());
|
||||
}
|
||||
|
||||
onEmojiSelected(keyboardId, params) {
|
||||
const { text } = this.state;
|
||||
const { emoji } = params;
|
||||
let newText = '';
|
||||
|
||||
// if messagebox has an active cursor
|
||||
if (this.component._lastNativeSelection) {
|
||||
const { start, end } = this.component._lastNativeSelection;
|
||||
const cursor = Math.max(start, end);
|
||||
newText = `${ text.substr(0, cursor) }${ emoji }${ text.substr(cursor) }`;
|
||||
} else {
|
||||
// if messagebox doesn't have a cursor, just append selected emoji
|
||||
newText = `${ text }${ emoji }`;
|
||||
}
|
||||
this.component.setNativeProps({ text: newText });
|
||||
this.setState({ text: newText });
|
||||
}
|
||||
|
||||
get leftButtons() {
|
||||
const { showEmojiKeyboard } = this.state;
|
||||
const { editing } = this.props;
|
||||
|
||||
if (editing) {
|
||||
return (<Icon
|
||||
return (
|
||||
<Icon
|
||||
style={styles.actionButtons}
|
||||
name='close'
|
||||
accessibilityLabel={I18n.t('Cancel_editing')}
|
||||
accessibilityTraits='button'
|
||||
onPress={() => this.editCancel()}
|
||||
testID='messagebox-cancel-editing'
|
||||
/>);
|
||||
/>
|
||||
);
|
||||
}
|
||||
return !this.state.showEmojiKeyboard ? (<Icon
|
||||
return !showEmojiKeyboard
|
||||
? (
|
||||
<Icon
|
||||
style={styles.actionButtons}
|
||||
onPress={() => this.openEmoji()}
|
||||
accessibilityLabel={I18n.t('Open_emoji_selector')}
|
||||
accessibilityTraits='button'
|
||||
name='mood'
|
||||
testID='messagebox-open-emoji'
|
||||
/>) : (<Icon
|
||||
/>)
|
||||
: (
|
||||
<Icon
|
||||
onPress={() => this.closeEmoji()}
|
||||
style={styles.actionButtons}
|
||||
accessibilityLabel={I18n.t('Close_emoji_selector')}
|
||||
|
@ -154,17 +212,19 @@ export default class MessageBox extends React.PureComponent {
|
|||
testID='messagebox-close-emoji'
|
||||
/>);
|
||||
}
|
||||
|
||||
get rightButtons() {
|
||||
const { text } = this.state;
|
||||
const icons = [];
|
||||
|
||||
if (this.state.text) {
|
||||
if (text) {
|
||||
icons.push(<MyIcon
|
||||
style={[styles.actionButtons, { color: '#1D74F5' }]}
|
||||
name='send'
|
||||
key='sendIcon'
|
||||
accessibilityLabel={I18n.t('Send message')}
|
||||
accessibilityTraits='button'
|
||||
onPress={() => this.submit(this.state.text)}
|
||||
onPress={() => this.submit(text)}
|
||||
testID='messagebox-send-message'
|
||||
/>);
|
||||
return icons;
|
||||
|
@ -198,123 +258,7 @@ export default class MessageBox extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
toggleFilesActions = () => {
|
||||
this.setState(prevState => ({ showFilesAction: !prevState.showFilesAction }));
|
||||
}
|
||||
|
||||
sendImageMessage = async(file) => {
|
||||
this.setState({ file: { isVisible: false } });
|
||||
const fileInfo = {
|
||||
name: file.name,
|
||||
description: file.description,
|
||||
size: file.size,
|
||||
type: file.mime,
|
||||
store: 'Uploads',
|
||||
path: file.path
|
||||
};
|
||||
try {
|
||||
await RocketChat.sendFileMessage(this.props.rid, fileInfo);
|
||||
} catch (e) {
|
||||
log('sendImageMessage', e);
|
||||
}
|
||||
}
|
||||
|
||||
takePhoto = async() => {
|
||||
try {
|
||||
const image = await ImagePicker.openCamera(imagePickerConfig);
|
||||
this.showUploadModal(image);
|
||||
} catch (e) {
|
||||
log('takePhoto', e);
|
||||
}
|
||||
}
|
||||
|
||||
chooseFromLibrary = async() => {
|
||||
try {
|
||||
const image = await ImagePicker.openPicker(imagePickerConfig);
|
||||
this.showUploadModal(image);
|
||||
} catch (e) {
|
||||
log('chooseFromLibrary', e);
|
||||
}
|
||||
}
|
||||
|
||||
showUploadModal = (file) => {
|
||||
this.setState({ file: { ...file, isVisible: true } });
|
||||
}
|
||||
|
||||
editCancel() {
|
||||
this.props.editCancel();
|
||||
this.setState({ text: '' });
|
||||
}
|
||||
|
||||
async openEmoji() {
|
||||
await this.setState({
|
||||
showEmojiKeyboard: true
|
||||
});
|
||||
}
|
||||
|
||||
async recordAudioMessage() {
|
||||
const recording = await Recording.permission();
|
||||
this.setState({ recording });
|
||||
}
|
||||
|
||||
finishAudioMessage = async(fileInfo) => {
|
||||
this.setState({
|
||||
recording: false
|
||||
});
|
||||
if (fileInfo) {
|
||||
try {
|
||||
await RocketChat.sendFileMessage(this.props.rid, fileInfo);
|
||||
} catch (e) {
|
||||
if (e && e.error === 'error-file-too-large') {
|
||||
return Alert.alert(I18n.t(e.error));
|
||||
}
|
||||
log('finishAudioMessage', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeEmoji() {
|
||||
this.setState({ showEmojiKeyboard: false });
|
||||
}
|
||||
|
||||
async submit(message) {
|
||||
this.setState({ text: '' });
|
||||
this.closeEmoji();
|
||||
this.stopTrackingMention();
|
||||
this.props.typing(false);
|
||||
if (message.trim() === '') {
|
||||
return;
|
||||
}
|
||||
// if is editing a message
|
||||
const {
|
||||
editing, replying
|
||||
} = this.props;
|
||||
|
||||
if (editing) {
|
||||
const { _id, rid } = this.props.message;
|
||||
this.props.editRequest({ _id, msg: message, rid });
|
||||
} else if (replying) {
|
||||
const {
|
||||
username, replyMessage, roomType, closeReply
|
||||
} = this.props;
|
||||
const permalink = await this.getPermalink(replyMessage);
|
||||
let msg = `[ ](${ permalink }) `;
|
||||
|
||||
// if original message wasn't sent by current user and neither from a direct room
|
||||
if (username !== replyMessage.u.username && roomType !== 'd' && replyMessage.mention) {
|
||||
msg += `@${ replyMessage.u.username } `;
|
||||
}
|
||||
|
||||
msg = `${ msg } ${ message }`;
|
||||
this.props.onSubmit(msg);
|
||||
closeReply();
|
||||
} else {
|
||||
// if is submiting a new message
|
||||
this.props.onSubmit(message);
|
||||
}
|
||||
}
|
||||
|
||||
_getFixedMentions(keyword) {
|
||||
getFixedMentions(keyword) {
|
||||
if ('all'.indexOf(keyword) !== -1) {
|
||||
this.users = [{ _id: -1, username: 'all' }, ...this.users];
|
||||
}
|
||||
|
@ -323,12 +267,12 @@ export default class MessageBox extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
async _getUsers(keyword) {
|
||||
async getUsers(keyword) {
|
||||
this.users = database.objects('users');
|
||||
if (keyword) {
|
||||
this.users = this.users.filtered('username CONTAINS[c] $0', keyword);
|
||||
}
|
||||
this._getFixedMentions(keyword);
|
||||
this.getFixedMentions(keyword);
|
||||
this.setState({ mentions: this.users.slice() });
|
||||
|
||||
const usernames = [];
|
||||
|
@ -359,12 +303,12 @@ export default class MessageBox extends React.PureComponent {
|
|||
} finally {
|
||||
delete this.oldPromise;
|
||||
this.users = database.objects('users').filtered('username CONTAINS[c] $0', keyword).slice();
|
||||
this._getFixedMentions(keyword);
|
||||
this.getFixedMentions(keyword);
|
||||
this.setState({ mentions: this.users });
|
||||
}
|
||||
}
|
||||
|
||||
async _getRooms(keyword = '') {
|
||||
async getRooms(keyword = '') {
|
||||
this.roomsCache = this.roomsCache || [];
|
||||
this.rooms = database.objects('subscriptions')
|
||||
.filtered('t != $0', 'd');
|
||||
|
@ -406,7 +350,7 @@ export default class MessageBox extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
_getEmojis(keyword) {
|
||||
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);
|
||||
|
@ -415,6 +359,149 @@ export default class MessageBox extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
toggleFilesActions = () => {
|
||||
this.setState(prevState => ({ showFilesAction: !prevState.showFilesAction }));
|
||||
}
|
||||
|
||||
sendImageMessage = async(file) => {
|
||||
const { rid } = this.props;
|
||||
|
||||
this.setState({ file: { isVisible: false } });
|
||||
const fileInfo = {
|
||||
name: file.name,
|
||||
description: file.description,
|
||||
size: file.size,
|
||||
type: file.mime,
|
||||
store: 'Uploads',
|
||||
path: file.path
|
||||
};
|
||||
try {
|
||||
await RocketChat.sendFileMessage(rid, fileInfo);
|
||||
} catch (e) {
|
||||
log('sendImageMessage', e);
|
||||
}
|
||||
}
|
||||
|
||||
takePhoto = async() => {
|
||||
try {
|
||||
const image = await ImagePicker.openCamera(imagePickerConfig);
|
||||
this.showUploadModal(image);
|
||||
} catch (e) {
|
||||
log('takePhoto', e);
|
||||
}
|
||||
}
|
||||
|
||||
chooseFromLibrary = async() => {
|
||||
try {
|
||||
const image = await ImagePicker.openPicker(imagePickerConfig);
|
||||
this.showUploadModal(image);
|
||||
} catch (e) {
|
||||
log('chooseFromLibrary', e);
|
||||
}
|
||||
}
|
||||
|
||||
showUploadModal = (file) => {
|
||||
this.setState({ file: { ...file, isVisible: true } });
|
||||
}
|
||||
|
||||
editCancel = () => {
|
||||
const { editCancel } = this.props;
|
||||
editCancel();
|
||||
this.setState({ text: '' });
|
||||
}
|
||||
|
||||
openEmoji = async() => {
|
||||
await this.setState({
|
||||
showEmojiKeyboard: true
|
||||
});
|
||||
}
|
||||
|
||||
recordAudioMessage = async() => {
|
||||
const recording = await Recording.permission();
|
||||
this.setState({ recording });
|
||||
}
|
||||
|
||||
finishAudioMessage = async(fileInfo) => {
|
||||
const { rid } = this.props;
|
||||
|
||||
this.setState({
|
||||
recording: false
|
||||
});
|
||||
if (fileInfo) {
|
||||
try {
|
||||
await RocketChat.sendFileMessage(rid, fileInfo);
|
||||
} catch (e) {
|
||||
if (e && e.error === 'error-file-too-large') {
|
||||
return Alert.alert(I18n.t(e.error));
|
||||
}
|
||||
log('finishAudioMessage', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeEmoji = () => {
|
||||
this.setState({ showEmojiKeyboard: false });
|
||||
}
|
||||
|
||||
submit = async(message) => {
|
||||
const {
|
||||
typing, message: editingMessage, editRequest, onSubmit
|
||||
} = this.props;
|
||||
|
||||
this.setState({ text: '' });
|
||||
this.closeEmoji();
|
||||
this.stopTrackingMention();
|
||||
typing(false);
|
||||
if (message.trim() === '') {
|
||||
return;
|
||||
}
|
||||
// if is editing a message
|
||||
const {
|
||||
editing, replying
|
||||
} = this.props;
|
||||
|
||||
if (editing) {
|
||||
const { _id, rid } = editingMessage;
|
||||
editRequest({ _id, msg: message, rid });
|
||||
} else if (replying) {
|
||||
const {
|
||||
username, replyMessage, roomType, closeReply
|
||||
} = this.props;
|
||||
const permalink = await this.getPermalink(replyMessage);
|
||||
let msg = `[ ](${ permalink }) `;
|
||||
|
||||
// if original message wasn't sent by current user and neither from a direct room
|
||||
if (username !== replyMessage.u.username && roomType !== 'd' && replyMessage.mention) {
|
||||
msg += `@${ replyMessage.u.username } `;
|
||||
}
|
||||
|
||||
msg = `${ msg } ${ message }`;
|
||||
onSubmit(msg);
|
||||
closeReply();
|
||||
} else {
|
||||
// if is submiting a new message
|
||||
onSubmit(message);
|
||||
}
|
||||
}
|
||||
|
||||
updateMentions = (keyword, type) => {
|
||||
if (type === MENTIONS_TRACKING_TYPE_USERS) {
|
||||
this.getUsers(keyword);
|
||||
} else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) {
|
||||
this.getEmojis(keyword);
|
||||
} else {
|
||||
this.getRooms(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
identifyMentionKeyword(keyword, type) {
|
||||
this.setState({
|
||||
showEmojiKeyboard: false,
|
||||
trackingType: type
|
||||
});
|
||||
this.updateMentions(keyword, type);
|
||||
}
|
||||
|
||||
stopTrackingMention() {
|
||||
this.setState({
|
||||
mentions: [],
|
||||
|
@ -426,76 +513,26 @@ export default class MessageBox extends React.PureComponent {
|
|||
this.emojis = [];
|
||||
}
|
||||
|
||||
identifyMentionKeyword(keyword, type) {
|
||||
this.setState({
|
||||
showEmojiKeyboard: false,
|
||||
trackingType: type
|
||||
});
|
||||
this.updateMentions(keyword, type);
|
||||
}
|
||||
|
||||
updateMentions = (keyword, type) => {
|
||||
if (type === MENTIONS_TRACKING_TYPE_USERS) {
|
||||
this._getUsers(keyword);
|
||||
} else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) {
|
||||
this._getEmojis(keyword);
|
||||
} else {
|
||||
this._getRooms(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
_onPressMention(item) {
|
||||
const msg = this.component._lastNativeText;
|
||||
|
||||
const { start, end } = this.component._lastNativeSelection;
|
||||
|
||||
const cursor = Math.max(start, end);
|
||||
|
||||
const regexp = /([a-z0-9._-]+)$/im;
|
||||
|
||||
const result = msg.substr(0, cursor).replace(regexp, '');
|
||||
const mentionName = this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ?
|
||||
`${ item.name || item }:` : (item.username || item.name);
|
||||
const text = `${ result }${ mentionName } ${ msg.slice(cursor) }`;
|
||||
this.component.setNativeProps({ text });
|
||||
this.setState({ text });
|
||||
this.component.focus();
|
||||
requestAnimationFrame(() => this.stopTrackingMention());
|
||||
}
|
||||
_onEmojiSelected(keyboardId, params) {
|
||||
const { text } = this.state;
|
||||
const { emoji } = params;
|
||||
let newText = '';
|
||||
|
||||
// if messagebox has an active cursor
|
||||
if (this.component._lastNativeSelection) {
|
||||
const { start, end } = this.component._lastNativeSelection;
|
||||
const cursor = Math.max(start, end);
|
||||
newText = `${ text.substr(0, cursor) }${ emoji }${ text.substr(cursor) }`;
|
||||
} else {
|
||||
// if messagebox doesn't have a cursor, just append selected emoji
|
||||
newText = `${ text }${ emoji }`;
|
||||
}
|
||||
this.component.setNativeProps({ text: newText });
|
||||
this.setState({ text: newText });
|
||||
}
|
||||
renderFixedMentionItem = item => (
|
||||
<TouchableOpacity
|
||||
style={styles.mentionItem}
|
||||
onPress={() => this._onPressMention(item)}
|
||||
onPress={() => this.onPressMention(item)}
|
||||
>
|
||||
<Text style={styles.fixedMentionAvatar}>{item.username}</Text>
|
||||
<Text>{item.username === 'here' ? I18n.t('Notify_active_in_this_room') : I18n.t('Notify_all_in_this_room')}</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
|
||||
renderMentionEmoji = (item) => {
|
||||
const { baseUrl } = this.props;
|
||||
|
||||
if (item.name) {
|
||||
return (
|
||||
<CustomEmoji
|
||||
key='mention-item-avatar'
|
||||
style={styles.mentionItemCustomEmoji}
|
||||
emoji={item}
|
||||
baseUrl={this.props.baseUrl}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -508,18 +545,22 @@ export default class MessageBox extends React.PureComponent {
|
|||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
renderMentionItem = (item) => {
|
||||
const { trackingType } = this.state;
|
||||
const { baseUrl } = this.props;
|
||||
|
||||
if (item.username === 'all' || item.username === 'here') {
|
||||
return this.renderFixedMentionItem(item);
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={styles.mentionItem}
|
||||
onPress={() => this._onPressMention(item)}
|
||||
testID={`mention-item-${ this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ? item.name || item : item.username || item.name }`}
|
||||
onPress={() => this.onPressMention(item)}
|
||||
testID={`mention-item-${ trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ? item.name || item : item.username || item.name }`}
|
||||
>
|
||||
{this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ?
|
||||
[
|
||||
{trackingType === MENTIONS_TRACKING_TYPE_EMOJIS
|
||||
? [
|
||||
this.renderMentionEmoji(item),
|
||||
<Text key='mention-item-name'>:{ item.name || item }:</Text>
|
||||
]
|
||||
|
@ -530,7 +571,7 @@ export default class MessageBox extends React.PureComponent {
|
|||
text={item.username || item.name}
|
||||
size={30}
|
||||
type={item.username ? 'd' : 'c'}
|
||||
baseUrl={this.props.baseUrl}
|
||||
baseUrl={baseUrl}
|
||||
/>,
|
||||
<Text key='mention-item-name'>{ item.username || item.name }</Text>
|
||||
]
|
||||
|
@ -538,6 +579,7 @@ export default class MessageBox extends React.PureComponent {
|
|||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
renderMentions = () => {
|
||||
const { mentions, trackingType } = this.state;
|
||||
if (!trackingType) {
|
||||
|
@ -567,7 +609,9 @@ export default class MessageBox extends React.PureComponent {
|
|||
};
|
||||
|
||||
renderFilesActions = () => {
|
||||
if (!this.state.showFilesAction) {
|
||||
const { showFilesAction } = this.state;
|
||||
|
||||
if (!showFilesAction) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
|
@ -581,7 +625,10 @@ export default class MessageBox extends React.PureComponent {
|
|||
}
|
||||
|
||||
renderContent() {
|
||||
if (this.state.recording) {
|
||||
const { recording, text } = this.state;
|
||||
const { editing } = this.props;
|
||||
|
||||
if (recording) {
|
||||
return (<Recording onFinish={this.finishAudioMessage} />);
|
||||
}
|
||||
return (
|
||||
|
@ -590,7 +637,7 @@ export default class MessageBox extends React.PureComponent {
|
|||
<View style={styles.composer} key='messagebox'>
|
||||
{this.renderReplyPreview()}
|
||||
<View
|
||||
style={[styles.textArea, this.props.editing && styles.editing]}
|
||||
style={[styles.textArea, editing && styles.editing]}
|
||||
testID='messagebox'
|
||||
>
|
||||
{this.leftButtons}
|
||||
|
@ -601,8 +648,8 @@ export default class MessageBox extends React.PureComponent {
|
|||
keyboardType='twitter'
|
||||
blurOnSubmit={false}
|
||||
placeholder={I18n.t('New_Message')}
|
||||
onChangeText={text => this.onChangeText(text)}
|
||||
value={this.state.text}
|
||||
onChangeText={t => this.onChangeText(t)}
|
||||
value={text}
|
||||
underlineColorAndroid='transparent'
|
||||
defaultValue=''
|
||||
multiline
|
||||
|
@ -617,15 +664,16 @@ export default class MessageBox extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { showEmojiKeyboard, file } = this.state;
|
||||
return (
|
||||
[
|
||||
<KeyboardAccessoryView
|
||||
key='input'
|
||||
renderContent={() => this.renderContent()}
|
||||
kbInputRef={this.component}
|
||||
kbComponent={this.state.showEmojiKeyboard ? 'EmojiKeyboard' : null}
|
||||
kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null}
|
||||
onKeyboardResigned={() => this.onKeyboardResigned()}
|
||||
onItemSelected={this._onEmojiSelected}
|
||||
onItemSelected={this.onEmojiSelected}
|
||||
trackInteractive
|
||||
// revealKeyboardInteractive
|
||||
requiresSameParentToManageScrollView
|
||||
|
@ -634,8 +682,8 @@ export default class MessageBox extends React.PureComponent {
|
|||
this.renderFilesActions(),
|
||||
<UploadModal
|
||||
key='upload-modal'
|
||||
isVisible={(this.state.file && this.state.file.isVisible)}
|
||||
file={this.state.file}
|
||||
isVisible={(file && file.isVisible)}
|
||||
file={file}
|
||||
close={() => this.setState({ file: {} })}
|
||||
submit={this.sendImageMessage}
|
||||
/>
|
||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { connect } from 'react-redux';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
|
||||
import { errorActionsHide } from '../actions/messages';
|
||||
import { errorActionsHide as errorActionsHideAction } from '../actions/messages';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import database from '../lib/realm';
|
||||
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
||||
|
@ -14,7 +14,7 @@ import I18n from '../i18n';
|
|||
actionMessage: state.messages.actionMessage
|
||||
}),
|
||||
dispatch => ({
|
||||
errorActionsHide: () => dispatch(errorActionsHide())
|
||||
errorActionsHide: () => dispatch(errorActionsHideAction())
|
||||
})
|
||||
)
|
||||
export default class MessageErrorActions extends React.Component {
|
||||
|
@ -23,6 +23,7 @@ export default class MessageErrorActions extends React.Component {
|
|||
actionMessage: PropTypes.object
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleActionPress = this.handleActionPress.bind(this);
|
||||
|
@ -35,16 +36,21 @@ export default class MessageErrorActions extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleResend = protectedFunction(() => RocketChat.resendMessage(this.props.actionMessage._id));
|
||||
handleResend = protectedFunction(() => {
|
||||
const { actionMessage } = this.props;
|
||||
RocketChat.resendMessage(actionMessage._id);
|
||||
});
|
||||
|
||||
handleDelete = protectedFunction(() => {
|
||||
const { actionMessage } = this.props;
|
||||
database.write(() => {
|
||||
const msg = database.objects('messages').filtered('_id = $0', this.props.actionMessage._id);
|
||||
const msg = database.objects('messages').filtered('_id = $0', actionMessage._id);
|
||||
database.delete(msg);
|
||||
});
|
||||
})
|
||||
|
||||
handleActionPress = (actionIndex) => {
|
||||
const { errorActionsHide } = this.props;
|
||||
switch (actionIndex) {
|
||||
case this.RESEND_INDEX:
|
||||
this.handleResend();
|
||||
|
@ -55,7 +61,7 @@ export default class MessageErrorActions extends React.Component {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
this.props.errorActionsHide();
|
||||
errorActionsHide();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import { View, StyleSheet, Image, TextInput, Platform } from 'react-native';
|
||||
import {
|
||||
View, StyleSheet, Image, TextInput, Platform
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import I18n from '../i18n';
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, SafeAreaView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import { appStart } from '../actions';
|
||||
import { logout } from '../actions/login';
|
||||
import Avatar from '../containers/Avatar';
|
||||
import Status from '../containers/status';
|
||||
import { appStart as appStartAction } from '../actions';
|
||||
import { logout as logoutAction } from '../actions/login';
|
||||
import Avatar from './Avatar';
|
||||
import Status from './status';
|
||||
import Touch from '../utils/touch';
|
||||
import { STATUS_COLORS } from '../constants/colors';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
@ -87,8 +89,8 @@ const keyExtractor = item => item.id;
|
|||
},
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||
}), dispatch => ({
|
||||
logout: () => dispatch(logout()),
|
||||
appStart: () => dispatch(appStart('outside'))
|
||||
logout: () => dispatch(logoutAction()),
|
||||
appStart: () => dispatch(appStartAction('outside'))
|
||||
}))
|
||||
export default class Sidebar extends Component {
|
||||
static propTypes = {
|
||||
|
@ -112,7 +114,8 @@ export default class Sidebar extends Component {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.user && this.props.user && this.props.user.language !== nextProps.user.language) {
|
||||
const { user } = this.props;
|
||||
if (nextProps.user && user && user.language !== nextProps.user.language) {
|
||||
this.setStatus();
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +141,8 @@ export default class Sidebar extends Component {
|
|||
}
|
||||
|
||||
closeDrawer = () => {
|
||||
this.props.navigator.toggleDrawer({
|
||||
const { navigator } = this.props;
|
||||
navigator.toggleDrawer({
|
||||
side: 'left',
|
||||
animated: true,
|
||||
to: 'close'
|
||||
|
@ -147,7 +151,7 @@ export default class Sidebar extends Component {
|
|||
|
||||
toggleStatus = () => {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
this.setState({ showStatus: !this.state.showStatus });
|
||||
this.setState(prevState => ({ showStatus: !prevState.showStatus }));
|
||||
}
|
||||
|
||||
sidebarNavigate = (screen, title) => {
|
||||
|
@ -178,15 +182,17 @@ export default class Sidebar extends Component {
|
|||
</Touch>
|
||||
)
|
||||
|
||||
renderStatusItem = ({ item }) => (
|
||||
renderStatusItem = ({ item }) => {
|
||||
const { user } = this.props;
|
||||
return (
|
||||
this.renderItem({
|
||||
text: item.name,
|
||||
left: <View style={[styles.status, { backgroundColor: STATUS_COLORS[item.id] }]} />,
|
||||
selected: this.props.user.status === item.id,
|
||||
selected: user.status === item.id,
|
||||
onPress: () => {
|
||||
this.closeDrawer();
|
||||
this.toggleStatus();
|
||||
if (this.props.user.status !== item.id) {
|
||||
if (user.status !== item.id) {
|
||||
try {
|
||||
RocketChat.setUserPresenceDefaultStatus(item.id);
|
||||
} catch (e) {
|
||||
|
@ -195,9 +201,12 @@ export default class Sidebar extends Component {
|
|||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderNavigation = () => (
|
||||
renderNavigation = () => {
|
||||
const { logout } = this.props;
|
||||
return (
|
||||
[
|
||||
this.renderItem({
|
||||
text: I18n.t('Chats'),
|
||||
|
@ -221,24 +230,31 @@ export default class Sidebar extends Component {
|
|||
this.renderItem({
|
||||
text: I18n.t('Logout'),
|
||||
left: <Icon name='exit-to-app' size={20} />,
|
||||
onPress: () => this.props.logout(),
|
||||
onPress: () => logout(),
|
||||
testID: 'sidebar-logout'
|
||||
})
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderStatus = () => (
|
||||
renderStatus = () => {
|
||||
const { status } = this.state;
|
||||
const { user } = this.props;
|
||||
return (
|
||||
<FlatList
|
||||
key='status-list'
|
||||
data={this.state.status}
|
||||
extraData={this.props.user}
|
||||
data={status}
|
||||
extraData={user}
|
||||
renderItem={this.renderStatusItem}
|
||||
keyExtractor={keyExtractor}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { showStatus } = this.state;
|
||||
const { user, server, baseUrl } = this.props;
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
@ -266,7 +282,7 @@ export default class Sidebar extends Component {
|
|||
<Text style={styles.currentServerText} numberOfLines={1}>{server}</Text>
|
||||
</View>
|
||||
<Icon
|
||||
name={this.state.showStatus ? 'keyboard-arrow-up' : 'keyboard-arrow-down'}
|
||||
name={showStatus ? 'keyboard-arrow-up' : 'keyboard-arrow-down'}
|
||||
size={30}
|
||||
style={{ paddingHorizontal: 10 }}
|
||||
/>
|
||||
|
@ -275,8 +291,8 @@ export default class Sidebar extends Component {
|
|||
|
||||
{this.renderSeparator('separator-header')}
|
||||
|
||||
{!this.state.showStatus ? this.renderNavigation() : null}
|
||||
{this.state.showStatus ? this.renderStatus() : null}
|
||||
{!showStatus ? this.renderNavigation() : null}
|
||||
{showStatus ? this.renderStatus() : null}
|
||||
</ScrollView>
|
||||
<Text style={styles.version}>
|
||||
{DeviceInfo.getReadableVersion()}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import { View, StyleSheet, Text, TextInput, ViewPropTypes, Platform } from 'react-native';
|
||||
import {
|
||||
View, StyleSheet, Text, TextInput, ViewPropTypes, Platform
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
|
||||
|
@ -68,9 +70,11 @@ export default class RCTextInput extends React.PureComponent {
|
|||
iconLeft: PropTypes.string,
|
||||
placeholder: PropTypes.string
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
error: {}
|
||||
}
|
||||
|
||||
state = {
|
||||
showPassword: false
|
||||
}
|
||||
|
@ -82,21 +86,30 @@ export default class RCTextInput extends React.PureComponent {
|
|||
testID
|
||||
}) => <Icon name={name} style={[styles.icon, style]} size={20} onPress={onPress} testID={testID} />
|
||||
|
||||
iconLeft = name => this.icon({
|
||||
iconLeft = (name) => {
|
||||
const { testID } = this.props;
|
||||
return this.icon({
|
||||
name,
|
||||
onPress: null,
|
||||
style: { left: 0 },
|
||||
testID: this.props.testID ? `${ this.props.testID }-icon-left` : null
|
||||
testID: testID ? `${ testID }-icon-left` : null
|
||||
});
|
||||
}
|
||||
|
||||
iconPassword = name => this.icon({
|
||||
iconPassword = (name) => {
|
||||
const { testID } = this.props;
|
||||
return this.icon({
|
||||
name,
|
||||
onPress: () => this.tooglePassword(),
|
||||
style: { right: 0 },
|
||||
testID: this.props.testID ? `${ this.props.testID }-icon-right` : null
|
||||
testID: testID ? `${ testID }-icon-right` : null
|
||||
});
|
||||
}
|
||||
|
||||
tooglePassword = () => this.setState({ showPassword: !this.state.showPassword });
|
||||
tooglePassword = () => {
|
||||
const { showPassword } = this.state;
|
||||
this.setState({ showPassword: !showPassword });
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, StyleSheet, Text, Keyboard, LayoutAnimation } from 'react-native';
|
||||
import {
|
||||
View, StyleSheet, Text, Keyboard, LayoutAnimation
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import I18n from '../i18n';
|
||||
|
||||
|
@ -22,18 +24,24 @@ const styles = StyleSheet.create({
|
|||
}))
|
||||
export default class Typing extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return this.props.usersTyping.join() !== nextProps.usersTyping.join();
|
||||
const { usersTyping } = this.props;
|
||||
return usersTyping.join() !== nextProps.usersTyping.join();
|
||||
}
|
||||
|
||||
componentWillUpdate() {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
}
|
||||
|
||||
onPress = () => {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
|
||||
get usersTyping() {
|
||||
const users = this.props.usersTyping.filter(_username => this.props.username !== _username);
|
||||
const { usersTyping, username } = this.props;
|
||||
const users = usersTyping.filter(_username => username !== _username);
|
||||
return users.length ? `${ users.join(', ') } ${ users.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing') }` : '';
|
||||
}
|
||||
|
||||
render() {
|
||||
const { usersTyping } = this;
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, StyleSheet, Text, Easing, Image } from 'react-native';
|
||||
import {
|
||||
View, StyleSheet, Text, Easing, Image
|
||||
} from 'react-native';
|
||||
import Video from 'react-native-video';
|
||||
import Slider from 'react-native-slider';
|
||||
import moment from 'moment';
|
||||
|
@ -72,7 +74,8 @@ export default class Audio extends React.PureComponent {
|
|||
}
|
||||
|
||||
onProgress(data) {
|
||||
if (data.currentTime <= this.state.duration) {
|
||||
const { duration } = this.state;
|
||||
if (data.currentTime <= duration) {
|
||||
this.setState({ currentTime: data.currentTime });
|
||||
}
|
||||
}
|
||||
|
@ -85,15 +88,19 @@ export default class Audio extends React.PureComponent {
|
|||
}
|
||||
|
||||
getDuration() {
|
||||
return formatTime(this.state.duration);
|
||||
const { duration } = this.state;
|
||||
return formatTime(duration);
|
||||
}
|
||||
|
||||
togglePlayPause() {
|
||||
this.setState({ paused: !this.state.paused });
|
||||
const { paused } = this.state;
|
||||
this.setState({ paused: !paused });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { uri, paused } = this.state;
|
||||
const {
|
||||
uri, paused, currentTime, duration
|
||||
} = this.state;
|
||||
const {
|
||||
user, baseUrl, customEmojis, file
|
||||
} = this.props;
|
||||
|
@ -122,15 +129,15 @@ export default class Audio extends React.PureComponent {
|
|||
onPress={() => this.togglePlayPause()}
|
||||
>
|
||||
{
|
||||
paused ?
|
||||
<Image source={{ uri: 'play' }} style={styles.playPauseImage} /> :
|
||||
<Image source={{ uri: 'pause' }} style={styles.playPauseImage} />
|
||||
paused
|
||||
? <Image source={{ uri: 'play' }} style={styles.playPauseImage} />
|
||||
: <Image source={{ uri: 'pause' }} style={styles.playPauseImage} />
|
||||
}
|
||||
</BorderlessButton>
|
||||
<Slider
|
||||
style={styles.slider}
|
||||
value={this.state.currentTime}
|
||||
maximumValue={this.state.duration}
|
||||
value={currentTime}
|
||||
maximumValue={duration}
|
||||
minimumValue={0}
|
||||
animateTransitions
|
||||
animationConfig={{
|
||||
|
|
|
@ -14,7 +14,8 @@ export default class Emoji extends React.PureComponent {
|
|||
PropTypes.array,
|
||||
PropTypes.object
|
||||
])
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
content, standardEmojiStyle, customEmojiStyle, customEmojis, baseUrl
|
||||
|
|
|
@ -40,7 +40,7 @@ export default class extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { isPressed } = this.state;
|
||||
const { modalVisible, isPressed } = this.state;
|
||||
const { baseUrl, file, user } = this.props;
|
||||
const img = `${ baseUrl }${ file.image_url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
||||
|
||||
|
@ -69,7 +69,7 @@ export default class extends React.PureComponent {
|
|||
title={file.title}
|
||||
description={file.description}
|
||||
image={img}
|
||||
isVisible={this.state.modalVisible}
|
||||
isVisible={modalVisible}
|
||||
onClose={() => this.setState({ modalVisible: false })}
|
||||
/>
|
||||
]
|
||||
|
|
|
@ -9,16 +9,17 @@ import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
|||
import MarkdownEmojiPlugin from './MarkdownEmojiPlugin';
|
||||
|
||||
// Support <http://link|Text>
|
||||
const formatText = text =>
|
||||
text.replace(
|
||||
const formatText = text => text.replace(
|
||||
new RegExp('(?:<|<)((?:https|http):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)', 'gm'),
|
||||
(match, url, title) => `[${ title }](${ url })`
|
||||
);
|
||||
|
||||
export default class Markdown extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.msg !== this.props.msg;
|
||||
const { msg } = this.props;
|
||||
return nextProps.msg !== msg;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
msg, customEmojis, style, rules, baseUrl, username, edited
|
||||
|
@ -33,8 +34,10 @@ export default class Markdown extends React.Component {
|
|||
<MarkdownRenderer
|
||||
rules={{
|
||||
paragraph: (node, children) => (
|
||||
// eslint-disable-next-line
|
||||
<Text key={node.key} style={styles.paragraph}>
|
||||
{children}{edited ? <Text style={styles.edited}> (edited)</Text> : null}
|
||||
{children}
|
||||
{edited ? <Text style={styles.edited}> (edited)</Text> : null}
|
||||
</Text>
|
||||
),
|
||||
mention: (node) => {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text, ViewPropTypes, Image as ImageRN } from 'react-native';
|
||||
import {
|
||||
View, Text, ViewPropTypes, Image as ImageRN
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import moment from 'moment';
|
||||
import { KeyboardUtils } from 'react-native-keyboard-input';
|
||||
|
@ -137,22 +139,29 @@ export default class Message extends PureComponent {
|
|||
KeyboardUtils.dismiss();
|
||||
}
|
||||
|
||||
isInfoMessage() {
|
||||
return SYSTEM_MESSAGES.includes(this.props.type);
|
||||
isInfoMessage = () => {
|
||||
const { type } = this.props;
|
||||
return SYSTEM_MESSAGES.includes(type);
|
||||
}
|
||||
|
||||
isOwn = () => this.props.author._id === this.props.user.id;
|
||||
isOwn = () => {
|
||||
const { author, user } = this.props;
|
||||
return author._id === user.id;
|
||||
}
|
||||
|
||||
isDeleted() {
|
||||
return this.props.type === 'rm';
|
||||
const { type } = this.props;
|
||||
return type === 'rm';
|
||||
}
|
||||
|
||||
isTemp() {
|
||||
return this.props.status === messagesStatus.TEMP || this.props.status === messagesStatus.ERROR;
|
||||
const { status } = this.props;
|
||||
return status === messagesStatus.TEMP || status === messagesStatus.ERROR;
|
||||
}
|
||||
|
||||
hasError() {
|
||||
return this.props.status === messagesStatus.ERROR;
|
||||
const { status } = this.props;
|
||||
return status === messagesStatus.ERROR;
|
||||
}
|
||||
|
||||
renderAvatar = () => {
|
||||
|
@ -242,19 +251,23 @@ export default class Message extends PureComponent {
|
|||
if (!this.hasError()) {
|
||||
return null;
|
||||
}
|
||||
return <Icon name='error-outline' color='red' size={20} style={styles.errorIcon} onPress={this.props.onErrorPress} />;
|
||||
const { onErrorPress } = this.props;
|
||||
return <Icon name='error-outline' color='red' size={20} style={styles.errorIcon} onPress={onErrorPress} />;
|
||||
}
|
||||
|
||||
renderReaction = (reaction) => {
|
||||
const reacted = reaction.usernames.findIndex(item => item.value === this.props.user.username) !== -1;
|
||||
const {
|
||||
user, onReactionLongPress, onReactionPress, customEmojis, baseUrl
|
||||
} = this.props;
|
||||
const reacted = reaction.usernames.findIndex(item => item.value === user.username) !== -1;
|
||||
const underlayColor = reacted ? '#fff' : '#e1e5e8';
|
||||
return (
|
||||
<LongPressGestureHandler
|
||||
key={reaction.emoji}
|
||||
onHandlerStateChange={({ nativeEvent }) => nativeEvent.state === State.ACTIVE && this.props.onReactionLongPress()}
|
||||
onHandlerStateChange={({ nativeEvent }) => nativeEvent.state === State.ACTIVE && onReactionLongPress()}
|
||||
>
|
||||
<RectButton
|
||||
onPress={() => this.props.onReactionPress(reaction.emoji)}
|
||||
onPress={() => onReactionPress(reaction.emoji)}
|
||||
testID={`message-reaction-${ reaction.emoji }`}
|
||||
style={[styles.reactionButton, reacted && { backgroundColor: '#e8f2ff' }]}
|
||||
activeOpacity={0.8}
|
||||
|
@ -263,10 +276,10 @@ export default class Message extends PureComponent {
|
|||
<View style={[styles.reactionContainer, reacted && styles.reactedContainer]}>
|
||||
<Emoji
|
||||
content={reaction.emoji}
|
||||
customEmojis={this.props.customEmojis}
|
||||
customEmojis={customEmojis}
|
||||
standardEmojiStyle={styles.reactionEmoji}
|
||||
customEmojiStyle={styles.reactionCustomEmoji}
|
||||
baseUrl={this.props.baseUrl}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
<Text style={styles.reactionCount}>{ reaction.usernames.length }</Text>
|
||||
</View>
|
||||
|
@ -276,7 +289,7 @@ export default class Message extends PureComponent {
|
|||
}
|
||||
|
||||
renderReactions() {
|
||||
const { reactions } = this.props;
|
||||
const { reactions, toggleReactionPicker } = this.props;
|
||||
if (reactions.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -284,7 +297,7 @@ export default class Message extends PureComponent {
|
|||
<View style={styles.reactionsContainer}>
|
||||
{reactions.map(this.renderReaction)}
|
||||
<RectButton
|
||||
onPress={this.props.toggleReactionPicker}
|
||||
onPress={toggleReactionPicker}
|
||||
key='message-add-reaction'
|
||||
testID='message-add-reaction'
|
||||
style={styles.reactionButton}
|
||||
|
@ -300,10 +313,11 @@ export default class Message extends PureComponent {
|
|||
}
|
||||
|
||||
renderBroadcastReply() {
|
||||
if (this.props.broadcast && !this.isOwn()) {
|
||||
const { broadcast, replyBroadcast } = this.props;
|
||||
if (broadcast && !this.isOwn()) {
|
||||
return (
|
||||
<RectButton
|
||||
onPress={this.props.replyBroadcast}
|
||||
onPress={replyBroadcast}
|
||||
style={styles.broadcastButton}
|
||||
activeOpacity={0.5}
|
||||
underlayColor='#fff'
|
||||
|
@ -349,7 +363,8 @@ export default class Message extends PureComponent {
|
|||
{this.renderBroadcastReply()}
|
||||
</View>
|
||||
</View>
|
||||
{reactionsModal ?
|
||||
{reactionsModal
|
||||
? (
|
||||
<ReactionsModal
|
||||
isVisible={reactionsModal}
|
||||
reactions={reactions}
|
||||
|
@ -358,6 +373,7 @@ export default class Message extends PureComponent {
|
|||
baseUrl={baseUrl}
|
||||
close={closeReactions}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</View>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import { View, Text, TouchableWithoutFeedback, ActivityIndicator, StyleSheet } from 'react-native';
|
||||
import {
|
||||
View, Text, TouchableWithoutFeedback, ActivityIndicator, StyleSheet
|
||||
} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import PropTypes from 'prop-types';
|
||||
import Modal from 'react-native-modal';
|
||||
|
@ -45,6 +47,7 @@ export default class PhotoModal extends React.PureComponent {
|
|||
onClose: PropTypes.func.isRequired,
|
||||
window: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
image, isVisible, onClose, title, description, window: { width, height }
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import { View, Text, TouchableWithoutFeedback, FlatList, StyleSheet } from 'react-native';
|
||||
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';
|
||||
|
@ -66,10 +68,12 @@ export default class ReactionsModal extends React.PureComponent {
|
|||
PropTypes.object
|
||||
])
|
||||
}
|
||||
|
||||
renderItem = (item) => {
|
||||
const { user, customEmojis, baseUrl } = this.props;
|
||||
const count = item.usernames.length;
|
||||
let usernames = item.usernames.slice(0, 3)
|
||||
.map(username => (username.value === this.props.user.username ? I18n.t('you') : username.value)).join(', ');
|
||||
.map(username => (username.value === user.username ? I18n.t('you') : username.value)).join(', ');
|
||||
if (count > 3) {
|
||||
usernames = `${ usernames } ${ I18n.t('and_more') } ${ count - 3 }`;
|
||||
} else {
|
||||
|
@ -82,8 +86,8 @@ export default class ReactionsModal extends React.PureComponent {
|
|||
content={item.emoji}
|
||||
standardEmojiStyle={standardEmojiStyle}
|
||||
customEmojiStyle={customEmojiStyle}
|
||||
customEmojis={this.props.customEmojis}
|
||||
baseUrl={this.props.baseUrl}
|
||||
customEmojis={customEmojis}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.peopleItemContainer}>
|
||||
|
|
|
@ -41,7 +41,7 @@ export default class User extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
const {
|
||||
username, alias, ts, temp
|
||||
username, alias, ts, temp, timeFormat
|
||||
} = this.props;
|
||||
|
||||
const extraStyle = {};
|
||||
|
@ -50,7 +50,7 @@ export default class User extends React.PureComponent {
|
|||
}
|
||||
|
||||
const aliasUsername = alias ? (<Text style={styles.alias}>@{username}</Text>) : null;
|
||||
const time = moment(ts).format(this.props.timeFormat);
|
||||
const time = moment(ts).format(timeFormat);
|
||||
|
||||
return (
|
||||
<View style={styles.usernameView}>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StyleSheet, Image, Platform, View } from 'react-native';
|
||||
import {
|
||||
StyleSheet, Image, Platform, View
|
||||
} from 'react-native';
|
||||
import Modal from 'react-native-modal';
|
||||
import VideoPlayer from 'react-native-video-controls';
|
||||
import { RectButton } from 'react-native-gesture-handler';
|
||||
|
@ -42,19 +44,20 @@ export default class Video extends React.PureComponent {
|
|||
state = { isVisible: false };
|
||||
|
||||
get uri() {
|
||||
const { video_url } = this.props.file;
|
||||
const { baseUrl, user } = this.props;
|
||||
const { baseUrl, user, file } = this.props;
|
||||
const { video_url } = file;
|
||||
return `${ baseUrl }${ video_url }?rc_uid=${ user.id }&rc_token=${ user.token }`;
|
||||
}
|
||||
|
||||
toggleModal() {
|
||||
this.setState({
|
||||
isVisible: !this.state.isVisible
|
||||
});
|
||||
this.setState(prevState => ({
|
||||
isVisible: !prevState.isVisible
|
||||
}));
|
||||
}
|
||||
|
||||
open() {
|
||||
if (isTypeSupported(this.props.file.video_type)) {
|
||||
const { file } = this.props;
|
||||
if (isTypeSupported(file.video_type)) {
|
||||
return this.toggleModal();
|
||||
}
|
||||
openLink(this.uri);
|
||||
|
@ -62,8 +65,10 @@ export default class Video extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
const { isVisible } = this.state;
|
||||
const { description } = this.props.file;
|
||||
const { baseUrl, user, customEmojis } = this.props;
|
||||
const {
|
||||
baseUrl, user, customEmojis, file
|
||||
} = this.props;
|
||||
const { description } = file;
|
||||
|
||||
if (!baseUrl) {
|
||||
return null;
|
||||
|
|
|
@ -5,7 +5,11 @@ import { connect } from 'react-redux';
|
|||
import equal from 'deep-equal';
|
||||
|
||||
import Message from './Message';
|
||||
import { errorActionsShow, toggleReactionPicker, replyBroadcast } from '../../actions/messages';
|
||||
import {
|
||||
errorActionsShow as errorActionsShowAction,
|
||||
toggleReactionPicker as toggleReactionPickerAction,
|
||||
replyBroadcast as replyBroadcastAction
|
||||
} from '../../actions/messages';
|
||||
|
||||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
|
@ -16,9 +20,9 @@ import { errorActionsShow, toggleReactionPicker, replyBroadcast } from '../../ac
|
|||
message: state.messages.message,
|
||||
useRealName: state.settings.UI_Use_Real_Name
|
||||
}), dispatch => ({
|
||||
errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage)),
|
||||
replyBroadcast: message => dispatch(replyBroadcast(message)),
|
||||
toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
|
||||
errorActionsShow: actionMessage => dispatch(errorActionsShowAction(actionMessage)),
|
||||
replyBroadcast: message => dispatch(replyBroadcastAction(message)),
|
||||
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message))
|
||||
}))
|
||||
export default class MessageContainer extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -67,39 +71,47 @@ export default class MessageContainer extends React.Component {
|
|||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (this.state.reactionsModal !== nextState.reactionsModal) {
|
||||
const { reactionsModal } = this.state;
|
||||
const {
|
||||
status, reactions, broadcast, editing, _updatedAt
|
||||
} = this.props;
|
||||
|
||||
if (reactionsModal !== nextState.reactionsModal) {
|
||||
return true;
|
||||
}
|
||||
if (this.props.status !== nextProps.status) {
|
||||
if (status !== nextProps.status) {
|
||||
return true;
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
if (!!this.props._updatedAt ^ !!nextProps._updatedAt) {
|
||||
if (!!_updatedAt ^ !!nextProps._updatedAt) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(this.props.reactions, nextProps.reactions)) {
|
||||
if (!equal(reactions, nextProps.reactions)) {
|
||||
return true;
|
||||
}
|
||||
if (this.props.broadcast !== nextProps.broadcast) {
|
||||
if (broadcast !== nextProps.broadcast) {
|
||||
return true;
|
||||
}
|
||||
if (this.props.editing !== nextProps.editing) {
|
||||
if (editing !== nextProps.editing) {
|
||||
return true;
|
||||
}
|
||||
return this.props._updatedAt.toGMTString() !== nextProps._updatedAt.toGMTString();
|
||||
return _updatedAt.toGMTString() !== nextProps._updatedAt.toGMTString();
|
||||
}
|
||||
|
||||
|
||||
onLongPress = () => {
|
||||
this.props.onLongPress(this.parseMessage());
|
||||
const { onLongPress } = this.props;
|
||||
onLongPress(this.parseMessage());
|
||||
}
|
||||
|
||||
onErrorPress = () => {
|
||||
this.props.errorActionsShow(this.parseMessage());
|
||||
const { errorActionsShow } = this.props;
|
||||
errorActionsShow(this.parseMessage());
|
||||
}
|
||||
|
||||
onReactionPress = (emoji) => {
|
||||
this.props.onReactionPress(emoji, this.props.item._id);
|
||||
const { onReactionPress, item } = this.props;
|
||||
onReactionPress(emoji, item._id);
|
||||
}
|
||||
|
||||
|
||||
|
@ -118,30 +130,38 @@ export default class MessageContainer extends React.Component {
|
|||
}
|
||||
|
||||
isHeader = () => {
|
||||
const { item, previousItem } = this.props;
|
||||
const {
|
||||
item, previousItem, broadcast, Message_GroupingPeriod
|
||||
} = this.props;
|
||||
if (previousItem && (
|
||||
(previousItem.ts.toDateString() === item.ts.toDateString()) &&
|
||||
(previousItem.u.username === item.u.username) &&
|
||||
!(previousItem.groupable === false || item.groupable === false || this.props.broadcast === true) &&
|
||||
(previousItem.status === item.status) &&
|
||||
(item.ts - previousItem.ts < this.props.Message_GroupingPeriod * 1000)
|
||||
(previousItem.ts.toDateString() === item.ts.toDateString())
|
||||
&& (previousItem.u.username === item.u.username)
|
||||
&& !(previousItem.groupable === false || item.groupable === false || broadcast === true)
|
||||
&& (previousItem.status === item.status)
|
||||
&& (item.ts - previousItem.ts < Message_GroupingPeriod * 1000)
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
parseMessage = () => JSON.parse(JSON.stringify(this.props.item));
|
||||
parseMessage = () => {
|
||||
const { item } = this.props;
|
||||
return JSON.parse(JSON.stringify(item));
|
||||
}
|
||||
|
||||
toggleReactionPicker = () => {
|
||||
this.props.toggleReactionPicker(this.parseMessage());
|
||||
const { toggleReactionPicker } = this.props;
|
||||
toggleReactionPicker(this.parseMessage());
|
||||
}
|
||||
|
||||
replyBroadcast = () => {
|
||||
this.props.replyBroadcast(this.parseMessage());
|
||||
const { replyBroadcast } = this.props;
|
||||
replyBroadcast(this.parseMessage());
|
||||
}
|
||||
|
||||
render() {
|
||||
const { reactionsModal } = this.state;
|
||||
const {
|
||||
item, message, editing, user, style, archived, baseUrl, customEmojis, useRealName, broadcast
|
||||
} = this.props;
|
||||
|
@ -171,7 +191,7 @@ export default class MessageContainer extends React.Component {
|
|||
broadcast={broadcast}
|
||||
baseUrl={baseUrl}
|
||||
customEmojis={customEmojis}
|
||||
reactionsModal={this.state.reactionsModal}
|
||||
reactionsModal={reactionsModal}
|
||||
useRealName={useRealName}
|
||||
role={role}
|
||||
closeReactions={this.closeReactions}
|
||||
|
|
|
@ -42,6 +42,7 @@ export default class Status extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
return (<View style={[styles.status, this.props.style, { backgroundColor: STATUS_COLORS[this.status] }]} />);
|
||||
const { style } = this.props;
|
||||
return (<View style={[styles.status, style, { backgroundColor: STATUS_COLORS[this.status] }]} />);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ class EventEmitter {
|
|||
constructor() {
|
||||
this.events = {};
|
||||
}
|
||||
|
||||
on(event, listener) {
|
||||
if (typeof this.events[event] !== 'object') {
|
||||
this.events[event] = [];
|
||||
|
@ -31,6 +32,7 @@ class EventEmitter {
|
|||
this.events[event].push(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
removeListener(event, listener) {
|
||||
if (typeof this.events[event] === 'object') {
|
||||
const idx = this.events[event].indexOf(listener);
|
||||
|
@ -42,6 +44,7 @@ class EventEmitter {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit(event, ...args) {
|
||||
if (typeof this.events[event] === 'object') {
|
||||
this.events[event].forEach((listener) => {
|
||||
|
@ -53,6 +56,7 @@ class EventEmitter {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
once(event, listener) {
|
||||
return this.on(event, function g(...args) {
|
||||
this.removeListener(event, g);
|
||||
|
@ -135,6 +139,7 @@ export default class Socket extends EventEmitter {
|
|||
|
||||
this._connect().catch(e => log('ddp.constructor._connect', e));
|
||||
}
|
||||
|
||||
check() {
|
||||
if (!this.lastping) {
|
||||
return false;
|
||||
|
@ -144,6 +149,7 @@ export default class Socket extends EventEmitter {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async login(params) {
|
||||
try {
|
||||
this.emit('login', params);
|
||||
|
@ -165,6 +171,7 @@ export default class Socket extends EventEmitter {
|
|||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
async send(obj, ignore) {
|
||||
console.log('send', obj);
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -187,9 +194,11 @@ export default class Socket extends EventEmitter {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this.connection && this.connection.readyState === 1 && this.check() && !!this._logged;
|
||||
}
|
||||
|
||||
_close() {
|
||||
try {
|
||||
// this.connection && this.connection.readyState > 1 && this.connection.close && this.connection.close(300, 'disconnect');
|
||||
|
@ -201,6 +210,7 @@ export default class Socket extends EventEmitter {
|
|||
// console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
_connect() {
|
||||
return new Promise((resolve) => {
|
||||
this.lastping = new Date();
|
||||
|
@ -240,12 +250,14 @@ export default class Socket extends EventEmitter {
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
this._login = null;
|
||||
return this.call('logout')
|
||||
.catch(e => log('logout', e))
|
||||
.finally(() => this.subscriptions = {});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this._logged = false;
|
||||
this._login = null;
|
||||
|
@ -256,6 +268,7 @@ export default class Socket extends EventEmitter {
|
|||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
async reconnect() {
|
||||
if (this._timer || this.forceDisconnect) {
|
||||
return;
|
||||
|
@ -272,6 +285,7 @@ export default class Socket extends EventEmitter {
|
|||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
call(method, ...params) {
|
||||
return this.send({
|
||||
msg: 'method', method, params
|
||||
|
@ -283,6 +297,7 @@ export default class Socket extends EventEmitter {
|
|||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
unsubscribe(id) {
|
||||
if (!this.subscriptions[id]) {
|
||||
return Promise.reject(id);
|
||||
|
@ -296,6 +311,7 @@ export default class Socket extends EventEmitter {
|
|||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
subscribe(name, ...params) {
|
||||
console.log(name, params);
|
||||
return this.send({
|
||||
|
|
|
@ -21,9 +21,11 @@ export default async function() {
|
|||
return permission;
|
||||
});
|
||||
|
||||
InteractionManager.runAfterInteractions(() =>
|
||||
database.write(() =>
|
||||
permissions.forEach(permission => database.create('permissions', permission, true))));
|
||||
InteractionManager.runAfterInteractions(
|
||||
() => database.write(
|
||||
() => permissions.forEach(permission => database.create('permissions', permission, true))
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
log('getPermissions', e);
|
||||
}
|
||||
|
|
|
@ -27,15 +27,17 @@ export default async function() {
|
|||
|
||||
const filteredSettings = this._prepareSettings(this._filterSettings(data));
|
||||
|
||||
InteractionManager.runAfterInteractions(() =>
|
||||
database.write(() =>
|
||||
filteredSettings.forEach((setting) => {
|
||||
InteractionManager.runAfterInteractions(
|
||||
() => database.write(
|
||||
() => filteredSettings.forEach((setting) => {
|
||||
database.create('settings', { ...setting, _updatedAt: new Date() }, true);
|
||||
|
||||
if (setting._id === 'Site_Name') {
|
||||
updateServer.call(this, { name: setting.valueAsString });
|
||||
}
|
||||
})));
|
||||
})
|
||||
)
|
||||
);
|
||||
reduxStore.dispatch(actions.addSettings(this.parseSettings(filteredSettings)));
|
||||
|
||||
const iconSetting = data.find(item => item._id === 'Assets_favicon_512');
|
||||
|
|
|
@ -20,7 +20,7 @@ export default async function readMessages(rid) {
|
|||
const { database: db } = database;
|
||||
try {
|
||||
// eslint-disable-next-line
|
||||
const data = await (this.ddp.status ? readMessagesDDP.call(this, rid) : readMessagesREST.call(this, rid));
|
||||
const data = await (this.ddp.status && false ? readMessagesDDP.call(this, rid) : readMessagesREST.call(this, rid));
|
||||
const [subscription] = db.objects('subscriptions').filtered('rid = $0', rid);
|
||||
db.write(() => {
|
||||
subscription.open = true;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Random from 'react-native-meteor/lib/Random';
|
||||
|
||||
import messagesStatus from '../../constants/messagesStatus';
|
||||
import buildMessage from '../methods/helpers/buildMessage';
|
||||
import buildMessage from './helpers/buildMessage';
|
||||
import { post } from './helpers/rest';
|
||||
import database from '../realm';
|
||||
import reduxStore from '../createStore';
|
||||
|
@ -46,7 +46,7 @@ function sendMessageByDDP(message) {
|
|||
export async function _sendMessageCall(message) {
|
||||
try {
|
||||
// eslint-disable-next-line
|
||||
const data = await (this.ddp.status ? sendMessageByDDP.call(this, message) : sendMessageByRest.call(this, message));
|
||||
const data = await (this.ddp.status && false ? sendMessageByDDP.call(this, message) : sendMessageByRest.call(this, message));
|
||||
return data;
|
||||
} catch (e) {
|
||||
database.write(() => {
|
||||
|
|
|
@ -9,8 +9,7 @@ const subscribe = (ddp, rid) => Promise.all([
|
|||
ddp.subscribe('stream-room-messages', rid, false),
|
||||
ddp.subscribe('stream-notify-room', `${ rid }/typing`, false)
|
||||
]);
|
||||
const unsubscribe = subscriptions =>
|
||||
subscriptions.forEach(sub => sub.unsubscribe().catch((e) => {
|
||||
const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch((e) => {
|
||||
log('unsubscribeRoom', e);
|
||||
}));
|
||||
|
||||
|
|
|
@ -309,22 +309,28 @@ class DB {
|
|||
],
|
||||
deleteRealmIfMigrationNeeded: true
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
deleteAll(...args) {
|
||||
return this.database.write(() => this.database.deleteAll(...args));
|
||||
}
|
||||
|
||||
delete(...args) {
|
||||
return this.database.delete(...args);
|
||||
}
|
||||
|
||||
write(...args) {
|
||||
return this.database.write(...args);
|
||||
}
|
||||
|
||||
create(...args) {
|
||||
return this.database.create(...args);
|
||||
}
|
||||
|
||||
objects(...args) {
|
||||
return this.database.objects(...args);
|
||||
}
|
||||
|
||||
get database() {
|
||||
return this.databases.activeDB;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@ import database from './realm';
|
|||
import log from '../utils/log';
|
||||
// import * as actions from '../actions';
|
||||
|
||||
import { setUser, setLoginServices, removeLoginServices, loginRequest, loginSuccess, loginFailure, logout } from '../actions/login';
|
||||
import {
|
||||
setUser, setLoginServices, removeLoginServices, loginRequest, loginSuccess, loginFailure, logout
|
||||
} from '../actions/login';
|
||||
import { disconnect, connectSuccess, connectFailure } from '../actions/connect';
|
||||
import { setActiveUser } from '../actions/activeUsers';
|
||||
import { starredMessagesReceived, starredMessageUnstarred } from '../actions/starredMessages';
|
||||
|
@ -598,8 +600,8 @@ const RocketChat = {
|
|||
getPermissions,
|
||||
getCustomEmoji,
|
||||
parseSettings: settings => settings.reduce((ret, item) => {
|
||||
ret[item._id] = item[defaultSettings[item._id].type] || item.valueAsString || item.valueAsNumber ||
|
||||
item.valueAsBoolean || item.value;
|
||||
ret[item._id] = item[defaultSettings[item._id].type] || item.valueAsString || item.valueAsNumber
|
||||
|| item.valueAsBoolean || item.value;
|
||||
return ret;
|
||||
}, {}),
|
||||
_prepareSettings(settings) {
|
||||
|
|
|
@ -17,17 +17,21 @@ export default class KeyboardView extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
style, contentContainerStyle, scrollEnabled, keyboardVerticalOffset, children
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<KeyboardAwareScrollView
|
||||
{...scrollPersistTaps}
|
||||
style={this.props.style}
|
||||
contentContainerStyle={this.props.contentContainerStyle}
|
||||
scrollEnabled={this.props.scrollEnabled}
|
||||
style={style}
|
||||
contentContainerStyle={contentContainerStyle}
|
||||
scrollEnabled={scrollEnabled}
|
||||
alwaysBounceVertical={false}
|
||||
extraHeight={this.props.keyboardVerticalOffset}
|
||||
extraHeight={keyboardVerticalOffset}
|
||||
behavior='position'
|
||||
>
|
||||
{this.props.children}
|
||||
{children}
|
||||
</KeyboardAwareScrollView>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text, StyleSheet, Image, Platform } from 'react-native';
|
||||
import {
|
||||
View, Text, StyleSheet, Image, Platform
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { emojify } from 'react-emojione';
|
||||
|
||||
|
@ -151,18 +153,22 @@ export default class RoomItem extends React.Component {
|
|||
showLastMessage: true,
|
||||
avatarSize: 48
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const oldlastMessage = this.props.lastMessage;
|
||||
const { lastMessage, _updatedAt } = this.props;
|
||||
const oldlastMessage = lastMessage;
|
||||
const newLastmessage = nextProps.lastMessage;
|
||||
|
||||
if (oldlastMessage && newLastmessage && oldlastMessage.ts.toGMTString() !== newLastmessage.ts.toGMTString()) {
|
||||
return true;
|
||||
}
|
||||
if (this.props._updatedAt && nextProps._updatedAt && nextProps._updatedAt.toGMTString() !== this.props._updatedAt.toGMTString()) {
|
||||
if (_updatedAt && nextProps._updatedAt && nextProps._updatedAt.toGMTString() !== _updatedAt.toGMTString()) {
|
||||
return true;
|
||||
}
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
return attrs.some(key => nextProps[key] !== this.props[key]);
|
||||
}
|
||||
|
||||
get avatar() {
|
||||
const {
|
||||
type, name, avatarSize, baseUrl
|
||||
|
@ -172,10 +178,10 @@ export default class RoomItem extends React.Component {
|
|||
|
||||
get lastMessage() {
|
||||
const {
|
||||
lastMessage, type, showLastMessage
|
||||
lastMessage, type, showLastMessage, StoreLastMessage, username
|
||||
} = this.props;
|
||||
|
||||
if (!this.props.StoreLastMessage || !showLastMessage) {
|
||||
if (!StoreLastMessage || !showLastMessage) {
|
||||
return '';
|
||||
}
|
||||
if (!lastMessage) {
|
||||
|
@ -184,7 +190,7 @@ export default class RoomItem extends React.Component {
|
|||
|
||||
let prefix = '';
|
||||
|
||||
if (lastMessage.u.username === this.props.username) {
|
||||
if (lastMessage.u.username === username) {
|
||||
prefix = I18n.t('You_colon');
|
||||
} else if (type !== 'd') {
|
||||
prefix = `${ lastMessage.u.username }: `;
|
||||
|
@ -223,7 +229,7 @@ export default class RoomItem extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
favorite, unread, userMentions, name, _updatedAt, alert, testID, height
|
||||
favorite, unread, userMentions, name, _updatedAt, alert, testID, height, onPress, onLongPress
|
||||
} = this.props;
|
||||
|
||||
const date = this.formatDate(_updatedAt);
|
||||
|
@ -245,8 +251,8 @@ export default class RoomItem extends React.Component {
|
|||
|
||||
return (
|
||||
<Touch
|
||||
onPress={this.props.onPress}
|
||||
onLongPress={this.props.onLongPress}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
underlayColor='#FFFFFF'
|
||||
activeOpacity={0.5}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Text, View, StyleSheet, Platform, ViewPropTypes, Image } from 'react-native';
|
||||
import {
|
||||
Text, View, StyleSheet, Platform, ViewPropTypes, Image
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Avatar from '../containers/Avatar';
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { call, takeLatest, select, put } from 'redux-saga/effects';
|
||||
import {
|
||||
call, takeLatest, select, put
|
||||
} from 'redux-saga/effects';
|
||||
import { AsyncStorage } from 'react-native';
|
||||
import { METEOR } from '../actions/actionsTypes';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { delay } from 'redux-saga';
|
||||
import { select, put, call, take, takeLatest } from 'redux-saga/effects';
|
||||
import {
|
||||
select, put, call, take, takeLatest
|
||||
} from 'redux-saga/effects';
|
||||
import { NavigationActions } from '../Navigation';
|
||||
|
||||
import { CREATE_CHANNEL, LOGIN } from '../actions/actionsTypes';
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { AsyncStorage } from 'react-native';
|
||||
import { takeLatest, take, select, put } from 'redux-saga/effects';
|
||||
import {
|
||||
takeLatest, take, select, put
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import { appStart } from '../actions';
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { AsyncStorage } from 'react-native';
|
||||
import { delay } from 'redux-saga';
|
||||
import { put, call, take, takeLatest, select, all } from 'redux-saga/effects';
|
||||
import {
|
||||
put, call, take, takeLatest, select, all
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import { appStart } from '../actions';
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { Alert } from 'react-native';
|
||||
import { put, call, takeLatest, take, select, race, fork, cancel, takeEvery } from 'redux-saga/effects';
|
||||
import {
|
||||
put, call, takeLatest, take, select, race, fork, cancel, takeEvery
|
||||
} from 'redux-saga/effects';
|
||||
import { delay } from 'redux-saga';
|
||||
import { BACKGROUND } from 'redux-enhancer-react-native-appstate';
|
||||
|
||||
|
@ -161,7 +163,7 @@ const handleLeaveRoom = function* handleLeaveRoom({ rid }) {
|
|||
yield goRoomsListAndDelete(rid);
|
||||
} catch (e) {
|
||||
if (e.error === 'error-you-are-last-owner') {
|
||||
Alert.alert(e.error);
|
||||
Alert.alert(I18n.t(e.error));
|
||||
} else {
|
||||
Alert.alert(I18n.t('There_was_an_error_while_action', { action: I18n.t('leaving_room') }));
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text, Switch, SafeAreaView, ScrollView, TextInput, StyleSheet, FlatList, Platform } from 'react-native';
|
||||
import {
|
||||
View, Text, Switch, SafeAreaView, ScrollView, TextInput, StyleSheet, FlatList, Platform
|
||||
} from 'react-native';
|
||||
|
||||
import Loading from '../containers/Loading';
|
||||
import LoggedView from './View';
|
||||
import { createChannelRequest } from '../actions/createChannel';
|
||||
import { removeUser } from '../actions/selectedUsers';
|
||||
import { createChannelRequest as createChannelRequestAction } from '../actions/createChannel';
|
||||
import { removeUser as removeUserAction } from '../actions/selectedUsers';
|
||||
import sharedStyles from './Styles';
|
||||
import KeyboardView from '../presentation/KeyboardView';
|
||||
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||
|
@ -73,8 +75,8 @@ const styles = StyleSheet.create({
|
|||
createChannel: state.createChannel,
|
||||
users: state.selectedUsers.users
|
||||
}), dispatch => ({
|
||||
create: data => dispatch(createChannelRequest(data)),
|
||||
removeUser: user => dispatch(removeUser(user))
|
||||
create: data => dispatch(createChannelRequestAction(data)),
|
||||
removeUser: user => dispatch(removeUserAction(user))
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class CreateChannelView extends LoggedView {
|
||||
|
@ -105,15 +107,19 @@ export default class CreateChannelView extends LoggedView {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.createChannel.error && prevProps.createChannel.error !== this.props.createChannel.error) {
|
||||
const { createChannel } = this.props;
|
||||
|
||||
if (createChannel.error && prevProps.createChannel.error !== createChannel.error) {
|
||||
setTimeout(() => {
|
||||
const msg = this.props.createChannel.error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') });
|
||||
const msg = createChannel.error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') });
|
||||
showErrorAlert(msg);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
onChangeText = (channelName) => {
|
||||
const { navigator } = this.props;
|
||||
|
||||
const rightButtons = [];
|
||||
if (channelName.trim().length > 0) {
|
||||
rightButtons.push({
|
||||
|
@ -122,7 +128,7 @@ export default class CreateChannelView extends LoggedView {
|
|||
testID: 'create-channel-submit'
|
||||
});
|
||||
}
|
||||
this.props.navigator.setButtons({ rightButtons });
|
||||
navigator.setButtons({ rightButtons });
|
||||
this.setState({ channelName });
|
||||
}
|
||||
|
||||
|
@ -135,28 +141,30 @@ export default class CreateChannelView extends LoggedView {
|
|||
}
|
||||
|
||||
submit = () => {
|
||||
if (!this.state.channelName.trim() || this.props.createChannel.isFetching) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
channelName, type, readOnly, broadcast
|
||||
} = this.state;
|
||||
let { users } = this.props;
|
||||
const { users: usersProps, createChannel, create } = this.props;
|
||||
|
||||
if (!channelName.trim() || createChannel.isFetching) {
|
||||
return;
|
||||
}
|
||||
|
||||
// transform users object into array of usernames
|
||||
users = users.map(user => user.name);
|
||||
const users = usersProps.map(user => user.name);
|
||||
|
||||
// create channel
|
||||
this.props.create({
|
||||
create({
|
||||
name: channelName, users, type, readOnly, broadcast
|
||||
});
|
||||
}
|
||||
|
||||
removeUser = (user) => {
|
||||
if (this.props.users.length === 1) {
|
||||
const { users, removeUser } = this.props;
|
||||
if (users.length === 1) {
|
||||
return;
|
||||
}
|
||||
this.props.removeUser(user);
|
||||
removeUser(user);
|
||||
}
|
||||
|
||||
renderSwitch = ({
|
||||
|
@ -215,20 +223,27 @@ export default class CreateChannelView extends LoggedView {
|
|||
|
||||
renderFormSeparator = () => <View style={[sharedStyles.separator, styles.formSeparator]} />
|
||||
|
||||
renderItem = ({ item }) => (
|
||||
renderItem = ({ item }) => {
|
||||
const { baseUrl } = this.props;
|
||||
|
||||
return (
|
||||
<UserItem
|
||||
name={item.fname}
|
||||
username={item.name}
|
||||
onPress={() => this.removeUser(item)}
|
||||
testID={`create-channel-view-item-${ item.name }`}
|
||||
baseUrl={this.props.baseUrl}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderInvitedList = () => (
|
||||
renderInvitedList = () => {
|
||||
const { users } = this.props;
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={this.props.users}
|
||||
extraData={this.props.users}
|
||||
data={users}
|
||||
extraData={users}
|
||||
keyExtractor={item => item._id}
|
||||
style={[styles.list, sharedStyles.separatorVertical]}
|
||||
renderItem={this.renderItem}
|
||||
|
@ -236,10 +251,14 @@ export default class CreateChannelView extends LoggedView {
|
|||
enableEmptySections
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const userCount = this.props.users.length;
|
||||
const { channelName } = this.state;
|
||||
const { users, createChannel } = this.props;
|
||||
const userCount = users.length;
|
||||
|
||||
return (
|
||||
<KeyboardView
|
||||
contentContainerStyle={[sharedStyles.container, styles.container]}
|
||||
|
@ -252,7 +271,7 @@ export default class CreateChannelView extends LoggedView {
|
|||
ref={ref => this.channelNameRef = ref}
|
||||
style={styles.input}
|
||||
label={I18n.t('Channel_Name')}
|
||||
value={this.state.channelName}
|
||||
value={channelName}
|
||||
onChangeText={this.onChangeText}
|
||||
placeholder={I18n.t('Channel_Name')}
|
||||
returnKeyType='done'
|
||||
|
@ -273,7 +292,7 @@ export default class CreateChannelView extends LoggedView {
|
|||
<Text style={styles.invitedCount}>{userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })}</Text>
|
||||
</View>
|
||||
{this.renderInvitedList()}
|
||||
<Loading visible={this.props.createChannel.isFetching} />
|
||||
<Loading visible={createChannel.isFetching} />
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</KeyboardView>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, View, SafeAreaView, ScrollView } from 'react-native';
|
||||
import {
|
||||
Text, View, SafeAreaView, ScrollView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import LoggedView from './View';
|
||||
import { forgotPasswordInit, forgotPasswordRequest } from '../actions/login';
|
||||
import { forgotPasswordInit as forgotPasswordInitAction, forgotPasswordRequest as forgotPasswordRequestAction } from '../actions/login';
|
||||
import KeyboardView from '../presentation/KeyboardView';
|
||||
import TextInput from '../containers/TextInput';
|
||||
import Button from '../containers/Button';
|
||||
|
@ -17,8 +19,8 @@ import I18n from '../i18n';
|
|||
@connect(state => ({
|
||||
login: state.login
|
||||
}), dispatch => ({
|
||||
forgotPasswordInit: () => dispatch(forgotPasswordInit()),
|
||||
forgotPasswordRequest: email => dispatch(forgotPasswordRequest(email))
|
||||
forgotPasswordInit: () => dispatch(forgotPasswordInitAction()),
|
||||
forgotPasswordRequest: email => dispatch(forgotPasswordRequestAction(email))
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class ForgotPasswordView extends LoggedView {
|
||||
|
@ -39,13 +41,14 @@ export default class ForgotPasswordView extends LoggedView {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.forgotPasswordInit();
|
||||
const { forgotPasswordInit } = this.props;
|
||||
forgotPasswordInit();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { login } = this.props;
|
||||
const { login, navigator } = this.props;
|
||||
if (login.success) {
|
||||
this.props.navigator.pop();
|
||||
navigator.pop();
|
||||
setTimeout(() => {
|
||||
showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert'));
|
||||
});
|
||||
|
@ -64,13 +67,17 @@ export default class ForgotPasswordView extends LoggedView {
|
|||
|
||||
resetPassword = () => {
|
||||
const { email, invalidEmail } = this.state;
|
||||
const { forgotPasswordRequest } = this.props;
|
||||
if (invalidEmail || !email) {
|
||||
return;
|
||||
}
|
||||
this.props.forgotPasswordRequest(email);
|
||||
forgotPasswordRequest(email);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { invalidEmail } = this.state;
|
||||
const { login } = this.props;
|
||||
|
||||
return (
|
||||
<KeyboardView
|
||||
contentContainerStyle={styles.container}
|
||||
|
@ -80,7 +87,7 @@ export default class ForgotPasswordView extends LoggedView {
|
|||
<SafeAreaView style={styles.container} testID='forgot-password-view'>
|
||||
<View>
|
||||
<TextInput
|
||||
inputStyle={this.state.invalidEmail ? { borderColor: 'red' } : {}}
|
||||
inputStyle={invalidEmail ? { borderColor: 'red' } : {}}
|
||||
label={I18n.t('Email')}
|
||||
placeholder={I18n.t('Email')}
|
||||
keyboardType='email-address'
|
||||
|
@ -99,8 +106,8 @@ export default class ForgotPasswordView extends LoggedView {
|
|||
/>
|
||||
</View>
|
||||
|
||||
{this.props.login.failure ? <Text style={styles.error}>{this.props.login.error.reason}</Text> : null}
|
||||
<Loading visible={this.props.login.isFetching} />
|
||||
{login.failure ? <Text style={styles.error}>{login.error.reason}</Text> : null}
|
||||
<Loading visible={login.isFetching} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, View, ScrollView, TouchableOpacity, LayoutAnimation, Image, StyleSheet, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
Text, View, ScrollView, TouchableOpacity, LayoutAnimation, Image, StyleSheet, SafeAreaView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
import { open, close } from '../actions/login';
|
||||
import { open as openAction, close as closeAction } from '../actions/login';
|
||||
import LoggedView from './View';
|
||||
import sharedStyles from './Styles';
|
||||
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||
|
@ -57,8 +59,8 @@ const styles = StyleSheet.create({
|
|||
Accounts_OAuth_Twitter: state.settings.Accounts_OAuth_Twitter,
|
||||
services: state.login.services
|
||||
}), dispatch => ({
|
||||
open: () => dispatch(open()),
|
||||
close: () => dispatch(close())
|
||||
open: () => dispatch(openAction()),
|
||||
close: () => dispatch(closeAction())
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class LoginSignupView extends LoggedView {
|
||||
|
@ -85,23 +87,27 @@ export default class LoginSignupView extends LoggedView {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.open();
|
||||
const { open } = this.props;
|
||||
open();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.services !== nextProps.services) {
|
||||
const { services } = this.props;
|
||||
if (services !== nextProps.services) {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.close();
|
||||
const { close } = this.props;
|
||||
close();
|
||||
}
|
||||
|
||||
onPressFacebook = () => {
|
||||
const { appId } = this.props.services.facebook;
|
||||
const { services, server } = this.props;
|
||||
const { appId } = services.facebook;
|
||||
const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth';
|
||||
const redirect_uri = `${ this.props.server }/_oauth/facebook?close`;
|
||||
const redirect_uri = `${ server }/_oauth/facebook?close`;
|
||||
const scope = 'email';
|
||||
const state = this.getOAuthState();
|
||||
const params = `?client_id=${ appId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&display=touch`;
|
||||
|
@ -109,9 +115,10 @@ export default class LoginSignupView extends LoggedView {
|
|||
}
|
||||
|
||||
onPressGithub = () => {
|
||||
const { clientId } = this.props.services.github;
|
||||
const { services, server } = this.props;
|
||||
const { clientId } = services.github;
|
||||
const endpoint = `https://github.com/login?client_id=${ clientId }&return_to=${ encodeURIComponent('/login/oauth/authorize') }`;
|
||||
const redirect_uri = `${ this.props.server }/_oauth/github?close`;
|
||||
const redirect_uri = `${ server }/_oauth/github?close`;
|
||||
const scope = 'user:email';
|
||||
const state = this.getOAuthState();
|
||||
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }`;
|
||||
|
@ -119,9 +126,10 @@ export default class LoginSignupView extends LoggedView {
|
|||
}
|
||||
|
||||
onPressGitlab = () => {
|
||||
const { clientId } = this.props.services.gitlab;
|
||||
const { services, server } = this.props;
|
||||
const { clientId } = services.gitlab;
|
||||
const endpoint = 'https://gitlab.com/oauth/authorize';
|
||||
const redirect_uri = `${ this.props.server }/_oauth/gitlab?close`;
|
||||
const redirect_uri = `${ server }/_oauth/gitlab?close`;
|
||||
const scope = 'read_user';
|
||||
const state = this.getOAuthState();
|
||||
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
|
||||
|
@ -129,9 +137,10 @@ export default class LoginSignupView extends LoggedView {
|
|||
}
|
||||
|
||||
onPressGoogle = () => {
|
||||
const { clientId } = this.props.services.google;
|
||||
const { services, server } = this.props;
|
||||
const { clientId } = services.google;
|
||||
const endpoint = 'https://accounts.google.com/o/oauth2/auth';
|
||||
const redirect_uri = `${ this.props.server }/_oauth/google?close`;
|
||||
const redirect_uri = `${ server }/_oauth/google?close`;
|
||||
const scope = 'email';
|
||||
const state = this.getOAuthState();
|
||||
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
|
||||
|
@ -139,9 +148,10 @@ export default class LoginSignupView extends LoggedView {
|
|||
}
|
||||
|
||||
onPressLinkedin = () => {
|
||||
const { clientId } = this.props.services.linkedin;
|
||||
const { services, server } = this.props;
|
||||
const { clientId } = services.linkedin;
|
||||
const endpoint = 'https://www.linkedin.com/uas/oauth2/authorization';
|
||||
const redirect_uri = `${ this.props.server }/_oauth/linkedin?close`;
|
||||
const redirect_uri = `${ server }/_oauth/linkedin?close`;
|
||||
const scope = 'r_emailaddress';
|
||||
const state = this.getOAuthState();
|
||||
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
|
||||
|
@ -149,17 +159,19 @@ export default class LoginSignupView extends LoggedView {
|
|||
}
|
||||
|
||||
onPressMeteor = () => {
|
||||
const { clientId } = this.props.services['meteor-developer'];
|
||||
const { services, server } = this.props;
|
||||
const { clientId } = services['meteor-developer'];
|
||||
const endpoint = 'https://www.meteor.com/oauth2/authorize';
|
||||
const redirect_uri = `${ this.props.server }/_oauth/meteor-developer`;
|
||||
const redirect_uri = `${ server }/_oauth/meteor-developer`;
|
||||
const state = this.getOAuthState();
|
||||
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&state=${ state }&response_type=code`;
|
||||
this.openOAuth(`${ endpoint }${ params }`);
|
||||
}
|
||||
|
||||
onPressTwitter = () => {
|
||||
const { server } = this.props;
|
||||
const state = this.getOAuthState();
|
||||
const url = `${ this.props.server }/_oauth/twitter/?requestTokenAndRedirect=true&state=${ state }`;
|
||||
const url = `${ server }/_oauth/twitter/?requestTokenAndRedirect=true&state=${ state }`;
|
||||
this.openOAuth(url);
|
||||
}
|
||||
|
||||
|
@ -169,7 +181,8 @@ export default class LoginSignupView extends LoggedView {
|
|||
}
|
||||
|
||||
openOAuth = (oAuthUrl) => {
|
||||
this.props.navigator.showModal({
|
||||
const { navigator } = this.props;
|
||||
navigator.showModal({
|
||||
screen: 'OAuthView',
|
||||
title: 'OAuth',
|
||||
passProps: {
|
||||
|
@ -179,93 +192,113 @@ export default class LoginSignupView extends LoggedView {
|
|||
}
|
||||
|
||||
login = () => {
|
||||
this.props.navigator.push({
|
||||
const { navigator, server } = this.props;
|
||||
navigator.push({
|
||||
screen: 'LoginView',
|
||||
title: this.props.server,
|
||||
title: server,
|
||||
backButtonTitle: ''
|
||||
});
|
||||
}
|
||||
|
||||
register = () => {
|
||||
this.props.navigator.push({
|
||||
const { navigator, server } = this.props;
|
||||
navigator.push({
|
||||
screen: 'RegisterView',
|
||||
title: this.props.server,
|
||||
title: server,
|
||||
backButtonTitle: ''
|
||||
});
|
||||
}
|
||||
|
||||
renderServices = () => {
|
||||
const { services } = this.props;
|
||||
const {
|
||||
services, Accounts_OAuth_Facebook, Accounts_OAuth_Github, Accounts_OAuth_Gitlab, Accounts_OAuth_Google, Accounts_OAuth_Linkedin, Accounts_OAuth_Meteor, Accounts_OAuth_Twitter
|
||||
} = this.props;
|
||||
|
||||
if (!Object.keys(services).length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.servicesContainer}>
|
||||
<Text style={styles.servicesTitle}>
|
||||
{I18n.t('Or_continue_using_social_accounts')}
|
||||
</Text>
|
||||
<View style={sharedStyles.loginOAuthButtons} key='services'>
|
||||
{this.props.Accounts_OAuth_Facebook && this.props.services.facebook ?
|
||||
{Accounts_OAuth_Facebook && services.facebook
|
||||
? (
|
||||
<TouchableOpacity
|
||||
style={[sharedStyles.oauthButton, sharedStyles.facebookButton]}
|
||||
onPress={this.onPressFacebook}
|
||||
>
|
||||
<Icon name='facebook' size={20} color='#ffffff' />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{this.props.Accounts_OAuth_Github && this.props.services.github ?
|
||||
{Accounts_OAuth_Github && services.github
|
||||
? (
|
||||
<TouchableOpacity
|
||||
style={[sharedStyles.oauthButton, sharedStyles.githubButton]}
|
||||
onPress={this.onPressGithub}
|
||||
>
|
||||
<Icon name='github' size={20} color='#ffffff' />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{this.props.Accounts_OAuth_Gitlab && this.props.services.gitlab ?
|
||||
{Accounts_OAuth_Gitlab && services.gitlab
|
||||
? (
|
||||
<TouchableOpacity
|
||||
style={[sharedStyles.oauthButton, sharedStyles.gitlabButton]}
|
||||
onPress={this.onPressGitlab}
|
||||
>
|
||||
<Icon name='gitlab' size={20} color='#ffffff' />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{this.props.Accounts_OAuth_Google && this.props.services.google ?
|
||||
{Accounts_OAuth_Google && services.google
|
||||
? (
|
||||
<TouchableOpacity
|
||||
style={[sharedStyles.oauthButton, sharedStyles.googleButton]}
|
||||
onPress={this.onPressGoogle}
|
||||
>
|
||||
<Icon name='google' size={20} color='#ffffff' />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{this.props.Accounts_OAuth_Linkedin && this.props.services.linkedin ?
|
||||
{Accounts_OAuth_Linkedin && services.linkedin
|
||||
? (
|
||||
<TouchableOpacity
|
||||
style={[sharedStyles.oauthButton, sharedStyles.linkedinButton]}
|
||||
onPress={this.onPressLinkedin}
|
||||
>
|
||||
<Icon name='linkedin' size={20} color='#ffffff' />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{this.props.Accounts_OAuth_Meteor && this.props.services['meteor-developer'] ?
|
||||
{Accounts_OAuth_Meteor && services['meteor-developer']
|
||||
? (
|
||||
<TouchableOpacity
|
||||
style={[sharedStyles.oauthButton, sharedStyles.meteorButton]}
|
||||
onPress={this.onPressMeteor}
|
||||
>
|
||||
<MaterialCommunityIcons name='meteor' size={25} color='#ffffff' />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{this.props.Accounts_OAuth_Twitter && this.props.services.twitter ?
|
||||
{Accounts_OAuth_Twitter && services.twitter
|
||||
? (
|
||||
<TouchableOpacity
|
||||
style={[sharedStyles.oauthButton, sharedStyles.twitterButton]}
|
||||
onPress={this.onPressTwitter}
|
||||
>
|
||||
<Icon name='twitter' size={20} color='#ffffff' />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</View>
|
||||
|
@ -274,6 +307,8 @@ export default class LoginSignupView extends LoggedView {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { isFetching } = this.props;
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={[sharedStyles.container, sharedStyles.containerScrollView]}
|
||||
|
@ -298,7 +333,7 @@ export default class LoginSignupView extends LoggedView {
|
|||
/>
|
||||
{this.renderServices()}
|
||||
</View>
|
||||
<Loading visible={this.props.isFetching} />
|
||||
<Loading visible={isFetching} />
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Keyboard, Text, ScrollView, View, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
Keyboard, Text, ScrollView, View, SafeAreaView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { Answers } from 'react-native-fabric';
|
||||
|
||||
|
@ -50,6 +52,8 @@ export default class LoginView extends LoggedView {
|
|||
|
||||
submit = async() => {
|
||||
const { username, password, code } = this.state;
|
||||
const { loginSubmit } = this.props;
|
||||
|
||||
if (username.trim() === '' || password.trim() === '') {
|
||||
showToast(I18n.t('Email_or_password_field_is_empty'));
|
||||
return;
|
||||
|
@ -57,7 +61,7 @@ export default class LoginView extends LoggedView {
|
|||
Keyboard.dismiss();
|
||||
|
||||
try {
|
||||
await this.props.loginSubmit({ username, password, code });
|
||||
await loginSubmit({ username, password, code });
|
||||
Answers.logLogin('Email', true);
|
||||
} catch (error) {
|
||||
console.warn('LoginView submit', error);
|
||||
|
@ -65,15 +69,17 @@ export default class LoginView extends LoggedView {
|
|||
}
|
||||
|
||||
register = () => {
|
||||
this.props.navigator.push({
|
||||
const { navigator, server } = this.props;
|
||||
navigator.push({
|
||||
screen: 'RegisterView',
|
||||
title: this.props.server,
|
||||
title: server,
|
||||
backButtonTitle: ''
|
||||
});
|
||||
}
|
||||
|
||||
forgotPassword = () => {
|
||||
this.props.navigator.push({
|
||||
const { navigator } = this.props;
|
||||
navigator.push({
|
||||
screen: 'ForgotPasswordView',
|
||||
title: I18n.t('Forgot_Password'),
|
||||
backButtonTitle: ''
|
||||
|
@ -81,7 +87,8 @@ export default class LoginView extends LoggedView {
|
|||
}
|
||||
|
||||
renderTOTP = () => {
|
||||
if (/totp/ig.test(this.props.error)) {
|
||||
const { error } = this.props;
|
||||
if (/totp/ig.test(error)) {
|
||||
return (
|
||||
<TextInput
|
||||
inputRef={ref => this.codeInput = ref}
|
||||
|
@ -99,6 +106,10 @@ export default class LoginView extends LoggedView {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder, failure, reason, isFetching
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<KeyboardView
|
||||
contentContainerStyle={styles.container}
|
||||
|
@ -110,7 +121,7 @@ export default class LoginView extends LoggedView {
|
|||
<Text style={[styles.loginText, styles.loginTitle]}>Login</Text>
|
||||
<TextInput
|
||||
label={I18n.t('Username')}
|
||||
placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || I18n.t('Username')}
|
||||
placeholder={Accounts_EmailOrUsernamePlaceholder || I18n.t('Username')}
|
||||
keyboardType='email-address'
|
||||
returnKeyType='next'
|
||||
iconLeft='at'
|
||||
|
@ -122,7 +133,7 @@ export default class LoginView extends LoggedView {
|
|||
<TextInput
|
||||
inputRef={(e) => { this.password = e; }}
|
||||
label={I18n.t('Password')}
|
||||
placeholder={this.props.Accounts_PasswordPlaceholder || I18n.t('Password')}
|
||||
placeholder={Accounts_PasswordPlaceholder || I18n.t('Password')}
|
||||
returnKeyType='done'
|
||||
iconLeft='key-variant'
|
||||
secureTextEntry
|
||||
|
@ -156,8 +167,8 @@ export default class LoginView extends LoggedView {
|
|||
</Text>
|
||||
</View>
|
||||
|
||||
{this.props.failure ? <Text style={styles.error}>{this.props.reason}</Text> : null}
|
||||
<Loading visible={this.props.isFetching} />
|
||||
{failure ? <Text style={styles.error}>{reason}</Text> : null}
|
||||
<Loading visible={isFetching} />
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
</KeyboardView>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
FlatList, View, Text, SafeAreaView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { openMentionedMessages as openMentionedMessagesAction, closeMentionedMessages as closeMentionedMessagesAction } from '../../actions/mentionedMessages';
|
||||
import LoggedView from '../View';
|
||||
import { openMentionedMessages, closeMentionedMessages } from '../../actions/mentionedMessages';
|
||||
import styles from './styles';
|
||||
import Message from '../../containers/message';
|
||||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||
|
@ -19,8 +21,8 @@ import I18n from '../../i18n';
|
|||
token: state.login.user && state.login.user.token
|
||||
}
|
||||
}), dispatch => ({
|
||||
openMentionedMessages: (rid, limit) => dispatch(openMentionedMessages(rid, limit)),
|
||||
closeMentionedMessages: () => dispatch(closeMentionedMessages())
|
||||
openMentionedMessages: (rid, limit) => dispatch(openMentionedMessagesAction(rid, limit)),
|
||||
closeMentionedMessages: () => dispatch(closeMentionedMessagesAction())
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class MentionedMessagesView extends LoggedView {
|
||||
|
@ -47,17 +49,20 @@ export default class MentionedMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.ready && nextProps.ready !== this.props.ready) {
|
||||
const { ready } = this.props;
|
||||
if (nextProps.ready && nextProps.ready !== ready) {
|
||||
this.setState({ loading: false, loadingMore: false });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.closeMentionedMessages();
|
||||
const { closeMentionedMessages } = this.props;
|
||||
closeMentionedMessages();
|
||||
}
|
||||
|
||||
load = () => {
|
||||
this.props.openMentionedMessages(this.props.rid, this.limit);
|
||||
const { openMentionedMessages, rid } = this.props;
|
||||
openMentionedMessages(rid, this.limit);
|
||||
}
|
||||
|
||||
moreData = () => {
|
||||
|
@ -79,15 +84,18 @@ export default class MentionedMessagesView extends LoggedView {
|
|||
</View>
|
||||
)
|
||||
|
||||
renderItem = ({ item }) => (
|
||||
renderItem = ({ item }) => {
|
||||
const { user } = this.props;
|
||||
return (
|
||||
<Message
|
||||
item={item}
|
||||
style={styles.message}
|
||||
reactions={item.reactions}
|
||||
user={this.props.user}
|
||||
user={user}
|
||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, loadingMore } = this.state;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, StyleSheet, SafeAreaView, FlatList, Text, Platform, Image } from 'react-native';
|
||||
import {
|
||||
View, StyleSheet, SafeAreaView, FlatList, Text, Platform, Image
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import database from '../lib/realm';
|
||||
|
@ -75,9 +77,10 @@ export default class NewMessageView extends LoggedView {
|
|||
}
|
||||
|
||||
async onNavigatorEvent(event) {
|
||||
const { navigator } = this.props;
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'cancel') {
|
||||
this.props.navigator.dismissModal();
|
||||
navigator.dismissModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,12 +90,14 @@ export default class NewMessageView extends LoggedView {
|
|||
}
|
||||
|
||||
onPressItem = (item) => {
|
||||
this.props.navigator.dismissModal();
|
||||
const { navigator, onPressItem } = this.props;
|
||||
navigator.dismissModal();
|
||||
setTimeout(() => {
|
||||
this.props.onPressItem(item);
|
||||
onPressItem(item);
|
||||
}, 600);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
updateState = debounce(() => {
|
||||
this.forceUpdate();
|
||||
}, 1000);
|
||||
|
@ -105,7 +110,8 @@ export default class NewMessageView extends LoggedView {
|
|||
}
|
||||
|
||||
createChannel = () => {
|
||||
this.props.navigator.push({
|
||||
const { navigator } = this.props;
|
||||
navigator.push({
|
||||
screen: 'SelectedUsersView',
|
||||
title: I18n.t('Select_Users'),
|
||||
backButtonTitle: '',
|
||||
|
@ -130,14 +136,17 @@ export default class NewMessageView extends LoggedView {
|
|||
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />;
|
||||
|
||||
renderItem = ({ item, index }) => {
|
||||
const { search } = this.state;
|
||||
const { baseUrl } = this.props;
|
||||
|
||||
let style = {};
|
||||
if (index === 0) {
|
||||
style = { ...sharedStyles.separatorTop };
|
||||
}
|
||||
if (this.state.search.length > 0 && index === this.state.search.length - 1) {
|
||||
if (search.length > 0 && index === search.length - 1) {
|
||||
style = { ...style, ...sharedStyles.separatorBottom };
|
||||
}
|
||||
if (this.state.search.length === 0 && index === this.data.length - 1) {
|
||||
if (search.length === 0 && index === this.data.length - 1) {
|
||||
style = { ...style, ...sharedStyles.separatorBottom };
|
||||
}
|
||||
return (
|
||||
|
@ -145,16 +154,18 @@ export default class NewMessageView extends LoggedView {
|
|||
name={item.search ? item.name : item.fname}
|
||||
username={item.search ? item.username : item.name}
|
||||
onPress={() => this.onPressItem(item)}
|
||||
baseUrl={this.props.baseUrl}
|
||||
baseUrl={baseUrl}
|
||||
testID={`new-message-view-item-${ item.name }`}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderList = () => (
|
||||
renderList = () => {
|
||||
const { search } = this.state;
|
||||
return (
|
||||
<FlatList
|
||||
data={this.state.search.length > 0 ? this.state.search : this.data}
|
||||
data={search.length > 0 ? search : this.data}
|
||||
extraData={this.state}
|
||||
keyExtractor={item => item._id}
|
||||
ListHeaderComponent={this.renderHeader}
|
||||
|
@ -162,7 +173,8 @@ export default class NewMessageView extends LoggedView {
|
|||
ItemSeparatorComponent={this.renderSeparator}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render = () => (
|
||||
<SafeAreaView style={styles.safeAreaView} testID='new-message-view'>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, ScrollView, Keyboard, SafeAreaView, Image, Alert, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
import {
|
||||
Text, ScrollView, Keyboard, SafeAreaView, Image, Alert, StyleSheet, TouchableOpacity
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
|
@ -74,9 +76,9 @@ export default class NewServerView extends LoggedView {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { server } = this.props;
|
||||
const { server, connectServer } = this.props;
|
||||
if (server) {
|
||||
this.props.connectServer(server);
|
||||
connectServer(server);
|
||||
this.setState({ text: server });
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
|
@ -86,7 +88,8 @@ export default class NewServerView extends LoggedView {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.failure && nextProps.failure !== this.props.failure) {
|
||||
const { failure } = this.props;
|
||||
if (nextProps.failure && nextProps.failure !== failure) {
|
||||
Alert.alert(I18n.t('Oops'), I18n.t('The_URL_is_invalid'));
|
||||
}
|
||||
}
|
||||
|
@ -96,17 +99,20 @@ export default class NewServerView extends LoggedView {
|
|||
}
|
||||
|
||||
submit = () => {
|
||||
if (this.state.text) {
|
||||
const { text } = this.state;
|
||||
const { connectServer } = this.props;
|
||||
|
||||
if (text) {
|
||||
Keyboard.dismiss();
|
||||
this.props.connectServer(this.completeUrl(this.state.text));
|
||||
connectServer(this.completeUrl(text));
|
||||
}
|
||||
}
|
||||
|
||||
completeUrl = (url) => {
|
||||
url = url && url.trim();
|
||||
|
||||
if (/^(\w|[0-9-_]){3,}$/.test(url) &&
|
||||
/^(htt(ps?)?)|(loca((l)?|(lh)?|(lho)?|(lhos)?|(lhost:?\d*)?)$)/.test(url) === false) {
|
||||
if (/^(\w|[0-9-_]){3,}$/.test(url)
|
||||
&& /^(htt(ps?)?)|(loca((l)?|(lh)?|(lho)?|(lhos)?|(lhost:?\d*)?)$)/.test(url) === false) {
|
||||
url = `${ url }.rocket.chat`;
|
||||
}
|
||||
|
||||
|
@ -122,6 +128,8 @@ export default class NewServerView extends LoggedView {
|
|||
}
|
||||
|
||||
renderBack = () => {
|
||||
const { navigator } = this.props;
|
||||
|
||||
let top = 15;
|
||||
if (DeviceInfo.getBrand() === 'Apple') {
|
||||
top = DeviceInfo.isNotch() ? 45 : 30;
|
||||
|
@ -130,7 +138,7 @@ export default class NewServerView extends LoggedView {
|
|||
return (
|
||||
<TouchableOpacity
|
||||
style={[styles.backButton, { top }]}
|
||||
onPress={() => this.props.navigator.pop()}
|
||||
onPress={() => navigator.pop()}
|
||||
>
|
||||
<Icon
|
||||
name='ios-arrow-back'
|
||||
|
|
|
@ -52,9 +52,10 @@ export default class OAuthView extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { oAuthUrl, navigator } = this.props;
|
||||
return (
|
||||
<WebView
|
||||
source={{ uri: this.props.oAuthUrl }}
|
||||
source={{ uri: oAuthUrl }}
|
||||
userAgent={userAgent}
|
||||
onNavigationStateChange={(webViewState) => {
|
||||
const url = decodeURIComponent(webViewState.url);
|
||||
|
@ -62,7 +63,7 @@ export default class OAuthView extends React.PureComponent {
|
|||
const parts = url.split('#');
|
||||
const credentials = JSON.parse(parts[1]);
|
||||
this.login({ oauth: { ...credentials } });
|
||||
this.props.navigator.dismissModal();
|
||||
navigator.dismissModal();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text, TouchableWithoutFeedback, Image } from 'react-native';
|
||||
import {
|
||||
View, Text, TouchableWithoutFeedback, Image
|
||||
} from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import { View, Text, Image, SafeAreaView, TouchableOpacity } from 'react-native';
|
||||
import {
|
||||
View, Text, Image, SafeAreaView, TouchableOpacity
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { connect } from 'react-redux';
|
||||
|
@ -56,11 +58,13 @@ export default class OnboardingView extends LoggedView {
|
|||
}
|
||||
|
||||
close = () => {
|
||||
this.props.navigator.dismissModal();
|
||||
const { navigator } = this.props;
|
||||
navigator.dismissModal();
|
||||
}
|
||||
|
||||
connectServer = () => {
|
||||
this.props.navigator.push({
|
||||
const { navigator } = this.props;
|
||||
navigator.push({
|
||||
screen: 'NewServerView',
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
|
@ -70,7 +74,8 @@ export default class OnboardingView extends LoggedView {
|
|||
}
|
||||
|
||||
joinCommunity = () => {
|
||||
this.props.navigator.push({
|
||||
const { navigator } = this.props;
|
||||
navigator.push({
|
||||
screen: 'NewServerView',
|
||||
backButtonTitle: '',
|
||||
passProps: {
|
||||
|
@ -87,7 +92,9 @@ export default class OnboardingView extends LoggedView {
|
|||
}
|
||||
|
||||
renderClose = () => {
|
||||
if (this.props.previousServer) {
|
||||
const { previousServer } = this.props;
|
||||
|
||||
if (previousServer) {
|
||||
let top = 15;
|
||||
if (DeviceInfo.getBrand() === 'Apple') {
|
||||
top = DeviceInfo.isNotch() ? 45 : 30;
|
||||
|
@ -96,6 +103,7 @@ export default class OnboardingView extends LoggedView {
|
|||
<TouchableOpacity
|
||||
style={[styles.closeModal, { top }]}
|
||||
onPress={this.close}
|
||||
testID='onboarding-close'
|
||||
>
|
||||
<Icon
|
||||
name='close'
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
FlatList, View, Text, SafeAreaView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
|
||||
import { openPinnedMessages as openPinnedMessagesAction, closePinnedMessages as closePinnedMessagesAction } from '../../actions/pinnedMessages';
|
||||
import { togglePinRequest as togglePinRequestAction } from '../../actions/messages';
|
||||
import LoggedView from '../View';
|
||||
import { openPinnedMessages, closePinnedMessages } from '../../actions/pinnedMessages';
|
||||
import styles from './styles';
|
||||
import Message from '../../containers/message';
|
||||
import { togglePinRequest } from '../../actions/messages';
|
||||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||
import I18n from '../../i18n';
|
||||
|
||||
|
@ -25,9 +27,9 @@ const options = [I18n.t('Unpin'), I18n.t('Cancel')];
|
|||
token: state.login.user && state.login.user.token
|
||||
}
|
||||
}), dispatch => ({
|
||||
openPinnedMessages: (rid, limit) => dispatch(openPinnedMessages(rid, limit)),
|
||||
closePinnedMessages: () => dispatch(closePinnedMessages()),
|
||||
togglePinRequest: message => dispatch(togglePinRequest(message))
|
||||
openPinnedMessages: (rid, limit) => dispatch(openPinnedMessagesAction(rid, limit)),
|
||||
closePinnedMessages: () => dispatch(closePinnedMessagesAction()),
|
||||
togglePinRequest: message => dispatch(togglePinRequestAction(message))
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class PinnedMessagesView extends LoggedView {
|
||||
|
@ -56,13 +58,15 @@ export default class PinnedMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.ready && nextProps.ready !== this.props.ready) {
|
||||
const { ready } = this.props;
|
||||
if (nextProps.ready && nextProps.ready !== ready) {
|
||||
this.setState({ loading: false, loadingMore: false });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.closePinnedMessages();
|
||||
const { closePinnedMessages } = this.props;
|
||||
closePinnedMessages();
|
||||
}
|
||||
|
||||
onLongPress = (message) => {
|
||||
|
@ -73,9 +77,12 @@ export default class PinnedMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
handleActionPress = (actionIndex) => {
|
||||
const { message } = this.state;
|
||||
const { togglePinRequest } = this.props;
|
||||
|
||||
switch (actionIndex) {
|
||||
case PIN_INDEX:
|
||||
this.props.togglePinRequest(this.state.message);
|
||||
togglePinRequest(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -83,7 +90,8 @@ export default class PinnedMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
load = () => {
|
||||
this.props.openPinnedMessages(this.props.rid, this.limit);
|
||||
const { openPinnedMessages, rid } = this.props;
|
||||
openPinnedMessages(rid, this.limit);
|
||||
}
|
||||
|
||||
moreData = () => {
|
||||
|
@ -105,16 +113,19 @@ export default class PinnedMessagesView extends LoggedView {
|
|||
</View>
|
||||
)
|
||||
|
||||
renderItem = ({ item }) => (
|
||||
renderItem = ({ item }) => {
|
||||
const { user } = this.props;
|
||||
return (
|
||||
<Message
|
||||
item={item}
|
||||
style={styles.message}
|
||||
reactions={item.reactions}
|
||||
user={this.props.user}
|
||||
user={user}
|
||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||
onLongPress={this.onLongPress}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, loadingMore } = this.state;
|
||||
|
|
|
@ -20,9 +20,11 @@ export default class PrivacyPolicyView extends LoggedView {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { privacyPolicy } = this.props;
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<WebView originWhitelist={['*']} source={{ html: this.props.privacyPolicy, baseUrl: '' }} />
|
||||
<WebView originWhitelist={['*']} source={{ html: privacyPolicy, baseUrl: '' }} />
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, ScrollView, SafeAreaView, Keyboard, Dimensions } from 'react-native';
|
||||
import {
|
||||
View, ScrollView, SafeAreaView, Keyboard, Dimensions
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import Dialog from 'react-native-dialog';
|
||||
import SHA256 from 'js-sha256';
|
||||
|
@ -61,7 +63,8 @@ export default class ProfileView extends LoggedView {
|
|||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.navigator.setButtons({
|
||||
const { navigator } = this.props;
|
||||
navigator.setButtons({
|
||||
leftButtons: [{
|
||||
id: 'settings',
|
||||
icon: { uri: 'settings', scale: Dimensions.get('window').scale }
|
||||
|
@ -70,9 +73,11 @@ export default class ProfileView extends LoggedView {
|
|||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const { navigator } = this.props;
|
||||
|
||||
this.init();
|
||||
|
||||
this.props.navigator.setDrawerEnabled({
|
||||
navigator.setDrawerEnabled({
|
||||
side: 'left',
|
||||
enabled: true
|
||||
});
|
||||
|
@ -86,15 +91,18 @@ export default class ProfileView extends LoggedView {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.user !== nextProps.user) {
|
||||
const { user } = this.props;
|
||||
if (user !== nextProps.user) {
|
||||
this.init(nextProps.user);
|
||||
}
|
||||
}
|
||||
|
||||
onNavigatorEvent(event) {
|
||||
const { navigator } = this.props;
|
||||
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'settings') {
|
||||
this.props.navigator.toggleDrawer({
|
||||
navigator.toggleDrawer({
|
||||
side: 'left'
|
||||
});
|
||||
}
|
||||
|
@ -106,9 +114,11 @@ export default class ProfileView extends LoggedView {
|
|||
}
|
||||
|
||||
init = (user) => {
|
||||
const { user: userProps } = this.props;
|
||||
const {
|
||||
name, username, emails, customFields
|
||||
} = user || this.props.user;
|
||||
} = user || userProps;
|
||||
|
||||
this.setState({
|
||||
name,
|
||||
username,
|
||||
|
@ -137,12 +147,12 @@ export default class ProfileView extends LoggedView {
|
|||
});
|
||||
}
|
||||
|
||||
return !(user.name === name &&
|
||||
user.username === username &&
|
||||
!newPassword &&
|
||||
(user.emails && user.emails[0].address === email) &&
|
||||
!avatar.data &&
|
||||
!customFieldsChanged
|
||||
return !(user.name === name
|
||||
&& user.username === username
|
||||
&& !newPassword
|
||||
&& (user.emails && user.emails[0].address === email)
|
||||
&& !avatar.data
|
||||
&& !customFieldsChanged
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -278,10 +288,14 @@ export default class ProfileView extends LoggedView {
|
|||
</Touch>
|
||||
)
|
||||
|
||||
renderAvatarButtons = () => (
|
||||
renderAvatarButtons = () => {
|
||||
const { avatarUrl, avatarSuggestions } = this.state;
|
||||
const { user, baseUrl } = this.props;
|
||||
|
||||
return (
|
||||
<View style={styles.avatarButtons}>
|
||||
{this.renderAvatarButton({
|
||||
child: <Avatar text={this.props.user.username} size={50} baseUrl={this.props.baseUrl} forceInitials />,
|
||||
child: <Avatar text={user.username} size={50} baseUrl={baseUrl} forceInitials />,
|
||||
onPress: () => this.resetAvatar(),
|
||||
key: 'profile-view-reset-avatar'
|
||||
})}
|
||||
|
@ -292,15 +306,15 @@ export default class ProfileView extends LoggedView {
|
|||
})}
|
||||
{this.renderAvatarButton({
|
||||
child: <Icon name='link' size={30} />,
|
||||
onPress: () => this.setAvatar({ url: this.state.avatarUrl, data: this.state.avatarUrl, service: 'url' }),
|
||||
disabled: !this.state.avatarUrl,
|
||||
onPress: () => this.setAvatar({ url: avatarUrl, data: avatarUrl, service: 'url' }),
|
||||
disabled: !avatarUrl,
|
||||
key: 'profile-view-avatar-url-button'
|
||||
})}
|
||||
{Object.keys(this.state.avatarSuggestions).map((service) => {
|
||||
const { url, blob, contentType } = this.state.avatarSuggestions[service];
|
||||
{Object.keys(avatarSuggestions).map((service) => {
|
||||
const { url, blob, contentType } = avatarSuggestions[service];
|
||||
return this.renderAvatarButton({
|
||||
key: `profile-view-avatar-${ service }`,
|
||||
child: <Avatar avatar={url} size={50} baseUrl={this.props.baseUrl} />,
|
||||
child: <Avatar avatar={url} size={50} baseUrl={baseUrl} />,
|
||||
onPress: () => this.setAvatar({
|
||||
url, data: blob, service, contentType
|
||||
})
|
||||
|
@ -308,13 +322,16 @@ export default class ProfileView extends LoggedView {
|
|||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderCustomFields = () => {
|
||||
const { customFields } = this.state;
|
||||
if (!this.props.Accounts_CustomFields) {
|
||||
const { Accounts_CustomFields } = this.props;
|
||||
|
||||
if (!Accounts_CustomFields) {
|
||||
return null;
|
||||
}
|
||||
const parsedCustomFields = JSON.parse(this.props.Accounts_CustomFields);
|
||||
const parsedCustomFields = JSON.parse(Accounts_CustomFields);
|
||||
return Object.keys(parsedCustomFields).map((key, index, array) => {
|
||||
if (parsedCustomFields[key].type === 'select') {
|
||||
const options = parsedCustomFields[key].options.map(option => ({ label: option, value: option }));
|
||||
|
@ -325,7 +342,7 @@ export default class ProfileView extends LoggedView {
|
|||
onValueChange={(value) => {
|
||||
const newValue = {};
|
||||
newValue[key] = value;
|
||||
this.setState({ customFields: { ...this.state.customFields, ...newValue } });
|
||||
this.setState({ customFields: { ...customFields, ...newValue } });
|
||||
}}
|
||||
value={customFields[key]}
|
||||
>
|
||||
|
@ -350,7 +367,7 @@ export default class ProfileView extends LoggedView {
|
|||
onChangeText={(value) => {
|
||||
const newValue = {};
|
||||
newValue[key] = value;
|
||||
this.setState({ customFields: { ...this.state.customFields, ...newValue } });
|
||||
this.setState({ customFields: { ...customFields, ...newValue } });
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
if (array.length - 1 > index) {
|
||||
|
@ -365,8 +382,10 @@ export default class ProfileView extends LoggedView {
|
|||
|
||||
render() {
|
||||
const {
|
||||
name, username, email, newPassword, avatarUrl, customFields
|
||||
name, username, email, newPassword, avatarUrl, customFields, avatar, saving, showPasswordAlert
|
||||
} = this.state;
|
||||
const { baseUrl } = this.props;
|
||||
|
||||
return (
|
||||
<KeyboardView
|
||||
contentContainerStyle={sharedStyles.container}
|
||||
|
@ -381,9 +400,9 @@ export default class ProfileView extends LoggedView {
|
|||
<View style={styles.avatarContainer} testID='profile-view-avatar'>
|
||||
<Avatar
|
||||
text={username}
|
||||
avatar={this.state.avatar && this.state.avatar.url}
|
||||
avatar={avatar && avatar.url}
|
||||
size={100}
|
||||
baseUrl={this.props.baseUrl}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
</View>
|
||||
<RCTextInput
|
||||
|
@ -448,8 +467,8 @@ export default class ProfileView extends LoggedView {
|
|||
testID='profile-view-submit'
|
||||
/>
|
||||
</View>
|
||||
<Loading visible={this.state.saving} />
|
||||
<Dialog.Container visible={this.state.showPasswordAlert}>
|
||||
<Loading visible={saving} />
|
||||
<Dialog.Container visible={showPasswordAlert}>
|
||||
<Dialog.Title>
|
||||
{I18n.t('Please_enter_your_password')}
|
||||
</Dialog.Title>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Keyboard, Text, View, ScrollView, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
Keyboard, Text, View, ScrollView, SafeAreaView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { registerSubmit, setUsernameSubmit } from '../actions/login';
|
||||
import { registerSubmit as registerSubmitAction, setUsernameSubmit as setUsernameSubmitAction } from '../actions/login';
|
||||
import TextInput from '../containers/TextInput';
|
||||
import Button from '../containers/Button';
|
||||
import Loading from '../containers/Loading';
|
||||
|
@ -22,8 +24,8 @@ import I18n from '../i18n';
|
|||
Accounts_RepeatPasswordPlaceholder: state.settings.Accounts_RepeatPasswordPlaceholder,
|
||||
login: state.login
|
||||
}), dispatch => ({
|
||||
registerSubmit: params => dispatch(registerSubmit(params)),
|
||||
setUsernameSubmit: params => dispatch(setUsernameSubmit(params))
|
||||
registerSubmit: params => dispatch(registerSubmitAction(params)),
|
||||
setUsernameSubmit: params => dispatch(setUsernameSubmitAction(params))
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class RegisterView extends LoggedView {
|
||||
|
@ -51,28 +53,31 @@ export default class RegisterView extends LoggedView {
|
|||
};
|
||||
}
|
||||
|
||||
valid() {
|
||||
valid = () => {
|
||||
const {
|
||||
name, email, password, confirmPassword
|
||||
} = this.state;
|
||||
return name.trim() && email.trim() &&
|
||||
password && confirmPassword && password === confirmPassword;
|
||||
return name.trim() && email.trim()
|
||||
&& password && confirmPassword && password === confirmPassword;
|
||||
}
|
||||
|
||||
invalidEmail() {
|
||||
return this.props.login.failure && /Email/.test(this.props.login.error.reason) ? this.props.login.error : {};
|
||||
invalidEmail = () => {
|
||||
const { login } = this.props;
|
||||
return login.failure && /Email/.test(login.error && login.error.reason) ? login.error : {};
|
||||
}
|
||||
|
||||
submit = () => {
|
||||
const {
|
||||
name, email, password, code
|
||||
} = this.state;
|
||||
const { registerSubmit } = this.props;
|
||||
|
||||
if (!this.valid()) {
|
||||
showToast(I18n.t('Some_field_is_invalid_or_empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.registerSubmit({
|
||||
registerSubmit({
|
||||
name, email, pass: password, code
|
||||
});
|
||||
Keyboard.dismiss();
|
||||
|
@ -80,17 +85,20 @@ export default class RegisterView extends LoggedView {
|
|||
|
||||
usernameSubmit = () => {
|
||||
const { username } = this.state;
|
||||
const { setUsernameSubmit } = this.props;
|
||||
|
||||
if (!username) {
|
||||
showToast(I18n.t('Username_is_empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.setUsernameSubmit({ username });
|
||||
setUsernameSubmit({ username });
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
|
||||
termsService = () => {
|
||||
this.props.navigator.push({
|
||||
const { navigator } = this.props;
|
||||
navigator.push({
|
||||
screen: 'TermsServiceView',
|
||||
title: I18n.t('Terms_of_Service'),
|
||||
backButtonTitle: ''
|
||||
|
@ -98,7 +106,8 @@ export default class RegisterView extends LoggedView {
|
|||
}
|
||||
|
||||
privacyPolicy = () => {
|
||||
this.props.navigator.push({
|
||||
const { navigator } = this.props;
|
||||
navigator.push({
|
||||
screen: 'PrivacyPolicyView',
|
||||
title: I18n.t('Privacy_Policy'),
|
||||
backButtonTitle: ''
|
||||
|
@ -106,15 +115,20 @@ export default class RegisterView extends LoggedView {
|
|||
}
|
||||
|
||||
_renderRegister() {
|
||||
if (this.props.login.token) {
|
||||
const { password, confirmPassword } = this.state;
|
||||
const {
|
||||
login, Accounts_NamePlaceholder, Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder, Accounts_RepeatPasswordPlaceholder
|
||||
} = this.props;
|
||||
|
||||
if (login.token) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
inputRef={(e) => { this.name = e; }}
|
||||
label={this.props.Accounts_NamePlaceholder || I18n.t('Name')}
|
||||
placeholder={this.props.Accounts_NamePlaceholder || I18n.t('Name')}
|
||||
label={Accounts_NamePlaceholder || I18n.t('Name')}
|
||||
placeholder={Accounts_NamePlaceholder || I18n.t('Name')}
|
||||
returnKeyType='next'
|
||||
iconLeft='account'
|
||||
onChangeText={name => this.setState({ name })}
|
||||
|
@ -123,8 +137,8 @@ export default class RegisterView extends LoggedView {
|
|||
/>
|
||||
<TextInput
|
||||
inputRef={(e) => { this.email = e; }}
|
||||
label={this.props.Accounts_EmailOrUsernamePlaceholder || I18n.t('Email')}
|
||||
placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || I18n.t('Email')}
|
||||
label={Accounts_EmailOrUsernamePlaceholder || I18n.t('Email')}
|
||||
placeholder={Accounts_EmailOrUsernamePlaceholder || I18n.t('Email')}
|
||||
returnKeyType='next'
|
||||
keyboardType='email-address'
|
||||
iconLeft='email'
|
||||
|
@ -135,28 +149,28 @@ export default class RegisterView extends LoggedView {
|
|||
/>
|
||||
<TextInput
|
||||
inputRef={(e) => { this.password = e; }}
|
||||
label={this.props.Accounts_PasswordPlaceholder || I18n.t('Password')}
|
||||
placeholder={this.props.Accounts_PasswordPlaceholder || I18n.t('Password')}
|
||||
label={Accounts_PasswordPlaceholder || I18n.t('Password')}
|
||||
placeholder={Accounts_PasswordPlaceholder || I18n.t('Password')}
|
||||
returnKeyType='next'
|
||||
iconLeft='key-variant'
|
||||
secureTextEntry
|
||||
onChangeText={password => this.setState({ password })}
|
||||
onChangeText={value => this.setState({ password: value })}
|
||||
onSubmitEditing={() => { this.confirmPassword.focus(); }}
|
||||
testID='register-view-password'
|
||||
/>
|
||||
<TextInput
|
||||
inputRef={(e) => { this.confirmPassword = e; }}
|
||||
inputStyle={
|
||||
this.state.password &&
|
||||
this.state.confirmPassword &&
|
||||
this.state.confirmPassword !== this.state.password ? { borderColor: 'red' } : {}
|
||||
password
|
||||
&& confirmPassword
|
||||
&& confirmPassword !== password ? { borderColor: 'red' } : {}
|
||||
}
|
||||
label={this.props.Accounts_RepeatPasswordPlaceholder || I18n.t('Repeat_Password')}
|
||||
placeholder={this.props.Accounts_RepeatPasswordPlaceholder || I18n.t('Repeat_Password')}
|
||||
label={Accounts_RepeatPasswordPlaceholder || I18n.t('Repeat_Password')}
|
||||
placeholder={Accounts_RepeatPasswordPlaceholder || I18n.t('Repeat_Password')}
|
||||
returnKeyType='done'
|
||||
iconLeft='key-variant'
|
||||
secureTextEntry
|
||||
onChangeText={confirmPassword => this.setState({ confirmPassword })}
|
||||
onChangeText={value => this.setState({ confirmPassword: value })}
|
||||
onSubmitEditing={this.submit}
|
||||
testID='register-view-repeat-password'
|
||||
/>
|
||||
|
@ -180,15 +194,17 @@ export default class RegisterView extends LoggedView {
|
|||
}
|
||||
|
||||
_renderUsername() {
|
||||
if (!this.props.login.token) {
|
||||
const { login, Accounts_UsernamePlaceholder } = this.props;
|
||||
|
||||
if (!login.token) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
inputRef={(e) => { this.username = e; }}
|
||||
label={this.props.Accounts_UsernamePlaceholder || I18n.t('Username')}
|
||||
placeholder={this.props.Accounts_UsernamePlaceholder || I18n.t('Username')}
|
||||
label={Accounts_UsernamePlaceholder || I18n.t('Username')}
|
||||
placeholder={Accounts_UsernamePlaceholder || I18n.t('Username')}
|
||||
returnKeyType='done'
|
||||
iconLeft='at'
|
||||
onChangeText={username => this.setState({ username })}
|
||||
|
@ -209,6 +225,7 @@ export default class RegisterView extends LoggedView {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { login } = this.props;
|
||||
return (
|
||||
<KeyboardView contentContainerStyle={styles.container}>
|
||||
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
|
||||
|
@ -216,13 +233,15 @@ export default class RegisterView extends LoggedView {
|
|||
<Text style={[styles.loginText, styles.loginTitle]}>{I18n.t('Sign_Up')}</Text>
|
||||
{this._renderRegister()}
|
||||
{this._renderUsername()}
|
||||
{this.props.login.failure ?
|
||||
{login.failure
|
||||
? (
|
||||
<Text style={styles.error} testID='register-view-error'>
|
||||
{this.props.login.error.reason}
|
||||
{login.error.reason}
|
||||
</Text>
|
||||
)
|
||||
: null
|
||||
}
|
||||
<Loading visible={this.props.login.isFetching} />
|
||||
<Loading visible={login.isFetching} />
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
</KeyboardView>
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, SectionList, Text, Alert, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
View, SectionList, Text, Alert, SafeAreaView
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { leaveRoom as leaveRoomAction } from '../../actions/room';
|
||||
import LoggedView from '../View';
|
||||
import styles from './styles';
|
||||
import sharedStyles from '../Styles';
|
||||
|
@ -13,7 +16,6 @@ import Status from '../../containers/status';
|
|||
import Touch from '../../utils/touch';
|
||||
import database from '../../lib/realm';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import { leaveRoom } from '../../actions/room';
|
||||
import log from '../../utils/log';
|
||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||
import I18n from '../../i18n';
|
||||
|
@ -26,7 +28,7 @@ const renderSeparator = () => <View style={styles.separator} />;
|
|||
username: state.login.user && state.login.user.username,
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||
}), dispatch => ({
|
||||
leaveRoom: rid => dispatch(leaveRoom(rid))
|
||||
leaveRoom: rid => dispatch(leaveRoomAction(rid))
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class RoomActionsView extends LoggedView {
|
||||
|
@ -63,8 +65,10 @@ export default class RoomActionsView extends LoggedView {
|
|||
}
|
||||
|
||||
onPressTouchable = (item) => {
|
||||
const { navigator } = this.props;
|
||||
|
||||
if (item.route) {
|
||||
this.props.navigator.push({
|
||||
navigator.push({
|
||||
screen: item.route,
|
||||
title: item.name,
|
||||
passProps: item.params,
|
||||
|
@ -81,8 +85,10 @@ export default class RoomActionsView extends LoggedView {
|
|||
rid, t
|
||||
} = this.room;
|
||||
const { allMembers } = this.state;
|
||||
const { username } = this.props;
|
||||
|
||||
// TODO: same test joined
|
||||
const userInRoom = !!allMembers.find(m => m.username === this.props.username);
|
||||
const userInRoom = !!allMembers.find(m => m.username === username);
|
||||
const permissions = RocketChat.hasPermission(['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room'], rid);
|
||||
|
||||
if (userInRoom && permissions['add-user-to-joined-room']) {
|
||||
|
@ -96,8 +102,10 @@ export default class RoomActionsView extends LoggedView {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get canViewMembers() {
|
||||
const { rid, t, broadcast } = this.state.room;
|
||||
const { room } = this.state;
|
||||
const { rid, t, broadcast } = room;
|
||||
if (broadcast) {
|
||||
const viewBroadcastMemberListPermission = 'view-broadcast-member-list';
|
||||
const permissions = RocketChat.hasPermission([viewBroadcastMemberListPermission], rid);
|
||||
|
@ -107,6 +115,7 @@ export default class RoomActionsView extends LoggedView {
|
|||
}
|
||||
return (t === 'c' || t === 'p');
|
||||
}
|
||||
|
||||
get sections() {
|
||||
const {
|
||||
rid, t, blocker, notifications
|
||||
|
@ -218,9 +227,9 @@ export default class RoomActionsView extends LoggedView {
|
|||
actions.push({
|
||||
icon: 'ios-people',
|
||||
name: I18n.t('Members'),
|
||||
description: (onlineMembers.length === 1 ?
|
||||
I18n.t('1_online_member') :
|
||||
I18n.t('N_online_members', { n: onlineMembers.length })),
|
||||
description: (onlineMembers.length === 1
|
||||
? I18n.t('1_online_member')
|
||||
: I18n.t('N_online_members', { n: onlineMembers.length })),
|
||||
route: 'RoomMembersView',
|
||||
params: { rid, members: onlineMembers },
|
||||
testID: 'room-actions-members'
|
||||
|
@ -257,7 +266,8 @@ export default class RoomActionsView extends LoggedView {
|
|||
}
|
||||
|
||||
updateRoomMembers = async() => {
|
||||
const { t } = this.state.room;
|
||||
const { room } = this.state;
|
||||
const { rid, t } = room;
|
||||
|
||||
if (!this.canViewMembers) {
|
||||
return {};
|
||||
|
@ -267,8 +277,8 @@ export default class RoomActionsView extends LoggedView {
|
|||
let onlineMembers = [];
|
||||
let allMembers = [];
|
||||
try {
|
||||
const onlineMembersCall = RocketChat.getRoomMembers(this.state.room.rid, false);
|
||||
const allMembersCall = RocketChat.getRoomMembers(this.state.room.rid, true);
|
||||
const onlineMembersCall = RocketChat.getRoomMembers(rid, false);
|
||||
const allMembersCall = RocketChat.getRoomMembers(rid, true);
|
||||
const [onlineMembersResult, allMembersResult] = await Promise.all([onlineMembersCall, allMembersCall]);
|
||||
onlineMembers = onlineMembersResult.records;
|
||||
allMembers = allMembersResult.records;
|
||||
|
@ -280,11 +290,15 @@ export default class RoomActionsView extends LoggedView {
|
|||
}
|
||||
|
||||
updateRoomMember = async() => {
|
||||
if (this.state.room.t !== 'd') {
|
||||
const { room } = this.state;
|
||||
const { rid, t } = room;
|
||||
const { userId } = this.props;
|
||||
|
||||
if (t !== 'd') {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
const member = await RocketChat.getRoomMember(this.state.room.rid, this.props.userId);
|
||||
const member = await RocketChat.getRoomMember(rid, userId);
|
||||
return { member };
|
||||
} catch (e) {
|
||||
log('RoomActions updateRoomMember', e);
|
||||
|
@ -297,7 +311,8 @@ export default class RoomActionsView extends LoggedView {
|
|||
}
|
||||
|
||||
toggleBlockUser = async() => {
|
||||
const { rid, blocker } = this.state.room;
|
||||
const { room } = this.state;
|
||||
const { rid, blocker } = room;
|
||||
const { member } = this.state;
|
||||
try {
|
||||
RocketChat.toggleBlockUser(rid, member._id, !blocker);
|
||||
|
@ -308,6 +323,8 @@ export default class RoomActionsView extends LoggedView {
|
|||
|
||||
leaveChannel = () => {
|
||||
const { room } = this.state;
|
||||
const { leaveRoom } = this.props;
|
||||
|
||||
Alert.alert(
|
||||
I18n.t('Are_you_sure_question_mark'),
|
||||
I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: room.t === 'd' ? room.fname : room.name }),
|
||||
|
@ -319,7 +336,7 @@ export default class RoomActionsView extends LoggedView {
|
|||
{
|
||||
text: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
|
||||
style: 'destructive',
|
||||
onPress: () => this.props.leaveRoom(room.rid)
|
||||
onPress: () => leaveRoom(room.rid)
|
||||
}
|
||||
]
|
||||
);
|
||||
|
@ -337,6 +354,8 @@ export default class RoomActionsView extends LoggedView {
|
|||
renderRoomInfo = ({ item }) => {
|
||||
const { room, member } = this.state;
|
||||
const { name, t, topic } = room;
|
||||
const { baseUrl } = this.props;
|
||||
|
||||
return (
|
||||
this.renderTouchableItem([
|
||||
<Avatar
|
||||
|
@ -345,17 +364,19 @@ export default class RoomActionsView extends LoggedView {
|
|||
size={50}
|
||||
style={styles.avatar}
|
||||
type={t}
|
||||
baseUrl={this.props.baseUrl}
|
||||
baseUrl={baseUrl}
|
||||
>
|
||||
{t === 'd' ? <Status style={sharedStyles.status} id={member._id} /> : null }
|
||||
</Avatar>,
|
||||
<View key='name' style={styles.roomTitleContainer}>
|
||||
{room.t === 'd' ?
|
||||
<Text style={styles.roomTitle}>{room.fname}</Text> :
|
||||
{room.t === 'd'
|
||||
? <Text style={styles.roomTitle}>{room.fname}</Text>
|
||||
: (
|
||||
<View style={styles.roomTitleRow}>
|
||||
<RoomTypeIcon type={room.t} />
|
||||
<Text style={styles.roomTitle}>{room.name}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
<Text style={styles.roomDescription} ellipsizeMode='tail' numberOfLines={1}>{t === 'd' ? `@${ name }` : topic}</Text>
|
||||
</View>,
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
FlatList, View, Text, SafeAreaView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { openRoomFiles as openRoomFilesAction, closeRoomFiles as closeRoomFilesAction } from '../../actions/roomFiles';
|
||||
import LoggedView from '../View';
|
||||
import { openRoomFiles, closeRoomFiles } from '../../actions/roomFiles';
|
||||
import styles from './styles';
|
||||
import Message from '../../containers/message';
|
||||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||
|
@ -19,8 +21,8 @@ import I18n from '../../i18n';
|
|||
token: state.login.user && state.login.user.token
|
||||
}
|
||||
}), dispatch => ({
|
||||
openRoomFiles: (rid, limit) => dispatch(openRoomFiles(rid, limit)),
|
||||
closeRoomFiles: () => dispatch(closeRoomFiles())
|
||||
openRoomFiles: (rid, limit) => dispatch(openRoomFilesAction(rid, limit)),
|
||||
closeRoomFiles: () => dispatch(closeRoomFilesAction())
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class RoomFilesView extends LoggedView {
|
||||
|
@ -47,17 +49,20 @@ export default class RoomFilesView extends LoggedView {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.ready && nextProps.ready !== this.props.ready) {
|
||||
const { ready } = this.props;
|
||||
if (nextProps.ready && nextProps.ready !== ready) {
|
||||
this.setState({ loading: false, loadingMore: false });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.closeRoomFiles();
|
||||
const { closeRoomFiles } = this.props;
|
||||
closeRoomFiles();
|
||||
}
|
||||
|
||||
load = () => {
|
||||
this.props.openRoomFiles(this.props.rid, this.limit);
|
||||
const { openRoomFiles, rid } = this.props;
|
||||
openRoomFiles(rid, this.limit);
|
||||
}
|
||||
|
||||
moreData = () => {
|
||||
|
@ -79,15 +84,19 @@ export default class RoomFilesView extends LoggedView {
|
|||
</View>
|
||||
)
|
||||
|
||||
renderItem = ({ item }) => (
|
||||
renderItem = ({ item }) => {
|
||||
const { user } = this.props;
|
||||
|
||||
return (
|
||||
<Message
|
||||
item={item}
|
||||
style={styles.message}
|
||||
reactions={item.reactions}
|
||||
user={this.props.user}
|
||||
user={user}
|
||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { messages, ready } = this.props;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { View, Text, Switch } from 'react-native';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import styles from './styles';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import sharedStyles from '../Styles';
|
||||
|
||||
export default class SwitchContainer extends React.PureComponent {
|
||||
static propTypes = {
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, View, ScrollView, TouchableOpacity, SafeAreaView, Keyboard, Alert } from 'react-native';
|
||||
import {
|
||||
Text, View, ScrollView, TouchableOpacity, SafeAreaView, Keyboard, Alert
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { eraseRoom as eraseRoomAction } from '../../actions/room';
|
||||
import LoggedView from '../View';
|
||||
import KeyboardView from '../../presentation/KeyboardView';
|
||||
import sharedStyles from '../Styles';
|
||||
|
@ -11,7 +14,6 @@ import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
|||
import { showErrorAlert, showToast } from '../../utils/info';
|
||||
import database from '../../lib/realm';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import { eraseRoom } from '../../actions/room';
|
||||
import RCTextInput from '../../containers/TextInput';
|
||||
import Loading from '../../containers/Loading';
|
||||
import SwitchContainer from './SwitchContainer';
|
||||
|
@ -35,7 +37,7 @@ const PERMISSIONS_ARRAY = [
|
|||
];
|
||||
|
||||
@connect(null, dispatch => ({
|
||||
eraseRoom: rid => dispatch(eraseRoom(rid))
|
||||
eraseRoom: rid => dispatch(eraseRoomAction(rid))
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class RoomInfoEditView extends LoggedView {
|
||||
|
@ -66,10 +68,11 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
|
||||
|
||||
async componentDidMount() {
|
||||
const { room } = this.state;
|
||||
await this.updateRoom();
|
||||
this.init();
|
||||
this.rooms.addListener(this.updateRoom);
|
||||
this.permissions = RocketChat.hasPermission(PERMISSIONS_ARRAY, this.state.room.rid);
|
||||
this.permissions = RocketChat.hasPermission(PERMISSIONS_ARRAY, room.rid);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -82,9 +85,10 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
}
|
||||
|
||||
init = () => {
|
||||
const { room } = this.state;
|
||||
const {
|
||||
name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired
|
||||
} = this.state.room;
|
||||
} = room;
|
||||
// fake password just to user knows about it
|
||||
this.randomValue = random(15);
|
||||
this.setState({
|
||||
|
@ -114,14 +118,14 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
const {
|
||||
room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode
|
||||
} = this.state;
|
||||
return !(room.name === name &&
|
||||
room.description === description &&
|
||||
room.topic === topic &&
|
||||
room.announcement === announcement &&
|
||||
this.randomValue === joinCode &&
|
||||
room.t === 'p' === t &&
|
||||
room.ro === ro &&
|
||||
room.reactWhenReadOnly === reactWhenReadOnly
|
||||
return !(room.name === name
|
||||
&& room.description === description
|
||||
&& room.topic === topic
|
||||
&& room.announcement === announcement
|
||||
&& this.randomValue === joinCode
|
||||
&& room.t === 'p' === t
|
||||
&& room.ro === ro
|
||||
&& room.reactWhenReadOnly === reactWhenReadOnly
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -199,6 +203,9 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
}
|
||||
|
||||
delete = () => {
|
||||
const { room } = this.state;
|
||||
const { eraseRoom } = this.props;
|
||||
|
||||
Alert.alert(
|
||||
I18n.t('Are_you_sure_question_mark'),
|
||||
I18n.t('Delete_Room_Warning'),
|
||||
|
@ -210,7 +217,7 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
{
|
||||
text: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
|
||||
style: 'destructive',
|
||||
onPress: () => this.props.eraseRoom(this.state.room.rid)
|
||||
onPress: () => eraseRoom(room.rid)
|
||||
}
|
||||
],
|
||||
{ cancelable: false }
|
||||
|
@ -218,7 +225,9 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
}
|
||||
|
||||
toggleArchive = () => {
|
||||
const { archived } = this.state.room;
|
||||
const { room } = this.state;
|
||||
const { rid, archived } = room;
|
||||
|
||||
const action = I18n.t(`${ archived ? 'un' : '' }archive`);
|
||||
Alert.alert(
|
||||
I18n.t('Are_you_sure_question_mark'),
|
||||
|
@ -233,7 +242,7 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
style: 'destructive',
|
||||
onPress: () => {
|
||||
try {
|
||||
RocketChat.toggleArchiveRoom(this.state.room.rid, !archived);
|
||||
RocketChat.toggleArchiveRoom(rid, !archived);
|
||||
} catch (e) {
|
||||
log('toggleArchive', e);
|
||||
}
|
||||
|
@ -244,9 +253,12 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
);
|
||||
}
|
||||
|
||||
hasDeletePermission = () => (
|
||||
this.state.room.t === 'p' ? this.permissions[PERMISSION_DELETE_P] : this.permissions[PERMISSION_DELETE_C]
|
||||
hasDeletePermission = () => {
|
||||
const { room } = this.state;
|
||||
return (
|
||||
room.t === 'p' ? this.permissions[PERMISSION_DELETE_P] : this.permissions[PERMISSION_DELETE_C]
|
||||
);
|
||||
}
|
||||
|
||||
hasArchivePermission = () => (
|
||||
this.permissions[PERMISSION_ARCHIVE] || this.permissions[PERMISSION_UNARCHIVE]
|
||||
|
@ -254,7 +266,7 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
|
||||
render() {
|
||||
const {
|
||||
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode
|
||||
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving
|
||||
} = this.state;
|
||||
return (
|
||||
<KeyboardView
|
||||
|
@ -328,7 +340,8 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
disabled={!this.permissions[PERMISSION_SET_READONLY] || room.broadcast}
|
||||
testID='room-info-edit-view-ro'
|
||||
/>
|
||||
{ro && !room.broadcast ?
|
||||
{ro && !room.broadcast
|
||||
? (
|
||||
<SwitchContainer
|
||||
value={reactWhenReadOnly}
|
||||
leftLabelPrimary={I18n.t('No_Reactions')}
|
||||
|
@ -339,10 +352,11 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
disabled={!this.permissions[PERMISSION_SET_REACT_WHEN_READONLY]}
|
||||
testID='room-info-edit-view-react-when-ro'
|
||||
/>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{room.broadcast ?
|
||||
[
|
||||
{room.broadcast
|
||||
? [
|
||||
<Text style={styles.broadcast}>{I18n.t('Broadcast_Channel')}</Text>,
|
||||
<View style={styles.divider} />
|
||||
]
|
||||
|
@ -394,7 +408,7 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
>
|
||||
<Text style={[sharedStyles.button_inverted, styles.colorDanger]} accessibilityTraits='button'>{I18n.t('DELETE')}</Text>
|
||||
</TouchableOpacity>
|
||||
<Loading visible={this.state.saving} />
|
||||
<Loading visible={saving} />
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
</KeyboardView>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text, ScrollView, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
View, Text, ScrollView, SafeAreaView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import moment from 'moment';
|
||||
|
||||
|
@ -20,14 +22,14 @@ import { iconsMap } from '../../Icons';
|
|||
const PERMISSION_EDIT_ROOM = 'edit-room';
|
||||
|
||||
const camelize = str => str.replace(/^(.)/, (match, chr) => chr.toUpperCase());
|
||||
const getRoomTitle = room => (room.t === 'd' ?
|
||||
<Text testID='room-info-view-name' style={styles.roomTitle}>{room.fname}</Text> :
|
||||
[
|
||||
const getRoomTitle = room => (room.t === 'd'
|
||||
? <Text testID='room-info-view-name' style={styles.roomTitle}>{room.fname}</Text>
|
||||
: (
|
||||
<View style={styles.roomTitleRow}>
|
||||
<RoomTypeIcon type={room.t} key='room-info-type' />
|
||||
<Text testID='room-info-view-name' style={styles.roomTitle} key='room-info-name'>{room.name}</Text>
|
||||
</View>
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
@connect(state => ({
|
||||
|
@ -35,7 +37,7 @@ const getRoomTitle = room => (room.t === 'd' ?
|
|||
userId: state.login.user && state.login.user.id,
|
||||
activeUsers: state.activeUsers,
|
||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||
roles: state.roles
|
||||
allRoles: state.roles
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class RoomInfoView extends LoggedView {
|
||||
|
@ -46,7 +48,7 @@ export default class RoomInfoView extends LoggedView {
|
|||
baseUrl: PropTypes.string,
|
||||
activeUsers: PropTypes.object,
|
||||
Message_TimeFormat: PropTypes.string,
|
||||
roles: PropTypes.object
|
||||
allRoles: PropTypes.object
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -64,19 +66,61 @@ export default class RoomInfoView extends LoggedView {
|
|||
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
await this.updateRoom();
|
||||
componentDidMount() {
|
||||
this.updateRoom();
|
||||
this.rooms.addListener(this.updateRoom);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.rooms.removeAllListeners();
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
|
||||
onNavigatorEvent(event) {
|
||||
const { rid, navigator } = this.props;
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'edit') {
|
||||
navigator.push({
|
||||
screen: 'RoomInfoEditView',
|
||||
title: I18n.t('Room_Info_Edit'),
|
||||
backButtonTitle: '',
|
||||
passProps: {
|
||||
rid
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getFullUserData = async(username) => {
|
||||
try {
|
||||
const result = await RocketChat.subscribe('fullUserData', username);
|
||||
this.sub = result;
|
||||
} catch (e) {
|
||||
log('getFullUserData', e);
|
||||
}
|
||||
}
|
||||
|
||||
isDirect = () => {
|
||||
const { room: { t } } = this.state;
|
||||
return t === 'd';
|
||||
}
|
||||
|
||||
updateRoom = async() => {
|
||||
const { userId, activeUsers, navigator } = this.props;
|
||||
|
||||
const [room] = this.rooms;
|
||||
this.setState({ room });
|
||||
|
||||
// get user of room
|
||||
if (this.state.room) {
|
||||
if (this.state.room.t === 'd') {
|
||||
if (room) {
|
||||
if (room.t === 'd') {
|
||||
try {
|
||||
const roomUser = await RocketChat.getRoomMember(this.state.room.rid, this.props.userId);
|
||||
const roomUser = await RocketChat.getRoomMember(room.rid, userId);
|
||||
this.setState({ roomUser });
|
||||
const username = this.state.room.name;
|
||||
const username = room.name;
|
||||
|
||||
const activeUser = this.props.activeUsers[roomUser._id];
|
||||
const activeUser = activeUsers[roomUser._id];
|
||||
if (!activeUser || !activeUser.utcOffset) {
|
||||
// get full user data looking for utcOffset
|
||||
// will be catched by .on('users) and saved on activeUsers reducer
|
||||
|
@ -94,9 +138,14 @@ export default class RoomInfoView extends LoggedView {
|
|||
log('RoomInfoView.componentDidMount', e);
|
||||
}
|
||||
} else {
|
||||
const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], this.state.room.rid);
|
||||
const isVisible = await navigator.screenIsCurrentlyVisible();
|
||||
|
||||
if (!isVisible) {
|
||||
return;
|
||||
}
|
||||
const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
|
||||
if (permissions[PERMISSION_EDIT_ROOM]) {
|
||||
this.props.navigator.setButtons({
|
||||
navigator.setButtons({
|
||||
rightButtons: [{
|
||||
id: 'edit',
|
||||
icon: iconsMap.create,
|
||||
|
@ -108,42 +157,6 @@ export default class RoomInfoView extends LoggedView {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.rooms.removeAllListeners();
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
|
||||
onNavigatorEvent(event) {
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'edit') {
|
||||
this.props.navigator.push({
|
||||
screen: 'RoomInfoEditView',
|
||||
title: I18n.t('Room_Info_Edit'),
|
||||
backButtonTitle: '',
|
||||
passProps: {
|
||||
rid: this.props.rid
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getFullUserData = async(username) => {
|
||||
try {
|
||||
const result = await RocketChat.subscribe('fullUserData', username);
|
||||
this.sub = result;
|
||||
} catch (e) {
|
||||
log('getFullUserData', e);
|
||||
}
|
||||
}
|
||||
|
||||
isDirect = () => this.state.room.t === 'd';
|
||||
|
||||
updateRoom = async() => {
|
||||
const [room] = this.rooms;
|
||||
this.setState({ room });
|
||||
}
|
||||
|
||||
renderItem = (key, room) => (
|
||||
<View style={styles.item}>
|
||||
<Text style={styles.itemLabel}>{I18n.t(camelize(key))}</Text>
|
||||
|
@ -155,24 +168,33 @@ export default class RoomInfoView extends LoggedView {
|
|||
</View>
|
||||
);
|
||||
|
||||
renderRoles = () => (
|
||||
this.state.roles.length > 0 ?
|
||||
renderRoles = () => {
|
||||
const { roles } = this.state;
|
||||
const { allRoles } = this.props;
|
||||
|
||||
return (
|
||||
roles.length > 0
|
||||
? (
|
||||
<View style={styles.item}>
|
||||
<Text style={styles.itemLabel}>{I18n.t('Roles')}</Text>
|
||||
<View style={styles.rolesContainer}>
|
||||
{this.state.roles.map(role => (
|
||||
{roles.map(role => (
|
||||
<View style={styles.roleBadge} key={role}>
|
||||
<Text>{ this.props.roles[role] }</Text>
|
||||
<Text>{ allRoles[role] }</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
: null
|
||||
)
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
renderTimezone = (userId) => {
|
||||
if (this.props.activeUsers[userId]) {
|
||||
const { utcOffset } = this.props.activeUsers[userId];
|
||||
const { activeUsers, Message_TimeFormat } = this.props;
|
||||
|
||||
if (activeUsers[userId]) {
|
||||
const { utcOffset } = activeUsers[userId];
|
||||
|
||||
if (!utcOffset) {
|
||||
return null;
|
||||
|
@ -180,24 +202,28 @@ export default class RoomInfoView extends LoggedView {
|
|||
return (
|
||||
<View style={styles.item}>
|
||||
<Text style={styles.itemLabel}>{I18n.t('Timezone')}</Text>
|
||||
<Text style={styles.itemContent}>{moment().utcOffset(utcOffset).format(this.props.Message_TimeFormat)} (UTC { utcOffset })</Text>
|
||||
<Text style={styles.itemContent}>{moment().utcOffset(utcOffset).format(Message_TimeFormat)} (UTC { utcOffset })</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderAvatar = (room, roomUser) => (
|
||||
renderAvatar = (room, roomUser) => {
|
||||
const { baseUrl } = this.props;
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
text={room.name}
|
||||
size={100}
|
||||
style={styles.avatar}
|
||||
type={room.t}
|
||||
baseUrl={this.props.baseUrl}
|
||||
baseUrl={baseUrl}
|
||||
>
|
||||
{room.t === 'd' ? <Status style={[sharedStyles.status, styles.status]} id={roomUser._id} /> : null}
|
||||
</Avatar>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderBroadcast = () => (
|
||||
<View style={styles.item}>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Vibration, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
FlatList, View, Vibration, SafeAreaView
|
||||
} from 'react-native';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
@ -37,6 +39,8 @@ export default class RoomMembersView extends LoggedView {
|
|||
|
||||
constructor(props) {
|
||||
super('MentionedMessagesView', props);
|
||||
const { navigator } = this.props;
|
||||
|
||||
this.CANCEL_INDEX = 0;
|
||||
this.MUTE_INDEX = 1;
|
||||
this.actionSheetOptions = [''];
|
||||
|
@ -52,7 +56,7 @@ export default class RoomMembersView extends LoggedView {
|
|||
userLongPressed: {},
|
||||
room: {}
|
||||
};
|
||||
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
|
||||
navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -64,16 +68,19 @@ export default class RoomMembersView extends LoggedView {
|
|||
}
|
||||
|
||||
async onNavigatorEvent(event) {
|
||||
const { rid, allUsers } = this.state;
|
||||
const { navigator } = this.props;
|
||||
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'toggleOnline') {
|
||||
try {
|
||||
const allUsers = !this.state.allUsers;
|
||||
const membersResult = await RocketChat.getRoomMembers(this.state.rid, allUsers);
|
||||
const allUsersFilter = !allUsers;
|
||||
const membersResult = await RocketChat.getRoomMembers(rid, allUsersFilter);
|
||||
const members = membersResult.records;
|
||||
this.setState({ allUsers, members });
|
||||
this.props.navigator.setButtons({
|
||||
this.setState({ allUsers: allUsersFilter, members });
|
||||
navigator.setButtons({
|
||||
rightButtons: [{
|
||||
title: this.state.allUsers ? I18n.t('Online') : I18n.t('All'),
|
||||
title: allUsers ? I18n.t('Online') : I18n.t('All'),
|
||||
id: 'toggleOnline',
|
||||
testID: 'room-members-view-toggle-status'
|
||||
}]
|
||||
|
@ -86,9 +93,11 @@ export default class RoomMembersView extends LoggedView {
|
|||
}
|
||||
|
||||
onSearchChangeText = (text) => {
|
||||
const { members } = this.state;
|
||||
|
||||
let membersFiltered = [];
|
||||
if (text) {
|
||||
membersFiltered = this.state.members.filter(m => m.username.toLowerCase().match(text.toLowerCase()));
|
||||
membersFiltered = members.filter(m => m.username.toLowerCase().match(text.toLowerCase()));
|
||||
}
|
||||
this.setState({ filtering: !!text, membersFiltered });
|
||||
}
|
||||
|
@ -111,8 +120,10 @@ export default class RoomMembersView extends LoggedView {
|
|||
if (!this.permissions['mute-user']) {
|
||||
return;
|
||||
}
|
||||
const { room } = this.state;
|
||||
const { muted } = room;
|
||||
|
||||
this.actionSheetOptions = [I18n.t('Cancel')];
|
||||
const { muted } = this.state.room;
|
||||
const userIsMuted = !!muted.find(m => m.value === user.username);
|
||||
user.muted = userIsMuted;
|
||||
if (userIsMuted) {
|
||||
|
@ -133,9 +144,10 @@ export default class RoomMembersView extends LoggedView {
|
|||
}
|
||||
|
||||
goRoom = ({ rid, name }) => {
|
||||
this.props.navigator.popToRoot();
|
||||
const { navigator } = this.props;
|
||||
navigator.popToRoot();
|
||||
setTimeout(() => {
|
||||
this.props.navigator.push({
|
||||
navigator.push({
|
||||
screen: 'RoomView',
|
||||
title: name,
|
||||
backButtonTitle: '',
|
||||
|
@ -170,16 +182,20 @@ export default class RoomMembersView extends LoggedView {
|
|||
|
||||
renderSeparator = () => <View style={styles.separator} />;
|
||||
|
||||
renderItem = ({ item }) => (
|
||||
renderItem = ({ item }) => {
|
||||
const { baseUrl } = this.props;
|
||||
|
||||
return (
|
||||
<UserItem
|
||||
name={item.name}
|
||||
username={item.username}
|
||||
onPress={() => this.onPressUser(item)}
|
||||
onLongPress={() => this.onLongPressUser(item)}
|
||||
baseUrl={this.props.baseUrl}
|
||||
baseUrl={baseUrl}
|
||||
testID={`room-members-view-item-${ item.username }`}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { filtering, members, membersFiltered } = this.state;
|
||||
|
|
|
@ -35,6 +35,7 @@ export class List extends React.Component {
|
|||
room: PropTypes.string,
|
||||
end: PropTypes.bool
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.data = database
|
||||
|
@ -43,16 +44,22 @@ export class List extends React.Component {
|
|||
.sorted('ts', true);
|
||||
this.dataSource = ds.cloneWithRows(this.data);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.data.addListener(this.updateState);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return this.props.end !== nextProps.end;
|
||||
const { end } = this.props;
|
||||
return end !== nextProps.end;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.data.removeAllListeners();
|
||||
this.updateState.stop();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
updateState = throttle(() => {
|
||||
// this.setState({
|
||||
this.dataSource = this.dataSource.cloneWithRows(this.data);
|
||||
|
@ -62,6 +69,8 @@ export class List extends React.Component {
|
|||
}, 1000);
|
||||
|
||||
render() {
|
||||
const { renderFooter, onEndReached, renderRow } = this.props;
|
||||
|
||||
return (
|
||||
<ListView
|
||||
enableEmptySections
|
||||
|
@ -69,11 +78,11 @@ export class List extends React.Component {
|
|||
data={this.data}
|
||||
keyExtractor={item => item._id}
|
||||
onEndReachedThreshold={100}
|
||||
renderFooter={this.props.renderFooter}
|
||||
renderFooter={renderFooter}
|
||||
renderHeader={() => <Typing />}
|
||||
onEndReached={() => this.props.onEndReached(this.data[this.data.length - 1])}
|
||||
onEndReached={() => onEndReached(this.data[this.data.length - 1])}
|
||||
dataSource={this.dataSource}
|
||||
renderRow={(item, previousItem) => this.props.renderRow(item, previousItem)}
|
||||
renderRow={(item, previousItem) => renderRow(item, previousItem)}
|
||||
initialListSize={1}
|
||||
pageSize={20}
|
||||
testID='room-view-messages'
|
||||
|
@ -106,7 +115,9 @@ export class ListView extends OldList2 {
|
|||
setNativeProps(props) {
|
||||
this.refs.listView.setNativeProps(props);
|
||||
}
|
||||
|
||||
static DataSource = DataSource;
|
||||
|
||||
render() {
|
||||
const bodyComponents = [];
|
||||
|
||||
|
@ -132,9 +143,9 @@ export class ListView extends OldList2 {
|
|||
continue; // eslint-disable-line
|
||||
}
|
||||
|
||||
const showUnreadSeparator = this.props.lastOpen &&
|
||||
moment(message.ts).isAfter(this.props.lastOpen) &&
|
||||
moment(previousMessage.ts).isBefore(this.props.lastOpen);
|
||||
const showUnreadSeparator = this.props.lastOpen
|
||||
&& moment(message.ts).isAfter(this.props.lastOpen)
|
||||
&& moment(previousMessage.ts).isBefore(this.props.lastOpen);
|
||||
const showDateSeparator = !moment(message.ts).isSame(previousMessage.ts, 'day');
|
||||
|
||||
if (showUnreadSeparator || showDateSeparator) {
|
||||
|
|
|
@ -4,8 +4,9 @@ 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 { toggleReactionPicker as toggleReactionPickerAction } from '../../actions/messages';
|
||||
import styles from './styles';
|
||||
|
||||
const margin = Platform.OS === 'android' ? 40 : 20;
|
||||
|
@ -15,7 +16,7 @@ const tabEmojiStyle = { fontSize: 15 };
|
|||
showReactionPicker: state.messages.showReactionPicker,
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||
}), dispatch => ({
|
||||
toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
|
||||
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message))
|
||||
}))
|
||||
@responsive
|
||||
export default class ReactionPicker extends React.Component {
|
||||
|
@ -28,25 +29,30 @@ export default class ReactionPicker extends React.Component {
|
|||
};
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.showReactionPicker !== this.props.showReactionPicker || this.props.window.width !== nextProps.window.width;
|
||||
const { showReactionPicker, window } = this.props;
|
||||
return nextProps.showReactionPicker !== showReactionPicker || 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);
|
||||
const { onEmojiSelected } = this.props;
|
||||
onEmojiSelected(shortname || emoji);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { window: { width, height }, showReactionPicker, baseUrl } = this.props;
|
||||
const {
|
||||
window: { width, height }, showReactionPicker, baseUrl, toggleReactionPicker
|
||||
} = this.props;
|
||||
|
||||
return (showReactionPicker ?
|
||||
return (showReactionPicker
|
||||
? (
|
||||
<Modal
|
||||
isVisible={this.props.showReactionPicker}
|
||||
isVisible={showReactionPicker}
|
||||
style={{ alignItems: 'center' }}
|
||||
onBackdropPress={() => this.props.toggleReactionPicker()}
|
||||
onBackButtonPress={() => this.props.toggleReactionPicker()}
|
||||
onBackdropPress={() => toggleReactionPicker()}
|
||||
onBackButtonPress={() => toggleReactionPicker()}
|
||||
animationIn='fadeIn'
|
||||
animationOut='fadeOut'
|
||||
>
|
||||
|
@ -61,7 +67,9 @@ export default class ReactionPicker extends React.Component {
|
|||
baseUrl={baseUrl}
|
||||
/>
|
||||
</View>
|
||||
</Modal> : null
|
||||
</Modal>
|
||||
)
|
||||
: null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React, { Component } from 'react';
|
||||
import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from 'react-native';
|
||||
import {
|
||||
View, Text, StyleSheet, TouchableOpacity, ScrollView
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { responsive } from 'react-native-responsive-ui';
|
||||
|
@ -64,7 +66,8 @@ export default class UploadProgress extends Component {
|
|||
this.state = {
|
||||
uploads: []
|
||||
};
|
||||
this.uploads = database.objects('uploads').filtered('rid = $0', this.props.rid);
|
||||
const { rid } = this.props;
|
||||
this.uploads = database.objects('uploads').filtered('rid = $0', rid);
|
||||
this.uploads.addListener(this.updateUploads);
|
||||
}
|
||||
|
||||
|
@ -98,11 +101,13 @@ export default class UploadProgress extends Component {
|
|||
}
|
||||
|
||||
tryAgain = async(item) => {
|
||||
const { rid } = this.props;
|
||||
|
||||
try {
|
||||
database.write(() => {
|
||||
item.error = false;
|
||||
});
|
||||
await RocketChat.sendFileMessage(this.props.rid, JSON.parse(JSON.stringify(item)));
|
||||
await RocketChat.sendFileMessage(rid, JSON.parse(JSON.stringify(item)));
|
||||
} catch (e) {
|
||||
log('UploadProgess.tryAgain', e);
|
||||
}
|
||||
|
@ -113,6 +118,8 @@ export default class UploadProgress extends Component {
|
|||
}
|
||||
|
||||
renderItemContent = (item) => {
|
||||
const { window } = this.props;
|
||||
|
||||
if (!item.error) {
|
||||
return (
|
||||
[
|
||||
|
@ -123,7 +130,7 @@ export default class UploadProgress extends Component {
|
|||
</Text>
|
||||
<Icon name='close' size={20} color='#9EA2A8' onPress={() => this.cancelUpload(item)} />
|
||||
</View>,
|
||||
<View key='progress' style={[styles.progress, { width: (this.props.window.width * item.progress) / 100 }]} />
|
||||
<View key='progress' style={[styles.progress, { width: (window.width * item.progress) / 100 }]} />
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, View, LayoutAnimation, ActivityIndicator, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
Text, View, LayoutAnimation, ActivityIndicator, SafeAreaView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import equal from 'deep-equal';
|
||||
import { RectButton } from 'react-native-gesture-handler';
|
||||
|
||||
import { openRoom as openRoomAction, closeRoom as closeRoomAction, setLastOpen as setLastOpenAction } from '../../actions/room';
|
||||
import { toggleReactionPicker as toggleReactionPickerAction, actionsShow as actionsShowAction } from '../../actions/messages';
|
||||
import LoggedView from '../View';
|
||||
import { List } from './ListView';
|
||||
import { openRoom, closeRoom, setLastOpen } from '../../actions/room';
|
||||
import { toggleReactionPicker, actionsShow } from '../../actions/messages';
|
||||
import database from '../../lib/realm';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import Message from '../../containers/message';
|
||||
|
@ -33,11 +35,11 @@ import { iconsMap } from '../../Icons';
|
|||
showActions: state.messages.showActions,
|
||||
showErrorActions: state.messages.showErrorActions
|
||||
}), dispatch => ({
|
||||
openRoom: room => dispatch(openRoom(room)),
|
||||
setLastOpen: date => dispatch(setLastOpen(date)),
|
||||
toggleReactionPicker: message => dispatch(toggleReactionPicker(message)),
|
||||
actionsShow: actionMessage => dispatch(actionsShow(actionMessage)),
|
||||
close: () => dispatch(closeRoom())
|
||||
openRoom: room => dispatch(openRoomAction(room)),
|
||||
setLastOpen: date => dispatch(setLastOpenAction(date)),
|
||||
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message)),
|
||||
actionsShow: actionMessage => dispatch(actionsShowAction(actionMessage)),
|
||||
closeRoom: () => dispatch(closeRoomAction())
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class RoomView extends LoggedView {
|
||||
|
@ -56,12 +58,12 @@ export default class RoomView extends LoggedView {
|
|||
actionMessage: PropTypes.object,
|
||||
toggleReactionPicker: PropTypes.func.isRequired,
|
||||
actionsShow: PropTypes.func,
|
||||
close: PropTypes.func
|
||||
closeRoom: PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super('RoomView', props);
|
||||
this.rid = this.props.rid;
|
||||
this.rid = props.rid;
|
||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
||||
this.state = {
|
||||
loaded: false,
|
||||
|
@ -74,7 +76,9 @@ export default class RoomView extends LoggedView {
|
|||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.navigator.setButtons({
|
||||
const { navigator } = this.props;
|
||||
|
||||
navigator.setButtons({
|
||||
rightButtons: [{
|
||||
id: 'more',
|
||||
testID: 'room-view-header-actions',
|
||||
|
@ -88,9 +92,11 @@ export default class RoomView extends LoggedView {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { navigator } = this.props;
|
||||
|
||||
this.updateRoom();
|
||||
this.rooms.addListener(this.updateRoom);
|
||||
this.props.navigator.setDrawerEnabled({
|
||||
navigator.setDrawerEnabled({
|
||||
side: 'left',
|
||||
enabled: false
|
||||
});
|
||||
|
@ -98,12 +104,16 @@ export default class RoomView extends LoggedView {
|
|||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !(equal(this.props, nextProps) && equal(this.state, nextState) && this.state.room.ro === nextState.room.ro);
|
||||
const { room } = this.state;
|
||||
return !(equal(this.props, nextProps) && equal(this.state, nextState) && room.ro === nextState.room.ro);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.room.f !== this.state.room.f) {
|
||||
this.props.navigator.setButtons({
|
||||
const { room } = this.state;
|
||||
const { navigator } = this.props;
|
||||
|
||||
if (prevState.room.f !== room.f) {
|
||||
navigator.setButtons({
|
||||
rightButtons: [{
|
||||
id: 'more',
|
||||
testID: 'room-view-header-actions',
|
||||
|
@ -111,32 +121,37 @@ export default class RoomView extends LoggedView {
|
|||
}, {
|
||||
id: 'star',
|
||||
testID: 'room-view-header-star',
|
||||
icon: this.state.room.f ? iconsMap.star : iconsMap.starOutline
|
||||
icon: room.f ? iconsMap.star : iconsMap.starOutline
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { closeRoom } = this.props;
|
||||
this.rooms.removeAllListeners();
|
||||
this.onEndReached.stop();
|
||||
this.props.close();
|
||||
closeRoom();
|
||||
}
|
||||
|
||||
onNavigatorEvent(event) {
|
||||
const { room } = this.state;
|
||||
const { rid, f } = room;
|
||||
const { navigator } = this.props;
|
||||
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'more') {
|
||||
this.props.navigator.push({
|
||||
navigator.push({
|
||||
screen: 'RoomActionsView',
|
||||
title: I18n.t('Actions'),
|
||||
backButtonTitle: '',
|
||||
passProps: {
|
||||
rid: this.state.room.rid
|
||||
rid
|
||||
}
|
||||
});
|
||||
} else if (event.id === 'star') {
|
||||
try {
|
||||
RocketChat.toggleFavorite(this.state.room.rid, this.state.room.f);
|
||||
RocketChat.toggleFavorite(rid, f);
|
||||
} catch (e) {
|
||||
log('toggleFavorite', e);
|
||||
}
|
||||
|
@ -151,8 +166,9 @@ export default class RoomView extends LoggedView {
|
|||
}
|
||||
|
||||
requestAnimationFrame(async() => {
|
||||
const { room } = this.state;
|
||||
try {
|
||||
const result = await RocketChat.loadMessagesForRoom({ rid: this.rid, t: this.state.room.t, latest: lastRowData.ts });
|
||||
const result = await RocketChat.loadMessagesForRoom({ rid: this.rid, t: room.t, latest: lastRowData.ts });
|
||||
this.setState({ end: result < 20 });
|
||||
} catch (e) {
|
||||
log('RoomView.onEndReached', e);
|
||||
|
@ -161,14 +177,16 @@ export default class RoomView extends LoggedView {
|
|||
})
|
||||
|
||||
onMessageLongPress = (message) => {
|
||||
this.props.actionsShow(message);
|
||||
const { actionsShow } = this.props;
|
||||
actionsShow(message);
|
||||
}
|
||||
|
||||
onReactionPress = (shortname, messageId) => {
|
||||
const { actionMessage, toggleReactionPicker } = this.props;
|
||||
try {
|
||||
if (!messageId) {
|
||||
RocketChat.setReaction(shortname, this.props.actionMessage._id);
|
||||
return this.props.toggleReactionPicker();
|
||||
RocketChat.setReaction(shortname, actionMessage._id);
|
||||
return toggleReactionPicker();
|
||||
}
|
||||
RocketChat.setReaction(shortname, messageId);
|
||||
} catch (e) {
|
||||
|
@ -177,53 +195,71 @@ export default class RoomView extends LoggedView {
|
|||
};
|
||||
|
||||
updateRoom = async() => {
|
||||
const { navigator, openRoom, setLastOpen } = this.props;
|
||||
|
||||
if (this.rooms.length > 0) {
|
||||
const { room: prevRoom } = this.state;
|
||||
const room = JSON.parse(JSON.stringify(this.rooms[0]));
|
||||
this.setState({ room });
|
||||
|
||||
if (!prevRoom.rid) {
|
||||
this.props.navigator.setTitle({ title: room.name });
|
||||
this.props.openRoom({
|
||||
navigator.setTitle({ title: room.name });
|
||||
openRoom({
|
||||
...room
|
||||
});
|
||||
if (room.alert || room.unread || room.userMentions) {
|
||||
this.props.setLastOpen(room.ls);
|
||||
setLastOpen(room.ls);
|
||||
} else {
|
||||
this.props.setLastOpen(null);
|
||||
setLastOpen(null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.props.openRoom({ rid: this.rid });
|
||||
openRoom({ rid: this.rid });
|
||||
this.setState({ joined: false });
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage = (message) => {
|
||||
const { setLastOpen } = this.props;
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
RocketChat.sendMessage(this.rid, message).then(() => {
|
||||
this.props.setLastOpen(null);
|
||||
setLastOpen(null);
|
||||
});
|
||||
};
|
||||
|
||||
joinRoom = async() => {
|
||||
const { rid } = this.props;
|
||||
try {
|
||||
await RocketChat.joinRoom(this.props.rid);
|
||||
this.setState({ joined: true });
|
||||
await RocketChat.joinRoom(rid);
|
||||
this.setState({
|
||||
joined: true
|
||||
});
|
||||
} catch (e) {
|
||||
log('joinRoom', e);
|
||||
}
|
||||
};
|
||||
|
||||
isOwner = () => this.state.room && this.state.room.roles && Array.from(Object.keys(this.state.room.roles), i => this.state.room.roles[i].value).includes('owner');
|
||||
isOwner = () => {
|
||||
const { room } = this.state;
|
||||
return room && room.roles && Array.from(Object.keys(room.roles), i => room.roles[i].value).includes('owner');
|
||||
}
|
||||
|
||||
isMuted = () => this.state.room && this.state.room.muted && Array.from(Object.keys(this.state.room.muted), i => this.state.room.muted[i].value).includes(this.props.user.username);
|
||||
isMuted = () => {
|
||||
const { room } = this.state;
|
||||
const { user } = this.props;
|
||||
return room && room.muted && Array.from(Object.keys(room.muted), i => room.muted[i].value).includes(user.username);
|
||||
}
|
||||
|
||||
isReadOnly = () => this.state.room.ro && this.isMuted() && !this.isOwner();
|
||||
isReadOnly = () => {
|
||||
const { room } = this.state;
|
||||
return room.ro && this.isMuted() && !this.isOwner();
|
||||
}
|
||||
|
||||
isBlocked = () => {
|
||||
if (this.state.room) {
|
||||
const { t, blocked, blocker } = this.state.room;
|
||||
const { room } = this.state;
|
||||
|
||||
if (room) {
|
||||
const { t, blocked, blocker } = room;
|
||||
if (t === 'd' && (blocked || blocker)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -231,24 +267,31 @@ export default class RoomView extends LoggedView {
|
|||
return false;
|
||||
}
|
||||
|
||||
renderItem = (item, previousItem) => (
|
||||
renderItem = (item, previousItem) => {
|
||||
const { room } = this.state;
|
||||
const { user } = this.props;
|
||||
|
||||
return (
|
||||
<Message
|
||||
key={item._id}
|
||||
item={item}
|
||||
status={item.status}
|
||||
reactions={JSON.parse(JSON.stringify(item.reactions))}
|
||||
user={this.props.user}
|
||||
archived={this.state.room.archived}
|
||||
broadcast={this.state.room.broadcast}
|
||||
user={user}
|
||||
archived={room.archived}
|
||||
broadcast={room.broadcast}
|
||||
previousItem={previousItem}
|
||||
_updatedAt={item._updatedAt}
|
||||
onReactionPress={this.onReactionPress}
|
||||
onLongPress={this.onMessageLongPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderFooter = () => {
|
||||
if (!this.state.joined) {
|
||||
const { joined, room } = this.state;
|
||||
|
||||
if (!joined) {
|
||||
return (
|
||||
<View style={styles.joinRoomContainer} key='room-view-join'>
|
||||
<Text style={styles.previewMode}>{I18n.t('You_are_in_preview_mode')}</Text>
|
||||
|
@ -263,7 +306,7 @@ export default class RoomView extends LoggedView {
|
|||
</View>
|
||||
);
|
||||
}
|
||||
if (this.state.room.archived || this.isReadOnly()) {
|
||||
if (room.archived || this.isReadOnly()) {
|
||||
return (
|
||||
<View style={styles.readOnly}>
|
||||
<Text>{I18n.t('This_room_is_read_only')}</Text>
|
||||
|
@ -281,21 +324,23 @@ export default class RoomView extends LoggedView {
|
|||
};
|
||||
|
||||
renderHeader = () => {
|
||||
if (!this.state.end) {
|
||||
const { end } = this.state;
|
||||
if (!end) {
|
||||
return <ActivityIndicator style={[styles.loading, { transform: [{ scaleY: -1 }] }]} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderList = () => {
|
||||
if (!this.state.loaded) {
|
||||
const { loaded, end } = this.state;
|
||||
if (!loaded) {
|
||||
return <ActivityIndicator style={styles.loading} />;
|
||||
}
|
||||
return (
|
||||
[
|
||||
<List
|
||||
key='room-view-messages'
|
||||
end={this.state.end}
|
||||
end={end}
|
||||
room={this.rid}
|
||||
renderFooter={this.renderHeader}
|
||||
onEndReached={this.onEndReached}
|
||||
|
@ -307,13 +352,17 @@ export default class RoomView extends LoggedView {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { room } = this.state;
|
||||
const { user, showActions, showErrorActions } = this.props;
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} testID='room-view'>
|
||||
{this.renderList()}
|
||||
{this.state.room._id && this.props.showActions ?
|
||||
<MessageActions room={this.state.room} user={this.props.user} /> :
|
||||
null}
|
||||
{this.props.showErrorActions ? <MessageErrorActions /> : null}
|
||||
{room._id && showActions
|
||||
? <MessageActions room={room} user={user} />
|
||||
: null
|
||||
}
|
||||
{showErrorActions ? <MessageErrorActions /> : null}
|
||||
<ReactionPicker onEmojiSelected={this.onReactionPress} />
|
||||
<UploadProgress rid={this.rid} />
|
||||
</SafeAreaView>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Text, View, TouchableOpacity, Image, StyleSheet } from 'react-native';
|
||||
import {
|
||||
Text, View, TouchableOpacity, Image, StyleSheet
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Text, View, TouchableOpacity, Image, StyleSheet } from 'react-native';
|
||||
import {
|
||||
Text, View, TouchableOpacity, Image, StyleSheet
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import I18n from '../../../i18n';
|
||||
|
|
|
@ -3,12 +3,12 @@ import { View, TextInput } from 'react-native';
|
|||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { setSearch } from '../../../actions/rooms';
|
||||
import { setSearch as setSearchAction } from '../../../actions/rooms';
|
||||
import styles from './styles';
|
||||
import I18n from '../../../i18n';
|
||||
|
||||
@connect(null, dispatch => ({
|
||||
setSearch: searchText => dispatch(setSearch(searchText))
|
||||
setSearch: searchText => dispatch(setSearchAction(searchText))
|
||||
}))
|
||||
export default class RoomsListSearchView extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -20,7 +20,8 @@ export default class RoomsListSearchView extends React.Component {
|
|||
}
|
||||
|
||||
onSearchChangeText(text) {
|
||||
this.props.setSearch(text.trim());
|
||||
const { setSearch } = this.props;
|
||||
setSearch(text.trim());
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import React, { Component } from 'react';
|
||||
import { View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Image, AsyncStorage } from 'react-native';
|
||||
import {
|
||||
View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Image, AsyncStorage
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms';
|
||||
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
|
||||
import { appStart as appStartAction } from '../../actions';
|
||||
import styles from './styles';
|
||||
import { toggleServerDropdown } from '../../actions/rooms';
|
||||
import { selectServerRequest } from '../../actions/server';
|
||||
import { appStart } from '../../actions';
|
||||
import database from '../../lib/realm';
|
||||
import Touch from '../../utils/touch';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
|
@ -19,9 +21,9 @@ const ANIMATION_DURATION = 200;
|
|||
closeServerDropdown: state.rooms.closeServerDropdown,
|
||||
server: state.server.server
|
||||
}), dispatch => ({
|
||||
toggleServerDropdown: () => dispatch(toggleServerDropdown()),
|
||||
selectServerRequest: server => dispatch(selectServerRequest(server)),
|
||||
appStart: () => dispatch(appStart('outside'))
|
||||
toggleServerDropdown: () => dispatch(toggleServerDropdownAction()),
|
||||
selectServerRequest: server => dispatch(selectServerRequestAction(server)),
|
||||
appStart: () => dispatch(appStartAction('outside'))
|
||||
}))
|
||||
export default class ServerDropdown extends Component {
|
||||
static propTypes = {
|
||||
|
@ -56,7 +58,8 @@ export default class ServerDropdown extends Component {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.closeServerDropdown !== this.props.closeServerDropdown) {
|
||||
const { closeServerDropdown } = this.props;
|
||||
if (prevProps.closeServerDropdown !== closeServerDropdown) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
@ -67,6 +70,7 @@ export default class ServerDropdown extends Component {
|
|||
}
|
||||
|
||||
close = () => {
|
||||
const { toggleServerDropdown } = this.props;
|
||||
Animated.timing(
|
||||
this.animatedValue,
|
||||
{
|
||||
|
@ -75,16 +79,18 @@ export default class ServerDropdown extends Component {
|
|||
easing: Easing.ease,
|
||||
useNativeDriver: true
|
||||
}
|
||||
).start(() => this.props.toggleServerDropdown());
|
||||
).start(() => toggleServerDropdown());
|
||||
}
|
||||
|
||||
addServer = () => {
|
||||
const { navigator, server } = this.props;
|
||||
|
||||
this.close();
|
||||
setTimeout(() => {
|
||||
this.props.navigator.showModal({
|
||||
navigator.showModal({
|
||||
screen: 'OnboardingView',
|
||||
passProps: {
|
||||
previousServer: this.props.server
|
||||
previousServer: server
|
||||
},
|
||||
navigatorStyle: {
|
||||
navBarHidden: true,
|
||||
|
@ -95,14 +101,18 @@ export default class ServerDropdown extends Component {
|
|||
}
|
||||
|
||||
select = async(server) => {
|
||||
const {
|
||||
server: serverProp, selectServerRequest, appStart, navigator
|
||||
} = this.props;
|
||||
|
||||
this.close();
|
||||
if (this.props.server !== server) {
|
||||
this.props.selectServerRequest(server);
|
||||
if (serverProp !== server) {
|
||||
selectServerRequest(server);
|
||||
const token = await AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`);
|
||||
if (!token) {
|
||||
this.props.appStart();
|
||||
appStart();
|
||||
setTimeout(() => {
|
||||
this.props.navigator.push({
|
||||
navigator.push({
|
||||
screen: 'NewServerView',
|
||||
backButtonTitle: '',
|
||||
passProps: {
|
||||
|
@ -119,32 +129,41 @@ export default class ServerDropdown extends Component {
|
|||
|
||||
renderSeparator = () => <View style={styles.serverSeparator} />;
|
||||
|
||||
renderServer = ({ item }) => (
|
||||
renderServer = ({ item }) => {
|
||||
const { server } = this.props;
|
||||
|
||||
return (
|
||||
<Touch onPress={() => this.select(item.id)} style={styles.serverItem} testID={`rooms-list-header-server-${ item.id }`}>
|
||||
<View style={styles.serverItemContainer}>
|
||||
{item.iconURL ?
|
||||
{item.iconURL
|
||||
? (
|
||||
<Image
|
||||
source={{ uri: item.iconURL }}
|
||||
defaultSource={{ uri: 'logo' }}
|
||||
style={styles.serverIcon}
|
||||
/> :
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Image
|
||||
source={{ uri: 'logo' }}
|
||||
style={styles.serverIcon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<View style={styles.serverTextContainer}>
|
||||
<Text style={styles.serverName}>{item.name || item.id}</Text>
|
||||
<Text style={styles.serverUrl}>{item.id}</Text>
|
||||
</View>
|
||||
{item.id === this.props.server ? <Image style={styles.checkIcon} source={{ uri: 'check' }} /> : null}
|
||||
{item.id === server ? <Image style={styles.checkIcon} source={{ uri: 'check' }} /> : null}
|
||||
</View>
|
||||
</Touch>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { servers } = this.state;
|
||||
const maxRows = 4;
|
||||
const initialTop = 41 + (Math.min(this.state.servers.length, maxRows) * ROW_HEIGHT);
|
||||
const initialTop = 41 + (Math.min(servers.length, maxRows) * ROW_HEIGHT);
|
||||
const translateY = this.animatedValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [-initialTop, 0]
|
||||
|
@ -171,7 +190,7 @@ export default class ServerDropdown extends Component {
|
|||
</View>
|
||||
<FlatList
|
||||
style={{ maxHeight: maxRows * ROW_HEIGHT }}
|
||||
data={this.state.servers}
|
||||
data={servers}
|
||||
keyExtractor={item => item.id}
|
||||
renderItem={this.renderServer}
|
||||
ItemSeparatorComponent={this.renderSeparator}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React, { Component } from 'react';
|
||||
import { View, Text, Animated, Easing, Image, TouchableWithoutFeedback } from 'react-native';
|
||||
import {
|
||||
View, Text, Animated, Easing, Image, TouchableWithoutFeedback
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
@ -46,14 +48,17 @@ export default class Sort extends Component {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.closeSortDropdown !== this.props.closeSortDropdown) {
|
||||
const { closeSortDropdown } = this.props;
|
||||
if (prevProps.closeSortDropdown !== closeSortDropdown) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
setSortPreference = async(param) => {
|
||||
const { setSortPreference } = this.props;
|
||||
|
||||
try {
|
||||
this.props.setSortPreference(param);
|
||||
setSortPreference(param);
|
||||
RocketChat.saveSortPreference(param);
|
||||
} catch (e) {
|
||||
log('RoomsListView.setSortPreference', e);
|
||||
|
@ -69,18 +74,22 @@ export default class Sort extends Component {
|
|||
}
|
||||
|
||||
toggleGroupByType = () => {
|
||||
this.setSortPreference({ groupByType: !this.props.groupByType });
|
||||
const { groupByType } = this.props;
|
||||
this.setSortPreference({ groupByType: !groupByType });
|
||||
}
|
||||
|
||||
toggleGroupByFavorites = () => {
|
||||
this.setSortPreference({ showFavorites: !this.props.showFavorites });
|
||||
const { showFavorites } = this.props;
|
||||
this.setSortPreference({ showFavorites: !showFavorites });
|
||||
}
|
||||
|
||||
toggleUnread = () => {
|
||||
this.setSortPreference({ showUnread: !this.props.showUnread });
|
||||
const { showUnread } = this.props;
|
||||
this.setSortPreference({ showUnread: !showUnread });
|
||||
}
|
||||
|
||||
close = () => {
|
||||
const { close } = this.props;
|
||||
Animated.timing(
|
||||
this.animatedValue,
|
||||
{
|
||||
|
@ -89,7 +98,7 @@ export default class Sort extends Component {
|
|||
easing: Easing.ease,
|
||||
useNativeDriver: true
|
||||
},
|
||||
).start(() => this.props.close());
|
||||
).start(() => close());
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -104,6 +113,7 @@ export default class Sort extends Component {
|
|||
const {
|
||||
sortBy, groupByType, showFavorites, showUnread
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
[
|
||||
<TouchableWithoutFeedback key='sort-backdrop' onPress={this.close}>
|
||||
|
@ -156,7 +166,7 @@ export default class Sort extends Component {
|
|||
style={[styles.dropdownContainerHeader, styles.sortToggleContainerClose]}
|
||||
>
|
||||
<View style={styles.sortItemContainer}>
|
||||
<Text style={styles.sortToggleText}>{I18n.t('Sorting_by', { key: I18n.t(this.props.sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
||||
<Text style={styles.sortToggleText}>{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
||||
<Image style={styles.sortIcon} source={{ uri: 'group_type' }} />
|
||||
</View>
|
||||
</Touch>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Platform, View, FlatList, BackHandler, ActivityIndicator, SafeAreaView, Text, Image, Dimensions, ScrollView, Keyboard } from 'react-native';
|
||||
import {
|
||||
Platform, View, FlatList, BackHandler, ActivityIndicator, SafeAreaView, Text, Image, Dimensions, ScrollView, Keyboard
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
|
@ -15,7 +17,7 @@ import I18n from '../../i18n';
|
|||
import SortDropdown from './SortDropdown';
|
||||
import ServerDropdown from './ServerDropdown';
|
||||
import Touch from '../../utils/touch';
|
||||
import { toggleSortDropdown } from '../../actions/rooms';
|
||||
import { toggleSortDropdown as toggleSortDropdownAction } from '../../actions/rooms';
|
||||
|
||||
const ROW_HEIGHT = 70;
|
||||
const SCROLL_OFFSET = 56;
|
||||
|
@ -54,7 +56,7 @@ if (Platform.OS === 'android') {
|
|||
showUnread: state.sortPreferences.showUnread,
|
||||
useRealName: state.settings.UI_Use_Real_Name
|
||||
}), dispatch => ({
|
||||
toggleSortDropdown: () => dispatch(toggleSortDropdown())
|
||||
toggleSortDropdown: () => dispatch(toggleSortDropdownAction())
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class RoomsListView extends LoggedView {
|
||||
|
@ -114,13 +116,15 @@ export default class RoomsListView extends LoggedView {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.server && this.props.loadingServer !== nextProps.loadingServer) {
|
||||
const { loadingServer, searchText } = this.props;
|
||||
|
||||
if (nextProps.server && loadingServer !== nextProps.loadingServer) {
|
||||
if (nextProps.loadingServer) {
|
||||
this.setState({ loading: true });
|
||||
} else {
|
||||
this.getSubscriptions();
|
||||
}
|
||||
} else if (this.props.searchText !== nextProps.searchText) {
|
||||
} else if (searchText !== nextProps.searchText) {
|
||||
this.search(nextProps.searchText);
|
||||
}
|
||||
}
|
||||
|
@ -130,11 +134,15 @@ export default class RoomsListView extends LoggedView {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
sortBy, groupByType, showFavorites, showUnread
|
||||
} = this.props;
|
||||
|
||||
if (!(
|
||||
(prevProps.sortBy === this.props.sortBy) &&
|
||||
(prevProps.groupByType === this.props.groupByType) &&
|
||||
(prevProps.showFavorites === this.props.showFavorites) &&
|
||||
(prevProps.showUnread === this.props.showUnread)
|
||||
(prevProps.sortBy === sortBy)
|
||||
&& (prevProps.groupByType === groupByType)
|
||||
&& (prevProps.showFavorites === showFavorites)
|
||||
&& (prevProps.showUnread === showUnread)
|
||||
)) {
|
||||
this.getSubscriptions();
|
||||
}
|
||||
|
@ -158,7 +166,7 @@ export default class RoomsListView extends LoggedView {
|
|||
const { navigator } = this.props;
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'newMessage') {
|
||||
this.props.navigator.showModal({
|
||||
navigator.showModal({
|
||||
screen: 'NewMessageView',
|
||||
title: I18n.t('New_Message'),
|
||||
passProps: {
|
||||
|
@ -183,8 +191,12 @@ export default class RoomsListView extends LoggedView {
|
|||
}
|
||||
|
||||
getSubscriptions = () => {
|
||||
if (this.props.server && this.hasActiveDB()) {
|
||||
if (this.props.sortBy === 'alphabetical') {
|
||||
const {
|
||||
server, sortBy, showUnread, showFavorites, groupByType
|
||||
} = this.props;
|
||||
|
||||
if (server && this.hasActiveDB()) {
|
||||
if (sortBy === 'alphabetical') {
|
||||
this.data = database.objects('subscriptions').filtered('archived != true && open == true').sorted('name', false);
|
||||
} else {
|
||||
this.data = database.objects('subscriptions').filtered('archived != true && open == true').sorted('roomUpdatedAt', true);
|
||||
|
@ -199,7 +211,7 @@ export default class RoomsListView extends LoggedView {
|
|||
let livechat = [];
|
||||
|
||||
// unread
|
||||
if (this.props.showUnread) {
|
||||
if (showUnread) {
|
||||
this.unread = this.data.filtered('archived != true && open == true').sorted('name', false).filtered('(unread > 0 || alert == true)');
|
||||
unread = this.unread.slice();
|
||||
setTimeout(() => {
|
||||
|
@ -209,7 +221,7 @@ export default class RoomsListView extends LoggedView {
|
|||
this.removeListener(unread);
|
||||
}
|
||||
// favorites
|
||||
if (this.props.showFavorites) {
|
||||
if (showFavorites) {
|
||||
this.favorites = this.data.filtered('f == true');
|
||||
favorites = this.favorites.slice();
|
||||
setTimeout(() => {
|
||||
|
@ -219,7 +231,7 @@ export default class RoomsListView extends LoggedView {
|
|||
this.removeListener(favorites);
|
||||
}
|
||||
// type
|
||||
if (this.props.groupByType) {
|
||||
if (groupByType) {
|
||||
// channels
|
||||
this.channels = this.data.filtered('t == $0', 'c');
|
||||
channels = this.channels.slice();
|
||||
|
@ -241,7 +253,7 @@ export default class RoomsListView extends LoggedView {
|
|||
this.removeListener(this.chats);
|
||||
} else {
|
||||
// chats
|
||||
if (this.props.showUnread) {
|
||||
if (showUnread) {
|
||||
this.chats = this.data.filtered('(unread == 0 && alert == false)');
|
||||
} else {
|
||||
this.chats = this.data;
|
||||
|
@ -327,7 +339,8 @@ export default class RoomsListView extends LoggedView {
|
|||
}
|
||||
|
||||
goRoom = (rid, name) => {
|
||||
this.props.navigator.push({
|
||||
const { navigator } = this.props;
|
||||
navigator.push({
|
||||
screen: 'RoomView',
|
||||
title: name,
|
||||
backButtonTitle: '',
|
||||
|
@ -358,34 +371,41 @@ export default class RoomsListView extends LoggedView {
|
|||
}
|
||||
|
||||
toggleSort = () => {
|
||||
const { toggleSortDropdown } = this.props;
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
this.scroll.scrollTo({ x: 0, y: SCROLL_OFFSET, animated: true });
|
||||
} else {
|
||||
this.scroll.scrollTo({ x: 0, y: 0, animated: true });
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.props.toggleSortDropdown();
|
||||
toggleSortDropdown();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
renderHeader = () => {
|
||||
if (this.state.search.length > 0) {
|
||||
const { search } = this.state;
|
||||
if (search.length > 0) {
|
||||
return null;
|
||||
}
|
||||
return this.renderSort();
|
||||
}
|
||||
|
||||
renderSort = () => (
|
||||
renderSort = () => {
|
||||
const { sortBy } = this.props;
|
||||
|
||||
return (
|
||||
<Touch
|
||||
onPress={this.toggleSort}
|
||||
style={styles.dropdownContainerHeader}
|
||||
>
|
||||
<View style={styles.sortItemContainer}>
|
||||
<Text style={styles.sortToggleText}>{I18n.t('Sorting_by', { key: I18n.t(this.props.sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
||||
<Text style={styles.sortToggleText}>{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
||||
<Image style={styles.sortIcon} source={{ uri: 'group_type' }} />
|
||||
</View>
|
||||
</Touch>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderSearchBar = () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
|
@ -394,9 +414,11 @@ export default class RoomsListView extends LoggedView {
|
|||
}
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
const id = item.rid.replace(this.props.userId, '').trim();
|
||||
const { useRealName } = this.props;
|
||||
return (<RoomItem
|
||||
const { useRealName, userId, baseUrl } = this.props;
|
||||
const id = item.rid.replace(userId, '').trim();
|
||||
|
||||
return (
|
||||
<RoomItem
|
||||
alert={item.alert}
|
||||
unread={item.unread}
|
||||
userMentions={item.userMentions}
|
||||
|
@ -407,23 +429,26 @@ export default class RoomsListView extends LoggedView {
|
|||
key={item._id}
|
||||
id={id}
|
||||
type={item.t}
|
||||
baseUrl={this.props.baseUrl}
|
||||
baseUrl={baseUrl}
|
||||
onPress={() => this._onPressItem(item)}
|
||||
testID={`rooms-list-view-item-${ item.name }`}
|
||||
height={ROW_HEIGHT}
|
||||
/>);
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderSeparator = () => <View style={styles.separator} />;
|
||||
|
||||
renderSection = (data, header) => {
|
||||
if (header === 'Unread' && !this.props.showUnread) {
|
||||
const { showUnread, showFavorites, groupByType } = this.props;
|
||||
|
||||
if (header === 'Unread' && !showUnread) {
|
||||
return null;
|
||||
} else if (header === 'Favorites' && !this.props.showFavorites) {
|
||||
} else if (header === 'Favorites' && !showFavorites) {
|
||||
return null;
|
||||
} else if (['Channels', 'Direct_Messages', 'Private_Groups', 'Livechat'].includes(header) && !this.props.groupByType) {
|
||||
} else if (['Channels', 'Direct_Messages', 'Private_Groups', 'Livechat'].includes(header) && !groupByType) {
|
||||
return null;
|
||||
} else if (header === 'Chats' && this.props.groupByType) {
|
||||
} else if (header === 'Chats' && groupByType) {
|
||||
return null;
|
||||
}
|
||||
if (data.length > 0) {
|
||||
|
@ -486,7 +511,9 @@ export default class RoomsListView extends LoggedView {
|
|||
}
|
||||
|
||||
renderScroll = () => {
|
||||
if (this.state.loading) {
|
||||
const { loading } = this.state;
|
||||
|
||||
if (loading) {
|
||||
return <ActivityIndicator style={styles.loading} />;
|
||||
}
|
||||
|
||||
|
@ -506,22 +533,25 @@ export default class RoomsListView extends LoggedView {
|
|||
|
||||
render = () => {
|
||||
const {
|
||||
sortBy, groupByType, showFavorites, showUnread, showServerDropdown, showSortDropdown
|
||||
sortBy, groupByType, showFavorites, showUnread, showServerDropdown, showSortDropdown, navigator
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container} testID='rooms-list-view'>
|
||||
{this.renderScroll()}
|
||||
{showSortDropdown ?
|
||||
{showSortDropdown
|
||||
? (
|
||||
<SortDropdown
|
||||
close={this.toggleSort}
|
||||
sortBy={sortBy}
|
||||
groupByType={groupByType}
|
||||
showFavorites={showFavorites}
|
||||
showUnread={showUnread}
|
||||
/> :
|
||||
null}
|
||||
{showServerDropdown ? <ServerDropdown navigator={this.props.navigator} /> : null}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
}
|
||||
{showServerDropdown ? <ServerDropdown navigator={navigator} /> : null}
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -52,22 +52,26 @@ export default class SearchMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
onChangeSearch = debounce((search) => {
|
||||
const { searching } = this.state;
|
||||
|
||||
this.searchText = search;
|
||||
this.limit = 0;
|
||||
if (!this.state.searching) {
|
||||
if (!searching) {
|
||||
this.setState({ searching: true });
|
||||
}
|
||||
this.search();
|
||||
}, 1000)
|
||||
|
||||
search = async() => {
|
||||
const { rid } = this.props;
|
||||
|
||||
if (this._cancel) {
|
||||
this._cancel('cancel');
|
||||
}
|
||||
const cancel = new Promise((r, reject) => this._cancel = reject);
|
||||
let messages = [];
|
||||
try {
|
||||
const result = await Promise.race([RocketChat.messageSearch(this.searchText, this.props.rid, this.limit), cancel]);
|
||||
const result = await Promise.race([RocketChat.messageSearch(this.searchText, rid, this.limit), cancel]);
|
||||
messages = result.message.docs.map(message => buildMessage(message));
|
||||
this.setState({ messages, searching: false, loadingMore: false });
|
||||
} catch (e) {
|
||||
|
@ -91,12 +95,14 @@ export default class SearchMessagesView extends LoggedView {
|
|||
}
|
||||
}
|
||||
|
||||
renderItem = ({ item }) => (
|
||||
renderItem = ({ item }) => {
|
||||
const { user } = this.props;
|
||||
return (
|
||||
<Message
|
||||
item={item}
|
||||
style={styles.message}
|
||||
reactions={item.reactions}
|
||||
user={this.props.user}
|
||||
user={user}
|
||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||
onReactionPress={async(emoji) => {
|
||||
try {
|
||||
|
@ -109,9 +115,10 @@ export default class SearchMessagesView extends LoggedView {
|
|||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { searching, loadingMore } = this.state;
|
||||
const { searching, loadingMore, messages } = this.state;
|
||||
return (
|
||||
<SafeAreaView style={styles.container} testID='search-messages-view'>
|
||||
<View style={styles.searchContainer}>
|
||||
|
@ -126,7 +133,7 @@ export default class SearchMessagesView extends LoggedView {
|
|||
<View style={styles.divider} />
|
||||
</View>
|
||||
<FlatList
|
||||
data={this.state.messages}
|
||||
data={messages}
|
||||
renderItem={this.renderItem}
|
||||
style={styles.list}
|
||||
keyExtractor={item => item._id}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, StyleSheet, SafeAreaView, FlatList, LayoutAnimation, Platform } from 'react-native';
|
||||
import {
|
||||
View, StyleSheet, SafeAreaView, FlatList, LayoutAnimation, Platform
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { addUser, removeUser, reset, setLoading } from '../actions/selectedUsers';
|
||||
import {
|
||||
addUser as addUserAction, removeUser as removeUserAction, reset as resetAction, setLoading as setLoadingAction
|
||||
} from '../actions/selectedUsers';
|
||||
import database from '../lib/realm';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import UserItem from '../presentation/UserItem';
|
||||
|
@ -33,10 +37,10 @@ const styles = StyleSheet.create({
|
|||
users: state.selectedUsers.users,
|
||||
loading: state.selectedUsers.loading
|
||||
}), dispatch => ({
|
||||
addUser: user => dispatch(addUser(user)),
|
||||
removeUser: user => dispatch(removeUser(user)),
|
||||
reset: () => dispatch(reset()),
|
||||
setLoadingInvite: loading => dispatch(setLoading(loading))
|
||||
addUser: user => dispatch(addUserAction(user)),
|
||||
removeUser: user => dispatch(removeUserAction(user)),
|
||||
reset: () => dispatch(resetAction()),
|
||||
setLoadingInvite: loading => dispatch(setLoadingAction(loading))
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class SelectedUsersView extends LoggedView {
|
||||
|
@ -64,19 +68,22 @@ export default class SelectedUsersView extends LoggedView {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.navigator.setDrawerEnabled({
|
||||
const { navigator } = this.props;
|
||||
navigator.setDrawerEnabled({
|
||||
side: 'left',
|
||||
enabled: false
|
||||
});
|
||||
}
|
||||
|
||||
async componentDidUpdate(prevProps) {
|
||||
const isVisible = await this.props.navigator.screenIsCurrentlyVisible();
|
||||
const { navigator, users } = this.props;
|
||||
const isVisible = await navigator.screenIsCurrentlyVisible();
|
||||
|
||||
if (!isVisible) {
|
||||
return;
|
||||
}
|
||||
if (prevProps.users.length !== this.props.users.length) {
|
||||
const { length } = this.props.users;
|
||||
if (prevProps.users.length !== users.length) {
|
||||
const { length } = users;
|
||||
const rightButtons = [];
|
||||
if (length > 0) {
|
||||
rightButtons.push({
|
||||
|
@ -85,14 +92,15 @@ export default class SelectedUsersView extends LoggedView {
|
|||
testID: 'selected-users-view-submit'
|
||||
});
|
||||
}
|
||||
this.props.navigator.setButtons({ rightButtons });
|
||||
navigator.setButtons({ rightButtons });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { reset } = this.props;
|
||||
this.updateState.stop();
|
||||
this.data.removeAllListeners();
|
||||
this.props.reset();
|
||||
reset();
|
||||
}
|
||||
|
||||
async onNavigatorEvent(event) {
|
||||
|
@ -100,15 +108,16 @@ export default class SelectedUsersView extends LoggedView {
|
|||
if (event.id === 'create') {
|
||||
const { nextAction, setLoadingInvite, navigator } = this.props;
|
||||
if (nextAction === 'CREATE_CHANNEL') {
|
||||
this.props.navigator.push({
|
||||
navigator.push({
|
||||
screen: 'CreateChannelView',
|
||||
title: I18n.t('Create_Channel'),
|
||||
backButtonTitle: ''
|
||||
});
|
||||
} else {
|
||||
const { rid } = this.props;
|
||||
try {
|
||||
setLoadingInvite(true);
|
||||
await RocketChat.addUsersToRoom(this.props.rid);
|
||||
await RocketChat.addUsersToRoom(rid);
|
||||
navigator.pop();
|
||||
} catch (e) {
|
||||
log('RoomActions Add User', e);
|
||||
|
@ -124,6 +133,7 @@ export default class SelectedUsersView extends LoggedView {
|
|||
this.search(text);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
updateState = debounce(() => {
|
||||
this.forceUpdate();
|
||||
}, 1000);
|
||||
|
@ -135,14 +145,19 @@ export default class SelectedUsersView extends LoggedView {
|
|||
});
|
||||
}
|
||||
|
||||
isChecked = username => this.props.users.findIndex(el => el.name === username) !== -1;
|
||||
isChecked = (username) => {
|
||||
const { users } = this.props;
|
||||
return users.findIndex(el => el.name === username) !== -1;
|
||||
}
|
||||
|
||||
toggleUser = (user) => {
|
||||
const { addUser, removeUser } = this.props;
|
||||
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
if (!this.isChecked(user.name)) {
|
||||
this.props.addUser(user);
|
||||
addUser(user);
|
||||
} else {
|
||||
this.props.removeUser(user);
|
||||
removeUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,12 +179,14 @@ export default class SelectedUsersView extends LoggedView {
|
|||
)
|
||||
|
||||
renderSelected = () => {
|
||||
if (this.props.users.length === 0) {
|
||||
const { users } = this.props;
|
||||
|
||||
if (users.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<FlatList
|
||||
data={this.props.users}
|
||||
data={users}
|
||||
keyExtractor={item => item._id}
|
||||
style={[styles.list, sharedStyles.separatorTop]}
|
||||
contentContainerStyle={{ marginVertical: 5 }}
|
||||
|
@ -181,30 +198,36 @@ export default class SelectedUsersView extends LoggedView {
|
|||
);
|
||||
}
|
||||
|
||||
renderSelectedItem = ({ item }) => (
|
||||
renderSelectedItem = ({ item }) => {
|
||||
const { baseUrl } = this.props;
|
||||
return (
|
||||
<UserItem
|
||||
name={item.fname}
|
||||
username={item.name}
|
||||
onPress={() => this._onPressSelectedItem(item)}
|
||||
testID={`selected-user-${ item.name }`}
|
||||
baseUrl={this.props.baseUrl}
|
||||
baseUrl={baseUrl}
|
||||
style={{ paddingRight: 15 }}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />
|
||||
|
||||
renderItem = ({ item, index }) => {
|
||||
const { search } = this.state;
|
||||
const { baseUrl } = this.props;
|
||||
|
||||
const name = item.search ? item.name : item.fname;
|
||||
const username = item.search ? item.username : item.name;
|
||||
let style = {};
|
||||
if (index === 0) {
|
||||
style = { ...sharedStyles.separatorTop };
|
||||
}
|
||||
if (this.state.search.length > 0 && index === this.state.search.length - 1) {
|
||||
if (search.length > 0 && index === search.length - 1) {
|
||||
style = { ...style, ...sharedStyles.separatorBottom };
|
||||
}
|
||||
if (this.state.search.length === 0 && index === this.data.length - 1) {
|
||||
if (search.length === 0 && index === this.data.length - 1) {
|
||||
style = { ...style, ...sharedStyles.separatorBottom };
|
||||
}
|
||||
return (
|
||||
|
@ -214,15 +237,17 @@ export default class SelectedUsersView extends LoggedView {
|
|||
onPress={() => this._onPressItem(item._id, item)}
|
||||
testID={`select-users-view-item-${ item.name }`}
|
||||
icon={this.isChecked(username) ? 'check' : null}
|
||||
baseUrl={this.props.baseUrl}
|
||||
baseUrl={baseUrl}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderList = () => (
|
||||
renderList = () => {
|
||||
const { search } = this.state;
|
||||
return (
|
||||
<FlatList
|
||||
data={this.state.search.length > 0 ? this.state.search : this.data}
|
||||
data={search.length > 0 ? search : this.data}
|
||||
extraData={this.props}
|
||||
keyExtractor={item => item._id}
|
||||
renderItem={this.renderItem}
|
||||
|
@ -231,12 +256,16 @@ export default class SelectedUsersView extends LoggedView {
|
|||
enableEmptySections
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render = () => (
|
||||
render = () => {
|
||||
const { loading } = this.props;
|
||||
return (
|
||||
<SafeAreaView style={styles.safeAreaView} testID='select-users-view'>
|
||||
{this.renderList()}
|
||||
<Loading visible={this.props.loading} />
|
||||
<Loading visible={loading} />
|
||||
</SafeAreaView>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, ScrollView, SafeAreaView, Dimensions } from 'react-native';
|
||||
import {
|
||||
View, ScrollView, SafeAreaView, Dimensions
|
||||
} from 'react-native';
|
||||
import RNPickerSelect from 'react-native-picker-select';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
@ -15,12 +17,12 @@ import Button from '../../containers/Button';
|
|||
import Loading from '../../containers/Loading';
|
||||
import { showErrorAlert, showToast } from '../../utils/info';
|
||||
import log from '../../utils/log';
|
||||
import { setUser } from '../../actions/login';
|
||||
import { setUser as setUserAction } from '../../actions/login';
|
||||
|
||||
@connect(state => ({
|
||||
userLanguage: state.login.user && state.login.user.language
|
||||
}), dispatch => ({
|
||||
setUser: params => dispatch(setUser(params))
|
||||
setUser: params => dispatch(setUserAction(params))
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class SettingsView extends LoggedView {
|
||||
|
@ -51,7 +53,8 @@ export default class SettingsView extends LoggedView {
|
|||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.navigator.setButtons({
|
||||
const { navigator } = this.props;
|
||||
navigator.setButtons({
|
||||
leftButtons: [{
|
||||
id: 'settings',
|
||||
icon: { uri: 'settings', scale: Dimensions.get('window').scale }
|
||||
|
@ -60,16 +63,18 @@ export default class SettingsView extends LoggedView {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.navigator.setDrawerEnabled({
|
||||
const { navigator } = this.props;
|
||||
navigator.setDrawerEnabled({
|
||||
side: 'left',
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
|
||||
onNavigatorEvent(event) {
|
||||
const { navigator } = this.props;
|
||||
if (event.type === 'NavBarButtonPress') {
|
||||
if (event.id === 'settings') {
|
||||
this.props.navigator.toggleDrawer({
|
||||
navigator.toggleDrawer({
|
||||
side: 'left'
|
||||
});
|
||||
}
|
||||
|
@ -86,17 +91,16 @@ export default class SettingsView extends LoggedView {
|
|||
}
|
||||
|
||||
formIsChanged = () => {
|
||||
const { userLanguage } = this.props;
|
||||
const { language } = this.state;
|
||||
return !(this.props.userLanguage === language);
|
||||
return !(userLanguage === language);
|
||||
}
|
||||
|
||||
submit = async() => {
|
||||
this.setState({ saving: true });
|
||||
|
||||
const {
|
||||
language
|
||||
} = this.state;
|
||||
const { userLanguage } = this.props;
|
||||
const { language } = this.state;
|
||||
const { userLanguage, setUser, navigator } = this.props;
|
||||
|
||||
if (!this.formIsChanged()) {
|
||||
return;
|
||||
|
@ -111,14 +115,14 @@ export default class SettingsView extends LoggedView {
|
|||
|
||||
try {
|
||||
await RocketChat.saveUserPreferences(params);
|
||||
this.props.setUser({ language: params.language });
|
||||
setUser({ language: params.language });
|
||||
|
||||
this.setState({ saving: false });
|
||||
setTimeout(() => {
|
||||
showToast(I18n.t('Preferences_saved'));
|
||||
|
||||
if (params.language) {
|
||||
this.props.navigator.setTitle({ title: I18n.t('Settings') });
|
||||
navigator.setTitle({ title: I18n.t('Settings') });
|
||||
}
|
||||
}, 300);
|
||||
} catch (e) {
|
||||
|
@ -134,7 +138,9 @@ export default class SettingsView extends LoggedView {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { language, languages, placeholder } = this.state;
|
||||
const {
|
||||
language, languages, placeholder, saving
|
||||
} = this.state;
|
||||
return (
|
||||
<KeyboardView
|
||||
contentContainerStyle={sharedStyles.container}
|
||||
|
@ -171,7 +177,7 @@ export default class SettingsView extends LoggedView {
|
|||
testID='settings-view-button'
|
||||
/>
|
||||
</View>
|
||||
<Loading visible={this.state.saving} />
|
||||
<Loading visible={saving} />
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
</KeyboardView>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
FlatList, View, Text, SafeAreaView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { openSnippetedMessages as openSnippetedMessagesAction, closeSnippetedMessages as closeSnippetedMessagesAction } from '../../actions/snippetedMessages';
|
||||
import LoggedView from '../View';
|
||||
import { openSnippetedMessages, closeSnippetedMessages } from '../../actions/snippetedMessages';
|
||||
import styles from './styles';
|
||||
import Message from '../../containers/message';
|
||||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||
|
@ -19,8 +21,8 @@ import I18n from '../../i18n';
|
|||
token: state.login.user && state.login.user.token
|
||||
}
|
||||
}), dispatch => ({
|
||||
openSnippetedMessages: (rid, limit) => dispatch(openSnippetedMessages(rid, limit)),
|
||||
closeSnippetedMessages: () => dispatch(closeSnippetedMessages())
|
||||
openSnippetedMessages: (rid, limit) => dispatch(openSnippetedMessagesAction(rid, limit)),
|
||||
closeSnippetedMessages: () => dispatch(closeSnippetedMessagesAction())
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class SnippetedMessagesView extends LoggedView {
|
||||
|
@ -47,17 +49,20 @@ export default class SnippetedMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.ready && nextProps.ready !== this.props.ready) {
|
||||
const { ready } = this.props;
|
||||
if (nextProps.ready && nextProps.ready !== ready) {
|
||||
this.setState({ loading: false, loadingMore: false });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.closeSnippetedMessages();
|
||||
const { closeSnippetedMessages } = this.props;
|
||||
closeSnippetedMessages();
|
||||
}
|
||||
|
||||
load() {
|
||||
this.props.openSnippetedMessages(this.props.rid, this.limit);
|
||||
load = () => {
|
||||
const { rid, openSnippetedMessages } = this.props;
|
||||
openSnippetedMessages(rid, this.limit);
|
||||
}
|
||||
|
||||
moreData = () => {
|
||||
|
@ -79,15 +84,18 @@ export default class SnippetedMessagesView extends LoggedView {
|
|||
</View>
|
||||
)
|
||||
|
||||
renderItem = ({ item }) => (
|
||||
renderItem = ({ item }) => {
|
||||
const { user } = this.props;
|
||||
return (
|
||||
<Message
|
||||
item={item}
|
||||
style={styles.message}
|
||||
reactions={item.reactions}
|
||||
user={this.props.user}
|
||||
user={user}
|
||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, loadingMore } = this.state;
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FlatList, View, Text, SafeAreaView } from 'react-native';
|
||||
import {
|
||||
FlatList, View, Text, SafeAreaView
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
|
||||
import { openStarredMessages as openStarredMessagesAction, closeStarredMessages as closeStarredMessagesAction } from '../../actions/starredMessages';
|
||||
import { toggleStarRequest as toggleStarRequestAction } from '../../actions/messages';
|
||||
import LoggedView from '../View';
|
||||
import { openStarredMessages, closeStarredMessages } from '../../actions/starredMessages';
|
||||
import styles from './styles';
|
||||
import Message from '../../containers/message';
|
||||
import { toggleStarRequest } from '../../actions/messages';
|
||||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||
import I18n from '../../i18n';
|
||||
|
||||
|
@ -25,9 +27,9 @@ const options = [I18n.t('Unstar'), I18n.t('Cancel')];
|
|||
token: state.login.user && state.login.user.token
|
||||
}
|
||||
}), dispatch => ({
|
||||
openStarredMessages: (rid, limit) => dispatch(openStarredMessages(rid, limit)),
|
||||
closeStarredMessages: () => dispatch(closeStarredMessages()),
|
||||
toggleStarRequest: message => dispatch(toggleStarRequest(message))
|
||||
openStarredMessages: (rid, limit) => dispatch(openStarredMessagesAction(rid, limit)),
|
||||
closeStarredMessages: () => dispatch(closeStarredMessagesAction()),
|
||||
toggleStarRequest: message => dispatch(toggleStarRequestAction(message))
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class StarredMessagesView extends LoggedView {
|
||||
|
@ -56,13 +58,15 @@ export default class StarredMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.ready && nextProps.ready !== this.props.ready) {
|
||||
const { ready } = this.props;
|
||||
if (nextProps.ready && nextProps.ready !== ready) {
|
||||
this.setState({ loading: false, loadingMore: false });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.closeStarredMessages();
|
||||
const { closeStarredMessages } = this.props;
|
||||
closeStarredMessages();
|
||||
}
|
||||
|
||||
onLongPress = (message) => {
|
||||
|
@ -73,9 +77,12 @@ export default class StarredMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
handleActionPress = (actionIndex) => {
|
||||
const { message } = this.state;
|
||||
const { toggleStarRequest } = this.props;
|
||||
|
||||
switch (actionIndex) {
|
||||
case STAR_INDEX:
|
||||
this.props.toggleStarRequest(this.state.message);
|
||||
toggleStarRequest(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -83,7 +90,8 @@ export default class StarredMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
load = () => {
|
||||
this.props.openStarredMessages(this.props.rid, this.limit);
|
||||
const { rid, openStarredMessages } = this.props;
|
||||
openStarredMessages(rid, this.limit);
|
||||
}
|
||||
|
||||
moreData = () => {
|
||||
|
@ -105,16 +113,19 @@ export default class StarredMessagesView extends LoggedView {
|
|||
</View>
|
||||
)
|
||||
|
||||
renderItem = ({ item }) => (
|
||||
renderItem = ({ item }) => {
|
||||
const { user } = this.props;
|
||||
return (
|
||||
<Message
|
||||
item={item}
|
||||
style={styles.message}
|
||||
reactions={item.reactions}
|
||||
user={this.props.user}
|
||||
user={user}
|
||||
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
|
||||
onLongPress={this.onLongPress}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, loadingMore } = this.state;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { StyleSheet, Platform } from 'react-native';
|
||||
|
||||
import { COLOR_DANGER, COLOR_BUTTON_PRIMARY, COLOR_TEXT, COLOR_SEPARATOR } from '../constants/colors';
|
||||
import {
|
||||
COLOR_DANGER, COLOR_BUTTON_PRIMARY, COLOR_TEXT, COLOR_SEPARATOR
|
||||
} from '../constants/colors';
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
|
|