From ff75ed657877457992bd2c837d18a4e63538ea8b Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 30 Aug 2017 00:03:35 -0300 Subject: [PATCH] add server saga --- app/actions/actionsTypes.js | 6 +- app/actions/createChannel.js | 29 +++++++ app/actions/index.js | 6 ++ app/actions/login.js | 2 +- app/actions/server.js | 31 ++++++++ app/components/Message.js | 44 +---------- app/components/RoomItem.js | 9 +-- app/components/avatar.js | 79 ++++++++++--------- app/components/tags.js | 8 +- app/index.js | 35 ++++++--- app/lib/rocketchat.js | 33 ++++---- app/reducers/server.js | 31 +++++++- app/sagas/connect.js | 15 ++-- app/sagas/init.js | 13 +++- app/sagas/login.js | 22 ++++-- app/sagas/selectServer.js | 1 - app/utils/avatarInitialsAndColor.js | 6 ++ app/views/login.js | 2 +- app/views/room.js | 2 +- app/views/roomsList.js | 4 +- app/views/serverList.js | 2 +- app/views/serverNew.js | 114 +++++----------------------- package.json | 1 + storybook/stories/Avatar.js | 13 ++++ storybook/stories/index.js | 3 + 25 files changed, 266 insertions(+), 245 deletions(-) create mode 100644 storybook/stories/Avatar.js diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index 1ce10d23b..0aca14405 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -12,11 +12,11 @@ function createRequestTypes(base, types = defaultTypes) { // Login events export const LOGIN = createRequestTypes('LOGIN', [...defaultTypes, 'SET_TOKEN', 'SUBMIT']); export const ROOMS = createRequestTypes('ROOMS'); -export const APP = createRequestTypes('APP', ['READY']); +export const APP = createRequestTypes('APP', ['READY', 'INIT']); export const MESSAGES = createRequestTypes('MESSAGES'); -export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL'); +export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes, 'REQUEST_USERS', 'SUCCESS_USERS', 'FAILURE_USERS', 'SET_USERS']); export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']); -export const SERVER = createRequestTypes('SERVER', ['SELECT', 'CHANGED']); +export const SERVER = createRequestTypes('SERVER', [...defaultTypes, 'SELECT', 'CHANGED', 'ADD']); export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']); export const LOGOUT = 'LOGOUT'; // logout is always success diff --git a/app/actions/createChannel.js b/app/actions/createChannel.js index 60a8cca30..df4cad35a 100644 --- a/app/actions/createChannel.js +++ b/app/actions/createChannel.js @@ -20,3 +20,32 @@ export function createChannelFailure(err) { err }; } + + +export function createChannelRequestUsers(data) { + return { + type: types.CREATE_CHANNEL.REQUEST_USERS, + data + }; +} + +export function createChannelSetUsers(data) { + return { + type: types.CREATE_CHANNEL.SET_USERS, + data + }; +} + +export function createChannelSuccessUsers(data) { + return { + type: types.CREATE_CHANNEL.SUCCESS_USERS, + data + }; +} + +export function createChannelFailureUsers(err) { + return { + type: types.CREATE_CHANNEL.FAILURE_USERS, + err + }; +} diff --git a/app/actions/index.js b/app/actions/index.js index 75a05545f..35af27309 100644 --- a/app/actions/index.js +++ b/app/actions/index.js @@ -6,6 +6,12 @@ export function appReady() { type: APP.READY }; } + +export function appInit() { + return { + type: APP.INIT + }; +} export function setCurrentServer(server) { return { type: types.SET_CURRENT_SERVER, diff --git a/app/actions/login.js b/app/actions/login.js index cffc3cdda..7969c8090 100644 --- a/app/actions/login.js +++ b/app/actions/login.js @@ -28,7 +28,7 @@ export function loginFailure(err) { }; } -export function setToken(user) { +export function setToken(user = {}) { return { type: types.LOGIN.SET_TOKEN, token: user.token, diff --git a/app/actions/server.js b/app/actions/server.js index 230b4cd06..9f0efeed1 100644 --- a/app/actions/server.js +++ b/app/actions/server.js @@ -6,6 +6,37 @@ export function setServer(server) { server }; } +export function serverRequest(server) { + console.log(server); + return { + type: SERVER.REQUEST, + server + }; +} + +export function addServer(server) { + console.log(server); + return { + type: SERVER.ADD, + server + }; +} + + +export function serverSuccess() { + return { + type: SERVER.SUCCESS + }; +} + +export function serverFailure(err) { + return { + type: SERVER.FAILURE, + err + }; +} + + export function changedServer(server) { return { type: SERVER.CHANGED, diff --git a/app/components/Message.js b/app/components/Message.js index 6a2523fcf..b1dee59ed 100644 --- a/app/components/Message.js +++ b/app/components/Message.js @@ -1,12 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import { View, Text, StyleSheet } from 'react-native'; -import { CachedImage } from 'react-native-img-cache'; import { emojify } from 'react-emojione'; import Markdown from 'react-native-easy-markdown'; import moment from 'moment'; - -import avatarInitialsAndColor from '../utils/avatarInitialsAndColor'; +import Avatar from './avatar'; import Card from './message/card'; const styles = StyleSheet.create({ @@ -20,26 +18,6 @@ const styles = StyleSheet.create({ flexDirection: 'row', transform: [{ scaleY: -1 }] }, - avatarContainer: { - backgroundColor: '#eee', - width: 40, - height: 40, - marginRight: 10, - borderRadius: 5 - }, - avatar: { - width: 40, - height: 40, - borderRadius: 5, - position: 'absolute' - }, - avatarInitials: { - margin: 2, - textAlign: 'center', - lineHeight: 36, - fontSize: 22, - color: '#ffffff' - }, texts: { flex: 1 }, @@ -87,33 +65,17 @@ export default class Message extends React.PureComponent { const username = item.alias || item.u.username; - let { initials, color } = avatarInitialsAndColor(username); - - const avatar = item.avatar || `${ this.props.baseUrl }/avatar/${ item.u.username }`; - if (item.avatar) { - initials = ''; - color = 'transparent'; - } - - let aliasUsername; - if (item.alias) { - aliasUsername = @{item.u.username}; - } - const time = moment(item.ts).format(this.props.Message_TimeFormat); return ( - - {initials} - - + {username} - {aliasUsername}{time} + {item.alias && @{item.u.username}}{time} {this.attachments()} diff --git a/app/components/RoomItem.js b/app/components/RoomItem.js index 7737e03f3..b1957787e 100644 --- a/app/components/RoomItem.js +++ b/app/components/RoomItem.js @@ -1,9 +1,8 @@ import React from 'react'; -import { CachedImage } from 'react-native-img-cache'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import PropTypes from 'prop-types'; import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; - +import Avatar from './avatar'; import avatarInitialsAndColor from '../utils/avatarInitialsAndColor'; const styles = StyleSheet.create({ @@ -81,12 +80,8 @@ export default class RoomItem extends React.PureComponent { } if (type === 'd') { - const { initials, color } = avatarInitialsAndColor(name); return ( - - {initials} - - + ); } diff --git a/app/components/avatar.js b/app/components/avatar.js index 8ecd3ad10..8cc05a7fc 100644 --- a/app/components/avatar.js +++ b/app/components/avatar.js @@ -4,49 +4,48 @@ import { StyleSheet, Text, View } from 'react-native'; import { CachedImage } from 'react-native-img-cache'; import avatarInitialsAndColor from '../utils/avatarInitialsAndColor'; +const styles = StyleSheet.create({ + iconContainer: { + overflow: 'hidden', + justifyContent: 'center', + alignItems: 'center' + }, + avatar: { + position: 'absolute' + }, + avatarInitials: { + color: '#ffffff' + } }); -const styles = StyleSheet.create({ iconContainer: { - height: 40, - width: 40, - borderRadius: 20, - overflow: 'hidden', - justifyContent: 'center', - alignItems: 'center' -}, -icon: { - fontSize: 20, - color: '#fff' -}, -avatar: { - width: 40, - height: 40, - position: 'absolute' -}, -avatarInitials: { - fontSize: 20, - color: '#ffffff' -} }); +class Avatar extends React.PureComponent { + render() { + const { text = '', size = 25, baseUrl = this.props.baseUrl, + borderRadius = 5, style, avatar } = this.props; + const { initials, color } = avatarInitialsAndColor(`${ text }`); + return ( + + {initials} + { (avatar || baseUrl) && } + ); + } +} - -const avatar = ({ text = '', width = 25, height = 25, fontSize = 12, baseUrl, - borderRadius = 2, style }) => { - const { initials, color } = avatarInitialsAndColor(`${ text }`); - return ( - - {initials} - { baseUrl ? : null} - ); -}; - -avatar.propTypes = { +Avatar.propTypes = { + style: PropTypes.object, baseUrl: PropTypes.string, text: PropTypes.string.isRequired, - width: PropTypes.number, - fontSize: PropTypes.number, - height: PropTypes.number, + avatar: PropTypes.string, + size: PropTypes.number, borderRadius: PropTypes.number }; -export default avatar; +export default Avatar; diff --git a/app/components/tags.js b/app/components/tags.js index 2eec8def4..4700ddab4 100644 --- a/app/components/tags.js +++ b/app/components/tags.js @@ -74,8 +74,8 @@ const styles = StyleSheet.create({ const renderTag = (item, index) => ( this.props.onPress(item, index)} />); -const renderItemResult = ({ item = '' }) => ( - +const renderItemResult = ({ item = '', onPress }) => ( + @{item} ); @@ -102,11 +102,11 @@ export default class tags extends React.PureComponent { onChangeText={this.props.onChangeText} /> - {this.props.result ? item} style={styles.result} data={this.props.result || []} - renderItem={this.props.renderItemResult || renderItemResult} + renderItem={e => (this.props.renderItemResult || renderItemResult)(e, this.props.onSelect)} /> : null} ); } diff --git a/app/index.js b/app/index.js index 394d881ab..c76830ae5 100644 --- a/app/index.js +++ b/app/index.js @@ -1,11 +1,16 @@ import PropTypes from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; -import { Text } from 'react-native'; +import * as Animatable from 'react-native-animatable'; import setNavigator from './actions/navigator'; +import { appInit } from './actions'; import LoginView from './views/login'; import ListServerView from './views/serverList'; +import { Keyboard, Text, TextInput, View, Image, TouchableOpacity } from 'react-native'; + +import styles from './views/Styles'; + import store from './lib/createStore'; // @@ -22,16 +27,16 @@ export const authenticated = WrappedComponent => class _p extends React.PureComp 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' + screen: 'Login', + animationType: 'none' }); } } render() { // Wraps the input component in a container, without mutating it. Good! - return ; + return ((this.login.isAuthenticated || this.login.user) && ); } }; // @@ -43,7 +48,7 @@ export class PublicScreen extends React.PureComponent { // } // } render() { - return !this.login.isAuthenticated || !this.login.user ? null : (); + return ((this.login.isAuthenticated || this.login.user) && ); } } @@ -66,21 +71,29 @@ export class PrivateScreen extends React.PureComponent { // logged: state.login.isAuthenticated }), dispatch => ({ // navigate: routeName => dispatch(NavigationActions.navigate({ routeName })), - setNavigator: navigator => dispatch(setNavigator(navigator)) + setNavigator: navigator => dispatch(setNavigator(navigator)), + appInit: () => dispatch(appInit()) })) export const HomeScreen = class extends React.PureComponent { static propTypes = { setNavigator: PropTypes.func.isRequired, navigator: PropTypes.object.isRequired } - + static navigatorStyle = { + navBarHidden: true + }; componentWillMount() { this.props.setNavigator(this.props.navigator); - this.props.navigator.resetTo({ - screen: 'public' - }); + this.props.appInit(); + // + // this.props.navigator.setDrawerEnabled({ + // side: 'left', // the side of the drawer since you can have two, 'left' / 'right' + // enabled: false // should the drawer be enabled or disabled (locked closed) + // }); } render() { - return (oieee); + return ( + + ); } }; diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 22647b488..7de5aadcc 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -23,12 +23,8 @@ const call = (method, ...params) => new Promise((resolve, reject) => { const RocketChat = { createChannel({ name, users, type }) { - console.log('FISTALEU', { name, users, type }); return new Promise((resolve, reject) => { - Meteor.call(type ? 'createChannel' : 'createPrivateGroup', name, users, type, (err, res) => { - console.log(err, res); - return (err ? reject(err) : resolve(res)); - }); + Meteor.call(type ? 'createChannel' : 'createPrivateGroup', name, users, type, (err, res) => (err ? reject(err) : resolve(res))); }); }, @@ -40,7 +36,15 @@ const RocketChat = { console.warn(`AsyncStorage error: ${ error.message }`); } }, - + async testServer(url) { + if (/^(https?:\/\/)?(((\w|[0-9])+(\.(\w|[0-9-_])+)+)|localhost)(:\d+)?$/.test(url)) { + const response = await fetch(url, { method: 'HEAD' }); + if (response.status === 200 && response.headers.get('x-instance-id') != null && response.headers.get('x-instance-id').length) { + return url; + } + } + throw new Error({ error: 'invalid server' }); + }, connect(_url) { return new Promise((resolve) => { const url = `${ _url }/websocket`; @@ -69,7 +73,7 @@ const RocketChat = { const setting = { _id: item._id }; - setting._server = { id: reduxStore.getState().server }; + setting._server = { id: reduxStore.getState().server.server }; if (settingsType[item.type]) { setting[settingsType[item.type]] = item.value; realm.create('settings', setting, true); @@ -87,7 +91,7 @@ const RocketChat = { realm.write(() => { const message = ddbMessage.fields.args[0]; message.temp = false; - message._server = { id: reduxStore.getState().server }; + message._server = { id: reduxStore.getState().server.server }; // write('messages', message); realm.create('messages', message, true); }); @@ -97,7 +101,7 @@ const RocketChat = { // console.log(ddbMessage); realm.write(() => { const data = ddbMessage.fields.args[1]; - data._server = { id: reduxStore.getState().server }; + data._server = { id: reduxStore.getState().server.server }; realm.create('subscriptions', data, true); }); } @@ -108,13 +112,11 @@ const RocketChat = { }, login(params, callback) { - console.log('login(params, callback)'); return new Promise((resolve, reject) => { Meteor._startLoggingIn(); return Meteor.call('login', params, (err, result) => { Meteor._endLoggingIn(); Meteor._handleLoginCallback(err, result); - console.log('login(params, callback)asdas', err, result); if (err) { reject(err); } else { @@ -183,7 +185,7 @@ const RocketChat = { // if (typeof item.value === 'string') { // subscription.value = item.value; // } - subscription._server = { id: reduxStore.getState().server }; + subscription._server = { id: reduxStore.getState().server.server }; // write('subscriptions', subscription); realm.create('subscriptions', subscription, true); }); @@ -207,7 +209,7 @@ const RocketChat = { realm.write(() => { data.messages.forEach((message) => { message.temp = false; - message._server = { id: reduxStore.getState().server }; + message._server = { id: reduxStore.getState().server.server }; // write('messages', message); realm.create('messages', message, true); }); @@ -237,7 +239,7 @@ const RocketChat = { ts: new Date(), _updatedAt: new Date(), temp: true, - _server: { id: reduxStore.getState().server }, + _server: { id: reduxStore.getState().server.server }, u: { _id: reduxStore.getState().login.user.id || '1', username: reduxStore.getState().login.user.id @@ -381,7 +383,6 @@ const RocketChat = { return subscription; }); // Meteor.subscribe('stream-notify-user', `${ Meteor.userId() }/rooms-changed`, false); - console.log('getRooms resolved', reduxStore.getState().server, data); realm.write(() => { data.forEach((subscription) => { // const subscription = { @@ -390,7 +391,7 @@ const RocketChat = { // if (typeof item.value === 'string') { // subscription.value = item.value; // } - subscription._server = { id: reduxStore.getState().server }; + subscription._server = { id: reduxStore.getState().server.server }; // write('subscriptions', subscription); realm.create('subscriptions', subscription, true); }); diff --git a/app/reducers/server.js b/app/reducers/server.js index 8ee296cd5..ea0315e25 100644 --- a/app/reducers/server.js +++ b/app/reducers/server.js @@ -1,9 +1,36 @@ import { SERVER } from '../actions/actionsTypes'; -export default function server(state = '', action) { +const initialState = { + connecting: false, + connected: false, + errorMessage: '', + failure: false, + server: '' +}; + + +export default function server(state = initialState, action) { switch (action.type) { + case SERVER.REQUEST: + return { ...state, + connecting: true, + failure: false + }; + case SERVER.SUCCESS: + return { ...state, + connecting: false, + connected: true, + failure: false + }; + case SERVER.FAILURE: + return { ...state, + connecting: false, + connected: false, + failure: true, + errorMessage: action.err + }; case SERVER.SELECT: - return action.server; + return { ...state, server: action.server }; default: return state; } diff --git a/app/sagas/connect.js b/app/sagas/connect.js index 4a8ac45f3..3b0c6f793 100644 --- a/app/sagas/connect.js +++ b/app/sagas/connect.js @@ -1,10 +1,10 @@ -import { take, put, call, fork, takeLatest, select } from 'redux-saga/effects'; +import { put, call, takeLatest, select } from 'redux-saga/effects'; import { METEOR } from '../actions/actionsTypes'; import RocketChat from '../lib/rocketchat'; import { connectSuccess, connectFailure } from '../actions/connect'; -const getServer = ({ server }) => server; +const getServer = ({ server }) => server.server; const connect = url => RocketChat.connect(url); @@ -17,14 +17,11 @@ const test = function* test() { yield put(connectFailure(err.status)); } }; -const watchConnect = function* watchConnect() { - yield takeLatest(METEOR.REQUEST, test); - while (true) { - yield take(METEOR.DISCONNECT); - } -}; +// const watchConnect = function* watchConnect() { +// }; const root = function* root() { - yield fork(watchConnect); + yield takeLatest(METEOR.REQUEST, test); + // yield fork(watchConnect); // yield fork(auto); }; export default root; diff --git a/app/sagas/init.js b/app/sagas/init.js index b06120211..ceedb0509 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -1,13 +1,22 @@ import { AsyncStorage } from 'react-native'; -import { call, put } from 'redux-saga/effects'; +import { call, put, select, take } from 'redux-saga/effects'; import * as actions from '../actions'; import { setServer } from '../actions/server'; +import { APP } from '../actions/actionsTypes'; const restore = function* restore() { try { + yield take(APP.INIT); + const { navigator } = yield select(state => state); const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer'); yield put(actions.appReady({})); - if (currentServer) { yield put(setServer(currentServer)); } + if (currentServer) { + yield put(setServer(currentServer)); + } else { + navigator.resetTo({ + screen: 'ListServer' + }); + } } catch (e) { console.log(e); } diff --git a/app/sagas/login.js b/app/sagas/login.js index b21d2503e..b987d4f39 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -1,12 +1,12 @@ import { AsyncStorage } from 'react-native'; import { take, put, call, takeEvery, fork, select, all, race } from 'redux-saga/effects'; import * as types from '../actions/actionsTypes'; -import { loginRequest, loginSuccess, loginFailure, setToken } from '../actions/login'; +import { loginRequest, loginSuccess, loginFailure, setToken, logout } from '../actions/login'; import RocketChat from '../lib/rocketchat'; const TOKEN_KEY = 'reactnativemeteor_usertoken'; const getUser = state => state.login; -const getServer = state => state.server; +const getServer = state => state.server.server; const loginCall = args => (args.resume ? RocketChat.login(args) : RocketChat.loginWithPassword(args)); const getToken = function* getToken() { @@ -20,6 +20,8 @@ const getToken = function* getToken() { } catch (e) { console.log('getTokenerr', e); } + } else { + yield put(setToken()); } }; @@ -27,10 +29,9 @@ const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() { // do { try { yield take(types.METEOR.SUCCESS); + yield call(getToken); const { navigator } = yield select(state => state); - navigator.resetTo({ - screen: 'Rooms' - }); + const user = yield select(getUser); if (user.token) { yield put(loginRequest({ resume: user.token })); @@ -47,6 +48,9 @@ const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() { // }); // } } + navigator.resetTo({ + screen: 'Rooms' + }); } catch (e) { console.log(e); } @@ -66,8 +70,11 @@ const handleLoginRequest = function* handleLoginRequest() { const response = yield call(loginCall, credentials); yield put(loginSuccess(response)); } catch (err) { - // console.log('login failed'); - yield put(loginFailure(err)); + if (err.error === 403) { + yield put(logout()); + } else { + yield put(loginFailure(err)); + } } } }; @@ -93,7 +100,6 @@ const handleLoginSubmit = function* handleLoginSubmit() { }; const root = function* root() { - yield takeEvery(types.SERVER.CHANGED, getToken); yield takeEvery(types.SERVER.CHANGED, handleLoginWhenServerChanges); yield fork(handleLoginRequest); yield takeEvery(types.LOGIN.SUCCESS, saveToken); diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index de45cd34c..35c3177ae 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -7,7 +7,6 @@ import { changedServer } from '../actions/server'; const selectServer = function* selectServer({ server }) { yield put(disconnect()); yield put(changedServer(server)); - yield console.log('SERVER->', server); yield call([AsyncStorage, 'setItem'], 'currentServer', server); yield put(connectRequest(server)); }; diff --git a/app/utils/avatarInitialsAndColor.js b/app/utils/avatarInitialsAndColor.js index 09c41fb1d..13029c66c 100644 --- a/app/utils/avatarInitialsAndColor.js +++ b/app/utils/avatarInitialsAndColor.js @@ -1,6 +1,12 @@ import { AVATAR_COLORS } from '../constants/colors'; export default function(username = '') { + if (username === '') { + return { + initials: '', + colors: 'transparent' + }; + } const position = username.length % AVATAR_COLORS.length; const color = AVATAR_COLORS[position]; diff --git a/app/views/login.js b/app/views/login.js index 1195b7c24..1c70b5490 100644 --- a/app/views/login.js +++ b/app/views/login.js @@ -119,7 +119,7 @@ class LoginView extends React.Component { function mapStateToProps(state) { // console.log(Object.keys(state)); return { - server: state.server, + server: state.server.server, Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder, login: state.login diff --git a/app/views/room.js b/app/views/room.js index 2f0e0a29a..0060f4342 100644 --- a/app/views/room.js +++ b/app/views/room.js @@ -46,7 +46,7 @@ const styles = StyleSheet.create({ @connect(state => ({ - server: state.server, + server: state.server.server, Site_Url: state.settings.Site_Url, Message_TimeFormat: state.settings.Message_TimeFormat, loading: state.messages.isFetching diff --git a/app/views/roomsList.js b/app/views/roomsList.js index b503bc4cb..559fbffff 100644 --- a/app/views/roomsList.js +++ b/app/views/roomsList.js @@ -56,10 +56,10 @@ const styles = StyleSheet.create({ const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }); @connect(state => ({ - server: state.server, + server: state.server.server, login: state.login, Site_Url: state.settings.Site_Url, - canShowList: state.login.token.length || state.login.user.token + canShowList: state.login.token || state.login.user.token }), dispatch => ({ login: () => dispatch(actions.login()), connect: () => dispatch(server.connectRequest()) diff --git a/app/views/serverList.js b/app/views/serverList.js index ec04b22c3..5bf276b08 100644 --- a/app/views/serverList.js +++ b/app/views/serverList.js @@ -65,7 +65,7 @@ const zeroconf = new Zeroconf(); @connect(state => ({ - server: state.server + server: state.server.server }), dispatch => ({ selectServer: server => dispatch(setServer(server)) })) diff --git a/app/views/serverNew.js b/app/views/serverNew.js index e4eae9fdf..0cd3a38e7 100644 --- a/app/views/serverNew.js +++ b/app/views/serverNew.js @@ -2,9 +2,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Navigation } from 'react-native-navigation'; import { Text, TextInput, View, StyleSheet } from 'react-native'; -import _ from 'underscore'; -import realm from '../lib/realm'; - +import { connect } from 'react-redux'; +import { serverRequest, addServer } from '../actions/server'; import KeyboardView from '../components/KeyboardView'; const styles = StyleSheet.create({ @@ -47,10 +46,17 @@ const styles = StyleSheet.create({ flexGrow: 1 } }); - +@connect(state => ({ + validInstance: !state.server.failure, + validating: state.server.connecting +}), dispatch => ({ + validateServer: url => dispatch(serverRequest(url)), + addServer: url => dispatch(addServer(url)) +})) export default class NewServerView extends React.Component { static propTypes = { - navigator: PropTypes.object.isRequired + navigator: PropTypes.object.isRequired, + validateServer: PropTypes.func.isRequired } static navigationOptions = () => ({ @@ -66,37 +72,11 @@ export default class NewServerView extends React.Component { }; this.submit = () => { - let url = this.state.text.trim(); - if (!url) { - url = this.state.defaultServer; - } else { - url = this.completeUrl(this.state.text); - } - - this.setState({ - editable: false - }); - - this.inputElement.blur(); - this.validateServer(url).then(() => { - realm.write(() => { - realm.create('servers', { id: url, current: false }, true); - }); - Navigation.dismissModal({ - animationType: 'slide-down' - }); - }).catch(() => { - this.setState({ - editable: true - }); - this.inputElement.focus(); - }); + this.props.addServer(this.completeUrl(this.state.text.trim() || this.state.defaultServer)); }; } componentDidMount() { - this._mounted = true; - this.props.navigator.setTitle({ title: 'New server' }); @@ -111,11 +91,6 @@ export default class NewServerView extends React.Component { this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); } - - componentWillUnmount() { - this._mounted = false; - } - onNavigatorEvent = (event) => { if (event.type === 'NavBarButtonPress') { if (event.id === 'close') { @@ -128,62 +103,8 @@ export default class NewServerView extends React.Component { onChangeText = (text) => { this.setState({ text }); - - this.validateServerDebounced(text); + this.props.validateServer(this.completeUrl(text)); } - - validateServer = url => new Promise((resolve, reject) => { - url = this.completeUrl(url); - - this.setState({ - validating: false, - url - }); - - if (/^(https?:\/\/)?(((\w|[0-9])+(\.(\w|[0-9-_])+)+)|localhost)(:\d+)?$/.test(url)) { - this.setState({ - validating: true - }); - - fetch(url, { method: 'HEAD' }) - .then((response) => { - if (!this._mounted) { - return; - } - if (response.status === 200 && response.headers.get('x-instance-id') != null && response.headers.get('x-instance-id').length) { - this.setState({ - validInstance: true, - validating: false - }); - resolve(url); - } else { - this.setState({ - validInstance: false, - validating: false - }); - reject(url); - } - }) - .catch(() => { - if (!this._mounted) { - return; - } - this.setState({ - validInstance: false, - validating: false - }); - reject(url); - }); - } else { - this.setState({ - validInstance: undefined - }); - reject(url); - } - }) - - validateServerDebounced = _.debounce(this.validateServer, 1000) - completeUrl = (url) => { url = url.trim(); @@ -203,7 +124,10 @@ export default class NewServerView extends React.Component { } renderValidation = () => { - if (this.state.validating) { + if (!this.state.text.trim()) { + return null; + } + if (this.props.validating) { return ( Validating {this.state.url} ... @@ -211,7 +135,7 @@ export default class NewServerView extends React.Component { ); } - if (this.state.validInstance) { + if (this.props.validInstance) { return ( {this.state.url} is a valid Rocket.Chat instance @@ -219,7 +143,7 @@ export default class NewServerView extends React.Component { ); } - if (this.state.validInstance === false) { + if (!this.state.validInstance) { return ( {this.state.url} is not a valid Rocket.Chat instance diff --git a/package.json b/package.json index 53b258670..88f21bf0a 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "react-emojione": "^3.1.10", "react-native": "0.46.1", "react-native-action-button": "^2.7.2", + "react-native-animatable": "^1.2.3", "react-native-card-view": "0.0.3", "react-native-easy-markdown": "git+https://github.com/lappalj4/react-native-easy-markdown.git", "react-native-fetch-blob": "^0.10.8", diff --git a/storybook/stories/Avatar.js b/storybook/stories/Avatar.js new file mode 100644 index 000000000..204a38bd6 --- /dev/null +++ b/storybook/stories/Avatar.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { ScrollView } from 'react-native'; + +import Avatar from '../../app/components/avatar'; + +export default ( + + + + + + +); diff --git a/storybook/stories/index.js b/storybook/stories/index.js index 4f10d5fa9..abba63548 100644 --- a/storybook/stories/index.js +++ b/storybook/stories/index.js @@ -8,6 +8,9 @@ import { storiesOf } from '@storybook/react-native'; import DirectMessage from './Channels/DirectMessage'; import TagInput from './TagInput'; +import Avatar from './Avatar'; + +storiesOf('Avatar', module).add('avatar', () => Avatar); storiesOf('Tag Input', module).add('Tag Input', () => TagInput); storiesOf('Channel Cell', module).add('Direct Messages', () => DirectMessage);