Merge pull request #86 from RocketChat/improves

improvements
This commit is contained in:
Diego Mello 2017-11-21 13:40:48 -02:00 committed by GitHub
commit 5ad676f7dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 126 additions and 175 deletions

View File

@ -1,4 +1,9 @@
{ {
"presets": ["react-native"], "presets": ["react-native"],
"plugins": ["transform-decorators-legacy"] "plugins": ["transform-decorators-legacy"],
"env": {
"production": {
"plugins": ["transform-remove-console"]
}
}
} }

View File

@ -18,7 +18,9 @@ const settingsSchema = {
_server: 'servers', _server: 'servers',
valueAsString: { type: 'string', optional: true }, valueAsString: { type: 'string', optional: true },
valueAsBoolean: { type: 'bool', optional: true }, valueAsBoolean: { type: 'bool', optional: true },
valueAsNumber: { type: 'int', optional: true } valueAsNumber: { type: 'int', optional: true },
_updatedAt: { type: 'date', optional: true }
} }
}; };

View File

@ -26,9 +26,7 @@ const RocketChat = {
TOKEN_KEY, TOKEN_KEY,
createChannel({ name, users, type }) { createChannel({ name, users, type }) {
return new Promise((resolve, reject) => { return call(type ? 'createChannel' : 'createPrivateGroup', name, users, type);
Meteor.call(type ? 'createChannel' : 'createPrivateGroup', name, users, type, (err, res) => (err ? reject(err) : resolve(res)));
});
}, },
async getUserToken() { async getUserToken() {
@ -52,61 +50,63 @@ const RocketChat = {
const url = `${ _url }/websocket`; const url = `${ _url }/websocket`;
Meteor.connect(url, { autoConnect: true, autoReconnect: true }); Meteor.connect(url, { autoConnect: true, autoReconnect: true });
Meteor.ddp.on('disconnected', () => { Meteor.ddp.on('disconnected', () => {
reduxStore.dispatch(disconnect()); reduxStore.dispatch(disconnect());
}); });
Meteor.ddp.on('connected', () => { Meteor.ddp.on('connected', () => {
reduxStore.dispatch(connectSuccess()); reduxStore.dispatch(connectSuccess());
resolve(); resolve();
}); });
Meteor.ddp.on('connected', () => {
Meteor.call('public-settings/get', (err, data) => {
if (err) {
console.error(err);
}
const settings = {};
realm.write(() => {
data.forEach((item) => {
const setting = {
_id: item._id
};
setting._server = { id: reduxStore.getState().server.server };
if (settingsType[item.type]) {
setting[settingsType[item.type]] = item.value;
realm.create('settings', setting, true);
}
settings[item._id] = item.value;
});
});
reduxStore.dispatch(actions.setAllSettings(settings));
});
Meteor.ddp.on('connected', async() => {
Meteor.ddp.on('changed', (ddbMessage) => { Meteor.ddp.on('changed', (ddbMessage) => {
const server = { id: reduxStore.getState().server.server };
if (ddbMessage.collection === 'stream-room-messages') { if (ddbMessage.collection === 'stream-room-messages') {
realm.write(() => { realm.write(() => {
const message = ddbMessage.fields.args[0]; const message = ddbMessage.fields.args[0];
message.temp = false; message.temp = false;
message._server = { id: reduxStore.getState().server.server }; message._server = server;
message.attachments = message.attachments || [];
message.starred = !!message.starred; message.starred = !!message.starred;
realm.create('messages', message, true); realm.create('messages', message, true);
}); });
} }
if (ddbMessage.collection === 'stream-notify-user') { if (ddbMessage.collection === 'stream-notify-user') {
realm.write(() => { const [type, data] = ddbMessage.fields.args;
const data = ddbMessage.fields.args[1]; const [, ev] = ddbMessage.fields.eventName.split('/');
data._server = { id: reduxStore.getState().server.server }; if (/subscriptions/.test(ev)) {
realm.create('subscriptions', data, true); switch (type) {
}); case 'inserted':
data._server = server;
realm.write(() => {
realm.create('subscriptions', data, true);
});
break;
case 'updated':
delete data._updatedAt;
realm.write(() => {
realm.create('subscriptions', data, true);
});
break;
default:
}
}
if (/rooms/.test(ev) && type === 'updated') {
const sub = realm.objects('subscriptions').filtered('rid == $0', data._id)[0];
realm.write(() => {
sub._updatedAt = data._updatedAt;
});
}
} }
}); });
RocketChat.getSettings();
}); });
}) })
.catch(e => console.error(e)); .catch(e => console.error(e));
}, },
login(params, callback) { login(params, callback) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Meteor._startLoggingIn(); Meteor._startLoggingIn();
@ -114,6 +114,11 @@ const RocketChat = {
Meteor._endLoggingIn(); Meteor._endLoggingIn();
Meteor._handleLoginCallback(err, result); Meteor._handleLoginCallback(err, result);
if (err) { if (err) {
if (/user not found/i.test(err.reason)) {
err.error = 1;
err.reason = 'User or Password incorrect';
err.message = 'User or Password incorrect';
}
reject(err); reject(err);
} else { } else {
resolve(result); resolve(result);
@ -137,36 +142,15 @@ const RocketChat = {
}, },
register({ credentials }) { register({ credentials }) {
return new Promise((resolve, reject) => { return call('registerUser', credentials);
Meteor.call('registerUser', credentials, (err, userId) => {
if (err) {
reject(err);
}
resolve(userId);
});
});
}, },
setUsername({ credentials }) { setUsername({ credentials }) {
return new Promise((resolve, reject) => { return call('setUsername', credentials.username);
Meteor.call('setUsername', credentials.username, (err, result) => {
if (err) {
reject(err);
}
resolve(result);
});
});
}, },
forgotPassword(email) { forgotPassword(email) {
return new Promise((resolve, reject) => { return call('sendForgotPasswordEmail', email);
Meteor.call('sendForgotPasswordEmail', email, (err, result) => {
if (err) {
reject(err);
}
resolve(result);
});
});
}, },
loginWithPassword({ username, password, code }, callback) { loginWithPassword({ username, password, code }, callback) {
@ -211,26 +195,6 @@ const RocketChat = {
return this.login(params, callback); return this.login(params, callback);
}, },
// loadRooms(cb) {
// console.warn('a');
// Meteor.call('rooms/get', (err, data) => {
// if (err) {
// console.error(err);
// }
// console.warn(`rooms ${ data.length }`);
// if (data.length) {
// realm.write(() => {
// data.forEach((room) => {
// room._server = { id: reduxStore.getState().server.server };
// realm.create('rooms', room, true);
// });
// });
// }
// return cb && cb();
// });
// },
loadSubscriptions(cb) { loadSubscriptions(cb) {
Meteor.call('subscriptions/get', (err, data) => { Meteor.call('subscriptions/get', (err, data) => {
if (err) { if (err) {
@ -285,6 +249,7 @@ const RocketChat = {
data.messages.forEach((message) => { data.messages.forEach((message) => {
message.temp = false; message.temp = false;
message._server = { id: reduxStore.getState().server.server }; message._server = { id: reduxStore.getState().server.server };
message.attachments = message.attachments || [];
// write('messages', message); // write('messages', message);
message.starred = !!message.starred; message.starred = !!message.starred;
realm.create('messages', message, true); realm.create('messages', message, true);
@ -334,38 +299,17 @@ const RocketChat = {
}, },
spotlight(search, usernames) { spotlight(search, usernames) {
return new Promise((resolve, reject) => { return call('spotlight', search, usernames);
Meteor.call('spotlight', search, usernames, (error, result) => {
if (error) {
return reject(error);
}
return resolve(result);
});
});
}, },
createDirectMessage(username) { createDirectMessage(username) {
return new Promise((resolve, reject) => { return call('createDirectMessage', username);
Meteor.call('createDirectMessage', username, (error, result) => {
if (error) {
return reject(error);
}
return resolve(result);
});
});
}, },
readMessages(rid) { readMessages(rid) {
return call('readMessages', rid); return call('readMessages', rid);
}, },
joinRoom(rid) { joinRoom(rid) {
return new Promise((resolve, reject) => { return call('joinRoom', rid);
Meteor.call('joinRoom', rid, (error, result) => {
if (error) {
return reject(error);
}
return resolve(result);
});
});
}, },
@ -379,26 +323,12 @@ const RocketChat = {
*/ */
_ufsCreate(fileInfo) { _ufsCreate(fileInfo) {
// return call('ufsCreate', fileInfo); // return call('ufsCreate', fileInfo);
return new Promise((resolve, reject) => { return call('ufsCreate', fileInfo);
Meteor.call('ufsCreate', fileInfo, (error, result) => {
if (error) {
return reject(error);
}
return resolve(result);
});
});
}, },
// ["ZTE8CKHJt7LATv7Me","fileSystem","e8E96b2819" // ["ZTE8CKHJt7LATv7Me","fileSystem","e8E96b2819"
_ufsComplete(fileId, store, token) { _ufsComplete(fileId, store, token) {
return new Promise((resolve, reject) => { return call('ufsComplete', fileId, store, token);
Meteor.call('ufsComplete', fileId, store, token, (error, result) => {
if (error) {
return reject(error);
}
return resolve(result);
});
});
}, },
/* /*
@ -412,14 +342,7 @@ const RocketChat = {
} }
*/ */
_sendFileMessage(rid, data, msg = {}) { _sendFileMessage(rid, data, msg = {}) {
return new Promise((resolve, reject) => { return call('sendFileMessage', rid, null, data, msg);
Meteor.call('sendFileMessage', rid, null, data, msg, (error, result) => {
if (error) {
return reject(error);
}
return resolve(result);
});
});
}, },
async sendFileMessage(rid, fileInfo, data) { async sendFileMessage(rid, fileInfo, data) {
const placeholder = RocketChat.getMessage(rid, 'Sending an image'); const placeholder = RocketChat.getMessage(rid, 'Sending an image');
@ -448,23 +371,36 @@ const RocketChat = {
}); });
} }
}, },
getRooms() { async getRooms() {
const { server, login } = reduxStore.getState(); const { server, login } = reduxStore.getState();
return Promise.all([call('subscriptions/get'), call('rooms/get')]).then(([subscriptions, rooms]) => { let lastMessage = realm
const data = subscriptions.map((subscription) => { .objects('subscriptions')
subscription._updatedAt = (rooms.find(room => room._id === subscription.rid) || {})._updatedAt; .filtered('_server.id = $0', server.server)
subscription._server = { id: server.server }; .sorted('_updatedAt', true)[0];
return subscription; lastMessage = lastMessage && new Date(lastMessage._updatedAt);
}); let [subscriptions, rooms] = await Promise.all([call('subscriptions/get', lastMessage), call('rooms/get', lastMessage)]);
realm.write(() => { if (lastMessage) {
data.forEach((subscription) => { subscriptions = subscriptions.update;
realm.create('subscriptions', subscription, true); rooms = rooms.update;
}); }
}); const data = subscriptions.map((subscription) => {
Meteor.subscribe('stream-notify-user', `${ login.user.id }/subscriptions-changed`, false); const room = rooms.find(({ _id }) => _id === subscription.rid);
return data; delete subscription._updatedAt;
if (room) {
subscription._updatedAt = room._updatedAt;
}
subscription._server = { id: server.server };
return subscription;
}); });
realm.write(() => {
data.forEach(subscription =>
realm.create('subscriptions', subscription, true));
});
Meteor.subscribe('stream-notify-user', `${ login.user.id }/subscriptions-changed`, false);
Meteor.subscribe('stream-notify-user', `${ login.user.id }/rooms-changed`, false);
return data;
}, },
logout({ server }) { logout({ server }) {
Meteor.logout(); Meteor.logout();
@ -472,6 +408,27 @@ const RocketChat = {
AsyncStorage.removeItem(TOKEN_KEY); AsyncStorage.removeItem(TOKEN_KEY);
AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`); AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`);
}, },
async getSettings() {
const temp = realm.objects('settings').sorted('_updatedAt', true)[0];
const result = await (!temp ? call('public-settings/get') : call('public-settings/get', new Date(temp._updatedAt)));
const settings = temp ? result.update : result;
const filteredSettings = RocketChat._prepareSettings(RocketChat._filterSettings(settings));
realm.write(() => {
filteredSettings.forEach(setting => realm.create('settings', setting, true));
});
reduxStore.dispatch(actions.setAllSettings(RocketChat.parseSettings(filteredSettings)));
},
parseSettings: settings => settings.reduce((ret, item) => {
ret[item._id] = item[settingsType[item.type]] || item.valueAsString || item.value;
return ret;
}, {}),
_prepareSettings(settings) {
return settings.map((setting) => {
setting[settingsType[setting.type]] = setting.value;
return setting;
});
},
_filterSettings: settings => settings.filter(setting => settingsType[setting.type] && setting.value),
deleteMessage(message) { deleteMessage(message) {
return call('deleteMessage', { _id: message._id }); return call('deleteMessage', { _id: message._id });
}, },

View File

@ -87,13 +87,12 @@ export default class RoomItem extends React.PureComponent {
return null; return null;
} }
const { color } = avatarInitialsAndColor(name);
if (type === 'd') { if (type === 'd') {
return ( return (
<Avatar text={name} baseUrl={baseUrl} size={40} /> <Avatar text={name} baseUrl={baseUrl} size={40} />
); );
} }
const { color } = avatarInitialsAndColor(name);
return ( return (
<View style={[styles.iconContainer, { backgroundColor: color }]}> <View style={[styles.iconContainer, { backgroundColor: color }]}>
@ -120,24 +119,13 @@ export default class RoomItem extends React.PureComponent {
render() { render() {
const { unread, name, _updatedAt } = this.props; const { unread, name, _updatedAt } = this.props;
if (_updatedAt) {
return (
<TouchableOpacity onPress={this.props.onPress} style={styles.container}>
{this.icon}
<View style={styles.roomNameView}>
<Text style={styles.roomName} ellipsizeMode='tail' numberOfLines={1}>{ name }</Text>
<Text style={styles.update} ellipsizeMode='tail' numberOfLines={1}>{ moment(_updatedAt).format(this.props.dateFormat) }</Text>
</View>
{this.renderNumber(unread)}
</TouchableOpacity>
);
}
return ( return (
<TouchableOpacity onPress={this.props.onPress} style={styles.container}> <TouchableOpacity onPress={this.props.onPress} style={styles.container}>
{this.icon} {this.icon}
<View style={styles.roomNameView}> <View style={styles.roomNameView}>
<Text style={styles.roomName} ellipsizeMode='tail' numberOfLines={1}>{ name }</Text> <Text style={styles.roomName} ellipsizeMode='tail' numberOfLines={1}>{ name }</Text>
{_updatedAt ? <Text style={styles.update} ellipsizeMode='tail' numberOfLines={1}>{ moment(_updatedAt).format(this.props.dateFormat) }</Text> : null}
</View> </View>
{this.renderNumber(unread)} {this.renderNumber(unread)}
</TouchableOpacity> </TouchableOpacity>

View File

@ -4,6 +4,8 @@ import * as actions from '../actions';
import { setServer } from '../actions/server'; import { setServer } from '../actions/server';
import { restoreToken } from '../actions/login'; import { restoreToken } from '../actions/login';
import { APP } from '../actions/actionsTypes'; import { APP } from '../actions/actionsTypes';
import realm from '../lib/realm';
import RocketChat from '../lib/rocketchat';
const restore = function* restore() { const restore = function* restore() {
try { try {
@ -16,6 +18,8 @@ const restore = function* restore() {
const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer'); const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer');
if (currentServer) { if (currentServer) {
yield put(setServer(currentServer)); yield put(setServer(currentServer));
const tmp = realm.objects('settings');
yield put(actions.setAllSettings(RocketChat.parseSettings(tmp.slice(0, tmp.length))));
} }
yield put(actions.appReady({})); yield put(actions.appReady({}));
} catch (e) { } catch (e) {

View File

@ -1,5 +1,5 @@
import { AsyncStorage } from 'react-native'; import { AsyncStorage } from 'react-native';
import { take, put, call, takeEvery, takeLatest, select, all } from 'redux-saga/effects'; import { take, put, call, takeLatest, select, all } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes'; import * as types from '../actions/actionsTypes';
import { import {
loginRequest, loginRequest,
@ -9,7 +9,6 @@ import {
loginSuccess, loginSuccess,
loginFailure, loginFailure,
setToken, setToken,
logout,
registerSuccess, registerSuccess,
setUsernameRequest, setUsernameRequest,
setUsernameSuccess, setUsernameSuccess,
@ -83,11 +82,7 @@ const handleLoginRequest = function* handleLoginRequest({ credentials }) {
yield put(loginSuccess(user)); yield put(loginSuccess(user));
} catch (err) { } catch (err) {
if (err.error === 403) { yield put(loginFailure(err));
yield put(logout());
} else {
yield put(loginFailure(err));
}
} }
}; };
@ -147,7 +142,7 @@ const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ emai
}; };
const root = function* root() { const root = function* root() {
yield takeEvery(types.SERVER.CHANGED, handleLoginWhenServerChanges); yield takeLatest(types.SERVER.CHANGED, handleLoginWhenServerChanges);
yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest); yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest);
yield takeLatest(types.LOGIN.SUCCESS, saveToken); yield takeLatest(types.LOGIN.SUCCESS, saveToken);
yield takeLatest(types.LOGIN.SUBMIT, handleLoginSubmit); yield takeLatest(types.LOGIN.SUBMIT, handleLoginSubmit);

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
// import Spinner from 'react-native-loading-spinner-overlay'; import Spinner from 'react-native-loading-spinner-overlay';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Keyboard, Text, TextInput, View, TouchableOpacity, SafeAreaView } from 'react-native'; import { Keyboard, Text, TextInput, View, TouchableOpacity, SafeAreaView } from 'react-native';
@ -143,6 +143,7 @@ class LoginView extends React.Component {
</TouchableOpacity> </TouchableOpacity>
{this.props.login.failure && <Text style={styles.error}>{this.props.login.error.reason}</Text>} {this.props.login.failure && <Text style={styles.error}>{this.props.login.error.reason}</Text>}
</View> </View>
<Spinner visible={this.props.login.isFetching} textContent='Loading...' textStyle={{ color: '#FFF' }} />
</SafeAreaView> </SafeAreaView>
</View> </View>
</KeyboardView> </KeyboardView>

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Text, View, StyleSheet, Button, SafeAreaView } from 'react-native'; import { Text, View, StyleSheet, Button, SafeAreaView, Dimensions } from 'react-native';
import { ListView } from 'realm/react-native'; import { ListView } from 'realm/react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
@ -38,7 +38,7 @@ const styles = StyleSheet.create({
textAlign: 'center', textAlign: 'center',
color: '#a00' color: '#a00'
}, },
header: { loadingMore: {
transform: [{ scaleY: -1 }], transform: [{ scaleY: -1 }],
textAlign: 'center', textAlign: 'center',
padding: 5, padding: 5,
@ -86,7 +86,7 @@ export default class RoomView extends React.Component {
.sorted('ts', true); .sorted('ts', true);
this.state = { this.state = {
slow: false, slow: false,
dataSource: [], dataSource: ds.cloneWithRows([]),
loaded: true, loaded: true,
joined: typeof props.rid === 'undefined' joined: typeof props.rid === 'undefined'
}; };
@ -102,10 +102,9 @@ export default class RoomView extends React.Component {
this.timer = setTimeout(() => this.setState({ slow: true }), 5000); this.timer = setTimeout(() => this.setState({ slow: true }), 5000);
this.props.getMessages(this.rid); this.props.getMessages(this.rid);
this.data.addListener(this.updateState); this.data.addListener(this.updateState);
this.state.dataSource = ds.cloneWithRows(this.data);
} }
componentDidMount() { componentDidMount() {
this.updateState();
} }
componentDidUpdate() { componentDidUpdate() {
return !this.props.loading && clearTimeout(this.timer); return !this.props.loading && clearTimeout(this.timer);
@ -124,14 +123,12 @@ export default class RoomView extends React.Component {
this.state.end !== true this.state.end !== true
) { ) {
this.setState({ this.setState({
// ...this.state,
loadingMore: true loadingMore: true
}); });
const lastRowData = this.data[rowCount - 1]; const lastRowData = this.data[rowCount - 1];
RocketChat.loadMessagesForRoom(this.rid, lastRowData.ts, ({ end }) => { RocketChat.loadMessagesForRoom(this.rid, lastRowData.ts, ({ end }) => {
this.setState({ this.setState({
// ...this.state,
loadingMore: false, loadingMore: false,
end end
}); });
@ -186,15 +183,16 @@ export default class RoomView extends React.Component {
renderHeader = () => { renderHeader = () => {
if (this.state.loadingMore) { if (this.state.loadingMore) {
return <Text style={styles.header}>Loading more messages...</Text>; return <Text style={styles.loadingMore}>Loading more messages...</Text>;
} }
if (this.state.end) { if (this.state.end) {
return <Text style={styles.header}>Start of conversation</Text>; return <Text style={styles.loadingMore}>Start of conversation</Text>;
} }
}; };
render() { render() {
const { height } = Dimensions.get('window');
return ( return (
<KeyboardView contentContainerStyle={styles.container} keyboardVerticalOffset={64}> <KeyboardView contentContainerStyle={styles.container} keyboardVerticalOffset={64}>
{this.renderBanner()} {this.renderBanner()}
@ -202,7 +200,7 @@ export default class RoomView extends React.Component {
<ListView <ListView
enableEmptySections enableEmptySections
style={styles.list} style={styles.list}
onEndReachedThreshold={10} onEndReachedThreshold={height / 2}
renderFooter={this.renderHeader} renderFooter={this.renderHeader}
onEndReached={this.onEndReached} onEndReached={this.onEndReached}
dataSource={this.state.dataSource} dataSource={this.state.dataSource}

View File

@ -231,7 +231,7 @@ export default class RoomsListView extends React.Component {
return; return;
} }
navigateToRoom({ sid: id }); navigateToRoom({ sid: id, ...item });
clearSearch(); clearSearch();
} }

View File

@ -15,6 +15,7 @@
}, },
"dependencies": { "dependencies": {
"babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-remove-console": "^6.8.5",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"moment": "^2.19.2", "moment": "^2.19.2",
"prop-types": "^15.6.0", "prop-types": "^15.6.0",