From 566a22c389a8af318134e1a4c8a461a237e20e84 Mon Sep 17 00:00:00 2001
From: Guilherme Gazzo <guilhermegazzo@gmail.com>
Date: Thu, 17 Aug 2017 03:28:41 -0300
Subject: [PATCH]  get message saga

---
 app/actions/actionsTypes.js |   1 +
 app/actions/messages.js     |  22 ++++++
 app/actions/rooms.js        |   4 +-
 app/components/banner.js    |  41 +++++++++++
 app/lib/createStore.js      |   4 +-
 app/lib/rocketchat.js       |  49 ++++++-------
 app/reducers/index.js       |  12 ++++
 app/reducers/messages.js    |  29 ++++++++
 app/reducers/reducers.js    |   2 +-
 app/reducers/rooms.js       |   2 +-
 app/reducers/rootReducer.js |  10 ---
 app/routes/index.js         |  39 +++++++++++
 app/sagas/connect.js        |   8 +--
 app/sagas/index.js          |   4 ++
 app/sagas/logger.js         |  12 ++++
 app/sagas/login.js          |   8 +--
 app/sagas/messages.js       |  27 ++++++++
 app/sagas/rooms.js          |   5 +-
 app/views/login.js          |   7 +-
 app/views/room.js           |  53 +++++++-------
 app/views/roomsList.js      |  37 ++--------
 index.ios.js                |   4 ++
 package-lock.json           | 133 ++++++++++++++++++++++++++++++++++++
 package.json                |   1 +
 24 files changed, 396 insertions(+), 118 deletions(-)
 create mode 100644 app/actions/messages.js
 create mode 100644 app/components/banner.js
 create mode 100644 app/reducers/index.js
 create mode 100644 app/reducers/messages.js
 delete mode 100644 app/reducers/rootReducer.js
 create mode 100644 app/routes/index.js
 create mode 100644 app/sagas/logger.js
 create mode 100644 app/sagas/messages.js

diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js
index 5b541dcd0..7d0d5906d 100644
--- a/app/actions/actionsTypes.js
+++ b/app/actions/actionsTypes.js
@@ -12,6 +12,7 @@ function createRequestTypes(base, types = defaultTypes) {
 // Login events
 export const LOGIN = createRequestTypes('LOGIN');
 export const ROOMS = createRequestTypes('ROOMS');
+export const MESSAGES = createRequestTypes('MESSAGES');
 export const METEOR = createRequestTypes('METEOR_CONNECT');
 export const LOGOUT = 'LOGOUT'; // logout is always success
 
diff --git a/app/actions/messages.js b/app/actions/messages.js
new file mode 100644
index 000000000..6c508fdcd
--- /dev/null
+++ b/app/actions/messages.js
@@ -0,0 +1,22 @@
+import * as types from './actionsTypes';
+
+export function messagesRequest({ rid }) {
+	console.log(types.MESSAGES.REQUEST, rid);
+	return {
+		type: types.MESSAGES.REQUEST,
+		rid
+	};
+}
+
+export function messagesSuccess() {
+	return {
+		type: types.MESSAGES.SUCCESS
+	};
+}
+
+export function messagesFailure(err) {
+	return {
+		type: types.MESSAGES.FAILURE,
+		err
+	};
+}
diff --git a/app/actions/rooms.js b/app/actions/rooms.js
index e22049e4b..bdbf94d05 100644
--- a/app/actions/rooms.js
+++ b/app/actions/rooms.js
@@ -1,6 +1,6 @@
 import * as types from './actionsTypes';
 
-export function roomsSuccessRequest() {
+export function roomsRequest() {
 	return {
 		type: types.ROOMS.REQUEST
 	};
@@ -12,7 +12,7 @@ export function roomsSuccess() {
 	};
 }
 
-export function roomsSuccessFailure(err) {
+export function roomsFailure(err) {
 	return {
 		type: types.ROOMS.FAILURE,
 		err
diff --git a/app/components/banner.js b/app/components/banner.js
new file mode 100644
index 000000000..eb4683385
--- /dev/null
+++ b/app/components/banner.js
@@ -0,0 +1,41 @@
+import { StyleSheet, View, Text } from 'react-native';
+import React from 'react';
+
+import { connect } from 'react-redux';
+
+const styles = StyleSheet.create({
+	bannerContainer: {
+		backgroundColor: '#ddd'
+	},
+	bannerText: {
+		textAlign: 'center',
+		margin: 5
+	}
+});
+
+@connect(state => ({
+	connecting: state.meteor && state.meteor.connecting,
+	authenticating: state.login && state.login.isFetching
+}))
+
+export default class Banner extends React.PureComponent {
+	render() {
+		const { connecting, authenticating } = this.props;
+		if (connecting) {
+			return (
+				<View style={[styles.bannerContainer, { backgroundColor: '#0d0' }]}>
+					<Text style={[styles.bannerText, { color: '#fff' }]}>Connecting...</Text>
+				</View>
+			);
+		}
+
+		if (authenticating) {
+			return (
+				<View style={[styles.bannerContainer, { backgroundColor: 'orange' }]}>
+					<Text style={[styles.bannerText, { color: '#a00' }]}>Authenticating...</Text>
+				</View>
+			);
+		}
+		return null;
+	}
+}
diff --git a/app/lib/createStore.js b/app/lib/createStore.js
index cd923bcbd..749e2860a 100644
--- a/app/lib/createStore.js
+++ b/app/lib/createStore.js
@@ -4,7 +4,7 @@ import 'regenerator-runtime/runtime';
 import { createStore, applyMiddleware } from 'redux';
 import createSagaMiddleware from 'redux-saga';
 import logger from 'redux-logger';
-import rootReducer from '../reducers/rootReducer';
+import reducers from '../reducers';
 import sagas from '../sagas';
 
 const sagaMiddleware = createSagaMiddleware();
@@ -19,7 +19,7 @@ if (__DEV__) {
 }
 
 export default createStore(
-	rootReducer,
+	reducers,
 	applyMiddleware(sagaMiddleware)
 );
 sagaMiddleware.run(sagas);
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index a7666ed46..450bdcd82 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -189,35 +189,36 @@ const RocketChat = {
 	},
 
 	loadMessagesForRoom(rid, end, cb) {
-		Meteor.call('loadHistory', rid, end, 20, (err, data) => {
-			if (err) {
-				console.error(err);
-				if (cb) {
-					cb({ end: true });
+		return new Promise((resolve, reject) => {
+			Meteor.call('loadHistory', rid, end, 20, (err, data) => {
+				if (err) {
+					if (cb) {
+						cb({ end: true });
+					}
+					return reject(err);
 				}
-				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 (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) {
-					cb({ end: true });
-				} else {
-					cb({ end: false });
+				if (cb) {
+					if (data.messages.length < 20) {
+						cb({ end: true });
+					} else {
+						cb({ end: false });
+					}
 				}
-			}
+				resolve();
+				Meteor.subscribe('stream-room-messages', rid, false);
+			});
 		});
-
-		Meteor.subscribe('stream-room-messages', rid, false);
 	},
 
 	getMessage(rid, msg = {}) {
diff --git a/app/reducers/index.js b/app/reducers/index.js
new file mode 100644
index 000000000..2bc2642d8
--- /dev/null
+++ b/app/reducers/index.js
@@ -0,0 +1,12 @@
+import { combineReducers } from 'redux';
+import * as reducers from './reducers';
+import login from './login';
+import meteor from './connect';
+import messages from './messages';
+
+console.log(Object.keys({
+	...reducers, login, meteor, messages
+}));
+export default combineReducers({
+	...reducers, login, meteor, messages
+});
diff --git a/app/reducers/messages.js b/app/reducers/messages.js
new file mode 100644
index 000000000..c0fb2749f
--- /dev/null
+++ b/app/reducers/messages.js
@@ -0,0 +1,29 @@
+import * as types from '../actions/actionsTypes';
+
+const initialState = {
+	isFetching: false,
+	failure: false
+};
+
+export default function messages(state = initialState, action) {
+	switch (action.type) {
+		case types.MESSAGES.REQUEST:
+			return { ...state,
+				isFetching: true
+			};
+		case types.MESSAGES.SUCCESS:
+			return { ...state,
+				isFetching: false
+			};
+		case types.LOGIN.FAILURE:
+			return { ...state,
+				isFetching: false,
+				failure: true,
+				errorMessage: action.err
+			};
+		// case types.LOGOUT:
+		// 	return initialState;
+		default:
+			return state;
+	}
+}
diff --git a/app/reducers/reducers.js b/app/reducers/reducers.js
index 3cd0c5ac6..7e23459fc 100644
--- a/app/reducers/reducers.js
+++ b/app/reducers/reducers.js
@@ -13,7 +13,7 @@ export function server(state = initialState.server, action) {
 
 export function settings(state = initialState.settings, action) {
 	if (action.type === types.SET_ALL_SETTINGS) {
-		return {
+		return { ...state,
 			...action.payload
 		};
 	}
diff --git a/app/reducers/rooms.js b/app/reducers/rooms.js
index f1e158134..5964a0d7d 100644
--- a/app/reducers/rooms.js
+++ b/app/reducers/rooms.js
@@ -15,7 +15,7 @@ export default function login(state = initialState, action) {
 			return { ...state,
 				isFetching: false
 			};
-		case types.LOGIN.FAILURE:
+		case types.ROOMS.FAILURE:
 			return { ...state,
 				isFetching: false,
 				failure: true,
diff --git a/app/reducers/rootReducer.js b/app/reducers/rootReducer.js
deleted file mode 100644
index 7c29d61c9..000000000
--- a/app/reducers/rootReducer.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { combineReducers } from 'redux';
-import * as reducers from './reducers';
-import * as login from './login';
-import * as connect from './connect';
-
-const rootReducer = combineReducers({
-	...reducers, ...login, ...connect
-});
-
-export default rootReducer;
diff --git a/app/routes/index.js b/app/routes/index.js
new file mode 100644
index 000000000..5b189b99c
--- /dev/null
+++ b/app/routes/index.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import {
+	Scene,
+	Router
+	// Actions,
+	// Reducer,
+	// ActionConst,
+	// Tabs,
+	// Modal,
+	// Drawer,
+	// Stack,
+	// Lightbox
+} from 'react-native-router-flux';
+// import { Navigation } from 'react-native-navigation';
+import { Provider } from 'react-redux';
+
+import LoginView from '../views/login';
+import NewServerView from '../views/serverNew';
+import ListServerView from '../views/serverList';
+import RoomsListView from '../views/roomsList';
+import RoomView from '../views/room';
+// import PhotoView from '../views/Photo';
+// import CreateChannel from '../views/CreateChannel';
+import store from '../lib/createStore';
+
+export default () => (
+	<Provider store={store}>
+		<Router>
+			<Scene key='root'>
+				<Scene key='listServer' component={ListServerView} title='Servers' />
+				<Scene key='newServer' component={NewServerView} title='New Server' />
+				<Scene key='login' component={LoginView} title='Login' />
+				<Scene key='roomList' component={RoomsListView} />
+				<Scene key='room' component={RoomView} initial />
+			</Scene>
+		</Router>
+	</Provider>
+);
+// <Scene key='register' component={Register} title='Register' />
diff --git a/app/sagas/connect.js b/app/sagas/connect.js
index deab72e0f..bc3a5b1f7 100644
--- a/app/sagas/connect.js
+++ b/app/sagas/connect.js
@@ -1,4 +1,4 @@
-import { take, put, call, fork } from 'redux-saga/effects';
+import { take, put, call } from 'redux-saga/effects';
 import { METEOR } from '../actions/actionsTypes';
 import RocketChat from '../lib/rocketchat';
 
@@ -20,8 +20,4 @@ const watchConnect = function* watchConnect() {
 		}
 	}
 };
-
-const root = function* root() {
-	yield fork(watchConnect);
-};
-export default root;
+export default watchConnect;
diff --git a/app/sagas/index.js b/app/sagas/index.js
index c2e3aead9..9b9e0f2de 100644
--- a/app/sagas/index.js
+++ b/app/sagas/index.js
@@ -3,12 +3,16 @@ import hello from './hello';
 import login from './login';
 import connect from './connect';
 import rooms from './rooms';
+import logger from './logger';
+import messages from './messages';
 
 const root = function* root() {
 	yield fork(hello);
 	yield fork(rooms);
 	yield fork(login);
 	yield fork(connect);
+	yield fork(logger);
+	yield fork(messages);
 };
 // Consider using takeEvery
 export default root;
diff --git a/app/sagas/logger.js b/app/sagas/logger.js
new file mode 100644
index 000000000..857811278
--- /dev/null
+++ b/app/sagas/logger.js
@@ -0,0 +1,12 @@
+import { select, takeEvery } from 'redux-saga/effects';
+
+const root = function* watchAndLog() {
+	yield takeEvery('*', function* logger(action) {
+		const state = yield select();
+		const tmp = { ...state };
+		delete tmp.settings;
+		console.log('action', action);
+		console.log('state after', tmp);
+	});
+};
+export default root;
diff --git a/app/sagas/login.js b/app/sagas/login.js
index aec7be89c..04b4ac62d 100644
--- a/app/sagas/login.js
+++ b/app/sagas/login.js
@@ -1,5 +1,5 @@
 import React from 'react';
-import { take, put, call, fork } from 'redux-saga/effects';
+import { take, put, call, takeLast } from 'redux-saga/effects';
 import * as types from '../actions/actionsTypes';
 import { loginSuccess, loginFailure } from '../actions/login';
 import RocketChat from '../lib/rocketchat';
@@ -23,8 +23,4 @@ const watchLoginRequest = function* watchLoginRequest() {
 		}
 	}
 };
-
-const root = function* root() {
-	yield fork(watchLoginRequest);
-};
-export default root;
+export default watchLoginRequest;
diff --git a/app/sagas/messages.js b/app/sagas/messages.js
new file mode 100644
index 000000000..47774474f
--- /dev/null
+++ b/app/sagas/messages.js
@@ -0,0 +1,27 @@
+import { takeEvery, takeLatest, select, take, put } from 'redux-saga/effects';
+import { MESSAGES, LOGIN } from '../actions/actionsTypes';
+import { messagesSuccess, messagesFailure } from '../actions/messages';
+import RocketChat from '../lib/rocketchat';
+
+const get = function* get({ rid }) {
+	const auth = yield select(state => state.login.isAuthenticated);
+	if (!auth) {
+		yield take(LOGIN.SUCCESS);
+	}
+	try {
+		yield RocketChat.loadMessagesForRoom(rid, null);
+		yield put(messagesSuccess());
+	} catch (err) {
+		console.log(err);
+		yield put(messagesFailure(err.status));
+	}
+};
+const getData = function* getData() {
+	yield takeLatest(MESSAGES.REQUEST, get);
+};
+
+const getMessages = function* getMessages() {
+	yield takeEvery(LOGIN.SUCCESS, getData);
+};
+
+export default getMessages;
diff --git a/app/sagas/rooms.js b/app/sagas/rooms.js
index f02a445a1..1c8dfeb9b 100644
--- a/app/sagas/rooms.js
+++ b/app/sagas/rooms.js
@@ -25,7 +25,4 @@ const watchRoomsRequest = function* watchRoomsRequest() {
 	}
 };
 
-const root = function* root() {
-	yield fork(watchRoomsRequest);
-};
-export default root;
+export default watchRoomsRequest;
diff --git a/app/views/login.js b/app/views/login.js
index 3b6d0b398..3b60024b0 100644
--- a/app/views/login.js
+++ b/app/views/login.js
@@ -65,7 +65,9 @@ class LoginView extends React.Component {
 	}
 	submit = () => {
 		const {	username, password, code } = this.state;
+		console.log({	username, password, code });
 		this.props.loginRequest({	username, password, code });
+		this.props.navigator.dismissModal();
 		//
 		//
 		// this.setState({
@@ -107,10 +109,10 @@ class LoginView extends React.Component {
 		}
 	}
 
+	// {this.props.login.isFetching && <Text> LOGANDO</Text>}
 	render() {
 		return (
 			<KeyboardView style={styles.view} keyboardVerticalOffset={64}>
-				{this.props.login.isFetching && <Text> LOGANDO</Text>}
 				<TextInput
 					style={styles.input}
 					onChangeText={username => this.setState({ username })}
@@ -140,11 +142,12 @@ class LoginView extends React.Component {
 }
 
 function mapStateToProps(state) {
+	// console.log(Object.keys(state));
 	return {
 		server: state.server,
 		Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder,
 		Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder,
-		login: state.default
+		login: state.login || state.default
 	};
 }
 
diff --git a/app/views/room.js b/app/views/room.js
index 1530799aa..243954184 100644
--- a/app/views/room.js
+++ b/app/views/room.js
@@ -6,6 +6,7 @@ import { connect } from 'react-redux';
 import { bindActionCreators } from 'redux';
 
 import * as actions from '../actions';
+import { messagesRequest } from '../actions/messages';
 import realm from '../lib/realm';
 import RocketChat from '../lib/rocketchat';
 import debounce from '../utils/throttle';
@@ -47,9 +48,11 @@ const styles = StyleSheet.create({
 @connect(state => ({
 	server: state.server,
 	Site_Url: state.settings.Site_Url,
-	Message_TimeFormat: state.settings.Message_TimeFormat
+	Message_TimeFormat: state.settings.Message_TimeFormat,
+	loading: state.messages.isFetching
 }), dispatch => ({
-	actions: bindActionCreators(actions, dispatch)
+	actions: bindActionCreators(actions, dispatch),
+	getMessages: rid => dispatch(messagesRequest({ rid }))
 }))
 export default class RoomView extends React.Component {
 	static propTypes = {
@@ -69,7 +72,7 @@ export default class RoomView extends React.Component {
 
 		this.data = realm.objects('messages').filtered('_server.id = $0 AND rid = $1', this.props.server, this.rid).sorted('ts', true);
 		this.state = {
-			dataSource: ds.cloneWithRows(this.data.slice(0, 10)),
+			dataSource: ds.cloneWithRows(this.data),
 			loaded: true,
 			joined: typeof props.rid === 'undefined'
 		};
@@ -80,17 +83,18 @@ export default class RoomView extends React.Component {
 	}
 
 	componentWillMount() {
-		const late = setTimeout(() => this.setState({
-			loaded: false
-		}), 1000);
-		RocketChat.loadMessagesForRoom(this.rid, null, () => {
-			clearTimeout(late);
-			this.setState({
-				loaded: true
-			});
-			this.data.addListener(this.updateState);
-		});
-		this.updateState();
+		this.props.getMessages(this.rid);
+		// const late = setTimeout(() => this.setState({
+		// 	loaded: false
+		// }), 1000);
+		// RocketChat.loadMessagesForRoom(this.rid, null, () => {
+		// 	clearTimeout(late);
+		// 	this.setState({
+		// 		loaded: true
+		// 	});
+		this.data.addListener(this.updateState);
+		// });
+		// this.updateState();
 	}
 
 	componentDidMount() {
@@ -141,15 +145,13 @@ export default class RoomView extends React.Component {
 			});
 	};
 
-	renderBanner = () => {
-		if (this.state.loaded === false) {
-			return (
-				<View style={styles.bannerContainer}>
-					<Text style={styles.bannerText}>Loading new messages...</Text>
-				</View>
-			);
-		}
-	};
+	renderBanner = () => (this.props.loading ?
+		(
+			<View style={styles.bannerContainer}>
+				<Text style={styles.bannerText}>Loading new messages...</Text>
+			</View>
+		) : null)
+
 
 	renderItem = ({ item }) => (
 		<Message
@@ -193,11 +195,6 @@ 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}>
 				{this.renderBanner()}
diff --git a/app/views/roomsList.js b/app/views/roomsList.js
index 9b0831f35..056955533 100644
--- a/app/views/roomsList.js
+++ b/app/views/roomsList.js
@@ -13,7 +13,8 @@ import * as meteor from '../actions/connect';
 import realm from '../lib/realm';
 import RocketChat from '../lib/rocketchat';
 import RoomItem from '../components/RoomItem';
-import debounce from '../utils/debounce';
+import Banner from '../components/banner';
+// import debounce from '../utils/debounce';
 
 const styles = StyleSheet.create({
 	container: {
@@ -38,13 +39,6 @@ const styles = StyleSheet.create({
 		fontSize: 18,
 		color: '#ccc'
 	},
-	bannerContainer: {
-		backgroundColor: '#ddd'
-	},
-	bannerText: {
-		textAlign: 'center',
-		margin: 5
-	},
 	actionButtonIcon: {
 		fontSize: 20,
 		height: 22,
@@ -254,11 +248,11 @@ export default class RoomsListView extends React.Component {
 		});
 	}
 
-	updateState = debounce(() => {
+	updateState = () => {
 		this.setState({
 			dataSource: ds.cloneWithRows(this.state.data)
 		});
-	}, 500);
+	};
 
 	_onPressItem = (id, item = {}) => {
 		const navigateToRoom = (room) => {
@@ -312,27 +306,6 @@ export default class RoomsListView extends React.Component {
 			screen: 'CreateChannel'
 		});
 	}
-
-	renderBanner = () => {
-		const status = Meteor.getData() && Meteor.getData().ddp && Meteor.getData().ddp.status;
-
-		if (status === 'disconnected') {
-			return (
-				<View style={[styles.bannerContainer, { backgroundColor: '#0d0' }]}>
-					<Text style={[styles.bannerText, { color: '#fff' }]}>Connecting...</Text>
-				</View>
-			);
-		}
-
-		if (status === 'connected' && Meteor._isLoggingIn) {
-			return (
-				<View style={[styles.bannerContainer, { backgroundColor: 'orange' }]}>
-					<Text style={[styles.bannerText, { color: '#a00' }]}>Authenticating...</Text>
-				</View>
-			);
-		}
-	}
-
 	renderItem = ({ item }) => (
 		<RoomsListItem
 			item={item}
@@ -393,7 +366,7 @@ export default class RoomsListView extends React.Component {
 	render() {
 		return (
 			<View style={styles.container}>
-				{this.renderBanner()}
+				<Banner />
 				{this.renderList()}
 				{this.renderCreateButtons()}
 			</View>
diff --git a/index.ios.js b/index.ios.js
index 31dc38cf6..dac1db546 100644
--- a/index.ios.js
+++ b/index.ios.js
@@ -1,3 +1,7 @@
 import 'babel-polyfill';
 import 'regenerator-runtime/runtime';
 import './app/navigation';
+// import { AppRegistry } from 'react-native';
+// import Routes from './app/routes';
+//
+// AppRegistry.registerComponent('RocketChatRN', () => Routes);
diff --git a/package-lock.json b/package-lock.json
index a297c56ea..8baaeece7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4512,6 +4512,11 @@
       "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
       "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U="
     },
+    "lodash.isequal": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+      "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
+    },
     "lodash.keys": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
@@ -4859,6 +4864,14 @@
       "resolved": "https://registry.npmjs.org/mobx/-/mobx-2.7.0.tgz",
       "integrity": "sha1-zz2C0YwMp/RY2PKiQIF7PcflSgE="
     },
+    "mobx-react": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-4.2.2.tgz",
+      "integrity": "sha1-25zDyv772DDQWEwRSa9armeCkgE=",
+      "requires": {
+        "hoist-non-react-statics": "1.2.0"
+      }
+    },
     "moment": {
       "version": "2.18.1",
       "resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz",
@@ -5156,6 +5169,92 @@
         "mimic-fn": "1.1.0"
       }
     },
+    "opencollective": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/opencollective/-/opencollective-1.0.3.tgz",
+      "integrity": "sha1-ruY3K8KBRFg2kMPKja7PwSDdDvE=",
+      "requires": {
+        "babel-polyfill": "6.23.0",
+        "chalk": "1.1.3",
+        "inquirer": "3.0.6",
+        "minimist": "1.2.0",
+        "node-fetch": "1.6.3",
+        "opn": "4.0.2"
+      },
+      "dependencies": {
+        "ansi-escapes": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
+          "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4="
+        },
+        "babel-polyfill": {
+          "version": "6.23.0",
+          "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz",
+          "integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0=",
+          "requires": {
+            "babel-runtime": "6.25.0",
+            "core-js": "2.5.0",
+            "regenerator-runtime": "0.10.5"
+          }
+        },
+        "core-js": {
+          "version": "2.5.0",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.0.tgz",
+          "integrity": "sha1-VpwFCRi+ZIazg3VSAorgRmtxcIY="
+        },
+        "inquirer": {
+          "version": "3.0.6",
+          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.0.6.tgz",
+          "integrity": "sha1-4EqqnQW3o8ubD0B9BDdfBEcZA0c=",
+          "requires": {
+            "ansi-escapes": "1.4.0",
+            "chalk": "1.1.3",
+            "cli-cursor": "2.1.0",
+            "cli-width": "2.1.0",
+            "external-editor": "2.0.4",
+            "figures": "2.0.0",
+            "lodash": "4.17.4",
+            "mute-stream": "0.0.7",
+            "run-async": "2.3.0",
+            "rx": "4.1.0",
+            "string-width": "2.1.1",
+            "strip-ansi": "3.0.1",
+            "through": "2.3.8"
+          }
+        },
+        "node-fetch": {
+          "version": "1.6.3",
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz",
+          "integrity": "sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ=",
+          "requires": {
+            "encoding": "0.1.12",
+            "is-stream": "1.1.0"
+          }
+        },
+        "opn": {
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz",
+          "integrity": "sha1-erwi5kTf9jsKltWrfyeQwPAavJU=",
+          "requires": {
+            "object-assign": "4.1.1",
+            "pinkie-promise": "2.0.1"
+          }
+        },
+        "regenerator-runtime": {
+          "version": "0.10.5",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
+          "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "requires": {
+            "ansi-regex": "2.1.1"
+          }
+        }
+      }
+    },
     "opn": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/opn/-/opn-3.0.3.tgz",
@@ -5842,6 +5941,14 @@
       "resolved": "https://registry.npmjs.org/react-native-autogrow-textinput/-/react-native-autogrow-textinput-4.1.0.tgz",
       "integrity": "sha1-p+WxfrPBarCOMbv7iNkkiO2H8nY="
     },
+    "react-native-button": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/react-native-button/-/react-native-button-2.1.0.tgz",
+      "integrity": "sha1-o54jKSkir+6k974UHdQ+GPG1GHY=",
+      "requires": {
+        "prop-types": "15.5.10"
+      }
+    },
     "react-native-card-view": {
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/react-native-card-view/-/react-native-card-view-0.0.3.tgz",
@@ -5958,6 +6065,27 @@
       "resolved": "https://registry.npmjs.org/react-native-optimized-flatlist/-/react-native-optimized-flatlist-1.0.1.tgz",
       "integrity": "sha1-2+6C8gi0i+8jxssm8dXzrFjmdbI="
     },
+    "react-native-router-flux": {
+      "version": "4.0.0-beta.18",
+      "resolved": "https://registry.npmjs.org/react-native-router-flux/-/react-native-router-flux-4.0.0-beta.18.tgz",
+      "integrity": "sha1-wjSIm2+VCZmlZoZaulGexfjJ54g=",
+      "requires": {
+        "lodash.isequal": "4.5.0",
+        "mobx": "3.2.2",
+        "mobx-react": "4.2.2",
+        "opencollective": "1.0.3",
+        "prop-types": "15.5.10",
+        "react-native-button": "2.1.0",
+        "react-navigation": "1.0.0-beta.11"
+      },
+      "dependencies": {
+        "mobx": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/mobx/-/mobx-3.2.2.tgz",
+          "integrity": "sha1-qmcUWb7e39mIDJSIiaP2K84JJ5w="
+        }
+      }
+    },
     "react-native-svg": {
       "version": "5.4.1",
       "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-5.4.1.tgz",
@@ -6520,6 +6648,11 @@
         "is-promise": "2.1.0"
       }
     },
+    "rx": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz",
+      "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I="
+    },
     "rx-lite": {
       "version": "4.0.8",
       "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz",
diff --git a/package.json b/package.json
index be7054a5a..b8111b8e0 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
     "react-native-meteor": "^1.1.0",
     "react-native-navigation": "^1.1.193",
     "react-native-optimized-flatlist": "^1.0.1",
+    "react-native-router-flux": "^4.0.0-beta.18",
     "react-native-svg": "^5.4.1",
     "react-native-svg-image": "^1.1.4",
     "react-native-vector-icons": "^4.3.0",
-- 
GitLab