From 37d34b0b1f5026bf63d5917c994d573660c35da3 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 12 Aug 2017 22:35:09 -0300 Subject: [PATCH] Init Redux usage --- .babelrc | 3 +- .eslintrc | 4 +- app/actions/index.js | 131 +++++++++++++++++++++++++++++++++++ app/constants/types.js | 10 +++ app/lib/createStore.js | 22 ++++++ app/lib/realm.js | 13 ++-- app/lib/rocketchat.js | 16 ++++- app/navigation.js | 16 +++-- app/reducers/initialState.js | 6 ++ app/reducers/reducers.js | 52 ++++++++++++++ app/reducers/rootReducer.js | 9 +++ app/views/login.js | 17 ++++- app/views/room.js | 15 +++- app/views/roomsList.js | 114 +++++++++++++++--------------- app/views/serverList.js | 56 ++++++++++----- app/views/serverNew.js | 4 +- jsconfig.json | 6 +- package.json | 6 ++ 18 files changed, 397 insertions(+), 103 deletions(-) create mode 100644 app/actions/index.js create mode 100644 app/constants/types.js create mode 100644 app/lib/createStore.js create mode 100644 app/reducers/initialState.js create mode 100644 app/reducers/reducers.js create mode 100644 app/reducers/rootReducer.js diff --git a/.babelrc b/.babelrc index a9ce1369e..d0cf03dfa 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 11327145f..0c700ae4e 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 000000000..f7c6888f5 --- /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 000000000..301c702dc --- /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 000000000..6571eab30 --- /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 f7ad194b1..64c057fec 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 7a8c0fa36..099929941 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 67ae8149d..a9c696a5c 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 000000000..94083a9a7 --- /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 000000000..f70ea40d2 --- /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 000000000..f240a6a91 --- /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 cb2ed6d3a..9d27f2df2 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 66a7207b4..d2c17c954 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 d594a3b37..04b49b673 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]: