Deep linking fix and more (#294)

* Fix - Any https link was deep linking to RocketChat

* Keyboard dismiss after add new server

* Room info bug fix

* Opacity animation

* Navigation when adding server fixed

* Throttle for unnecessary render on receiving several messages

* Search inputs without autocorrect and autocapitalize

* Search messages fixed

* Messagebox unnecessary render and spotlight fixed

* react-native-keyboard-input updated
This commit is contained in:
Diego Mello 2018-05-18 14:55:08 -03:00 committed by Guilherme Gazzo
parent a0bb61642d
commit 2b172b359e
52 changed files with 610 additions and 404 deletions

View File

@ -187,9 +187,9 @@ dependencies {
compile project(':react-native-fast-image')
compile project(':realm')
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:23.0.1"
compile "com.android.support:appcompat-v7:27.1.0"
compile "com.android.support:support-v4:27.1.0"
compile 'com.android.support:customtabs:23.0.1'
compile 'com.android.support:customtabs:27.1.0'
compile "com.facebook.react:react-native:+" // From node_modules
compile 'com.facebook.fresco:fresco:1.7.1'
compile 'com.facebook.fresco:animated-gif:1.7.1'

View File

@ -44,7 +44,8 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="go.rocket.chat" />
<data android:scheme="rocketchat" android:host="*" />
<data android:scheme="rocketchat" android:host="room" />
<data android:scheme="rocketchat" android:host="auth" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

View File

@ -12,4 +12,5 @@ if (__DEV__) {
.connect();
// Running on android device
// $ adb reverse tcp:9090 tcp:9090
console.warn = Reactotron.log;
}

View File

@ -42,8 +42,7 @@ export const ROOM = createRequestTypes('ROOM', [
'ERASE',
'USER_TYPING',
'MESSAGE_RECEIVED',
'SET_LAST_OPEN',
'LAYOUT_ANIMATION'
'SET_LAST_OPEN'
]);
export const APP = createRequestTypes('APP', ['READY', 'INIT']);
export const MESSAGES = createRequestTypes('MESSAGES', [
@ -81,8 +80,7 @@ export const SERVER = createRequestTypes('SERVER', [
...defaultTypes,
'SELECT',
'CHANGED',
'ADD',
'GOTO_ADD'
'ADD'
]);
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT', 'DISCONNECT_BY_USER']);
export const LOGOUT = 'LOGOUT'; // logout is always success

View File

@ -69,9 +69,3 @@ export function setLastOpen(date = new Date()) {
date
};
}
export function layoutAnimation() {
return {
type: types.ROOM.LAYOUT_ANIMATION
};
}

View File

@ -41,9 +41,3 @@ export function changedServer(server) {
server
};
}
export function gotoAddServer() {
return {
type: SERVER.GOTO_ADD
};
}

View File

@ -10,6 +10,7 @@ import styles from './styles';
import categories from './categories';
import database from '../../lib/realm';
import { emojisByCategory } from '../../emojis';
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
const scrollProps = {
keyboardShouldPersistTaps: 'always',
@ -68,11 +69,11 @@ export default class EmojiPicker extends Component {
}
}
_addFrequentlyUsed = (emoji) => {
_addFrequentlyUsed = protectedFunction((emoji) => {
database.write(() => {
database.create('frequentlyUsedEmoji', emoji, true);
});
}
})
_getFrequentlyUsedCount = (content) => {
const emojiRow = this.frequentlyUsed.filtered('content == $0', content);
return emojiRow.length ? emojiRow[0].count + 1 : 1;

View File

@ -7,7 +7,7 @@ import { connect } from 'react-redux';
import { emojify } from 'react-emojione';
import { KeyboardAccessoryView } from 'react-native-keyboard-input';
import { userTyping, layoutAnimation } from '../../actions/room';
import { userTyping } from '../../actions/room';
import RocketChat from '../../lib/rocketchat';
import { editRequest, editCancel, clearInput } from '../../actions/messages';
import styles from './styles';
@ -18,7 +18,7 @@ import CustomEmoji from '../EmojiPicker/CustomEmoji';
import { emojis } from '../../emojis';
import Recording from './Recording';
import './EmojiKeyboard';
import log from '../../utils/log';
const MENTIONS_TRACKING_TYPE_USERS = '@';
const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
@ -36,8 +36,7 @@ const onlyUnique = function onlyUnique(value, index, self) {
editCancel: () => dispatch(editCancel()),
editRequest: message => dispatch(editRequest(message)),
typing: status => dispatch(userTyping(status)),
clearInput: () => dispatch(clearInput()),
layoutAnimation: () => dispatch(layoutAnimation())
clearInput: () => dispatch(clearInput())
}))
export default class MessageBox extends React.PureComponent {
static propTypes = {
@ -49,8 +48,7 @@ export default class MessageBox extends React.PureComponent {
message: PropTypes.object,
editing: PropTypes.bool,
typing: PropTypes.func,
clearInput: PropTypes.func,
layoutAnimation: PropTypes.func
clearInput: PropTypes.func
}
constructor(props) {
@ -58,7 +56,6 @@ export default class MessageBox extends React.PureComponent {
this.state = {
text: '',
mentions: [],
showMentionsContainer: false,
showEmojiKeyboard: false,
recording: false
};
@ -176,7 +173,7 @@ export default class MessageBox extends React.PureComponent {
if (response.didCancel) {
console.warn('User cancelled image picker');
} else if (response.error) {
console.warn('ImagePicker Error: ', response.error);
log('ImagePicker Error', response.error);
} else if (response.customButton) {
console.warn('User tapped custom button: ', response.customButton);
} else {
@ -272,11 +269,13 @@ export default class MessageBox extends React.PureComponent {
RocketChat.spotlight(keyword, usernames, { users: true }),
new Promise((resolve, reject) => (this.oldPromise = reject))
]);
database.write(() => {
results.users.forEach((user) => {
database.create('users', user, true);
if (results.users && results.users.length) {
database.write(() => {
results.users.forEach((user) => {
database.create('users', user, true);
});
});
});
}
} catch (e) {
console.warn('spotlight canceled');
} finally {
@ -318,7 +317,9 @@ export default class MessageBox extends React.PureComponent {
RocketChat.spotlight(keyword, [...rooms, ...this.roomsCache].map(r => r.name), { rooms: true }),
new Promise((resolve, reject) => (this.oldPromise = reject))
]);
this.roomsCache = [...this.roomsCache, ...results.rooms].filter(onlyUnique);
if (results.rooms && results.rooms.length) {
this.roomsCache = [...this.roomsCache, ...results.rooms].filter(onlyUnique);
}
this.setState({ mentions: [...rooms.slice(), ...results.rooms] });
} catch (e) {
console.warn('spotlight canceled');
@ -338,7 +339,6 @@ export default class MessageBox extends React.PureComponent {
stopTrackingMention() {
this.setState({
showMentionsContainer: false,
mentions: [],
trackingType: ''
});
@ -349,11 +349,7 @@ export default class MessageBox extends React.PureComponent {
}
identifyMentionKeyword(keyword, type) {
if (!this.state.showMentionsContainer) {
this.props.layoutAnimation();
}
this.setState({
showMentionsContainer: true,
showEmojiKeyboard: false,
trackingType: type
});
@ -461,16 +457,22 @@ export default class MessageBox extends React.PureComponent {
</TouchableOpacity>
);
}
renderMentions = () => (
<FlatList
key='messagebox-container'
style={styles.mentionList}
data={this.state.mentions}
renderItem={({ item }) => this.renderMentionItem(item)}
keyExtractor={item => item._id || item}
keyboardShouldPersistTaps='always'
/>
);
renderMentions = () => {
const { mentions, trackingType } = this.state;
if (!trackingType) {
return null;
}
return (
<FlatList
key='messagebox-container'
style={styles.mentionList}
data={mentions}
renderItem={({ item }) => this.renderMentionItem(item)}
keyExtractor={item => item._id || item}
keyboardShouldPersistTaps='always'
/>
);
};
renderContent() {
if (this.state.recording) {

View File

@ -15,7 +15,9 @@ export default StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
flexGrow: 0,
backgroundColor: '#fff'
backgroundColor: '#fff',
borderTopColor: '#ECECEC',
borderTopWidth: 1
},
textBoxInput: {
textAlignVertical: 'center',
@ -40,19 +42,16 @@ export default StyleSheet.create({
flex: 0
},
mentionList: {
maxHeight: MENTION_HEIGHT * 4,
borderTopColor: '#ECECEC',
borderTopWidth: 1,
paddingHorizontal: 5,
backgroundColor: '#fff'
maxHeight: MENTION_HEIGHT * 4
},
mentionItem: {
height: MENTION_HEIGHT,
backgroundColor: '#F7F8FA',
borderBottomWidth: 1,
borderBottomColor: '#ECECEC',
borderTopWidth: 1,
borderTopColor: '#ECECEC',
flexDirection: 'row',
alignItems: 'center'
alignItems: 'center',
paddingHorizontal: 5
},
mentionItemCustomEmoji: {
margin: 8,

View File

@ -6,6 +6,7 @@ import ActionSheet from 'react-native-actionsheet';
import { errorActionsHide } from '../actions/messages';
import RocketChat from '../lib/rocketchat';
import database from '../lib/realm';
import protectedFunction from '../lib/methods/helpers/protectedFunction';
@connect(
state => ({
@ -38,14 +39,14 @@ export default class MessageErrorActions extends React.Component {
}
}
handleResend = () => RocketChat.resendMessage(this.props.actionMessage._id);
handleResend = protectedFunction(() => RocketChat.resendMessage(this.props.actionMessage._id));
handleDelete = () => {
handleDelete = protectedFunction(() => {
database.write(() => {
const msg = database.objects('messages').filtered('_id = $0', this.props.actionMessage._id);
database.delete(msg);
});
}
})
handleActionPress = (actionIndex) => {
switch (actionIndex) {

View File

@ -4,7 +4,7 @@ import { ScrollView, Text, View, StyleSheet, FlatList, TouchableHighlight } from
import { connect } from 'react-redux';
import database from '../lib/realm';
import { setServer, gotoAddServer } from '../actions/server';
import { setServer } from '../actions/server';
import { logout } from '../actions/login';
const styles = StyleSheet.create({
@ -40,16 +40,14 @@ const keyExtractor = item => item.id;
server: state.server.server
}), dispatch => ({
selectServer: server => dispatch(setServer(server)),
logout: () => dispatch(logout()),
gotoAddServer: () => dispatch(gotoAddServer())
logout: () => dispatch(logout())
}))
export default class Sidebar extends Component {
static propTypes = {
server: PropTypes.string.isRequired,
selectServer: PropTypes.func.isRequired,
navigation: PropTypes.object.isRequired,
logout: PropTypes.func.isRequired,
gotoAddServer: PropTypes.func.isRequired
logout: PropTypes.func.isRequired
}
constructor(props) {
@ -120,7 +118,7 @@ export default class Sidebar extends Component {
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={() => { this.props.gotoAddServer(); }}
onPress={() => { this.props.navigation.navigate({ key: 'AddServer', routeName: 'AddServer' }); }}
>
<View style={styles.serverItem}>
<Text>

View File

@ -1,7 +1,8 @@
import EJSON from 'ejson';
import { Answers } from 'react-native-fabric';
import { AppState } from 'react-native';
import debounce from '../utils/debounce';
import log from '../utils/log';
// import { AppState, NativeModules } from 'react-native';
// const { WebSocketModule, BlobManager } = NativeModules;
@ -44,8 +45,7 @@ class EventEmitter {
try {
listener.apply(this, args);
} catch (e) {
Answers.logCustom(e);
console.warn(e);
log('EventEmitter.emit', e);
}
});
}
@ -129,7 +129,11 @@ export default class Socket extends EventEmitter {
this.send({ msg: 'connect', version: '1', support: ['1', 'pre2', 'pre1'] });
});
this._connect();
try {
this._connect();
} catch (e) {
log('ddp.constructor._connect', e);
}
}
check() {
if (!this.lastping) {
@ -213,7 +217,7 @@ export default class Socket extends EventEmitter {
this.emit(data.msg, data);
return data.collection && this.emit(data.collection, data);
} catch (err) {
Answers.logCustom('EJSON parse', err);
log('EJSON parse', err);
}
};
});
@ -234,16 +238,20 @@ export default class Socket extends EventEmitter {
delete this.connection;
this._logged = false;
this._timer = setTimeout(() => {
this._timer = setTimeout(async() => {
delete this._timer;
this._connect();
try {
await this._connect();
} catch (e) {
log('ddp.reconnect._connect', e);
}
}, 1000);
}
call(method, ...params) {
return this.send({
msg: 'method', method, params
}).then(data => data.result || data.subs).catch((err) => {
Answers.logCustom('DDP call Error', err);
log('DDP call Error', err);
return Promise.reject(err);
});
}
@ -256,8 +264,7 @@ export default class Socket extends EventEmitter {
msg: 'unsub',
id
}).then(data => data.result || data.subs).catch((err) => {
console.warn('unsubscribe', err);
Answers.logCustom('DDP unsubscribe Error', err);
log('DDP unsubscribe Error', err);
return Promise.reject(err);
});
}
@ -277,8 +284,7 @@ export default class Socket extends EventEmitter {
// console.log(args);
return args;
}).catch((err) => {
console.warn('subscribe', err);
Answers.logCustom('DDP subscribe Error', err);
log('DDP subscribe Error', err);
return Promise.reject(err);
});
}

View File

@ -1,5 +1,6 @@
import { post } from './helpers/rest';
import database from '../realm';
import log from '../../utils/log';
// TODO: api fix
const ddpTypes = {
@ -45,7 +46,12 @@ export default async function canOpenRoom({ rid, path }) {
}
const [type, name] = path.split('/');
// eslint-disable-next-line
const data = await (this.ddp && this.ddp.status ? canOpenRoomDDP.call(this, { rid, type, name }) : canOpenRoomREST.call(this, { type, rid }));
return data;
try {
// eslint-disable-next-line
const data = await (this.ddp && this.ddp.status ? canOpenRoomDDP.call(this, { rid, type, name }) : canOpenRoomREST.call(this, { type, rid }));
return data;
} catch (e) {
log('canOpenRoom', e);
}
}

View File

@ -1,9 +1,11 @@
import { InteractionManager } from 'react-native';
import reduxStore from '../createStore';
// import { get } from './helpers/rest';
import database from '../realm';
import * as actions from '../../actions';
import log from '../../utils/log';
const getLastMessage = () => {
const setting = database.objects('customEmojis').sorted('_updatedAt', true)[0];
@ -12,12 +14,16 @@ const getLastMessage = () => {
export default async function() {
const lastMessage = getLastMessage();
let emojis = await this.ddp.call('listEmojiCustom');
emojis = emojis.filter(emoji => !lastMessage || emoji._updatedAt > lastMessage);
emojis = this._prepareEmojis(emojis);
InteractionManager.runAfterInteractions(() => database.write(() => {
emojis.forEach(emoji => database.create('customEmojis', emoji, true));
}));
reduxStore.dispatch(actions.setCustomEmojis(this.parseEmojis(emojis)));
try {
const lastMessage = getLastMessage();
let emojis = await this.ddp.call('listEmojiCustom');
emojis = emojis.filter(emoji => !lastMessage || emoji._updatedAt > lastMessage);
emojis = this._prepareEmojis(emojis);
InteractionManager.runAfterInteractions(() => database.write(() => {
emojis.forEach(emoji => database.create('customEmojis', emoji, true));
}));
reduxStore.dispatch(actions.setCustomEmojis(this.parseEmojis(emojis)));
} catch (e) {
log('getCustomEmojis', e);
}
}

View File

@ -1,9 +1,11 @@
import { InteractionManager } from 'react-native';
import reduxStore from '../createStore';
// import { get } from './helpers/rest';
import database from '../realm';
import * as actions from '../../actions';
import log from '../../utils/log';
const getLastMessage = () => {
const setting = database.objects('permissions').sorted('_updatedAt', true)[0];
@ -12,11 +14,14 @@ const getLastMessage = () => {
export default async function() {
const lastMessage = getLastMessage();
const result = await (!lastMessage ? this.ddp.call('permissions/get') : this.ddp.call('permissions/get', new Date(lastMessage)));
const permissions = this._preparePermissions(result.update || result);
console.log('getPermissions', permissions);
InteractionManager.runAfterInteractions(() => database.write(() =>
permissions.forEach(permission => database.create('permissions', permission, true))));
reduxStore.dispatch(actions.setAllPermissions(this.parsePermissions(permissions)));
try {
const lastMessage = getLastMessage();
const result = await (!lastMessage ? this.ddp.call('permissions/get') : this.ddp.call('permissions/get', new Date(lastMessage)));
const permissions = this._preparePermissions(result.update || result);
InteractionManager.runAfterInteractions(() => database.write(() =>
permissions.forEach(permission => database.create('permissions', permission, true))));
reduxStore.dispatch(actions.setAllPermissions(this.parsePermissions(permissions)));
} catch (e) {
log('getPermissions', e);
}
}

View File

@ -1,8 +1,10 @@
import { InteractionManager } from 'react-native';
// import { showToast } from '../../utils/info';
import { get } from './helpers/rest';
import mergeSubscriptionsRooms, { merge } from './helpers/mergeSubscriptionsRooms';
import database from '../realm';
import log from '../../utils/log';
const lastMessage = () => {
const message = database
@ -34,18 +36,23 @@ const getRoomDpp = async function() {
export default async function() {
const { database: db } = database;
return new Promise(async(resolve) => {
// eslint-disable-next-line
const { subscriptions, rooms } = await (false && this.ddp.status ? getRoomDpp.apply(this) : getRoomRest.apply(this));
return new Promise(async(resolve, reject) => {
try {
// eslint-disable-next-line
const { subscriptions, rooms } = await (false && this.ddp.status ? getRoomDpp.apply(this) : getRoomRest.apply(this));
const data = rooms.map(room => ({ room, sub: database.objects('subscriptions').filtered('rid == $0', room._id) }));
const data = rooms.map(room => ({ room, sub: database.objects('subscriptions').filtered('rid == $0', room._id) }));
InteractionManager.runAfterInteractions(() => {
db.write(() => {
subscriptions.forEach(subscription => db.create('subscriptions', subscription, true));
data.forEach(({ sub, room }) => sub[0] && merge(sub[0], room));
InteractionManager.runAfterInteractions(() => {
db.write(() => {
subscriptions.forEach(subscription => db.create('subscriptions', subscription, true));
data.forEach(({ sub, room }) => sub[0] && merge(sub[0], room));
});
resolve(data);
});
resolve(data);
});
} catch (e) {
log('getRooms', e);
reject(e);
}
});
}

View File

@ -1,9 +1,10 @@
import { InteractionManager } from 'react-native';
import reduxStore from '../createStore';
// import { get } from './helpers/rest';
import database from '../realm';
import * as actions from '../../actions';
import log from '../../utils/log';
const getLastMessage = () => {
const [setting] = database.objects('settings').sorted('_updatedAt', true);
@ -11,14 +12,17 @@ const getLastMessage = () => {
};
export default async function() {
const lastMessage = getLastMessage();
const result = await (!lastMessage ? this.ddp.call('public-settings/get') : this.ddp.call('public-settings/get', new Date(lastMessage)));
console.log('getSettings', lastMessage, result);
try {
const lastMessage = getLastMessage();
const result = await (!lastMessage ? this.ddp.call('public-settings/get') : this.ddp.call('public-settings/get', new Date(lastMessage)));
const filteredSettings = this._prepareSettings(this._filterSettings(result.update || result));
const filteredSettings = this._prepareSettings(this._filterSettings(result.update || result));
InteractionManager.runAfterInteractions(() =>
database.write(() =>
filteredSettings.forEach(setting => database.create('settings', setting, true))));
reduxStore.dispatch(actions.addSettings(this.parseSettings(filteredSettings)));
InteractionManager.runAfterInteractions(() =>
database.write(() =>
filteredSettings.forEach(setting => database.create('settings', setting, true))));
reduxStore.dispatch(actions.addSettings(this.parseSettings(filteredSettings)));
} catch (e) {
log('getSettings', e);
}
}

View File

@ -4,7 +4,7 @@ export default fn => (params) => {
try {
fn(params);
} catch (e) {
Answers.logCustom('erro', e);
Answers.logCustom('error', e);
if (__DEV__) {
alert(e);
}

View File

@ -3,7 +3,7 @@ import { InteractionManager } from 'react-native';
import { get } from './helpers/rest';
import buildMessage from './helpers/buildMessage';
import database from '../realm';
import log from '../../utils/log';
// TODO: api fix
const types = {
@ -46,23 +46,22 @@ async function loadMessagesForRoomDDP(...args) {
}
export default async function loadMessagesForRoom(...args) {
console.log('aqui');
const { database: db } = database;
console.log('database', db);
return new Promise(async(resolve) => {
// eslint-disable-next-line
const data = (await (false && this.ddp.status ? loadMessagesForRoomDDP.call(this, ...args) : loadMessagesForRoomRest.call(this, ...args))).map(buildMessage);
if (data) {
InteractionManager.runAfterInteractions(() => {
try {
return new Promise(async(resolve, reject) => {
try {
// eslint-disable-next-line
const data = (await (false && this.ddp.status ? loadMessagesForRoomDDP.call(this, ...args) : loadMessagesForRoomRest.call(this, ...args))).map(buildMessage);
if (data) {
InteractionManager.runAfterInteractions(() => {
db.write(() => data.forEach(message => db.create('messages', message, true)));
resolve(data);
} catch (e) {
console.warn('loadMessagesForRoom', e);
}
});
return resolve(data);
});
}
return resolve([]);
} catch (e) {
log('loadMessagesForRoom', e);
reject(e);
}
return resolve([]);
});
}

View File

@ -3,7 +3,7 @@ import { InteractionManager } from 'react-native';
import { get } from './helpers/rest';
import buildMessage from './helpers/buildMessage';
import database from '../realm';
import log from '../../utils/log';
async function loadMissedMessagesRest({ rid: roomId, lastOpen: lastUpdate }) {
const { token, id } = this.ddp._login;
@ -40,21 +40,22 @@ async function loadMissedMessagesDDP(...args) {
export default async function(...args) {
const { database: db } = database;
return new Promise(async(resolve) => {
// eslint-disable-next-line
const data = (await (false && this.ddp.status ? loadMissedMessagesDDP.call(this, ...args) : loadMissedMessagesRest.call(this, ...args)));
return new Promise(async(resolve, reject) => {
try {
// eslint-disable-next-line
const data = (await (false && this.ddp.status ? loadMissedMessagesDDP.call(this, ...args) : loadMissedMessagesRest.call(this, ...args)));
if (data) {
data.forEach(buildMessage);
return InteractionManager.runAfterInteractions(() => {
try {
if (data) {
data.forEach(buildMessage);
return InteractionManager.runAfterInteractions(() => {
db.write(() => data.forEach(message => db.create('messages', message, true)));
resolve(data);
} catch (e) {
console.warn('loadMessagesForRoom', e);
}
});
});
}
resolve([]);
} catch (e) {
log('loadMissedMessages', e);
reject(e);
}
resolve([]);
});
}

View File

@ -1,5 +1,6 @@
import { post } from './helpers/rest';
import database from '../realm';
import log from '../../utils/log';
const readMessagesREST = function readMessagesREST(rid) {
const { token, id } = this.ddp._login;
@ -17,17 +18,21 @@ const readMessagesDDP = function readMessagesDDP(rid) {
export default async function readMessages(rid) {
const { database: db } = database;
// eslint-disable-next-line
const data = await (false && this.ddp.status ? readMessagesDDP.call(this, rid) : readMessagesREST.call(this, rid));
const [subscription] = db.objects('subscriptions').filtered('rid = $0', rid);
db.write(() => {
subscription.open = true;
subscription.alert = false;
subscription.unread = 0;
subscription.userMentions = 0;
subscription.groupMentions = 0;
subscription.ls = new Date();
subscription.lastOpen = new Date();
});
return data;
try {
// eslint-disable-next-line
const data = await (false && this.ddp.status ? readMessagesDDP.call(this, rid) : readMessagesREST.call(this, rid));
const [subscription] = db.objects('subscriptions').filtered('rid = $0', rid);
db.write(() => {
subscription.open = true;
subscription.alert = false;
subscription.unread = 0;
subscription.userMentions = 0;
subscription.groupMentions = 0;
subscription.ls = new Date();
subscription.lastOpen = new Date();
});
return data;
} catch (e) {
log('readMessages', e);
}
}

View File

@ -1,10 +1,11 @@
import Random from 'react-native-meteor/lib/Random';
import messagesStatus from '../../constants/messagesStatus';
import messagesStatus from '../../constants/messagesStatus';
import buildMessage from '../methods/helpers/buildMessage';
import { post } from './helpers/rest';
import database from '../realm';
import reduxStore from '../createStore';
import log from '../../utils/log';
export const getMessage = (rid, msg = {}) => {
const _id = Random.id();
@ -67,6 +68,6 @@ export default async function(rid, msg) {
db.create('messages', buildMessage({ ...message, ...ret }), true);
});
} catch (e) {
console.warn('sendMessage', e);
log('sendMessage', e);
}
}

View File

@ -3,12 +3,16 @@
// import normalizeMessage from '../helpers/normalizeMessage';
// import _buildMessage from '../helpers/buildMessage';
// import protectedFunction from '../helpers/protectedFunction';
import log from '../../../utils/log';
const subscribe = (ddp, rid) => Promise.all([
ddp.subscribe('stream-room-messages', rid, false),
ddp.subscribe('stream-notify-room', `${ rid }/typing`, false)
]);
const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch(e => console.warn(e)));
const unsubscribe = subscriptions =>
subscriptions.forEach(sub => sub.unsubscribe().catch((e) => {
log('unsubscribeRoom', e);
}));
let timer = null;
let promises;
@ -58,7 +62,7 @@ export default async function subscribeRoom({ rid, t }) {
logged = this.ddp.on('logged', () => {
clearTimeout(timer);
timer = false;
promises = subscribe(this.ddp, rid);
// promises = subscribe(this.ddp, rid);
});
disconnected = this.ddp.on('disconnected', () => {
@ -67,7 +71,11 @@ export default async function subscribeRoom({ rid, t }) {
}
});
promises = subscribe(this.ddp, rid);
try {
promises = subscribe(this.ddp, rid);
} catch (e) {
log('subscribeRoom', e);
}
}
return {

View File

@ -1,5 +1,7 @@
import database from '../../realm';
import { merge } from '../helpers/mergeSubscriptionsRooms';
import protectedFunction from '../helpers/protectedFunction';
import log from '../../../utils/log';
export default async function subscribeRooms(id) {
const subscriptions = Promise.all([
@ -41,7 +43,7 @@ export default async function subscribeRooms(id) {
}
});
this.ddp.on('stream-notify-user', (ddpMessage) => {
this.ddp.on('stream-notify-user', protectedFunction((ddpMessage) => {
const [type, data] = ddpMessage.fields.args;
const [, ev] = ddpMessage.fields.eventName.split('/');
if (/subscriptions/.test(ev)) {
@ -56,9 +58,13 @@ export default async function subscribeRooms(id) {
merge(sub, data);
});
}
});
}));
}
await subscriptions;
try {
await subscriptions;
} catch (e) {
log('subscribeRooms', e);
}
// console.log(this.ddp.subscriptions);
}

View File

@ -2,13 +2,13 @@ import { AsyncStorage, Platform } from 'react-native';
import { hashPassword } from 'react-native-meteor/lib/utils';
import foreach from 'lodash/forEach';
import Random from 'react-native-meteor/lib/Random';
import { Answers } from 'react-native-fabric';
import RNFetchBlob from 'react-native-fetch-blob';
import reduxStore from './createStore';
import settingsType from '../constants/settings';
import messagesStatus from '../constants/messagesStatus';
import database from './realm';
import log from '../utils/log';
// import * as actions from '../actions';
import { setUser, setLoginServices, removeLoginServices, loginRequest, loginSuccess, loginFailure } from '../actions/login';
@ -112,26 +112,30 @@ const RocketChat = {
this.activeUsers[ddpMessage.id] = ddpMessage.fields;
},
async loginSuccess(user) {
if (!user) {
const { user: u } = reduxStore.getState().login;
user = Object.assign({}, u);
}
// TODO: one api call
// call /me only one time
if (!user.username) {
const me = await this.me({ token: user.token, userId: user.id });
// eslint-disable-next-line
user.username = me.username;
}
if (user.username) {
const userInfo = await this.userInfo({ token: user.token, userId: user.id });
user.username = userInfo.user.username;
if (userInfo.user.roles) {
user.roles = userInfo.user.roles;
try {
if (!user) {
const { user: u } = reduxStore.getState().login;
user = Object.assign({}, u);
}
// TODO: one api call
// call /me only one time
if (!user.username) {
const me = await this.me({ token: user.token, userId: user.id });
// eslint-disable-next-line
user.username = me.username;
}
if (user.username) {
const userInfo = await this.userInfo({ token: user.token, userId: user.id });
user.username = userInfo.user.username;
if (userInfo.user.roles) {
user.roles = userInfo.user.roles;
}
}
return reduxStore.dispatch(loginSuccess(user));
} catch (e) {
log('rocketchat.loginSuccess', e);
}
return reduxStore.dispatch(loginSuccess(user));
},
connect(url, login) {
return new Promise((resolve) => {
@ -151,12 +155,12 @@ const RocketChat = {
this.ddp.on('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage)));
this.ddp.on('background', () => this.getRooms().catch(e => console.warn('background getRooms', e)));
this.ddp.on('background', () => this.getRooms().catch(e => log('background getRooms', e)));
this.ddp.on('disconnected', () => console.log('disconnected'));
this.ddp.on('logged', protectedFunction((user) => {
this.getRooms().catch(e => console.warn('logged getRooms', e));
this.getRooms().catch(e => log('logged getRooms', e));
this.loginSuccess(user);
}));
this.ddp.once('logged', protectedFunction(({ id }) => { this.subscribeRooms(id); }));
@ -420,11 +424,10 @@ const RocketChat = {
this.roles[ddpMessage.id] = (ddpMessage.fields && ddpMessage.fields.description) || undefined;
}));
this.ddp.on('error', protectedFunction((err) => {
console.warn('onError', JSON.stringify(err));
Answers.logCustom('disconnect', err);
this.ddp.on('error', (err) => {
log('rocketchat.onerror', err);
reduxStore.dispatch(connectFailure());
}));
});
// TODO: fix api (get emojis by date/version....)
@ -440,7 +443,9 @@ const RocketChat = {
this.ddp.subscribe('roles');
RocketChat.getCustomEmoji();
}));
}).catch(err => console.warn(`asd ${ err }`));
}).catch((e) => {
log('rocketchat.connect catch', e);
});
},
register({ credentials }) {
@ -502,7 +507,11 @@ const RocketChat = {
},
logout({ server }) {
if (this.ddp) {
this.ddp.logout();
try {
this.ddp.logout();
} catch (e) {
log('rocketchat.logout', e);
}
}
database.deleteAll();
AsyncStorage.removeItem(TOKEN_KEY);
@ -703,7 +712,13 @@ const RocketChat = {
return Promise.resolve(result);
},
async getPermalink(message) {
const room = await RocketChat.getRoom(message.rid);
let room;
try {
room = await RocketChat.getRoom(message.rid);
} catch (e) {
log('rocketchat.getPermalink', e);
return null;
}
const { server } = reduxStore.getState().server;
const roomType = {
p: 'group',

View File

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

View File

@ -1,10 +1,12 @@
import { AsyncStorage } from 'react-native';
import { call, put, takeLatest } from 'redux-saga/effects';
import * as actions from '../actions';
import { setServer } from '../actions/server';
import { restoreToken, setUser } from '../actions/login';
import { APP } from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
import log from '../utils/log';
const restore = function* restore() {
try {
@ -25,7 +27,7 @@ const restore = function* restore() {
yield put(actions.appReady({}));
} catch (e) {
console.warn('restore', e);
log('restore', e);
}
};

View File

@ -19,6 +19,7 @@ import {
} from '../actions/login';
import RocketChat from '../lib/rocketchat';
import * as NavigationService from '../containers/routes/NavigationService';
import log from '../utils/log';
const getUser = state => state.login;
const getServer = state => state.server.server;
@ -60,15 +61,19 @@ const forgotPasswordCall = args => RocketChat.forgotPassword(args);
// };
const saveToken = function* saveToken() {
const [server, user] = yield all([select(getServer), select(getUser)]);
yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token);
yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user));
const token = yield AsyncStorage.getItem('pushId');
if (token) {
yield RocketChat.registerPushToken(user.user.id, token);
}
if (!user.user.username && !user.isRegistering) {
yield put(registerIncomplete());
try {
const [server, user] = yield all([select(getServer), select(getUser)]);
yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token);
yield AsyncStorage.setItem(`${ RocketChat.TOKEN_KEY }-${ server }`, JSON.stringify(user));
const token = yield AsyncStorage.getItem('pushId');
if (token) {
yield RocketChat.registerPushToken(user.user.id, token);
}
if (!user.user.username && !user.isRegistering) {
yield put(registerIncomplete());
}
} catch (e) {
log('saveToken', e);
}
};
@ -130,7 +135,11 @@ const handleSetUsernameRequest = function* handleSetUsernameRequest({ credential
const handleLogout = function* handleLogout() {
const server = yield select(getServer);
if (server) {
yield call(logoutCall, { server });
try {
yield call(logoutCall, { server });
} catch (e) {
log('handleLogout', e);
}
}
};
@ -155,9 +164,9 @@ const watchLoginOpen = function* watchLoginOpen() {
}
const sub = yield RocketChat.subscribe('meteor.loginServiceConfiguration');
yield take(types.LOGIN.CLOSE);
sub.unsubscribe().catch(e => console.warn('watchLoginOpen unsubscribe', e));
} catch (error) {
console.warn('watchLoginOpen', error);
sub.unsubscribe();
} catch (e) {
log('watchLoginOpen', e);
}
};

View File

@ -1,26 +1,36 @@
import { put, takeLatest } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
import { readyMentionedMessages } from '../actions/mentionedMessages';
import log from '../utils/log';
let sub;
let newSub;
const openMentionedMessagesRoom = function* openMentionedMessagesRoom({ rid, limit }) {
newSub = yield RocketChat.subscribe('mentionedMessages', rid, limit);
yield put(readyMentionedMessages());
if (sub) {
sub.unsubscribe().catch(e => console.warn('openMentionedMessagesRoom', e));
try {
newSub = yield RocketChat.subscribe('mentionedMessages', rid, limit);
yield put(readyMentionedMessages());
if (sub) {
sub.unsubscribe();
}
sub = newSub;
} catch (e) {
log('openMentionedMessagesRoom', e);
}
sub = newSub;
};
const closeMentionedMessagesRoom = function* closeMentionedMessagesRoom() {
if (sub) {
yield sub.unsubscribe().catch(e => console.warn('closeMentionedMessagesRoom sub', e));
}
if (newSub) {
yield newSub.unsubscribe().catch(e => console.warn('closeMentionedMessagesRoom newSub', e));
try {
if (sub) {
yield sub.unsubscribe();
}
if (newSub) {
yield newSub.unsubscribe();
}
} catch (e) {
log('closeMentionedMessagesRoom', e);
}
};

View File

@ -1,26 +1,36 @@
import { put, takeLatest } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
import { readyPinnedMessages } from '../actions/pinnedMessages';
import log from '../utils/log';
let sub;
let newSub;
const openPinnedMessagesRoom = function* openPinnedMessagesRoom({ rid, limit }) {
newSub = yield RocketChat.subscribe('pinnedMessages', rid, limit);
yield put(readyPinnedMessages());
if (sub) {
sub.unsubscribe().catch(e => console.warn('openPinnedMessagesRoom', e));
try {
newSub = yield RocketChat.subscribe('pinnedMessages', rid, limit);
yield put(readyPinnedMessages());
if (sub) {
sub.unsubscribe();
}
sub = newSub;
} catch (e) {
log('openPinnedMessagesRoom', e);
}
sub = newSub;
};
const closePinnedMessagesRoom = function* closePinnedMessagesRoom() {
if (sub) {
yield sub.unsubscribe().catch(e => console.warn('closePinnedMessagesRoom sub', e));
}
if (newSub) {
yield newSub.unsubscribe().catch(e => console.warn('closePinnedMessagesRoom newSub', e));
try {
if (sub) {
yield sub.unsubscribe();
}
if (newSub) {
yield newSub.unsubscribe();
}
} catch (e) {
log('closePinnedMessagesRoom', e);
}
};

View File

@ -1,26 +1,36 @@
import { put, takeLatest } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
import { readyRoomFiles } from '../actions/roomFiles';
import log from '../utils/log';
let sub;
let newSub;
const openRoomFiles = function* openRoomFiles({ rid, limit }) {
newSub = yield RocketChat.subscribe('roomFiles', rid, limit);
yield put(readyRoomFiles());
if (sub) {
sub.unsubscribe().catch(e => console.warn('openRoomFiles', e));
try {
newSub = yield RocketChat.subscribe('roomFiles', rid, limit);
yield put(readyRoomFiles());
if (sub) {
sub.unsubscribe();
}
sub = newSub;
} catch (e) {
log('openRoomFiles', e);
}
sub = newSub;
};
const closeRoomFiles = function* closeRoomFiles() {
if (sub) {
yield sub.unsubscribe().catch(e => console.warn('closeRoomFiles sub', e));
}
if (newSub) {
yield newSub.unsubscribe().catch(e => console.warn('closeRoomFiles newSub', e));
try {
if (sub) {
yield sub.unsubscribe();
}
if (newSub) {
yield newSub.unsubscribe();
}
} catch (e) {
log('closeRoomFiles', e);
}
};

View File

@ -2,6 +2,7 @@ import { Alert } from 'react-native';
import { put, call, takeLatest, take, select, race, fork, cancel, takeEvery } from 'redux-saga/effects';
import { delay } from 'redux-saga';
import { BACKGROUND } from 'redux-enhancer-react-native-appstate';
import * as types from '../actions/actionsTypes';
// import { roomsSuccess, roomsFailure } from '../actions/rooms';
import { addUserTyping, removeUserTyping, setLastOpen } from '../actions/room';
@ -9,10 +10,14 @@ import { messagesRequest } from '../actions/messages';
import RocketChat from '../lib/rocketchat';
import database from '../lib/realm';
import * as NavigationService from '../containers/routes/NavigationService';
import log from '../utils/log';
const leaveRoom = rid => RocketChat.leaveRoom(rid);
const eraseRoom = rid => RocketChat.eraseRoom(rid);
let sub;
let thread;
// const getRooms = function* getRooms() {
// return yield RocketChat.getRooms();
// };
@ -66,30 +71,34 @@ const handleMessageReceived = function* handleMessageReceived({ message }) {
};
const watchRoomOpen = function* watchRoomOpen({ room }) {
yield put(messagesRequest({ ...room }));
// const { open } = yield race({
// messages: take(types.MESSAGES.SUCCESS),
// open: take(types.ROOM.OPEN)
// });
//
// if (open) {
// return;
// }
try {
yield put(messagesRequest({ ...room }));
// const { open } = yield race({
// messages: take(types.MESSAGES.SUCCESS),
// open: take(types.ROOM.OPEN)
// });
//
// if (open) {
// return;
// }
RocketChat.readMessages(room.rid);
const sub = yield RocketChat.subscribeRoom(room);
// const subscriptions = yield Promise.all([RocketChat.subscribe('stream-room-messages', room.rid, false), RocketChat.subscribe('stream-notify-room', `${ room.rid }/typing`, false)]);
const thread = yield fork(usersTyping, { rid: room.rid });
yield race({
open: take(types.ROOM.OPEN),
close: take(types.ROOM.CLOSE)
});
cancel(thread);
sub.stop();
RocketChat.readMessages(room.rid);
sub = yield RocketChat.subscribeRoom(room);
// const subscriptions = yield Promise.all([RocketChat.subscribe('stream-room-messages', room.rid, false), RocketChat.subscribe('stream-notify-room', `${ room.rid }/typing`, false)]);
thread = yield fork(usersTyping, { rid: room.rid });
yield race({
open: take(types.ROOM.OPEN),
close: take(types.ROOM.CLOSE)
});
cancel(thread);
sub.stop();
// subscriptions.forEach((sub) => {
// sub.unsubscribe().catch(e => alert(e));
// });
// subscriptions.forEach((sub) => {
// sub.unsubscribe().catch(e => alert(e));
// });
} catch (e) {
log('watchRoomOpen', e);
}
};
const watchuserTyping = function* watchuserTyping({ status }) {
@ -103,11 +112,16 @@ const watchuserTyping = function* watchuserTyping({ status }) {
if (!room) {
return;
}
yield RocketChat.emitTyping(room.rid, status);
if (status) {
yield call(delay, 5000);
yield RocketChat.emitTyping(room.rid, false);
try {
yield RocketChat.emitTyping(room.rid, status);
if (status) {
yield call(delay, 5000);
yield RocketChat.emitTyping(room.rid, false);
}
} catch (e) {
log('watchuserTyping', e);
}
};
@ -136,6 +150,7 @@ const goRoomsListAndDelete = function* goRoomsListAndDelete(rid) {
const handleLeaveRoom = function* handleLeaveRoom({ rid }) {
try {
sub.stop();
yield call(leaveRoom, rid);
yield goRoomsListAndDelete(rid);
} catch (e) {
@ -149,6 +164,7 @@ const handleLeaveRoom = function* handleLeaveRoom({ rid }) {
const handleEraseRoom = function* handleEraseRoom({ rid }) {
try {
sub.stop();
yield call(eraseRoom, rid);
yield goRoomsListAndDelete(rid);
} catch (e) {

View File

@ -1,6 +1,7 @@
import { put, call, takeLatest, take } from 'redux-saga/effects';
import { delay } from 'redux-saga';
import { AsyncStorage } from 'react-native';
import { SERVER, LOGIN } from '../actions/actionsTypes';
import * as actions from '../actions';
import { connectRequest } from '../actions/connect';
@ -9,6 +10,7 @@ import { setRoles } from '../actions/roles';
import RocketChat from '../lib/rocketchat';
import database from '../lib/realm';
import { navigate } from '../containers/routes/NavigationService';
import log from '../utils/log';
const validate = function* validate(server) {
return yield RocketChat.testServer(server);
@ -36,7 +38,7 @@ const selectServer = function* selectServer({ server }) {
yield put(connectRequest());
} catch (e) {
console.warn('selectServer', e);
log('selectServer', e);
}
};
@ -52,23 +54,21 @@ const validateServer = function* validateServer({ server }) {
};
const addServer = function* addServer({ server }) {
database.databases.serversDB.write(() => {
database.databases.serversDB.create('servers', { id: server, current: false }, true);
});
yield put(setServer(server));
yield take(LOGIN.SET_TOKEN);
navigate('LoginSignup');
};
const handleGotoAddServer = function* handleGotoAddServer() {
yield call(AsyncStorage.removeItem, RocketChat.TOKEN_KEY);
yield call(navigate, 'AddServer');
try {
database.databases.serversDB.write(() => {
database.databases.serversDB.create('servers', { id: server, current: false }, true);
});
yield put(setServer(server));
yield take(LOGIN.SET_TOKEN);
navigate('LoginSignup');
} catch (e) {
log('addServer', e);
}
};
const root = function* root() {
yield takeLatest(SERVER.REQUEST, validateServer);
yield takeLatest(SERVER.SELECT, selectServer);
yield takeLatest(SERVER.ADD, addServer);
yield takeLatest(SERVER.GOTO_ADD, handleGotoAddServer);
};
export default root;

View File

@ -1,26 +1,36 @@
import { put, takeLatest } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
import { readySnippetedMessages } from '../actions/snippetedMessages';
import log from '../utils/log';
let sub;
let newSub;
const openSnippetedMessagesRoom = function* openSnippetedMessagesRoom({ rid, limit }) {
newSub = yield RocketChat.subscribe('snippetedMessages', rid, limit);
yield put(readySnippetedMessages());
if (sub) {
sub.unsubscribe().catch(e => console.warn('openSnippetedMessagesRoom', e));
try {
newSub = yield RocketChat.subscribe('snippetedMessages', rid, limit);
yield put(readySnippetedMessages());
if (sub) {
sub.unsubscribe();
}
sub = newSub;
} catch (e) {
log('openSnippetedMessagesRoom', e);
}
sub = newSub;
};
const closeSnippetedMessagesRoom = function* closeSnippetedMessagesRoom() {
if (sub) {
yield sub.unsubscribe().catch(e => console.warn('closeSnippetedMessagesRoom sub', e));
}
if (newSub) {
yield newSub.unsubscribe().catch(e => console.warn('closeSnippetedMessagesRoom newSub', e));
try {
if (sub) {
yield sub.unsubscribe();
}
if (newSub) {
yield newSub.unsubscribe();
}
} catch (e) {
log('closeSnippetedMessagesRoom', e);
}
};

View File

@ -1,26 +1,36 @@
import { put, takeLatest } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
import { readyStarredMessages } from '../actions/starredMessages';
import log from '../utils/log';
let sub;
let newSub;
const openStarredMessagesRoom = function* openStarredMessagesRoom({ rid, limit }) {
newSub = yield RocketChat.subscribe('starredMessages', rid, limit);
yield put(readyStarredMessages());
if (sub) {
sub.unsubscribe().catch(e => console.warn('openStarredMessagesRoom', e));
try {
newSub = yield RocketChat.subscribe('starredMessages', rid, limit);
yield put(readyStarredMessages());
if (sub) {
sub.unsubscribe();
}
sub = newSub;
} catch (e) {
log('openStarredMessagesRoom', e);
}
sub = newSub;
};
const closeStarredMessagesRoom = function* closeStarredMessagesRoom() {
if (sub) {
yield sub.unsubscribe().catch(e => console.warn('closeStarredMessagesRoom sub', e));
}
if (newSub) {
yield newSub.unsubscribe().catch(e => console.warn('closeStarredMessagesRoom newSub', e));
try {
if (sub) {
yield sub.unsubscribe();
}
if (newSub) {
yield newSub.unsubscribe();
}
} catch (e) {
log('closeStarredMessagesRoom', e);
}
};

View File

@ -2,13 +2,18 @@ import { takeLatest, select } from 'redux-saga/effects';
import { FOREGROUND, BACKGROUND, INACTIVE } from 'redux-enhancer-react-native-appstate';
import RocketChat from '../lib/rocketchat';
import log from '../utils/log';
const appHasComeBackToForeground = function* appHasComeBackToForeground() {
const auth = yield select(state => state.login.isAuthenticated);
if (!auth) {
return;
}
return yield RocketChat.setUserPresenceOnline();
try {
return yield RocketChat.setUserPresenceOnline();
} catch (e) {
log('appHasComeBackToForeground', e);
}
};
const appHasComeBackToBackground = function* appHasComeBackToBackground() {
@ -16,7 +21,11 @@ const appHasComeBackToBackground = function* appHasComeBackToBackground() {
if (!auth) {
return;
}
return yield RocketChat.setUserPresenceAway();
try {
return yield RocketChat.setUserPresenceAway();
} catch (e) {
log('appHasComeBackToBackground', e);
}
};
const root = function* root() {

8
app/utils/log.js Normal file
View File

@ -0,0 +1,8 @@
import { Answers } from 'react-native-fabric';
export default (event, error) => {
Answers.logCustom(event, error);
if (__DEV__) {
console.warn(event, error);
}
};

View File

@ -73,10 +73,6 @@ export default class ForgotPasswordView extends LoggedView {
this.props.forgotPasswordRequest(email);
}
backLogin = () => {
this.props.navigation.goBack();
}
render() {
return (
<KeyboardView

View File

@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
// import Zeroconf from 'react-native-zeroconf';
import { View, Text, SectionList, StyleSheet, SafeAreaView } from 'react-native';
import { connect } from 'react-redux';
import { withNavigationFocus } from 'react-navigation';
import LoggedView from './View';
import { setServer } from '../actions/server';
@ -71,7 +72,7 @@ const styles = StyleSheet.create({
}), dispatch => ({
selectServer: server => dispatch(setServer(server))
}))
export default class ListServerView extends LoggedView {
class ListServerView extends LoggedView {
static propTypes = {
navigation: PropTypes.object.isRequired,
login: PropTypes.object.isRequired,
@ -127,8 +128,10 @@ export default class ListServerView extends LoggedView {
jumpToSelectedServer() {
if (this.props.server && !this.props.login.isRegistering) {
setTimeout(() => {
this.openLogin();
}, 300);
if (this.props.isFocused) {
this.openLogin();
}
}, 500);
}
}
@ -219,3 +222,4 @@ export default class ListServerView extends LoggedView {
);
}
}
export default withNavigationFocus(ListServerView);

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Text, ScrollView, View, SafeAreaView } from 'react-native';
import { Text, ScrollView, View, SafeAreaView, Keyboard } from 'react-native';
import { connect } from 'react-redux';
import { serverRequest, addServer } from '../actions/server';
@ -48,6 +48,7 @@ export default class NewServerView extends LoggedView {
}
submit = () => {
Keyboard.dismiss();
this.props.addServer(this.completeUrl(this.state.text));
}
@ -114,6 +115,7 @@ export default class NewServerView extends LoggedView {
placeholder={this.state.defaultServer}
returnKeyType='done'
onChangeText={this.onChangeText}
onSubmitEditing={this.submit}
/>
{this.renderValidation()}
<View style={[styles.alignItemsFlexStart, { marginTop: 20 }]}>

View File

@ -15,6 +15,7 @@ import database from '../../lib/realm';
import RocketChat from '../../lib/rocketchat';
import { leaveRoom } from '../../actions/room';
import { setLoading } from '../../actions/selectedUsers';
import log from '../../utils/log';
import RoomTypeIcon from '../../containers/RoomTypeIcon';
const renderSeparator = () => <View style={styles.separator} />;
@ -87,14 +88,14 @@ export default class RoomActionsView extends LoggedView {
}
updateRoomMember = async() => {
if (this.state.room.t === 'd') {
if (this.state.room.t !== 'd') {
return {};
}
try {
const member = await RocketChat.getRoomMember(this.state.room.rid, this.props.user_id);
return { member };
} catch (error) {
console.warn('RoomActions updateRoomMember', error);
} catch (e) {
log('RoomActions updateRoomMember', e);
return {};
}
}
@ -222,8 +223,8 @@ export default class RoomActionsView extends LoggedView {
this.props.setLoadingInvite(true);
await RocketChat.addUsersToRoom(rid);
this.props.navigation.goBack();
} catch (error) {
console.warn('RoomActions Add User', error);
} catch (e) {
log('RoomActions Add User', e);
} finally {
this.props.setLoadingInvite(false);
}
@ -250,7 +251,11 @@ export default class RoomActionsView extends LoggedView {
toggleBlockUser = () => {
const { rid, blocked } = this.state.room;
const { member } = this.state;
RocketChat.toggleBlockUser(rid, member._id, !blocked);
try {
RocketChat.toggleBlockUser(rid, member._id, !blocked);
} catch (e) {
log('toggleBlockUser', e);
}
}
leaveChannel = () => {
@ -274,7 +279,11 @@ export default class RoomActionsView extends LoggedView {
toggleNotifications = () => {
const { room } = this.state;
RocketChat.saveNotificationSettings(room.rid, 'mobilePushNotifications', room.notifications ? 'default' : 'nothing');
try {
RocketChat.saveNotificationSettings(room.rid, 'mobilePushNotifications', room.notifications ? 'default' : 'nothing');
} catch (e) {
log('toggleNotifications', e);
}
}
renderRoomInfo = ({ item }) => {

View File

@ -16,6 +16,7 @@ import RCTextInput from '../../containers/TextInput';
import Loading from '../../containers/Loading';
import SwitchContainer from './SwitchContainer';
import random from '../../utils/random';
import log from '../../utils/log';
const PERMISSION_SET_READONLY = 'set-readonly';
const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly';
@ -182,6 +183,7 @@ export default class RoomInfoEditView extends LoggedView {
this.setState({ nameError: e });
}
error = true;
log('saveRoomSettings', e);
}
await this.setState({ saving: false });
@ -230,8 +232,8 @@ export default class RoomInfoEditView extends LoggedView {
onPress: () => {
try {
RocketChat.toggleArchiveRoom(this.state.room.rid, !archived);
} catch (error) {
console.warn('toggleArchive', error);
} catch (e) {
log('toggleArchive', e);
}
}
}
@ -274,7 +276,6 @@ export default class RoomInfoEditView extends LoggedView {
value={description}
onChangeText={value => this.setState({ description: value })}
onSubmitEditing={() => { this.topic.focus(); }}
inputProps={{ multiline: true }}
/>
<RCTextInput
inputRef={(e) => { this.topic = e; }}
@ -282,7 +283,6 @@ export default class RoomInfoEditView extends LoggedView {
value={topic}
onChangeText={value => this.setState({ topic: value })}
onSubmitEditing={() => { this.announcement.focus(); }}
inputProps={{ multiline: true }}
/>
<RCTextInput
inputRef={(e) => { this.announcement = e; }}
@ -290,7 +290,6 @@ export default class RoomInfoEditView extends LoggedView {
value={announcement}
onChangeText={value => this.setState({ announcement: value })}
onSubmitEditing={() => { this.joinCode.focus(); }}
inputProps={{ multiline: true }}
/>
<RCTextInput
inputRef={(e) => { this.joinCode = e; }}
@ -298,7 +297,7 @@ export default class RoomInfoEditView extends LoggedView {
value={joinCode}
onChangeText={value => this.setState({ joinCode: value })}
onSubmitEditing={this.submit}
inputProps={{ secureTextEntry: true }}
secureTextEntry
/>
<SwitchContainer
value={t}

View File

@ -13,6 +13,8 @@ import sharedStyles from '../Styles';
import database from '../../lib/realm';
import RocketChat from '../../lib/rocketchat';
import Touch from '../../utils/touch';
import log from '../../utils/log';
import RoomTypeIcon from '../../containers/RoomTypeIcon';
const PERMISSION_EDIT_ROOM = 'edit-room';
@ -98,8 +100,8 @@ export default class RoomInfoView extends LoggedView {
if (userRoles) {
this.setState({ roles: userRoles.roles || [] });
}
} catch (error) {
console.warn('RoomInfoView', error);
} catch (e) {
log('RoomInfoView.componentDidMount', e);
}
} else {
const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], this.state.room.rid);
@ -163,25 +165,28 @@ export default class RoomInfoView extends LoggedView {
return null;
}
renderAvatar = (room, roomUser) => (
<Avatar
text={room.name}
size={100}
style={styles.avatar}
type={room.t}
>
{room.t === 'd' ? <Status style={[sharedStyles.status, styles.status]} id={roomUser._id} /> : null}
</Avatar>
)
render() {
const { room, roomUser } = this.state;
const { name, t } = room;
if (!room) {
return <View />;
}
return (
<ScrollView style={styles.container}>
<View style={styles.avatarContainer}>
<Avatar
text={name}
size={100}
style={styles.avatar}
type={t}
>
{t === 'd' ? <Status style={[sharedStyles.status, styles.status]} id={roomUser._id} /> : null}
</Avatar>
<Text style={styles.roomTitle}>
{ getRoomTitle(room) }
</Text>
{this.renderAvatar(room, roomUser)}
<Text style={styles.roomTitle}>{ this.getRoomTitle(room) }</Text>
</View>
{!this.isDirect() && this.renderItem('description', room)}
{!this.isDirect() && this.renderItem('topic', room)}
{!this.isDirect() && this.renderItem('announcement', room)}

View File

@ -6,7 +6,6 @@ import ActionSheet from 'react-native-actionsheet';
import LoggedView from '../View';
import styles from './styles';
import RoomItem from '../../presentation/RoomItem';
import Touch from '../../utils/touch';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
@ -14,6 +13,7 @@ import RocketChat from '../../lib/rocketchat';
import { goRoom } from '../../containers/routes/NavigationService';
import database from '../../lib/realm';
import { showToast } from '../../utils/info';
import log from '../../utils/log';
@connect(state => ({
user: state.login.user,
@ -93,20 +93,28 @@ export default class MentionedMessagesView extends LoggedView {
}
onPressToogleStatus = async() => {
const allUsers = !this.state.allUsers;
this.props.navigation.setParams({ allUsers });
const membersResult = await RocketChat.getRoomMembers(this.state.rid, allUsers);
const members = membersResult.records;
this.setState({ allUsers, members });
try {
const allUsers = !this.state.allUsers;
this.props.navigation.setParams({ allUsers });
const membersResult = await RocketChat.getRoomMembers(this.state.rid, allUsers);
const members = membersResult.records;
this.setState({ allUsers, members });
} catch (e) {
log('onPressToogleStatus', e);
}
}
onPressUser = async(item) => {
const subscriptions = database.objects('subscriptions').filtered('name = $0', item.username);
if (subscriptions.length) {
goRoom({ rid: subscriptions[0].rid, name: subscriptions[0].name });
} else {
const room = await RocketChat.createDirectMessage(item.username);
goRoom({ room: room.rid, name: item.username });
try {
const subscriptions = database.objects('subscriptions').filtered('name = $0', item.username);
if (subscriptions.length) {
goRoom({ rid: subscriptions[0].rid, name: subscriptions[0].name });
} else {
const room = await RocketChat.createDirectMessage(item.username);
goRoom({ rid: room.rid, name: item.username });
}
} catch (e) {
log('onPressUser', e);
}
}
@ -133,8 +141,8 @@ export default class MentionedMessagesView extends LoggedView {
try {
await RocketChat.toggleMuteUserInRoom(rid, userLongPressed.username, !userLongPressed.muted);
showToast(`User has been ${ userLongPressed.muted ? 'unmuted' : 'muted' }!`);
} catch (error) {
console.warn('handleMute', error);
} catch (e) {
log('handleMute', e);
}
}
@ -158,6 +166,8 @@ export default class MentionedMessagesView extends LoggedView {
placeholder='Search'
clearButtonMode='while-editing'
blurOnSubmit
autoCorrect={false}
autoCapitalize='none'
/>
</View>
)

View File

@ -11,6 +11,8 @@ import Avatar from '../../../containers/Avatar';
import { STATUS_COLORS } from '../../../constants/colors';
import styles from './styles';
import { closeRoom } from '../../../actions/room';
import log from '../../../utils/log';
import RoomTypeIcon from '../../../containers/RoomTypeIcon';
const title = (offline, connecting, authenticating, logged) => {
@ -108,7 +110,7 @@ export default class RoomHeaderView extends React.PureComponent {
renderCenter() {
if (!this.state.room.name) {
return null;
return <View style={styles.titleContainer} />;
}
let accessibilityLabel = this.state.room.name;
@ -134,7 +136,7 @@ export default class RoomHeaderView extends React.PureComponent {
style={styles.titleContainer}
accessibilityLabel={accessibilityLabel}
accessibilityTraits='header'
onPress={() => this.props.navigation.navigate({ key: 'RoomInfo', routeName: 'RoomInfo', params: { rid: this.state.rid } })}
onPress={() => this.props.navigation.navigate({ key: 'RoomInfo', routeName: 'RoomInfo', params: this.state.room })}
>
<Avatar
@ -165,7 +167,13 @@ export default class RoomHeaderView extends React.PureComponent {
<View style={styles.right}>
<TouchableOpacity
style={styles.headerButton}
onPress={() => RocketChat.toggleFavorite(this.state.room.rid, this.state.room.f)}
onPress={() => {
try {
RocketChat.toggleFavorite(this.state.room.rid, this.state.room.f);
} catch (e) {
log('toggleFavorite', e);
}
}}
accessibilityLabel='Star room'
accessibilityTraits='button'
>

View File

@ -12,6 +12,7 @@ import styles from './styles';
import Typing from '../../containers/Typing';
import database from '../../lib/realm';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import throttle from '../../utils/throttle';
const DEFAULT_SCROLL_CALLBACK_THROTTLE = 100;
@ -54,13 +55,13 @@ export class List extends React.Component {
this.data.removeAllListeners();
this.updateState.stop();
}
updateState = () => {
updateState = throttle(() => {
// this.setState({
this.dataSource = this.dataSource.cloneWithRows(this.data);
LayoutAnimation.easeInEaseOut();
this.forceUpdate();
// });
};
}, 1000);
render() {
return (<ListView

View File

@ -20,6 +20,7 @@ import Header from '../../containers/Header';
import RoomsHeader from './Header';
import ReactionPicker from './ReactionPicker';
import styles from './styles';
import log from '../../utils/log';
@connect(
state => ({
@ -27,8 +28,7 @@ import styles from './styles';
Message_TimeFormat: state.settings.Message_TimeFormat,
loading: state.messages.isFetching,
user: state.login.user,
actionMessage: state.messages.actionMessage,
layoutAnimation: state.room.layoutAnimation
actionMessage: state.messages.actionMessage
}),
dispatch => ({
actions: bindActionCreators(actions, dispatch),
@ -76,14 +76,6 @@ export default class RoomView extends LoggedView {
async componentDidMount() {
await this.updateRoom();
await this.props.openRoom({
...this.state.room
});
if (this.state.room.alert || this.state.room.unread || this.state.room.userMentions) {
this.props.setLastOpen(this.state.room.ls);
} else {
this.props.setLastOpen(null);
}
this.rooms.addListener(this.updateRoom);
}
shouldComponentUpdate(nextProps, nextState) {
@ -116,16 +108,31 @@ export default class RoomView extends LoggedView {
}
onReactionPress = (shortname, messageId) => {
if (!messageId) {
RocketChat.setReaction(shortname, this.props.actionMessage._id);
return this.props.toggleReactionPicker();
try {
if (!messageId) {
RocketChat.setReaction(shortname, this.props.actionMessage._id);
return this.props.toggleReactionPicker();
}
RocketChat.setReaction(shortname, messageId);
} catch (e) {
log('RoomView.onReactionPress', e);
}
RocketChat.setReaction(shortname, messageId);
};
updateRoom = async() => {
if (this.rooms.length > 0) {
const { room: prevRoom } = this.state;
await this.setState({ room: JSON.parse(JSON.stringify(this.rooms[0])) });
if (!prevRoom.rid) {
await this.props.openRoom({
...this.state.room
});
if (this.state.room.alert || this.state.room.unread || this.state.room.userMentions) {
this.props.setLastOpen(this.state.room.ls);
} else {
this.props.setLastOpen(null);
}
}
}
}
@ -136,10 +143,14 @@ export default class RoomView extends LoggedView {
};
joinRoom = async() => {
await RocketChat.joinRoom(this.props.rid);
this.setState({
joined: true
});
try {
await RocketChat.joinRoom(this.props.rid);
this.setState({
joined: true
});
} catch (e) {
log('joinRoom', e);
}
};
renderItem = (item, previousItem) => (

View File

@ -13,6 +13,7 @@ import RocketChat from '../../../lib/rocketchat';
import { STATUS_COLORS } from '../../../constants/colors';
import { setSearch } from '../../../actions/rooms';
import styles from './styles';
import log from '../../../utils/log';
const title = (offline, connecting, authenticating, logged) => {
if (offline) {
@ -65,7 +66,11 @@ export default class RoomsListHeaderView extends React.PureComponent {
}
onPressModalButton(status) {
RocketChat.setUserPresenceDefaultStatus(status);
try {
RocketChat.setUserPresenceDefaultStatus(status);
} catch (e) {
log('onPressModalButton', e);
}
this.hideModal();
}
@ -103,8 +108,11 @@ export default class RoomsListHeaderView extends React.PureComponent {
}
createChannel() {
const params = this.props.navigation.state.params || {};
params.createChannel();
this.props.navigation.navigate({
key: 'SelectedUsers',
routeName: 'SelectedUsers',
params: { nextAction: () => this.props.navigation.navigate('CreateChannel') }
});
}
renderLeft() {
@ -234,6 +242,8 @@ export default class RoomsListHeaderView extends React.PureComponent {
placeholder='Search'
clearButtonMode='while-editing'
blurOnSubmit
autoCorrect={false}
autoCapitalize='none'
/>
</View>
);

View File

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import Icon from 'react-native-vector-icons/Ionicons';
import { Platform, View, TextInput, FlatList, LayoutAnimation } from 'react-native';
import { connect } from 'react-redux';
import database from '../../lib/realm';
import RocketChat from '../../lib/rocketchat';
import RoomItem from '../../presentation/RoomItem';
@ -13,6 +14,7 @@ import RoomsListHeader from './Header';
import styles from './styles';
import throttle from '../../utils/throttle';
import LoggedView from '../View';
import log from '../../utils/log';
@connect(state => ({
user: state.login.user,
@ -45,12 +47,6 @@ export default class RoomsListView extends LoggedView {
componentDidMount() {
this.data.addListener(this.updateState);
this.props.navigation.setParams({
createChannel: () => this._createChannel()
});
this.updateState();
}
componentWillReceiveProps(props) {
@ -88,7 +84,7 @@ export default class RoomsListView extends LoggedView {
let data = database.objects('subscriptions').filtered('name CONTAINS[c] $0', searchText).slice(0, 7);
const usernames = data.map(sub => sub.map);
const usernames = data.map(sub => sub.name);
try {
if (data.length < 7) {
if (this.oldPromise) {
@ -124,21 +120,21 @@ export default class RoomsListView extends LoggedView {
_onPressItem = async(item = {}) => {
if (!item.search) {
return goRoom({ rid: item.rid });
return goRoom({ rid: item.rid, name: item.name });
}
if (item.t === 'd') {
// if user is using the search we need first to join/create room
try {
const sub = await RocketChat.createDirectMessage(item.username);
return goRoom(sub);
} catch (error) {
console.warn('_onPressItem', error);
} catch (e) {
log('RoomsListView._onPressItem', e);
}
}
return goRoom(item);
}
_createChannel() {
createChannel() {
this.props.navigation.navigate({
key: 'SelectedUsers',
routeName: 'SelectedUsers',
@ -160,6 +156,8 @@ export default class RoomsListView extends LoggedView {
placeholder='Search'
clearButtonMode='while-editing'
blurOnSubmit
autoCorrect={false}
autoCapitalize='none'
/>
</View>
);
@ -198,7 +196,7 @@ export default class RoomsListView extends LoggedView {
renderCreateButtons = () => (
<ActionButton buttonColor='rgba(231,76,60,1)'>
<ActionButton.Item buttonColor='#9b59b6' title='Create Channel' onPress={() => { this._createChannel(); }} >
<ActionButton.Item buttonColor='#9b59b6' title='Create Channel' onPress={() => { this.createChannel(); }} >
<Icon name='md-chatbubbles' style={styles.actionButtonIcon} />
</ActionButton.Item>
</ActionButton>

View File

@ -13,6 +13,7 @@ import RocketChat from '../../lib/rocketchat';
import buildMessage from '../../lib/methods/helpers/buildMessage';
import Message from '../../containers/message';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import log from '../../utils/log';
@connect(state => ({
user: state.login.user,
@ -50,14 +51,14 @@ export default class SearchMessagesView extends LoggedView {
let messages = [];
try {
const result = await Promise.race([RocketChat.messageSearch(this.searchText, this.props.navigation.state.params.rid, this.limit), cancel]);
messages = result.messages.map(message => buildMessage(message));
messages = result.message.docs.map(message => buildMessage(message));
this.setState({ messages, searching: false, loadingMore: false });
} catch (error) {
} catch (e) {
this._cancel = null;
if (error !== 'cancel') {
if (e !== 'cancel') {
return this.setState({ searching: false, loadingMore: false });
}
console.warn('search', error);
log('SearchMessagesView.search', e);
}
}
@ -92,9 +93,13 @@ export default class SearchMessagesView extends LoggedView {
customTimeFormat='MMMM Do YYYY, h:mm:ss a'
onLongPress={() => {}}
onReactionPress={async(emoji) => {
await RocketChat.setReaction(emoji, item._id);
this.search();
this.forceUpdate();
try {
await RocketChat.setReaction(emoji, item._id);
this.search();
this.forceUpdate();
} catch (e) {
log('SearchMessagesView.onReactionPress', e);
}
}}
/>
);

View File

@ -101,7 +101,7 @@ export default class SelectedUsersView extends LoggedView {
justifyContent: 'center'
}}
onPress={() => params.nextAction()}
accessibilityLabel='Create channel'
accessibilityLabel='Submit'
accessibilityTraits='button'
>
<Icon
@ -229,6 +229,8 @@ export default class SelectedUsersView extends LoggedView {
placeholder='Search'
clearButtonMode='while-editing'
blurOnSubmit
autoCorrect={false}
autoCapitalize='none'
/>
</View>
);

2
package-lock.json generated
View File

@ -15921,7 +15921,7 @@
}
},
"react-native-keyboard-input": {
"version": "git+https://github.com/RocketChat/react-native-keyboard-input.git#1b5c45176e846ec5eb18e6d24c11c0481783a6d2",
"version": "git+https://github.com/RocketChat/react-native-keyboard-input.git#67a441ee3e6166fa8727b0e6969010da0d710db6",
"requires": {
"lodash": "4.17.5",
"react-native-keyboard-tracking-view": "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git#82be12805eb3aa448c1f09f545c334e4776b3148"