saga finished

This commit is contained in:
Guilherme Gazzo 2017-08-20 21:11:46 -03:00
parent 46b491cf32
commit 0a48fadf16
No known key found for this signature in database
GPG Key ID: 1F85C9AD922D0829
24 changed files with 531 additions and 138 deletions

View File

@ -10,9 +10,10 @@ function createRequestTypes(base, types = defaultTypes) {
} }
// Login events // Login events
export const LOGIN = createRequestTypes('LOGIN', [...defaultTypes, 'SET_TOKEN']); export const LOGIN = createRequestTypes('LOGIN', [...defaultTypes, 'SET_TOKEN', 'SUBMIT']);
export const ROOMS = createRequestTypes('ROOMS'); export const ROOMS = createRequestTypes('ROOMS');
export const MESSAGES = createRequestTypes('MESSAGES'); export const MESSAGES = createRequestTypes('MESSAGES');
export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']);
export const SERVER = createRequestTypes('SERVER', ['SELECT', 'CHANGED']); export const SERVER = createRequestTypes('SERVER', ['SELECT', 'CHANGED']);
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']); export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']);
export const LOGOUT = 'LOGOUT'; // logout is always success export const LOGOUT = 'LOGOUT'; // logout is always success

View File

@ -1,9 +1,15 @@
import * as types from './actionsTypes'; import * as types from './actionsTypes';
export function loginSubmit(credentials) {
return {
type: types.LOGIN.SUBMIT,
credentials
};
}
export function loginRequest(credentials) { export function loginRequest(credentials) {
return { return {
type: types.LOGIN.REQUEST, type: types.LOGIN.REQUEST,
...credentials credentials
}; };
} }
@ -22,10 +28,11 @@ export function loginFailure(err) {
}; };
} }
export function setToken(token) { export function setToken(user) {
return { return {
type: types.LOGIN.SET_TOKEN, type: types.LOGIN.SET_TOKEN,
token token: user.token,
user
}; };
} }

8
app/actions/navigator.js Normal file
View File

@ -0,0 +1,8 @@
import * as types from './actionsTypes';
export default function setNavigation(navigator = {}) {
return {
type: types.NAVIGATION.SET,
navigator
};
}

53
app/animations/fade.js Normal file
View File

@ -0,0 +1,53 @@
import React from 'react';
import { Animated, Text } from 'react-native';
export default class Fade extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: props.visible
};
}
componentWillMount() {
this._visibility = new Animated.Value(this.props.visible ? 1 : 0);
}
componentWillReceiveProps(nextProps) {
if (nextProps.visible) {
this.setState({ visible: true });
}
Animated.timing(this._visibility, {
toValue: nextProps.visible ? 1 : 0,
duration: 300
}).start(() => {
this.setState({ visible: nextProps.visible });
});
}
render() {
const { visible, style, children, ...rest } = this.props;
const containerStyle = {
opacity: this._visibility.interpolate({
inputRange: [0, 1],
outputRange: [0, 1]
}),
transform: [
{
scale: this._visibility.interpolate({
inputRange: [0, 1],
outputRange: [1.1, 1]
})
}
]
};
const combinedStyle = [containerStyle, style];
return (
<Animated.View style={this.state.visible ? combinedStyle : containerStyle} {...rest}>
<Text>{this.state.visible ? children : null}</Text>
</Animated.View>
);
}
}

View File

@ -5,7 +5,11 @@ import { connect } from 'react-redux';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
bannerContainer: { bannerContainer: {
backgroundColor: '#ddd' backgroundColor: '#ddd',
position: 'absolute',
top: '0%',
zIndex: 10,
width: '100%'
}, },
bannerText: { bannerText: {
textAlign: 'center', textAlign: 'center',

BIN
app/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

77
app/images/logo.svg Normal file
View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="435.721px" height="85.242px" viewBox="62.402 -18.766 435.721 85.242"
enable-background="new 62.402 -18.766 435.721 85.242" xml:space="preserve">
<g>
<path fill="#F4F4F4" d="M205.456,22.207c0,4.297-1.603,7.119-4.681,8.465l4.425,16.803c0.192,0.771-0.192,1.154-0.898,1.154h-6.67
c-0.641,0-0.961-0.32-1.09-0.898l-4.297-16.289h-4.425v16.162c0,0.642-0.384,1.025-1.026,1.025h-6.67
c-0.641,0-1.026-0.385-1.026-1.025V-1.651c0-0.641,0.385-1.026,1.026-1.026h16.097c6.028,0,9.235,3.207,9.235,9.235V22.207
L205.456,22.207z M194.169,22.976c1.667,0,2.565-0.898,2.565-2.565V8.354c0-1.667-0.898-2.564-2.565-2.564h-6.349v17.187
L194.169,22.976L194.169,22.976z"/>
<path fill="#F4F4F4" d="M210.583,6.558c0-6.028,3.206-9.235,9.235-9.235h7.183c6.028,0,9.235,3.207,9.235,9.235v32.836
c0,6.027-3.207,9.234-9.235,9.234h-7.183c-6.029,0-9.235-3.207-9.235-9.234V6.558z M225.397,40.355
c1.667,0,2.565-0.834,2.565-2.565V8.162c0-1.667-0.898-2.565-2.565-2.565h-3.719c-1.667,0-2.565,0.898-2.565,2.565v29.629
c0,1.73,0.898,2.564,2.565,2.564H225.397L225.397,40.355z"/>
<path fill="#F4F4F4" d="M268.362,13.484c0,0.642-0.385,1.026-1.025,1.026h-6.413c-0.706,0-1.026-0.384-1.026-1.026v-5.13
c0-1.667-0.897-2.564-2.564-2.564h-3.335c-1.731,0-2.565,0.897-2.565,2.564V37.6c0,1.731,0.897,2.563,2.565,2.563h3.335
c1.667,0,2.564-0.833,2.564-2.563v-5.132c0-0.642,0.32-1.026,1.026-1.026h6.413c0.643,0,1.025,0.384,1.025,1.026v6.927
c0,6.027-3.271,9.234-9.234,9.234h-7.183c-6.028,0-9.299-3.207-9.299-9.234V6.558c0-6.028,3.271-9.235,9.299-9.235h7.183
c5.964,0,9.234,3.207,9.234,9.235V13.484z"/>
<path fill="#F4F4F4" d="M295.422,48.629c-0.771,0-1.218-0.32-1.476-0.961l-8.079-19.048l-2.374,4.554v14.172
c0,0.834-0.448,1.283-1.282,1.283h-6.157c-0.834,0-1.283-0.449-1.283-1.283v-48.74c0-0.833,0.449-1.283,1.283-1.283h6.157
c0.833,0,1.282,0.449,1.282,1.283v19.881l9.876-20.202c0.321-0.641,0.771-0.962,1.476-0.962h6.733c0.962,0,1.347,0.642,0.897,1.539
l-10.901,22.382l11.606,25.91c0.449,0.834,0.064,1.475-0.961,1.475H295.422z"/>
<path fill="#F4F4F4" d="M333.45,4.763c0,0.641-0.257,1.09-1.026,1.09h-16.033v12.826h12.249c0.643,0,1.026,0.385,1.026,1.09v6.349
c0,0.706-0.385,1.091-1.026,1.091h-12.249v12.954h16.033c0.771,0,1.026,0.321,1.026,1.026v6.414c0,0.641-0.257,1.024-1.026,1.024
h-23.6c-0.578,0-0.963-0.385-0.963-1.024V-1.651c0-0.641,0.385-1.026,0.963-1.026h23.6c0.771,0,1.026,0.385,1.026,1.026V4.763z"/>
<path fill="#F4F4F4" d="M363.204-2.677c0.705,0,1.026,0.385,1.026,1.026v6.414c0,0.641-0.321,1.026-1.026,1.026h-7.439v41.814
c0,0.705-0.32,1.024-1.025,1.024h-6.67c-0.643,0-1.026-0.319-1.026-1.024V5.789h-7.438c-0.643,0-1.026-0.385-1.026-1.026v-6.414
c0-0.641,0.385-1.026,1.026-1.026H363.204z"/>
<path fill="#F4F4F4" d="M363.585,41.445c0-0.834,0.449-1.282,1.283-1.282h5.836c0.834,0,1.282,0.448,1.282,1.282v5.899
c0,0.835-0.448,1.283-1.282,1.283h-5.836c-0.834,0-1.283-0.448-1.283-1.283V41.445z"/>
<path fill="#F4F4F4" d="M404.114,13.484c0,0.642-0.386,1.026-1.026,1.026h-6.413c-0.705,0-1.025-0.384-1.025-1.026v-5.13
c0-1.667-0.897-2.564-2.564-2.564h-3.335c-1.732,0-2.565,0.897-2.565,2.564V37.6c0,1.731,0.897,2.563,2.565,2.563h3.335
c1.667,0,2.564-0.833,2.564-2.563v-5.132c0-0.642,0.32-1.026,1.025-1.026h6.413c0.643,0,1.026,0.384,1.026,1.026v6.927
c0,6.027-3.271,9.234-9.235,9.234h-7.183c-6.028,0-9.299-3.207-9.299-9.234V6.558c0-6.028,3.271-9.235,9.299-9.235h7.183
c5.965,0,9.235,3.207,9.235,9.235V13.484z"/>
<path fill="#F4F4F4" d="M427.455-1.651c0-0.641,0.384-1.026,1.025-1.026h6.605c0.77,0,1.089,0.385,1.089,1.026v49.254
c0,0.641-0.32,1.024-1.089,1.024h-6.605c-0.643,0-1.025-0.385-1.025-1.024V27.209h-8.209v20.395c0,0.642-0.385,1.025-1.026,1.025
h-6.604c-0.771,0-1.091-0.385-1.091-1.025V-1.651c0-0.641,0.32-1.026,1.091-1.026h6.604c0.643,0,1.026,0.385,1.026,1.026v20.394
h8.209V-1.651L427.455-1.651z"/>
<path fill="#F4F4F4" d="M465.419,48.629c-0.577,0-0.897-0.32-1.026-0.898l-1.795-9.362h-11.416l-1.73,9.362
c-0.129,0.578-0.449,0.898-1.026,0.898h-6.861c-0.705,0-1.026-0.385-0.835-1.09l10.646-49.318c0.129-0.641,0.513-0.898,1.09-0.898
h8.915c0.577,0,0.962,0.257,1.09,0.898l10.646,49.318c0.129,0.705-0.128,1.09-0.897,1.09H465.419z M456.889,8.546l-4.104,22.382
h8.209L456.889,8.546z"/>
<path fill="#F4F4F4" d="M497.097-2.677c0.705,0,1.026,0.385,1.026,1.026v6.414c0,0.641-0.321,1.026-1.026,1.026h-7.438v41.814
c0,0.705-0.321,1.024-1.026,1.024h-6.67c-0.641,0-1.025-0.319-1.025-1.024V5.789h-7.438c-0.642,0-1.025-0.385-1.025-1.026v-6.414
c0-0.641,0.385-1.026,1.025-1.026H497.097z"/>
</g>
<path fill="#C1272D" d="M162.586,23.788c0-5.031-1.505-9.854-4.474-14.339c-2.666-4.025-6.401-7.588-11.1-10.591
c-9.074-5.796-21-8.989-33.579-8.989c-4.202,0-8.344,0.355-12.361,1.059c-2.492-2.333-5.41-4.432-8.497-6.091
c-16.494-7.994-30.172-0.188-30.172-0.188S75.12-4.904,73.052,4.253c-5.689,5.644-8.773,12.45-8.773,19.535
c0,0.022,0.001,0.045,0.001,0.068c0,0.022-0.001,0.044-0.001,0.068c0,7.085,3.083,13.891,8.773,19.534
c2.068,9.158-10.649,19.605-10.649,19.605s13.678,7.805,30.172-0.188c3.087-1.659,6.004-3.759,8.497-6.091
c4.018,0.703,8.159,1.058,12.361,1.058c12.58,0,24.505-3.191,33.579-8.987c4.699-3.003,8.434-6.565,11.1-10.592
c2.969-4.484,4.474-9.309,4.474-14.338c0-0.023-0.001-0.045-0.001-0.068S162.586,23.81,162.586,23.788z"/>
<path fill="#FFFFFF" d="M113.433-3.018c23.293,0,42.177,12.062,42.177,26.941c0,14.878-18.884,26.941-42.177,26.941
c-5.187,0-10.154-0.6-14.743-1.693c-4.664,5.61-14.924,13.411-24.891,10.89c3.242-3.482,8.045-9.366,7.017-19.058
c-5.974-4.648-9.56-10.597-9.56-17.08C71.255,9.043,90.139-3.018,113.433-3.018"/>
<g>
<g>
<circle fill="#C1272D" cx="113.433" cy="24.79" r="5.603"/>
</g>
<g>
<circle fill="#C1272D" cx="132.913" cy="24.79" r="5.603"/>
</g>
<g>
<circle fill="#C1272D" cx="93.952" cy="24.79" r="5.602"/>
</g>
</g>
<g>
<path fill="#CCCCCC" d="M113.433,47.319c-5.187,0-10.154-0.52-14.743-1.468c-4.118,4.294-12.6,10.066-21.39,9.854
c-1.158,1.755-2.417,3.19-3.501,4.355c9.967,2.521,20.227-5.279,24.891-10.89c4.589,1.094,9.557,1.693,14.743,1.693
c23.106,0,41.87-11.871,42.169-26.585C155.303,37.032,136.539,47.319,113.433,47.319z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

80
app/index.js Normal file
View File

@ -0,0 +1,80 @@
import React from 'react';
import { connect } from 'react-redux';
import { Text } from 'react-native';
import setNavigator from './actions/navigator';
import LoginView from './views/login';
import ListServerView from './views/serverList';
import store from './lib/createStore';
//
// export const authenticated = (view) => {
// if (!store.getState().login.authenticated) {
// return store.getState().navigator.resetTo({
// screen: 'Login'
// });
// }
// return view;
// };
export const authenticated = WrappedComponent => class _p extends React.PureComponent {
constructor() {
super();
this.login = store.getState().login;
console.log('this.login.token', this.login.token);
if (!this.login.token || this.login.failure) {
return store.getState().navigator.resetTo({
screen: 'Login'
});
}
}
render() {
// Wraps the input component in a container, without mutating it. Good!
return <WrappedComponent {...this.props} />;
}
};
//
export class PublicScreen extends React.PureComponent {
// componentWillMount() {
// this.props.setNavigator(this.props.navigator);
// if (this.props.currentServer) {
// return this.props.navigator.navigate('private');
// }
// }
render() {
return !this.login.isAuthenticated || !this.login.user ? null : (<ListServerView {...this.props} />);
}
}
@connect(null, dispatch => ({
setNavigator: navigator => dispatch(setNavigator(navigator))
}))
export class PrivateScreen extends React.PureComponent {
componentWillMount() {
// this.props.setNavigator(this.props.navigator);
}
render() {
// if (this.props.logged) {
// return (<Text>oi</Text>);
// }
return (<LoginView {...this.props} />);
}
}
@connect(state => ({
// logged: state.login.isAuthenticated
}), dispatch => ({
// navigate: routeName => dispatch(NavigationActions.navigate({ routeName })),
setNavigator: navigator => dispatch(setNavigator(navigator))
}))
export const HomeScreen = class extends React.PureComponent {
componentWillMount() {
this.props.setNavigator(this.props.navigator);
this.props.navigator.resetTo({
screen: 'public'
});
}
render() {
return (<Text>oieee</Text>);
}
};

View File

@ -13,7 +13,7 @@ let middleware;
if (__DEV__) { if (__DEV__) {
/* eslint-disable global-require */ /* eslint-disable global-require */
const reduxImmutableStateInvariant = require('redux-immutable-state-invariant').default(); const reduxImmutableStateInvariant = require('redux-immutable-state-invariant').default();
middleware = [sagaMiddleware, reduxImmutableStateInvariant, logger]; middleware = [sagaMiddleware, reduxImmutableStateInvariant];
} else { } else {
middleware = [sagaMiddleware]; middleware = [sagaMiddleware];
} }

View File

@ -1,4 +1,5 @@
import Realm from 'realm'; import Realm from 'realm';
import { AsyncStorage } from 'react-native';
const serversSchema = { const serversSchema = {
name: 'servers', name: 'servers',
@ -153,9 +154,9 @@ const messagesSchema = {
// ] // ]
// } // }
}; };
//
// Realm.clearTestState(); // Realm.clearTestState();
// AsyncStorage.clear();
const realm = new Realm({ const realm = new Realm({
schema: [settingsSchema, serversSchema, subscriptionSchema, messagesSchema, usersSchema, attachment] schema: [settingsSchema, serversSchema, subscriptionSchema, messagesSchema, usersSchema, attachment]
}); });

View File

@ -51,9 +51,9 @@ const RocketChat = {
reduxStore.dispatch(connectSuccess()); reduxStore.dispatch(connectSuccess());
resolve(); resolve();
}); });
Meteor.ddp.on('loggin', () => { // Meteor.ddp.on('loggin', () => {
reduxStore.dispatch(loginSuccess({})); // reduxStore.dispatch(loginSuccess({}));
}); // });
Meteor.ddp.on('connected', () => { Meteor.ddp.on('connected', () => {
Meteor.call('public-settings/get', (err, data) => { Meteor.call('public-settings/get', (err, data) => {
if (err) { if (err) {
@ -105,11 +105,13 @@ const RocketChat = {
}, },
login(params, callback) { login(params, callback) {
console.log('login(params, callback)');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Meteor._startLoggingIn(); Meteor._startLoggingIn();
return Meteor.call('login', params, (err, result) => { return Meteor.call('login', params, (err, result) => {
Meteor._endLoggingIn(); Meteor._endLoggingIn();
Meteor._handleLoginCallback(err, result); Meteor._handleLoginCallback(err, result);
console.log('login(params, callback)asdas', err, result);
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
@ -224,6 +226,7 @@ const RocketChat = {
getMessage(rid, msg = {}) { getMessage(rid, msg = {}) {
const _id = Random.id(); const _id = Random.id();
// console.log('reduxStore.getState().login.id ', reduxStore.getState().login);
const message = { const message = {
_id, _id,
rid, rid,
@ -233,8 +236,8 @@ const RocketChat = {
temp: true, temp: true,
_server: { id: reduxStore.getState().server }, _server: { id: reduxStore.getState().server },
u: { u: {
_id: reduxStore.getState()._id, _id: reduxStore.getState().login.user.id || '1',
username: reduxStore.getState()._id username: reduxStore.getState().login.user.id
} }
}; };
@ -367,14 +370,15 @@ const RocketChat = {
getRooms() { getRooms() {
// Meteor.Accounts.onLogin(() => { // Meteor.Accounts.onLogin(() => {
return Promise.all([call('subscriptions/get'), call('rooms/get')]).then(([subscriptions, rooms]) => { return Promise.all([call('subscriptions/get'), call('rooms/get')]).then(([subscriptions, rooms]) => {
// console.log('getRooms resolved', reduxStore.getState().server, subscriptions);
subscriptions = subscriptions.sort((s1, s2) => (s1.rid > s2.rid ? 1 : -1)); subscriptions = subscriptions.sort((s1, s2) => (s1.rid > s2.rid ? 1 : -1));
rooms = rooms.sort((s1, s2) => (s1._id > s2._id ? 1 : -1)); rooms = rooms.sort((s1, s2) => (s1._id > s2._id ? 1 : -1));
const data = subscriptions.map((subscription, index) => { const data = subscriptions.map((subscription, index) => {
subscription._updatedAt = rooms[index]._updatedAt; subscription._updatedAt = rooms[index]._updatedAt;
return subscription; return subscription;
}); });
Meteor.subscribe('stream-notify-user', `${ Meteor.userId() }/subscriptions-changed`, false);
// Meteor.subscribe('stream-notify-user', `${ Meteor.userId() }/rooms-changed`, false); // Meteor.subscribe('stream-notify-user', `${ Meteor.userId() }/rooms-changed`, false);
console.log('getRooms resolved', reduxStore.getState().server, data);
realm.write(() => { realm.write(() => {
data.forEach((subscription) => { data.forEach((subscription) => {
// const subscription = { // const subscription = {
@ -388,6 +392,7 @@ const RocketChat = {
realm.create('subscriptions', subscription, true); realm.create('subscriptions', subscription, true);
}); });
}); });
Meteor.subscribe('stream-notify-user', `${ reduxStore.getState().user.id }/subscriptions-changed`, false);
return data; return data;
}).then(data => data); }).then(data => data);
// }); // });

View File

@ -9,8 +9,13 @@ import RoomView from './views/room';
import PhotoView from './views/Photo'; import PhotoView from './views/Photo';
import CreateChannel from './views/CreateChannel'; import CreateChannel from './views/CreateChannel';
import store from './lib/createStore'; import store from './lib/createStore';
import { PrivateScreen, HomeScreen, authenticated } from './index';
Navigation.registerComponent('Rooms', () => RoomsListView, store, Provider); // console.log('fisateile/', PublicRoute(PublicScreen));
Navigation.registerComponent('home', () => HomeScreen, store, Provider);
Navigation.registerComponent('private', () => PrivateScreen, store, Provider);
Navigation.registerComponent('public', () => ListServerView, store, Provider);
Navigation.registerComponent('Rooms', () => authenticated(RoomsListView), store, Provider);
Navigation.registerComponent('Room', () => RoomView, store, Provider); Navigation.registerComponent('Room', () => RoomView, store, Provider);
Navigation.registerComponent('Photo', () => PhotoView, store, Provider); Navigation.registerComponent('Photo', () => PhotoView, store, Provider);
Navigation.registerComponent('ListServer', () => ListServerView, store, Provider); Navigation.registerComponent('ListServer', () => ListServerView, store, Provider);
@ -20,8 +25,8 @@ Navigation.registerComponent('CreateChannel', () => CreateChannel, store, Provid
Navigation.startSingleScreenApp({ Navigation.startSingleScreenApp({
screen: { screen: {
screen: 'ListServer', screen: 'home',
title: 'ListServer' title: 'private'
}, },
animationType: 'none' animationType: 'slide-up'
}); });

View File

@ -1,11 +1,12 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import * as reducers from './reducers'; import settings from './reducers';
import login from './login'; import login from './login';
import meteor from './connect'; import meteor from './connect';
import messages from './messages'; import messages from './messages';
import server from './server'; import server from './server';
import navigator from './navigator';
export default combineReducers({ export default combineReducers({
...reducers, login, meteor, messages, server settings, login, meteor, messages, server, navigator
}); });

View File

@ -11,16 +11,18 @@ const initialState = {
export default function login(state = initialState, action) { export default function login(state = initialState, action) {
switch (action.type) { switch (action.type) {
case types.LOGIN.REQUEST: case types.LOGIN.REQUEST:
console.log('types.LOGIN.REQUEST', action);
return { ...state, return { ...state,
isFetching: true, isFetching: true,
isAuthenticated: false isAuthenticated: false,
failure: false
}; };
case types.LOGIN.SUCCESS: case types.LOGIN.SUCCESS:
return { ...state, return { ...state,
isFetching: false, isFetching: false,
isAuthenticated: true, isAuthenticated: true,
user: action.user, user: action.user,
// token: action.token, token: action.user.token,
failure: false failure: false
// user: action.user // user: action.user
}; };
@ -32,13 +34,11 @@ export default function login(state = initialState, action) {
errorMessage: action.err errorMessage: action.err
}; };
case types.LOGOUT: case types.LOGOUT:
return { ...state, return initialState;
isFetching: false,
isAuthenticated: false
};
case types.LOGIN.SET_TOKEN: case types.LOGIN.SET_TOKEN:
return { ...state, return { ...state,
token: action.token token: action.token,
user: action.user
}; };
default: default:
return state; return state;

13
app/reducers/navigator.js Normal file
View File

@ -0,0 +1,13 @@
import * as types from '../actions/actionsTypes';
const initialState = {};
export default function navigations(state = initialState, action) {
switch (action.type) {
case types.NAVIGATION.SET:
return action.navigator
;
default:
return state;
}
}

View File

@ -1,55 +1,102 @@
import { AsyncStorage } from 'react-native'; import { AsyncStorage } from 'react-native';
import { take, put, call, takeEvery, fork, select, all } from 'redux-saga/effects'; import { take, put, call, takeEvery, fork, select, all, race } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes'; import * as types from '../actions/actionsTypes';
import { loginRequest, loginSuccess, loginFailure, setToken } from '../actions/login'; import { loginRequest, loginSuccess, loginFailure, setToken } from '../actions/login';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
const TOKEN_KEY = 'reactnativemeteor_usertoken'; const TOKEN_KEY = 'reactnativemeteor_usertoken';
const getUser = state => state.login.user; const getUser = state => state.login;
const getServer = state => state.server; const getServer = state => state.server;
const loginCall = args => (args.resume ? RocketChat.login(args) : RocketChat.loginWithPassword(args)); const loginCall = args => (args.resume ? RocketChat.login(args) : RocketChat.loginWithPassword(args));
const getToken = function* getToken() { const getToken = function* getToken() {
const currentServer = yield select(getServer); const currentServer = yield select(getServer);
console.log('currentServer', currentServer); const user = yield call([AsyncStorage, 'getItem'], `${ TOKEN_KEY }-${ currentServer }`);
const token = yield call([AsyncStorage, 'getItem'], `${ TOKEN_KEY }-${ currentServer }`); if (user) {
console.log('currentServer TOKEN', token); try {
if (token) { yield put(setToken(token)); } yield put(setToken(JSON.parse(user)));
// yield call([AsyncStorage, 'setItem'], TOKEN_KEY, token || ''); yield call([AsyncStorage, 'setItem'], TOKEN_KEY, JSON.parse(user).token || '');
return token; return JSON.parse(user);
}; } catch (e) {
console.log('getTokenerr', e);
const sagaLogin = function* sagaLogin(payload) { }
try {
const response = yield call(loginCall, payload);
yield put(loginSuccess(response));
} catch (err) {
yield put(loginFailure(err));
} }
}; };
const watchLoginRequest = function* watchLoginRequest() {
do { const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() {
try { // do {
yield all([take(types.METEOR.SUCCESS), take(types.SERVER.CHANGED)]); try {
const token = yield call(getToken); yield take(types.METEOR.SUCCESS);
if (token) { const { navigator } = yield select(state => state);
yield put(loginRequest({ resume: token })); navigator.resetTo({
} screen: 'Rooms'
} catch (e) { });
console.log(e); const user = yield select(getUser);
if (user.token) {
yield put(loginRequest({ resume: user.token }));
// console.log('AEEEEEEEEOOOOO');
// // wait for a response
// const { error } = yield race({
// success: take(types.LOGIN.SUCCESS),
// error: take(types.LOGIN.FAILURE)
// });
// console.log('AEEEEEEEEOOOOO', error);
// if (!error) {
// navigator.resetTo({
// screen: 'Rooms'
// });
// }
} }
} while (true); } catch (e) {
console.log(e);
}
// } while (true);
}; };
const saveToken = function* saveToken() { const saveToken = function* saveToken() {
const [server, user] = yield all([select(getServer), select(getUser)]); const [server, user] = yield all([select(getServer), select(getUser)]);
yield AsyncStorage.setItem(TOKEN_KEY, user.token); yield AsyncStorage.setItem(TOKEN_KEY, user.token);
yield AsyncStorage.setItem(`${ TOKEN_KEY }-${ server }`, user.token); yield AsyncStorage.setItem(`${ TOKEN_KEY }-${ server }`, JSON.stringify(user));
};
const handleLoginRequest = function* handleLoginRequest() {
while (true) {
const { credentials } = yield take(types.LOGIN.REQUEST);
try {
const response = yield call(loginCall, credentials);
yield put(loginSuccess(response));
} catch (err) {
// console.log('login failed');
yield put(loginFailure(err));
}
}
};
const handleLoginSubmit = function* handleLoginSubmit() {
while (true) {
const { credentials } = yield take(types.LOGIN.SUBMIT);
// put a login request
yield put(loginRequest(credentials));
// wait for a response
const { error } = yield race({
success: take(types.LOGIN.SUCCESS),
error: take(types.LOGIN.FAILURE)
});
if (!error) {
const { navigator } = yield select(state => state);
navigator.resetTo({
screen: 'Rooms'
});
}
}
}; };
const root = function* root() { const root = function* root() {
yield fork(watchLoginRequest); yield takeEvery(types.SERVER.CHANGED, getToken);
yield takeEvery(types.LOGIN.REQUEST, sagaLogin); yield takeEvery(types.SERVER.CHANGED, handleLoginWhenServerChanges);
yield fork(handleLoginRequest);
yield takeEvery(types.LOGIN.SUCCESS, saveToken); yield takeEvery(types.LOGIN.SUCCESS, saveToken);
yield fork(handleLoginSubmit);
}; };
export default root; export default root;

View File

@ -5,6 +5,7 @@ import RocketChat from '../lib/rocketchat';
const get = function* get({ rid }) { const get = function* get({ rid }) {
const auth = yield select(state => state.login.isAuthenticated); const auth = yield select(state => state.login.isAuthenticated);
console.log('hey now', yield select(state => state.login));
if (!auth) { if (!auth) {
yield take(LOGIN.SUCCESS); yield take(LOGIN.SUCCESS);
} }
@ -19,9 +20,4 @@ const get = function* get({ rid }) {
const getData = function* getData() { const getData = function* getData() {
yield takeLatest(MESSAGES.REQUEST, get); yield takeLatest(MESSAGES.REQUEST, get);
}; };
export default getData;
const getMessages = function* getMessages() {
yield takeEvery(LOGIN.SUCCESS, getData);
};
export default getMessages;

View File

@ -9,6 +9,7 @@ const getRooms = function* getRooms() {
const watchRoomsRequest = function* watchRoomsRequest() { const watchRoomsRequest = function* watchRoomsRequest() {
try { try {
console.log('getRooms');
yield call(getRooms); yield call(getRooms);
yield put(roomsSuccess()); yield put(roomsSuccess());
} catch (err) { } catch (err) {

View File

@ -5,15 +5,9 @@ import { connectRequest, disconnect } from '../actions/connect';
import { changedServer } from '../actions/server'; import { changedServer } from '../actions/server';
const selectServer = function* selectServer(server) { const selectServer = function* selectServer(server) {
try { yield put(disconnect());
yield put(disconnect()); yield put(changedServer(server));
yield put(changedServer(server)); yield put(connectRequest(server));
yield (server && put(connectRequest(server)));
// console.log(Actions.login());
// Actions.replace('login', {});
} catch (e) {
console.log(e);
}
}; };
const root = function* root() { const root = function* root() {
yield takeEvery(SERVER.SELECT, selectServer); yield takeEvery(SERVER.SELECT, selectServer);

View File

@ -1,41 +1,78 @@
import React from 'react'; import React from 'react';
import Spinner from 'react-native-loading-spinner-overlay';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Text, TextInput, StyleSheet } from 'react-native'; import { Keyboard, Text, TextInput, StyleSheet, View, Image, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
// import * as actions from '../actions'; // import * as actions from '../actions';
import * as loginActions from '../actions/login'; import * as loginActions from '../actions/login';
import KeyboardView from '../components/KeyboardView'; import KeyboardView from '../components/KeyboardView';
// import { Keyboard } from 'react-native'
const styles = StyleSheet.create({ const styles = StyleSheet.create({
view: { view: {
flex: 1, flex: 1,
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'center', justifyContent: 'center',
padding: 20,
alignItems: 'stretch', alignItems: 'stretch',
backgroundColor: '#fff' backgroundColor: '#2f343d'
},
logoContainer: {
flex: 1,
alignItems: 'center',
flexGrow: 1,
justifyContent: 'center'
},
logo: {
width: 150,
// backgroundColor: 'red'
// height: 150,
resizeMode: 'contain'
},
formContainer: {
// marginBottom: 20
}, },
input: { input: {
height: 40, height: 40,
borderColor: '#aaa', marginBottom: 20,
marginLeft: 20, borderRadius: 2,
marginRight: 20, paddingHorizontal: 10,
marginTop: 10,
padding: 5,
borderWidth: 0, borderWidth: 0,
backgroundColor: '#f6f6f6' backgroundColor: 'rgba(255,255,255,.2)',
color: 'white'
},
buttonContainer: {
paddingVertical: 15,
backgroundColor: '#414852',
marginBottom: 20
},
button: {
textAlign: 'center',
color: 'white',
borderRadius: 2,
fontWeight: '700'
}, },
error: { error: {
textAlign: 'center', textAlign: 'center',
color: 'red', color: 'red',
paddingTop: 5 paddingTop: 5
},
loading: {
flex: 1,
position: 'absolute',
backgroundColor: 'rgba(255,255,255,.2)',
left: 0,
top: 0
} }
}); });
class LoginView extends React.Component { class LoginView extends React.Component {
static propTypes = { static propTypes = {
navigator: PropTypes.object.isRequired, navigator: PropTypes.object.isRequired,
loginRequest: PropTypes.func.isRequired, loginSubmit: PropTypes.func.isRequired,
server: PropTypes.string.isRequired, server: PropTypes.string.isRequired,
Accounts_EmailOrUsernamePlaceholder: PropTypes.string, Accounts_EmailOrUsernamePlaceholder: PropTypes.string,
Accounts_PasswordPlaceholder: PropTypes.string Accounts_PasswordPlaceholder: PropTypes.string
@ -65,9 +102,8 @@ class LoginView extends React.Component {
} }
submit = () => { submit = () => {
const { username, password, code } = this.state; const { username, password, code } = this.state;
console.log({ username, password, code }); this.props.loginSubmit({ username, password, code });
this.props.loginRequest({ username, password, code }); Keyboard.dismiss();
this.props.navigator.dismissModal();
} }
renderTOTP = () => { renderTOTP = () => {
@ -92,29 +128,45 @@ class LoginView extends React.Component {
render() { render() {
return ( return (
<KeyboardView style={styles.view} keyboardVerticalOffset={64}> <KeyboardView style={styles.view} keyboardVerticalOffset={64}>
<TextInput <View style={styles.logoContainer}>
style={styles.input} <Image style={styles.logo} source={require('../images/logo.png')} />
onChangeText={username => this.setState({ username })} </View>
keyboardType='email-address' <View style={styles.formContainer}>
autoCorrect={false} <TextInput
returnKeyType='done' placeholderTextColor={'rgba(255,255,255,.2)'}
autoCapitalize='none' style={styles.input}
autoFocus onChangeText={username => this.setState({ username })}
onSubmitEditing={this.submit} keyboardType='email-address'
placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || 'Email or username'} autoCorrect={false}
/> returnKeyType='done'
<TextInput autoCapitalize='none'
style={styles.input} autoFocus
onChangeText={password => this.setState({ password })}
secureTextEntry underlineColorAndroid='transparent'
autoCorrect={false} onSubmitEditing={this.submit}
returnKeyType='done' placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || 'Email or username'}
autoCapitalize='none' />
onSubmitEditing={this.submit} <TextInput
placeholder={this.props.Accounts_PasswordPlaceholder || 'Password'} placeholderTextColor={'rgba(255,255,255,.2)'}
/> style={styles.input}
{this.renderTOTP()} onChangeText={password => this.setState({ password })}
<Text style={styles.error}>{this.state.error}</Text> secureTextEntry
autoCorrect={false}
returnKeyType='done'
autoCapitalize='none'
underlineColorAndroid='transparent'
onSubmitEditing={this.submit}
placeholder={this.props.Accounts_PasswordPlaceholder || 'Password'}
/>
{this.renderTOTP()}
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.button} onPress={this.submit}>LOGIN</Text>
</TouchableOpacity>
{this.props.login.error && <Text style={styles.error}>{this.props.login.error}</Text>}
</View>
<Spinner visible={this.props.login.isFetching} textContent={'Loading...'} textStyle={{ color: '#FFF' }} />
</KeyboardView> </KeyboardView>
); );
} }

View File

@ -1,9 +1,9 @@
import ActionButton from 'react-native-action-button'; import ActionButton from 'react-native-action-button';
// import { Navigation } from 'react-native-navigation'; import { Navigation } from 'react-native-navigation';
import { ListView } from 'realm/react-native'; import { ListView } from 'realm/react-native';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { View, StyleSheet, TextInput } from 'react-native'; import { View, StyleSheet, TextInput, Platform } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import * as actions from '../actions'; import * as actions from '../actions';
import * as server from '../actions/connect'; import * as server from '../actions/connect';
@ -67,7 +67,7 @@ const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
export default class RoomsListView extends React.Component { export default class RoomsListView extends React.Component {
static propTypes = { static propTypes = {
// navigator: PropTypes.object.isRequired, navigator: PropTypes.object.isRequired,
server: PropTypes.string server: PropTypes.string
} }
@ -81,8 +81,29 @@ export default class RoomsListView extends React.Component {
searchText: '', searchText: '',
login: false login: false
}; };
this.data.addListener(this.updateState);
this.props.navigator.setOnNavigatorEvent(event => event.type === 'NavBarButtonPress' && event.id === 'servers' &&
Navigation.showModal({
screen: 'ListServer',
passProps: {},
navigatorStyle: {},
navigatorButtons: {},
animationType: 'slide-up'
}));
this.props.navigator.setSubTitle({
subtitle: this.props.server
});
}
componentWillMount() {
const button = Platform.OS === 'ios' ? 'leftButtons' : 'rightButtons';
this.props.navigator.setButtons({
[button]: [{
id: 'servers',
title: 'Servers'
}],
animated: true
});
} }
componentWillUnmount() { componentWillUnmount() {
this.data.removeListener(this.updateState); this.data.removeListener(this.updateState);
} }
@ -148,16 +169,16 @@ export default class RoomsListView extends React.Component {
updateState = () => { updateState = () => {
this.setState({ this.setState({
dataSource: ds.cloneWithRows(this.state.data) dataSource: ds.cloneWithRows(this.data)
}); });
}; };
_onPressItem = (id, item = {}) => { _onPressItem = (id, item = {}) => {
const navigateToRoom = (room) => { const navigateToRoom = (room) => {
// this.props.navigator.push({ this.props.navigator.push({
// screen: 'Room', screen: 'Room',
// passProps: room passProps: room
// }); });
}; };
const clearSearch = () => { const clearSearch = () => {
@ -218,6 +239,7 @@ export default class RoomsListView extends React.Component {
<RoomItem <RoomItem
id={item._id} id={item._id}
item={item} item={item}
baseUrl={this.props.Site_Url}
onPress={() => this._onPressItem(item._id, item)} onPress={() => this._onPressItem(item._id, item)}
/> />
) )
@ -234,15 +256,10 @@ export default class RoomsListView extends React.Component {
) )
renderCreateButtons = () => ( renderCreateButtons = () => (
<ActionButton buttonColor='rgba(231,76,60,1)' />); <ActionButton buttonColor='rgba(231,76,60,1)' />);
render= () => { render= () => (
if (this.props.canShowList) { <View style={styles.container}>
return ( <Banner />
<View style={styles.container}> {this.renderList()}
<Banner /> {this.renderCreateButtons()}
{this.renderList()} </View>)
{this.renderCreateButtons()}
</View>);
}
return null;
}
} }

View File

@ -1,4 +1,6 @@
import React from 'react'; import React from 'react';
import Icon from 'react-native-vector-icons/Ionicons';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Navigation } from 'react-native-navigation'; import { Navigation } from 'react-native-navigation';
import Zeroconf from 'react-native-zeroconf'; import Zeroconf from 'react-native-zeroconf';
@ -6,6 +8,8 @@ import { View, Text, SectionList, Platform, StyleSheet } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { setServer } from '../actions/server'; import { setServer } from '../actions/server';
import realm from '../lib/realm'; import realm from '../lib/realm';
import Fade from '../animations/fade';
import Banner from '../components/banner';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
view: { view: {
@ -27,11 +31,6 @@ const styles = StyleSheet.create({
textAlign: 'center', textAlign: 'center',
color: '#888' color: '#888'
}, },
listItem: {
lineHeight: 18,
color: '#666',
padding: 14
},
container: { container: {
flex: 1 flex: 1
}, },
@ -44,6 +43,21 @@ const styles = StyleSheet.create({
lineHeight: 24, lineHeight: 24,
paddingLeft: 14, paddingLeft: 14,
color: '#888' color: '#888'
},
serverItem: {
flex: 1,
flexDirection: 'row',
// justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
padding: 14
},
listItem: {
color: '#666', flexGrow: 1, lineHeight: 30
},
serverChecked: {
flexGrow: 0
} }
}); });
@ -166,12 +180,16 @@ export default class ListServerView extends React.Component {
} }
renderItem = ({ item }) => ( renderItem = ({ item }) => (
<Text
style={[styles.listItem, this.props.server === item.id ? { backgroundColor: 'red' } : {}]} <View style={styles.serverItem}>
onPress={() => { this.onPressItem(item); }} <Text
> style={[styles.listItem]}
{item.id} onPress={() => { this.onPressItem(item); }}
</Text> >
{item.id}
</Text>
<Fade visible={this.props.server === item.id}><Icon iconSize={24} size={24} style={styles.serverChecked} name='ios-checkmark-circle-outline' /> </Fade>
</View>
); );
renderSectionHeader = ({ section }) => ( renderSectionHeader = ({ section }) => (
@ -185,6 +203,7 @@ export default class ListServerView extends React.Component {
render() { render() {
return ( return (
<View style={styles.view}> <View style={styles.view}>
<Banner />
<SectionList <SectionList
style={styles.list} style={styles.list}
sections={this.state.sections} sections={this.state.sections}

10
package-lock.json generated
View File

@ -6035,6 +6035,16 @@
} }
} }
}, },
"react-native-loader": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/react-native-loader/-/react-native-loader-1.1.0.tgz",
"integrity": "sha1-nofojVsKA6JrKFkbI9pzQIoYnkc="
},
"react-native-loading-spinner-overlay": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/react-native-loading-spinner-overlay/-/react-native-loading-spinner-overlay-0.5.1.tgz",
"integrity": "sha512-LOgWzd1AJ4SYeqoomjYcHA0A/ngtBR49gt23GKzHKtiO4I1MFcccJXek774K/xhlIA4qfcwx9ufj5f2HuyBbEw=="
},
"react-native-meteor": { "react-native-meteor": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/react-native-meteor/-/react-native-meteor-1.1.0.tgz", "resolved": "https://registry.npmjs.org/react-native-meteor/-/react-native-meteor-1.1.0.tgz",

View File

@ -29,6 +29,8 @@
"react-native-form-generator": "^0.9.9", "react-native-form-generator": "^0.9.9",
"react-native-image-picker": "^0.26.3", "react-native-image-picker": "^0.26.3",
"react-native-img-cache": "^1.4.0", "react-native-img-cache": "^1.4.0",
"react-native-loader": "^1.1.0",
"react-native-loading-spinner-overlay": "^0.5.1",
"react-native-meteor": "^1.1.0", "react-native-meteor": "^1.1.0",
"react-native-navigation": "^1.1.193", "react-native-navigation": "^1.1.193",
"react-native-optimized-flatlist": "^1.0.1", "react-native-optimized-flatlist": "^1.0.1",