diff options
Diffstat (limited to 'screens')
| -rw-r--r-- | screens/discover.js | 6 | ||||
| -rw-r--r-- | screens/home.js | 162 | ||||
| -rw-r--r-- | screens/modal.js | 18 | ||||
| -rw-r--r-- | screens/passwordRecovery.js | 54 | ||||
| -rw-r--r-- | screens/profile.js | 21 | ||||
| -rw-r--r-- | screens/signin.js | 178 | ||||
| -rw-r--r-- | screens/signup.js | 201 | ||||
| -rw-r--r-- | screens/topRated.js | 6 |
8 files changed, 646 insertions, 0 deletions
diff --git a/screens/discover.js b/screens/discover.js new file mode 100644 index 0000000..0213eed --- /dev/null +++ b/screens/discover.js @@ -0,0 +1,6 @@ +import {Text} from 'react-native'; +const Discover = () => { + return <Text>Discover</Text>; +}; + +export default Discover; diff --git a/screens/home.js b/screens/home.js new file mode 100644 index 0000000..184bbf9 --- /dev/null +++ b/screens/home.js @@ -0,0 +1,162 @@ +import {useCallback, useEffect, useState} from 'react'; +import { + ImageBackground, + Platform, + Pressable, + SafeAreaView, + ScrollView, + Text, + TextInput, + View, +} from 'react-native'; +import tw from 'twrnc'; +import {useDeviceContext} from 'twrnc'; +import requests from '../lib/requests'; +import PreviewCard from '../components/PreviewCard'; +import Modal from './modal'; +//ICONS +import {TvIcon} from 'react-native-heroicons/outline'; +import {MagnifyingGlassIcon} from 'react-native-heroicons/outline'; +import {ChevronDownIcon} from 'react-native-heroicons/solid'; +import {UserIcon} from 'react-native-heroicons/outline'; +import ImageColors from 'react-native-image-colors'; +import LinearGradient from 'react-native-linear-gradient'; + +const Home = ({navigation}) => { + useDeviceContext(tw); + const imgAssets = process.env.IMG_ASSETS; + const [heroMovie, setHeroMovie] = useState(); + const [heroMovieColor, setHeroMovieColor] = useState(); + const [topRatedMovies, setTopRatedMovies] = useState(); + const [isModalVisible, setModalVisible] = useState(false); + const [modalContent, setModalContent] = useState(); + + const toggleModal = (movie) => { + setModalVisible(!isModalVisible); + setModalContent(movie); +}; + + useEffect(() => { + fetch(`https://api.themoviedb.org/3/movie/top_rated?api_key=f247ff737ec8062f3b5e027789eab748&language=en`) + .then(res => res.json()) + .then(data => { + setHeroMovie(data.results[1]); + setTopRatedMovies(data.results) + }); + }, []); + + useEffect(() => { + const fetchColor = async () => { + const data = await ImageColors.getColors( + `https://image.tmdb.org/t/p/original${heroMovie?.poster_path}`, + { + fallback: '#000000', + cache: true, + key: 'unique_key', + }, + ).then(data => { + if (Platform.OS === 'ios') { + setHeroMovieColor(data.primary); + } else { + setHeroMovieColor(data.average); + } + }); + } + if (heroMovie?.poster_path != undefined) { + fetchColor() + .catch(e => { + console.log(e) + return e + }); + } + }, [heroMovie]) + + return ( + <ScrollView style={tw`bg-black flex-1`}> + <SafeAreaView> + <View + style={tw`relative z-10 flex flex-row justify-between items-center px-6 gap-4 android:pt-4`} + > + <Text style={tw`android:hidden text-white font-bold text-xl`}> + For Matias + </Text> + <View style={tw`ios:hidden flex-6 relative`}> + <MagnifyingGlassIcon + style={tw`absolute z-10 left-2 top-2.5`} + color={tw.color('gray-300')} + size={20} + /> + <TextInput + style={tw`pr-4 pl-8 h-10 font-medium bg-white rounded w-full text-white`} + autoCorrect={false} + autoCapitalize={'none'} + placeholderTextColor={tw.color('text-gray-400')} + placeholder={'Search'} + /> + </View> + <View style={tw`flex android:flex-1 flex-row items-center gap-4`}> + <MagnifyingGlassIcon + style={tw`android:hidden`} + color={tw.color('white')} + size={20} + /> + <TvIcon color={tw.color('white')} size={20} /> + <Pressable + onPress={() => navigation.navigate('Profile')} + style={tw`rounded bg-white/20 w-6 h-6 flex items-center justify-center`} + > + <UserIcon color={tw.color('white')} size={16} /> + </Pressable> + </View> + </View> + <View style={tw`flex flex-row px-6 pt-3 gap-4 relative z-10`}> + <Pressable style={tw`border border-white/30 rounded-full px-2 py-1`}> + <Text style={tw`text-white`}>TV Shows</Text> + </Pressable> + <Pressable style={tw`border border-white/30 rounded-full px-2 py-1`}> + <Text style={tw`text-white`}>Movies</Text> + </Pressable> + <Pressable + style={tw`border border-white/30 rounded-full px-2 py-1 flex flex-row items-center gap-1`} + > + <Text style={tw`text-white`}>Categories</Text> + <ChevronDownIcon color={tw.color('gray-300')} size={16} /> + </Pressable> + </View> + <View style={tw`p-6 relative z-10`}> + <ImageBackground + source={{uri: `https://image.tmdb.org/t/p/original${heroMovie?.poster_path}`}} + resizeMode={'cover'} + style={tw`rounded-xl bg-white h-[500px] border border-white/30 overflow-hidden`} + ></ImageBackground> + </View> + {heroMovieColor !== undefined && ( + <LinearGradient + style={tw`absolute top-0 left-0 w-screen h-screen z-0`} + colors={[`${heroMovieColor}`, tw.color('black')]} + /> + )} + <View> + <Text style={tw`text-white mx-8`}>Popular on Netflix</Text> + <ScrollView + contentContainerStyle={tw`p-3 flex gap-3`} + horizontal + showsHorizontalScrollIndicator={false} + > + { + topRatedMovies?.map((movie, index) => ( + <Pressable key={index} onPress={() => toggleModal(movie)}> + <PreviewCard key={index} movie={movie} /> + </Pressable> + ))} + </ScrollView> + </View> + {isModalVisible && ( + <Modal closeModal={toggleModal} movie={modalContent}/> + )} + </SafeAreaView> + </ScrollView> + ); +}; + +export default Home; diff --git a/screens/modal.js b/screens/modal.js new file mode 100644 index 0000000..43181e4 --- /dev/null +++ b/screens/modal.js @@ -0,0 +1,18 @@ +import { Pressable, SafeAreaView, Text, View } from 'react-native'; +import tw from 'twrnc'; +import PreviewCard from '../components/PreviewCard'; + +const Modal = ({ closeModal, movie }) => { + return ( + <View style={tw`z-100 absolute top-0 left-0 px-12 py-30`}> + <View style={tw`h-120 w-80 bg-red-500 flex items-center justify-center`}> + <Text style={tw`text-xl text-black`}>{movie?.original_name || movie?.original_title}</Text> + <Pressable onPress={closeModal}> + <Text style={tw`text-xl text-black`}>Dismiss</Text> + </Pressable> + </View> + </View> + ); +}; + +export default Modal;
\ No newline at end of file diff --git a/screens/passwordRecovery.js b/screens/passwordRecovery.js new file mode 100644 index 0000000..61a5f10 --- /dev/null +++ b/screens/passwordRecovery.js @@ -0,0 +1,54 @@ +import { + Keyboard, + KeyboardAvoidingView, + Platform, + Pressable, + SafeAreaView, + Text, + TextInput, + TouchableWithoutFeedback, + View, +} from 'react-native'; + +import tw from 'twrnc'; + +import Logo from '../static/images/netflix-logo.svg'; + +const PasswordRecovery = ({navigation}) => { + return ( + <SafeAreaView style={tw`bg-black flex-1`}> + <TouchableWithoutFeedback + onPress={() => { + Keyboard.dismiss(); + }} + > + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + style={tw`flex-1`} + > + <View style={tw`py-6 px-8 flex items-center justify-center`}> + </View> + <View style={tw`flex-1 items-center justify-center px-12`}> + <Text style={tw`text-white text-2xl mb-12 self-start`}>Recuperar contraseña</Text> + <TextInput + style={tw`h-12 px-4 font-medium bg-white/20 rounded w-full text-white mb-8`} + placeholderTextColor={tw.color('text-white/20')} + placeholder={'Email'} + autoCorrect={false} + autoCapitalize={'none'} + keyboardType={'email-address'} + /> + <Pressable style={tw`mb-12`}> + <Text style={tw`text-white/40 text-xl`}>Recuperar contraseña</Text> + </Pressable> + <Pressable onPress={() => navigation.goBack()} style={tw`mb-16`}> + <Text style={tw`text-white`}>Volver</Text> + </Pressable> + </View> + </KeyboardAvoidingView> + </TouchableWithoutFeedback> + </SafeAreaView> + ); +}; + +export default PasswordRecovery; diff --git a/screens/profile.js b/screens/profile.js new file mode 100644 index 0000000..0bd1cd9 --- /dev/null +++ b/screens/profile.js @@ -0,0 +1,21 @@ +import React, { Component } from 'react'; +import { View, Text , Pressable} from 'react-native'; +import tw from 'twrnc' +import auth from '@react-native-firebase/auth'; + + +const Profile = () => { + return ( + <View style={tw`flex flex-1 items-center justify-center`}> + <Pressable + style={tw`bg-red-500`} + onPress={() => auth().signOut()} + > + <Text> Logout </Text> + </Pressable> + </View> + ); +} + + +export default Profile; diff --git a/screens/signin.js b/screens/signin.js new file mode 100644 index 0000000..49d328d --- /dev/null +++ b/screens/signin.js @@ -0,0 +1,178 @@ +import {useEffect, useState} from 'react'; +import { + Image, + Keyboard, + KeyboardAvoidingView, + Platform, + Pressable, + SafeAreaView, + Text, + TextInput, + TouchableWithoutFeedback, + View, +} from 'react-native'; + +import tw from 'twrnc'; + +import {EyeIcon} from 'react-native-heroicons/outline'; +import {EyeSlashIcon} from 'react-native-heroicons/outline'; +import auth from '@react-native-firebase/auth'; + +const SignIn = ({navigation}) => { + + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [isValidEmail, setIsValidEmail] = useState(false); + const [isValidPassword, setIsValidPassword] = useState(false); + const [isPasswordVisible, setIsPasswordVisible] = useState(true); + + useEffect(() => { + debounce(emailValidation()); + }, [email]); + + useEffect(() => { + debounce(passwordValidation()); + }, [password]); + + const emailValidation = () => { + const emailRegex = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; + if (!email || emailRegex.test(email) === false) { + setIsValidEmail(false); + return false; + } + setIsValidEmail(true); + return true; + }; + + const passwordValidation = () => { + const passwordRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{8,}$/i; + + + if (!password || passwordRegex.test(password) === false) { + setIsValidPassword(false) + return false + } + setIsValidPassword(true) + return true + } + + // Utility FN · Mover a carpeta utils/utils.js + const debounce = fn => { + let id = null; + + return (...args) => { + if (id) { + clearTimeout(id); + } + id = setTimeout(() => { + fn(...args); + id = null; + }, 300); + }; + }; + + const loginUser = () => { + if (isValidEmail && isValidPassword) { + auth() + .signInWithEmailAndPassword(email, password) + .then(() => { + console.log('User signed in successfully!'); + // Navigate to the home screen or other desired screen + }) + .catch(error => { + console.error(error); + // Display an error message to the user + }); + } +}; + + return ( + <SafeAreaView style={tw`bg-black flex-1`}> + <TouchableWithoutFeedback + onPress={() => { + Keyboard.dismiss(); + }} + > + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + style={tw`flex-1`} + > + <View style={tw`py-6 px-8 flex items-center justify-center`}> + <Image + source={require('../static/images/Vector.png')} + style={{ width: 140, height: 37 }} + /> + </View> + <View style={tw`flex-1 items-center justify-center px-12`}> + <Text style={tw`text-white text-2xl mb-12 self-start`}> + ingresar + </Text> + <TextInput + style={tw`h-12 px-4 font-medium bg-white/20 rounded w-full text-white mb-2 ${(!isValidEmail && email !== '') ? 'border border-red-500' : ''}`} + placeholderTextColor={tw.color('text-white/20')} + placeholder={'Email'} + value={email} + onChangeText={textInput => { + setEmail(textInput); + }} + autoCapitalize={'none'} + keyboardType={'email-address'} + /> + <Text + style={tw`text-red-500 self-start mb-6 ${ + (isValidEmail || email === '') && 'opacity-0' + }`} + > + email incorrecto. + </Text> + <View style={tw`relative flex flex-row`}> + <Pressable onPress={() => { + setIsPasswordVisible(!isPasswordVisible) + }} style={tw`absolute top-3 right-3 z-10`}> + {!isPasswordVisible? ( + <EyeSlashIcon style={tw`w-6 h-6 text-white/40`} /> + ) : ( + <EyeIcon style={tw`w-6 h-6 text-white/40`} /> + )} + </Pressable> + <TextInput + style={tw`h-12 px-4 font-medium bg-white/20 rounded w-full text-white mb-2 ${(!isValidPassword && password !== '') ? 'border border-red-500' : ''}`} + placeholderTextColor={tw.color('text-white/20')} + placeholder={'Contraseña'} + value={password} + onChangeText={passwordInput => { + setPassword(passwordInput) + }} + secureTextEntry={!isPasswordVisible} + /> + </View> + <Text + style={tw`text-red-500 self-start mb-6 ${ + (isValidPassword || password === '') && 'opacity-0' + }`} + > + debe contener a-b-0-! + </Text> + <Pressable style={tw`mb-12`} onPress={loginUser}> + <Text style={tw`text-white/40 text-xl`}>ingresar</Text> + </Pressable> + <Pressable + onPress={() => navigation.navigate('PasswordRecovery')} + style={tw`mb-16`} + > + <Text style={tw`text-white`}>¿olvidaste tu contraseña?</Text> + </Pressable> + <View style={tw`flex flex-row items-center`}> + <Text style={tw`text-white`}>¿no tienes una cuenta? </Text> + <Pressable onPress={() => navigation.navigate('SignUp')}> + <Text style={tw`text-red-500 font-bold`}>registrate</Text> + </Pressable> + </View> + </View> + </KeyboardAvoidingView> + </TouchableWithoutFeedback> + </SafeAreaView> + ); +}; + +export default SignIn; diff --git a/screens/signup.js b/screens/signup.js new file mode 100644 index 0000000..73edf4f --- /dev/null +++ b/screens/signup.js @@ -0,0 +1,201 @@ +import {useEffect, useState} from 'react'; +import { + Keyboard, + KeyboardAvoidingView, + Platform, + Pressable, + SafeAreaView, + Text, + TextInput, + TouchableWithoutFeedback, + View, +} from 'react-native'; + +import auth from '@react-native-firebase/auth'; +import tw from 'twrnc'; +import {useToast} from 'react-native-toast-notifications'; + +import Logo from '../static/images/netflix-logo.svg'; +import {EyeIcon} from 'react-native-heroicons/outline'; +import {EyeSlashIcon} from 'react-native-heroicons/outline'; + +const SignUp = ({navigation}) => { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [isValidEmail, setIsValidEmail] = useState(false); + const [isValidPassword, setIsValidPassword] = useState(false); + const [isPasswordVisible, setIsPasswordVisible] = useState(false); + + const toast = useToast(); + + useEffect(() => { + debounce(emailValidation()); + }, [email]); + + useEffect(() => { + debounce(passwordValidation()); + }, [password]); + + const emailValidation = () => { + const emailRegex = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; + if (!email || emailRegex.test(email) === false) { + setIsValidEmail(false); + return false; + } + setIsValidEmail(true); + return true; + }; + + const passwordValidation = () => { + const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/i; + if (!password || passwordRegex.test(password) === false) { + setIsValidPassword(false); + return false; + } + setIsValidPassword(true); + return true; + }; + + // Utility FN · Mover a carpeta utils/utils.js + const debounce = fn => { + let id = null; + + return (...args) => { + if (id) { + clearTimeout(id); + } + id = setTimeout(() => { + fn(...args); + id = null; + }, 300); + }; + }; + + const registerUser = () => { + if (isValidEmail && isValidPassword) { + auth() + .createUserWithEmailAndPassword(email, password) + .then(() => { + console.log('User account created & signed in!'); + }) + .catch(error => { + if (error.code === 'auth/email-already-in-use') { + console.log('That email address is already in use!'); + toast.show({ + type: 'info', + text1: 'Este email ya existe.', + text2: 'Por favor prueba un diferente.' + }); + toast.show('Este email ya existe', { + type: 'info', + data: { + subtitle: 'Por favor prueba uno diferente.', + }, + }); + console.log('hello') + return error + } + + if (error.code === 'auth/invalid-email') { + console.log('That email address is invalid!'); + return error + } + + console.error(error); + }); + } + }; + + return ( + <SafeAreaView style={tw`bg-black flex-1`}> + <TouchableWithoutFeedback + onPress={() => { + Keyboard.dismiss(); + }} + > + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + style={tw`flex-1`} + > + <View style={tw`py-6 px-8 flex items-center justify-center`}> + </View> + <View style={tw`flex-1 items-center justify-center px-12`}> + <Text style={tw`text-white text-2xl mb-12 self-start`}> + Registrate + </Text> + <TextInput + style={tw`h-12 px-4 font-medium bg-white/20 rounded w-full text-white mb-2 ${ + !isValidEmail && email !== '' && 'border border-red-500' + }`} + placeholderTextColor={tw.color('text-white/20')} + placeholder={'Email'} + value={email} + onChangeText={textInput => { + setEmail(textInput); + }} + autoCorrect={false} + autoCapitalize={'none'} + keyboardType={'email-address'} + /> + <Text + style={tw`text-red-500 self-start mb-6 ${ + (isValidEmail || email === '') && 'opacity-0' + }`} + > + Email incorrecto. + </Text> + <View style={tw`relative flex flex-row`}> + <Pressable + onPress={() => { + setIsPasswordVisible(!isPasswordVisible); + }} + style={tw`absolute top-3 right-3 z-10`} + > + {!isPasswordVisible ? ( + <EyeSlashIcon style={tw`w-6 h-6 text-white/40`} /> + ) : ( + <EyeIcon style={tw`w-6 h-6 text-white/40`} /> + )} + </Pressable> + <TextInput + style={tw`h-12 px-4 font-medium bg-white/20 rounded w-full text-white mb-2 ${ + !isValidPassword && password !== '' && 'border border-red-500' + }`} + placeholderTextColor={tw.color('text-white/20')} + placeholder={'Contraseña'} + value={password} + onChangeText={passwordInput => { + setPassword(passwordInput); + }} + secureTextEntry={!isPasswordVisible} + /> + </View> + <Text + style={tw`text-red-500 self-start mb-6 ${ + (isValidPassword || password === '') && 'opacity-0' + }`} + > + Debe contener A-b-0-! + </Text> + <Pressable + onPress={() => { + registerUser(); + }} + style={tw`mb-12`} + > + <Text style={tw`text-white/40 text-xl`}>Registrate</Text> + </Pressable> + <View style={tw`flex flex-row items-center`}> + <Text style={tw`text-white`}>¿Ya tienes una cuenta? </Text> + <Pressable onPress={() => navigation.navigate('SignIn')}> + <Text style={tw`text-red-500 font-bold`}>Ingresa</Text> + </Pressable> + </View> + </View> + </KeyboardAvoidingView> + </TouchableWithoutFeedback> + </SafeAreaView> + ); +}; + +export default SignUp; diff --git a/screens/topRated.js b/screens/topRated.js new file mode 100644 index 0000000..e4685fe --- /dev/null +++ b/screens/topRated.js @@ -0,0 +1,6 @@ +import {Text} from 'react-native'; +const TopRated = () => { + return <Text>Top Rated</Text>; +}; + +export default TopRated; |