Fix merge conflicts from 'develop'.

This commit is contained in:
Gilmar Quinelato 2017-09-01 17:00:31 -03:00
commit 3abc47b7d4
36 changed files with 1934 additions and 815 deletions

View File

@ -231,15 +231,16 @@ exports[`render unread +999 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#3F51B5", "backgroundColor": "#3F51B5",
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -249,29 +250,18 @@ exports[`render unread +999 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
NA NA
</Text> </Text>
<View
source={
Object {
"uri": "undefined/avatar/name",
}
}
style={
Object {
"borderRadius": 20,
"height": 40,
"position": "absolute",
"width": 40,
}
}
/>
</View> </View>
<Text <Text
accessible={true} accessible={true}
@ -350,15 +340,16 @@ exports[`render unread 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#3F51B5", "backgroundColor": "#3F51B5",
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -368,29 +359,18 @@ exports[`render unread 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
NA NA
</Text> </Text>
<View
source={
Object {
"uri": "undefined/avatar/name",
}
}
style={
Object {
"borderRadius": 20,
"height": 40,
"position": "absolute",
"width": 40,
}
}
/>
</View> </View>
<Text <Text
accessible={true} accessible={true}
@ -469,15 +449,16 @@ exports[`renders correctly 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#3F51B5", "backgroundColor": "#3F51B5",
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -487,29 +468,18 @@ exports[`renders correctly 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
NA NA
</Text> </Text>
<View
source={
Object {
"uri": "undefined/avatar/name",
}
}
style={
Object {
"borderRadius": 20,
"height": 40,
"position": "absolute",
"width": 40,
}
}
/>
</View> </View>
<Text <Text
accessible={true} accessible={true}

View File

@ -1,5 +1,160 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Avatar avatar 1`] = `
<RCTScrollView>
<View>
<View
style={
Array [
Object {
"alignItems": "center",
"justifyContent": "center",
"overflow": "hidden",
},
Object {
"backgroundColor": "#3F51B5",
"borderRadius": 5,
"height": 25,
"width": 25,
},
undefined,
]
}
>
<Text
accessible={true}
allowFontScaling={true}
disabled={false}
ellipsizeMode="tail"
style={
Array [
Object {
"color": "#ffffff",
},
Object {
"fontSize": 12.5,
},
]
}
>
TE
</Text>
</View>
<View
style={
Array [
Object {
"alignItems": "center",
"justifyContent": "center",
"overflow": "hidden",
},
Object {
"backgroundColor": "#9C27B0",
"borderRadius": 5,
"height": 40,
"width": 40,
},
undefined,
]
}
>
<Text
accessible={true}
allowFontScaling={true}
disabled={false}
ellipsizeMode="tail"
style={
Array [
Object {
"color": "#ffffff",
},
Object {
"fontSize": 20,
},
]
}
>
AA
</Text>
</View>
<View
style={
Array [
Object {
"alignItems": "center",
"justifyContent": "center",
"overflow": "hidden",
},
Object {
"backgroundColor": "#9C27B0",
"borderRadius": 5,
"height": 30,
"width": 30,
},
undefined,
]
}
>
<Text
accessible={true}
allowFontScaling={true}
disabled={false}
ellipsizeMode="tail"
style={
Array [
Object {
"color": "#ffffff",
},
Object {
"fontSize": 15,
},
]
}
>
BB
</Text>
</View>
<View
style={
Array [
Object {
"alignItems": "center",
"justifyContent": "center",
"overflow": "hidden",
},
Object {
"backgroundColor": "#3F51B5",
"borderRadius": 2,
"height": 25,
"width": 25,
},
undefined,
]
}
>
<Text
accessible={true}
allowFontScaling={true}
disabled={false}
ellipsizeMode="tail"
style={
Array [
Object {
"color": "#ffffff",
},
Object {
"fontSize": 12.5,
},
]
}
>
TE
</Text>
</View>
</View>
</RCTScrollView>
`;
exports[`Storyshots Channel Cell Direct Messages 1`] = ` exports[`Storyshots Channel Cell Direct Messages 1`] = `
<RCTScrollView> <RCTScrollView>
<View> <View>
@ -37,15 +192,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#8BC34A", "backgroundColor": "#8BC34A",
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -55,10 +211,14 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
RC RC
@ -70,12 +230,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
} }
} }
style={ style={
Object { Array [
"borderRadius": 20, Object {
"height": 40, "position": "absolute",
"position": "absolute", },
"width": 40, Object {
} "height": 40,
"width": 40,
},
]
} }
/> />
</View> </View>
@ -132,15 +295,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#8BC34A", "backgroundColor": "#8BC34A",
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -150,10 +314,14 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
RC RC
@ -165,12 +333,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
} }
} }
style={ style={
Object { Array [
"borderRadius": 20, Object {
"height": 40, "position": "absolute",
"position": "absolute", },
"width": 40, Object {
} "height": 40,
"width": 40,
},
]
} }
/> />
</View> </View>
@ -227,15 +398,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#8BC34A", "backgroundColor": "#8BC34A",
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -245,10 +417,14 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
RC RC
@ -260,12 +436,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
} }
} }
style={ style={
Object { Array [
"borderRadius": 20, Object {
"height": 40, "position": "absolute",
"position": "absolute", },
"width": 40, Object {
} "height": 40,
"width": 40,
},
]
} }
/> />
</View> </View>
@ -343,15 +522,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#795548", "backgroundColor": "#795548",
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -361,10 +541,14 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
LC LC
@ -376,12 +560,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
} }
} }
style={ style={
Object { Array [
"borderRadius": 20, Object {
"height": 40, "position": "absolute",
"position": "absolute", },
"width": 40, Object {
} "height": 40,
"width": 40,
},
]
} }
/> />
</View> </View>
@ -459,15 +646,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#795548", "backgroundColor": "#795548",
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -477,10 +665,14 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
LC LC
@ -492,12 +684,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
} }
} }
style={ style={
Object { Array [
"borderRadius": 20, Object {
"height": 40, "position": "absolute",
"position": "absolute", },
"width": 40, Object {
} "height": 40,
"width": 40,
},
]
} }
/> />
</View> </View>
@ -575,15 +770,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#795548", "backgroundColor": "#795548",
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -593,10 +789,14 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
LC LC
@ -608,12 +808,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
} }
} }
style={ style={
Object { Array [
"borderRadius": 20, Object {
"height": 40, "position": "absolute",
"position": "absolute", },
"width": 40, Object {
} "height": 40,
"width": 40,
},
]
} }
/> />
</View> </View>
@ -691,15 +894,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#795548", "backgroundColor": "#795548",
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -709,10 +913,14 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
LC LC
@ -724,12 +932,15 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
} }
} }
style={ style={
Object { Array [
"borderRadius": 20, Object {
"height": 40, "position": "absolute",
"position": "absolute", },
"width": 40, Object {
} "height": 40,
"width": 40,
},
]
} }
/> />
</View> </View>
@ -807,15 +1018,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#E91E63", "backgroundColor": "#E91E63",
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -825,29 +1037,18 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
W W
</Text> </Text>
<CachedImage
source={
Object {
"uri": "undefined/avatar/W",
}
}
style={
Object {
"borderRadius": 20,
"height": 40,
"position": "absolute",
"width": 40,
}
}
/>
</View> </View>
<Text <Text
accessible={true} accessible={true}
@ -902,15 +1103,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#9C27B0", "backgroundColor": "#9C27B0",
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -920,29 +1122,18 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
WW WW
</Text> </Text>
<CachedImage
source={
Object {
"uri": "undefined/avatar/WW",
}
}
style={
Object {
"borderRadius": 20,
"height": 40,
"position": "absolute",
"width": 40,
}
}
/>
</View> </View>
<Text <Text
accessible={true} accessible={true}
@ -997,15 +1188,16 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
Array [ Array [
Object { Object {
"alignItems": "center", "alignItems": "center",
"borderRadius": 20,
"height": 40,
"justifyContent": "center", "justifyContent": "center",
"overflow": "hidden", "overflow": "hidden",
"width": 40,
}, },
Object { Object {
"backgroundColor": "#F44336", "backgroundColor": undefined,
"borderRadius": 20,
"height": 40,
"width": 40,
}, },
undefined,
] ]
} }
> >
@ -1015,29 +1207,18 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
disabled={false} disabled={false}
ellipsizeMode="tail" ellipsizeMode="tail"
style={ style={
Object { Array [
"color": "#ffffff", Object {
"fontSize": 20, "color": "#ffffff",
} },
Object {
"fontSize": 20,
},
]
} }
> >
</Text> </Text>
<CachedImage
source={
Object {
"uri": "undefined/avatar/",
}
}
style={
Object {
"borderRadius": 20,
"height": 40,
"position": "absolute",
"width": 40,
}
}
/>
</View> </View>
<Text <Text
accessible={true} accessible={true}

View File

@ -12,10 +12,11 @@ function createRequestTypes(base, types = defaultTypes) {
// Login events // Login events
export const LOGIN = createRequestTypes('LOGIN', [...defaultTypes, 'SET_TOKEN', 'SUBMIT']); export const LOGIN = createRequestTypes('LOGIN', [...defaultTypes, 'SET_TOKEN', 'SUBMIT']);
export const ROOMS = createRequestTypes('ROOMS'); 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 MESSAGES = createRequestTypes('MESSAGES');
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes, 'REQUEST_USERS', 'SUCCESS_USERS', 'FAILURE_USERS', 'SET_USERS']);
export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']); 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 METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']);
export const LOGOUT = 'LOGOUT'; // logout is always success export const LOGOUT = 'LOGOUT'; // logout is always success

View File

@ -20,7 +20,6 @@ export function connectFailure(err) {
} }
export function disconnect(err) { export function disconnect(err) {
console.log('types.METEOR.DISCONNECT');
return { return {
type: types.METEOR.DISCONNECT, type: types.METEOR.DISCONNECT,
err err

View File

@ -0,0 +1,51 @@
import * as types from './actionsTypes';
export function createChannelRequest(data) {
return {
type: types.CREATE_CHANNEL.REQUEST,
data
};
}
export function createChannelSuccess(data) {
return {
type: types.CREATE_CHANNEL.SUCCESS,
data
};
}
export function createChannelFailure(err) {
return {
type: types.CREATE_CHANNEL.FAILURE,
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
};
}

View File

@ -6,6 +6,12 @@ export function appReady() {
type: APP.READY type: APP.READY
}; };
} }
export function appInit() {
return {
type: APP.INIT
};
}
export function setCurrentServer(server) { export function setCurrentServer(server) {
return { return {
type: types.SET_CURRENT_SERVER, type: types.SET_CURRENT_SERVER,

View File

@ -28,7 +28,7 @@ export function loginFailure(err) {
}; };
} }
export function setToken(user) { export function setToken(user = {}) {
return { return {
type: types.LOGIN.SET_TOKEN, type: types.LOGIN.SET_TOKEN,
token: user.token, token: user.token,

View File

@ -1,7 +1,6 @@
import * as types from './actionsTypes'; import * as types from './actionsTypes';
export function messagesRequest({ rid }) { export function messagesRequest({ rid }) {
console.log(types.MESSAGES.REQUEST, rid);
return { return {
type: types.MESSAGES.REQUEST, type: types.MESSAGES.REQUEST,
rid rid

View File

@ -6,6 +6,35 @@ export function setServer(server) {
server server
}; };
} }
export function serverRequest(server) {
return {
type: SERVER.REQUEST,
server
};
}
export function addServer(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) { export function changedServer(server) {
return { return {
type: SERVER.CHANGED, type: SERVER.CHANGED,

View File

@ -4,7 +4,7 @@ import { View, StyleSheet } from 'react-native';
import { emojify } from 'react-emojione'; import { emojify } from 'react-emojione';
import Markdown from 'react-native-easy-markdown'; import Markdown from 'react-native-easy-markdown';
import Card from './message/card'; import Card from './message/Card';
import Avatar from './message/Avatar'; import Avatar from './message/Avatar';
import User from './message/User'; import User from './message/User';
@ -41,10 +41,17 @@ export default class Message extends React.PureComponent {
} }
const msg = emojify(item.msg, { output: 'unicode' }); const msg = emojify(item.msg, { output: 'unicode' });
const username = item.alias || item.u.username;
return ( return (
<View style={[styles.message, extraStyle]}> <View style={[styles.message, extraStyle]}>
<Avatar item={item} baseUrl={this.props.baseUrl} /> <Avatar
style={{ marginRight: 10 }}
text={item.avatar ? '' : username}
size={40}
baseUrl={this.props.baseUrl}
avatar={item.avatar}
/>
<View style={[styles.content]}> <View style={[styles.content]}>
<User <User
onPress={this._onPress} onPress={this._onPress}

View File

@ -1,55 +1,54 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { View, Text, StyleSheet } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import { CachedImage } from 'react-native-img-cache'; import { CachedImage } from 'react-native-img-cache';
import avatarInitialsAndColor from '../../utils/avatarInitialsAndColor'; import avatarInitialsAndColor from '../../utils/avatarInitialsAndColor';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
avatarContainer: { iconContainer: {
backgroundColor: '#eee', overflow: 'hidden',
width: 40, justifyContent: 'center',
height: 40, alignItems: 'center'
marginRight: 10,
borderRadius: 5
}, },
avatar: { avatar: {
width: 40,
height: 40,
borderRadius: 5,
position: 'absolute' position: 'absolute'
}, },
avatarInitials: { avatarInitials: {
margin: 2,
textAlign: 'center',
lineHeight: 36,
fontSize: 22,
color: '#ffffff' color: '#ffffff'
} }
}); });
export default class Message extends React.PureComponent { class Avatar extends React.PureComponent {
static propTypes = {
item: PropTypes.object.isRequired,
baseUrl: PropTypes.string
}
render() { render() {
const { item } = this.props; const { text = '', size = 25, baseUrl = this.props.baseUrl,
borderRadius = 5, style, avatar } = this.props;
const username = item.alias || item.u.username; const { initials, color } = avatarInitialsAndColor(`${ text }`);
const { initials, color } = avatarInitialsAndColor(username);
const avatar = item.avatar || `${ this.props.baseUrl }/avatar/${ item.u.username }`;
const avatarInitials = item.avatar ? '' : initials;
const avatarColor = item.avatar ? 'transparent' : color;
return ( return (
<View style={[styles.avatarContainer, { backgroundColor: avatarColor }]}> <View style={[styles.iconContainer, {
<Text style={styles.avatarInitials}>{avatarInitials}</Text> backgroundColor: color,
<CachedImage style={styles.avatar} source={{ uri: avatar }} /> width: size,
</View> height: size,
); borderRadius
}, style]}
>
<Text style={[styles.avatarInitials, { fontSize: size / 2 }]}>{initials}</Text>
{(avatar || baseUrl) && <CachedImage
style={[styles.avatar, {
width: size,
height: size
}]}
source={{ uri: avatar || `${ baseUrl }/avatar/${ text }` }}
/>}
</View>);
} }
} }
Avatar.propTypes = {
style: PropTypes.object,
baseUrl: PropTypes.string,
text: PropTypes.string.isRequired,
avatar: PropTypes.string,
size: PropTypes.number,
borderRadius: PropTypes.number
};
export default Avatar;

View File

@ -1,33 +1,38 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { View, Image } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Text } from 'react-native'; import * as Animatable from 'react-native-animatable';
import setNavigator from './actions/navigator'; import setNavigator from './actions/navigator';
import { appInit } from './actions';
import LoginView from './views/login'; import LoginView from './views/login';
import ListServerView from './views/serverList'; import ListServerView from './views/serverList';
import styles from './views/Styles';
import store from './lib/createStore'; import store from './lib/createStore';
export const authenticated = WrappedComponent => class _p extends React.PureComponent { export const authenticated = WrappedComponent => class _p extends React.PureComponent {
constructor() { constructor() {
super(); super();
this.login = store.getState().login; this.login = store.getState().login;
console.log('this.login.token', this.login.token);
if (!this.login.token || this.login.failure) { if (!this.login.token || this.login.failure) {
return store.getState().navigator.resetTo({ return store.getState().navigator.resetTo({
screen: 'Login' screen: 'Login',
animated: false
}); });
} }
} }
render() { render() {
// Wraps the input component in a container, without mutating it. Good! // Wraps the input component in a container, without mutating it. Good!
return <WrappedComponent {...this.props} />; return ((this.login.isAuthenticated || this.login.user) && <WrappedComponent {...this.props} />);
} }
}; };
// //
export class PublicScreen extends React.PureComponent { export class PublicScreen extends React.PureComponent {
render() { render() {
return !this.login.isAuthenticated || !this.login.user ? null : (<ListServerView {...this.props} />); return ((this.login.isAuthenticated || this.login.user) && <ListServerView {...this.props} />);
} }
} }
@ -43,21 +48,35 @@ export class PrivateScreen extends React.PureComponent {
@connect(() => ({ @connect(() => ({
// logged: state.login.isAuthenticated // logged: state.login.isAuthenticated
}), dispatch => ({ }), dispatch => ({
setNavigator: navigator => dispatch(setNavigator(navigator)) setNavigator: navigator => dispatch(setNavigator(navigator)),
appInit: () => dispatch(appInit())
})) }))
export const HomeScreen = class extends React.PureComponent { export const HomeScreen = class extends React.PureComponent {
static propTypes = { static propTypes = {
appInit: PropTypes.func.isRequired,
setNavigator: PropTypes.func.isRequired, setNavigator: PropTypes.func.isRequired,
navigator: PropTypes.object.isRequired navigator: PropTypes.object.isRequired
} }
static navigatorStyle = {
navBarHidden: true,
rightButtons: [{
id: 'close',
title: 'Cancel'
}]
};
componentWillMount() { componentWillMount() {
this.props.setNavigator(this.props.navigator); this.props.setNavigator(this.props.navigator);
this.props.navigator.resetTo({ this.props.appInit();
screen: 'public' //
}); // 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() { render() {
return (<Text>oieee</Text>); return (<View style={styles.logoContainer}><Animatable.Text animation='pulse' easing='ease-out' iterationCount='infinite' style={{ textAlign: 'center' }}>
<Image style={styles.logo} source={require('./images/logo.png')} />
</Animatable.Text></View>);
} }
}; };

View File

@ -36,7 +36,15 @@ const RocketChat = {
console.warn(`AsyncStorage error: ${ error.message }`); 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) { connect(_url) {
return new Promise((resolve) => { return new Promise((resolve) => {
const url = `${ _url }/websocket`; const url = `${ _url }/websocket`;
@ -61,7 +69,7 @@ const RocketChat = {
const setting = { const setting = {
_id: item._id _id: item._id
}; };
setting._server = { id: reduxStore.getState().server }; setting._server = { id: reduxStore.getState().server.server };
if (settingsType[item.type]) { if (settingsType[item.type]) {
setting[settingsType[item.type]] = item.value; setting[settingsType[item.type]] = item.value;
realm.create('settings', setting, true); realm.create('settings', setting, true);
@ -78,7 +86,7 @@ const RocketChat = {
realm.write(() => { realm.write(() => {
const message = ddbMessage.fields.args[0]; const message = ddbMessage.fields.args[0];
message.temp = false; message.temp = false;
message._server = { id: reduxStore.getState().server }; message._server = { id: reduxStore.getState().server.server };
realm.create('messages', message, true); realm.create('messages', message, true);
}); });
} }
@ -86,7 +94,7 @@ const RocketChat = {
if (ddbMessage.collection === 'stream-notify-user') { if (ddbMessage.collection === 'stream-notify-user') {
realm.write(() => { realm.write(() => {
const data = ddbMessage.fields.args[1]; const data = ddbMessage.fields.args[1];
data._server = { id: reduxStore.getState().server }; data._server = { id: reduxStore.getState().server.server };
realm.create('subscriptions', data, true); realm.create('subscriptions', data, true);
}); });
} }
@ -97,13 +105,11 @@ 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 {
@ -172,7 +178,7 @@ const RocketChat = {
// if (typeof item.value === 'string') { // if (typeof item.value === 'string') {
// subscription.value = item.value; // subscription.value = item.value;
// } // }
subscription._server = { id: reduxStore.getState().server }; subscription._server = { id: reduxStore.getState().server.server };
// write('subscriptions', subscription); // write('subscriptions', subscription);
realm.create('subscriptions', subscription, true); realm.create('subscriptions', subscription, true);
}); });
@ -196,7 +202,7 @@ const RocketChat = {
realm.write(() => { realm.write(() => {
data.messages.forEach((message) => { data.messages.forEach((message) => {
message.temp = false; message.temp = false;
message._server = { id: reduxStore.getState().server }; message._server = { id: reduxStore.getState().server.server };
// write('messages', message); // write('messages', message);
realm.create('messages', message, true); realm.create('messages', message, true);
}); });
@ -226,7 +232,7 @@ const RocketChat = {
ts: new Date(), ts: new Date(),
_updatedAt: new Date(), _updatedAt: new Date(),
temp: true, temp: true,
_server: { id: reduxStore.getState().server }, _server: { id: reduxStore.getState().server.server },
u: { u: {
_id: reduxStore.getState().login.user.id || '1', _id: reduxStore.getState().login.user.id || '1',
username: reduxStore.getState().login.user.id username: reduxStore.getState().login.user.id
@ -370,7 +376,6 @@ const RocketChat = {
return subscription; return subscription;
}); });
// 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 = {
@ -379,7 +384,7 @@ const RocketChat = {
// if (typeof item.value === 'string') { // if (typeof item.value === 'string') {
// subscription.value = item.value; // subscription.value = item.value;
// } // }
subscription._server = { id: reduxStore.getState().server }; subscription._server = { id: reduxStore.getState().server.server };
// write('subscriptions', subscription); // write('subscriptions', subscription);
realm.create('subscriptions', subscription, true); realm.create('subscriptions', subscription, true);
}); });

View File

@ -1,9 +1,8 @@
import React from 'react'; import React from 'react';
import { CachedImage } from 'react-native-img-cache';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import Avatar from './avatar';
import avatarInitialsAndColor from '../utils/avatarInitialsAndColor'; import avatarInitialsAndColor from '../utils/avatarInitialsAndColor';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -84,10 +83,7 @@ export default class RoomItem extends React.PureComponent {
if (type === 'd') { if (type === 'd') {
return ( return (
<View style={[styles.iconContainer, { backgroundColor: color }]}> <Avatar text={name} baseUrl={baseUrl} size={40} borderRadius={20} />
<Text style={styles.avatarInitials}>{initials}</Text>
<CachedImage style={styles.avatar} source={{ uri: `${ baseUrl }/avatar/${ name }` }} />
</View>
); );
} }

View File

@ -0,0 +1,31 @@
import { CREATE_CHANNEL } from '../actions/actionsTypes';
const initialState = {
isFetching: false,
failure: false
};
export default function messages(state = initialState, action) {
switch (action.type) {
case CREATE_CHANNEL.REQUEST:
return { ...state,
error: undefined,
failure: false,
isFetching: true
};
case CREATE_CHANNEL.SUCCESS:
return { ...state,
isFetching: false,
failure: false,
result: action.data
};
case CREATE_CHANNEL.FAILURE:
return { ...state,
isFetching: false,
failure: true,
error: action.err
};
default:
return state;
}
}

View File

@ -5,8 +5,9 @@ import meteor from './connect';
import messages from './messages'; import messages from './messages';
import server from './server'; import server from './server';
import navigator from './navigator'; import navigator from './navigator';
import createChannel from './createChannel';
export default combineReducers({ export default combineReducers({
settings, login, meteor, messages, server, navigator settings, login, meteor, messages, server, navigator, createChannel
}); });

View File

@ -1,9 +1,36 @@
import { SERVER } from '../actions/actionsTypes'; 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) { 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: case SERVER.SELECT:
return action.server; return { ...state, server: action.server };
default: default:
return state; return state;
} }

View File

@ -1,39 +0,0 @@
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,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 { METEOR } from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import { connectSuccess, connectFailure } from '../actions/connect'; import { connectSuccess, connectFailure } from '../actions/connect';
const getServer = ({ server }) => server; const getServer = ({ server }) => server.server;
const connect = url => RocketChat.connect(url); const connect = url => RocketChat.connect(url);
@ -17,14 +17,11 @@ const test = function* test() {
yield put(connectFailure(err.status)); yield put(connectFailure(err.status));
} }
}; };
const watchConnect = function* watchConnect() { // const watchConnect = function* watchConnect() {
yield takeLatest(METEOR.REQUEST, test); // };
while (true) {
yield take(METEOR.DISCONNECT);
}
};
const root = function* root() { const root = function* root() {
yield fork(watchConnect); yield takeLatest(METEOR.REQUEST, test);
// yield fork(watchConnect);
// yield fork(auto); // yield fork(auto);
}; };
export default root; export default root;

View File

@ -0,0 +1,34 @@
import { delay } from 'redux-saga';
import { select, put, call, fork, take } from 'redux-saga/effects';
import { CREATE_CHANNEL, LOGIN } from '../actions/actionsTypes';
import { createChannelSuccess, createChannelFailure } from '../actions/createChannel';
import RocketChat from '../lib/rocketchat';
const create = function* create(data) {
return yield RocketChat.createChannel(data);
};
const get = function* get() {
while (true) {
try {
const { data } = yield take(CREATE_CHANNEL.REQUEST);
const auth = yield select(state => state.login.isAuthenticated);
if (!auth) {
yield take(LOGIN.SUCCESS);
}
const result = yield call(create, data);
yield put(createChannelSuccess(result));
select(({ navigator }) => navigator).dismissModal({
animationType: 'slide-down'
});
} catch (err) {
yield delay(2000);
yield put(createChannelFailure(err));
}
}
};
const getData = function* getData() {
yield fork(get);
};
export default getData;

View File

@ -6,11 +6,13 @@ import connect from './connect';
import rooms from './rooms'; import rooms from './rooms';
import messages from './messages'; import messages from './messages';
import selectServer from './selectServer'; import selectServer from './selectServer';
import createChannel from './createChannel';
import init from './init'; import init from './init';
const root = function* root() { const root = function* root() {
yield fork(init); yield fork(init);
yield take(types.APP.READY); yield take(types.APP.READY);
yield fork(createChannel);
yield fork(hello); yield fork(hello);
yield fork(rooms); yield fork(rooms);
yield fork(login); yield fork(login);

View File

@ -1,13 +1,23 @@
import { AsyncStorage } from 'react-native'; 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 * as actions from '../actions';
import { setServer } from '../actions/server'; import { setServer } from '../actions/server';
import { APP } from '../actions/actionsTypes';
const restore = function* restore() { const restore = function* restore() {
try { try {
yield take(APP.INIT);
const { navigator } = yield select(state => state);
const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer'); const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer');
yield put(actions.appReady({})); yield put(actions.appReady({}));
if (currentServer) { yield put(setServer(currentServer)); } if (currentServer) {
yield put(setServer(currentServer));
} else {
navigator.resetTo({
screen: 'ListServer',
animated: false
});
}
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }

View File

@ -1,12 +1,12 @@
import { AsyncStorage } from 'react-native'; import { AsyncStorage } from 'react-native';
import { take, put, call, takeEvery, fork, select, all, race } 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, logout } 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; 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 loginCall = args => (args.resume ? RocketChat.login(args) : RocketChat.loginWithPassword(args));
const getToken = function* getToken() { const getToken = function* getToken() {
@ -20,6 +20,8 @@ const getToken = function* getToken() {
} catch (e) { } catch (e) {
console.log('getTokenerr', e); console.log('getTokenerr', e);
} }
} else {
yield put(setToken());
} }
}; };
@ -27,10 +29,9 @@ const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() {
// do { // do {
try { try {
yield take(types.METEOR.SUCCESS); yield take(types.METEOR.SUCCESS);
yield call(getToken);
const { navigator } = yield select(state => state); const { navigator } = yield select(state => state);
navigator.resetTo({
screen: 'Rooms'
});
const user = yield select(getUser); const user = yield select(getUser);
if (user.token) { if (user.token) {
yield put(loginRequest({ resume: user.token })); yield put(loginRequest({ resume: user.token }));
@ -47,6 +48,9 @@ const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() {
// }); // });
// } // }
} }
navigator.resetTo({
screen: 'Rooms'
});
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
@ -66,8 +70,11 @@ const handleLoginRequest = function* handleLoginRequest() {
const response = yield call(loginCall, credentials); const response = yield call(loginCall, credentials);
yield put(loginSuccess(response)); yield put(loginSuccess(response));
} catch (err) { } catch (err) {
// console.log('login failed'); if (err.error === 403) {
yield put(loginFailure(err)); yield put(logout());
} else {
yield put(loginFailure(err));
}
} }
} }
}; };
@ -93,7 +100,6 @@ const handleLoginSubmit = function* handleLoginSubmit() {
}; };
const root = function* root() { const root = function* root() {
yield takeEvery(types.SERVER.CHANGED, getToken);
yield takeEvery(types.SERVER.CHANGED, handleLoginWhenServerChanges); yield takeEvery(types.SERVER.CHANGED, handleLoginWhenServerChanges);
yield fork(handleLoginRequest); yield fork(handleLoginRequest);
yield takeEvery(types.LOGIN.SUCCESS, saveToken); yield takeEvery(types.LOGIN.SUCCESS, saveToken);

View File

@ -1,17 +1,60 @@
import { put, takeEvery, call } from 'redux-saga/effects'; import { put, takeEvery, call, takeLatest, race, take } from 'redux-saga/effects';
import { delay } from 'redux-saga';
import { AsyncStorage } from 'react-native'; import { AsyncStorage } from 'react-native';
import { Navigation } from 'react-native-navigation';
import { SERVER } from '../actions/actionsTypes'; import { SERVER } from '../actions/actionsTypes';
import { connectRequest, disconnect } from '../actions/connect'; import { connectRequest, disconnect } from '../actions/connect';
import { changedServer } from '../actions/server'; import { changedServer, serverSuccess, serverFailure, serverRequest } from '../actions/server';
import RocketChat from '../lib/rocketchat';
import realm from '../lib/realm';
const validate = function* validate(server) {
return yield RocketChat.testServer(server);
};
const selectServer = function* selectServer({ server }) { const selectServer = function* selectServer({ server }) {
yield put(disconnect()); yield put(disconnect());
yield put(changedServer(server)); yield put(changedServer(server));
yield console.log('SERVER->', server);
yield call([AsyncStorage, 'setItem'], 'currentServer', server); yield call([AsyncStorage, 'setItem'], 'currentServer', server);
yield put(connectRequest(server)); yield put(connectRequest(server));
yield Navigation.dismissModal({
animationType: 'slide-down'
});
}; };
const validateServer = function* validateServer({ server }) {
try {
yield delay(1000);
yield call(validate, server);
yield put(serverSuccess());
} catch (e) {
console.log(e);
yield put(serverFailure(e));
}
};
const addServer = function* addServer({ server }) {
yield call(serverRequest, server);
const { error } = race({
error: take(SERVER.FAILURE),
success: take(SERVER.SUCCESS)
});
if (!error) {
realm.write(() => {
realm.create('servers', { id: server, current: false }, true);
});
Navigation.dismissModal({
animationType: 'slide-down'
});
}
};
const root = function* root() { const root = function* root() {
yield takeLatest(SERVER.REQUEST, validateServer);
yield takeEvery(SERVER.SELECT, selectServer); yield takeEvery(SERVER.SELECT, selectServer);
yield takeEvery(SERVER.ADD, addServer);
}; };
export default root; export default root;

View File

@ -1,6 +1,12 @@
import { AVATAR_COLORS } from '../constants/colors'; import { AVATAR_COLORS } from '../constants/colors';
export default function(username = '') { export default function(username = '') {
if (username === '') {
return {
initials: '',
colors: 'transparent'
};
}
const position = username.length % AVATAR_COLORS.length; const position = username.length % AVATAR_COLORS.length;
const color = AVATAR_COLORS[position]; const color = AVATAR_COLORS[position];

View File

@ -1,101 +1,83 @@
import ActionButton from 'react-native-action-button';
import Icon from 'react-native-vector-icons/Ionicons';
import React from 'react'; import React from 'react';
// import PropTypes from 'prop-types'; import { connect } from 'react-redux';
import { TextInput, StyleSheet, View, Text, Switch } from 'react-native'; import PropTypes from 'prop-types';
import RocketChat from '../lib/rocketchat'; import { TextInput, View, Text, Switch, TouchableOpacity, ScrollView } from 'react-native';
import { createChannelRequest } from '../actions/createChannel';
import styles from './Styles';
import KeyboardView from '../presentation/KeyboardView';
// import KeyboardView from '../components/KeyboardView'; @connect(state => ({
result: state.createChannel
}), dispatch => ({
createChannel: data => dispatch(createChannelRequest(data))
}))
const styles = StyleSheet.create({
view: {
flex: 1,
flexDirection: 'column',
padding: 24
},
input: {
// height: 50,
fontSize: 20,
borderColor: '#ffffff',
padding: 5,
borderWidth: 0,
backgroundColor: 'white'
},
field: {
flex: 1,
flexDirection: 'row',
alignItems: 'center'
},
field_label: {
flexGrow: 1
},
field_input: {
flexGrow: 1,
fontSize: 20,
borderColor: '#ffffff',
padding: 5,
borderWidth: 0,
backgroundColor: 'white'
},
actionButtonIcon: {
fontSize: 20,
height: 22,
color: 'white'
}
});
const mainIcon = <Icon name='md-checkmark' style={styles.actionButtonIcon} />;
export default class CreateChannelView extends React.Component { export default class CreateChannelView extends React.Component {
// static propTypes = {
// navigation: PropTypes.object.isRequired
// }
static navigationOptions = () => ({ static navigationOptions = () => ({
title: 'Create Channel' title: 'Create a New Channel'
}); });
static propTypes = {
createChannel: PropTypes.func.isRequired,
result: PropTypes.object.isRequired,
navigator: PropTypes.object.isRequired
}
constructor(props) { constructor(props) {
super(props); super(props);
this.default = {
this.state = {
channelName: '', channelName: '',
type: true type: true
}; };
this.state = this.default;
this.props.navigator.setTitle({
title: 'Create Channel'
});
// this.props.navigator.setSubTitle({
// subtitle: 'Channels are where your team communicate.'
// });
} }
submit() { submit() {
if (!this.state.channelName.trim() || this.props.result.isFetching) {
return;
}
const { channelName, users = [], type = true } = this.state; const { channelName, users = [], type = true } = this.state;
RocketChat.createChannel({ name: channelName, users, type }).then(res => Promise.reject(res)); this.props.createChannel({ name: channelName, users, type });
// { username: this.state.username }, this.state.password, () => {
// this.props.navigation.dispatch({ type: 'Navigation/BACK' });
// });
} }
render() { render() {
return ( return (
<View style={styles.view}> <KeyboardView style={[styles.view_white, { flex: 0, justifyContent: 'flex-start' }]}>
<View style={styles.field}> <ScrollView>
<TextInput <View style={styles.formContainer}>
style={styles.field_input} <Text style={styles.label_white}>Channel Name</Text>
onChangeText={channelName => this.setState({ channelName })} <TextInput
autoCorrect={false} value={this.state.channelName}
returnKeyType='done' style={styles.input_white}
autoCapitalize='none' onChangeText={channelName => this.setState({ channelName })}
autoFocus autoCorrect={false}
// onSubmitEditing={() => this.textInput.focus()} returnKeyType='done'
placeholder='Type the channel name here' autoCapitalize='none'
/> autoFocus
</View> // onSubmitEditing={() => this.textInput.focus()}
<View style={styles.field}> placeholder='Type the channel name here'
<Text style={styles.field_label}>{this.state.type ? 'Public' : 'Private'}</Text> />
<Switch {(this.props.result.failure && this.props.result.error.error === 'error-duplicate-channel-name') ? <Text style={[styles.label_white, { color: 'red', flexGrow: 1, paddingHorizontal: 0, marginBottom: 20 }]}>{this.props.result.error.reason}</Text> : null}
style={styles.field_input} <View style={[styles.view_white, { flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center', paddingHorizontal: 0 }]}>
value={this.state.type} <Switch
onValueChange={type => this.setState({ type })} style={[{ flexGrow: 0, flexShrink: 1 }]}
/> value={this.state.type}
</View> onValueChange={type => this.setState({ type })}
{this.state.channelName.length > 0 ? />
<ActionButton buttonColor='green' icon={mainIcon} onPress={() => this.submit()} /> : null } <Text style={[styles.label_white, { flexGrow: 1, paddingHorizontal: 10 }]}>{this.state.type ? 'Public' : 'Private'}</Text>
</View> </View>
<Text style={[styles.label_white, { color: '#9ea2a8', flexGrow: 1, paddingHorizontal: 0, marginBottom: 20 }]}>{this.state.type ? 'Everyone can access this channel' : 'Just invited people can access this channel'}</Text>
<TouchableOpacity onPress={() => this.submit()} style={[styles.buttonContainer_white, { backgroundColor: (this.state.channelName.length === 0 || this.props.result.isFetching) ? '#e1e5e8' : '#1d74f5' }]}>
<Text style={styles.button_white}> { this.props.result.isFetching ? 'LOADING' : 'CREATE' }!</Text>
</TouchableOpacity>
</View>
</ScrollView>
</KeyboardView>
); );
} }
} }

105
app/views/Styles.js Normal file
View File

@ -0,0 +1,105 @@
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
view: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
padding: 20,
alignItems: 'stretch',
backgroundColor: '#2f343d'
},
view_white: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
padding: 20,
alignItems: 'stretch',
backgroundColor: '#fff'
},
logoContainer: {
flex: 1,
alignItems: 'center',
flexGrow: 1,
justifyContent: 'center'
},
logo: {
width: 150,
// backgroundColor: 'red'
// height: 150,
resizeMode: 'contain'
},
formContainer: {
// marginBottom: 20
},
label: {
lineHeight: 40,
height: 40,
fontSize: 16,
marginBottom: 5,
color: 'white'
},
label_white: {
lineHeight: 40,
height: 40,
fontSize: 16,
marginBottom: 5,
color: '#2f343d'
},
input: {
height: 45,
marginBottom: 20,
borderRadius: 2,
// padding: 14,
paddingHorizontal: 10,
borderWidth: 2,
backgroundColor: 'rgba(255,255,255,.2)',
borderColor: '#e1e5e8',
color: 'white'
},
input_white: {
height: 45,
marginBottom: 20,
borderRadius: 2,
// padding: 14,
paddingHorizontal: 10,
borderWidth: 2,
backgroundColor: 'white',
borderColor: 'rgba(0,0,0,.15)',
color: 'black'
},
buttonContainer: {
paddingVertical: 15,
backgroundColor: '#414852',
marginBottom: 20
},
buttonContainer_white: {
paddingVertical: 15,
backgroundColor: '#1d74f5',
marginBottom: 20
},
button: {
textAlign: 'center',
color: 'white',
borderRadius: 2,
fontWeight: '700'
},
button_white: {
textAlign: 'center',
color: 'white',
borderRadius: 2,
fontWeight: '700'
},
error: {
textAlign: 'center',
color: 'red',
paddingTop: 5
},
loading: {
flex: 1,
position: 'absolute',
backgroundColor: 'rgba(255,255,255,.2)',
left: 0,
top: 0
}
});

View File

@ -3,7 +3,7 @@ import React from 'react';
import Spinner from 'react-native-loading-spinner-overlay'; import Spinner from 'react-native-loading-spinner-overlay';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Keyboard, Text, TextInput, StyleSheet, View, Image, TouchableOpacity } from 'react-native'; import { Keyboard, Text, TextInput, 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';
@ -11,63 +11,7 @@ import * as loginActions from '../actions/login';
import KeyboardView from '../presentation/KeyboardView'; import KeyboardView from '../presentation/KeyboardView';
// import { Keyboard } from 'react-native' // import { Keyboard } from 'react-native'
const styles = StyleSheet.create({ import styles from './Styles';
view: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
padding: 20,
alignItems: 'stretch',
backgroundColor: '#2f343d'
},
logoContainer: {
flex: 1,
alignItems: 'center',
flexGrow: 1,
justifyContent: 'center'
},
logo: {
width: 150,
// backgroundColor: 'red'
// height: 150,
resizeMode: 'contain'
},
formContainer: {
// marginBottom: 20
},
input: {
height: 40,
marginBottom: 20,
borderRadius: 2,
paddingHorizontal: 10,
borderWidth: 0,
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: {
textAlign: 'center',
color: 'red',
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 = {
@ -90,16 +34,33 @@ class LoginView extends React.Component {
username: '', username: '',
password: '' password: ''
}; };
}
this.props.navigator.setTitle({ componentWillReceiveProps() {
const { navigator } = this.props;
navigator.setTitle({
title: 'Login' title: 'Login'
}); });
} navigator.setSubTitle({
subtitle: this.props.server
componentWillReceiveProps(nextProps) {
this.props.navigator.setSubTitle({
subtitle: nextProps.server
}); });
navigator.setButtons({
rightButtons: [{
id: 'close',
title: 'Cancel'
}]
});
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
}
onNavigatorEvent = (event) => {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'close') {
this.props.navigator.resetTo({
screen: 'ListServer',
animated: false
});
}
}
} }
submit = () => { submit = () => {
const { username, password, code } = this.state; const { username, password, code } = this.state;
@ -175,7 +136,7 @@ class LoginView extends React.Component {
function mapStateToProps(state) { function mapStateToProps(state) {
// console.log(Object.keys(state)); // console.log(Object.keys(state));
return { return {
server: state.server, server: state.server.server,
Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder, Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder,
Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder, Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder,
login: state.login login: state.login

View File

@ -46,7 +46,7 @@ const styles = StyleSheet.create({
@connect(state => ({ @connect(state => ({
server: state.server, server: state.server.server,
Site_Url: state.settings.Site_Url, Site_Url: state.settings.Site_Url,
Message_TimeFormat: state.settings.Message_TimeFormat, Message_TimeFormat: state.settings.Message_TimeFormat,
loading: state.messages.isFetching loading: state.messages.isFetching

View File

@ -3,6 +3,7 @@ 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 Icon from 'react-native-vector-icons/Ionicons';
import { View, StyleSheet, TextInput, Platform } 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';
@ -55,10 +56,10 @@ const styles = StyleSheet.create({
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }); const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
@connect(state => ({ @connect(state => ({
server: state.server, server: state.server.server,
login: state.login, login: state.login,
Site_Url: state.settings.Site_Url, Site_Url: state.settings.Site_Url,
canShowList: state.login.token.length || state.login.user.token canShowList: state.login.token || state.login.user.token
}), dispatch => ({ }), dispatch => ({
login: () => dispatch(actions.login()), login: () => dispatch(actions.login()),
connect: () => dispatch(server.connectRequest()) connect: () => dispatch(server.connectRequest())
@ -219,7 +220,16 @@ export default class RoomsListView extends React.Component {
navigateToRoom({ sid: id }); navigateToRoom({ sid: id });
clearSearch(); clearSearch();
} }
_createChannel = () => {
Navigation.showModal({
screen: 'CreateChannel',
title: 'Create a New Channel',
passProps: {},
navigatorStyle: {},
navigatorButtons: {},
animationType: 'slide-up'
});
}
renderSearchBar = () => ( renderSearchBar = () => (
<View style={styles.searchBoxView}> <View style={styles.searchBoxView}>
<TextInput <TextInput
@ -256,7 +266,11 @@ export default class RoomsListView extends React.Component {
/> />
) )
renderCreateButtons = () => ( renderCreateButtons = () => (
<ActionButton buttonColor='rgba(231,76,60,1)' />); <ActionButton buttonColor='rgba(231,76,60,1)'>
<ActionButton.Item buttonColor='#9b59b6' title='Create Channel' onPress={() => { this._createChannel(); }} >
<Icon name='md-chatbubbles' style={styles.actionButtonIcon} />
</ActionButton.Item>
</ActionButton>);
render= () => ( render= () => (
<View style={styles.container}> <View style={styles.container}>
<Banner /> <Banner />

View File

@ -65,13 +65,15 @@ const zeroconf = new Zeroconf();
@connect(state => ({ @connect(state => ({
server: state.server server: state.server.server,
login: state.login
}), dispatch => ({ }), dispatch => ({
selectServer: server => dispatch(setServer(server)) selectServer: server => dispatch(setServer(server))
})) }))
export default class ListServerView extends React.Component { export default class ListServerView extends React.Component {
static propTypes = { static propTypes = {
navigator: PropTypes.object.isRequired, navigator: PropTypes.object.isRequired,
login: PropTypes.object.isRequired,
selectServer: PropTypes.func.isRequired, selectServer: PropTypes.func.isRequired,
actions: PropTypes.object, actions: PropTypes.object,
server: PropTypes.string server: PropTypes.string
@ -92,7 +94,7 @@ export default class ListServerView extends React.Component {
id: 'add', id: 'add',
title: 'Add' title: 'Add'
}], }],
leftButtons: props.server && Platform.select({ leftButtons: props.login.isAuthenticated && props.server && Platform.select({
ios: [{ ios: [{
id: 'close', id: 'close',
title: 'Close' title: 'Close'

View File

@ -2,9 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Navigation } from 'react-native-navigation'; import { Navigation } from 'react-native-navigation';
import { Text, TextInput, View, StyleSheet } from 'react-native'; import { Text, TextInput, View, StyleSheet } from 'react-native';
import _ from 'underscore'; import { connect } from 'react-redux';
import realm from '../lib/realm'; import { serverRequest, addServer } from '../actions/server';
import KeyboardView from '../presentation/KeyboardView'; import KeyboardView from '../presentation/KeyboardView';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -47,10 +46,20 @@ const styles = StyleSheet.create({
flexGrow: 1 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 { export default class NewServerView extends React.Component {
static propTypes = { static propTypes = {
navigator: PropTypes.object.isRequired navigator: PropTypes.object.isRequired,
validateServer: PropTypes.func.isRequired,
addServer: PropTypes.func.isRequired,
validating: PropTypes.bool.isRequired,
validInstance: PropTypes.bool.isRequired
} }
static navigationOptions = () => ({ static navigationOptions = () => ({
@ -66,37 +75,11 @@ export default class NewServerView extends React.Component {
}; };
this.submit = () => { this.submit = () => {
let url = this.state.text.trim(); this.props.addServer(this.completeUrl(this.state.text.trim() || this.state.defaultServer));
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();
});
}; };
} }
componentDidMount() { componentDidMount() {
this._mounted = true;
this.props.navigator.setTitle({ this.props.navigator.setTitle({
title: 'New server' title: 'New server'
}); });
@ -111,11 +94,6 @@ export default class NewServerView extends React.Component {
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
} }
componentWillUnmount() {
this._mounted = false;
}
onNavigatorEvent = (event) => { onNavigatorEvent = (event) => {
if (event.type === 'NavBarButtonPress') { if (event.type === 'NavBarButtonPress') {
if (event.id === 'close') { if (event.id === 'close') {
@ -128,62 +106,8 @@ export default class NewServerView extends React.Component {
onChangeText = (text) => { onChangeText = (text) => {
this.setState({ text }); this.setState({ text });
this.props.validateServer(this.completeUrl(text));
this.validateServerDebounced(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) => { completeUrl = (url) => {
url = url.trim(); url = url.trim();
@ -203,7 +127,10 @@ export default class NewServerView extends React.Component {
} }
renderValidation = () => { renderValidation = () => {
if (this.state.validating) { if (!this.state.text.trim()) {
return null;
}
if (this.props.validating) {
return ( return (
<Text style={[styles.validateText, styles.validatingText]}> <Text style={[styles.validateText, styles.validatingText]}>
Validating {this.state.url} ... Validating {this.state.url} ...
@ -211,21 +138,18 @@ export default class NewServerView extends React.Component {
); );
} }
if (this.state.validInstance) { if (this.props.validInstance) {
return ( return (
<Text style={[styles.validateText, styles.validText]}> <Text style={[styles.validateText, styles.validText]}>
{this.state.url} is a valid Rocket.Chat instance {this.state.url} is a valid Rocket.Chat instance
</Text> </Text>
); );
} }
return (
if (this.state.validInstance === false) { <Text style={[styles.validateText, styles.invalidText]}>
return ( {this.state.url} is not a valid Rocket.Chat instance
<Text style={[styles.validateText, styles.invalidText]}> </Text>
{this.state.url} is not a valid Rocket.Chat instance );
</Text>
);
}
} }
render() { render() {

1130
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,22 +22,16 @@
"react-emojione": "^3.1.10", "react-emojione": "^3.1.10",
"react-native": "0.46.1", "react-native": "0.46.1",
"react-native-action-button": "^2.7.2", "react-native-action-button": "^2.7.2",
"react-native-auto-grow-textinput": "^1.2.0", "react-native-animatable": "^1.2.3",
"react-native-autogrow-input": "^0.2.1",
"react-native-autogrow-textinput": "^4.1.0",
"react-native-card-view": "0.0.3", "react-native-card-view": "0.0.3",
"react-native-console-time-polyfill": "0.0.6",
"react-native-easy-markdown": "git+https://github.com/lappalj4/react-native-easy-markdown.git", "react-native-easy-markdown": "git+https://github.com/lappalj4/react-native-easy-markdown.git",
"react-native-fetch-blob": "^0.10.8", "react-native-fetch-blob": "^0.10.8",
"react-native-form-generator": "^0.9.9", "react-native-image-picker": "^0.26.4",
"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.2",
"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",
"react-native-router-flux": "^4.0.0-beta.18",
"react-native-svg": "^5.4.1", "react-native-svg": "^5.4.1",
"react-native-svg-image": "^1.1.4", "react-native-svg-image": "^1.1.4",
"react-native-vector-icons": "^4.3.0", "react-native-vector-icons": "^4.3.0",

View File

@ -0,0 +1,13 @@
import React from 'react';
import { ScrollView } from 'react-native';
import Avatar from '../../app/components/avatar';
export default (
<ScrollView>
<Avatar text={'test'} />
<Avatar size={40} text={'aa'} />
<Avatar size={30} text={'bb'} />
<Avatar text={'test'} borderRadius={2} />
</ScrollView>
);

View File

@ -7,7 +7,9 @@ import { storiesOf } from '@storybook/react-native';
// import { linkTo } from '@storybook/addon-links'; // import { linkTo } from '@storybook/addon-links';
import DirectMessage from './Channels/DirectMessage'; import DirectMessage from './Channels/DirectMessage';
import Avatar from './Avatar';
storiesOf('Avatar', module).add('avatar', () => Avatar);
storiesOf('Channel Cell', module).add('Direct Messages', () => DirectMessage); storiesOf('Channel Cell', module).add('Direct Messages', () => DirectMessage);
// storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} />); // storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} />);