diff --git a/__tests__/__snapshots__/RoomItem.js.snap b/__tests__/__snapshots__/RoomItem.js.snap
index 8f1df86e2..ba93694b3 100644
--- a/__tests__/__snapshots__/RoomItem.js.snap
+++ b/__tests__/__snapshots__/RoomItem.js.snap
@@ -35,15 +35,16 @@ exports[`render channel 1`] = `
Array [
Object {
"alignItems": "center",
- "borderRadius": 4,
- "height": 40,
"justifyContent": "center",
"overflow": "hidden",
- "width": 40,
},
Object {
"backgroundColor": "#00BCD4",
+ "borderRadius": 4,
+ "height": 40,
+ "width": 40,
},
+ undefined,
]
}
>
@@ -57,10 +58,14 @@ exports[`render channel 1`] = `
"color": undefined,
"fontSize": 12,
},
- Object {
- "color": "#fff",
- "fontSize": 20,
- },
+ Array [
+ Object {
+ "color": "#ffffff",
+ },
+ Object {
+ "fontSize": 20,
+ },
+ ],
Object {
"fontFamily": "Material Design Icons",
"fontStyle": "normal",
@@ -151,6 +156,53 @@ exports[`render no icon 1`] = `
testID={undefined}
tvParallaxProperties={undefined}
>
+
+
+
+
+
+
+
+
+
+
- );
+ if (type === 'd') {
+ const uri = avatar || `${ baseUrl }/avatar/${ text }`;
+ const image = (avatar || baseUrl) && (
+
+ );
+ return (
+
+ {initials}
+ {image}
+ );
+ }
+
+ const icon = {
+ c: 'pound',
+ p: 'lock',
+ l: 'account'
+ }[type];
return (
- {initials}
- {image}
- );
+
+
+ );
}
}
@@ -64,6 +78,7 @@ Avatar.propTypes = {
text: PropTypes.string.isRequired,
avatar: PropTypes.string,
size: PropTypes.number,
- borderRadius: PropTypes.number
+ borderRadius: PropTypes.number,
+ type: PropTypes.string
};
export default Avatar;
diff --git a/app/containers/Header.js b/app/containers/Header.js
index 4ffa7d9e9..b285d4121 100644
--- a/app/containers/Header.js
+++ b/app/containers/Header.js
@@ -1,18 +1,8 @@
import React from 'react';
-import { Text, View, StyleSheet, Platform, TouchableOpacity, Dimensions } from 'react-native';
-import Icon from 'react-native-vector-icons/Ionicons';
+import { View, StyleSheet, Platform } from 'react-native';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import Modal from 'react-native-modal';
import { SafeAreaView } from 'react-navigation';
-import DrawerMenuButton from '../presentation/DrawerMenuButton';
-import Avatar from './Avatar';
-import RocketChat from '../lib/rocketchat';
-import { STATUS_COLORS } from '../constants/colors';
-
-const TITLE_OFFSET = Platform.OS === 'ios' ? 70 : 56;
-
let platformContainerStyles;
if (Platform.OS === 'ios') {
platformContainerStyles = {
@@ -32,7 +22,6 @@ if (Platform.OS === 'ios') {
}
const appBarHeight = Platform.OS === 'ios' ? 44 : 56;
-const { width } = Dimensions.get('window');
const styles = StyleSheet.create({
container: {
backgroundColor: Platform.OS === 'ios' ? '#F7F7F7' : '#FFF',
@@ -41,171 +30,20 @@ const styles = StyleSheet.create({
},
appBar: {
flex: 1
- },
- header: {
- flexDirection: 'row',
- alignItems: 'center',
- flex: 1
- },
- titleContainer: {
- left: TITLE_OFFSET,
- right: TITLE_OFFSET,
- position: 'absolute',
- alignItems: 'center',
- justifyContent: Platform.OS === 'ios' ? 'center' : 'flex-start',
- flexDirection: 'row'
- },
- status: {
- borderRadius: 4,
- width: 8,
- height: 8,
- marginRight: 10
- },
- avatar: {
- marginRight: 10
- },
- title: {
- fontWeight: 'bold'
- },
- left: {
- left: 0,
- position: 'absolute'
- },
- right: {
- right: 0,
- position: 'absolute'
- },
- modal: {
- width: width - 60,
- height: width - 60,
- backgroundColor: '#F7F7F7',
- borderRadius: 4,
- flexDirection: 'column'
- },
- modalButton: {
- flex: 1,
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: 'transparent',
- borderBottomWidth: StyleSheet.hairlineWidth,
- borderBottomColor: 'rgba(0, 0, 0, .3)',
- paddingHorizontal: 20
}
});
-@connect(state => ({
- user: state.login.user,
- Site_Url: state.settings.Site_Url
-}))
export default class extends React.PureComponent {
static propTypes = {
- navigation: PropTypes.object.isRequired,
- user: PropTypes.object.isRequired,
- Site_Url: PropTypes.string
+ subview: PropTypes.object.isRequired
}
- constructor(props) {
- super(props);
- this.state = {
- isModalVisible: false
- };
- }
-
- onPressModalButton(status) {
- RocketChat.setUserPresenceDefaultStatus(status);
- this.hideModal();
- }
-
- showModal() {
- this.setState({ isModalVisible: true });
- }
-
- hideModal() {
- this.setState({ isModalVisible: false });
- }
-
- createChannel() {
- this.props.navigation.navigate('SelectUsers');
- }
-
- renderTitle() {
- if (!this.props.user.username) {
- return null;
- }
- return (
- this.showModal()}>
-
-
- {this.props.user.username}
-
- );
- }
-
- renderRight() {
- if (Platform.OS !== 'ios') {
- return;
- }
- return (
-
- this.createChannel()}
- />
-
- );
- }
-
- renderModalButton = (status, text) => {
- const statusStyle = [styles.status, { backgroundColor: STATUS_COLORS[status] }];
- const textStyle = { flex: 1, fontWeight: this.props.user.status === status ? 'bold' : 'normal' };
- return (
- this.onPressModalButton(status)}
- >
-
-
- {text || status.charAt(0).toUpperCase() + status.slice(1)}
-
-
- );
- };
-
render() {
return (
-
-
-
-
- {this.renderTitle()}
- {this.renderRight()}
-
+ {this.props.subview}
- this.hideModal()}
- onBackdropPress={() => this.hideModal()}
- >
-
- {this.renderModalButton('online')}
- {this.renderModalButton('busy')}
- {this.renderModalButton('away')}
- {this.renderModalButton('offline', 'Invisible')}
-
-
);
}
diff --git a/app/containers/routes/AuthRoutes.js b/app/containers/routes/AuthRoutes.js
index 95cbd0a9d..a5ab0837d 100644
--- a/app/containers/routes/AuthRoutes.js
+++ b/app/containers/routes/AuthRoutes.js
@@ -1,38 +1,19 @@
-import React from 'react';
import { Platform } from 'react-native';
import { StackNavigator, DrawerNavigator } from 'react-navigation';
import Sidebar from '../../containers/Sidebar';
-import DrawerMenuButton from '../../presentation/DrawerMenuButton';
-import Header from '../../containers/Header';
-
import RoomsListView from '../../views/RoomsListView';
import RoomView from '../../views/RoomView';
import CreateChannelView from '../../views/CreateChannelView';
import SelectUsersView from '../../views/SelectUsersView';
-const drawerPosition = 'left';
-const drawerIconPosition = 'headerLeft';
-
const AuthRoutes = StackNavigator(
{
RoomsList: {
- screen: RoomsListView,
- navigationOptions({ navigation }) {
- return {
- title: 'Rooms',
- header:
- };
- }
+ screen: RoomsListView
},
Room: {
- screen: RoomView,
- navigationOptions({ navigation }) {
- return {
- title: navigation.state.params.name || navigation.state.params.room.name || 'Room'
- // [drawerIconPosition]: ()รท
- };
- }
+ screen: RoomView
},
CreateChannel: {
screen: CreateChannelView,
@@ -53,18 +34,11 @@ const AuthRoutes = StackNavigator(
const Routes = DrawerNavigator(
{
Home: {
- screen: AuthRoutes,
- navigationOptions({ navigation }) {
- return {
- title: 'Rooms',
- [drawerIconPosition]:
- };
- }
+ screen: AuthRoutes
}
},
{
contentComponent: Sidebar,
- drawerPosition,
navigationOptions: {
drawerLockMode: Platform.OS === 'ios' ? 'locked-closed' : 'unlocked'
}
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index 4b2c6abee..c7144600a 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -11,6 +11,7 @@ import * as actions from '../actions';
import { someoneTyping } from '../actions/room';
import { setUser } from '../actions/login';
import { disconnect, connectSuccess } from '../actions/connect';
+import { requestActiveUser } from '../actions/activeUsers';
export { Accounts } from 'react-native-meteor';
@@ -47,6 +48,23 @@ const RocketChat = {
}
throw new Error({ error: 'invalid server' });
},
+ _setUser(ddpMessage) {
+ let status;
+ if (!ddpMessage.fields) {
+ status = 'offline';
+ } else {
+ status = ddpMessage.fields.status || 'offline';
+ }
+
+ const { user } = reduxStore.getState().login;
+ if (user && user.id === ddpMessage.id) {
+ return reduxStore.dispatch(setUser({ status }));
+ }
+
+ const activeUser = {};
+ activeUser[ddpMessage.id] = status;
+ return reduxStore.dispatch(requestActiveUser(activeUser));
+ },
connect(_url) {
return new Promise((resolve) => {
const url = `${ _url }/websocket`;
@@ -63,6 +81,16 @@ const RocketChat = {
});
Meteor.ddp.on('connected', async() => {
+ Meteor.ddp.on('added', (ddpMessage) => {
+ if (ddpMessage.collection === 'users') {
+ return RocketChat._setUser(ddpMessage);
+ }
+ });
+ Meteor.ddp.on('removed', (ddpMessage) => {
+ if (ddpMessage.collection === 'users') {
+ return RocketChat._setUser(ddpMessage);
+ }
+ });
Meteor.ddp.on('changed', (ddpMessage) => {
if (ddpMessage.collection === 'stream-room-messages') {
return realm.write(() => {
@@ -96,7 +124,7 @@ const RocketChat = {
}
}
if (ddpMessage.collection === 'users') {
- return reduxStore.dispatch(setUser({ status: ddpMessage.fields.status || ddpMessage.fields.statusDefault }));
+ return RocketChat._setUser(ddpMessage);
}
});
RocketChat.getSettings();
@@ -436,7 +464,7 @@ const RocketChat = {
});
Meteor.subscribe('stream-notify-user', `${ login.user.id }/subscriptions-changed`, false);
Meteor.subscribe('stream-notify-user', `${ login.user.id }/rooms-changed`, false);
- Meteor.subscribe('userData', null, false);
+ Meteor.subscribe('activeUsers', null, false);
return data;
},
logout({ server }) {
diff --git a/app/presentation/DrawerMenuButton.js b/app/presentation/DrawerMenuButton.js
deleted file mode 100644
index c1d743a75..000000000
--- a/app/presentation/DrawerMenuButton.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { TouchableOpacity } from 'react-native';
-import Icon from 'react-native-vector-icons/FontAwesome';
-
-const DrawerMenuButton = ({ navigation }) => (
- navigation.navigate('DrawerOpen')}
- >
-
-
-);
-
-DrawerMenuButton.propTypes = {
- navigation: PropTypes.object.isRequired
-};
-
-export default DrawerMenuButton;
diff --git a/app/presentation/RoomItem.js b/app/presentation/RoomItem.js
index a6bd3ce0a..3cf02dd65 100644
--- a/app/presentation/RoomItem.js
+++ b/app/presentation/RoomItem.js
@@ -1,10 +1,8 @@
import React from 'react';
import moment from 'moment';
-import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import PropTypes from 'prop-types';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import Avatar from '../containers/Avatar';
-import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
const styles = StyleSheet.create({
container: {
@@ -49,22 +47,6 @@ const styles = StyleSheet.create({
fontSize: 10,
height: 10,
color: '#888'
- },
- iconContainer: {
- height: 40,
- width: 40,
- borderRadius: 4,
- overflow: 'hidden',
- justifyContent: 'center',
- alignItems: 'center'
- },
- icon: {
- fontSize: 20,
- color: '#fff'
- },
- avatarInitials: {
- fontSize: 20,
- color: '#ffffff'
}
});
@@ -83,30 +65,7 @@ export default class RoomItem extends React.PureComponent {
get icon() {
const { type, name, baseUrl } = this.props;
-
- const icon = {
- d: 'at',
- c: 'pound',
- p: 'lock',
- l: 'account'
- }[type];
-
- if (!icon) {
- return null;
- }
-
- if (type === 'd') {
- return (
-
- );
- }
- const { color } = avatarInitialsAndColor(name);
-
- return (
-
-
-
- );
+ return ;
}
formatDate = date => moment(date).calendar(null, {
diff --git a/app/reducers/activeUsers.js b/app/reducers/activeUsers.js
new file mode 100644
index 000000000..71aa7c4a4
--- /dev/null
+++ b/app/reducers/activeUsers.js
@@ -0,0 +1,15 @@
+import * as types from '../actions/actionsTypes';
+
+const initialState = {};
+
+export default (state = initialState, action) => {
+ switch (action.type) {
+ case types.ACTIVE_USERS.SET:
+ return {
+ ...state,
+ ...action.data
+ };
+ default:
+ return state;
+ }
+};
diff --git a/app/reducers/index.js b/app/reducers/index.js
index 860dbdc35..88ca8ae6f 100644
--- a/app/reducers/index.js
+++ b/app/reducers/index.js
@@ -4,12 +4,14 @@ import login from './login';
import meteor from './connect';
import messages from './messages';
import room from './room';
+import rooms from './rooms';
import server from './server';
import navigator from './navigator';
import createChannel from './createChannel';
import app from './app';
import permissions from './permissions';
+import activeUsers from './activeUsers';
export default combineReducers({
- settings, login, meteor, messages, server, navigator, createChannel, app, room, permissions
+ settings, login, meteor, messages, server, navigator, createChannel, app, room, rooms, permissions, activeUsers
});
diff --git a/app/reducers/rooms.js b/app/reducers/rooms.js
index bd7ac7e98..0565dc80c 100644
--- a/app/reducers/rooms.js
+++ b/app/reducers/rooms.js
@@ -2,7 +2,8 @@ import * as types from '../actions/actionsTypes';
const initialState = {
isFetching: false,
- failure: false
+ failure: false,
+ searchText: ''
};
export default function login(state = initialState, action) {
@@ -24,8 +25,11 @@ export default function login(state = initialState, action) {
failure: true,
errorMessage: action.err
};
- // case types.LOGOUT:
- // return initialState;
+ case types.ROOMS.SET_SEARCH:
+ return {
+ ...state,
+ searchText: action.searchText
+ };
default:
return state;
}
diff --git a/app/sagas/activeUsers.js b/app/sagas/activeUsers.js
new file mode 100644
index 000000000..de5c02582
--- /dev/null
+++ b/app/sagas/activeUsers.js
@@ -0,0 +1,30 @@
+import { put, take, race, fork } from 'redux-saga/effects';
+import { delay } from 'redux-saga';
+import * as types from '../actions/actionsTypes';
+
+import { setActiveUser } from '../actions/activeUsers';
+
+const watchActiveUsers = function* handleInput() {
+ let obj = {};
+ while (true) {
+ const { status, timeout } = yield race({
+ status: take(types.ACTIVE_USERS.REQUEST),
+ timeout: delay(3000)
+ });
+ if (timeout && Object.keys(obj).length > 0) {
+ yield put(setActiveUser(obj));
+ obj = {};
+ }
+ if (status) {
+ obj = {
+ ...obj,
+ ...status.user
+ };
+ }
+ }
+};
+
+const root = function* root() {
+ yield fork(watchActiveUsers);
+};
+export default root;
diff --git a/app/sagas/index.js b/app/sagas/index.js
index 068401c54..a27f59a54 100644
--- a/app/sagas/index.js
+++ b/app/sagas/index.js
@@ -8,6 +8,7 @@ import selectServer from './selectServer';
import createChannel from './createChannel';
import init from './init';
import state from './state';
+import activeUsers from './activeUsers';
const root = function* root() {
yield all([
@@ -19,7 +20,8 @@ const root = function* root() {
connect(),
messages(),
selectServer(),
- state()
+ state(),
+ activeUsers()
]);
};
diff --git a/app/views/RoomView/Header/index.js b/app/views/RoomView/Header/index.js
new file mode 100644
index 000000000..85485ac61
--- /dev/null
+++ b/app/views/RoomView/Header/index.js
@@ -0,0 +1,115 @@
+import React from 'react';
+import { Text, View, Platform, TouchableOpacity } from 'react-native';
+import Icon from 'react-native-vector-icons/Ionicons';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { HeaderBackButton } from 'react-navigation';
+
+import realm from '../../../lib/realm';
+import Avatar from '../../../containers/Avatar';
+import { STATUS_COLORS } from '../../../constants/colors';
+import styles from './styles';
+
+@connect(state => ({
+ user: state.login.user,
+ baseUrl: state.settings.Site_Url,
+ activeUsers: state.activeUsers
+}))
+export default class extends React.Component {
+ static propTypes = {
+ navigation: PropTypes.object.isRequired,
+ user: PropTypes.object.isRequired,
+ baseUrl: PropTypes.string,
+ activeUsers: PropTypes.object
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ room: {},
+ roomName: props.navigation.state.params.name
+ };
+ this.rid = props.navigation.state.params.room.rid;
+ this.room = realm.objects('subscriptions').filtered('rid = $0', this.rid);
+ this.room.addListener(this.updateState);
+ }
+
+ componentDidMount() {
+ this.updateState();
+ }
+ componentWillUnmount() {
+ this.room.removeAllListeners();
+ }
+
+ getUserStatus() {
+ const userId = this.rid.replace(this.props.user.id, '').trim();
+ return this.props.activeUsers[userId] || 'offline';
+ }
+
+ getUserStatusLabel() {
+ const status = this.getUserStatus();
+ return status.charAt(0).toUpperCase() + status.slice(1);
+ }
+
+ updateState = () => {
+ this.setState({ room: this.room[0] });
+ };
+
+ isDirect = () => this.state.room && this.state.room.t === 'd';
+
+ renderLeft = () => this.props.navigation.goBack(null)} tintColor='#292E35' />;
+
+ renderTitle() {
+ if (!this.state.roomName) {
+ return null;
+ }
+ return (
+
+ {this.isDirect() ?
+
+ : null
+ }
+
+
+ {this.state.roomName}
+ {this.isDirect() ?
+ {this.getUserStatusLabel()}
+ : null
+ }
+
+
+ );
+ }
+
+ renderRight = () => (
+
+ {}}
+ >
+
+
+
+ );
+
+ render() {
+ return (
+
+ {this.renderLeft()}
+ {this.renderTitle()}
+ {this.renderRight()}
+
+ );
+ }
+}
diff --git a/app/views/RoomView/Header/styles.js b/app/views/RoomView/Header/styles.js
new file mode 100644
index 000000000..794dc83ea
--- /dev/null
+++ b/app/views/RoomView/Header/styles.js
@@ -0,0 +1,49 @@
+import { StyleSheet, Platform } from 'react-native';
+
+const TITLE_OFFSET = Platform.OS === 'ios' ? 70 : 56;
+export default StyleSheet.create({
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ flex: 1
+ },
+ titleContainer: {
+ left: TITLE_OFFSET,
+ right: TITLE_OFFSET,
+ position: 'absolute',
+ alignItems: 'center',
+ justifyContent: Platform.OS === 'ios' ? 'center' : 'flex-start',
+ flexDirection: 'row',
+ height: 44
+ },
+ status: {
+ borderRadius: 4,
+ width: 8,
+ height: 8,
+ marginRight: 10
+ },
+ userStatus: {
+ fontSize: 10,
+ color: '#888'
+ },
+ title: {
+ fontWeight: '500',
+ color: '#292E35'
+ },
+ left: {
+ left: 0,
+ position: 'absolute'
+ },
+ right: {
+ right: 0,
+ position: 'absolute',
+ flexDirection: 'row'
+ },
+ headerButton: {
+ backgroundColor: 'transparent',
+ height: 44,
+ width: 44,
+ alignItems: 'center',
+ justifyContent: 'center'
+ }
+});
diff --git a/app/views/RoomView.js b/app/views/RoomView/index.js
similarity index 80%
rename from app/views/RoomView.js
rename to app/views/RoomView/index.js
index 474930577..85f19bf8a 100644
--- a/app/views/RoomView.js
+++ b/app/views/RoomView/index.js
@@ -1,54 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Text, View, StyleSheet, Button, SafeAreaView } from 'react-native';
+import { Text, View, Button, SafeAreaView } from 'react-native';
import { ListView } from 'realm/react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import * as actions from '../actions';
-import { openRoom } from '../actions/room';
-import { editCancel } from '../actions/messages';
-import realm from '../lib/realm';
-import RocketChat from '../lib/rocketchat';
-import Message from '../containers/message';
-import MessageActions from '../containers/MessageActions';
-import MessageBox from '../containers/MessageBox';
-import Typing from '../containers/Typing';
-import KeyboardView from '../presentation/KeyboardView';
+import * as actions from '../../actions';
+import { openRoom } from '../../actions/room';
+import { editCancel } from '../../actions/messages';
+import realm from '../../lib/realm';
+import RocketChat from '../../lib/rocketchat';
+import Message from '../../containers/message';
+import MessageActions from '../../containers/MessageActions';
+import MessageBox from '../../containers/MessageBox';
+import Typing from '../../containers/Typing';
+import KeyboardView from '../../presentation/KeyboardView';
+import Header from '../../containers/Header';
+import RoomsHeader from './Header';
+import styles from './styles';
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
-const styles = StyleSheet.create({
- typing: { fontWeight: 'bold', paddingHorizontal: 15, height: 25 },
- container: {
- flex: 1,
- backgroundColor: '#fff'
- },
- safeAreaView: {
- flex: 1
- },
- list: {
- flex: 1,
- transform: [{ scaleY: -1 }]
- },
- separator: {
- height: 1,
- backgroundColor: '#CED0CE'
- },
- bannerContainer: {
- backgroundColor: 'orange'
- },
- bannerText: {
- margin: 5,
- textAlign: 'center',
- color: '#a00'
- },
- loadingMore: {
- transform: [{ scaleY: -1 }],
- textAlign: 'center',
- padding: 5,
- color: '#ccc'
- }
-});
+
const typing = () => ;
@connect(
state => ({
@@ -78,6 +50,10 @@ export default class RoomView extends React.Component {
loading: PropTypes.bool
};
+ static navigationOptions = ({ navigation }) => ({
+ header: } />
+ });
+
constructor(props) {
super(props);
this.rid =
diff --git a/app/views/RoomView/styles.js b/app/views/RoomView/styles.js
new file mode 100644
index 000000000..66ddd313d
--- /dev/null
+++ b/app/views/RoomView/styles.js
@@ -0,0 +1,34 @@
+import { StyleSheet } from 'react-native';
+
+export default StyleSheet.create({
+ typing: { fontWeight: 'bold', paddingHorizontal: 15, height: 25 },
+ container: {
+ flex: 1,
+ backgroundColor: '#fff'
+ },
+ safeAreaView: {
+ flex: 1
+ },
+ list: {
+ flex: 1,
+ transform: [{ scaleY: -1 }]
+ },
+ separator: {
+ height: 1,
+ backgroundColor: '#CED0CE'
+ },
+ bannerContainer: {
+ backgroundColor: 'orange'
+ },
+ bannerText: {
+ margin: 5,
+ textAlign: 'center',
+ color: '#a00'
+ },
+ loadingMore: {
+ transform: [{ scaleY: -1 }],
+ textAlign: 'center',
+ padding: 5,
+ color: '#ccc'
+ }
+});
diff --git a/app/views/RoomsListView/Header/index.js b/app/views/RoomsListView/Header/index.js
new file mode 100644
index 000000000..302fce5fe
--- /dev/null
+++ b/app/views/RoomsListView/Header/index.js
@@ -0,0 +1,210 @@
+import React from 'react';
+import { Text, View, Platform, TouchableOpacity, TextInput } from 'react-native';
+import Icon from 'react-native-vector-icons/Ionicons';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import Modal from 'react-native-modal';
+import { CachedImage } from 'react-native-img-cache';
+import { HeaderBackButton } from 'react-navigation';
+
+import Avatar from '../../../containers/Avatar';
+import RocketChat from '../../../lib/rocketchat';
+import { STATUS_COLORS } from '../../../constants/colors';
+import { setSearch } from '../../../actions/rooms';
+import styles from './styles';
+
+@connect(state => ({
+ user: state.login.user,
+ baseUrl: state.settings.Site_Url
+}), dispatch => ({
+ setSearch: searchText => dispatch(setSearch(searchText))
+}))
+export default class extends React.Component {
+ static propTypes = {
+ navigation: PropTypes.object.isRequired,
+ user: PropTypes.object.isRequired,
+ baseUrl: PropTypes.string,
+ setSearch: PropTypes.func
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ isModalVisible: false,
+ searching: false
+ };
+ }
+
+ onPressModalButton(status) {
+ RocketChat.setUserPresenceDefaultStatus(status);
+ this.hideModal();
+ }
+
+ onSearchChangeText(text) {
+ this.props.setSearch(text.trim());
+ }
+
+ onPressCancelSearchButton() {
+ this.setState({ searching: false });
+ this.props.setSearch('');
+ }
+
+ onPressSearchButton() {
+ this.setState({ searching: true });
+ requestAnimationFrame(() => {
+ this.inputSearch.focus();
+ });
+ }
+
+ showModal() {
+ this.setState({ isModalVisible: true });
+ }
+
+ hideModal() {
+ this.setState({ isModalVisible: false });
+ }
+
+ createChannel() {
+ this.props.navigation.navigate('SelectUsers');
+ }
+
+ renderLeft() {
+ return (
+
+ this.props.navigation.navigate('DrawerOpen')}
+ >
+
+
+
+ );
+ }
+
+ renderTitle() {
+ if (!this.props.user.username) {
+ return null;
+ }
+ return (
+ this.showModal()}>
+
+
+ {this.props.user.username}
+
+ );
+ }
+
+ renderRight() {
+ return (
+
+ {Platform.OS === 'android' ?
+ this.onPressSearchButton()}
+ >
+
+ : null}
+ {Platform.OS === 'ios' ?
+ this.createChannel()}
+ >
+
+ : null}
+
+ );
+ }
+
+ renderModalButton = (status, text) => {
+ const statusStyle = [styles.status, { backgroundColor: STATUS_COLORS[status] }];
+ const textStyle = { flex: 1, fontWeight: this.props.user.status === status ? 'bold' : 'normal' };
+ return (
+ this.onPressModalButton(status)}
+ >
+
+
+ {text || status.charAt(0).toUpperCase() + status.slice(1)}
+
+
+ );
+ };
+
+ renderHeader() {
+ if (this.state.searching) {
+ return null;
+ }
+ return (
+
+ {this.renderLeft()}
+ {this.renderTitle()}
+ {this.renderRight()}
+
+ );
+ }
+
+ renderSearch() {
+ if (!this.state.searching) {
+ return null;
+ }
+ return (
+
+
+ this.onPressCancelSearchButton()} />
+
+ this.inputSearch = inputSearch}
+ underlineColorAndroid='transparent'
+ style={styles.inputSearch}
+ onChangeText={text => this.onSearchChangeText(text)}
+ returnKeyType='search'
+ placeholder='Search'
+ clearButtonMode='while-editing'
+ blurOnSubmit
+ />
+
+ );
+ }
+
+ render() {
+ return (
+
+ {this.renderHeader()}
+ {this.renderSearch()}
+ this.hideModal()}
+ onBackdropPress={() => this.hideModal()}
+ >
+
+ {this.renderModalButton('online')}
+ {this.renderModalButton('busy')}
+ {this.renderModalButton('away')}
+ {this.renderModalButton('offline', 'Invisible')}
+
+
+
+ );
+ }
+}
diff --git a/app/views/RoomsListView/Header/styles.js b/app/views/RoomsListView/Header/styles.js
new file mode 100644
index 000000000..33d252ce8
--- /dev/null
+++ b/app/views/RoomsListView/Header/styles.js
@@ -0,0 +1,75 @@
+import { StyleSheet, Platform, Dimensions } from 'react-native';
+
+const TITLE_OFFSET = Platform.OS === 'ios' ? 70 : 56;
+const { width } = Dimensions.get('window');
+export default StyleSheet.create({
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ flex: 1
+ },
+ titleContainer: {
+ left: TITLE_OFFSET,
+ right: TITLE_OFFSET,
+ position: 'absolute',
+ alignItems: 'center',
+ justifyContent: Platform.OS === 'ios' ? 'center' : 'flex-start',
+ flexDirection: 'row',
+ height: 44
+ },
+ status: {
+ borderRadius: 4,
+ width: 8,
+ height: 8,
+ marginRight: 10
+ },
+ avatar: {
+ marginRight: 10
+ },
+ title: {
+ fontWeight: '500',
+ color: '#292E35'
+ },
+ left: {
+ left: 0,
+ position: 'absolute'
+ },
+ right: {
+ right: 0,
+ position: 'absolute',
+ flexDirection: 'row'
+ },
+ modal: {
+ width: width - 60,
+ height: width - 60,
+ backgroundColor: '#F7F7F7',
+ borderRadius: 4,
+ flexDirection: 'column'
+ },
+ modalButton: {
+ flex: 1,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: 'transparent',
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ borderBottomColor: 'rgba(0, 0, 0, .3)',
+ paddingHorizontal: 20
+ },
+ headerButton: {
+ backgroundColor: 'transparent',
+ height: 44,
+ width: 44,
+ alignItems: 'center',
+ justifyContent: 'center'
+ },
+ serverImage: {
+ width: 24,
+ height: 24,
+ borderRadius: 4
+ },
+ inputSearch: {
+ flex: 1,
+ marginLeft: 44
+ }
+});
diff --git a/app/views/RoomsListView.js b/app/views/RoomsListView/index.js
similarity index 77%
rename from app/views/RoomsListView.js
rename to app/views/RoomsListView/index.js
index 7ae5eeb8f..410524010 100644
--- a/app/views/RoomsListView.js
+++ b/app/views/RoomsListView/index.js
@@ -3,69 +3,26 @@ import { ListView } from 'realm/react-native';
import React from 'react';
import PropTypes from 'prop-types';
import Icon from 'react-native-vector-icons/Ionicons';
-import { Platform, View, StyleSheet, TextInput, SafeAreaView } from 'react-native';
+import { Platform, View, TextInput, SafeAreaView } from 'react-native';
import { connect } from 'react-redux';
-import * as actions from '../actions';
-import * as server from '../actions/connect';
-import realm from '../lib/realm';
-import RocketChat from '../lib/rocketchat';
-import RoomItem from '../presentation/RoomItem';
-import Banner from '../containers/Banner';
-import { goRoom } from '../containers/routes/NavigationService';
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- alignItems: 'stretch',
- justifyContent: 'center'
- },
- separator: {
- height: 1,
- backgroundColor: '#E7E7E7'
- },
- list: {
- width: '100%',
- backgroundColor: '#FFFFFF'
- },
- emptyView: {
- flexGrow: 1,
- alignItems: 'stretch',
- justifyContent: 'center'
- },
- emptyText: {
- textAlign: 'center',
- fontSize: 18,
- color: '#ccc'
- },
- actionButtonIcon: {
- fontSize: 20,
- height: 22,
- color: 'white'
- },
- searchBoxView: {
- backgroundColor: '#eee'
- },
- searchBox: {
- backgroundColor: '#fff',
- margin: 5,
- borderRadius: 5,
- padding: 5,
- paddingLeft: 10,
- color: '#aaa'
- },
- safeAreaView: {
- flex: 1,
- backgroundColor: '#fff'
- }
-});
+import * as actions from '../../actions';
+import * as server from '../../actions/connect';
+import realm from '../../lib/realm';
+import RocketChat from '../../lib/rocketchat';
+import RoomItem from '../../presentation/RoomItem';
+import Banner from '../../containers/Banner';
+import { goRoom } from '../../containers/routes/NavigationService';
+import Header from '../../containers/Header';
+import RoomsListHeader from './Header';
+import styles from './styles';
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
@connect(state => ({
server: state.server.server,
login: state.login,
Site_Url: state.settings.Site_Url,
- canShowList: state.login.token || state.login.user.token
- // Message_DateFormat: state.settings.Message_DateFormat
+ canShowList: state.login.token || state.login.user.token,
+ searchText: state.rooms.searchText
}), dispatch => ({
login: () => dispatch(actions.login()),
connect: () => dispatch(server.connectRequest())
@@ -75,10 +32,14 @@ export default class RoomsListView extends React.Component {
static propTypes = {
navigation: PropTypes.object.isRequired,
Site_Url: PropTypes.string,
- // Message_DateFormat: PropTypes.string,
- server: PropTypes.string
+ server: PropTypes.string,
+ searchText: PropTypes.string
}
+ static navigationOptions = ({ navigation }) => ({
+ header: } />
+ });
+
constructor(props) {
super(props);
@@ -104,6 +65,8 @@ export default class RoomsListView extends React.Component {
this.data.removeListener(this.updateState);
this.data = realm.objects('subscriptions').filtered('_server.id = $0', props.server).sorted('roomUpdatedAt', true);
this.data.addListener(this.updateState);
+ } else if (this.props.searchText !== props.searchText) {
+ this.search(props.searchText);
}
}
@@ -111,11 +74,13 @@ export default class RoomsListView extends React.Component {
this.data.removeAllListeners();
}
- onSearchChangeText = (text) => {
+ onSearchChangeText(text) {
+ this.setState({ searchText: text });
+ this.search(text);
+ }
+
+ search(text) {
const searchText = text.trim();
- this.setState({
- searchText: text
- });
if (searchText === '') {
return this.setState({
dataSource: ds.cloneWithRows(this.data)
@@ -222,7 +187,7 @@ export default class RoomsListView extends React.Component {
underlineColorAndroid='transparent'
style={styles.searchBox}
value={this.state.searchText}
- onChangeText={this.onSearchChangeText}
+ onChangeText={text => this.onSearchChangeText(text)}
returnKeyType='search'
placeholder='Search'
clearButtonMode='while-editing'
@@ -251,8 +216,8 @@ export default class RoomsListView extends React.Component {
dataSource={this.state.dataSource}
style={styles.list}
renderRow={this.renderItem}
- renderHeader={this.renderSearchBar}
- contentOffset={{ x: 0, y: 38 }}
+ renderHeader={Platform.OS === 'ios' ? this.renderSearchBar : null}
+ contentOffset={Platform.OS === 'ios' ? { x: 0, y: 38 } : {}}
enableEmptySections
keyboardShouldPersistTaps='always'
/>
diff --git a/app/views/RoomsListView/styles.js b/app/views/RoomsListView/styles.js
new file mode 100644
index 000000000..f3417bf10
--- /dev/null
+++ b/app/views/RoomsListView/styles.js
@@ -0,0 +1,47 @@
+import { StyleSheet } from 'react-native';
+
+export default StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'stretch',
+ justifyContent: 'center'
+ },
+ separator: {
+ height: 1,
+ backgroundColor: '#E7E7E7'
+ },
+ list: {
+ width: '100%',
+ backgroundColor: '#FFFFFF'
+ },
+ emptyView: {
+ flexGrow: 1,
+ alignItems: 'stretch',
+ justifyContent: 'center'
+ },
+ emptyText: {
+ textAlign: 'center',
+ fontSize: 18,
+ color: '#ccc'
+ },
+ actionButtonIcon: {
+ fontSize: 20,
+ height: 22,
+ color: 'white'
+ },
+ searchBoxView: {
+ backgroundColor: '#eee'
+ },
+ searchBox: {
+ backgroundColor: '#fff',
+ margin: 5,
+ borderRadius: 5,
+ padding: 5,
+ paddingLeft: 10,
+ color: '#aaa'
+ },
+ safeAreaView: {
+ flex: 1,
+ backgroundColor: '#fff'
+ }
+});