This commit is contained in:
Guilherme Gazzo 2017-08-11 15:18:09 -03:00
parent 0e1cb6e5ed
commit 138546e4c9
No known key found for this signature in database
GPG Key ID: 1F85C9AD922D0829
11 changed files with 293 additions and 141 deletions

View File

@ -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 (
<View style={[styles.message, extraStyle]}>
<View style={[styles.avatarContainer, { backgroundColor: color }]}>

View File

@ -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 {
<View style={styles.textBox}>
<Icon style={styles.fileButton} name='add-circle-outline' onPress={this.addFile} />
<TextInput
ref={component => 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'
/>
</View>
);

View File

@ -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);
// });

View File

@ -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);
});
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,7 +134,7 @@ const RocketChat = {
if (err) {
console.error(err);
}
if (data.length) {
realm.write(() => {
data.forEach((subscription) => {
// const subscription = {
@ -96,9 +144,11 @@ const RocketChat = {
// 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;
}
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,13 +319,14 @@ 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 = {
@ -268,9 +336,13 @@ Meteor.Accounts.onLogin(() => {
// 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.');
});
});

View File

@ -61,6 +61,6 @@ export default new StackNavigator({
initialRouteName: 'Main',
cardStyle: {
backgroundColor: '#fff'
},
mode: 'modal'
}
// mode: 'modal'
});

14
app/utils/debounce.js Normal file
View File

@ -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); }
};
}

21
app/utils/throttle.js Normal file
View File

@ -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);
}
};
}

View File

@ -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 = 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 (
<MessageBox
ref={box => 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 (
<KeyboardView style={styles.container} keyboardVerticalOffset={64}>
<View style={styles.container}>
{this.renderBanner()}
<FlatList
ref={ref => this.listView = ref}
<ListView
style={styles.list}
data={this.state.dataSource}
extraData={this.state}
renderItem={this.renderItem}
keyExtractor={item => 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()}
</KeyboardView>
</View>
);
}
}

View File

@ -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 (
<TouchableOpacity key={item._id} onPress={() => this.props.onPress(item._id, item)}>
<RoomItem
id={item._id}
item={item}
/>
</TouchableOpacity>
);
}
}
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 }) => (
<TouchableOpacity onPress={() => this._onPressItem(item._id, item)}>
<RoomItem
id={item._id}
item={item}
/>
</TouchableOpacity>
<RoomsListItem item={item} onPress={() => this._onPressItem(item._id, item)} />
);
renderSeparator = () => (
@ -282,25 +284,25 @@ export default class RoomsListView extends React.Component {
</View>
);
renderList = () => {
if (!this.state.searching && !this.state.dataSource.length) {
return (
<View style={styles.emptyView}>
<Text style={styles.emptyText}>No rooms</Text>
</View>
);
}
return (
<FlatList
// if (!this.state.searching && !this.state.dataSource.length) {
// return (
// <View style={styles.emptyView}>
// <Text style={styles.emptyText}>No rooms</Text>
// </View>
// );
// }
renderList = () => (
// data={this.state.searching ? this.state.searchDataSource : this.state.dataSource}
// keyExtractor={item => item._id}
// ItemSeparatorComponent={this.renderSeparator}
// renderItem={this.renderItem}
<ListView
dataSource={this.state.dataSource}
style={styles.list}
data={this.state.searching ? this.state.searchDataSource : this.state.dataSource}
renderItem={this.renderItem}
keyExtractor={item => item._id}
ItemSeparatorComponent={this.renderSeparator}
renderRow={item => this.renderItem({ item })}
/>
);
}
)
renderCreateButtons() {
return (
<ActionButton buttonColor='rgba(231,76,60,1)'>

30
package-lock.json generated
View File

@ -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",

View File

@ -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",