Commit 3975f50f authored by Wietse Kuipers's avatar Wietse Kuipers
Browse files

Merge branch 'style/navigation' into 'master'

Style/navigation

See merge request !13
parents 3c5c6b32 da1a78c5
......@@ -127,6 +127,7 @@ android {
}
dependencies {
compile project(':react-native-vector-icons')
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:23.0.1"
compile "com.facebook.react:react-native:+" // From node_modules
......
......@@ -3,13 +3,19 @@ import * as types from './actionTypes';
const USERNAMEKEY = '@MyStore:username';
const TOKENKEY = '@MyStore:token';
const DISPLAYNAMEKEY = '@MyStore:displayName';
const PHOTOKEY = '@MyStore:photo';
export function loginSuccess(username, token) {
const defaultAvatar = 'http://localhost:8000/static/members/images/default-avatar.jpg';
export function loginSuccess(username, token, displayName, photo) {
return {
type: types.LOGINSUCCESS,
loginState: 'loggedIn',
username,
token,
displayName,
photo,
};
}
......@@ -27,7 +33,37 @@ export function loginFailure() {
};
}
export function login(username, password) {
function getUserInfo(token) {
const data = {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Token ${token}`,
},
};
return fetch('http://localhost:8000/api/members/info/', data)
.then(
response => response.json())
.then(
(responseJson) => {
const avatar = responseJson.photo === null ? defaultAvatar : responseJson.photo;
return {
photo: avatar,
displayName: responseJson.display_name,
};
},
)
.catch(
() => ({
photo: defaultAvatar,
displayName: 'Naamloos',
}),
);
}
export function login(user, pass) {
return (dispatch) => {
dispatch(loginProgress());
const data = {
......@@ -37,8 +73,8 @@ export function login(username, password) {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
password,
username: user,
password: pass,
}),
};
return fetch('http://localhost:8000/api/token-auth/', data)
......@@ -47,10 +83,20 @@ export function login(username, password) {
.then(
(responseJson) => {
if (responseJson.token) {
return AsyncStorage.multiSet([[USERNAMEKEY, username], [TOKENKEY, responseJson.token]])
const token = responseJson.token;
return getUserInfo(token)
.then(
() => dispatch(loginSuccess(username, responseJson.token)),
);
userInfo => AsyncStorage.multiSet([
[USERNAMEKEY, user],
[TOKENKEY, token],
[DISPLAYNAMEKEY, userInfo.displayName],
[PHOTOKEY, userInfo.photo],
])
.then(() => dispatch(
loginSuccess(
user, token, userInfo.displayName, userInfo.photo,
)),
));
}
return dispatch(loginFailure());
})
......
......@@ -14,6 +14,9 @@ const store = createStoreWithMiddleware(reducer);
const USERNAMEKEY = '@MyStore:username';
const TOKENKEY = '@MyStore:token';
const DISPLAYNAMEKEY = '@MyStore:displayName';
const PHOTOKEY = '@MyStore:photo';
const pairsToObject = (obj, pair) => {
const obj2 = { ...obj };
obj2[pair[0]] = pair[1];
......@@ -22,15 +25,17 @@ const pairsToObject = (obj, pair) => {
class Main extends Component {
componentDidMount() {
AsyncStorage.multiGet([USERNAMEKEY, TOKENKEY])
AsyncStorage.multiGet([USERNAMEKEY, TOKENKEY, DISPLAYNAMEKEY, PHOTOKEY])
.then(
(result) => {
const values = result.reduce(pairsToObject, {});
const username = values[USERNAMEKEY];
const token = values[TOKENKEY];
const displayName = values[DISPLAYNAMEKEY];
const photo = values[DISPLAYNAMEKEY];
if (username !== null && token !== null) {
store.dispatch(loginSuccess(username, token));
store.dispatch(loginSuccess(username, token, displayName, photo));
}
});
}
......
......@@ -26,6 +26,7 @@ const Calendar = (props) => {
<ListView
dataSource={dataSource}
renderRow={rowData => rowData}
enableEmptySections
/>
</View>
);
......
import React from 'react';
import { Text, View } from 'react-native';
import { Text, View, Image, TouchableHighlight } from 'react-native';
import { connect } from 'react-redux';
import styles from '../style';
import Icon from 'react-native-vector-icons/MaterialIcons';
import { colors } from '../style';
import styles from './style/sidebar';
import * as actions from '../actions/navigation';
import * as loginActions from '../actions/login';
const Sidebar = props =>
<View
style={styles.sidebar}
>
<Text style={styles.header}>MENU</Text>
<Text onPress={() => props.navigate('welcome')} style={styles.button} >Welcome</Text>
<Text onPress={() => props.navigate('eventList')} style={styles.button} >Calendar</Text>
</View>
;
const background = require('../img/huygens.jpg');
const Sidebar = (props) => {
const buttons = [
{
onPress: () => props.navigate('welcome'),
iconName: 'home',
text: 'Welkom',
style: {},
scene: 'welcome',
},
{
onPress: () => props.navigate('eventList'),
iconName: 'event',
text: 'Agenda',
style: {},
scene: 'eventList',
},
{
onPress: props.logout,
iconName: 'lock',
text: 'Uitloggen',
style: {
borderTopColor: colors.lightGray,
borderTopWidth: 1,
},
scene: 'logout',
},
];
return (
<View
style={styles.sidebar}
>
<TouchableHighlight
onPress={() => props.navigate('profile')}
style={styles.headerButton}
>
<Image
source={background}
style={styles.headerImage}
resizeMode="cover"
>
<Image
source={{ uri: props.photo }}
style={styles.profileImage}
resizeMode="cover"
/>
<Text style={styles.nameField}>{props.displayName}</Text>
</Image>
</TouchableHighlight>
<View style={styles.buttonList}>
{buttons.map(button => (
<Icon.Button
onPress={button.onPress}
name={button.iconName}
borderRadius={0}
backgroundColor={colors.white}
color={props.currentScene === button.scene ?
colors.magenta : colors.textColour}
size={28}
iconStyle={styles.buttonIcon}
style={[styles.buttonText, button.style]}
key={button.scene}
>
{button.text}
</Icon.Button>
))}
</View>
</View>
);
};
Sidebar.propTypes = {
currentScene: React.PropTypes.string.isRequired,
displayName: React.PropTypes.string.isRequired,
photo: React.PropTypes.string.isRequired,
navigate: React.PropTypes.func.isRequired,
logout: React.PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
currentScene: state.navigation.currentScene,
displayName: state.session.displayName,
photo: state.session.photo,
});
const mapDispatchToProps = dispatch => ({
navigate: scene => dispatch(actions.navigate(scene)),
logout: () => dispatch(loginActions.logout()),
});
export default connect(() => ({}), mapDispatchToProps)(Sidebar);
export default connect(mapStateToProps, mapDispatchToProps)(Sidebar);
import React from 'react';
import { Text, View, StatusBar, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import Drawer from 'react-native-drawer';
import Icon from 'react-native-vector-icons/MaterialIcons';
import Login from './Login';
import Welcome from './Welcome';
import Sidebar from './Sidebar';
......@@ -8,16 +10,8 @@ import Event from './Event';
import Calendar from './Calendar';
import * as actions from '../actions/navigation';
const mapStateToProps = state => ({
currentScene: state.navigation.currentScene,
loggedIn: state.navigation.loggedIn,
drawerOpen: state.navigation.drawerOpen,
});
const mapDispatchToProps = dispatch => ({
updateDrawer: isOpen => dispatch(actions.updateDrawer(isOpen)),
});
import styles from './style/navigator';
import { colors } from '../style';
const sceneToComponent = (scene) => {
switch (scene) {
......@@ -32,33 +26,88 @@ const sceneToComponent = (scene) => {
}
};
const sceneToTitle = (scene) => {
switch (scene) {
case 'welcome':
return 'Welkom';
case 'event':
return 'Evenement';
case 'eventList':
return 'Agenda';
default:
return 'ThaliApp';
}
};
const ReduxNavigator = (props) => {
const { currentScene, loggedIn, drawerOpen, updateDrawer } = props;
if (loggedIn) {
return (<Drawer
type="displace"
content={<Sidebar />}
openDrawerOffset={0.4}
openDrawerOffset={0.2}
panOpenMask={0.2}
panCloseMask={0.2}
panThreshold={0.3}
tweenHandler={ratio => ({ main: { opacity: (2 - ratio) / 2 } })}
styles={{
mainOverlay: {
backgroundColor: colors.black,
opacity: 0,
},
}}
tweenHandler={ratio => ({ mainOverlay: { opacity: ratio * 0.75 } })}
open={drawerOpen}
onOpen={() => updateDrawer(true)}
onClose={() => updateDrawer(false)}
tapToClose
>
<View style={styles.statusBar}>
<StatusBar backgroundColor={colors.darkMagenta} barStyle="light-content" />
</View>
<View style={styles.appBar}>
<TouchableOpacity
onPress={props.isFirstScene ? () => props.updateDrawer(!props.drawerOpen) : props.back}
>
<Icon
name={props.isFirstScene ? 'menu' : 'arrow-back'}
onClick={props.back}
style={styles.icon}
size={20}
/>
</TouchableOpacity>
<Text style={styles.title}>{sceneToTitle(currentScene)}</Text>
</View>
{sceneToComponent(currentScene)}
</Drawer>);
}
return <Login />;
return (
<View>
<View style={styles.statusBar}>
<StatusBar backgroundColor={colors.darkMagenta} barStyle="light-content" />
</View>
<Login />
</View>);
};
ReduxNavigator.propTypes = {
currentScene: React.PropTypes.string.isRequired,
loggedIn: React.PropTypes.bool.isRequired,
drawerOpen: React.PropTypes.bool.isRequired,
isFirstScene: React.PropTypes.bool.isRequired,
updateDrawer: React.PropTypes.func.isRequired,
back: React.PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
currentScene: state.navigation.currentScene,
loggedIn: state.navigation.loggedIn,
drawerOpen: state.navigation.drawerOpen,
isFirstScene: state.navigation.previousScenes.length === 0,
});
const mapDispatchToProps = dispatch => ({
updateDrawer: isOpen => dispatch(actions.updateDrawer(isOpen)),
back: () => dispatch(actions.back()),
});
export default connect(mapStateToProps, mapDispatchToProps)(ReduxNavigator);
import { Platform, StyleSheet } from 'react-native';
import { colors } from '../../style';
const STATUSBAR_HEIGHT = Platform.OS === 'ios' ? 20 : 0;
const APPBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 56;
const styles = StyleSheet.create({
statusBar: {
height: STATUSBAR_HEIGHT,
backgroundColor: colors.darkMagenta,
},
appBar: {
backgroundColor: colors.magenta,
height: APPBAR_HEIGHT,
justifyContent: 'flex-start',
alignItems: 'center',
flexWrap: 'wrap',
flexDirection: 'row',
},
title: {
color: colors.white,
fontSize: 24,
},
icon: {
paddingLeft: 20,
paddingRight: 50,
color: colors.white,
},
});
export default styles;
import { StyleSheet } from 'react-native';
import { colors } from '../../style';
const styles = StyleSheet.create({
sidebar: {
backgroundColor: colors.white,
flex: 1,
alignItems: 'stretch',
},
headerButton: {
flex: 1,
},
headerImage: {
flex: 1,
width: null,
height: null,
justifyContent: 'space-around',
paddingLeft: 10,
},
profileImage: {
width: 100,
height: 100,
borderRadius: 50,
},
nameField: {
color: colors.white,
fontSize: 24,
},
buttonList: {
flex: 3,
},
buttonIcon: {
marginRight: 30,
width: 28,
textAlign: 'center',
},
buttonText: {
padding: 20,
},
});
export default styles;
......@@ -4,12 +4,21 @@ const initialState = {
loginState: '',
token: '',
username: '',
displayName: '',
photo: '',
};
export default function session(state = initialState, action = {}) {
switch (action.type) {
case types.LOGINSUCCESS:
return { ...state, loginState: 'success', username: action.username, token: action.token };
return {
...state,
loginState: 'success',
username: action.username,
token: action.token,
displayName: action.displayName,
photo: action.photo,
};
case types.LOGINFAILURE:
return { ...state, loginState: 'failure' };
case types.LOGINPROGRESS:
......
import { StyleSheet } from 'react-native';
const magenta = '#E62272';
const white = '#FFFFFF';
const black = '#000000';
const styles = StyleSheet.create({
sidebar: {
flex: 1,
backgroundColor: white,
},
header: {
backgroundColor: magenta,
color: white,
fontWeight: 'bold',
padding: 20,
},
button: {
backgroundColor: white,
color: black,
paddingLeft: 10,
paddingTop: 20,
paddingBottom: 20,
borderBottomWidth: 1,
},
});
export default styles;
export const colors = {
magenta: '#E62272',
darkMagenta: '#C2185B',
white: '#FFFFFF',
black: '#000000',
lightGray: '#BBBBBB',
textColour: '#313131',
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment