diff --git a/package.json b/package.json
index 41c4658d1..1c838c329 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,6 @@
"intl": "1.2.5",
"isomorphic-fetch": "2.2.1",
"keymirror": "0.1.1",
- "lodash": "4.16.4",
"react": "15.3.2",
"react-addons-pure-render-mixin": "15.3.2",
"react-intl": "2.1.5",
@@ -15,7 +14,6 @@
"react-native-button": "1.7.1",
"react-native-drawer": "2.3.0",
"react-native-keyboard-spacer": "0.3.0",
- "react-native-router-flux": "3.36.0",
"react-native-vector-icons": "2.1.0",
"react-redux": "4.4.5",
"redux": "3.6.0",
diff --git a/src/actions/navigation/index.js b/src/actions/navigation/index.js
new file mode 100644
index 000000000..e058ea725
--- /dev/null
+++ b/src/actions/navigation/index.js
@@ -0,0 +1,68 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {NavigationTypes} from 'constants';
+import Routes from 'navigation/routes';
+
+export function goBack() {
+ return async (dispatch, getState) => {
+ dispatch({
+ type: NavigationTypes.NAVIGATION_POP
+ }, getState);
+ };
+}
+
+export function goToLogin() {
+ return async (dispatch, getState) => {
+ dispatch({
+ type: NavigationTypes.NAVIGATION_PUSH,
+ route: Routes.Login
+ }, getState);
+ };
+}
+
+export function goToSelectTeam() {
+ return async (dispatch, getState) => {
+ dispatch({
+ type: NavigationTypes.NAVIGATION_PUSH,
+ route: Routes.SelectTeam
+ }, getState);
+ };
+}
+
+export function goToChannelView() {
+ return async (dispatch, getState) => {
+ dispatch({
+ type: NavigationTypes.NAVIGATION_PUSH,
+ route: Routes.ChannelView
+ }, getState);
+ };
+}
+
+export function goToRecentMentions() {
+ return async (dispatch, getState) => {
+ dispatch({
+ type: NavigationTypes.NAVIGATION_PUSH,
+ route: {
+ ...Routes.Search,
+ props: {
+ searchType: 'recent_mentions'
+ }
+ }
+ }, getState);
+ };
+}
+
+export function goToFlaggedPosts() {
+ return async (dispatch, getState) => {
+ dispatch({
+ type: NavigationTypes.NAVIGATION_PUSH,
+ route: {
+ ...Routes.Search,
+ props: {
+ searchType: 'flagged_posts'
+ }
+ }
+ }, getState);
+ };
+}
diff --git a/src/actions/views/root.js b/src/actions/views/root.js
new file mode 100644
index 000000000..0c5d462bc
--- /dev/null
+++ b/src/actions/views/root.js
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {NavigationTypes} from 'constants';
+import Routes from 'navigation/routes';
+
+export function goToSelectServer() {
+ return async (dispatch, getState) => {
+ dispatch({
+ type: NavigationTypes.NAVIGATION_RESET,
+ routes: [Routes.SelectServer],
+ index: 0
+ }, getState);
+ };
+}
diff --git a/src/components/logout.js b/src/components/logout.js
deleted file mode 100644
index af3397240..000000000
--- a/src/components/logout.js
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React, {Component, PropTypes} from 'react';
-import {TouchableHighlight, Text} from 'react-native';
-import {logout} from 'actions/users';
-import {RequestStatus} from 'constants';
-import {bindActionCreators} from 'redux';
-import {connect} from 'react-redux';
-import {Actions as Routes} from 'react-native-router-flux';
-
-const propTypes = {
- logout: PropTypes.object.isRequired,
- actions: PropTypes.object.isRequired
-};
-
-class Logout extends Component {
- static propTypes = propTypes;
-
- componentWillReceiveProps(nextProps) {
- if (this.props.logout.status === RequestStatus.STARTED &&
- nextProps.logout.status === RequestStatus.SUCCESS) {
- Routes.popTo('goToSelectServer');
- }
- }
-
- logout = () => this.props.actions.logout();
-
- render() {
- return (
-
- {'logout'}
-
- );
- }
-}
-
-function mapStateToProps(state) {
- return {
- logout: state.requests.users.logout
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({logout}, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(Logout);
diff --git a/src/components/logout/index.js b/src/components/logout/index.js
new file mode 100644
index 000000000..0e5754cd9
--- /dev/null
+++ b/src/components/logout/index.js
@@ -0,0 +1,6 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import LogoutContainer from './logout_container';
+
+export default LogoutContainer;
diff --git a/src/components/logout/logout.js b/src/components/logout/logout.js
new file mode 100644
index 000000000..6324858cd
--- /dev/null
+++ b/src/components/logout/logout.js
@@ -0,0 +1,24 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import {TouchableHighlight} from 'react-native';
+import FormattedText from 'components/formatted_text';
+
+export default class Logout extends React.Component {
+ static propTypes = {
+ actions: React.PropTypes.object.isRequired
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
diff --git a/src/components/logout/logout_container.js b/src/components/logout/logout_container.js
new file mode 100644
index 000000000..05687e3b1
--- /dev/null
+++ b/src/components/logout/logout_container.js
@@ -0,0 +1,17 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {bindActionCreators} from 'redux';
+import {connect} from 'react-redux';
+
+import {logout} from 'actions/users';
+
+import Logout from './logout.js';
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({logout}, dispatch)
+ };
+}
+
+export default connect(null, mapDispatchToProps)(Logout);
diff --git a/src/components/right_sidebar_menu/components/item.js b/src/components/right_sidebar_menu/components/item.js
new file mode 100644
index 000000000..0942bb603
--- /dev/null
+++ b/src/components/right_sidebar_menu/components/item.js
@@ -0,0 +1,42 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import {StyleSheet, TouchableHighlight, View} from 'react-native';
+
+const Styles = StyleSheet.create({
+ item: {
+ alignItems: 'center',
+ height: 40,
+ paddingLeft: 10,
+ paddingRight: 10,
+ flex: 1,
+ flexDirection: 'row'
+ }
+});
+
+export default class Item extends React.Component {
+ static propTypes = {
+ children: React.PropTypes.node,
+ onPress: React.PropTypes.func,
+ style: React.PropTypes.oneOfType([
+ React.PropTypes.object,
+ React.PropTypes.array
+ ])
+ }
+
+ render() {
+ return (
+
+
+ {this.props.children}
+
+
+ );
+ }
+}
diff --git a/src/components/right_sidebar_menu/index.js b/src/components/right_sidebar_menu/index.js
new file mode 100644
index 000000000..be9b8dd2f
--- /dev/null
+++ b/src/components/right_sidebar_menu/index.js
@@ -0,0 +1,6 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import RightSidebarMenuContainer from './right_sidebar_menu_container';
+
+export default RightSidebarMenuContainer;
diff --git a/src/components/right_sidebar_menu.js b/src/components/right_sidebar_menu/right_sidebar_menu.js
similarity index 80%
rename from src/components/right_sidebar_menu.js
rename to src/components/right_sidebar_menu/right_sidebar_menu.js
index 8c502999f..2b81fcbc1 100644
--- a/src/components/right_sidebar_menu.js
+++ b/src/components/right_sidebar_menu/right_sidebar_menu.js
@@ -3,13 +3,13 @@
import React from 'react';
-import FormattedText from './formatted_text.js';
+import FormattedText from 'components/formatted_text';
import Icon from 'react-native-vector-icons/FontAwesome';
+import Item from './components/item';
import {
ScrollView,
StyleSheet,
Text,
- TouchableHighlight,
View
} from 'react-native';
@@ -19,14 +19,6 @@ const Styles = StyleSheet.create({
flex: 1,
paddingTop: 20
},
- item: {
- alignItems: 'center',
- height: 40,
- paddingLeft: 10,
- paddingRight: 10,
- flex: 1,
- flexDirection: 'row'
- },
itemText: {
color: 'white'
},
@@ -45,14 +37,29 @@ const Styles = StyleSheet.create({
});
export default class RightSidebarMenu extends React.Component {
- handlePress() {
- console.log('press'); // eslint-disable-line no-console
+ static propTypes = {
+ actions: React.PropTypes.shape({
+ goToFlaggedPosts: React.PropTypes.func.isRequired,
+ goToRecentMentions: React.PropTypes.func.isRequired,
+ logout: React.PropTypes.func.isRequired
+ }).isRequired,
+ onClose: React.PropTypes.func.isRequired
+ }
+
+ goToRecentMentions = () => {
+ this.props.onClose();
+ this.props.actions.goToRecentMentions();
+ }
+
+ goToFlaggedPosts = () => {
+ this.props.onClose();
+ this.props.actions.goToFlaggedPosts();
}
render() {
return (
- -
+
-
{'@'}
- -
+
-
- -
+
-
- -
+
-
- -
+
-
- -
+
-
- -
+
-
- -
+
-
- -
+
-
- -
+
-
- -
+
-
- -
+
-
-
- {children}
-
-
- );
-}
-Item.propTypes = {
- onPress: React.PropTypes.func.isRequired,
- children: React.PropTypes.node
-};
-
function Divider() {
return ;
}
diff --git a/src/components/right_sidebar_menu/right_sidebar_menu_container.js b/src/components/right_sidebar_menu/right_sidebar_menu_container.js
new file mode 100644
index 000000000..6e62baa67
--- /dev/null
+++ b/src/components/right_sidebar_menu/right_sidebar_menu_container.js
@@ -0,0 +1,26 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {bindActionCreators} from 'redux';
+import {connect} from 'react-redux';
+
+import {goToFlaggedPosts, goToRecentMentions} from 'actions/navigation';
+import {logout} from 'actions/users';
+
+import RightSidebarMenu from './right_sidebar_menu';
+
+function mapStateToProps(state, ownProps) {
+ return ownProps;
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ goToFlaggedPosts,
+ goToRecentMentions,
+ logout
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(RightSidebarMenu);
diff --git a/src/constants/index.js b/src/constants/index.js
index d982e1852..8fda08805 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -8,6 +8,8 @@ import GeneralTypes from './general';
import UsersTypes from './users';
import TeamsTypes from './teams';
import PostsTypes from './posts';
+import NavigationTypes from './navigation';
+
import RequestStatus from './request_status';
export {
@@ -18,5 +20,7 @@ export {
TeamsTypes,
ChannelTypes,
PostsTypes,
+ NavigationTypes,
+
RequestStatus
};
diff --git a/src/constants/navigation.js b/src/constants/navigation.js
new file mode 100644
index 000000000..660a34548
--- /dev/null
+++ b/src/constants/navigation.js
@@ -0,0 +1,14 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import keymirror from 'keymirror';
+
+const NavigationTypes = keymirror({
+ NAVIGATION_PUSH: null,
+ NAVIGATION_POP: null,
+ NAVIGATION_JUMP: null,
+ NAVIGATION_JUMP_TO_INDEX: null,
+ NAVIGATION_RESET: null
+});
+
+export default NavigationTypes;
diff --git a/src/containers/root_container.js b/src/containers/root_container.js
deleted file mode 100644
index 4ffc751fe..000000000
--- a/src/containers/root_container.js
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import Config from 'config';
-import {connect} from 'react-redux';
-import {loadDevice} from 'actions/device';
-import Loading from 'components/loading';
-import Routes from 'routes';
-
-import {getTranslations} from 'i18n';
-import {IntlProvider} from 'react-intl';
-
-class RootContainer extends React.Component {
- static propTypes = {
- device: React.PropTypes.object.isRequired,
- loadDevice: React.PropTypes.func.isRequired
- };
-
- constructor(props) {
- super(props);
-
- this.props.loadDevice();
- }
-
- render() {
- const device = this.props.device;
-
- if (device.loading) {
- return (
-
- );
- }
-
- const locale = Config.DefaultLocale;
-
- return (
-
-
-
- );
- }
-}
-
-function mapStateToProps(state) {
- return {
- device: state.views.device
- };
-}
-
-export default connect(mapStateToProps, {
- loadDevice
-})(RootContainer);
diff --git a/src/layouts/root_layout/root_layout.js b/src/layouts/root_layout/root_layout.js
new file mode 100644
index 000000000..36c539d2e
--- /dev/null
+++ b/src/layouts/root_layout/root_layout.js
@@ -0,0 +1,27 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import {getTranslations} from 'i18n';
+import {IntlProvider} from 'react-intl';
+
+export default class RootLayout extends React.Component {
+ static propTypes = {
+ children: React.PropTypes.node,
+ locale: React.PropTypes.string.isRequired
+ }
+
+ render() {
+ const locale = this.props.locale;
+
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}
diff --git a/src/layouts/root_layout/root_layout_container.js b/src/layouts/root_layout/root_layout_container.js
new file mode 100644
index 000000000..192b62551
--- /dev/null
+++ b/src/layouts/root_layout/root_layout_container.js
@@ -0,0 +1,25 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {connect} from 'react-redux';
+
+import Config from 'config';
+
+import RootLayout from './root_layout';
+
+function mapStateToProps(state, ownProps) {
+ const users = state.entities.users;
+ const currentUserId = users.currentId;
+
+ let locale = Config.DefaultLocale;
+ if (currentUserId && users.profiles[currentUserId]) {
+ locale = users.profiles[currentUserId].locale;
+ }
+
+ return {
+ ...ownProps,
+ locale
+ };
+}
+
+export default connect(mapStateToProps)(RootLayout);
diff --git a/src/mattermost.js b/src/mattermost.js
index 00586f925..294a658b1 100644
--- a/src/mattermost.js
+++ b/src/mattermost.js
@@ -5,13 +5,17 @@ import React from 'react';
import {Provider} from 'react-redux';
import store from 'store';
-import RootContainer from 'containers/root_container.js';
+
+import Router from 'navigation/router';
+import RootLayout from 'layouts/root_layout/root_layout_container';
export default class Mattermost extends React.Component {
render() {
return (
-
+
+
+
);
}
diff --git a/src/navigation/router.js b/src/navigation/router.js
new file mode 100644
index 000000000..b0ca125ea
--- /dev/null
+++ b/src/navigation/router.js
@@ -0,0 +1,122 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import {bindActionCreators} from 'redux';
+import {connect} from 'react-redux';
+
+import {goBack} from 'actions/navigation';
+
+import {getComponentForScene} from 'scenes';
+
+import {Easing, NavigationExperimental, View} from 'react-native';
+import FormattedText from 'components/formatted_text.js';
+
+class Router extends React.Component {
+ static propTypes = {
+ navigation: React.PropTypes.object,
+ actions: React.PropTypes.shape({
+ goBack: React.PropTypes.func
+ }).isRequired
+ }
+
+ renderTransition = (transitionProps) => {
+ let title;
+ if (transitionProps.scene.route.title) {
+ title = (
+
+ );
+ }
+
+ const renderedScenes = transitionProps.scenes.map((scene) => {
+ const cardProps = {
+ ...transitionProps,
+ scene
+ };
+
+ return (
+
+ );
+ });
+
+ return (
+
+
+ {renderedScenes}
+
+ {title}
+
+ );
+ }
+
+ renderTitle = ({scene}) => {
+ const title = scene.route.title;
+
+ if (!title) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+ }
+
+ renderScene = ({scene}) => {
+ const SceneComponent = getComponentForScene(scene.route.key);
+
+ return ;
+ }
+
+ configureTransition = () => {
+ return {
+ duration: 500,
+ easing: Easing.inOut(Easing.ease)
+ };
+ }
+
+ render = () => {
+ return (
+
+ );
+ }
+}
+
+function mapStateToProps(state) {
+ return {
+ navigation: state.navigation
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ goBack
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Router);
diff --git a/src/navigation/routes.js b/src/navigation/routes.js
new file mode 100644
index 000000000..ab60c4512
--- /dev/null
+++ b/src/navigation/routes.js
@@ -0,0 +1,26 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export default {
+ Root: {
+ key: 'Root'
+ },
+ SelectServer: {
+ key: 'SelectServer',
+ title: {id: 'mobile.routes.enterServerUrl', defaultMessage: 'Enter Server URL'}
+ },
+ Login: {
+ key: 'Login',
+ title: {id: 'mobile.routes.login', defaultMessage: 'Login'}
+ },
+ SelectTeam: {
+ key: 'SelectTeam',
+ title: {id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}
+ },
+ ChannelView: {
+ key: 'ChannelView'
+ },
+ Search: {
+ key: 'Search'
+ }
+};
diff --git a/src/reducers/index.js b/src/reducers/index.js
index cd1202fb2..34c152ae4 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -6,9 +6,11 @@ import {combineReducers} from 'redux';
import entities from './entities';
import requests from './requests';
import views from './views';
+import navigation from './navigation';
export default combineReducers({
entities,
requests,
- views
+ views,
+ navigation
});
diff --git a/src/reducers/navigation/index.js b/src/reducers/navigation/index.js
new file mode 100644
index 000000000..0e072d6ae
--- /dev/null
+++ b/src/reducers/navigation/index.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {NavigationExperimental} from 'react-native';
+
+import {UsersTypes, NavigationTypes} from 'constants';
+
+import Routes from 'navigation/routes.js';
+
+const initialState = {
+ index: 0,
+ routes: [
+ Routes.Root
+ ]
+};
+
+export default function(state = initialState, action) {
+ switch (action.type) {
+ case NavigationTypes.NAVIGATION_PUSH:
+ return NavigationExperimental.StateUtils.push(state, action.route);
+
+ case NavigationTypes.NAVIGATION_POP:
+ return NavigationExperimental.StateUtils.pop(state);
+
+ case NavigationTypes.NAVIGATION_JUMP:
+ return NavigationExperimental.StateUtils.jumpTo(state, action.key);
+
+ case NavigationTypes.NAVIGATION_JUMP_TO_INDEX:
+ return NavigationExperimental.StateUtils.jumpToIndex(state, action.index);
+
+ case NavigationTypes.NAVIGATION_RESET:
+ return NavigationExperimental.StateUtils.reset(state, action.routes, action.index);
+
+ case UsersTypes.LOGOUT_SUCCESS:
+ return NavigationExperimental.StateUtils.reset(state, initialState.routes, initialState.index);
+
+ default:
+ return state;
+ }
+}
diff --git a/src/routes/index.js b/src/routes/index.js
deleted file mode 100644
index 515f019d4..000000000
--- a/src/routes/index.js
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React, {Component} from 'react';
-import {Scene, Router} from 'react-native-router-flux';
-
-import LoginContainer from './login/login_container.js';
-import SelectServerContainer from './select_server/select_server_container.js';
-import SelectTeamContainer from './select_team/select_team_container.js';
-import ChannelContainer from './channel/channel_container.js';
-import Logout from 'components/logout';
-import {logout} from 'actions/users';
-
-import {injectIntl, intlShape} from 'react-intl';
-
-class Routes extends Component {
- static propTypes = {
- intl: intlShape.isRequired
- };
-
- render() {
- const {formatMessage} = this.props.intl;
-
- return (
-
-
-
-
-
-
- }
- />
-
-
-
- );
- }
-}
-
-export default injectIntl(Routes);
diff --git a/src/routes/channel/channel.js b/src/scenes/channel/channel.js
similarity index 90%
rename from src/routes/channel/channel.js
rename to src/scenes/channel/channel.js
index 5c67047ce..59f43eb97 100644
--- a/src/routes/channel/channel.js
+++ b/src/scenes/channel/channel.js
@@ -6,9 +6,9 @@ import React from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import Drawer from 'react-native-drawer';
-import ChannelSidebar from 'components/channel_sidebar.js';
-import Loading from 'components/loading.js';
-import RightSidebarMenu from 'components/right_sidebar_menu.js';
+import ChannelSidebar from 'components/channel_sidebar';
+import Loading from 'components/loading';
+import RightSidebarMenu from 'components/right_sidebar_menu';
import {StatusBar, Text, TouchableHighlight, View} from 'react-native';
export default class Channel extends React.Component {
@@ -81,13 +81,13 @@ export default class Channel extends React.Component {
}
+ content={}
side='right'
tapToClose={true}
onCloseStart={this.closeRightSidebar}
openDrawerOffset={0.2}
>
-
+
+
+ {'This is a search for ' + this.props.searchType}
+
+
+
+ );
+ }
+}
diff --git a/src/scenes/search/search_container.js b/src/scenes/search/search_container.js
new file mode 100644
index 000000000..3908b834e
--- /dev/null
+++ b/src/scenes/search/search_container.js
@@ -0,0 +1,26 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {bindActionCreators} from 'redux';
+import {connect} from 'react-redux';
+
+import {goBack} from 'actions/navigation';
+
+import Search from './search';
+
+function mapStateToProps(state, ownProps) {
+ return {
+ ...ownProps,
+ ...state.views.search
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ goBack
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Search);
diff --git a/src/routes/select_server/select_server.js b/src/scenes/select_server/select_server.js
similarity index 96%
rename from src/routes/select_server/select_server.js
rename to src/scenes/select_server/select_server.js
index aefb102a3..8109dda79 100644
--- a/src/routes/select_server/select_server.js
+++ b/src/scenes/select_server/select_server.js
@@ -4,7 +4,6 @@
import React, {Component} from 'react';
import {View, TextInput, Image} from 'react-native';
import KeyboardSpacer from 'react-native-keyboard-spacer';
-import {Actions as Routes} from 'react-native-router-flux';
import Client from 'client';
import Button from 'components/button';
@@ -30,7 +29,7 @@ class SelectServer extends Component {
this.props.actions.getPing().then(() => {
if (this.props.server.status === RequestStatus.SUCCESS) {
- Routes.goToLogin();
+ this.props.actions.goToLogin();
}
});
};
diff --git a/src/routes/select_server/select_server_container.js b/src/scenes/select_server/select_server_container.js
similarity index 88%
rename from src/routes/select_server/select_server_container.js
rename to src/scenes/select_server/select_server_container.js
index 2e759f5c1..c74e5ae72 100644
--- a/src/routes/select_server/select_server_container.js
+++ b/src/scenes/select_server/select_server_container.js
@@ -5,6 +5,7 @@ import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getPing} from 'actions/general';
+import {goToLogin} from 'actions/navigation';
import * as SelectServerActions from 'actions/views/select_server';
import SelectServer from './select_server';
@@ -20,7 +21,8 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
...SelectServerActions,
- getPing
+ getPing,
+ goToLogin
}, dispatch)
};
}
diff --git a/src/routes/select_team/select_team.js b/src/scenes/select_team/select_team.js
similarity index 96%
rename from src/routes/select_team/select_team.js
rename to src/scenes/select_team/select_team.js
index c62d4c0a8..0910516d5 100644
--- a/src/routes/select_team/select_team.js
+++ b/src/scenes/select_team/select_team.js
@@ -4,7 +4,6 @@
import React, {Component, PropTypes} from 'react';
import {View, Image, Text} from 'react-native';
-import {Actions as Routes} from 'react-native-router-flux';
import Button from 'react-native-button';
import Loading from 'components/loading';
import Icon from 'react-native-vector-icons/MaterialIcons';
@@ -30,7 +29,7 @@ export default class SelectTeam extends Component {
onSelectTeam(team) {
this.props.actions.selectTeam(team);
- Routes.goToMain({currentTeamId: team.id});
+ this.props.actions.goToChannelView();
}
render() {
diff --git a/src/routes/select_team/select_team_container.js b/src/scenes/select_team/select_team_container.js
similarity index 79%
rename from src/routes/select_team/select_team_container.js
rename to src/scenes/select_team/select_team_container.js
index 8bdd05550..126c2d809 100644
--- a/src/routes/select_team/select_team_container.js
+++ b/src/scenes/select_team/select_team_container.js
@@ -3,7 +3,10 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
+
import * as teamActions from 'actions/teams';
+import {goToChannelView} from 'actions/navigation';
+
import SelectTeamView from './select_team.js';
function mapStateToProps(state) {
@@ -17,7 +20,10 @@ function mapStateToProps(state) {
function mapDispatchToProps(dispatch) {
return {
- actions: bindActionCreators(teamActions, dispatch)
+ actions: bindActionCreators({
+ ...teamActions,
+ goToChannelView
+ }, dispatch)
};
}
diff --git a/test/actions/users.test.js b/test/actions/users.test.js
index 53009fdbb..93dafff87 100644
--- a/test/actions/users.test.js
+++ b/test/actions/users.test.js
@@ -7,6 +7,7 @@ import * as Actions from 'actions/users';
import Client from 'client';
import configureStore from 'store/configureStore';
import {RequestStatus} from 'constants';
+import Routes from 'navigation/routes';
import TestHelper from 'test_helper';
describe('Actions.Users', () => {
@@ -54,13 +55,15 @@ describe('Actions.Users', () => {
const store = configureStore();
store.subscribe(() => {
- const logoutRequest = store.getState().requests.users.logout;
- const general = store.getState().entities.general;
- const users = store.getState().entities.users;
- const loginView = store.getState().views.login;
- const teams = store.getState().entities.teams;
- const channels = store.getState().entities.channels;
- const posts = store.getState().entities.posts;
+ const state = store.getState();
+ const logoutRequest = state.requests.users.logout;
+ const general = state.entities.general;
+ const users = state.entities.users;
+ const loginView = state.views.login;
+ const teams = state.entities.teams;
+ const channels = state.entities.channels;
+ const posts = state.entities.posts;
+ const navigation = state.navigation;
if (logoutRequest.status === RequestStatus.SUCCESS || logoutRequest.status === RequestStatus.FAILURE) {
if (logoutRequest.error) {
@@ -95,6 +98,8 @@ describe('Actions.Users', () => {
assert.strictEqual(posts.currentFocusedPostId, '', 'current focused post id is not empty');
assert.deepStrictEqual(posts.postsInfo, {}, 'posts info is not empty');
assert.deepStrictEqual(posts.latestPageTime, {}, 'posts latest page time is not empty');
+ assert.strictEqual(navigation.index, 0, 'navigation not reset to first element of stack');
+ assert.deepStrictEqual(navigation.routes, [Routes.Root], 'navigation not reset to root route');
done();
}
diff --git a/test/reducers/navigation.test.js b/test/reducers/navigation.test.js
new file mode 100644
index 000000000..b6a3b6ce6
--- /dev/null
+++ b/test/reducers/navigation.test.js
@@ -0,0 +1,223 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import assert from 'assert';
+import deepFreeze from 'deep-freeze';
+
+import {NavigationTypes} from 'constants';
+import Routes from 'navigation/routes';
+import reduceNavigation from 'reducers/navigation';
+
+function reduceAndFreeze(state, actions) {
+ return deepFreeze(reduceNavigation(state, actions));
+}
+
+function initialState() {
+ return reduceAndFreeze(undefined, {});
+}
+
+describe('Reducers.Navigation', () => {
+ it('initial state', () => {
+ const state = initialState();
+
+ assert.deepEqual(state, {
+ index: 0,
+ routes: [Routes.Root]
+ }, 'initial state');
+ });
+
+ it('NAVIGATION_PUSH', () => {
+ let state = initialState();
+
+ it('first push', () => {
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.PUSH,
+ route: {key: 'fake'}
+ });
+
+ assert.deepEqual(state, {
+ index: 1,
+ routes: [Routes.Root, {key: 'fake'}]
+ });
+ });
+
+ it('second push', () => {
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.PUSH,
+ route: {key: 'fake2'}
+ });
+
+ assert.deepEqual(state, {
+ index: 1,
+ routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}]
+ });
+ });
+
+ it('push when not at head of stack', () => {
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.JUMP,
+ key: Routes.key.root
+ });
+
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.PUSH,
+ route: {key: 'fake3'}
+ });
+
+ // The next route on the stack is replaced, but anything past that is left unchanged
+ assert.deepEqual(state, {
+ index: 1,
+ routes: [Routes.Root, {key: 'fake3'}, {key: 'fake2'}]
+ });
+ });
+ });
+
+ it('NAVIGATION_POP', () => {
+ let state = initialState();
+
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.PUSH,
+ route: {key: 'fake'}
+ });
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.PUSH,
+ route: {key: 'fake2'}
+ });
+
+ it('first pop', () => {
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.POP
+ });
+
+ assert.deepEqual(state, {
+ index: 1,
+ routes: [Routes.Root, {key: 'fake'}]
+ });
+ });
+
+ it('second pop', () => {
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.POP
+ });
+
+ assert.deepEqual(state, {
+ index: 0,
+ routes: [Routes.Root]
+ });
+ });
+
+ it('pop last entry on stack', () => {
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.POP
+ });
+
+ assert.deepEqual(state, {
+ index: 0,
+ routes: [Routes.Root]
+ });
+ });
+ });
+
+ it('NAVIGATION_JUMP', () => {
+ let state = initialState();
+
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.PUSH,
+ route: {key: 'fake'}
+ });
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.PUSH,
+ route: {key: 'fake2'}
+ });
+
+ it('jump back', () => {
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.NAVIGATION_JUMP,
+ key: Routes.Root.key
+ });
+
+ // The rest of the stack is preserved
+ assert.deepEqual(state, {
+ index: 0,
+ routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}]
+ });
+ });
+
+ it('jump forward', () => {
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.NAVIGATION_JUMP,
+ key: 'fake'
+ });
+
+ assert.deepEqual(state, {
+ index: 2,
+ routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}]
+ });
+ });
+ });
+
+ it('NAVIGATION_JUMP_TO_INDEX', () => {
+ let state = initialState();
+
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.PUSH,
+ route: {key: 'fake'}
+ });
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.PUSH,
+ route: {key: 'fake2'}
+ });
+
+ it('jump back', () => {
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.NAVIGATION_JUMP_TO_INDEX,
+ index: 1
+ });
+
+ // The rest of the stack is preserved
+ assert.deepEqual(state, {
+ index: 0,
+ routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}]
+ });
+ });
+
+ it('jump forward', () => {
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.NAVIGATION_JUMP_TO_INDEX,
+ index: 2
+ });
+
+ assert.deepEqual(state, {
+ index: 2,
+ routes: [Routes.Root, {key: 'fake'}, {key: 'fake2'}]
+ });
+ });
+ });
+
+ it('NAVIGATION_RESET', () => {
+ let state = initialState();
+
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.PUSH,
+ route: {key: 'fake'}
+ });
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.PUSH,
+ route: {key: 'fake2'}
+ });
+
+ it('reset', () => {
+ state = reduceAndFreeze(state, {
+ type: NavigationTypes.NAVIGATION_RESET,
+ index: 1,
+ routes: [{key: 'fake3'}, {key: 'fake4'}]
+ });
+
+ // The navigation state is wiped out and replaced with the new state
+ assert.deepEqual(state, {
+ index: 1,
+ routes: [{key: 'fake3'}, {key: 'fake4'}]
+ });
+ });
+ });
+});