diff --git a/app/components/Message.js b/app/components/Message.js
index bbf3d0cbf..8a7e74b23 100644
--- a/app/components/Message.js
+++ b/app/components/Message.js
@@ -71,7 +71,6 @@ export default class Message extends React.PureComponent {
let initials = usernameParts.length > 1 ? usernameParts[0][0] + usernameParts[usernameParts.length - 1][0] : username.replace(/[^A-Za-z0-9]/g, '').substr(0, 2);
initials = initials.toUpperCase();
-
return (
diff --git a/app/components/MessageBox.js b/app/components/MessageBox.js
index 8bd34bca7..2485032a7 100644
--- a/app/components/MessageBox.js
+++ b/app/components/MessageBox.js
@@ -17,6 +17,7 @@ const styles = StyleSheet.create({
},
textBoxInput: {
height: 40,
+ alignSelf: 'stretch',
backgroundColor: '#fff',
flexGrow: 1
},
@@ -44,14 +45,15 @@ export default class MessageBox extends React.PureComponent {
};
}
- submit = () => {
- if (this.state.text.trim() === '') {
+ submit(message) {
+ // console.log(this.state);
+ const text = message;
+ this.setState({ text: '' });
+ if (text.trim() === '') {
return;
}
-
- this.props.onSubmit(this.state.text);
- this.setState({ text: '' });
- };
+ this.props.onSubmit(text);
+ }
addFile = () => {
const options = {
@@ -106,13 +108,15 @@ export default class MessageBox extends React.PureComponent {
this.component = component}
style={styles.textBoxInput}
value={this.state.text}
onChangeText={text => this.setState({ text })}
returnKeyType='send'
- onSubmitEditing={this.submit}
+ onSubmitEditing={event => this.submit(event.nativeEvent.text)}
blurOnSubmit={false}
placeholder='New message'
+ underlineColorAndroid='transparent'
/>
);
diff --git a/app/lib/realm.js b/app/lib/realm.js
index 62c90dac4..f7ad194b1 100644
--- a/app/lib/realm.js
+++ b/app/lib/realm.js
@@ -35,10 +35,10 @@ const subscriptionSchema = {
open: { type: 'bool', optional: true },
alert: { type: 'bool', optional: true },
// roles: [ 'owner' ],
- unread: { type: 'int', optional: true }
+ unread: { type: 'int', optional: true },
// userMentions: 0,
// groupMentions: 0,
- // _updatedAt: Fri Jul 28 2017 18:31:35 GMT-0300 (-03),
+ _updatedAt: { type: 'date', optional: true }
}
};
@@ -80,11 +80,10 @@ const realm = new Realm({
export default realm;
// Clear settings
-realm.write(() => {
- // const allSettins = realm.objects('settings');
- // realm.delete(allSettins);
-
- // realm.create('servers', { id: 'https://demo.rocket.chat', current: false }, true);
- // realm.create('servers', { id: 'http://localhost:3000', current: false }, true);
- // realm.create('servers', { id: 'http://10.0.2.2:3000', current: false }, true);
-});
+// realm.write(() => {
+// // const allSettins = realm.objects('settings');
+// // realm.delete(allSettins);
+//
+// // realm.create('servers', { id: 'https://demo.rocket.chat', current: false }, true);
+// // realm.create('servers', { id: 'http://localhost:3000', current: false }, true);
+// });
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index 980ae3c16..2426ef309 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -1,9 +1,37 @@
import Meteor from 'react-native-meteor';
import Random from 'react-native-meteor/lib/Random';
import realm from './realm';
+import debounce from '../utils/debounce';
export { Accounts } from 'react-native-meteor';
+const call = (method, ...params) => new Promise((resolve, reject) => {
+ Meteor.call(method, ...params, (err, data) => {
+ if (err) {
+ reject(err);
+ }
+ resolve(data);
+ });
+});
+
+const write = (() => {
+ const cache = [];
+ const run = debounce(() => {
+ if (!cache.length) {
+ return;
+ }
+ realm.write(() => {
+ cache.forEach(([name, obj]) => {
+ realm.create(name, obj, true);
+ });
+ });
+ // cache = [];
+ }, 1000);
+ return (name, obj) => {
+ cache.push([name, obj]);
+ run();
+ };
+})();
const RocketChat = {
createChannel({ name, users, type }) {
@@ -45,6 +73,7 @@ const RocketChat = {
if (typeof item.value === 'string') {
setting.value = item.value;
}
+ // write('settings', setting);
realm.create('settings', setting, true);
});
});
@@ -61,17 +90,36 @@ const RocketChat = {
const message = ddbMessage.fields.args[0];
message.temp = false;
message._server = { id: RocketChat.currentServer };
+ // write('messages', message);
realm.create('messages', message, true);
});
}
-
+ this.subCache = this.subCache || {};
+ this.roomCache = this.roomCache || {};
+ this.cache = {};
if (ddbMessage.collection === 'stream-notify-user') {
- console.log(ddbMessage);
- realm.write(() => {
- const data = ddbMessage.fields.args[1];
- data._server = { id: RocketChat.currentServer };
- realm.create('subscriptions', data, true);
- });
+ const data = ddbMessage.fields.args[1];
+ let key;
+ if (ddbMessage.fields.eventName && ddbMessage.fields.eventName.indexOf('rooms-changed') > -1) {
+ this.roomCache[data._id] = data;
+ key = data._id;
+ } else {
+ this.subCache[data.rid] = data;
+ key = data.rid;
+ delete this.subCache[key]._updatedAt;
+ }
+ this.cache[key] = this.cache[key] ||
+ setTimeout(() => {
+ this.subCache[key] = this.subCache[key] || realm.objects('subscriptions').filtered('rid = $0', key).slice(0, 1)[0];
+ if (this.roomCache[key]) {
+ this.subCache[key]._updatedAt = this.roomCache[key]._updatedAt;
+ }
+
+ write('subscriptions', this.subCache[key]);
+ delete this.subCache[key];
+ delete this.roomCache[key];
+ delete this.cache[key];
+ }, 550);
}
});
});
@@ -86,19 +134,21 @@ const RocketChat = {
if (err) {
console.error(err);
}
-
- realm.write(() => {
- data.forEach((subscription) => {
- // const subscription = {
- // _id: item._id
- // };
- // if (typeof item.value === 'string') {
- // subscription.value = item.value;
- // }
- subscription._server = { id: RocketChat.currentServer };
- realm.create('subscriptions', subscription, true);
+ if (data.length) {
+ realm.write(() => {
+ data.forEach((subscription) => {
+ // const subscription = {
+ // _id: item._id
+ // };
+ // if (typeof item.value === 'string') {
+ // subscription.value = item.value;
+ // }
+ subscription._server = { id: RocketChat.currentServer };
+ write('subscriptions', subscription);
+ realm.create('subscriptions', subscription, true);
+ });
});
- });
+ }
return cb && cb();
});
@@ -113,14 +163,16 @@ const RocketChat = {
}
return;
}
-
- realm.write(() => {
- data.messages.forEach((message) => {
- message.temp = false;
- message._server = { id: RocketChat.currentServer };
- realm.create('messages', message, true);
+ if (data.messages.length) {
+ realm.write(() => {
+ data.messages.forEach((message) => {
+ message.temp = false;
+ message._server = { id: RocketChat.currentServer };
+ // write('messages', message);
+ realm.create('messages', message, true);
+ });
});
- });
+ }
if (cb) {
if (data.messages.length < 20) {
@@ -139,6 +191,19 @@ const RocketChat = {
const user = Meteor.user();
realm.write(() => {
+ // write('messages', {
+ // _id,
+ // rid,
+ // msg,
+ // ts: new Date(),
+ // _updatedAt: new Date(),
+ // temp: true,
+ // _server: { id: RocketChat.currentServer },
+ // u: {
+ // _id: user._id,
+ // username: user.username
+ // }
+ // });
realm.create('messages', {
_id,
rid,
@@ -185,7 +250,9 @@ const RocketChat = {
});
});
},
-
+ readMessages(rid) {
+ return call('readMessages', rid);
+ },
joinRoom(rid) {
return new Promise((resolve, reject) => {
Meteor.call('joinRoom', rid, (error, result) => {
@@ -252,25 +319,30 @@ const RocketChat = {
};
export default RocketChat;
-
Meteor.Accounts.onLogin(() => {
- Meteor.call('subscriptions/get', (err, data) => {
- if (err) {
- console.error(err);
- }
-
+ Promise.all([call('subscriptions/get'), call('rooms/get')]).then(([subscriptions, rooms]) => {
+ subscriptions = subscriptions.sort((s1, s2) => (s1.rid > s2.rid ? 1 : -1));
+ rooms = rooms.sort((s1, s2) => (s1._id > s2._id ? 1 : -1));
+ const data = subscriptions.map((subscription, index) => {
+ subscription._updatedAt = rooms[index]._updatedAt;
+ return subscription;
+ });
realm.write(() => {
data.forEach((subscription) => {
- // const subscription = {
- // _id: item._id
- // };
- // if (typeof item.value === 'string') {
- // subscription.value = item.value;
- // }
+ // const subscription = {
+ // _id: item._id
+ // };
+ // if (typeof item.value === 'string') {
+ // subscription.value = item.value;
+ // }
subscription._server = { id: RocketChat.currentServer };
+ // write('subscriptions', subscription);
realm.create('subscriptions', subscription, true);
});
});
+ }).then(() => {
Meteor.subscribe('stream-notify-user', `${ Meteor.userId() }/subscriptions-changed`, false);
+ Meteor.subscribe('stream-notify-user', `${ Meteor.userId() }/rooms-changed`, false);
+ console.log('subscriptions done.');
});
});
diff --git a/app/navigation.js b/app/navigation.js
index d465d6092..8044889c6 100644
--- a/app/navigation.js
+++ b/app/navigation.js
@@ -61,6 +61,6 @@ export default new StackNavigator({
initialRouteName: 'Main',
cardStyle: {
backgroundColor: '#fff'
- },
- mode: 'modal'
+ }
+ // mode: 'modal'
});
diff --git a/app/utils/debounce.js b/app/utils/debounce.js
new file mode 100644
index 000000000..2291167c7
--- /dev/null
+++ b/app/utils/debounce.js
@@ -0,0 +1,14 @@
+export default function debounce(func, wait, immediate) {
+ let timeout;
+ return function _debounce(...args) {
+ const context = this;
+ const later = function __debounce() {
+ timeout = null;
+ if (!immediate) { func.apply(context, args); }
+ };
+ const callNow = immediate && !timeout;
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ if (callNow) { func.apply(context, args); }
+ };
+}
diff --git a/app/utils/throttle.js b/app/utils/throttle.js
new file mode 100644
index 000000000..58afcbffd
--- /dev/null
+++ b/app/utils/throttle.js
@@ -0,0 +1,21 @@
+export default function throttle(fn, threshhold = 250, scope) {
+ let last,
+ deferTimer;
+ return function() {
+ const context = scope || this;
+
+ let now = +new Date(),
+ args = arguments;
+ if (last && now < last + threshhold) {
+ // hold on to it
+ clearTimeout(deferTimer);
+ deferTimer = setTimeout(() => {
+ last = now;
+ fn.apply(context, args);
+ }, threshhold);
+ } else {
+ last = now;
+ fn.apply(context, args);
+ }
+ };
+}
diff --git a/app/views/room.js b/app/views/room.js
index 18d6bf223..cc798fd1a 100644
--- a/app/views/room.js
+++ b/app/views/room.js
@@ -1,13 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Text, View, FlatList, StyleSheet, Button } from 'react-native';
+import { Text, View, StyleSheet, Button } from 'react-native';
+import { ListView } from 'realm/react-native';
import realm from '../lib/realm';
import RocketChat from '../lib/rocketchat';
-
+import debounce from '../utils/throttle';
import Message from '../components/Message';
import MessageBox from '../components/MessageBox';
-import KeyboardView from '../components/KeyboardView';
-
+// import KeyboardView from '../components/KeyboardView';
+const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
const styles = StyleSheet.create({
container: {
flex: 1
@@ -45,17 +46,18 @@ export default class RoomView extends React.Component {
title: navigation.state.params.name || realm.objectForPrimaryKey('subscriptions', navigation.state.params.sid).name
});
+
constructor(props) {
super(props);
this.rid = props.navigation.state.params.rid || realm.objectForPrimaryKey('subscriptions', props.navigation.state.params.sid).rid;
// this.rid = 'GENERAL';
-
+ this.data = realm.objects('messages').filtered('_server.id = $0 AND rid = $1', RocketChat.currentServer, this.rid).sorted('ts', true);
this.state = {
- dataSource: [],
+ dataSource: ds.cloneWithRows(this.data.slice(0, 10)),
loaded: true,
joined: typeof props.navigation.state.params.rid === 'undefined'
};
-
+ // console.log(this.messages);
this.url = realm.objectForPrimaryKey('settings', 'Site_Url').value;
}
@@ -68,16 +70,13 @@ export default class RoomView extends React.Component {
this.setState({
loaded: true
});
+ this.data.addListener(this.updateState);
});
-
- this.data = realm.objects('messages').filtered('_server.id = $0 AND rid = $1', RocketChat.currentServer, this.rid).sorted('ts', true);
-
- this.setState({
- dataSource: this.data
- });
- this.data.addListener(this.updateState);
+ this.updateState();
+ }
+ componentDidMount() {
+ return RocketChat.readMessages(this.rid);
}
-
componentWillUnmount() {
this.data.removeListener(this.updateState);
}
@@ -85,12 +84,12 @@ export default class RoomView extends React.Component {
onEndReached = () => {
if (this.state.dataSource.length && this.state.loaded && this.state.loadingMore !== true && this.state.end !== true) {
this.setState({
- ...this.state,
+ // ...this.state,
loadingMore: true
});
RocketChat.loadMessagesForRoom(this.rid, this.state.dataSource[this.state.dataSource.length - 1].ts, ({ end }) => {
this.setState({
- ...this.state,
+ // ...this.state,
loadingMore: false,
end
});
@@ -98,11 +97,15 @@ export default class RoomView extends React.Component {
}
}
- updateState = (data) => {
+ updateState = debounce(() => {
this.setState({
- dataSource: data
+ dataSource: ds.cloneWithRows(this.data)
});
- };
+ // RocketChat.readMessages(this.rid);
+ // this.setState({
+ // messages: this.messages
+ // });
+ }, 100);
sendMessage = message => RocketChat.sendMessage(this.rid, message);
@@ -148,6 +151,7 @@ export default class RoomView extends React.Component {
}
return (
this.box = box}
onSubmit={this.sendMessage}
rid={this.rid}
/>
@@ -165,22 +169,24 @@ export default class RoomView extends React.Component {
}
render() {
+ // data={this.state.dataSource}
+ // extraData={this.state}
+ // renderItem={this.renderItem}
+ // keyExtractor={item => item._id}
+ //
return (
-
+
{this.renderBanner()}
- this.listView = ref}
+ item._id}
- onEndReached={this.onEndReached}
onEndReachedThreshold={0.1}
ListFooterComponent={this.renderHeader()}
+ onEndReached={this.onEndReached}
+ dataSource={this.state.dataSource}
+ renderRow={item => this.renderItem({ item })}
/>
{this.renderFooter()}
-
+
);
}
}
diff --git a/app/views/roomsList.js b/app/views/roomsList.js
index 6690c8d6d..30eac8c0d 100644
--- a/app/views/roomsList.js
+++ b/app/views/roomsList.js
@@ -1,12 +1,14 @@
import ActionButton from 'react-native-action-button';
+import { ListView } from 'realm/react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import React from 'react';
import PropTypes from 'prop-types';
-import { Button, Text, View, FlatList, StyleSheet, TouchableOpacity, Platform, TextInput } from 'react-native';
+import { Button, Text, View, StyleSheet, TouchableOpacity, Platform, TextInput } from 'react-native';
import Meteor from 'react-native-meteor';
import realm from '../lib/realm';
import RocketChat from '../lib/rocketchat';
import RoomItem from '../components/RoomItem';
+import debounce from '../utils/debounce';
const styles = StyleSheet.create({
container: {
@@ -72,6 +74,28 @@ Meteor.Accounts.onLogin(() => {
console.log('onLogin');
});
+const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
+class RoomsListItem extends React.PureComponent {
+ static propTypes = {
+ item: PropTypes.object.isRequired,
+ onPress: PropTypes.func.isRequired
+ }
+ _onPress = (...args) => {
+ this.props.onPress(...args);
+ };
+
+ render() {
+ const { item } = this.props;
+ return (
+ this.props.onPress(item._id, item)}>
+
+
+ );
+ }
+}
export default class RoomsListView extends React.Component {
static propTypes = {
navigation: PropTypes.object.isRequired
@@ -95,9 +119,9 @@ export default class RoomsListView extends React.Component {
constructor(props) {
super(props);
-
+ this.data = realm.objects('subscriptions').filtered('_server.id = $0', RocketChat.currentServer).sorted('_updatedAt', true);
this.state = {
- dataSource: [],
+ dataSource: ds.cloneWithRows(this.data.sorted('_updatedAt', true).slice(0, 10)),
searching: false,
searchDataSource: [],
searchText: ''
@@ -168,40 +192,23 @@ export default class RoomsListView extends React.Component {
}
}
}
-
setInitialData = () => {
if (this.data) {
this.data.removeListener(this.updateState);
}
- this.data = realm.objects('subscriptions').filtered('_server.id = $0', RocketChat.currentServer).sorted('name');
-
this.data.addListener(this.updateState);
+ this.updateState();
+ }
+
+ getSubscriptions = () => this.data.sorted('_updatedAt', true)
+
+ updateState = debounce(() => {
this.setState({
- dataSource: this.sort(this.data)
+ dataSource: ds.cloneWithRows(this.data)
});
- }
-
- sort = (data) => {
- return data.slice().sort((a, b) => {
- if (a.unread < b.unread) {
- return 1;
- }
-
- if (a.unread > b.unread) {
- return -1;
- }
-
- return 0;
- });
- }
-
- updateState = (data) => {
- this.setState({
- dataSource: this.sort(data)
- });
- }
+ }, 500);
_onPressItem = (id, item = {}) => {
const { navigate } = this.props.navigation;
@@ -222,8 +229,8 @@ export default class RoomsListView extends React.Component {
.then(subs => navigate('Room', { sid: subs[0]._id }))
.then(() => clearSearch());
} else {
- navigate('Room', { rid: item._id, name: item.name });
clearSearch();
+ navigate('Room', { rid: item._id, name: item.name });
}
return;
}
@@ -256,12 +263,7 @@ export default class RoomsListView extends React.Component {
}
renderItem = ({ item }) => (
- this._onPressItem(item._id, item)}>
-
-
+ this._onPressItem(item._id, item)} />
);
renderSeparator = () => (
@@ -282,25 +284,25 @@ export default class RoomsListView extends React.Component {
);
- renderList = () => {
- if (!this.state.searching && !this.state.dataSource.length) {
- return (
-
- No rooms
-
- );
- }
+ // if (!this.state.searching && !this.state.dataSource.length) {
+ // return (
+ //
+ // No rooms
+ //
+ // );
+ // }
+ renderList = () => (
+ // data={this.state.searching ? this.state.searchDataSource : this.state.dataSource}
+ // keyExtractor={item => item._id}
+ // ItemSeparatorComponent={this.renderSeparator}
+ // renderItem={this.renderItem}
+ this.renderItem({ item })}
+ />
+ )
- return (
- item._id}
- ItemSeparatorComponent={this.renderSeparator}
- />
- );
- }
renderCreateButtons() {
return (
diff --git a/package-lock.json b/package-lock.json
index 906c8db80..77753daf3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3683,6 +3683,26 @@
"resolved": "https://registry.npmjs.org/react-native-action-button/-/react-native-action-button-2.7.2.tgz",
"integrity": "sha1-BvEYjo/h0Y0D/JBg1LYEybgbtso="
},
+ "react-native-auto-grow-textinput": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/react-native-auto-grow-textinput/-/react-native-auto-grow-textinput-1.2.0.tgz",
+ "integrity": "sha512-O+mT2GOrDRzJdg2GbdfuGlO/nn/J8c9pdBCPahLYA8yiAjayAG67XOujGrfuv/wNCF7W94NsYdyfaf2hlOIhYQ=="
+ },
+ "react-native-autogrow-input": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/react-native-autogrow-input/-/react-native-autogrow-input-0.2.1.tgz",
+ "integrity": "sha512-vWcfqGqzDw4XqRJr4HnHC+dcGAfJDYZiF2B0tBZjtjA6MNSv2TNz5knYZjvLggRgmEflj02r88scvfFputsRig=="
+ },
+ "react-native-autogrow-textinput": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/react-native-autogrow-textinput/-/react-native-autogrow-textinput-4.1.0.tgz",
+ "integrity": "sha1-p+WxfrPBarCOMbv7iNkkiO2H8nY="
+ },
+ "react-native-console-time-polyfill": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/react-native-console-time-polyfill/-/react-native-console-time-polyfill-0.0.6.tgz",
+ "integrity": "sha1-eCPYb+g0OcdEgNGxJKkrGnhXGIk="
+ },
"react-native-dismiss-keyboard": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/react-native-dismiss-keyboard/-/react-native-dismiss-keyboard-1.0.0.tgz",
@@ -3718,6 +3738,11 @@
"resolved": "https://registry.npmjs.org/react-native-form-generator/-/react-native-form-generator-0.9.9.tgz",
"integrity": "sha1-aKribR6Nw+MAc8zXuymPvf3OG8o="
},
+ "react-native-image-picker": {
+ "version": "0.26.3",
+ "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-0.26.3.tgz",
+ "integrity": "sha1-CtLu3klQGnBG2ARqc4E2llOcPc0="
+ },
"react-native-img-cache": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/react-native-img-cache/-/react-native-img-cache-1.4.0.tgz",
@@ -3735,6 +3760,11 @@
"resolved": "https://registry.npmjs.org/react-native-meteor/-/react-native-meteor-1.1.0.tgz",
"integrity": "sha1-Vake/i1GbTqMzrW1QZeZru3NZ1Y="
},
+ "react-native-optimized-flatlist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/react-native-optimized-flatlist/-/react-native-optimized-flatlist-1.0.1.tgz",
+ "integrity": "sha1-2+6C8gi0i+8jxssm8dXzrFjmdbI="
+ },
"react-native-svg": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-5.4.1.tgz",
diff --git a/package.json b/package.json
index 5cc619e61..bd8f8ba14 100644
--- a/package.json
+++ b/package.json
@@ -16,12 +16,17 @@
"react-emojione": "^3.1.10",
"react-native": "0.46.1",
"react-native-action-button": "^2.7.2",
+ "react-native-auto-grow-textinput": "^1.2.0",
+ "react-native-autogrow-input": "^0.2.1",
+ "react-native-autogrow-textinput": "^4.1.0",
+ "react-native-console-time-polyfill": "0.0.6",
"react-native-easy-markdown": "git+https://github.com/lappalj4/react-native-easy-markdown.git",
"react-native-fetch-blob": "^0.10.8",
"react-native-form-generator": "^0.9.9",
"react-native-image-picker": "^0.26.3",
"react-native-img-cache": "^1.4.0",
"react-native-meteor": "^1.1.0",
+ "react-native-optimized-flatlist": "^1.0.1",
"react-native-svg": "^5.4.1",
"react-native-svg-image": "^1.1.4",
"react-native-vector-icons": "^4.3.0",