diff --git a/.babelrc b/.babelrc
index a9ce1369..d0cf03df 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,3 +1,4 @@
{
- "presets": ["react-native"]
+ "presets": ["react-native"],
+ "plugins": ["transform-decorators-legacy"]
}
diff --git a/.eslintrc b/.eslintrc
index 11327145..0c700ae4 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -115,5 +115,7 @@
"object-shorthand": 2,
"consistent-return": 0
},
- "globals": {}
+ "globals": {
+ "__DEV__": true
+ }
}
diff --git a/app/actions/index.js b/app/actions/index.js
new file mode 100644
index 00000000..f7c6888f
--- /dev/null
+++ b/app/actions/index.js
@@ -0,0 +1,131 @@
+import * as types from '../constants/types';
+
+export function setCurrentServer(server) {
+ return {
+ type: types.SET_CURRENT_SERVER,
+ payload: server
+ };
+}
+
+export function preventESLintError() {
+ return {};
+}
+// // GENRES
+// export function retrieveMoviesGenresSuccess(res) {
+// return {
+// type: types.RETRIEVE_MOVIES_GENRES_SUCCESS,
+// moviesGenres: res.data
+// };
+// }
+
+// export function retrieveMoviesGenres() {
+// return function (dispatch) {
+// return axios.get(`${TMDB_URL}/genre/movie/list?api_key=${TMDB_API_KEY}`)
+// .then(res => {
+// dispatch(retrieveMoviesGenresSuccess(res));
+// })
+// .catch(error => {
+// console.log(error); //eslint-disable-line
+// });
+// };
+// }
+
+// // POPULAR
+// export function retrievePopularMoviesSuccess(res) {
+// return {
+// type: types.RETRIEVE_POPULAR_MOVIES_SUCCESS,
+// popularMovies: res.data
+// };
+// }
+
+// export function retrievePopularMovies(page) {
+// return function (dispatch) {
+// return axios.get(`${TMDB_URL}/movie/popular?api_key=${TMDB_API_KEY}&page=${page}`)
+// .then(res => {
+// dispatch(retrievePopularMoviesSuccess(res));
+// })
+// .catch(error => {
+// console.log('Popular', error); //eslint-disable-line
+// });
+// };
+// }
+
+// // NOW PLAYING
+// export function retrieveNowPlayingMoviesSuccess(res) {
+// return {
+// type: types.RETRIEVE_NOWPLAYING_MOVIES_SUCCESS,
+// nowPlayingMovies: res.data
+// };
+// }
+
+// export function retrieveNowPlayingMovies(page) {
+// return function (dispatch) {
+// return axios.get(`${TMDB_URL}/movie/now_playing?api_key=${TMDB_API_KEY}&page=${page}`)
+// .then(res => {
+// dispatch(retrieveNowPlayingMoviesSuccess(res));
+// })
+// .catch(error => {
+// console.log('Now Playing', error); //eslint-disable-line
+// });
+// };
+// }
+
+// // MOVIES LIST
+// export function retrieveMoviesListSuccess(res) {
+// return {
+// type: types.RETRIEVE_MOVIES_LIST_SUCCESS,
+// list: res.data
+// };
+// }
+
+// export function retrieveMoviesList(type, page) {
+// return function (dispatch) {
+// return axios.get(`${TMDB_URL}/movie/${type}?api_key=${TMDB_API_KEY}&page=${page}`)
+// .then(res => {
+// dispatch(retrieveMoviesListSuccess(res));
+// })
+// .catch(error => {
+// console.log('Movies List', error); //eslint-disable-line
+// });
+// };
+// }
+
+// // SEARCH RESULTS
+// export function retrieveMoviesSearchResultsSuccess(res) {
+// return {
+// type: types.RETRIEVE_MOVIES_SEARCH_RESULT_SUCCESS,
+// searchResults: res.data
+// };
+// }
+
+// export function retrieveMoviesSearchResults(query, page) {
+// return function (dispatch) {
+// return axios.get(`${TMDB_URL}/search/movie?api_key=${TMDB_API_KEY}&query=${query}&page=${page}`)
+// .then(res => {
+// dispatch(retrieveMoviesSearchResultsSuccess(res));
+// })
+// .catch(error => {
+// console.log('Movies Search Results', error); //eslint-disable-line
+// });
+// };
+// }
+
+// // MOVIE DETAILS
+// export function retrieveMovieDetailsSuccess(res) {
+// return {
+// type: types.RETRIEVE_MOVIE_DETAILS_SUCCESS,
+// details: res.data
+// };
+// }
+
+// export function retrieveMovieDetails(movieId) {
+// return function (dispatch) {
+// return axios.get(`${TMDB_URL}/movie/${movieId}?api_key=${TMDB_API_KEY}&append_to_response=casts,images,videos`)
+// .then(res => {
+// dispatch(retrieveMovieDetailsSuccess(res));
+// })
+// .catch(error => {
+// console.log('Movie Details', error); //eslint-disable-line
+// });
+// };
+// }
diff --git a/app/constants/types.js b/app/constants/types.js
new file mode 100644
index 00000000..301c702d
--- /dev/null
+++ b/app/constants/types.js
@@ -0,0 +1,10 @@
+// export const RETRIEVE_MOVIES_GENRES_SUCCESS = 'RETRIEVE_MOVIES_GENRES_SUCCESS';
+
+// export const RETRIEVE_POPULAR_MOVIES_SUCCESS = 'RETRIEVE_POPULAR_MOVIES_SUCCESS';
+// export const RETRIEVE_NOWPLAYING_MOVIES_SUCCESS = 'RETRIEVE_NOWPLAYING_MOVIES_SUCCESS';
+// export const RETRIEVE_MOVIES_LIST_SUCCESS = 'RETRIEVE_MOVIES_LIST_SUCCESS';
+// export const RETRIEVE_MOVIE_DETAILS_SUCCESS = 'RETRIEVE_MOVIE_DETAILS_SUCCESS';
+// export const RETRIEVE_MOVIES_SEARCH_RESULT_SUCCESS = 'RETRIEVE_MOVIES_SEARCH_RESULT_SUCCESS';
+
+export const SET_CURRENT_SERVER = 'SET_CURRENT_SERVER';
+export const ESLINT_FIX = 'ESLINT_FIX';
diff --git a/app/lib/createStore.js b/app/lib/createStore.js
new file mode 100644
index 00000000..6571eab3
--- /dev/null
+++ b/app/lib/createStore.js
@@ -0,0 +1,22 @@
+import { createStore, applyMiddleware } from 'redux';
+import thunk from 'redux-thunk';
+import logger from 'redux-logger';
+import rootReducer from '../reducers/rootReducer';
+
+let middleware = [thunk];
+
+if (__DEV__) {
+ /* eslint-disable global-require */
+ const reduxImmutableStateInvariant = require('redux-immutable-state-invariant').default();
+ middleware = [...middleware, reduxImmutableStateInvariant, logger];
+} else {
+ middleware = [...middleware];
+}
+
+export default function configureStore(initialState) {
+ return createStore(
+ rootReducer,
+ initialState,
+ applyMiddleware(...middleware)
+ );
+}
diff --git a/app/lib/realm.js b/app/lib/realm.js
index f7ad194b..64c057fe 100644
--- a/app/lib/realm.js
+++ b/app/lib/realm.js
@@ -70,20 +70,15 @@ const messagesSchema = {
}
};
-
-// Realm.clearTestState();
-
const realm = new Realm({
schema: [settingsSchema, serversSchema, subscriptionSchema, messagesSchema, usersSchema]
});
export default realm;
-// Clear settings
+// Realm.clearTestState();
// realm.write(() => {
-// // const allSettins = realm.objects('settings');
-// // realm.delete(allSettins);
-//
-// // realm.create('servers', { id: 'https://demo.rocket.chat', current: false }, true);
-// // realm.create('servers', { id: 'http://localhost:3000', current: false }, true);
+// realm.create('servers', { id: 'https://demo.rocket.chat', current: false }, true);
+// realm.create('servers', { id: 'http://localhost:3000', current: false }, true);
+// realm.create('servers', { id: 'http://10.0.2.2:3000', current: false }, true);
// });
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index 7a8c0fa3..09992994 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -1,5 +1,7 @@
import Meteor from 'react-native-meteor';
import Random from 'react-native-meteor/lib/Random';
+import { AsyncStorage } from 'react-native';
+
import realm from './realm';
import debounce from '../utils/debounce';
@@ -32,13 +34,14 @@ const write = (() => {
run();
};
})();
-const RocketChat = {
+const RocketChat = {
createChannel({ name, users, type }) {
return new Promise((resolve, reject) => {
Meteor.call(type ? 'createChannel' : 'createPrivateGroup', name, users, type, (err, res) => (err ? reject(err) : resolve(res)));
});
},
+
get currentServer() {
const current = realm.objects('servers').filtered('current = true').slice(0, 1)[0];
return current && current.id;
@@ -51,6 +54,15 @@ const RocketChat = {
});
},
+ async getUserToken() {
+ const TOKEN_KEY = 'reactnativemeteor_usertoken';
+ try {
+ return await AsyncStorage.getItem(TOKEN_KEY);
+ } catch (error) {
+ console.warn(`AsyncStorage error: ${ error.message }`);
+ }
+ },
+
connect(cb) {
const url = `${ RocketChat.currentServer }/websocket`;
@@ -94,6 +106,7 @@ const RocketChat = {
realm.create('messages', message, true);
});
}
+
this.subCache = this.subCache || {};
this.roomCache = this.roomCache || {};
this.cache = {};
@@ -319,6 +332,7 @@ const RocketChat = {
};
export default RocketChat;
+
Meteor.Accounts.onLogin(() => {
Promise.all([call('subscriptions/get'), call('rooms/get')]).then(([subscriptions, rooms]) => {
subscriptions = subscriptions.sort((s1, s2) => (s1.rid > s2.rid ? 1 : -1));
diff --git a/app/navigation.js b/app/navigation.js
index 67ae8149..a9c696a5 100644
--- a/app/navigation.js
+++ b/app/navigation.js
@@ -1,18 +1,22 @@
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 CreateChannel from './views/CreateChannel';
+import configureStore from './lib/createStore';
+const store = configureStore();
-Navigation.registerComponent('Rooms', () => RoomsListView);
-Navigation.registerComponent('Room', () => RoomView);
-Navigation.registerComponent('ListServer', () => ListServerView);
-Navigation.registerComponent('Login', () => LoginView);
-Navigation.registerComponent('NewServer', () => NewServerView);
-Navigation.registerComponent('CreateChannel', () => CreateChannel);
+Navigation.registerComponent('Rooms', () => RoomsListView, store, Provider);
+Navigation.registerComponent('Room', () => RoomView, store, Provider);
+Navigation.registerComponent('ListServer', () => ListServerView, store, Provider);
+Navigation.registerComponent('Login', () => LoginView, store, Provider);
+Navigation.registerComponent('NewServer', () => NewServerView, store, Provider);
+Navigation.registerComponent('CreateChannel', () => CreateChannel, store, Provider);
Navigation.startSingleScreenApp({
screen: {
diff --git a/app/reducers/initialState.js b/app/reducers/initialState.js
new file mode 100644
index 00000000..94083a9a
--- /dev/null
+++ b/app/reducers/initialState.js
@@ -0,0 +1,6 @@
+import RocketChat from '../lib/rocketchat';
+
+export default {
+ server: RocketChat.currentServer,
+ login: {}
+};
diff --git a/app/reducers/reducers.js b/app/reducers/reducers.js
new file mode 100644
index 00000000..f70ea40d
--- /dev/null
+++ b/app/reducers/reducers.js
@@ -0,0 +1,52 @@
+import RocketChat from '../lib/rocketchat';
+import * as types from '../constants/types';
+import initialState from './initialState';
+
+export default function(state = initialState, action) {
+ switch (action.type) {
+ case types.SET_CURRENT_SERVER:
+ RocketChat.currentServer = action.payload;
+ return {
+ ...state,
+ server: action.payload
+ };
+
+ // case types.RETRIEVE_POPULAR_MOVIES_SUCCESS:
+ // return {
+ // ...state,
+ // popularMovies: action.popularMovies
+ // };
+
+ // case types.RETRIEVE_NOWPLAYING_MOVIES_SUCCESS:
+ // return {
+ // ...state,
+ // nowPlayingMovies: action.nowPlayingMovies
+ // };
+
+ // case types.RETRIEVE_MOVIES_GENRES_SUCCESS:
+ // return {
+ // ...state,
+ // genres: action.moviesGenres
+ // };
+
+ // case types.RETRIEVE_MOVIES_LIST_SUCCESS:
+ // return {
+ // ...state,
+ // list: action.list
+ // };
+
+ // case types.RETRIEVE_MOVIE_DETAILS_SUCCESS:
+ // return {
+ // ...state,
+ // details: action.details
+ // };
+
+ // case types.RETRIEVE_MOVIES_SEARCH_RESULT_SUCCESS:
+ // return {
+ // ...state,
+ // searchResults: action.searchResults
+ // };
+ default:
+ return state;
+ }
+}
diff --git a/app/reducers/rootReducer.js b/app/reducers/rootReducer.js
new file mode 100644
index 00000000..f240a6a9
--- /dev/null
+++ b/app/reducers/rootReducer.js
@@ -0,0 +1,9 @@
+// import { combineReducers } from 'redux';
+import reducers from './reducers';
+
+// const rootReducer = combineReducers({
+// reducers
+// });
+
+// export default rootReducer;
+export default reducers;
diff --git a/app/views/login.js b/app/views/login.js
index cb2ed6d3..9d27f2df 100644
--- a/app/views/login.js
+++ b/app/views/login.js
@@ -1,8 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import { TextInput, StyleSheet } from 'react-native';
-import RocketChat from '../lib/rocketchat';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import * as actions from '../actions';
+import RocketChat from '../lib/rocketchat';
import KeyboardView from '../components/KeyboardView';
const styles = StyleSheet.create({
@@ -25,9 +28,15 @@ const styles = StyleSheet.create({
}
});
+@connect(state => ({
+ server: state.server
+}), dispatch => ({
+ actions: bindActionCreators(actions, dispatch)
+}))
export default class LoginView extends React.Component {
static propTypes = {
- navigator: PropTypes.object.isRequired
+ navigator: PropTypes.object.isRequired,
+ server: PropTypes.string.isRequired
}
static navigationOptions = () => ({
@@ -45,9 +54,11 @@ export default class LoginView extends React.Component {
this.props.navigator.setTitle({
title: 'Login'
});
+ }
+ componentWillReceiveProps(nextProps) {
this.props.navigator.setSubTitle({
- subtitle: RocketChat.currentServer
+ subtitle: nextProps.server
});
}
diff --git a/app/views/room.js b/app/views/room.js
index 66a7207b..d2c17c95 100644
--- a/app/views/room.js
+++ b/app/views/room.js
@@ -2,6 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Text, View, StyleSheet, Button } from 'react-native';
import { ListView } from 'realm/react-native';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import * as actions from '../actions';
import realm from '../lib/realm';
import RocketChat from '../lib/rocketchat';
import debounce from '../utils/throttle';
@@ -37,12 +41,19 @@ const styles = StyleSheet.create({
}
});
+
+@connect(state => ({
+ server: state.server
+}), dispatch => ({
+ actions: bindActionCreators(actions, dispatch)
+}))
export default class RoomView extends React.Component {
static propTypes = {
navigator: PropTypes.object.isRequired,
rid: PropTypes.string,
sid: PropTypes.string,
- name: PropTypes.string
+ name: PropTypes.string,
+ server: PropTypes.string
}
constructor(props) {
@@ -50,7 +61,7 @@ export default class RoomView extends React.Component {
this.rid = props.rid || realm.objectForPrimaryKey('subscriptions', props.sid).rid;
// this.rid = 'GENERAL';
- this.data = realm.objects('messages').filtered('_server.id = $0 AND rid = $1', RocketChat.currentServer, this.rid).sorted('ts', true);
+ 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)),
loaded: true,
diff --git a/app/views/roomsList.js b/app/views/roomsList.js
index d594a3b3..04b49b67 100644
--- a/app/views/roomsList.js
+++ b/app/views/roomsList.js
@@ -4,8 +4,12 @@ import { ListView } from 'realm/react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import React from 'react';
import PropTypes from 'prop-types';
-import { Button, Text, View, StyleSheet, TouchableOpacity, Platform, TextInput } from 'react-native';
+import { Text, View, StyleSheet, TouchableOpacity, Platform, TextInput } from 'react-native';
import Meteor from 'react-native-meteor';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+
+import * as actions from '../actions';
import realm from '../lib/realm';
import RocketChat from '../lib/rocketchat';
import RoomItem from '../components/RoomItem';
@@ -59,24 +63,17 @@ const styles = StyleSheet.create({
}
});
-let setInitialData;
-
-Meteor.getData().on('loggingIn', () => {
- setTimeout(() => {
- if (Meteor._isLoggingIn === false && Meteor.userId() == null) {
- console.log('loggingIn', Meteor.userId());
- Navigation.showModal({
- screen: 'Login',
- animationType: 'slide-up'
- });
- }
- }, 100);
-});
-
Meteor.Accounts.onLogin(() => {
console.log('onLogin');
});
+Meteor.Accounts.onLoginFailure(() => {
+ Navigation.showModal({
+ screen: 'Login',
+ animationType: 'slide-up'
+ });
+});
+
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
class RoomsListItem extends React.PureComponent {
static propTypes = {
@@ -99,30 +96,21 @@ class RoomsListItem extends React.PureComponent {
);
}
}
+
+@connect(state => ({
+ server: state.server
+}), dispatch => ({
+ actions: bindActionCreators(actions, dispatch)
+}))
export default class RoomsListView extends React.Component {
static propTypes = {
- navigator: PropTypes.object.isRequired
- }
-
- static navigationOptions = (props) => {
- const server = RocketChat.currentServer ? RocketChat.currentServer.replace(/^https?:\/\//, '') : '';
- const textAlign = Platform.OS === 'ios' ? 'center' : 'left';
- const marginLeft = Platform.OS === 'ios' ? 0 : 20;
- const position = Platform.OS === 'ios' ? 'headerLeft' : 'headerRight';
-
- return {
- headerTitle:
- Channels
- {server}
- ,
- title: 'Channels',
- [position]: