diff --git a/.expo/packager-info.json b/.expo/packager-info.json new file mode 100644 index 0000000000000000000000000000000000000000..24e8ec3992f01f95d47c2c90460c8bf6d20cc9dc --- /dev/null +++ b/.expo/packager-info.json @@ -0,0 +1,5 @@ +{ + "expoServerPort": null, + "packagerPort": null, + "packagerPid": null +} \ No newline at end of file diff --git a/.expo/settings.json b/.expo/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..5eee78b49b946f13ce7ed81acb4d2e969b2a6815 --- /dev/null +++ b/.expo/settings.json @@ -0,0 +1,9 @@ +{ + "hostType": "tunnel", + "lanType": "ip", + "dev": true, + "strict": false, + "minify": false, + "urlType": "exp", + "urlRandomness": null +} \ No newline at end of file diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 52102d67d0a999893244204d50fa9c4674d6170c..ba368a144e32ac6bdb33749d1094b0056416c1c8 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -154,6 +154,39 @@ const RocketChat = { return resolve(result); }); }); + }, + + spotlight(search, usernames) { + return new Promise((resolve, reject) => { + Meteor.call('spotlight', search, usernames, (error, result) => { + if (error) { + return reject(error); + } + return resolve(result); + }); + }); + }, + + createDirectMessage(username) { + return new Promise((resolve, reject) => { + Meteor.call('createDirectMessage', username, (error, result) => { + if (error) { + return reject(error); + } + return resolve(result); + }); + }); + }, + + joinRoom(rid) { + return new Promise((resolve, reject) => { + Meteor.call('joinRoom', rid, (error, result) => { + if (error) { + return reject(error); + } + return resolve(result); + }); + }); } }; diff --git a/app/views/room.js b/app/views/room.js index d8d52f52716f1f81e6c09affcb003ff6fb3974af..69619f2a4d253163a35ae8960aed4197209122de 100644 --- a/app/views/room.js +++ b/app/views/room.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Text, View, FlatList, StyleSheet } from 'react-native'; +import { Text, View, FlatList, StyleSheet, Button } from 'react-native'; import realm from '../lib/realm'; import RocketChat from '../lib/rocketchat'; @@ -36,17 +36,18 @@ export default class RoomView extends React.Component { } static navigationOptions = ({ navigation }) => ({ - title: realm.objectForPrimaryKey('subscriptions', navigation.state.params.sid).name + title: navigation.state.params.name || realm.objectForPrimaryKey('subscriptions', navigation.state.params.sid).name }); constructor(props) { super(props); - this.rid = realm.objectForPrimaryKey('subscriptions', props.navigation.state.params.sid).rid; + this.rid = props.navigation.state.params.rid || realm.objectForPrimaryKey('subscriptions', props.navigation.state.params.sid).rid; // this.rid = 'GENERAL'; this.state = { dataSource: this.getMessages(), - loaded: false + loaded: false, + joined: typeof props.navigation.state.params.rid === 'undefined' }; this.url = realm.objectForPrimaryKey('settings', 'Site_Url').value; @@ -77,6 +78,25 @@ export default class RoomView extends React.Component { sendMessage = message => RocketChat.sendMessage(this.rid, message); + joinRoom = () => { + RocketChat.joinRoom(this.props.navigation.state.params.rid) + .then(() => { + this.setState({ + joined: true + }); + }); + }; + + renderBanner = () => { + if (this.state.loaded === false) { + return ( + <View style={styles.bannerContainer}> + <Text style={styles.bannerText}>Loading new messages...</Text> + </View> + ); + } + }; + renderItem = ({ item }) => ( <Message id={item._id} @@ -85,14 +105,24 @@ export default class RoomView extends React.Component { /> ); - renderBanner = () => { - if (this.state.loaded === false) { + renderSeparator = () => ( + <View style={styles.separator} /> + ); + + renderFooter = () => { + if (!this.state.joined) { return ( - <View style={styles.bannerContainer}> - <Text style={styles.bannerText}>Loading new messages...</Text> + <View> + <Text>You are in preview mode.</Text> + <Button title='Join' onPress={this.joinRoom} /> </View> ); } + return ( + <MessageBox + onSubmit={this.sendMessage} + /> + ); } render() { @@ -107,9 +137,7 @@ export default class RoomView extends React.Component { renderItem={this.renderItem} keyExtractor={item => item._id} /> - <MessageBox - onSubmit={this.sendMessage} - /> + {this.renderFooter()} </KeyboardView> ); } diff --git a/app/views/roomsList.js b/app/views/roomsList.js index e9d69be4f57328c7ac11c1ae77ada318891fbece..bdbc9ff18d88f939169cd765e14f43ebffde9099 100644 --- a/app/views/roomsList.js +++ b/app/views/roomsList.js @@ -2,7 +2,7 @@ import ActionButton from 'react-native-action-button'; import Icon from 'react-native-vector-icons/Ionicons'; import React from 'react'; import PropTypes from 'prop-types'; -import { Text, View, FlatList, StyleSheet, TouchableOpacity } from 'react-native'; +import { Text, View, FlatList, StyleSheet, TouchableOpacity, Platform, TextInput } from 'react-native'; import Meteor from 'react-native-meteor'; import realm from '../lib/realm'; import RocketChat from '../lib/rocketchat'; @@ -80,8 +80,13 @@ export default class RoomsListView extends React.Component { constructor(props) { super(props); - this._listViewOffset = 0; - this.state = this.getState(); + + this.state = { + dataSource: this.getSubscriptions(), + searching: false, + searchDataSource: [], + searchText: '' + }; } componentWillMount() { @@ -100,28 +105,97 @@ export default class RoomsListView extends React.Component { realm.removeListener('change', this.updateState); } - getState = () => ({ - dataSource: realm.objects('subscriptions').filtered('_server.id = $0', RocketChat.currentServer).sorted('name').slice() - .sort((a, b) => { - if (a.unread < b.unread) { - return 1; - } + onSearchChangeText = (text) => { + const searchText = text.trim(); + this.setState({ + searchText: text, + searching: searchText !== '' + }); - if (a.unread > b.unread) { - return -1; + if (searchText !== '') { + const dataSource = []; + const usernames = []; + realm.objects('subscriptions').filtered('_server.id = $0 AND name CONTAINS[c] $1', RocketChat.currentServer, searchText).forEach((sub) => { + dataSource.push(sub); + + if (sub.t === 'd') { + usernames.push(sub.name); } + }); + + if (dataSource.length < 5) { + RocketChat.spotlight(searchText, usernames) + .then((results) => { + results.users.forEach((user) => { + dataSource.push({ + ...user, + name: user.username, + t: 'd', + search: true + }); + }); + + results.rooms.forEach((room) => { + dataSource.push({ + ...room, + search: true + }); + }); + + this.setState({ + searchDataSource: dataSource + }); + }); + } + } + } + + getSubscriptions = () => realm.objects('subscriptions').filtered('_server.id = $0', RocketChat.currentServer).sorted('name').slice() + .sort((a, b) => { + if (a.unread < b.unread) { + return 1; + } + + if (a.unread > b.unread) { + return -1; + } - return 0; - }) - }) + return 0; + }); updateState = () => { - this.setState(this.getState()); + this.setState({ + dataSource: this.getSubscriptions() + }); } - _onPressItem = (id) => { + _onPressItem = (id, item) => { const { navigate } = this.props.navigation; + + const clearSearch = () => { + this.setState({ + searchText: '', + searching: false, + searchDataSource: [] + }); + }; + + // if user is using the search we need first to join/create room + if (item.search) { + if (item.t === 'd') { + RocketChat.createDirectMessage(item.username) + .then(room => realm.objects('subscriptions').filtered('_server.id = $0 AND rid = $1', RocketChat.currentServer, room.rid)) + .then(subs => navigate('Room', { sid: subs[0]._id })) + .then(() => clearSearch()); + } else { + navigate('Room', { rid: item._id, name: item.name }); + clearSearch(); + } + return; + } + navigate('Room', { sid: id }); + clearSearch(); } _createChannel = () => { const { navigate } = this.props.navigation; @@ -160,23 +234,33 @@ export default class RoomsListView extends React.Component { <View style={styles.separator} /> ); + renderSearchBar = () => ( + <TextInput + style={styles.searchBox} + value={this.state.searchText} + onChangeText={this.onSearchChangeText} + returnKeyType='search' + placeholder='Search' + /> + ); + renderList = () => { - if (this.state.dataSource.length) { + if (!this.state.searching && !this.state.dataSource.length) { return ( - <FlatList - style={styles.list} - data={this.state.dataSource} - renderItem={this.renderItem} - keyExtractor={item => item._id} - ItemSeparatorComponent={this.renderSeparator} - /> + <View style={styles.emptyView}> + <Text style={styles.emptyText}>No rooms</Text> + </View> ); } return ( - <View style={styles.emptyView}> - <Text style={styles.emptyText}>No rooms</Text> - </View> + <FlatList + style={styles.list} + data={this.state.searching ? this.state.searchDataSource : this.state.dataSource} + renderItem={this.renderItem} + keyExtractor={item => item._id} + ItemSeparatorComponent={this.renderSeparator} + /> ); } renderCreateButtons() { @@ -191,6 +275,7 @@ export default class RoomsListView extends React.Component { return ( <View style={styles.container}> {this.renderBanner()} + {this.renderSearchBar()} {this.renderList()} {this.renderCreateButtons()} </View> diff --git a/package.json b/package.json index 2af10f30adc9cb524f3dab38be16fb507fb0c1c3..c459c63b011e2faa975967a95be9058741f06b6b 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "react": "16.0.0-alpha.12", "react-emojione": "^3.1.10", "react-native": "0.46.1", + "react-native-action-button": "^2.7.2", "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",