Unnecessary re-renders removed (#570)
* shouldComponentUpdate * Rooms list shouldcomponentupdate * RoomView shouldComponentUpdate * Messagebox and Message shouldComponentUpdate * EmojiPicker shouldComponentUpdate * RoomActions shouldComponentUpdate * Room info shouldComponentUpdate * Update RNN * Use only one Flatlist if none group filter is selected * Update fix * shouldComponentUpdate * Bug fixes * ListView changes * Bug fix * render list bug fix * Changes on public channels * - RoomView saga leak removed - Join room e2e tests added * Rest versions * Method call versions * Min RocketChat version alert
This commit is contained in:
parent
8384d4eeff
commit
d23c055584
|
@ -8,6 +8,8 @@
|
|||
[![CodeFactor](https://www.codefactor.io/repository/github/rocketchat/rocket.chat.reactnative/badge)](https://www.codefactor.io/repository/github/rocketchat/rocket.chat.reactnative)
|
||||
[![Known Vulnerabilities](https://snyk.io/test/github/rocketchat/rocket.chat.reactnative/badge.svg)](https://snyk.io/test/github/rocketchat/rocket.chat.reactnative)
|
||||
|
||||
**Supported Server Versions:** 0.66.0+
|
||||
|
||||
## Download
|
||||
<a href="https://play.google.com/store/apps/details?id=chat.rocket.reactnative">
|
||||
<img alt="Download on Google Play" src="https://play.google.com/intl/en_us/badges/images/badge_new.png" height=43>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, ViewPropTypes } from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
export default class Avatar extends React.PureComponent {
|
||||
export default class Avatar extends PureComponent {
|
||||
static propTypes = {
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
style: ViewPropTypes.style,
|
||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { View, TouchableOpacity, Text } from 'react-native';
|
||||
import styles from './styles';
|
||||
|
||||
export default class TabBar extends React.PureComponent {
|
||||
export default class TabBar extends React.Component {
|
||||
static propTypes = {
|
||||
goToPage: PropTypes.func,
|
||||
activeTab: PropTypes.number,
|
||||
|
@ -11,6 +11,14 @@ export default class TabBar extends React.PureComponent {
|
|||
tabEmojiStyle: PropTypes.object
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const { activeTab } = this.props;
|
||||
if (nextProps.activeTab !== activeTab) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
tabs, goToPage, tabEmojiStyle, activeTab
|
||||
|
|
|
@ -4,6 +4,8 @@ import { ScrollView } from 'react-native';
|
|||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||
import map from 'lodash/map';
|
||||
import { emojify } from 'react-emojione';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import TabBar from './TabBar';
|
||||
import EmojiCategory from './EmojiCategory';
|
||||
import styles from './styles';
|
||||
|
@ -28,26 +30,41 @@ export default class EmojiPicker extends Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
frequentlyUsed: [],
|
||||
customEmojis: []
|
||||
};
|
||||
this.frequentlyUsed = database.objects('frequentlyUsedEmoji').sorted('count', true);
|
||||
this.customEmojis = database.objects('customEmojis');
|
||||
this.state = {
|
||||
frequentlyUsed: [],
|
||||
customEmojis: [],
|
||||
show: false
|
||||
};
|
||||
this.updateFrequentlyUsed = this.updateFrequentlyUsed.bind(this);
|
||||
this.updateCustomEmojis = this.updateCustomEmojis.bind(this);
|
||||
}
|
||||
//
|
||||
// shouldComponentUpdate(nextProps) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
componentDidMount() {
|
||||
this.updateFrequentlyUsed();
|
||||
this.updateCustomEmojis();
|
||||
requestAnimationFrame(() => this.setState({ show: true }));
|
||||
this.frequentlyUsed.addListener(this.updateFrequentlyUsed);
|
||||
this.customEmojis.addListener(this.updateCustomEmojis);
|
||||
this.updateFrequentlyUsed();
|
||||
this.updateCustomEmojis();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { frequentlyUsed, customEmojis, show } = this.state;
|
||||
const { width } = this.props;
|
||||
if (nextState.show !== show) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.width !== width) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.frequentlyUsed, frequentlyUsed)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.customEmojis, customEmojis)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { Component } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ActionSheet from 'react-native-actionsheet';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
|
||||
export default class FilesActions extends Component {
|
||||
export default class FilesActions extends PureComponent {
|
||||
static propTypes = {
|
||||
hideActions: PropTypes.func.isRequired,
|
||||
takePhoto: PropTypes.func.isRequired,
|
||||
|
|
|
@ -56,6 +56,10 @@ export default class ReplyPreview extends Component {
|
|||
username: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
close = () => {
|
||||
const { close } = this.props;
|
||||
close();
|
||||
|
|
|
@ -90,6 +90,28 @@ export default class UploadModal extends Component {
|
|||
return null;
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { name, description, file } = this.state;
|
||||
const { window, isVisible } = this.props;
|
||||
|
||||
if (nextState.name !== name) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.description !== description) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.isVisible !== isVisible) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.window.width !== window.width) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.file, file)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
submit = () => {
|
||||
const { file, submit } = this.props;
|
||||
const { name, description } = this.state;
|
||||
|
@ -162,12 +184,12 @@ export default class UploadModal extends Component {
|
|||
<ScrollView style={styles.scrollView}>
|
||||
<Image source={{ isStatic: true, uri: file.path }} style={styles.image} />
|
||||
<TextInput
|
||||
placeholder='File name'
|
||||
placeholder={I18n.t('File_name')}
|
||||
value={name}
|
||||
onChangeText={value => this.setState({ name: value })}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder='File description'
|
||||
placeholder={I18n.t('File_description')}
|
||||
value={description}
|
||||
onChangeText={value => this.setState({ description: value })}
|
||||
/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
View, TextInput, FlatList, Text, TouchableOpacity, Alert, Image
|
||||
|
@ -9,6 +9,7 @@ import { emojify } from 'react-emojione';
|
|||
import { KeyboardAccessoryView } from 'react-native-keyboard-input';
|
||||
import ImagePicker from 'react-native-image-crop-picker';
|
||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import { userTyping as userTypingAction } from '../../actions/room';
|
||||
import {
|
||||
|
@ -59,7 +60,7 @@ const imagePickerConfig = {
|
|||
typing: status => dispatch(userTypingAction(status)),
|
||||
closeReply: () => dispatch(replyCancelAction())
|
||||
}))
|
||||
export default class MessageBox extends React.PureComponent {
|
||||
export default class MessageBox extends Component {
|
||||
static propTypes = {
|
||||
rid: PropTypes.string.isRequired,
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
|
@ -108,6 +109,43 @@ export default class MessageBox extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {
|
||||
showEmojiKeyboard, showFilesAction, showSend, recording, mentions, file
|
||||
} = this.state;
|
||||
const {
|
||||
roomType, replying, editing
|
||||
} = this.props;
|
||||
if (nextProps.roomType !== roomType) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.replying !== replying) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.editing !== editing) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.showEmojiKeyboard !== showEmojiKeyboard) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.showFilesAction !== showFilesAction) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.showSend !== showSend) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.recording !== recording) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.mentions, mentions)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.file, file)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
onChangeText(text) {
|
||||
const { typing } = this.props;
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
import { connect } from 'react-redux';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { Navigation } from 'react-native-navigation';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import { setStackRoot as setStackRootAction } from '../actions';
|
||||
import { logout as logoutAction } from '../actions/login';
|
||||
|
@ -111,7 +112,8 @@ export default class Sidebar extends Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showStatus: false
|
||||
showStatus: false,
|
||||
status: []
|
||||
};
|
||||
Navigation.events().bindComponent(this);
|
||||
}
|
||||
|
@ -127,6 +129,43 @@ export default class Sidebar extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { status, showStatus } = this.state;
|
||||
const {
|
||||
Site_Name, stackRoot, user, baseUrl
|
||||
} = this.props;
|
||||
if (nextState.showStatus !== showStatus) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.Site_Name !== Site_Name) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.stackRoot !== stackRoot) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.Site_Name !== Site_Name) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.baseUrl !== baseUrl) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.user && user) {
|
||||
if (nextProps.user.language !== user.language) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.user.status !== user.status) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.user.username !== user.username) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!equal(nextState.status, status)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
handleChangeStack = (event) => {
|
||||
const { stack } = event;
|
||||
this.setStack(stack);
|
||||
|
@ -140,7 +179,6 @@ export default class Sidebar extends Component {
|
|||
}
|
||||
|
||||
setStatus = () => {
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
status: [{
|
||||
id: 'online',
|
||||
|
@ -156,7 +194,6 @@ export default class Sidebar extends Component {
|
|||
name: I18n.t('Invisible')
|
||||
}]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setStack = async(stack) => {
|
||||
|
|
|
@ -7,6 +7,7 @@ import Video from 'react-native-video';
|
|||
import Slider from 'react-native-slider';
|
||||
import moment from 'moment';
|
||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import Markdown from './Markdown';
|
||||
|
||||
|
@ -47,7 +48,7 @@ const styles = StyleSheet.create({
|
|||
|
||||
const formatTime = seconds => moment.utc(seconds * 1000).format('mm:ss');
|
||||
|
||||
export default class Audio extends React.PureComponent {
|
||||
export default class Audio extends React.Component {
|
||||
static propTypes = {
|
||||
file: PropTypes.object.isRequired,
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
|
@ -69,6 +70,29 @@ export default class Audio extends React.PureComponent {
|
|||
};
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {
|
||||
currentTime, duration, paused, uri
|
||||
} = this.state;
|
||||
const { file } = this.props;
|
||||
if (nextState.currentTime !== currentTime) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.duration !== duration) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.paused !== paused) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.uri !== uri) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps.file, file)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
onLoad(data) {
|
||||
this.setState({ duration: data.duration > 0 ? data.duration : 0 });
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { RectButton } from 'react-native-gesture-handler';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import PhotoModal from './PhotoModal';
|
||||
import Markdown from './Markdown';
|
||||
import styles from './styles';
|
||||
|
||||
export default class extends React.PureComponent {
|
||||
export default class extends Component {
|
||||
static propTypes = {
|
||||
file: PropTypes.object.isRequired,
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
|
@ -18,7 +19,22 @@ export default class extends React.PureComponent {
|
|||
])
|
||||
}
|
||||
|
||||
state = { modalVisible: false };
|
||||
state = { modalVisible: false, isPressed: false };
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { modalVisible, isPressed } = this.state;
|
||||
const { file } = this.props;
|
||||
if (nextState.modalVisible !== modalVisible) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.isPressed !== isPressed) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps.file, file)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
onPressButton() {
|
||||
this.setState({
|
||||
|
|
|
@ -155,6 +155,8 @@ export default {
|
|||
Error_uploading: 'Error uploading',
|
||||
Favorites: 'Favorites',
|
||||
Files: 'Files',
|
||||
File_description: 'File description',
|
||||
File_name: 'File name',
|
||||
Finish_recording: 'Finish recording',
|
||||
For_your_security_you_must_enter_your_current_password_to_continue: 'For your security, you must enter your current password to continue',
|
||||
Forgot_my_password: 'Forgot my password',
|
||||
|
@ -170,6 +172,7 @@ export default {
|
|||
is_a_valid_RocketChat_instance: 'is a valid Rocket.Chat instance',
|
||||
is_not_a_valid_RocketChat_instance: 'is not a valid Rocket.Chat instance',
|
||||
is_typing: 'is typing',
|
||||
Invalid_server_version: 'The server you\'re trying to connect is using a version that\'s not supported by the app anymore: {{currentVersion}}.\n\nWe require version {{minVersion}}',
|
||||
Join_the_community: 'Join the community',
|
||||
Join: 'Join',
|
||||
Just_invited_people_can_access_this_channel: 'Just invited people can access this channel',
|
||||
|
|
|
@ -162,6 +162,8 @@ export default {
|
|||
Error_uploading: 'Erro subindo',
|
||||
Favorites: 'Favoritos',
|
||||
Files: 'Arquivos',
|
||||
File_description: 'Descrição do arquivo',
|
||||
File_name: 'Nome do arquivo',
|
||||
Finish_recording: 'Encerrar gravação',
|
||||
For_your_security_you_must_enter_your_current_password_to_continue: 'Para sua segurança, você precisa digitar sua senha',
|
||||
Forgot_my_password: 'Esqueci minha senha',
|
||||
|
@ -175,6 +177,7 @@ export default {
|
|||
Invisible: 'Invisível',
|
||||
Invite: 'Convidar',
|
||||
is_typing: 'está digitando',
|
||||
Invalid_server_version: 'O servidor que você está conectando não é suportado mais por esta versão do aplicativo: {{currentVersion}}.\n\nEsta versão do aplicativo requer a versão {{minVersion}} do servidor para funcionar corretamente.',
|
||||
Join_the_community: 'Junte-se à comunidade',
|
||||
Join: 'Entrar',
|
||||
Just_invited_people_can_access_this_channel: 'Apenas as pessoas convidadas podem acessar este canal',
|
||||
|
|
|
@ -8,6 +8,7 @@ const restTypes = {
|
|||
|
||||
async function open({ type, rid }) {
|
||||
try {
|
||||
// RC 0.61.0
|
||||
await SDK.api.post(`${ restTypes[type] }.open`, { roomId: rid });
|
||||
return true;
|
||||
} catch (e) {
|
||||
|
|
|
@ -16,6 +16,7 @@ const getLastMessage = () => {
|
|||
export default async function() {
|
||||
try {
|
||||
const lastMessage = getLastMessage();
|
||||
// RC 0.61.0
|
||||
const result = await SDK.api.get('emoji-custom');
|
||||
let { emojis } = result;
|
||||
emojis = emojis.filter(emoji => !lastMessage || emoji._updatedAt > lastMessage);
|
||||
|
|
|
@ -7,6 +7,7 @@ import defaultPermissions from '../../constants/permissions';
|
|||
|
||||
export default async function() {
|
||||
try {
|
||||
// RC 0.66.0
|
||||
const result = await SDK.api.get('permissions.list');
|
||||
|
||||
if (!result.success) {
|
||||
|
|
|
@ -18,6 +18,8 @@ export default function() {
|
|||
return new Promise(async(resolve, reject) => {
|
||||
try {
|
||||
const updatedSince = lastMessage();
|
||||
// subscriptions.get: Since RC 0.60.0
|
||||
// rooms.get: Since RC 0.62.0
|
||||
const [subscriptionsResult, roomsResult] = await (updatedSince
|
||||
? Promise.all([SDK.api.get('subscriptions.get', { updatedSince }), SDK.api.get('rooms.get', { updatedSince })])
|
||||
: Promise.all([SDK.api.get('subscriptions.get'), SDK.api.get('rooms.get')])
|
||||
|
|
|
@ -16,6 +16,7 @@ function updateServer(param) {
|
|||
export default async function() {
|
||||
try {
|
||||
const settingsParams = JSON.stringify(Object.keys(settings));
|
||||
// RC 0.60.0
|
||||
const result = await fetch(`${ SDK.api.url }settings.public?query={"_id":{"$in":${ settingsParams }}}`).then(response => response.json());
|
||||
|
||||
if (!result.success) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import log from '../../utils/log';
|
|||
async function load({ rid: roomId, latest, t }) {
|
||||
if (t === 'l') {
|
||||
try {
|
||||
// RC 0.51.0
|
||||
const data = await SDK.driver.asyncCall('loadHistory', roomId, null, 50, latest);
|
||||
if (!data || data.status === 'error') {
|
||||
return [];
|
||||
|
@ -23,6 +24,7 @@ async function load({ rid: roomId, latest, t }) {
|
|||
if (latest) {
|
||||
params = { ...params, latest: new Date(latest).toISOString() };
|
||||
}
|
||||
// RC 0.48.0
|
||||
const data = await SDK.api.get(`${ this.roomTypeToApiType(t) }.history`, params);
|
||||
if (!data || data.status === 'error') {
|
||||
return [];
|
||||
|
|
|
@ -12,6 +12,7 @@ async function load({ rid: roomId, lastOpen }) {
|
|||
} else {
|
||||
return [];
|
||||
}
|
||||
// RC 0.60.0
|
||||
const { result } = await SDK.api.get('chat.syncMessages', { roomId, lastUpdate, count: 50 });
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import log from '../../utils/log';
|
|||
export default async function readMessages(rid) {
|
||||
const ls = new Date();
|
||||
try {
|
||||
// RC 0.61.0
|
||||
const data = await SDK.api.post('subscriptions.read', { rid });
|
||||
const [subscription] = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
database.write(() => {
|
||||
|
|
|
@ -15,6 +15,7 @@ function _ufsComplete(fileId, store, token) {
|
|||
}
|
||||
|
||||
function _sendFileMessage(rid, data, msg = {}) {
|
||||
// RC 0.22.0
|
||||
return SDK.driver.asyncCall('sendFileMessage', rid, null, data, msg);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ export const getMessage = (rid, msg = {}) => {
|
|||
|
||||
export async function sendMessageCall(message) {
|
||||
const { _id, rid, msg } = message;
|
||||
// RC 0.60.0
|
||||
const data = await SDK.api.post('chat.sendMessage', { message: { _id, rid, msg } });
|
||||
return data;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { AsyncStorage, Platform } from 'react-native';
|
||||
import foreach from 'lodash/forEach';
|
||||
import * as SDK from '@rocket.chat/sdk';
|
||||
import semver from 'semver';
|
||||
|
||||
import reduxStore from './createStore';
|
||||
import defaultSettings from '../constants/settings';
|
||||
|
@ -42,6 +43,7 @@ const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
|||
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
|
||||
const call = (method, ...params) => SDK.driver.asyncCall(method, ...params);
|
||||
const returnAnArray = obj => obj || [];
|
||||
const MIN_ROCKETCHAT_VERSION = '0.66.0';
|
||||
|
||||
const RocketChat = {
|
||||
TOKEN_KEY,
|
||||
|
@ -51,6 +53,7 @@ const RocketChat = {
|
|||
createChannel({
|
||||
name, users, type, readOnly, broadcast
|
||||
}) {
|
||||
// RC 0.51.0
|
||||
return call(type ? 'createPrivateGroup' : 'createChannel', name, users, readOnly, {}, { broadcast });
|
||||
},
|
||||
async createDirectMessageAndWait(username) {
|
||||
|
@ -81,12 +84,27 @@ const RocketChat = {
|
|||
try {
|
||||
const result = await fetch(`${ server }/api/v1/info`).then(response => response.json());
|
||||
if (result.success && result.info) {
|
||||
return server;
|
||||
if (semver.lt(result.info.version, MIN_ROCKETCHAT_VERSION)) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Invalid_server_version',
|
||||
messageOptions: {
|
||||
currentVersion: result.info.version,
|
||||
minVersion: MIN_ROCKETCHAT_VERSION
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
log('testServer', e);
|
||||
}
|
||||
throw new Error({ error: 'invalid server' });
|
||||
return {
|
||||
success: false,
|
||||
message: 'The_URL_is_invalid'
|
||||
};
|
||||
},
|
||||
_setUser(ddpMessage) {
|
||||
this.activeUsers = this.activeUsers || {};
|
||||
|
@ -235,14 +253,17 @@ const RocketChat = {
|
|||
},
|
||||
|
||||
register(credentials) {
|
||||
// RC 0.50.0
|
||||
return SDK.api.post('users.register', credentials, false);
|
||||
},
|
||||
|
||||
setUsername(username) {
|
||||
// RC 0.51.0
|
||||
return call('setUsername', username);
|
||||
},
|
||||
|
||||
forgotPassword(email) {
|
||||
// RC 0.64.0
|
||||
return SDK.api.post('users.forgotPassword', { email }, false);
|
||||
},
|
||||
|
||||
|
@ -288,6 +309,7 @@ const RocketChat = {
|
|||
|
||||
async login(params) {
|
||||
try {
|
||||
// RC 0.64.0
|
||||
return await SDK.api.login(params);
|
||||
} catch (e) {
|
||||
reduxStore.dispatch(loginFailure(e));
|
||||
|
@ -302,6 +324,7 @@ const RocketChat = {
|
|||
console.log('logout -> removePushToken -> catch -> error', error);
|
||||
}
|
||||
try {
|
||||
// RC 0.60.0
|
||||
await SDK.api.logout();
|
||||
} catch (error) {
|
||||
console.log('logout -> api logout -> catch -> error', error);
|
||||
|
@ -343,6 +366,7 @@ const RocketChat = {
|
|||
type,
|
||||
appName: 'chat.rocket.reactnative' // TODO: try to get from config file
|
||||
};
|
||||
// RC 0.60.0
|
||||
return SDK.api.post('push.token', data);
|
||||
}
|
||||
return resolve();
|
||||
|
@ -351,6 +375,7 @@ const RocketChat = {
|
|||
removePushToken() {
|
||||
const token = getDeviceToken();
|
||||
if (token) {
|
||||
// RC 0.60.0
|
||||
return SDK.api.del('push.token', { token });
|
||||
}
|
||||
return Promise.resolve();
|
||||
|
@ -430,14 +455,17 @@ const RocketChat = {
|
|||
},
|
||||
|
||||
spotlight(search, usernames, type) {
|
||||
// RC 0.51.0
|
||||
return call('spotlight', search, usernames, type);
|
||||
},
|
||||
|
||||
createDirectMessage(username) {
|
||||
// RC 0.59.0
|
||||
return SDK.api.post('im.create', { username });
|
||||
},
|
||||
joinRoom(roomId) {
|
||||
// TODO: join code
|
||||
// RC 0.48.0
|
||||
return SDK.api.post('channels.join', { roomId });
|
||||
},
|
||||
sendFileMessage,
|
||||
|
@ -471,22 +499,28 @@ const RocketChat = {
|
|||
},
|
||||
deleteMessage(message) {
|
||||
const { _id, rid } = message;
|
||||
// RC 0.48.0
|
||||
return SDK.api.post('chat.delete', { roomId: rid, msgId: _id });
|
||||
},
|
||||
editMessage(message) {
|
||||
const { _id, msg, rid } = message;
|
||||
// RC 0.49.0
|
||||
return SDK.api.post('chat.update', { roomId: rid, msgId: _id, text: msg });
|
||||
},
|
||||
toggleStarMessage(message) {
|
||||
if (message.starred) {
|
||||
// RC 0.59.0
|
||||
return SDK.api.post('chat.unStarMessage', { messageId: message._id });
|
||||
}
|
||||
// RC 0.59.0
|
||||
return SDK.api.post('chat.starMessage', { messageId: message._id });
|
||||
},
|
||||
togglePinMessage(message) {
|
||||
if (message.pinned) {
|
||||
// RC 0.59.0
|
||||
return SDK.api.post('chat.unPinMessage', { messageId: message._id });
|
||||
}
|
||||
// RC 0.59.0
|
||||
return SDK.api.post('chat.pinMessage', { messageId: message._id });
|
||||
},
|
||||
getRoom(rid) {
|
||||
|
@ -496,9 +530,6 @@ const RocketChat = {
|
|||
}
|
||||
return Promise.resolve(result);
|
||||
},
|
||||
getRoomInfo(roomId) {
|
||||
return SDK.api.get('rooms.info', { roomId });
|
||||
},
|
||||
async getPermalink(message) {
|
||||
let room;
|
||||
try {
|
||||
|
@ -535,22 +566,30 @@ const RocketChat = {
|
|||
return call('UserPresence:setDefaultStatus', status);
|
||||
},
|
||||
setReaction(emoji, messageId) {
|
||||
// RC 0.62.2
|
||||
return SDK.api.post('chat.react', { emoji, messageId });
|
||||
},
|
||||
toggleFavorite(roomId, favorite) {
|
||||
// RC 0.64.0
|
||||
return SDK.api.post('rooms.favorite', { roomId, favorite });
|
||||
},
|
||||
getRoomMembers(rid, allUsers) {
|
||||
// RC 0.42.0
|
||||
return call('getUsersOfRoom', rid, allUsers);
|
||||
},
|
||||
getUserRoles() {
|
||||
// RC 0.27.0
|
||||
return call('getUserRoles');
|
||||
},
|
||||
getRoomCounters(roomId, t) {
|
||||
// RC 0.65.0
|
||||
return SDK.api.get(`${ this.roomTypeToApiType(t) }.counters`, { roomId });
|
||||
},
|
||||
async getRoomMember(rid, currentUserId) {
|
||||
try {
|
||||
if (rid === `${ currentUserId }${ currentUserId }`) {
|
||||
return Promise.resolve(currentUserId);
|
||||
}
|
||||
const membersResult = await RocketChat.getRoomMembers(rid, true);
|
||||
return Promise.resolve(membersResult.records.find(m => m._id !== currentUserId));
|
||||
} catch (error) {
|
||||
|
@ -559,53 +598,71 @@ const RocketChat = {
|
|||
},
|
||||
toggleBlockUser(rid, blocked, block) {
|
||||
if (block) {
|
||||
// RC 0.49.0
|
||||
return call('blockUser', { rid, blocked });
|
||||
}
|
||||
// RC 0.49.0
|
||||
return call('unblockUser', { rid, blocked });
|
||||
},
|
||||
leaveRoom(roomId, t) {
|
||||
// RC 0.48.0
|
||||
return SDK.api.post(`${ this.roomTypeToApiType(t) }.leave`, { roomId });
|
||||
},
|
||||
eraseRoom(roomId, t) {
|
||||
// RC 0.49.0
|
||||
return SDK.api.post(`${ this.roomTypeToApiType(t) }.delete`, { roomId });
|
||||
},
|
||||
toggleMuteUserInRoom(rid, username, mute) {
|
||||
if (mute) {
|
||||
// RC 0.51.0
|
||||
return call('muteUserInRoom', { rid, username });
|
||||
}
|
||||
// RC 0.51.0
|
||||
return call('unmuteUserInRoom', { rid, username });
|
||||
},
|
||||
toggleArchiveRoom(roomId, t, archive) {
|
||||
if (archive) {
|
||||
// RC 0.48.0
|
||||
return SDK.api.post(`${ this.roomTypeToApiType(t) }.archive`, { roomId });
|
||||
}
|
||||
// RC 0.48.0
|
||||
return SDK.api.post(`${ this.roomTypeToApiType(t) }.unarchive`, { roomId });
|
||||
},
|
||||
saveRoomSettings(rid, params) {
|
||||
// RC 0.55.0
|
||||
return call('saveRoomSettings', rid, params);
|
||||
},
|
||||
saveUserProfile(data) {
|
||||
// RC 0.62.2
|
||||
return SDK.api.post('users.updateOwnBasicInfo', { data });
|
||||
},
|
||||
saveUserPreferences(params) {
|
||||
// RC 0.51.0
|
||||
return call('saveUserPreferences', params);
|
||||
},
|
||||
saveNotificationSettings(roomId, notifications) {
|
||||
// RC 0.63.0
|
||||
return SDK.api.post('rooms.saveNotification', { roomId, notifications });
|
||||
},
|
||||
addUsersToRoom(rid) {
|
||||
let { users } = reduxStore.getState().selectedUsers;
|
||||
users = users.map(u => u.name);
|
||||
// RC 0.51.0
|
||||
return call('addUsersToRoom', { rid, users });
|
||||
},
|
||||
hasPermission(permissions, rid) {
|
||||
let roles = [];
|
||||
try {
|
||||
// get the room from realm
|
||||
const room = database.objects('subscriptions').filtered('rid = $0', rid)[0];
|
||||
// get room roles
|
||||
roles = room.roles; // eslint-disable-line prefer-destructuring
|
||||
} catch (error) {
|
||||
console.log('hasPermission -> error', error);
|
||||
}
|
||||
// get permissions from realm
|
||||
const permissionsFiltered = database.objects('permissions')
|
||||
.filter(permission => permissions.includes(permission._id));
|
||||
// get room roles
|
||||
const { roles } = room;
|
||||
// transform room roles to array
|
||||
const roomRoles = Array.from(Object.keys(roles), i => roles[i].value);
|
||||
// get user roles on the server from redux
|
||||
|
@ -625,12 +682,15 @@ const RocketChat = {
|
|||
}, {});
|
||||
},
|
||||
getAvatarSuggestion() {
|
||||
// RC 0.51.0
|
||||
return call('getAvatarSuggestion');
|
||||
},
|
||||
resetAvatar(userId) {
|
||||
// RC 0.55.0
|
||||
return SDK.api.post('users.resetAvatar', { userId });
|
||||
},
|
||||
setAvatarFromService({ data, contentType = '', service = null }) {
|
||||
// RC 0.51.0
|
||||
return call('setAvatarFromService', data, contentType, service);
|
||||
},
|
||||
async getSortPreferences() {
|
||||
|
@ -668,6 +728,7 @@ const RocketChat = {
|
|||
}
|
||||
},
|
||||
getUsernameSuggestion() {
|
||||
// RC 0.65.0
|
||||
return SDK.api.get('users.getUsernameSuggestion');
|
||||
},
|
||||
roomTypeToApiType(t) {
|
||||
|
@ -677,6 +738,7 @@ const RocketChat = {
|
|||
return types[t];
|
||||
},
|
||||
getFiles(roomId, type, offset) {
|
||||
// RC 0.59.0
|
||||
return SDK.api.get(`${ this.roomTypeToApiType(type) }.files`, {
|
||||
roomId,
|
||||
offset,
|
||||
|
@ -687,6 +749,7 @@ const RocketChat = {
|
|||
});
|
||||
},
|
||||
getMessages(roomId, type, query, offset) {
|
||||
// RC 0.59.0
|
||||
return SDK.api.get(`${ this.roomTypeToApiType(type) }.messages`, {
|
||||
roomId,
|
||||
query,
|
||||
|
@ -695,6 +758,7 @@ const RocketChat = {
|
|||
});
|
||||
},
|
||||
searchMessages(roomId, searchText) {
|
||||
// RC 0.60.0
|
||||
return SDK.api.get('chat.search', {
|
||||
roomId,
|
||||
searchText
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AsyncStorage } from 'react-native';
|
||||
import { delay } from 'redux-saga';
|
||||
import {
|
||||
takeLatest, take, select, put, all
|
||||
takeLatest, take, select, put, all, race
|
||||
} from 'redux-saga/effects';
|
||||
import { Navigation } from 'react-native-navigation';
|
||||
|
||||
|
@ -12,6 +12,10 @@ import database from '../lib/realm';
|
|||
import RocketChat from '../lib/rocketchat';
|
||||
import EventEmitter from '../utils/events';
|
||||
|
||||
const roomTypes = {
|
||||
channel: 'c', direct: 'd', group: 'p'
|
||||
};
|
||||
|
||||
const navigate = function* navigate({ params, sameServer = true }) {
|
||||
if (!sameServer) {
|
||||
yield put(appStart('inside'));
|
||||
|
@ -37,11 +41,12 @@ const navigate = function* navigate({ params, sameServer = true }) {
|
|||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
const [type, name] = params.path.split('/');
|
||||
Navigation.push(stack, {
|
||||
component: {
|
||||
name: 'RoomView',
|
||||
passProps: {
|
||||
rid: params.rid
|
||||
rid: params.rid, name, t: roomTypes[type]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -78,15 +83,16 @@ const handleOpen = function* handleOpen({ params }) {
|
|||
// if deep link is from same server
|
||||
if (server === host) {
|
||||
if (user) {
|
||||
yield take(types.SERVER.SELECT_SUCCESS);
|
||||
yield race({
|
||||
typing: take(types.SERVER.SELECT_SUCCESS),
|
||||
timeout: delay(3000)
|
||||
});
|
||||
yield navigate({ params });
|
||||
}
|
||||
} else {
|
||||
// if deep link is from a different server
|
||||
try {
|
||||
// Verify if server is real
|
||||
yield RocketChat.testServer(host);
|
||||
} catch (error) {
|
||||
const result = yield RocketChat.testServer(server);
|
||||
if (!result.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -74,13 +74,13 @@ const handleTogglePinRequest = function* handleTogglePinRequest({ message }) {
|
|||
}
|
||||
};
|
||||
|
||||
const goRoom = function* goRoom({ rid }) {
|
||||
const goRoom = function* goRoom({ rid, name }) {
|
||||
yield Navigation.popToRoot('RoomsListView');
|
||||
Navigation.push('RoomsListView', {
|
||||
component: {
|
||||
name: 'RoomView',
|
||||
passProps: {
|
||||
rid
|
||||
rid, name, t: 'd'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -91,10 +91,10 @@ const handleReplyBroadcast = function* handleReplyBroadcast({ message }) {
|
|||
const { username } = message.u;
|
||||
const subscriptions = database.objects('subscriptions').filtered('name = $0', username);
|
||||
if (subscriptions.length) {
|
||||
yield goRoom({ rid: subscriptions[0].rid });
|
||||
yield goRoom({ rid: subscriptions[0].rid, name: username });
|
||||
} else {
|
||||
const room = yield RocketChat.createDirectMessage(username);
|
||||
yield goRoom({ rid: room.rid });
|
||||
yield goRoom({ rid: room.rid, name: username });
|
||||
}
|
||||
yield delay(500);
|
||||
yield put(replyInit(message, false));
|
||||
|
|
|
@ -120,7 +120,6 @@ const watchuserTyping = function* watchuserTyping({ status }) {
|
|||
|
||||
const handleLeaveRoom = function* handleLeaveRoom({ rid, t }) {
|
||||
try {
|
||||
sub.stop();
|
||||
const result = yield RocketChat.leaveRoom(rid, t);
|
||||
if (result.success) {
|
||||
yield Navigation.popToRoot('RoomsListView');
|
||||
|
@ -136,7 +135,6 @@ const handleLeaveRoom = function* handleLeaveRoom({ rid, t }) {
|
|||
|
||||
const handleEraseRoom = function* handleEraseRoom({ rid, t }) {
|
||||
try {
|
||||
sub.stop();
|
||||
const result = yield RocketChat.eraseRoom(rid, t);
|
||||
if (result.success) {
|
||||
yield Navigation.popToRoot('RoomsListView');
|
||||
|
@ -148,7 +146,7 @@ const handleEraseRoom = function* handleEraseRoom({ rid, t }) {
|
|||
|
||||
const root = function* root() {
|
||||
yield takeLatest(types.ROOM.USER_TYPING, watchuserTyping);
|
||||
yield takeLatest(types.ROOM.OPEN, watchRoomOpen);
|
||||
yield takeEvery(types.ROOM.OPEN, watchRoomOpen);
|
||||
yield takeEvery(types.ROOM.MESSAGE_RECEIVED, handleMessageReceived);
|
||||
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
|
||||
yield takeLatest(types.ROOM.ERASE, handleEraseRoom);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { put, takeLatest } from 'redux-saga/effects';
|
||||
import { AsyncStorage } from 'react-native';
|
||||
import { AsyncStorage, Alert } from 'react-native';
|
||||
import { Navigation } from 'react-native-navigation';
|
||||
import { Provider } from 'react-redux';
|
||||
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
||||
|
@ -13,6 +13,7 @@ import RocketChat from '../lib/rocketchat';
|
|||
import database from '../lib/realm';
|
||||
import log from '../utils/log';
|
||||
import store from '../lib/createStore';
|
||||
import I18n from '../i18n';
|
||||
|
||||
let LoginSignupView = null;
|
||||
let LoginView = null;
|
||||
|
@ -49,7 +50,13 @@ const handleSelectServer = function* handleSelectServer({ server }) {
|
|||
|
||||
const handleServerRequest = function* handleServerRequest({ server }) {
|
||||
try {
|
||||
yield RocketChat.testServer(server);
|
||||
const result = yield RocketChat.testServer(server);
|
||||
if (!result.success) {
|
||||
Alert.alert(I18n.t('Oops'), I18n.t(result.message, result.messageOptions));
|
||||
yield put(serverFailure());
|
||||
return;
|
||||
}
|
||||
|
||||
const loginServicesLength = yield RocketChat.getLoginServices(server);
|
||||
if (loginServicesLength === 0) {
|
||||
if (LoginView == null) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
} from 'react-native';
|
||||
import { Navigation } from 'react-native-navigation';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import Loading from '../containers/Loading';
|
||||
import LoggedView from './View';
|
||||
|
@ -128,6 +129,43 @@ export default class CreateChannelView extends LoggedView {
|
|||
}, 600);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {
|
||||
channelName, type, readOnly, broadcast
|
||||
} = this.state;
|
||||
const {
|
||||
error, failure, isFetching, result, users
|
||||
} = this.props;
|
||||
if (nextState.channelName !== channelName) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.type !== type) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.readOnly !== readOnly) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.broadcast !== broadcast) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.failure !== failure) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.isFetching !== isFetching) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps.error, error)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps.result, result)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps.users, users)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
isFetching, failure, error, result, componentId
|
||||
|
@ -139,13 +177,14 @@ export default class CreateChannelView extends LoggedView {
|
|||
const msg = error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') });
|
||||
showErrorAlert(msg);
|
||||
} else {
|
||||
const { rid } = result;
|
||||
const { type } = this.state;
|
||||
const { rid, name } = result;
|
||||
await Navigation.dismissModal(componentId);
|
||||
Navigation.push('RoomsListView', {
|
||||
component: {
|
||||
name: 'RoomView',
|
||||
passProps: {
|
||||
rid
|
||||
rid, name, t: type ? 'p' : 'c'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -44,6 +44,20 @@ export default class ForgotPasswordView extends LoggedView {
|
|||
}, 600);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { email, invalidEmail, isFetching } = this.state;
|
||||
if (nextState.email !== email) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.invalidEmail !== invalidEmail) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.isFetching !== isFetching) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
|
|
|
@ -8,6 +8,7 @@ import { Navigation } from 'react-native-navigation';
|
|||
import { Base64 } from 'js-base64';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import { gestureHandlerRootHOC, RectButton, BorderlessButton } from 'react-native-gesture-handler';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import LoggedView from './View';
|
||||
import sharedStyles from './Styles';
|
||||
|
@ -94,7 +95,6 @@ const SERVICES_COLLAPSED_HEIGHT = 174;
|
|||
|
||||
@connect(state => ({
|
||||
server: state.server.server,
|
||||
isFetching: state.login.isFetching,
|
||||
Site_Name: state.settings.Site_Name,
|
||||
services: state.login.services
|
||||
}))
|
||||
|
@ -116,7 +116,6 @@ export default class LoginSignupView extends LoggedView {
|
|||
|
||||
static propTypes = {
|
||||
componentId: PropTypes.string,
|
||||
isFetching: PropTypes.bool,
|
||||
server: PropTypes.string,
|
||||
services: PropTypes.object,
|
||||
Site_Name: PropTypes.string
|
||||
|
@ -133,6 +132,27 @@ export default class LoginSignupView extends LoggedView {
|
|||
this.setTitle(componentId, Site_Name);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { collapsed, servicesHeight } = this.state;
|
||||
const { server, Site_Name, services } = this.props;
|
||||
if (nextState.collapsed !== collapsed) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.servicesHeight !== servicesHeight) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.server !== server) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.Site_Name !== Site_Name) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps.services, services)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { componentId, Site_Name } = this.props;
|
||||
if (Site_Name && prevProps.Site_Name !== Site_Name) {
|
||||
|
|
|
@ -126,6 +126,46 @@ export default class LoginView extends LoggedView {
|
|||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {
|
||||
user, password, code, showTOTP
|
||||
} = this.state;
|
||||
const {
|
||||
isFetching, failure, error, Site_Name, Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder
|
||||
} = this.props;
|
||||
if (nextState.user !== user) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.password !== password) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.code !== code) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.showTOTP !== showTOTP) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.isFetching !== isFetching) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.failure !== failure) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.Site_Name !== Site_Name) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.Accounts_EmailOrUsernamePlaceholder !== Accounts_EmailOrUsernamePlaceholder) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.Accounts_PasswordPlaceholder !== Accounts_PasswordPlaceholder) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps.error, error)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
|
|
|
@ -12,11 +12,11 @@ import RCActivityIndicator from '../../containers/ActivityIndicator';
|
|||
import I18n from '../../i18n';
|
||||
import { DEFAULT_HEADER } from '../../constants/headerOptions';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import database from '../../lib/realm';
|
||||
|
||||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
customEmojis: state.customEmojis,
|
||||
room: state.room,
|
||||
user: {
|
||||
id: state.login.user && state.login.user.id,
|
||||
username: state.login.user && state.login.user.username,
|
||||
|
@ -39,18 +39,16 @@ export default class MentionedMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
static propTypes = {
|
||||
rid: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
customEmojis: PropTypes.object
|
||||
customEmojis: PropTypes.object,
|
||||
room: PropTypes.object
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super('StarredMessagesView', props);
|
||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
|
||||
this.state = {
|
||||
loading: false,
|
||||
room: this.rooms[0],
|
||||
messages: []
|
||||
};
|
||||
}
|
||||
|
@ -60,12 +58,19 @@ export default class MentionedMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !equal(this.state, nextState);
|
||||
const { loading, messages } = this.state;
|
||||
if (nextState.loading !== loading) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.messages, messages)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
load = async() => {
|
||||
const {
|
||||
messages, total, loading, room
|
||||
messages, total, loading
|
||||
} = this.state;
|
||||
const { user } = this.props;
|
||||
if (messages.length === total || loading) {
|
||||
|
@ -75,6 +80,7 @@ export default class MentionedMessagesView extends LoggedView {
|
|||
this.setState({ loading: true });
|
||||
|
||||
try {
|
||||
const { room } = this.props;
|
||||
const result = await RocketChat.getMessages(
|
||||
room.rid,
|
||||
room.t,
|
||||
|
|
|
@ -7,6 +7,7 @@ import { connect, Provider } from 'react-redux';
|
|||
import { Navigation } from 'react-native-navigation';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import database from '../lib/realm';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
@ -85,6 +86,14 @@ export default class NewMessageView extends LoggedView {
|
|||
Navigation.events().bindComponent(this);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { search } = this.state;
|
||||
if (!equal(nextState.search, search)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.updateState.stop();
|
||||
this.data.removeAllListeners();
|
||||
|
@ -94,12 +103,10 @@ export default class NewMessageView extends LoggedView {
|
|||
this.search(text);
|
||||
}
|
||||
|
||||
onPressItem = (item) => {
|
||||
onPressItem = async(item) => {
|
||||
const { onPressItem } = this.props;
|
||||
this.dismiss();
|
||||
setTimeout(() => {
|
||||
await this.dismiss();
|
||||
onPressItem(item);
|
||||
}, 600);
|
||||
}
|
||||
|
||||
navigationButtonPressed = ({ buttonId }) => {
|
||||
|
@ -110,7 +117,7 @@ export default class NewMessageView extends LoggedView {
|
|||
|
||||
dismiss = () => {
|
||||
const { componentId } = this.props;
|
||||
Navigation.dismissModal(componentId);
|
||||
return Navigation.dismissModal(componentId);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text, ScrollView, Keyboard, Image, Alert, StyleSheet, TouchableOpacity
|
||||
Text, ScrollView, Keyboard, Image, StyleSheet, TouchableOpacity
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
|
@ -58,8 +58,7 @@ const styles = StyleSheet.create({
|
|||
const defaultServer = 'https://open.rocket.chat';
|
||||
|
||||
@connect(state => ({
|
||||
connecting: state.server.connecting,
|
||||
failure: state.server.failure
|
||||
connecting: state.server.connecting
|
||||
}), dispatch => ({
|
||||
connectServer: server => dispatch(serverRequest(server))
|
||||
}))
|
||||
|
@ -79,7 +78,6 @@ export default class NewServerView extends LoggedView {
|
|||
componentId: PropTypes.string,
|
||||
server: PropTypes.string,
|
||||
connecting: PropTypes.bool.isRequired,
|
||||
failure: PropTypes.bool.isRequired,
|
||||
connectServer: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
|
@ -103,11 +101,16 @@ export default class NewServerView extends LoggedView {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { failure } = this.props;
|
||||
if (nextProps.failure && nextProps.failure !== failure) {
|
||||
Alert.alert(I18n.t('Oops'), I18n.t('The_URL_is_invalid'));
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { text } = this.state;
|
||||
const { connecting } = this.props;
|
||||
if (nextState.text !== text) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.connecting !== connecting) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
|
|
@ -68,6 +68,10 @@ export default class OnboardingView extends LoggedView {
|
|||
EventEmitter.addEventListener('NewServer', this.handleNewServerEvent);
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const {
|
||||
selectServer, previousServer, currentServer, adding, finishAdd
|
||||
|
|
|
@ -13,7 +13,6 @@ import RCActivityIndicator from '../../containers/ActivityIndicator';
|
|||
import I18n from '../../i18n';
|
||||
import { DEFAULT_HEADER } from '../../constants/headerOptions';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import database from '../../lib/realm';
|
||||
|
||||
const PIN_INDEX = 0;
|
||||
const CANCEL_INDEX = 1;
|
||||
|
@ -22,6 +21,7 @@ const options = [I18n.t('Unpin'), I18n.t('Cancel')];
|
|||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
customEmojis: state.customEmojis,
|
||||
room: state.room,
|
||||
user: {
|
||||
id: state.login.user && state.login.user.id,
|
||||
username: state.login.user && state.login.user.username,
|
||||
|
@ -44,18 +44,16 @@ export default class PinnedMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
static propTypes = {
|
||||
rid: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
customEmojis: PropTypes.object
|
||||
customEmojis: PropTypes.object,
|
||||
room: PropTypes.object
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super('PinnedMessagesView', props);
|
||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
|
||||
this.state = {
|
||||
loading: false,
|
||||
room: this.rooms[0],
|
||||
messages: []
|
||||
};
|
||||
}
|
||||
|
@ -65,7 +63,14 @@ export default class PinnedMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !equal(this.state, nextState);
|
||||
const { loading, messages } = this.state;
|
||||
if (nextState.loading !== loading) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.messages, messages)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
onLongPress = (message) => {
|
||||
|
@ -101,7 +106,7 @@ export default class PinnedMessagesView extends LoggedView {
|
|||
|
||||
load = async() => {
|
||||
const {
|
||||
messages, total, loading, room
|
||||
messages, total, loading
|
||||
} = this.state;
|
||||
if (messages.length === total || loading) {
|
||||
return;
|
||||
|
@ -110,6 +115,7 @@ export default class PinnedMessagesView extends LoggedView {
|
|||
this.setState({ loading: true });
|
||||
|
||||
try {
|
||||
const { room } = this.props;
|
||||
const result = await RocketChat.getMessages(room.rid, room.t, { pinned: true }, messages.length);
|
||||
if (result.success) {
|
||||
this.setState(prevState => ({
|
||||
|
|
|
@ -11,6 +11,7 @@ import ImagePicker from 'react-native-image-crop-picker';
|
|||
import RNPickerSelect from 'react-native-picker-select';
|
||||
import { Navigation } from 'react-native-navigation';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import LoggedView from '../View';
|
||||
import KeyboardView from '../../presentation/KeyboardView';
|
||||
|
@ -118,6 +119,16 @@ export default class ProfileView extends LoggedView {
|
|||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (!equal(nextState, this.state)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps, this.props)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ let TermsServiceView = null;
|
|||
let PrivacyPolicyView = null;
|
||||
let LegalView = null;
|
||||
|
||||
const shouldUpdateState = ['name', 'email', 'password', 'username', 'saving'];
|
||||
|
||||
@connect(null, dispatch => ({
|
||||
loginRequest: params => dispatch(loginRequestAction(params))
|
||||
}))
|
||||
|
@ -67,6 +69,11 @@ export default class RegisterView extends LoggedView {
|
|||
}, 600);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
return shouldUpdateState.some(key => nextState[key] !== this.state[key]);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { componentId, Site_Name } = this.props;
|
||||
if (Site_Name && prevProps.Site_Name !== Site_Name) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { connect, Provider } from 'react-redux';
|
|||
import { Navigation } from 'react-native-navigation';
|
||||
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import { leaveRoom as leaveRoomAction } from '../../actions/room';
|
||||
import LoggedView from '../View';
|
||||
|
@ -33,7 +34,8 @@ const modules = {};
|
|||
@connect(state => ({
|
||||
userId: state.login.user && state.login.user.id,
|
||||
username: state.login.user && state.login.user.username,
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
room: state.room
|
||||
}), dispatch => ({
|
||||
leaveRoom: (rid, t) => dispatch(leaveRoomAction(rid, t))
|
||||
}))
|
||||
|
@ -58,15 +60,16 @@ export default class RoomActionsView extends LoggedView {
|
|||
componentId: PropTypes.string,
|
||||
userId: PropTypes.string,
|
||||
username: PropTypes.string,
|
||||
room: PropTypes.object,
|
||||
leaveRoom: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super('RoomActionsView', props);
|
||||
const { rid } = props;
|
||||
const { rid, room } = props;
|
||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
this.state = {
|
||||
room: this.rooms[0] || {},
|
||||
room,
|
||||
membersCount: 0,
|
||||
member: {},
|
||||
joined: false,
|
||||
|
@ -86,14 +89,34 @@ export default class RoomActionsView extends LoggedView {
|
|||
} catch (error) {
|
||||
console.log('RoomActionsView -> getRoomCounters -> error', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (room.t === 'd') {
|
||||
} else if (room.t === 'd') {
|
||||
this.updateRoomMember();
|
||||
}
|
||||
this.rooms.addListener(this.updateRoom);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {
|
||||
room, membersCount, member, joined, canViewMembers
|
||||
} = this.state;
|
||||
if (nextState.membersCount !== membersCount) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.joined !== joined) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.canViewMembers !== canViewMembers) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.room, room)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.member, member)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.rooms.removeAllListeners();
|
||||
}
|
||||
|
@ -109,7 +132,8 @@ export default class RoomActionsView extends LoggedView {
|
|||
Navigation.push(componentId, {
|
||||
component: {
|
||||
name: item.route,
|
||||
passProps: item.params
|
||||
passProps: item.params,
|
||||
options: item.navigationOptions
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -156,11 +180,20 @@ export default class RoomActionsView extends LoggedView {
|
|||
}
|
||||
|
||||
get sections() {
|
||||
const { room, membersCount, canViewMembers } = this.state;
|
||||
const {
|
||||
room, membersCount, canViewMembers, joined
|
||||
} = this.state;
|
||||
const {
|
||||
rid, t, blocker, notifications
|
||||
} = room;
|
||||
|
||||
const notificationsAction = {
|
||||
icon: `ios-notifications${ notifications ? '' : '-off' }`,
|
||||
name: I18n.t(`${ notifications ? 'Enable' : 'Disable' }_notifications`),
|
||||
event: () => this.toggleNotifications(),
|
||||
testID: 'room-actions-notifications'
|
||||
};
|
||||
|
||||
const sections = [{
|
||||
data: [{
|
||||
icon: 'ios-star',
|
||||
|
@ -193,7 +226,6 @@ export default class RoomActionsView extends LoggedView {
|
|||
icon: 'ios-attach',
|
||||
name: I18n.t('Files'),
|
||||
route: 'RoomFilesView',
|
||||
params: { rid },
|
||||
testID: 'room-actions-files',
|
||||
require: () => require('../RoomFilesView').default
|
||||
},
|
||||
|
@ -201,7 +233,6 @@ export default class RoomActionsView extends LoggedView {
|
|||
icon: 'ios-at',
|
||||
name: I18n.t('Mentions'),
|
||||
route: 'MentionedMessagesView',
|
||||
params: { rid },
|
||||
testID: 'room-actions-mentioned',
|
||||
require: () => require('../MentionedMessagesView').default
|
||||
},
|
||||
|
@ -209,7 +240,6 @@ export default class RoomActionsView extends LoggedView {
|
|||
icon: 'ios-star',
|
||||
name: I18n.t('Starred'),
|
||||
route: 'StarredMessagesView',
|
||||
params: { rid },
|
||||
testID: 'room-actions-starred',
|
||||
require: () => require('../StarredMessagesView').default
|
||||
},
|
||||
|
@ -231,7 +261,6 @@ export default class RoomActionsView extends LoggedView {
|
|||
icon: 'ios-pin',
|
||||
name: I18n.t('Pinned'),
|
||||
route: 'PinnedMessagesView',
|
||||
params: { rid },
|
||||
testID: 'room-actions-pinned',
|
||||
require: () => require('../PinnedMessagesView').default
|
||||
},
|
||||
|
@ -242,12 +271,6 @@ export default class RoomActionsView extends LoggedView {
|
|||
params: { rid },
|
||||
testID: 'room-actions-snippeted',
|
||||
require: () => require('../SnippetedMessagesView').default
|
||||
},
|
||||
{
|
||||
icon: `ios-notifications${ notifications ? '' : '-off' }`,
|
||||
name: I18n.t(`${ notifications ? 'Enable' : 'Disable' }_notifications`),
|
||||
event: () => this.toggleNotifications(),
|
||||
testID: 'room-actions-notifications'
|
||||
}
|
||||
],
|
||||
renderItem: this.renderItem
|
||||
|
@ -266,6 +289,7 @@ export default class RoomActionsView extends LoggedView {
|
|||
],
|
||||
renderItem: this.renderItem
|
||||
});
|
||||
sections[2].data.push(notificationsAction);
|
||||
} else if (t === 'c' || t === 'p') {
|
||||
const actions = [];
|
||||
|
||||
|
@ -273,7 +297,7 @@ export default class RoomActionsView extends LoggedView {
|
|||
actions.push({
|
||||
icon: 'ios-people',
|
||||
name: I18n.t('Members'),
|
||||
description: `${ membersCount } ${ I18n.t('members') }`,
|
||||
description: membersCount > 0 ? `${ membersCount } ${ I18n.t('members') }` : null,
|
||||
route: 'RoomMembersView',
|
||||
params: { rid },
|
||||
testID: 'room-actions-members',
|
||||
|
@ -290,11 +314,21 @@ export default class RoomActionsView extends LoggedView {
|
|||
nextAction: 'ADD_USER',
|
||||
rid
|
||||
},
|
||||
navigationOptions: {
|
||||
topBar: {
|
||||
title: {
|
||||
text: I18n.t('Add_user')
|
||||
}
|
||||
}
|
||||
},
|
||||
testID: 'room-actions-add-user',
|
||||
require: () => require('../SelectedUsersView').default
|
||||
});
|
||||
}
|
||||
sections[2].data = [...actions, ...sections[2].data];
|
||||
|
||||
if (joined) {
|
||||
sections[2].data.push(notificationsAction);
|
||||
sections.push({
|
||||
data: [
|
||||
{
|
||||
|
@ -308,11 +342,14 @@ export default class RoomActionsView extends LoggedView {
|
|||
renderItem: this.renderItem
|
||||
});
|
||||
}
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
updateRoom = () => {
|
||||
this.setState({ room: this.rooms[0] || {} });
|
||||
if (this.rooms.length > 0) {
|
||||
this.setState({ room: JSON.parse(JSON.stringify(this.rooms[0])) });
|
||||
}
|
||||
}
|
||||
|
||||
updateRoomMember = async() => {
|
||||
|
@ -322,10 +359,10 @@ export default class RoomActionsView extends LoggedView {
|
|||
|
||||
try {
|
||||
const member = await RocketChat.getRoomMember(rid, userId);
|
||||
this.setState({ member });
|
||||
this.setState({ member: member || {} });
|
||||
} catch (e) {
|
||||
log('RoomActions updateRoomMember', e);
|
||||
return {};
|
||||
this.setState({ member: {} });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,12 +11,12 @@ import Message from '../../containers/message/Message';
|
|||
import RCActivityIndicator from '../../containers/ActivityIndicator';
|
||||
import I18n from '../../i18n';
|
||||
import { DEFAULT_HEADER } from '../../constants/headerOptions';
|
||||
import database from '../../lib/realm';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
|
||||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
customEmojis: state.customEmojis,
|
||||
room: state.room,
|
||||
user: {
|
||||
id: state.login.user && state.login.user.id,
|
||||
username: state.login.user && state.login.user.username,
|
||||
|
@ -39,18 +39,16 @@ export default class RoomFilesView extends LoggedView {
|
|||
}
|
||||
|
||||
static propTypes = {
|
||||
rid: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
customEmojis: PropTypes.object
|
||||
customEmojis: PropTypes.object,
|
||||
room: PropTypes.object
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super('RoomFilesView', props);
|
||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
|
||||
this.state = {
|
||||
loading: false,
|
||||
room: this.rooms[0],
|
||||
messages: []
|
||||
};
|
||||
}
|
||||
|
@ -60,12 +58,19 @@ export default class RoomFilesView extends LoggedView {
|
|||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !equal(this.state, nextState);
|
||||
const { loading, messages } = this.state;
|
||||
if (nextState.loading !== loading) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.messages, messages)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
load = async() => {
|
||||
const {
|
||||
messages, total, loading, room
|
||||
messages, total, loading
|
||||
} = this.state;
|
||||
if (messages.length === total || loading) {
|
||||
return;
|
||||
|
@ -74,6 +79,7 @@ export default class RoomFilesView extends LoggedView {
|
|||
this.setState({ loading: true });
|
||||
|
||||
try {
|
||||
const { room } = this.props;
|
||||
const result = await RocketChat.getFiles(room.rid, room.t, messages.length);
|
||||
if (result.success) {
|
||||
this.setState(prevState => ({
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import { eraseRoom as eraseRoomAction } from '../../actions/room';
|
||||
import LoggedView from '../View';
|
||||
|
@ -67,7 +68,7 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
this.permissions = {};
|
||||
this.state = {
|
||||
room: this.rooms[0] || {},
|
||||
room: JSON.parse(JSON.stringify(this.rooms[0] || {})),
|
||||
name: '',
|
||||
description: '',
|
||||
topic: '',
|
||||
|
@ -90,12 +91,26 @@ export default class RoomInfoEditView extends LoggedView {
|
|||
this.permissions = RocketChat.hasPermission(PERMISSIONS_ARRAY, room.rid);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { room } = this.state;
|
||||
if (!equal(nextState, this.state)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.room, room)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps, this.props)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.rooms.removeAllListeners();
|
||||
}
|
||||
|
||||
updateRoom = () => {
|
||||
this.setState({ room: this.rooms[0] || {} });
|
||||
this.setState({ room: JSON.parse(JSON.stringify(this.rooms[0] || {})) });
|
||||
}
|
||||
|
||||
init = () => {
|
||||
|
|
|
@ -6,6 +6,7 @@ import moment from 'moment';
|
|||
import { Navigation } from 'react-native-navigation';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import LoggedView from '../View';
|
||||
import Status from '../../containers/status';
|
||||
|
@ -40,9 +41,10 @@ let RoomInfoEditView = null;
|
|||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
userId: state.login.user && state.login.user.id,
|
||||
activeUsers: state.activeUsers,
|
||||
activeUsers: state.activeUsers, // TODO: remove it
|
||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||
allRoles: state.roles
|
||||
allRoles: state.roles,
|
||||
room: state.room
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class RoomInfoView extends LoggedView {
|
||||
|
@ -66,29 +68,34 @@ export default class RoomInfoView extends LoggedView {
|
|||
baseUrl: PropTypes.string,
|
||||
activeUsers: PropTypes.object,
|
||||
Message_TimeFormat: PropTypes.string,
|
||||
allRoles: PropTypes.object
|
||||
allRoles: PropTypes.object,
|
||||
room: PropTypes.object
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super('RoomInfoView', props);
|
||||
const { rid } = props;
|
||||
const { rid, room } = props;
|
||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
this.sub = {
|
||||
unsubscribe: () => {}
|
||||
};
|
||||
this.state = {
|
||||
room: {},
|
||||
room,
|
||||
roomUser: {},
|
||||
roles: []
|
||||
};
|
||||
Navigation.events().bindComponent(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateRoom();
|
||||
async componentDidMount() {
|
||||
this.rooms.addListener(this.updateRoom);
|
||||
|
||||
const [room] = this.rooms;
|
||||
let room = {};
|
||||
if (this.rooms.length > 0) {
|
||||
room = this.rooms[0]; // eslint-disable-line prefer-destructuring
|
||||
} else {
|
||||
room = this.state.room; // eslint-disable-line
|
||||
}
|
||||
const { componentId } = this.props;
|
||||
const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
|
||||
if (permissions[PERMISSION_EDIT_ROOM]) {
|
||||
|
@ -102,6 +109,57 @@ export default class RoomInfoView extends LoggedView {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
// get user of room
|
||||
if (room) {
|
||||
if (room.t === 'd') {
|
||||
try {
|
||||
const { userId, activeUsers } = this.props;
|
||||
const roomUser = await RocketChat.getRoomMember(room.rid, userId);
|
||||
this.setState({ roomUser: roomUser || {} });
|
||||
const username = room.name;
|
||||
|
||||
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
|
||||
this.getFullUserData(username);
|
||||
}
|
||||
|
||||
// get all users roles
|
||||
// needs to be changed by a better method
|
||||
const allUsersRoles = await RocketChat.getUserRoles();
|
||||
const userRoles = allUsersRoles.find(user => user.username === username);
|
||||
if (userRoles) {
|
||||
this.setState({ roles: userRoles.roles || [] });
|
||||
}
|
||||
} catch (e) {
|
||||
log('RoomInfoView.componentDidMount', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {
|
||||
room, roomUser, roles
|
||||
} = this.state;
|
||||
const { activeUsers } = this.props;
|
||||
if (!equal(nextState.room, room)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.roomUser, roomUser)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.roles, roles)) {
|
||||
return true;
|
||||
}
|
||||
if (roomUser._id) {
|
||||
if (nextProps.activeUsers[roomUser._id] !== activeUsers[roomUser._id]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -143,38 +201,9 @@ export default class RoomInfoView extends LoggedView {
|
|||
return t === 'd';
|
||||
}
|
||||
|
||||
updateRoom = async() => {
|
||||
const { userId, activeUsers } = this.props;
|
||||
|
||||
const [room] = this.rooms;
|
||||
this.setState({ room });
|
||||
|
||||
// get user of room
|
||||
if (room) {
|
||||
if (room.t === 'd') {
|
||||
try {
|
||||
const roomUser = await RocketChat.getRoomMember(room.rid, userId);
|
||||
this.setState({ roomUser: roomUser || {} });
|
||||
const username = room.name;
|
||||
|
||||
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
|
||||
this.getFullUserData(username);
|
||||
}
|
||||
|
||||
// get all users roles
|
||||
// needs to be changed by a better method
|
||||
const allUsersRoles = await RocketChat.getUserRoles();
|
||||
const userRoles = allUsersRoles.find(user => user.username === username);
|
||||
if (userRoles) {
|
||||
this.setState({ roles: userRoles.roles || [] });
|
||||
}
|
||||
} catch (e) {
|
||||
log('RoomInfoView.componentDidMount', e);
|
||||
}
|
||||
}
|
||||
updateRoom = () => {
|
||||
if (this.rooms.length > 0) {
|
||||
this.setState({ room: JSON.parse(JSON.stringify(this.rooms[0])) });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import ActionSheet from 'react-native-actionsheet';
|
|||
import { connect } from 'react-redux';
|
||||
import { Navigation } from 'react-native-navigation';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import LoggedView from '../View';
|
||||
import styles from './styles';
|
||||
|
@ -21,7 +22,8 @@ import SearchBox from '../../containers/SearchBox';
|
|||
import { DEFAULT_HEADER } from '../../constants/headerOptions';
|
||||
|
||||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
room: state.room
|
||||
}))
|
||||
/** @extends React.Component */
|
||||
export default class RoomMembersView extends LoggedView {
|
||||
|
@ -48,7 +50,8 @@ export default class RoomMembersView extends LoggedView {
|
|||
componentId: PropTypes.string,
|
||||
rid: PropTypes.string,
|
||||
members: PropTypes.array,
|
||||
baseUrl: PropTypes.string
|
||||
baseUrl: PropTypes.string,
|
||||
room: PropTypes.object
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -57,7 +60,7 @@ export default class RoomMembersView extends LoggedView {
|
|||
this.CANCEL_INDEX = 0;
|
||||
this.MUTE_INDEX = 1;
|
||||
this.actionSheetOptions = [''];
|
||||
const { rid, members } = props;
|
||||
const { rid, members, room } = props;
|
||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
|
||||
this.permissions = RocketChat.hasPermission(['mute-user'], rid);
|
||||
this.state = {
|
||||
|
@ -67,7 +70,8 @@ export default class RoomMembersView extends LoggedView {
|
|||
members,
|
||||
membersFiltered: [],
|
||||
userLongPressed: {},
|
||||
room: this.rooms[0] || {}
|
||||
room,
|
||||
options: []
|
||||
};
|
||||
Navigation.events().bindComponent(this);
|
||||
}
|
||||
|
@ -77,6 +81,34 @@ export default class RoomMembersView extends LoggedView {
|
|||
this.rooms.addListener(this.updateRoom);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {
|
||||
allUsers, filtering, members, membersFiltered, userLongPressed, room, options
|
||||
} = this.state;
|
||||
if (nextState.allUsers !== allUsers) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.filtering !== filtering) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.members, members)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.options, options)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.membersFiltered, membersFiltered)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.userLongPressed, userLongPressed)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.room.muted, room.muted)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.rooms.removeAllListeners();
|
||||
}
|
||||
|
@ -118,11 +150,11 @@ export default class RoomMembersView extends LoggedView {
|
|||
try {
|
||||
const subscriptions = database.objects('subscriptions').filtered('name = $0', item.username);
|
||||
if (subscriptions.length) {
|
||||
this.goRoom({ rid: subscriptions[0].rid });
|
||||
this.goRoom({ rid: subscriptions[0].rid, name: item.username });
|
||||
} else {
|
||||
const result = await RocketChat.createDirectMessage(item.username);
|
||||
if (result.success) {
|
||||
this.goRoom({ rid: result.room._id });
|
||||
this.goRoom({ rid: result.room._id, name: item.username });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -134,22 +166,26 @@ export default class RoomMembersView extends LoggedView {
|
|||
if (!this.permissions['mute-user']) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { room } = this.state;
|
||||
const { muted } = room;
|
||||
|
||||
this.actionSheetOptions = [I18n.t('Cancel')];
|
||||
const options = [I18n.t('Cancel')];
|
||||
const userIsMuted = !!muted.find(m => m.value === user.username);
|
||||
user.muted = userIsMuted;
|
||||
if (userIsMuted) {
|
||||
this.actionSheetOptions.push(I18n.t('Unmute'));
|
||||
options.push(I18n.t('Unmute'));
|
||||
} else {
|
||||
this.actionSheetOptions.push(I18n.t('Mute'));
|
||||
options.push(I18n.t('Mute'));
|
||||
}
|
||||
this.setState({ userLongPressed: user });
|
||||
this.setState({ userLongPressed: user, options });
|
||||
Vibration.vibrate(50);
|
||||
if (this.actionSheet && this.actionSheet.show) {
|
||||
this.actionSheet.show();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('onLongPressUser -> catch -> error', error);
|
||||
}
|
||||
}
|
||||
|
||||
fetchMembers = async(status) => {
|
||||
|
@ -159,19 +195,21 @@ export default class RoomMembersView extends LoggedView {
|
|||
this.setState({ allUsers: status, members });
|
||||
}
|
||||
|
||||
updateRoom = async() => {
|
||||
updateRoom = () => {
|
||||
if (this.rooms.length > 0) {
|
||||
const [room] = this.rooms;
|
||||
await this.setState({ room });
|
||||
this.setState({ room });
|
||||
}
|
||||
}
|
||||
|
||||
goRoom = async({ rid }) => {
|
||||
goRoom = async({ rid, name }) => {
|
||||
const { componentId } = this.props;
|
||||
await Navigation.popToRoot(componentId);
|
||||
Navigation.push('RoomsListView', {
|
||||
component: {
|
||||
name: 'RoomView',
|
||||
passProps: {
|
||||
rid
|
||||
rid, name, t: 'd'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -219,7 +257,9 @@ export default class RoomMembersView extends LoggedView {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { filtering, members, membersFiltered } = this.state;
|
||||
const {
|
||||
filtering, members, membersFiltered, options
|
||||
} = this.state;
|
||||
return (
|
||||
<SafeAreaView style={styles.list} testID='room-members-view' forceInset={{ bottom: 'never' }}>
|
||||
<FlatList
|
||||
|
@ -234,7 +274,7 @@ export default class RoomMembersView extends LoggedView {
|
|||
<ActionSheet
|
||||
ref={o => this.actionSheet = o}
|
||||
title={I18n.t('Actions')}
|
||||
options={this.actionSheetOptions}
|
||||
options={options}
|
||||
cancelButtonIndex={this.CANCEL_INDEX}
|
||||
onPress={this.handleActionPress}
|
||||
/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
View, Text, StyleSheet, Image, Platform, LayoutAnimation
|
||||
|
@ -81,7 +81,7 @@ const styles = StyleSheet.create({
|
|||
status
|
||||
};
|
||||
})
|
||||
export default class RoomHeaderView extends PureComponent {
|
||||
export default class RoomHeaderView extends Component {
|
||||
static propTypes = {
|
||||
title: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
|
@ -90,12 +90,39 @@ export default class RoomHeaderView extends PureComponent {
|
|||
status: PropTypes.string
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const {
|
||||
type, title, status, usersTyping, window
|
||||
} = this.props;
|
||||
if (nextProps.type !== type) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.title !== title) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.status !== status) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.window.width !== window.width) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.window.height !== window.height) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps.usersTyping, usersTyping)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (isIOS()) {
|
||||
const { usersTyping } = this.props;
|
||||
if (!equal(prevProps.usersTyping, usersTyping)) {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get typing() {
|
||||
const { usersTyping } = this.props;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { ListView as OldList } from 'realm/react-native';
|
||||
import React from 'react';
|
||||
import { ScrollView, ListView as OldList2, ImageBackground } from 'react-native';
|
||||
import {
|
||||
ScrollView, ListView as OldList2, ImageBackground, ActivityIndicator
|
||||
} from 'react-native';
|
||||
import moment from 'moment';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -9,7 +11,9 @@ import Separator from './Separator';
|
|||
import styles from './styles';
|
||||
import database from '../../lib/realm';
|
||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
import throttle from '../../utils/throttle';
|
||||
import debounce from '../../utils/debounce';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import log from '../../utils/log';
|
||||
|
||||
const DEFAULT_SCROLL_CALLBACK_THROTTLE = 100;
|
||||
|
||||
|
@ -24,34 +28,38 @@ export class DataSource extends OldList.DataSource {
|
|||
}
|
||||
}
|
||||
|
||||
const ds = new DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id || r1._updatedAt.toISOString() !== r2._updatedAt.toISOString() });
|
||||
const ds = new DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
|
||||
|
||||
export class List extends React.Component {
|
||||
static propTypes = {
|
||||
onEndReached: PropTypes.func,
|
||||
renderFooter: PropTypes.func,
|
||||
renderRow: PropTypes.func,
|
||||
room: PropTypes.string,
|
||||
end: PropTypes.bool,
|
||||
loadingMore: PropTypes.bool
|
||||
room: PropTypes.object
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.data = database
|
||||
.objects('messages')
|
||||
.filtered('rid = $0', props.room)
|
||||
.filtered('rid = $0', props.room.rid)
|
||||
.sorted('ts', true);
|
||||
this.state = {
|
||||
loading: true,
|
||||
loadingMore: false,
|
||||
end: false
|
||||
};
|
||||
this.dataSource = ds.cloneWithRows(this.data);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateState();
|
||||
this.data.addListener(this.updateState);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const { end, loadingMore } = this.props;
|
||||
return end !== nextProps.end || loadingMore !== nextProps.loadingMore;
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { loadingMore, loading, end } = this.state;
|
||||
return end !== nextState.end || loadingMore !== nextState.loadingMore || loading !== nextState.loading;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -60,16 +68,39 @@ export class List extends React.Component {
|
|||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
updateState = throttle(() => {
|
||||
// this.setState({
|
||||
updateState = debounce(() => {
|
||||
this.setState({ loading: true });
|
||||
this.dataSource = this.dataSource.cloneWithRows(this.data);
|
||||
// LayoutAnimation.easeInEaseOut();
|
||||
this.forceUpdate();
|
||||
// });
|
||||
}, 1000);
|
||||
this.setState({ loading: false });
|
||||
}, 300);
|
||||
|
||||
onEndReached = async() => {
|
||||
const { loadingMore, end } = this.state;
|
||||
if (loadingMore || end || this.data.length < 50) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ loadingMore: true });
|
||||
const { room } = this.props;
|
||||
try {
|
||||
const result = await RocketChat.loadMessagesForRoom({ rid: room.rid, t: room.t, latest: this.data[this.data.length - 1].ts });
|
||||
this.setState({ end: result.length < 50, loadingMore: false });
|
||||
} catch (e) {
|
||||
this.setState({ loadingMore: false });
|
||||
log('ListView.onEndReached', e);
|
||||
}
|
||||
}
|
||||
|
||||
renderFooter = () => {
|
||||
const { loadingMore, loading } = this.state;
|
||||
if (loadingMore || loading) {
|
||||
return <ActivityIndicator style={styles.loadingMore} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { renderFooter, onEndReached, renderRow } = this.props;
|
||||
const { renderRow } = this.props;
|
||||
|
||||
return (
|
||||
<ListView
|
||||
|
@ -78,8 +109,8 @@ export class List extends React.Component {
|
|||
data={this.data}
|
||||
keyExtractor={item => item._id}
|
||||
onEndReachedThreshold={100}
|
||||
renderFooter={renderFooter}
|
||||
onEndReached={() => onEndReached(this.data[this.data.length - 1])}
|
||||
renderFooter={this.renderFooter}
|
||||
onEndReached={this.onEndReached}
|
||||
dataSource={this.dataSource}
|
||||
renderRow={(item, previousItem) => renderRow(item, previousItem)}
|
||||
initialListSize={1}
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
import PropTypes from 'prop-types';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import { responsive } from 'react-native-responsive-ui';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import database from '../../lib/realm';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
|
@ -84,6 +85,18 @@ export default class UploadProgress extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { uploads } = this.state;
|
||||
const { window } = this.props;
|
||||
if (nextProps.window.width !== window.width) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.uploads, uploads)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.uploads.removeAllListeners();
|
||||
}
|
||||
|
@ -107,14 +120,15 @@ export default class UploadProgress extends Component {
|
|||
database.write(() => {
|
||||
item.error = false;
|
||||
});
|
||||
await RocketChat.sendFileMessage(rid, JSON.parse(JSON.stringify(item)));
|
||||
await RocketChat.sendFileMessage(rid, item);
|
||||
} catch (e) {
|
||||
log('UploadProgess.tryAgain', e);
|
||||
}
|
||||
}
|
||||
|
||||
updateUploads = () => {
|
||||
this.setState({ uploads: this.uploads });
|
||||
const uploads = this.uploads.map(item => JSON.parse(JSON.stringify(item)));
|
||||
this.setState({ uploads });
|
||||
}
|
||||
|
||||
renderItemContent = (item) => {
|
||||
|
|
|
@ -84,6 +84,8 @@ export default class RoomView extends LoggedView {
|
|||
token: PropTypes.string.isRequired
|
||||
}),
|
||||
rid: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
t: PropTypes.string,
|
||||
showActions: PropTypes.bool,
|
||||
showErrorActions: PropTypes.bool,
|
||||
actionMessage: PropTypes.object,
|
||||
|
@ -100,32 +102,28 @@ export default class RoomView extends LoggedView {
|
|||
this.state = {
|
||||
loaded: false,
|
||||
joined: this.rooms.length > 0,
|
||||
room: {},
|
||||
end: false,
|
||||
loadingMore: false
|
||||
room: {}
|
||||
};
|
||||
this.focused = true;
|
||||
this.onReactionPress = this.onReactionPress.bind(this);
|
||||
Navigation.events().bindComponent(this);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
componentDidMount() {
|
||||
if (this.rooms.length === 0 && this.rid) {
|
||||
const result = await RocketChat.getRoomInfo(this.rid);
|
||||
if (result.success) {
|
||||
const { room } = result;
|
||||
const { rid, name, t } = this.props;
|
||||
this.setState(
|
||||
{ room: { rid: room._id, t: room.t, name: room.name } },
|
||||
{ room: { rid, name, t } },
|
||||
() => this.updateRoom()
|
||||
);
|
||||
}
|
||||
}
|
||||
this.rooms.addListener(this.updateRoom);
|
||||
this.internalSetState({ loaded: true });
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {
|
||||
room, loaded, joined, end, loadingMore
|
||||
room, loaded, joined
|
||||
} = this.state;
|
||||
const { showActions, showErrorActions, appState } = this.props;
|
||||
|
||||
|
@ -143,10 +141,6 @@ export default class RoomView extends LoggedView {
|
|||
return true;
|
||||
} else if (joined !== nextState.joined) {
|
||||
return true;
|
||||
} else if (end !== nextState.end) {
|
||||
return true;
|
||||
} else if (loadingMore !== nextState.loadingMore) {
|
||||
return true;
|
||||
} else if (showActions !== nextProps.showActions) {
|
||||
return true;
|
||||
} else if (showErrorActions !== nextProps.showErrorActions) {
|
||||
|
@ -187,32 +181,18 @@ export default class RoomView extends LoggedView {
|
|||
|
||||
componentWillUnmount() {
|
||||
const { closeRoom } = this.props;
|
||||
this.rooms.removeAllListeners();
|
||||
if (this.onEndReached && this.onEndReached.stop) {
|
||||
this.onEndReached.stop();
|
||||
}
|
||||
closeRoom();
|
||||
this.rooms.removeAllListeners();
|
||||
}
|
||||
|
||||
onEndReached = async(lastRowData) => {
|
||||
if (!lastRowData) {
|
||||
return;
|
||||
// eslint-disable-next-line
|
||||
componentDidAppear() {
|
||||
this.focused = true;
|
||||
}
|
||||
|
||||
const { loadingMore, end } = this.state;
|
||||
if (loadingMore || end) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ loadingMore: true });
|
||||
const { room } = this.state;
|
||||
try {
|
||||
const result = await RocketChat.loadMessagesForRoom({ rid: this.rid, t: room.t, latest: lastRowData.ts });
|
||||
this.internalSetState({ end: result.length < 50, loadingMore: false });
|
||||
} catch (e) {
|
||||
this.internalSetState({ loadingMore: false });
|
||||
log('RoomView.onEndReached', e);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
componentDidDisappear() {
|
||||
this.focused = false;
|
||||
}
|
||||
|
||||
onMessageLongPress = (message) => {
|
||||
|
@ -269,15 +249,19 @@ export default class RoomView extends LoggedView {
|
|||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
updateRoom = () => {
|
||||
const { openRoom, setLastOpen } = this.props;
|
||||
|
||||
if (!this.focused) {
|
||||
return;
|
||||
}
|
||||
if (this.rooms.length > 0) {
|
||||
const { room: prevRoom } = this.state;
|
||||
const room = JSON.parse(JSON.stringify(this.rooms[0] || {}));
|
||||
this.internalSetState({ room });
|
||||
|
||||
if (!prevRoom.rid) {
|
||||
if (!prevRoom._id) {
|
||||
openRoom({
|
||||
...room
|
||||
});
|
||||
|
@ -289,10 +273,12 @@ export default class RoomView extends LoggedView {
|
|||
}
|
||||
} else {
|
||||
const { room } = this.state;
|
||||
if (room.rid) {
|
||||
openRoom(room);
|
||||
this.internalSetState({ joined: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage = (message) => {
|
||||
const { setLastOpen } = this.props;
|
||||
|
@ -370,7 +356,7 @@ export default class RoomView extends LoggedView {
|
|||
|
||||
if (!joined) {
|
||||
return (
|
||||
<View style={styles.joinRoomContainer} key='room-view-join'>
|
||||
<View style={styles.joinRoomContainer} key='room-view-join' testID='room-view-join'>
|
||||
<Text style={styles.previewMode}>{I18n.t('You_are_in_preview_mode')}</Text>
|
||||
<RectButton
|
||||
onPress={this.joinRoom}
|
||||
|
@ -378,7 +364,7 @@ export default class RoomView extends LoggedView {
|
|||
activeOpacity={0.5}
|
||||
underlayColor='#fff'
|
||||
>
|
||||
<Text style={styles.joinRoomText}>{I18n.t('Join')}</Text>
|
||||
<Text style={styles.joinRoomText} testID='room-view-join-button'>{I18n.t('Join')}</Text>
|
||||
</RectButton>
|
||||
</View>
|
||||
);
|
||||
|
@ -400,28 +386,16 @@ export default class RoomView extends LoggedView {
|
|||
return <MessageBox key='room-view-messagebox' onSubmit={this.sendMessage} rid={this.rid} />;
|
||||
};
|
||||
|
||||
renderHeader = () => {
|
||||
const { loadingMore } = this.state;
|
||||
if (loadingMore) {
|
||||
return <ActivityIndicator style={styles.loadingMore} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderList = () => {
|
||||
const { loaded, end, loadingMore } = this.state;
|
||||
if (!loaded) {
|
||||
const { loaded, room } = this.state;
|
||||
if (!loaded || !room.rid) {
|
||||
return <ActivityIndicator style={styles.loading} />;
|
||||
}
|
||||
return (
|
||||
[
|
||||
<List
|
||||
key='room-view-messages'
|
||||
end={end}
|
||||
loadingMore={loadingMore}
|
||||
room={this.rid}
|
||||
renderFooter={this.renderHeader}
|
||||
onEndReached={this.onEndReached}
|
||||
room={room}
|
||||
renderRow={this.renderItem}
|
||||
/>,
|
||||
this.renderFooter()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Component } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
@ -18,7 +18,7 @@ import Header from './Header';
|
|||
closeSort: () => dispatch(closeSortDropdown()),
|
||||
setSearch: searchText => dispatch(setSearchAction(searchText))
|
||||
}))
|
||||
export default class RoomsListHeaderView extends Component {
|
||||
export default class RoomsListHeaderView extends PureComponent {
|
||||
static propTypes = {
|
||||
showServerDropdown: PropTypes.bool,
|
||||
showSortDropdown: PropTypes.bool,
|
||||
|
|
|
@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
|
|||
import { connect } from 'react-redux';
|
||||
import { Navigation } from 'react-native-navigation';
|
||||
import * as SDK from '@rocket.chat/sdk';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms';
|
||||
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
|
||||
|
@ -39,10 +40,12 @@ export default class ServerDropdown extends Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.servers = database.databases.serversDB.objects('servers');
|
||||
this.state = {
|
||||
servers: []
|
||||
servers: this.servers
|
||||
};
|
||||
this.animatedValue = new Animated.Value(0);
|
||||
this.servers.addListener(this.updateState);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -51,12 +54,25 @@ export default class ServerDropdown extends Component {
|
|||
{
|
||||
toValue: 1,
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.ease,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
useNativeDriver: true
|
||||
},
|
||||
).start();
|
||||
this.servers = database.databases.serversDB.objects('servers');
|
||||
this.servers.addListener(this.updateState);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { servers } = this.state;
|
||||
const { closeServerDropdown, server } = this.props;
|
||||
if (nextProps.closeServerDropdown !== closeServerDropdown) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.server !== server) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.servers, servers)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
|
@ -78,7 +94,7 @@ export default class ServerDropdown extends Component {
|
|||
{
|
||||
toValue: 0,
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.ease,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
useNativeDriver: true
|
||||
}
|
||||
).start(() => toggleServerDropdown());
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Component } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
View, Text, Animated, Easing, Image, TouchableWithoutFeedback
|
||||
} from 'react-native';
|
||||
|
@ -19,7 +19,7 @@ const ANIMATION_DURATION = 200;
|
|||
}), dispatch => ({
|
||||
setSortPreference: preference => dispatch(setPreference(preference))
|
||||
}))
|
||||
export default class Sort extends Component {
|
||||
export default class Sort extends PureComponent {
|
||||
static propTypes = {
|
||||
closeSortDropdown: PropTypes.bool,
|
||||
close: PropTypes.func,
|
||||
|
@ -41,7 +41,7 @@ export default class Sort extends Component {
|
|||
{
|
||||
toValue: 1,
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.ease,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
useNativeDriver: true
|
||||
},
|
||||
).start();
|
||||
|
@ -95,7 +95,7 @@ export default class Sort extends Component {
|
|||
{
|
||||
toValue: 0,
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: Easing.ease,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
useNativeDriver: true
|
||||
},
|
||||
).start(() => close());
|
||||
|
|
|
@ -26,10 +26,12 @@ import { appStart as appStartAction } from '../../actions';
|
|||
import store from '../../lib/createStore';
|
||||
import Drawer from '../../Drawer';
|
||||
import { DEFAULT_HEADER } from '../../constants/headerOptions';
|
||||
import debounce from '../../utils/debounce';
|
||||
|
||||
const ROW_HEIGHT = 70;
|
||||
const SCROLL_OFFSET = 56;
|
||||
|
||||
const shouldUpdateProps = ['searchText', 'loadingServer', 'showServerDropdown', 'showSortDropdown', 'sortBy', 'groupByType', 'showFavorites', 'showUnread', 'useRealName', 'appState'];
|
||||
const isAndroid = () => Platform.OS === 'android';
|
||||
const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index });
|
||||
const keyExtractor = item => item.rid;
|
||||
|
@ -161,7 +163,61 @@ export default class RoomsListView extends LoggedView {
|
|||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !(isEqual(this.props, nextProps) && isEqual(this.state, nextState));
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
const propsUpdated = shouldUpdateProps.some(key => nextProps[key] !== this.props[key]);
|
||||
if (propsUpdated) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { loading, searching } = this.state;
|
||||
if (nextState.loading !== loading) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.searching !== searching) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { showUnread, showFavorites, groupByType } = this.props;
|
||||
if (showUnread) {
|
||||
const { unread } = this.state;
|
||||
if (!isEqual(nextState.unread, unread)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (showFavorites) {
|
||||
const { favorites } = this.state;
|
||||
if (!isEqual(nextState.favorites, favorites)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (groupByType) {
|
||||
const {
|
||||
channels, privateGroup, direct, livechat
|
||||
} = this.state;
|
||||
if (!isEqual(nextState.channels, channels)) {
|
||||
return true;
|
||||
}
|
||||
if (!isEqual(nextState.privateGroup, privateGroup)) {
|
||||
return true;
|
||||
}
|
||||
if (!isEqual(nextState.direct, direct)) {
|
||||
return true;
|
||||
}
|
||||
if (!isEqual(nextState.livechat, livechat)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
const { chats } = this.state;
|
||||
if (!isEqual(nextState.chats, chats)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const { search } = this.state;
|
||||
if (!isEqual(nextState.search, search)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
|
@ -190,10 +246,6 @@ export default class RoomsListView extends LoggedView {
|
|||
this.removeListener(this.direct);
|
||||
this.removeListener(this.livechat);
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
|
||||
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
navigationButtonPressed = ({ buttonId }) => {
|
||||
|
@ -262,9 +314,7 @@ export default class RoomsListView extends LoggedView {
|
|||
if (showUnread) {
|
||||
this.unread = this.data.filtered('archived != true && open == true').filtered('(unread > 0 || alert == true)');
|
||||
unread = this.removeRealmInstance(this.unread);
|
||||
setTimeout(() => {
|
||||
this.unread.addListener(() => this.internalSetState({ unread: this.removeRealmInstance(this.unread) }));
|
||||
});
|
||||
this.unread.addListener(debounce(() => this.internalSetState({ unread: this.removeRealmInstance(this.unread) }), 300));
|
||||
} else {
|
||||
this.removeListener(unread);
|
||||
}
|
||||
|
@ -272,9 +322,7 @@ export default class RoomsListView extends LoggedView {
|
|||
if (showFavorites) {
|
||||
this.favorites = this.data.filtered('f == true');
|
||||
favorites = this.removeRealmInstance(this.favorites);
|
||||
setTimeout(() => {
|
||||
this.favorites.addListener(() => this.internalSetState({ favorites: this.removeRealmInstance(this.favorites) }));
|
||||
});
|
||||
this.favorites.addListener(debounce(() => this.internalSetState({ favorites: this.removeRealmInstance(this.favorites) }), 300));
|
||||
} else {
|
||||
this.removeListener(favorites);
|
||||
}
|
||||
|
@ -296,12 +344,10 @@ export default class RoomsListView extends LoggedView {
|
|||
this.livechat = this.data.filtered('t == $0', 'l');
|
||||
livechat = this.removeRealmInstance(this.livechat);
|
||||
|
||||
setTimeout(() => {
|
||||
this.channels.addListener(() => this.internalSetState({ channels: this.removeRealmInstance(this.channels) }));
|
||||
this.privateGroup.addListener(() => this.internalSetState({ privateGroup: this.removeRealmInstance(this.privateGroup) }));
|
||||
this.direct.addListener(() => this.internalSetState({ direct: this.removeRealmInstance(this.direct) }));
|
||||
this.livechat.addListener(() => this.internalSetState({ livechat: this.removeRealmInstance(this.livechat) }));
|
||||
});
|
||||
this.channels.addListener(debounce(() => this.internalSetState({ channels: this.removeRealmInstance(this.channels) }), 300));
|
||||
this.privateGroup.addListener(debounce(() => this.internalSetState({ privateGroup: this.removeRealmInstance(this.privateGroup) }), 300));
|
||||
this.direct.addListener(debounce(() => this.internalSetState({ direct: this.removeRealmInstance(this.direct) }), 300));
|
||||
this.livechat.addListener(debounce(() => this.internalSetState({ livechat: this.removeRealmInstance(this.livechat) }), 300));
|
||||
this.removeListener(this.chats);
|
||||
} else {
|
||||
// chats
|
||||
|
@ -312,11 +358,7 @@ export default class RoomsListView extends LoggedView {
|
|||
}
|
||||
chats = this.removeRealmInstance(this.chats);
|
||||
|
||||
setTimeout(() => {
|
||||
this.chats.addListener(() => {
|
||||
this.internalSetState({ chats: this.removeRealmInstance(this.chats) });
|
||||
});
|
||||
});
|
||||
this.chats.addListener(debounce(() => this.internalSetState({ chats: this.removeRealmInstance(this.chats) }), 300));
|
||||
this.removeListener(this.channels);
|
||||
this.removeListener(this.privateGroup);
|
||||
this.removeListener(this.direct);
|
||||
|
@ -325,12 +367,9 @@ export default class RoomsListView extends LoggedView {
|
|||
|
||||
// setState
|
||||
this.internalSetState({
|
||||
chats, unread, favorites, channels, privateGroup, direct, livechat
|
||||
chats, unread, favorites, channels, privateGroup, direct, livechat, loading: false
|
||||
});
|
||||
}
|
||||
this.timeout = setTimeout(() => {
|
||||
this.internalSetState({ loading: false });
|
||||
}, 200);
|
||||
}
|
||||
|
||||
removeRealmInstance = (data) => {
|
||||
|
@ -399,13 +438,13 @@ export default class RoomsListView extends LoggedView {
|
|||
});
|
||||
}
|
||||
|
||||
goRoom = (rid) => {
|
||||
goRoom = ({ rid, name, t }) => {
|
||||
this.cancelSearchingAndroid();
|
||||
Navigation.push('RoomsListView', {
|
||||
component: {
|
||||
name: 'RoomView',
|
||||
passProps: {
|
||||
rid
|
||||
rid, name, t
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -413,8 +452,8 @@ export default class RoomsListView extends LoggedView {
|
|||
|
||||
_onPressItem = async(item = {}) => {
|
||||
if (!item.search) {
|
||||
const { rid } = item;
|
||||
return this.goRoom(rid);
|
||||
const { rid, name, t } = item;
|
||||
return this.goRoom({ rid, name, t });
|
||||
}
|
||||
if (item.t === 'd') {
|
||||
// if user is using the search we need first to join/create room
|
||||
|
@ -422,24 +461,25 @@ export default class RoomsListView extends LoggedView {
|
|||
const { username } = item;
|
||||
const result = await RocketChat.createDirectMessage(username);
|
||||
if (result.success) {
|
||||
return this.goRoom(result.room._id);
|
||||
return this.goRoom({ rid: result.room._id, name: username, t: 'd' });
|
||||
}
|
||||
} catch (e) {
|
||||
log('RoomsListView._onPressItem', e);
|
||||
}
|
||||
} else {
|
||||
const { rid } = item;
|
||||
return this.goRoom(rid);
|
||||
const { rid, name, t } = item;
|
||||
return this.goRoom({ rid, name, t });
|
||||
}
|
||||
}
|
||||
|
||||
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 });
|
||||
const offset = isAndroid() ? 0 : SCROLL_OFFSET;
|
||||
if (this.scroll.scrollTo) {
|
||||
this.scroll.scrollTo({ x: 0, y: offset, animated: true });
|
||||
} else if (this.scroll.scrollToOffset) {
|
||||
this.scroll.scrollToOffset({ offset });
|
||||
}
|
||||
setTimeout(() => {
|
||||
toggleSortDropdown();
|
||||
|
@ -461,6 +501,7 @@ export default class RoomsListView extends LoggedView {
|
|||
|
||||
return (
|
||||
<Touch
|
||||
key='rooms-list-view-sort'
|
||||
onPress={this.toggleSort}
|
||||
style={styles.dropdownContainerHeader}
|
||||
>
|
||||
|
@ -474,10 +515,17 @@ export default class RoomsListView extends LoggedView {
|
|||
|
||||
renderSearchBar = () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
return <SearchBox onChangeText={this.search} testID='rooms-list-view-search' />;
|
||||
return <SearchBox onChangeText={this.search} testID='rooms-list-view-search' key='rooms-list-view-search' />;
|
||||
}
|
||||
}
|
||||
|
||||
renderListHeader = () => (
|
||||
[
|
||||
this.renderSearchBar(),
|
||||
this.renderHeader()
|
||||
]
|
||||
)
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
const { useRealName, userId, baseUrl } = this.props;
|
||||
const id = item.rid.replace(userId, '').trim();
|
||||
|
@ -504,17 +552,11 @@ export default class RoomsListView extends LoggedView {
|
|||
|
||||
renderSeparator = () => <View style={styles.separator} />
|
||||
|
||||
renderSectionHeader = (header) => {
|
||||
const { showUnread, showFavorites, groupByType } = this.props;
|
||||
if (!(showUnread || showFavorites || groupByType)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
renderSectionHeader = header => (
|
||||
<View style={styles.groupTitleContainer}>
|
||||
<Text style={styles.groupTitle}>{I18n.t(header)}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
)
|
||||
|
||||
renderSection = (data, header) => {
|
||||
const { showUnread, showFavorites, groupByType } = this.props;
|
||||
|
@ -542,6 +584,8 @@ export default class RoomsListView extends LoggedView {
|
|||
enableEmptySections
|
||||
removeClippedSubviews
|
||||
keyboardShouldPersistTaps='always'
|
||||
initialNumToRender={12}
|
||||
windowSize={7}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -566,6 +610,8 @@ export default class RoomsListView extends LoggedView {
|
|||
enableEmptySections
|
||||
removeClippedSubviews
|
||||
keyboardShouldPersistTaps='always'
|
||||
initialNumToRender={12}
|
||||
windowSize={7}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -590,6 +636,30 @@ export default class RoomsListView extends LoggedView {
|
|||
return <ActivityIndicator style={styles.loading} />;
|
||||
}
|
||||
|
||||
const { showUnread, showFavorites, groupByType } = this.props;
|
||||
if (!(showUnread || showFavorites || groupByType)) {
|
||||
const { chats, search } = this.state;
|
||||
return (
|
||||
<FlatList
|
||||
ref={this.getScrollRef}
|
||||
data={search.length ? search : chats}
|
||||
extraData={search.length ? search : chats}
|
||||
contentOffset={Platform.OS === 'ios' ? { x: 0, y: SCROLL_OFFSET } : {}}
|
||||
keyExtractor={keyExtractor}
|
||||
style={styles.list}
|
||||
renderItem={this.renderItem}
|
||||
ItemSeparatorComponent={this.renderSeparator}
|
||||
ListHeaderComponent={this.renderListHeader}
|
||||
getItemLayout={getItemLayout}
|
||||
enableEmptySections
|
||||
removeClippedSubviews
|
||||
keyboardShouldPersistTaps='always'
|
||||
initialNumToRender={12}
|
||||
windowSize={7}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
ref={this.getScrollRef}
|
||||
|
@ -597,8 +667,7 @@ export default class RoomsListView extends LoggedView {
|
|||
keyboardShouldPersistTaps='always'
|
||||
testID='rooms-list-view-list'
|
||||
>
|
||||
{this.renderSearchBar()}
|
||||
{this.renderHeader()}
|
||||
{this.renderListHeader()}
|
||||
{this.renderList()}
|
||||
</ScrollView>
|
||||
);
|
||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { View, FlatList, Text } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import LoggedView from '../View';
|
||||
import RCTextInput from '../../containers/TextInput';
|
||||
|
@ -15,7 +16,6 @@ import Message from '../../containers/message/Message';
|
|||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||
import I18n from '../../i18n';
|
||||
import { DEFAULT_HEADER } from '../../constants/headerOptions';
|
||||
import database from '../../lib/realm';
|
||||
|
||||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
|
@ -50,10 +50,8 @@ export default class SearchMessagesView extends LoggedView {
|
|||
|
||||
constructor(props) {
|
||||
super('SearchMessagesView', props);
|
||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
|
||||
this.state = {
|
||||
loading: false,
|
||||
room: this.rooms[0],
|
||||
messages: [],
|
||||
searchText: ''
|
||||
};
|
||||
|
@ -63,17 +61,31 @@ export default class SearchMessagesView extends LoggedView {
|
|||
this.name.focus();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { loading, searchText, messages } = this.state;
|
||||
if (nextState.loading !== loading) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.searchText !== searchText) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.messages, messages)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.search.stop();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
search = debounce(async(searchText) => {
|
||||
const { room } = this.state;
|
||||
const { rid } = this.props;
|
||||
this.setState({ searchText, loading: true, messages: [] });
|
||||
|
||||
try {
|
||||
const result = await RocketChat.searchMessages(room.rid, searchText);
|
||||
const result = await RocketChat.searchMessages(rid, searchText);
|
||||
if (result.success) {
|
||||
this.setState({
|
||||
messages: result.messages || [],
|
||||
|
|
|
@ -21,5 +21,11 @@ export default StyleSheet.create({
|
|||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: '#E7EBF2',
|
||||
marginVertical: 20
|
||||
},
|
||||
listEmptyContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
backgroundColor: '#ffffff'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ import { connect, Provider } from 'react-redux';
|
|||
import { Navigation } from 'react-native-navigation';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import {
|
||||
addUser as addUserAction, removeUser as removeUserAction, reset as resetAction, setLoading as setLoadingAction
|
||||
|
@ -80,6 +81,21 @@ export default class SelectedUsersView extends LoggedView {
|
|||
Navigation.events().bindComponent(this);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { search } = this.state;
|
||||
const { users, loading } = this.props;
|
||||
if (nextProps.loading !== loading) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps.users, users)) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.search, search)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { componentId, users } = this.props;
|
||||
if (prevProps.users.length !== users.length) {
|
||||
|
|
|
@ -72,6 +72,17 @@ export default class SetUsernameView extends LoggedView {
|
|||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { username, saving } = this.state;
|
||||
if (nextState.username !== username) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.saving !== saving) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
|
|
|
@ -89,6 +89,21 @@ export default class SettingsView extends LoggedView {
|
|||
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { language, saving } = this.state;
|
||||
const { userLanguage } = this.props;
|
||||
if (nextState.language !== language) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.saving !== saving) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.userLanguage !== userLanguage) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { FlatList, View, Text } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import SafeAreaView from 'react-native-safe-area-view';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import { openSnippetedMessages as openSnippetedMessagesAction, closeSnippetedMessages as closeSnippetedMessagesAction } from '../../actions/snippetedMessages';
|
||||
import LoggedView from '../View';
|
||||
|
@ -68,6 +69,24 @@ export default class SnippetedMessagesView extends LoggedView {
|
|||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { loading, loadingMore } = this.state;
|
||||
const { messages, ready } = this.props;
|
||||
if (nextState.loading !== loading) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.loadingMore !== loadingMore) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.ready !== ready) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.messages, messages)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { closeSnippetedMessages } = this.props;
|
||||
closeSnippetedMessages();
|
||||
|
|
|
@ -13,7 +13,6 @@ import RCActivityIndicator from '../../containers/ActivityIndicator';
|
|||
import I18n from '../../i18n';
|
||||
import { DEFAULT_HEADER } from '../../constants/headerOptions';
|
||||
import RocketChat from '../../lib/rocketchat';
|
||||
import database from '../../lib/realm';
|
||||
|
||||
const STAR_INDEX = 0;
|
||||
const CANCEL_INDEX = 1;
|
||||
|
@ -22,6 +21,7 @@ const options = [I18n.t('Unstar'), I18n.t('Cancel')];
|
|||
@connect(state => ({
|
||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||
customEmojis: state.customEmojis,
|
||||
room: state.room,
|
||||
user: {
|
||||
id: state.login.user && state.login.user.id,
|
||||
username: state.login.user && state.login.user.username,
|
||||
|
@ -44,18 +44,16 @@ export default class StarredMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
static propTypes = {
|
||||
rid: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
customEmojis: PropTypes.object
|
||||
customEmojis: PropTypes.object,
|
||||
room: PropTypes.object
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super('StarredMessagesView', props);
|
||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', props.rid);
|
||||
this.state = {
|
||||
loading: false,
|
||||
room: this.rooms[0],
|
||||
messages: []
|
||||
};
|
||||
}
|
||||
|
@ -65,7 +63,14 @@ export default class StarredMessagesView extends LoggedView {
|
|||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !equal(this.state, nextState);
|
||||
const { loading, messages } = this.state;
|
||||
if (nextState.loading !== loading) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextState.messages, messages)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
onLongPress = (message) => {
|
||||
|
@ -101,7 +106,7 @@ export default class StarredMessagesView extends LoggedView {
|
|||
|
||||
load = async() => {
|
||||
const {
|
||||
messages, total, loading, room
|
||||
messages, total, loading
|
||||
} = this.state;
|
||||
const { user } = this.props;
|
||||
if (messages.length === total || loading) {
|
||||
|
@ -111,6 +116,7 @@ export default class StarredMessagesView extends LoggedView {
|
|||
this.setState({ loading: true });
|
||||
|
||||
try {
|
||||
const { room } = this.props;
|
||||
const result = await RocketChat.getMessages(
|
||||
room.rid,
|
||||
room.t,
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
const {
|
||||
device, expect, element, by, waitFor
|
||||
} = require('detox');
|
||||
const { takeScreenshot } = require('./helpers/screenshot');
|
||||
const data = require('./data');
|
||||
const { tapBack } = require('./helpers/app');
|
||||
|
||||
const room = 'detox-public';
|
||||
|
||||
async function mockMessage(message) {
|
||||
await element(by.id('messagebox-input')).tap();
|
||||
await element(by.id('messagebox-input')).typeText(`${ data.random }${ message }`);
|
||||
await element(by.id('messagebox-send-message')).tap();
|
||||
await waitFor(element(by.text(`${ data.random }${ message }`))).toExist().withTimeout(60000);
|
||||
};
|
||||
|
||||
async function navigateToRoom() {
|
||||
await element(by.id('rooms-list-view-search')).replaceText(room);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||
}
|
||||
|
||||
async function navigateToRoomActions() {
|
||||
await element(by.id('room-view-header-actions')).tap();
|
||||
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000);
|
||||
}
|
||||
|
||||
describe('Join public room', () => {
|
||||
before(async() => {
|
||||
await device.reloadReactNative();
|
||||
await navigateToRoom();
|
||||
});
|
||||
|
||||
describe('Render', async() => {
|
||||
it('should have room screen', async() => {
|
||||
await expect(element(by.id('room-view'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have messages list', async() => {
|
||||
await expect(element(by.id('room-view-messages'))).toBeVisible();
|
||||
});
|
||||
|
||||
// Render - Header
|
||||
describe('Header', async() => {
|
||||
it('should have star button', async() => {
|
||||
await expect(element(by.id('room-view-header-star'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have actions button ', async() => {
|
||||
await expect(element(by.id('room-view-header-actions'))).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
// Render - Join
|
||||
describe('Join', async() => {
|
||||
it('should have join', async() => {
|
||||
await expect(element(by.id('room-view-join'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have join text', async() => {
|
||||
await expect(element(by.text('You are in preview mode'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have join button', async() => {
|
||||
await expect(element(by.id('room-view-join-button'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should not have messagebox', async() => {
|
||||
await expect(element(by.id('messagebox'))).toBeNotVisible();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Room Actions', async() => {
|
||||
before(async() => {
|
||||
await navigateToRoomActions('c');
|
||||
});
|
||||
|
||||
it('should have room actions screen', async() => {
|
||||
await expect(element(by.id('room-actions-view'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have info', async() => {
|
||||
await expect(element(by.id('room-actions-info'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have voice', async() => {
|
||||
await expect(element(by.id('room-actions-voice'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have video', async() => {
|
||||
await expect(element(by.id('room-actions-video'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have members', async() => {
|
||||
await expect(element(by.id('room-actions-members'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have files', async() => {
|
||||
await expect(element(by.id('room-actions-files'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have mentions', async() => {
|
||||
await expect(element(by.id('room-actions-mentioned'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have starred', async() => {
|
||||
await expect(element(by.id('room-actions-starred'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have search', async() => {
|
||||
await expect(element(by.id('room-actions-search'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have share', async() => {
|
||||
await element(by.id('room-actions-list')).swipe('up');
|
||||
await expect(element(by.id('room-actions-share'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have pinned', async() => {
|
||||
await expect(element(by.id('room-actions-pinned'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have snippeted', async() => {
|
||||
await expect(element(by.id('room-actions-snippeted'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should not have notifications', async() => {
|
||||
await expect(element(by.id('room-actions-notifications'))).toBeNotVisible();
|
||||
});
|
||||
|
||||
it('should not have leave channel', async() => {
|
||||
await expect(element(by.id('room-actions-leave-channel'))).toBeNotVisible();
|
||||
});
|
||||
|
||||
after(async() => {
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
|
||||
})
|
||||
});
|
||||
|
||||
after(async() => {
|
||||
takeScreenshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Usage', async() => {
|
||||
it('should join room', async() => {
|
||||
await element(by.id('room-view-join-button')).tap();
|
||||
await waitFor(element(by.id('messagebox'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('messagebox'))).toBeVisible();
|
||||
await expect(element(by.id('room-view-join'))).toBeNotVisible();
|
||||
});
|
||||
|
||||
it('should send message', async() => {
|
||||
await mockMessage('message');
|
||||
await expect(element(by.text(`${ data.random }message`))).toExist();
|
||||
});
|
||||
|
||||
it('should have disable notifications and leave channel', async() => {
|
||||
await navigateToRoomActions('c');
|
||||
await expect(element(by.id('room-actions-view'))).toBeVisible();
|
||||
await expect(element(by.id('room-actions-info'))).toBeVisible();
|
||||
await expect(element(by.id('room-actions-voice'))).toBeVisible();
|
||||
await expect(element(by.id('room-actions-video'))).toBeVisible();
|
||||
await expect(element(by.id('room-actions-members'))).toBeVisible();
|
||||
await expect(element(by.id('room-actions-files'))).toBeVisible();
|
||||
await expect(element(by.id('room-actions-mentioned'))).toBeVisible();
|
||||
await expect(element(by.id('room-actions-starred'))).toBeVisible();
|
||||
await expect(element(by.id('room-actions-search'))).toBeVisible();
|
||||
await element(by.id('room-actions-list')).swipe('up');
|
||||
await expect(element(by.id('room-actions-share'))).toBeVisible();
|
||||
await expect(element(by.id('room-actions-pinned'))).toBeVisible();
|
||||
await expect(element(by.id('room-actions-snippeted'))).toBeVisible();
|
||||
await expect(element(by.id('room-actions-notifications'))).toBeVisible();
|
||||
await expect(element(by.id('room-actions-leave-channel'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should leave room', async() => {
|
||||
await element(by.id('room-actions-leave-channel')).tap();
|
||||
await waitFor(element(by.text('Yes, leave it!'))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.text('Yes, leave it!'))).toBeVisible();
|
||||
await element(by.text('Yes, leave it!')).tap();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
|
||||
await element(by.id('rooms-list-view-search')).replaceText('');
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible().withTimeout(60000);
|
||||
await expect(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible();
|
||||
});
|
||||
|
||||
it('should navigate to room and user should be joined', async() => {
|
||||
await navigateToRoom();
|
||||
await expect(element(by.id('room-view-join'))).toBeVisible();
|
||||
})
|
||||
|
||||
after(async() => {
|
||||
takeScreenshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1 +1 @@
|
|||
--recursive --timeout 120000
|
||||
--recursive --timeout 120000 -b
|
|
@ -18282,21 +18282,21 @@
|
|||
}
|
||||
},
|
||||
"react-native-navigation": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-navigation/-/react-native-navigation-2.1.3.tgz",
|
||||
"integrity": "sha512-CtjDhw7eaDWCqfhK6Fq6IUDq3agl3oGDd8SNaPF7318tLni1qTmmFdz/3CpoNlegNWhAMAjNu+ONDAbe7ksADw==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-navigation/-/react-native-navigation-2.2.1.tgz",
|
||||
"integrity": "sha512-m0RyVQMiNNIoMlcy3zADgazRRU5qeJNOpRhx9ERA/V5t2uPq1vTGoG3bvoYbkFmsrsgyWZax4l+5AdnygDzNKg==",
|
||||
"requires": {
|
||||
"hoist-non-react-statics": "3.x.x",
|
||||
"lodash": "4.x.x",
|
||||
"lodash": "4.17.x",
|
||||
"prop-types": "15.x.x",
|
||||
"react-lifecycles-compat": "2.0.0",
|
||||
"tslib": "1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"hoist-non-react-statics": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.1.0.tgz",
|
||||
"integrity": "sha512-MYcYuROh7SBM69xHGqXEwQqDux34s9tz+sCnxJmN18kgWh6JFdTw/5YdZtqsOdZJXddE/wUpCzfEdDrJj8p0Iw==",
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.2.1.tgz",
|
||||
"integrity": "sha512-TFsu3TV3YLY+zFTZDrN8L2DTFanObwmBLpWvJs1qfUuEQ5bTAdFcwfx2T/bsCXfM9QHSLvjfP+nihEl0yvozxw==",
|
||||
"requires": {
|
||||
"react-is": "^16.3.2"
|
||||
}
|
||||
|
@ -19958,9 +19958,9 @@
|
|||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
||||
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
|
||||
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.16.2",
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"react-native-keyboard-tracking-view": "^5.5.0",
|
||||
"react-native-markdown-renderer": "^3.2.8",
|
||||
"react-native-modal": "^7.0.0",
|
||||
"react-native-navigation": "^2.1.3",
|
||||
"react-native-navigation": "^2.2.1",
|
||||
"react-native-notifications": "^1.1.21",
|
||||
"react-native-optimized-flatlist": "^1.0.4",
|
||||
"react-native-picker-select": "^5.1.0",
|
||||
|
@ -70,6 +70,7 @@
|
|||
"redux-immutable-state-invariant": "^2.1.0",
|
||||
"redux-saga": "^0.16.2",
|
||||
"rn-fetch-blob": "^0.10.13",
|
||||
"semver": "^5.6.0",
|
||||
"snyk": "^1.109.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue