get message saga

This commit is contained in:
Guilherme Gazzo 2017-08-17 03:28:41 -03:00
parent b02ff73285
commit 566a22c389
No known key found for this signature in database
GPG Key ID: 1F85C9AD922D0829
24 changed files with 397 additions and 119 deletions

View File

@ -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

22
app/actions/messages.js Normal file
View File

@ -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
};
}

View File

@ -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

41
app/components/banner.js Normal file
View File

@ -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;
}
}

View File

@ -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);

View File

@ -189,13 +189,13 @@ const RocketChat = {
},
loadMessagesForRoom(rid, end, cb) {
return new Promise((resolve, reject) => {
Meteor.call('loadHistory', rid, end, 20, (err, data) => {
if (err) {
console.error(err);
if (cb) {
cb({ end: true });
}
return;
return reject(err);
}
if (data.messages.length) {
realm.write(() => {
@ -215,9 +215,10 @@ const RocketChat = {
cb({ end: false });
}
}
});
resolve();
Meteor.subscribe('stream-room-messages', rid, false);
});
});
},
getMessage(rid, msg = {}) {

12
app/reducers/index.js Normal file
View File

@ -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
});

29
app/reducers/messages.js Normal file
View File

@ -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;
}
}

View File

@ -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
};
}

View File

@ -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,

View File

@ -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;

39
app/routes/index.js Normal file
View File

@ -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' />

View File

@ -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;

View File

@ -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;

12
app/sagas/logger.js Normal file
View File

@ -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;

View File

@ -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;

27
app/sagas/messages.js Normal file
View File

@ -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;

View File

@ -25,7 +25,4 @@ const watchRoomsRequest = function* watchRoomsRequest() {
}
};
const root = function* root() {
yield fork(watchRoomsRequest);
};
export default root;
export default watchRoomsRequest;

View File

@ -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
};
}

View File

@ -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.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();
// });
// this.updateState();
}
componentDidMount() {
@ -141,15 +145,13 @@ export default class RoomView extends React.Component {
});
};
renderBanner = () => {
if (this.state.loaded === false) {
return (
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()}

View File

@ -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>

View File

@ -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);

133
package-lock.json generated
View File

@ -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",

View File

@ -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",