From f836982b09cb70ed18de8f91ad59aa67b93ff72a Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sun, 6 Aug 2017 21:34:35 -0300 Subject: [PATCH] Sending messages :) --- android/app/build.gradle | 1 + .../com/rocketchatrn/MainApplication.java | 2 + android/settings.gradle | 2 + app/login.js | 69 ++----- app/meteor.js | 80 +++++++-- app/navigation.js | 32 +++- app/realm.js | 14 +- app/room.js | 90 ++++++++-- app/rooms.js | 2 +- app/servers/list.js | 170 ++++++++++++++++++ app/{new-server.js => servers/new.js} | 44 +++-- ios/RocketChatRN-tvOS/Info.plist | 29 ++- ios/RocketChatRN.xcodeproj/project.pbxproj | 69 ++++++- ios/RocketChatRN/Info.plist | 31 ++-- package.json | 1 + 15 files changed, 486 insertions(+), 150 deletions(-) create mode 100644 app/servers/list.js rename app/{new-server.js => servers/new.js} (55%) diff --git a/android/app/build.gradle b/android/app/build.gradle index d172a411a..fcb3f01a5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -133,6 +133,7 @@ android { } dependencies { + compile project(':react-native-zeroconf') compile project(':realm') compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" diff --git a/android/app/src/main/java/com/rocketchatrn/MainApplication.java b/android/app/src/main/java/com/rocketchatrn/MainApplication.java index c89d85e56..17b6b71be 100644 --- a/android/app/src/main/java/com/rocketchatrn/MainApplication.java +++ b/android/app/src/main/java/com/rocketchatrn/MainApplication.java @@ -3,6 +3,7 @@ package com.rocketchatrn; import android.app.Application; import com.facebook.react.ReactApplication; +import com.balthazargronon.RCTZeroconf.ZeroconfReactPackage; import io.realm.react.RealmReactPackage; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; @@ -24,6 +25,7 @@ public class MainApplication extends Application implements ReactApplication { protected List getPackages() { return Arrays.asList( new MainReactPackage(), + new ZeroconfReactPackage(), new RealmReactPackage() ); } diff --git a/android/settings.gradle b/android/settings.gradle index 7eda65b95..9a71101a2 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,6 @@ rootProject.name = 'RocketChatRN' +include ':react-native-zeroconf' +project(':react-native-zeroconf').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-zeroconf/android') include ':realm' project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android') diff --git a/app/login.js b/app/login.js index 5b6c26944..fc3aaa831 100644 --- a/app/login.js +++ b/app/login.js @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, TextInput, StyleSheet } from 'react-native'; +import { View, TextInput, StyleSheet, KeyboardAvoidingView } from 'react-native'; import realm from './realm'; -import { loginWithPassword } from './meteor'; +import { loginWithPassword, loadSubscriptions, Accounts } from './meteor'; const styles = StyleSheet.create({ @@ -14,7 +14,6 @@ const styles = StyleSheet.create({ }, input: { height: 40, - // flex: 1, borderColor: '#aaa', marginLeft: 20, marginRight: 20, @@ -38,36 +37,26 @@ export default class LoginView extends React.Component { super(props); this.state = { - username: 'rodrigo', - password: 'rodrigo' + username: '', + password: '' }; const { navigate } = this.props.navigation; - this.submit = () => { - loginWithPassword({ username: this.state.username }, this.state.password, () => { + Accounts.onLogin(() => { + loadSubscriptions(() => { navigate('Rooms'); }); + }); - // let url = this.state.text.trim(); - // if (!url) { - // url = defaultServer; - // } - - // // TODO: validate URL - - // realm.write(() => { - // realm.objects('servers').filtered('current = true').forEach(item => item.current = false); - // realm.create('servers', {id: url, current: true}, true); - // }); - - // navigate('Login'); + this.submit = () => { + loginWithPassword({ username: this.state.username }, this.state.password); }; } render() { return ( - + this.setState({ username })} @@ -89,43 +78,7 @@ export default class LoginView extends React.Component { onSubmitEditing={this.submit} placeholder='Password' /> - + ); } } - -// export class LoginView extends React.Component { -// renderRow(setting) { -// return ( -// {setting._id} -// ); -// } - -// constructor(props) { -// super(props); -// connect(); -// const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); - -// const getState = () => { -// return { -// dataSource: ds.cloneWithRows(realm.objects('settings')) -// }; -// }; - -// realm.addListener('change', () => this.setState(getState())); - -// this.state = getState(); -// } - -// render() { -// return ( -// -// Title -// -// -// ); -// } -// } diff --git a/app/meteor.js b/app/meteor.js index 803cc6fa8..1fe53bcd8 100644 --- a/app/meteor.js +++ b/app/meteor.js @@ -1,6 +1,9 @@ import Meteor from 'react-native-meteor'; +import Random from 'react-native-meteor/lib/Random'; import realm from './realm'; +export { Accounts } from 'react-native-meteor'; + export function connect(cb) { const currentServer = realm.objects('servers').filtered('current = true')[0]; const url = `${ currentServer.id }/websocket`; @@ -31,29 +34,47 @@ export function connect(cb) { cb(); }); + + Meteor.ddp.on("changed", ddbMessage => { + console.log('changed', ddbMessage); + if (ddbMessage.collection === 'stream-room-messages') { + setTimeout(function() { + realm.write(() => { + const message = ddbMessage.fields.args[0]; + message.temp = false; + realm.create('messages', message, true); + }); + }, 1000) + } + }); }); } export function loginWithPassword(selector, password, cb) { - Meteor.loginWithPassword(selector, password, () => { - Meteor.call('subscriptions/get', (err, data) => { - if (err) { - console.error(err); - } + Meteor.loginWithPassword(selector, password, (err, data) => { + cb && cb(); + }); +} - realm.write(() => { - data.forEach((subscription) => { - // const subscription = { - // _id: item._id - // }; - // if (typeof item.value === 'string') { - // subscription.value = item.value; - // } - realm.create('subscriptions', subscription, true); - }); +export function loadSubscriptions(cb) { + Meteor.call('subscriptions/get', (err, data) => { + if (err) { + console.error(err); + } + + realm.write(() => { + data.forEach((subscription) => { + // const subscription = { + // _id: item._id + // }; + // if (typeof item.value === 'string') { + // subscription.value = item.value; + // } + realm.create('subscriptions', subscription, true); }); }); - cb(); + + cb && cb(); }); } @@ -66,8 +87,35 @@ export function loadMessagesForRoom(rid) { realm.write(() => { data.messages.forEach((message) => { + message.temp = false; realm.create('messages', message, true); }); }); }); + + Meteor.subscribe('stream-room-messages', rid, false); +} + +export function sendMessage(rid, msg, cb) { + const _id = Random.id(); + const user = Meteor.user(); + + realm.write(() => { + realm.create('messages', { + _id, + rid, + msg, + ts: new Date, + _updatedAt: new Date, + temp: true, + u: { + _id: user._id, + username: user.username + } + }, true); + }); + + Meteor.call('sendMessage', {_id, rid, msg}, (err, data) => { + cb && cb(); + }); } diff --git a/app/navigation.js b/app/navigation.js index b5788e4b0..166a41f04 100644 --- a/app/navigation.js +++ b/app/navigation.js @@ -1,22 +1,38 @@ import { StackNavigator } from 'react-navigation'; import LoginView from './login'; -import NewServerView from './new-server'; +import NewServerView from './servers/new'; +import ListServerView from './servers/list'; import RoomsView from './rooms'; import RoomView from './room'; +const navigationOptions = { + // headerStyle: { + // backgroundColor: '#c1272d' + // }, + // headerTitleStyle: { + // color: '#fff' + // } +}; export default new StackNavigator({ - // Room: { screen: RoomView }, - Home: { - navigationOptions: { - header: null - }, - screen: NewServerView + ListServer: { + screen: ListServerView, + navigationOptions + }, + NewServer: { + screen: NewServerView, + navigationOptions }, Login: { screen: LoginView }, Rooms: { screen: RoomsView }, - Room: { screen: RoomView } + Room: { + screen: RoomView + // navigationOptions: { + // header: null + // } + } }, { + // initialRouteName: 'Room', cardStyle: { backgroundColor: '#fff' } diff --git a/app/realm.js b/app/realm.js index 6e0624fb7..d33201d88 100644 --- a/app/realm.js +++ b/app/realm.js @@ -24,16 +24,16 @@ const subscriptionSchema = { properties: { _id: 'string', t: 'string', - ts: 'date', - ls: 'date', + ts: { type: 'date', optional: true }, + ls: { type: 'date', optional: true }, name: 'string', fname: { type: 'string', optional: true }, rid: 'string', // u: { _id: 'hKCY2XGzHYk89SAaM', username: 'rodrigo', name: null }, - open: 'bool', - alert: 'bool', + open: { type: 'bool', optional: true }, + alert: { type: 'bool', optional: true }, // roles: [ 'owner' ], - unread: 'int' + unread: { type: 'int', optional: true } // userMentions: 0, // groupMentions: 0, // _updatedAt: Fri Jul 28 2017 18:31:35 GMT-0300 (-03), @@ -61,10 +61,12 @@ const messagesSchema = { u: 'users', // mentions: [], // channels: [], - _updatedAt: 'date' + _updatedAt: 'date', + temp: { type: 'bool', optional: true } } }; + // Realm.clearTestState(); const realm = new Realm({ diff --git a/app/room.js b/app/room.js index d4ee59721..a1514bd6f 100644 --- a/app/room.js +++ b/app/room.js @@ -1,16 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Text, FlatList, StyleSheet, Image } from 'react-native'; +import { View, KeyboardAvoidingView, Text, TextInput, FlatList, StyleSheet, Image } from 'react-native'; // import Markdown from 'react-native-simple-markdown'; import realm from './realm'; -import { loadMessagesForRoom } from './meteor'; +import { loadMessagesForRoom, sendMessage } from './meteor'; const styles = StyleSheet.create({ roomItem: { borderColor: '#aaa', padding: 14, - flexDirection: 'row' + flexDirection: 'row', + transform: [{ scaleY: -1 }] }, avatar: { backgroundColor: '#ccc', @@ -23,14 +24,33 @@ const styles = StyleSheet.create({ fontWeight: 'bold', marginBottom: 5 }, + texts: { + flex: 1 + }, + msg: { + flex: 1 + }, container: { flex: 1 }, + list: { + flex: 1, + transform: [{ scaleY: -1 }] + }, separator: { height: 1, // width: "86%", backgroundColor: '#CED0CE' // marginLeft: "14%" + }, + textBox: { + paddingTop: 1, + backgroundColor: '#ccc' + }, + textBoxInput: { + height: 40, + backgroundColor: '#fff', + paddingLeft: 15 } }); @@ -40,14 +60,19 @@ class RoomItem extends React.PureComponent { } render() { + const extraStyle = {}; + if (this.props.item.temp) { + extraStyle.opacity = .3; + } + return ( - + - + {this.props.item.u.username} - + {this.props.item.msg} {/* @@ -73,16 +98,26 @@ export default class RoomView extends React.Component { this.rid = realm.objectForPrimaryKey('subscriptions', props.navigation.state.params.sid).rid; // this.rid = 'GENERAL'; + this.state = this.getState(); + loadMessagesForRoom(this.rid); - const getState = () => ({ - selected: new Map(), - dataSource: realm.objects('messages').filtered('rid = $0', this.rid) - }); + this.state = this.getState(); + } - realm.addListener('change', () => this.setState(getState())); + getState = () => ({ + ...this.state, + dataSource: realm.objects('messages').filtered('rid = $0', this.rid).sorted('ts', true) + }); - this.state = getState(); + updateState = () => (this.setState(this.getState())) + + componentDidMount() { + realm.addListener('change', this.updateState); + } + + componentWillUnmount() { + realm.removeListener('change', this.updateState); } renderItem = ({ item }) => ( @@ -96,17 +131,44 @@ export default class RoomView extends React.Component { ); + submit = () => { + console.log(this.state.text); + if (this.state.text.trim() === '') { + return; + } + + sendMessage(this.rid, this.state.text); + + this.setState({ + ...this.state, + text: '' + }); + } + render() { return ( - + this.listView = ref} style={styles.list} data={this.state.dataSource} + extraData={this.state} renderItem={this.renderItem} keyExtractor={item => item._id} ItemSeparatorComponent={this.renderSeparator} /> - + + this.setState({ text })} + returnKeyType='send' + onSubmitEditing={this.submit} + autoFocus + placeholder='New message' + > + + ); } } diff --git a/app/rooms.js b/app/rooms.js index ae9413089..18a85238c 100644 --- a/app/rooms.js +++ b/app/rooms.js @@ -50,7 +50,7 @@ export default class RoomsView extends React.Component { const getState = () => ({ selected: new Map(), - dataSource: realm.objects('subscriptions') + dataSource: realm.objects('subscriptions').sorted('name') }); realm.addListener('change', () => this.setState(getState())); diff --git a/app/servers/list.js b/app/servers/list.js new file mode 100644 index 000000000..6e08d3ce1 --- /dev/null +++ b/app/servers/list.js @@ -0,0 +1,170 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Zeroconf from 'react-native-zeroconf'; +import { H1, View, TouchableOpacity, Text, TextInput, SectionList, Button, StyleSheet } from 'react-native'; + +import realm from '../realm'; +import { connect } from '../meteor'; + +const styles = StyleSheet.create({ + view: { + flex: 1, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'stretch' + }, + input: { + height: 40, + borderColor: '#aaa', + margin: 20, + padding: 5, + borderWidth: 0, + backgroundColor: '#f8f8f8' + }, + text: { + textAlign: 'center', + color: '#888' + }, + listItem: { + lineHeight: 18, + borderTopWidth: 2, + color: '#666', + padding: 14 + }, + container: { + flex: 1 + }, + separator: { + height: 1, + backgroundColor: '#eee' + }, + headerStyle: { + backgroundColor: '#eee', + lineHeight: 24, + paddingLeft: 14, + color: '#888' + } +}); + +const zeroconf = new Zeroconf(); + +export default class ListServerView extends React.Component { + static propTypes = { + navigation: PropTypes.object.isRequired + } + + static navigationOptions = ({navigation}) => ({ + title: 'Servers', + headerRight: ( +