Pārlūkot izejas kodu

Inicio version plataforma con login+cursadas

David 9 gadi atpakaļ
vecāks
revīzija
3ed666e530

+ 2 - 2
index.android.js

@@ -1,4 +1,4 @@
 import { AppRegistry } from 'react-native';
-import LiveVideo from './src/App'
+import App from './src/App'
 
-AppRegistry.registerComponent('video', () => LiveVideo);
+AppRegistry.registerComponent('video', () => App);

+ 2 - 2
index.ios.js

@@ -1,4 +1,4 @@
 import { AppRegistry } from 'react-native';
-import LiveVideo from './src/App'
+import App from './src/App'
 
-AppRegistry.registerComponent('video', () => LiveVideo);
+AppRegistry.registerComponent('video', () => App);

+ 6 - 1
package.json

@@ -7,9 +7,14 @@
     "test": "jest"
   },
   "dependencies": {
+    "lodash": "^4.17.2",
     "react": "15.3.2",
     "react-native": "0.37.0",
-    "react-native-video": "^0.9.0"
+    "react-native-router-flux": "^3.37.0",
+    "react-native-video": "^0.9.0",
+    "react-redux": "^4.4.6",
+    "redux": "^3.6.0",
+    "redux-thunk": "^2.1.0"
   },
   "jest": {
     "preset": "jest-react-native"

+ 12 - 85
src/App.js

@@ -1,94 +1,21 @@
 import React, { Component } from 'react';
-import {
-  StyleSheet,
-  Text,
-  View,
-  ScrollView
-} from 'react-native';
+import { Provider } from 'react-redux';
+import { createStore, applyMiddleware } from 'redux';
+import ReduxThunk from 'redux-thunk';
 
-import Video from 'react-native-video';
-import Canvas from './components/Canvas';
-import Socket from './components/Socket';
-import Chat from './components/Chat';
-
-class LiveVideo extends Component {
-  state = { pdf: "EBA", diapo: 1, messages: [] };
-  getTime() {
-	var d = new Date();
-	var curr_hour = d.getHours();
-	var curr_min = d.getMinutes();
-	if (curr_min < 10)
-		curr_min='0'+curr_min;
-	return curr_hour+":"+curr_min;
-  }
-
-  onMessage(user,text) {
-	  this.setState({messages: [ ...this.state.messages, { time: this.getTime(), user, text }] });
-  }
-
-  onDiapoChanged(pdf, diapo) {
-	console.log("Diapo changed!", pdf, diapo);
-	this.setState({ pdf, diapo });
-  }
+import Router from './Router';
+import LiveVideo from './components/LiveVideo';
+import reducers from './reducers';
 
+class App extends Component {
   render() {
+    const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
     return (
-	  <View style={{flex:1}}>
-		  <ScrollView
-			horizontal={true}
-			pagingEnabled={true}
-		  >
-			  <View style={styles.videoPage}>
-			      <View style={styles.video}>
-			        <Video source={{ uri: "https://plataforma.especificosba.com.ar/hls/movie.m3u8" }}
-						ref={(ref) => { this.player = ref }}
-						playInBackground={true}
-						volume={0.2}
-						resizeMode={"contain"}
-						style={{position:'absolute', top: 0, left: 0, right: 0, bottom:0}}
-					/>
-			      </View>
-			      <View style={styles.imageContainer}>
-					<Canvas pdf={this.state.pdf} diapo={this.state.diapo} style={{flex:1}}/>
-			      </View>
-	
-			  </View>
-	
-			  <View style={{flex:1,width:700}}>
-					<Chat messages={this.state.messages} />
-   			  </View>
-	
-		  </ScrollView>
-		  <Socket onDiapoChanged={this.onDiapoChanged.bind(this)} onMessage={this.onMessage.bind(this)} />
-	  </View>
-	
+        <Provider store={store}>
+  	  	  <Router />
+        </Provider>
     );
   }
 }
 
-const styles = StyleSheet.create({
-  videoPage: {
-	  flexDirection: 'row',
-	  flex: 1
-  },
-  video: {
-	width: 320,
-    justifyContent: 'center',
-    alignItems: 'center',
-    backgroundColor: '#F5FCFF',
-	margin: 5
-	
-  },
-  imageContainer: {
-    width: 320,
-    margin: 5
-  },
-  instructions: {
-    textAlign: 'center',
-    color: '#333333',
-    marginBottom: 5,
-  },
-});
-
-
-export default LiveVideo;
+export default App;

+ 22 - 0
src/Router.js

@@ -0,0 +1,22 @@
+import React from 'react';
+import { Scene, Router, Actions } from 'react-native-router-flux';
+import LoginForm from './components/LoginForm';
+import LiveVideo from './components/LiveVideo';
+import ListaCursadas from './components/ListaCursadas';
+
+const RouterComponent = () => {
+	return (
+		<Router sceneStyle={{ paddingTop: 55 }}>
+			<Scene key="auth">
+				<Scene key="login" component={LoginForm} title="Please Login" initial />
+			</Scene>
+
+			<Scene key="main">
+				<Scene key="ListaCursadas" component={ListaCursadas} />
+				<Scene key="Live" component={LiveVideo} />
+			</Scene>
+		</Router>
+	);
+};
+
+export default RouterComponent;

+ 59 - 0
src/actions/AuthActions.js

@@ -0,0 +1,59 @@
+import { Actions } from 'react-native-router-flux';
+import {
+  EMAIL_CHANGED,
+  PASSWORD_CHANGED,
+  LOGIN_USER_SUCCESS,
+  LOGIN_USER_FAIL,
+  LOGIN_USER
+} from './types';
+
+export const emailChanged = (text) => {
+  return {
+    type: EMAIL_CHANGED,
+    payload: text
+  };
+};
+
+export const passwordChanged = (text) => {
+  return {
+    type: PASSWORD_CHANGED,
+    payload: text
+  };
+};
+
+export const loginUser = ({ email, password }) => {
+  return (dispatch) => {
+    dispatch({ type: LOGIN_USER })
+    fetch('https://plataforma.especificosba.com.ar/back/login.php', {
+      method: 'POST',
+      headers: {
+          'Accept': 'application/json',
+          'Content-Type': 'application/json',
+      },
+      body: JSON.stringify({
+        user: email,
+        pass: password,
+      })
+    })
+    .then( (response) => response.json() )
+      .then( (responseJson) => {
+        console.log(responseJson);
+        loginUserSuccess(dispatch, responseJson);
+      });
+  //FIXME: Catch
+  };
+
+
+};
+
+const loginUserFail = (dispatch) => {
+  dispatch({ type: LOGIN_USER_FAIL });
+};
+
+const loginUserSuccess = (dispatch, user) => {
+  dispatch({
+    type: LOGIN_USER_SUCCESS,
+    payload: user
+  });
+  Actions.main();
+};

+ 30 - 0
src/actions/CursadaActions.js

@@ -0,0 +1,30 @@
+import { Actions } from 'react-native-router-flux';
+import {
+	CURSADA_FETCH_SUCCESS
+} from './types';
+
+export const cursadasFetch = () => {
+	//const { currentUser } = firebase.auth();
+  console.log("Fetching cursadas?");
+	return (dispatch) => {
+    console.log("Fetching cursadas!");
+    fetch('https://plataforma.especificosba.com.ar/back/listaCursos.php', {
+      method: 'POST',
+      headers: {
+          'Accept': 'application/json',
+          'Content-Type': 'application/json',
+      },
+      body: JSON.stringify({
+        cursadas: 1
+      })
+    })
+    .then( (response) => response.json() )
+      .then( (responseJson) => {
+        console.log(responseJson);
+        dispatch({
+          type: CURSADA_FETCH_SUCCESS,
+          payload: responseJson
+        });
+      });
+	};
+};

+ 2 - 0
src/actions/index.js

@@ -0,0 +1,2 @@
+export * from './AuthActions';
+export * from './CursadaActions';

+ 6 - 0
src/actions/types.js

@@ -0,0 +1,6 @@
+export const EMAIL_CHANGED = "email_changed";
+export const PASSWORD_CHANGED = "password_changed";
+export const LOGIN_USER_SUCCESS = "login_user_success";
+export const LOGIN_USER_FAIL = "login_user_fail";
+export const LOGIN_USER = "login_user";
+export const CURSADA_FETCH_SUCCESS = "cursada_fetch_success";

+ 51 - 0
src/components/ItemCursada.js

@@ -0,0 +1,51 @@
+import React, { Component } from 'react';
+import { Text, ScrollView } from 'react-native';
+import { Actions } from 'react-native-router-flux';
+import { Button, Card, CardSection } from './common';
+
+class ItemCursada extends Component {
+  onRowPress(){
+    console.log(this.props.data); //_id
+    //Actions.cursadaView( { _id: this.props.data._id } );
+  }
+
+  render() {
+    const { data } = this.props;
+    return (
+      <ScrollView>
+        <Card>
+          <CardSection>
+            <Text style={styles.titleStyle}>
+              {data.titulo}
+            </Text>
+          </CardSection>
+
+          <CardSection>
+            <Text>{data.descCorta}</Text>
+          </CardSection>
+
+          <CardSection>
+            <Text>{data.dia}</Text>
+            <Text>Mostrar como hora ajustado a zona</Text>
+            <Text>{data.horaIni} a {data.horaFin}</Text>
+          </CardSection>
+
+          <CardSection>
+            <Button onPress={this.onRowPress.bind(this)}>
+              Ingresar
+            </Button>
+          </CardSection>
+        </Card>
+      </ScrollView>
+    );
+  }
+}
+
+const styles = {
+  titleStyle: {
+    fontSize: 18,
+    paddingLeft: 15
+  }
+};
+
+export default ItemCursada;

+ 51 - 0
src/components/ListaCursadas.js

@@ -0,0 +1,51 @@
+import _ from 'lodash';
+import React, { Component } from 'react';
+import { Text, ListView } from 'react-native';
+import { connect } from 'react-redux';
+import { cursadasFetch } from '../actions/';
+import ItemCursada from './ItemCursada';
+class ListaCursadas extends Component  {
+
+	componentWillMount() {
+    this.props.cursadasFetch();
+		this.createDataSource(this.props);
+	}
+	componentWillReceiveProps(nextProps) {
+		this.createDataSource(nextProps);
+	}
+
+	createDataSource({cursadas,cursos}){
+    //const {cursadas, cursos} = cursada;
+
+		const ds = new ListView.DataSource({
+			rowHasChanged: (r1,r2) => r1 !== r2
+		});
+		this.dataSource = ds.cloneWithRows(cursadas);
+	}
+
+	renderRow(data) {
+		return <ItemCursada data={data}/>;
+	}
+
+	render() {
+    console.log("props");
+    console.log(this.props);
+		return (
+      <ListView
+        enableEmptySections
+        dataSource={this.dataSource}
+        renderRow={this.renderRow} />
+		);
+	}
+};
+
+const mapStateToProps = state => {
+  console.log("state");
+  console.log(state);
+	/*const cursadas = _.map(state.cursadas, (val, uid) => {
+		return { ...val, uid };
+	});*/
+	return { cursadas: state.cursadasR.cursadas, cursos: state.cursadasR.cursos };
+};
+
+export default connect(mapStateToProps, { cursadasFetch })(ListaCursadas);

+ 94 - 0
src/components/LiveVideo.js

@@ -0,0 +1,94 @@
+import React, { Component } from 'react';
+import {
+  StyleSheet,
+  Text,
+  View,
+  ScrollView
+} from 'react-native';
+
+import Video from 'react-native-video';
+import Canvas from './Canvas';
+import Socket from './Socket';
+import Chat from './Chat';
+
+class LiveVideo extends Component {
+  state = { pdf: "EBA", diapo: 1, messages: [] };
+  getTime() {
+	var d = new Date();
+	var curr_hour = d.getHours();
+	var curr_min = d.getMinutes();
+	if (curr_min < 10)
+		curr_min='0'+curr_min;
+	return curr_hour+":"+curr_min;
+  }
+
+  onMessage(user,text) {
+	  this.setState({messages: [ ...this.state.messages, { time: this.getTime(), user, text }] });
+  }
+
+  onDiapoChanged(pdf, diapo) {
+	console.log("Diapo changed!", pdf, diapo);
+	this.setState({ pdf, diapo });
+  }
+
+  render() {
+    return (
+	  <View style={{flex:1}}>
+		  <ScrollView
+			horizontal={true}
+			pagingEnabled={true}
+		  >
+			  <View style={styles.videoPage}>
+			      <View style={styles.video}>
+			        <Video source={{ uri: "https://plataforma.especificosba.com.ar/hls/movie.m3u8" }}
+						ref={(ref) => { this.player = ref }}
+						playInBackground={true}
+						volume={0.2}
+						resizeMode={"contain"}
+						style={{position:'absolute', top: 0, left: 0, right: 0, bottom:0}}
+					/>
+			      </View>
+			      <View style={styles.imageContainer}>
+					<Canvas pdf={this.state.pdf} diapo={this.state.diapo} style={{flex:1}}/>
+			      </View>
+
+			  </View>
+
+			  <View style={{flex:1,width:700}}>
+					<Chat messages={this.state.messages} />
+   			  </View>
+
+		  </ScrollView>
+		  <Socket onDiapoChanged={this.onDiapoChanged.bind(this)} onMessage={this.onMessage.bind(this)} />
+	  </View>
+
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  videoPage: {
+	  flexDirection: 'row',
+	  flex: 1
+  },
+  video: {
+	width: 320,
+    justifyContent: 'center',
+    alignItems: 'center',
+    backgroundColor: '#F5FCFF',
+	margin: 5
+
+  },
+  imageContainer: {
+    width: 320,
+    margin: 5
+  },
+  instructions: {
+    textAlign: 'center',
+    color: '#333333',
+    marginBottom: 5,
+  },
+});
+
+
+export default LiveVideo;

+ 81 - 0
src/components/LoginForm.js

@@ -0,0 +1,81 @@
+import React, { Component} from 'react';
+import { Text } from 'react-native';
+import { connect } from 'react-redux';
+import { Card, CardSection, Input, Button, Spinner } from './common';
+import { emailChanged, passwordChanged, loginUser } from '../actions/';
+
+class LoginForm extends Component {
+
+  onEmailChange(text) {
+    this.props.emailChanged(text);
+  }
+
+  onPasswordChange(text){
+    this.props.passwordChanged(text);
+  }
+
+  onButtonPress(){
+    const { email, password } = this.props;
+    this.props.loginUser({ email, password });
+  }
+
+  renderButton() {
+    if (this.props.loading) {
+      return <Spinner size="large" />;
+    }
+    return (
+      <Button onPress={this.onButtonPress.bind(this)}>
+        Login
+      </Button>
+    );
+  }
+  render() {
+    return (
+      <Card>
+        <CardSection>
+          <Input
+            label="Email"
+            placeholder="email@gmail.com"
+            value={this.props.email}
+            onChangeText={this.onEmailChange.bind(this)}
+          />
+        </CardSection>
+
+        <CardSection>
+          <Input
+            secureTextEntry
+            label="Password"
+            placeholder="password"
+            value={this.props.password}
+            onChangeText={this.onPasswordChange.bind(this)}
+          />
+        </CardSection>
+
+        <Text style={styles.errorTextStyle}>
+          {this.props.error}
+        </Text>
+        <CardSection>
+          {this.renderButton()}
+        </CardSection>
+      </Card>
+
+    );
+  }
+}
+
+const styles = {
+  errorTextStyle: {
+    fontSize: 20,
+    alignSelf: 'center',
+    color: 'red'
+  }
+}
+
+const mapStateToProps = ({ auth }) => {
+  const { email, password, error, loading } = auth;
+  return { email, password, error, loading };
+};
+
+export default connect(mapStateToProps, {
+  emailChanged, passwordChanged, loginUser
+} )(LoginForm);

+ 36 - 0
src/components/common/Button.js

@@ -0,0 +1,36 @@
+import React from 'react';
+import { TouchableOpacity, Text } from 'react-native';
+
+const Button = (props) => {
+  const { buttonStyle, textStyle } = styles;
+  const { children, onPress } = props;
+  return (
+    <TouchableOpacity onPress={onPress} style={buttonStyle}>
+      <Text style={textStyle}>
+        {children}
+      </Text>
+    </TouchableOpacity>
+  );
+}
+
+const styles = {
+  textStyle: {
+    alignSelf: 'center',
+    color: '#007aff',
+    fontSize:16,
+    fontWeight: '600',
+    paddingTop: 10,
+    paddingBottom: 10
+  },
+  buttonStyle: {
+    flex: 1,
+    alignSelf: 'stretch',
+    backgroundColor: '#fff',
+    borderRadius: 5,
+    borderWidth: 1,
+    borderColor: '#007aff',
+    marginLeft: 5,
+    marginRight: 5
+  }
+};
+export {Button};

+ 29 - 0
src/components/common/Card.js

@@ -0,0 +1,29 @@
+import React from 'react';
+import { Text, View } from 'react-native';
+
+const Card = (props) => {
+  return (
+    <View style={styles.containerStyle}>
+      {props.children}
+    </View>
+  );
+
+};
+
+const styles = {
+  containerStyle: {
+    borderWidth: 1,
+    borderRadius: 2,
+    borderColor: '#ddd',
+    borderBottomWidth: 0,
+    shadowColor: '#000',
+    shadowOffset: { width: 0, height: 2},
+    shadowOpacity: 0.1,
+    shadowRadius: 2,
+    elevation: 1,
+    marginLeft: 5,
+    marginRight: 5,
+    marginTop: 10
+  }
+};
+export {Card};

+ 23 - 0
src/components/common/CardSection.js

@@ -0,0 +1,23 @@
+import React from 'react';
+import { View } from 'react-native';
+
+const CardSection = (props) => {
+  return (
+    <View style={[styles.containerStyle, props.style]}>
+      {props.children}
+    </View>
+  );
+};
+
+const styles = {
+  containerStyle: {
+    borderBottomWidth: 1,
+    padding: 5,
+    backgroundColor: '#fff',
+    justifyContent: 'flex-start',
+    flexDirection: 'row',
+    borderColor: '#ddd',
+    position: 'relative'
+  }
+};
+export {CardSection};

+ 47 - 0
src/components/common/Confirm.js

@@ -0,0 +1,47 @@
+import { Text, View, Modal } from 'react-native';
+import React from 'react';
+import { CardSection } from './CardSection';
+import { Button } from './Button';
+
+const Confirm = ({ children, visible, onAccept, onDecline }) => {
+  return (
+    <Modal
+      transparent
+      animationType="slide"
+      visible={visible}
+      onRequestClose={ () => {}}
+    >
+      <View style={styles.containerStyle}>
+        <CardSection style={styles.cardSectionStyle}>
+          <Text style={styles.textStyle}>
+            {children}
+          </Text>
+        </CardSection>
+
+        <CardSection>
+          <Button onPress={onAccept}>Yes</Button>
+          <Button onPress={onDecline}>No</Button>
+        </CardSection>
+      </View>
+    </Modal>
+  )
+};
+
+const styles = {
+  containerStyle: {
+    backgroundColor: 'rgba(0,0,0,0.75)',
+    position: 'relative',
+    flex: 1,
+    justifyContent: 'center'
+  },
+  textStyle: {
+    flex: 1,
+    fontSize: 18,
+    textAlign: 'center',
+    lineHeight: 40
+  },
+  cardSectionStyle: {
+    justifyContent: 'center'
+  }
+};
+export { Confirm };

+ 29 - 0
src/components/common/Header.js

@@ -0,0 +1,29 @@
+import React from 'react';
+import { Text, View } from 'react-native';
+
+const Header = (props) => {
+	const { textStyle, viewStyle } = styles;
+	return (
+	<View style={viewStyle}>
+		<Text style={textStyle}>{props.headerText}</Text>
+	</View>
+	);
+};
+
+const styles = {
+	viewStyle: {
+		backgroundColor: '#F8F8F8',
+		justifyContent: 'center',
+		alignItems: 'center',
+		height: 60,
+		shadowColor: '#000',
+		shadowOffset: { width: 0, height: 2},
+		shadowOpacity: 0.2,
+		elevation: 2,
+		position: 'relative'
+	},
+	textStyle: {
+		fontSize: 20
+	}
+};
+export  {Header};

+ 43 - 0
src/components/common/Input.js

@@ -0,0 +1,43 @@
+import React from 'react';
+import { View, TextInput, Text } from 'react-native';
+
+const Input = ({label, value, onChangeText, placeholder, secureTextEntry}) => {
+	const  { InputStyle, LabelStyle, ContainerStyle } = styles;
+	return (
+		<View style={ContainerStyle}>
+			<Text style={LabelStyle}> {label} </Text>
+			<TextInput
+				autoCorrect={false}
+				secureTextEntry={secureTextEntry}
+				placeholder={placeholder}
+				style={InputStyle}
+				value={value}
+				onChangeText={onChangeText}
+			/>
+		</View>
+	);
+};
+
+const styles = {
+	InputStyle: {
+		color: "#000",
+		paddingRight: 5,
+		paddingLeft: 5,
+		fontSize: 18,
+		lineHeight: 23,
+		flex: 2
+	},
+	LabelStyle: {
+		fontSize: 18,
+		paddingLeft: 20,
+		flex: 1
+	},
+	ContainerStyle: {
+		height: 40,
+		flex: 1,
+		flexDirection: 'row',
+		alignItems: 'center'
+	}
+};
+
+export { Input };

+ 19 - 0
src/components/common/Spinner.js

@@ -0,0 +1,19 @@
+import React from 'react';
+import { View, ActivityIndicator } from 'react-native';
+
+const Spinner = ( {size} ) => {
+	return (
+		<View style={styles.spinnerStyle}>
+			<ActivityIndicator size={size || 'large'} />
+		</View>
+	);
+};
+
+const styles = {
+	spinnerStyle: {
+		flex: 1,
+		justifyContent: 'center',
+		alignItems: 'center'
+	}
+};
+export { Spinner } ;

+ 8 - 0
src/components/common/index.js

@@ -0,0 +1,8 @@
+//import { Header, Button, CardSection, Card } from './components/common';
+export * from './Button';
+export * from './Card';
+export * from './CardSection';
+export * from './Header';
+export * from './Input';
+export * from './Spinner';
+export * from './Confirm';

+ 32 - 0
src/reducers/AuthReducer.js

@@ -0,0 +1,32 @@
+import {
+  EMAIL_CHANGED,
+  PASSWORD_CHANGED,
+  LOGIN_USER_SUCCESS,
+  LOGIN_USER_FAIL,
+  LOGIN_USER
+} from '../actions/types';
+
+const INITIAL_STATE = {
+  email: 'davidventura27@gmail.com',
+  password: 'cdb9af22908c2c6d1d29961f3a8a23022ff8e21d9ea50f40ebc72759c6cb76dc',
+  user: null,
+  error: '',
+  loading: false
+};
+
+export default (state = INITIAL_STATE, action) => {
+  switch(action.type) {
+    case EMAIL_CHANGED:
+      return { ...state, email: action.payload };
+    case PASSWORD_CHANGED:
+      return { ...state, password: action.payload };
+    case LOGIN_USER_SUCCESS:
+      return { ...state, user: action.payload, error: '', loading: false };
+    case LOGIN_USER_FAIL:
+      return { ...state, error: 'Auth failed', password: '', loading: false };
+    case LOGIN_USER:
+      return { ...state, loading: true, error: '' };
+    default:
+      return state;
+  }
+};

+ 18 - 0
src/reducers/CursadaReducer.js

@@ -0,0 +1,18 @@
+import {
+  CURSADA_FETCH_SUCCESS
+} from '../actions/types';
+
+const INITIAL_STATE = {
+  cursos: [],
+  cursadas: []
+};
+
+export default (state = INITIAL_STATE, action) => {
+  switch(action.type) {
+    case CURSADA_FETCH_SUCCESS:
+      const { cursadas, cursos } = action.payload;
+      return { ...state, cursadas, cursos };
+    default:
+      return state;
+  }
+};

+ 8 - 0
src/reducers/index.js

@@ -0,0 +1,8 @@
+import { combineReducers } from 'redux';
+import AuthReducer from './AuthReducer';
+import CursadaReducer from './CursadaReducer';
+
+export default combineReducers({
+  auth: AuthReducer,
+  cursadasR: CursadaReducer
+});