├── .buckconfig ├── .bundle └── config ├── .gitignore ├── .ruby-version ├── .watchmanconfig ├── App.tsx ├── Gemfile ├── Gemfile.lock ├── README.md ├── __tests__ └── App-test.tsx ├── android ├── app │ ├── _BUCK │ ├── build.gradle │ ├── build_defs.bzl │ ├── debug.keystore │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── kristhdev │ │ │ └── maketasks │ │ │ └── ReactNativeFlipper.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── kristhdev │ │ │ └── maketasks │ │ │ ├── MainActivity.java │ │ │ └── MainApplication.java │ │ └── res │ │ ├── drawable │ │ ├── background_gradient.xml │ │ ├── play_store_512.png │ │ └── rn_edit_text_material.xml │ │ ├── layout │ │ └── launch_screen.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── app.json ├── babel.config.js ├── env.d.ts ├── example.env ├── index.js ├── ios ├── MakeTasks.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── NotesAppRN.xcscheme ├── MakeTasks │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Info.plist │ ├── LaunchScreen.storyboard │ └── main.m ├── MakeTasksTests │ ├── Info.plist │ └── NotesAppRNTests.m └── Podfile ├── metro.config.js ├── package-lock.json ├── package.json ├── src ├── api │ └── tasksApi.ts ├── assets │ └── default-user.jpg ├── components │ ├── auth │ │ ├── LoginForm.tsx │ │ ├── ProfileForm.tsx │ │ └── RegisterForm.tsx │ ├── tasks │ │ ├── TaskDeleteModal.tsx │ │ ├── TaskForm.tsx │ │ ├── TaskItem.tsx │ │ ├── TaskOptionBtn.tsx │ │ ├── TasksList.tsx │ │ └── TasksLoader.tsx │ └── ui │ │ ├── Fab.tsx │ │ ├── FormBtn.tsx │ │ ├── FormGroup.tsx │ │ ├── ModalPermissions.tsx │ │ ├── ModalStatus.tsx │ │ ├── ScreenTitle.tsx │ │ └── SearchBar.tsx ├── context │ ├── auth │ │ ├── AuthContext.tsx │ │ ├── AuthProvider.tsx │ │ └── authReducer.ts │ ├── permissions │ │ ├── PermissionsContext.tsx │ │ └── PermissionsProvider.tsx │ ├── status │ │ ├── StatusContext.tsx │ │ └── StatusProvider.tsx │ └── tasks │ │ ├── TasksContext.tsx │ │ ├── TasksProvider.tsx │ │ └── tasksReducer.ts ├── hooks │ ├── useAuth.ts │ ├── useForm.ts │ ├── useImage.ts │ ├── useKeyboard.ts │ ├── usePermissions.ts │ ├── useStatus.ts │ └── useTasks.ts ├── interfaces │ ├── auth.ts │ ├── permissions.ts │ ├── tasks.ts │ └── ui.ts ├── layout │ ├── AuthLayout.tsx │ ├── LinearGradientLayout.tsx │ └── TasksLayout.tsx ├── navigation │ ├── AuthNavigator.tsx │ ├── Navigator.tsx │ └── TasksNavigator.tsx ├── screens │ ├── auth │ │ ├── LoginScreen.tsx │ │ ├── ProfileScreen.tsx │ │ └── RegisterScreen.tsx │ ├── tasks │ │ ├── CreateTaskScreen.tsx │ │ ├── HomeScreen.tsx │ │ └── SearchScreen.tsx │ └── ui │ │ ├── LoadingScreen.tsx │ │ └── ModalScreen.tsx ├── theme │ └── app-theme.ts └── utils │ ├── errors.ts │ └── upload.ts └── tsconfig.json /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | *.hprof 32 | 33 | # node.js 34 | # 35 | google-services.json 36 | node_modules/ 37 | npm-debug.log 38 | yarn-error.log 39 | 40 | # BUCK 41 | buck-out/ 42 | \.buckd/ 43 | *.keystore 44 | !debug.keystore 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/ 52 | 53 | */fastlane/report.xml 54 | */fastlane/Preview.html 55 | */fastlane/screenshots 56 | 57 | # Bundle artifact 58 | *.jsbundle 59 | 60 | # CocoaPods 61 | /ios/Pods/ 62 | 63 | # Environments 64 | .env -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.4 -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native-gesture-handler'; 2 | 3 | import React, { FC, useEffect } from 'react'; 4 | import { NavigationContainer } from '@react-navigation/native'; 5 | import SplashScreen from 'react-native-splash-screen' 6 | import dayjs from 'dayjs'; 7 | import 'dayjs/locale/es'; 8 | 9 | /* Providers */ 10 | import AuthProvider from './src/context/auth/AuthProvider'; 11 | import StatusProvider from './src/context/status/StatusProvider'; 12 | import TasksProvider from './src/context/tasks/TasksProvider'; 13 | import PermissionsProvider from './src/context/permissions/PermissionsProvider'; 14 | 15 | /* Navigators */ 16 | import Navigator from './src/navigation/Navigator'; 17 | 18 | /* Seteo de idioma para las fechas */ 19 | dayjs.locale('es'); 20 | 21 | /* HOC donde están todos los contexts de la aplicación */ 22 | const AppState: FC = ({ children }) => { 23 | return ( 24 | 25 | 26 | 27 | 28 | { children } 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | 36 | const App = () => { 37 | /* useEffect para desparecer el splash screen */ 38 | useEffect(() => { 39 | SplashScreen.hide(); 40 | }, []); 41 | 42 | return ( 43 | 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | 51 | export default App; -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 3 | ruby '2.7.4' 4 | gem 'cocoapods', '~> 1.11', '>= 1.11.2' 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.5) 5 | rexml 6 | activesupport (6.1.4.4) 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | i18n (>= 1.6, < 2) 9 | minitest (>= 5.1) 10 | tzinfo (~> 2.0) 11 | zeitwerk (~> 2.3) 12 | addressable (2.8.0) 13 | public_suffix (>= 2.0.2, < 5.0) 14 | algoliasearch (1.27.5) 15 | httpclient (~> 2.8, >= 2.8.3) 16 | json (>= 1.5.1) 17 | atomos (0.1.3) 18 | claide (1.1.0) 19 | cocoapods (1.11.2) 20 | addressable (~> 2.8) 21 | claide (>= 1.0.2, < 2.0) 22 | cocoapods-core (= 1.11.2) 23 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 24 | cocoapods-downloader (>= 1.4.0, < 2.0) 25 | cocoapods-plugins (>= 1.0.0, < 2.0) 26 | cocoapods-search (>= 1.0.0, < 2.0) 27 | cocoapods-trunk (>= 1.4.0, < 2.0) 28 | cocoapods-try (>= 1.1.0, < 2.0) 29 | colored2 (~> 3.1) 30 | escape (~> 0.0.4) 31 | fourflusher (>= 2.3.0, < 3.0) 32 | gh_inspector (~> 1.0) 33 | molinillo (~> 0.8.0) 34 | nap (~> 1.0) 35 | ruby-macho (>= 1.0, < 3.0) 36 | xcodeproj (>= 1.21.0, < 2.0) 37 | cocoapods-core (1.11.2) 38 | activesupport (>= 5.0, < 7) 39 | addressable (~> 2.8) 40 | algoliasearch (~> 1.0) 41 | concurrent-ruby (~> 1.1) 42 | fuzzy_match (~> 2.0.4) 43 | nap (~> 1.0) 44 | netrc (~> 0.11) 45 | public_suffix (~> 4.0) 46 | typhoeus (~> 1.0) 47 | cocoapods-deintegrate (1.0.5) 48 | cocoapods-downloader (1.5.1) 49 | cocoapods-plugins (1.0.0) 50 | nap 51 | cocoapods-search (1.0.1) 52 | cocoapods-trunk (1.6.0) 53 | nap (>= 0.8, < 2.0) 54 | netrc (~> 0.11) 55 | cocoapods-try (1.2.0) 56 | colored2 (3.1.2) 57 | concurrent-ruby (1.1.9) 58 | escape (0.0.4) 59 | ethon (0.15.0) 60 | ffi (>= 1.15.0) 61 | ffi (1.15.5) 62 | fourflusher (2.3.1) 63 | fuzzy_match (2.0.4) 64 | gh_inspector (1.1.3) 65 | httpclient (2.8.3) 66 | i18n (1.9.1) 67 | concurrent-ruby (~> 1.0) 68 | json (2.6.1) 69 | minitest (5.15.0) 70 | molinillo (0.8.0) 71 | nanaimo (0.3.0) 72 | nap (1.1.0) 73 | netrc (0.11.0) 74 | public_suffix (4.0.6) 75 | rexml (3.2.5) 76 | ruby-macho (2.5.1) 77 | typhoeus (1.4.0) 78 | ethon (>= 0.9.0) 79 | tzinfo (2.0.4) 80 | concurrent-ruby (~> 1.0) 81 | xcodeproj (1.21.0) 82 | CFPropertyList (>= 2.3.3, < 4.0) 83 | atomos (~> 0.1.3) 84 | claide (>= 1.0.2, < 2.0) 85 | colored2 (~> 3.1) 86 | nanaimo (~> 0.3.0) 87 | rexml (~> 3.2.4) 88 | zeitwerk (2.5.4) 89 | PLATFORMS 90 | ruby 91 | DEPENDENCIES 92 | cocoapods (~> 1.11, >= 1.11.2) 93 | RUBY VERSION 94 | ruby 2.7.4p191 95 | BUNDLED WITH 96 | 2.2.27 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MakeTasks 2 | 3 | ![MakeTasks logo](https://res.cloudinary.com/dzs8lf9lc/image/upload/v1647967003/maketasks/maketasks-logo_gf2vmf.png) 4 | 5 | **MakeTasks** es una pequeña aplicación movil que te permite crear tareas, una agenda para tu día a día y que estén vinculadas a tu cuenta de usuario. Usa las técnologias React Native y Firebase. Este pequeño proyecto se hizo para la clase de Modelación y Simulación de Sistemas de la Universidad del Norte de Nicaragua. El fin de este es tener una mejor gestión de nuestras tareas del día a día acompañado de una interfaz amigable y sencilla. Queriamos hacer algo sencillo y práctico. 6 | 7 |   8 | 9 | 10 | Apartir de este punto se daran una serie de instrucciones para probar el proyecto con tus propias configuraciones. Si solo quieres probar la aplicación, puedes descargarla en el siguiente link: [Descargar aquí](https://www.mediafire.com/file/o25eag7hfbolhs5/maketasks.apk/file). 11 | 12 |   13 | 14 | 15 | ![MakeTasks Image 1](https://res.cloudinary.com/dzs8lf9lc/image/upload/c_scale,w_290/v1647978814/maketasks/Screenshot_20220322-134813_MakeTasks_fmz2fp.jpg)     ![MakeTasks Image 2](https://res.cloudinary.com/dzs8lf9lc/image/upload/c_scale,w_290/v1647978814/maketasks/Screenshot_20220322-134841_MakeTasks_pvozty.jpg)     ![MakeTasks Image 3](https://res.cloudinary.com/dzs8lf9lc/image/upload/c_scale,w_290/v1647978813/maketasks/Screenshot_20220322-134903_MakeTasks_c6zepu.jpg) 16 | 17 | ## Instrucciones de uso 18 | 19 |   20 | 21 | 22 | ### 1) Entorno de desarrollo de React Native 23 | Antes de comenzar debes asegurarte de tener configurado en tu computadora el entorno de desarrollo de React Native, por lo que te dejo los pasos de la documentación oficial: [Click para ir](https://reactnative.dev/docs/environment-setup) 24 | 25 |   26 | 27 | 28 | ### 2) Renombrar proyecto 29 | Como segundo paso hay que renombrar el identificador de la app. para ello ve a la carpeta ```android/app/src/main/java/com```. Dentro de com encontraras una carpeta llamada ```kristhdev```, esta carpeta la vas a renombrar por el nombre que tu quieras, preferiblemente tu nombre de usuario. 30 | 31 |   32 | 33 | 34 | Despues de eso busca el archivo ```AndroidManifest.xml``` dentro de ```android/app/src/main```, lo abres y cambia el atributo ```package```, cambia la parte que dice ```kristhdev``` por el nombre que lo cambiaste anteriormente, por ejemplo si lo renombraste por ```andredev``` quedaria así: 35 | 36 | ```xml 37 | // <-- Ahí es donde haras el cambio 39 | 40 | 41 | 42 | 43 | 50 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ``` 64 | 65 |   66 | 67 | 68 | Por último vas buscar los archivos ```MainActivity.java``` y ```MainApplication.java``` que se encuentran en ```android/app/src/main/java/con//maketasks```, abres los dos archivos y vas a cambiar la definición del ```package```, es la primera linea de cada archivo, nuevamente, si la renombraste como andredev quedaria así: 69 | 70 | ```java 71 | package com.andredev.maketasks; 72 | ``` 73 | 74 |   75 | 76 | 77 | ### 3) Crear proyecto de Firebase 78 | Lo siguiente es crear un proyecto de Firebase, te dejo el link oficial: [Click para ir](https://firebase.google.com). Una vez que hayas creado el proyecto var a ir a la configuración del proyecto, al bajar un poco encontraras un boton que dice **Agregar una app**. Luego le das ckick al boton con el icono de android y como nombre de paquete var a poner lo que renombraste anteriormenete ```com..maketasks```, siguiendo con el ejemplo si lo renombraste como andredev seria ```com.andredev.maketasks``` y le das al boton de siguiente. 79 | 80 |   81 | 82 | 83 | Te dara la opción de descargar el archivo ```google-services.json```, este archivo lo vas a pegar en ```android/app```, después solo le das a siguiente y siguiente, no hay nada que hacer, ya todo esta configurado en el proyecto. 84 | 85 |   86 | 87 | 88 | Lo siguiente es agregar un ```metodo de autenticación``` en tu proyecto de firebase, debes habilitar el ```método por correo```, también crea una base de datos en la pestaña de ```RealTime Database```, una vez creada agrega las reglas en la pestaña de ```rules```, las reglas que vas a agregar son las de autenticación, solo usuarios logeados podran leer e insertar en la base de datos y solo el dueño de los recursos podra leerlos, editarlos y borrarlos. Las otras reglas a agregar son para definir un esquema de campos que tendra cada tarea. Por último ve a la pestaña de Messaging y agrega una notificación programada que avise cada día de las tareas. 89 | 90 |   91 | 92 | 93 | ### 4) Subida de imagenes a Cloudinary 94 | Está aplicación usa el servicio de cloudinary para subir las imagenes, te comparto el link oficial: [Click para ir](https://cloudinary.com). Crea una cuenta, automaticamente vas a estar en el dashboard, ve a la configuración y crea un ```upload preset público```. Guarda el nombre de ese preset y también el ```Cloud Name``` que se encuentra en el dashboard. 95 | 96 |   97 | 98 | 99 | ### 5) Variables de entorno 100 | En la raíz de la aplicación vas a encontrar el archivo ```example.env```, ese archivo contiene el nombre de las variables de entorno que nesesita la aplicación, crea un nuevo archivo en la raíz de la aplicación que se llame ```.env```, copia las variables de ```example.env``` y pegalas ```.env```, ahora cambia los valores. (sin espacios) 101 | 102 | ```env 103 | ASYNCSTORAGE_ID_TOKEN= Es la clave con la que vas a guardar el idToken en la cache, puedes poner lo que quieras. 104 | ASYNCSTORAGE_USER= Es la clave con la que vas a guardar al usuario autenticado en la cache, es a tu discreción. 105 | CLOUDINARY_CLOUD_NAME= Valor del Cloud Name que se encuentra en el dashboard de Cloudinary. 106 | CLOUDINARY_UPLOAD_PRESET= Nombre del preset que creaste en la configuración de Cloudinary. 107 | FIREBASE_API_KEY= Clave de Firebase, la encuentras en tu google-services.json, toma el valor de current_key. 108 | ``` 109 | 110 |   111 | 112 | 113 | ### 6) Instalación 114 | Ahora solo queda instalar las dependencias del proyecto, abre una terminal, navega a la carpeta del proyecto y ejecuta el siguiente comando: 115 | ``` 116 | npm install 117 | ``` 118 | 119 |   120 | 121 | 122 | Cuando termine la instalación solo queda levantar la app con el siguiente comando: 123 | ``` 124 | npx react-native run-android 125 | ``` 126 | 127 | Y listo ya puedes probar la aplicación 128 | 129 |   130 | 131 | 132 | ## License 133 | 134 | MIT 135 | -------------------------------------------------------------------------------- /__tests__/App-test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import 'react-native'; 6 | import React from 'react'; 7 | import App from '../App'; 8 | 9 | // Note: test renderer must be required after react-native. 10 | import renderer from 'react-test-renderer'; 11 | 12 | it('renders correctly', () => { 13 | renderer.create(); 14 | }); 15 | -------------------------------------------------------------------------------- /android/app/_BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.kristhdev.maketasks", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.kristhdev.maketasks", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: 'com.google.gms.google-services' 3 | 4 | import com.android.build.OutputFile 5 | 6 | project.ext.vectoricons = [ 7 | iconFontNames: [ 'Ionicons.ttf' ] // Name of the font files you want to copy 8 | ] 9 | 10 | apply from: "../../node_modules/react-native-vector-icons/fonts.gradle" 11 | 12 | project.ext.react = [ 13 | enableHermes: true // <- here | clean and rebuild if changing 14 | ] 15 | 16 | /** 17 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 18 | * and bundleReleaseJsAndAssets). 19 | * These basically call `react-native bundle` with the correct arguments during the Android build 20 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 21 | * bundle directly from the development server. Below you can see all the possible configurations 22 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 23 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 24 | * 25 | * project.ext.react = [ 26 | * // the name of the generated asset file containing your JS bundle 27 | * bundleAssetName: "index.android.bundle", 28 | * 29 | * // the entry file for bundle generation. If none specified and 30 | * // "index.android.js" exists, it will be used. Otherwise "index.js" is 31 | * // default. Can be overridden with ENTRY_FILE environment variable. 32 | * entryFile: "index.android.js", 33 | * 34 | * // https://reactnative.dev/docs/performance#enable-the-ram-format 35 | * bundleCommand: "ram-bundle", 36 | * 37 | * // whether to bundle JS and assets in debug mode 38 | * bundleInDebug: false, 39 | * 40 | * // whether to bundle JS and assets in release mode 41 | * bundleInRelease: true, 42 | * 43 | * // whether to bundle JS and assets in another build variant (if configured). 44 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 45 | * // The configuration property can be in the following formats 46 | * // 'bundleIn${productFlavor}${buildType}' 47 | * // 'bundleIn${buildType}' 48 | * // bundleInFreeDebug: true, 49 | * // bundleInPaidRelease: true, 50 | * // bundleInBeta: true, 51 | * 52 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 53 | * // for example: to disable dev mode in the staging build type (if configured) 54 | * devDisabledInStaging: true, 55 | * // The configuration property can be in the following formats 56 | * // 'devDisabledIn${productFlavor}${buildType}' 57 | * // 'devDisabledIn${buildType}' 58 | * 59 | * // the root of your project, i.e. where "package.json" lives 60 | * root: "../../", 61 | * 62 | * // where to put the JS bundle asset in debug mode 63 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 64 | * 65 | * // where to put the JS bundle asset in release mode 66 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 67 | * 68 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 69 | * // require('./image.png')), in debug mode 70 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 71 | * 72 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 73 | * // require('./image.png')), in release mode 74 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 75 | * 76 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 77 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 78 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 79 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 80 | * // for example, you might want to remove it from here. 81 | * inputExcludes: ["android/**", "ios/**"], 82 | * 83 | * // override which node gets called and with what additional arguments 84 | * nodeExecutableAndArgs: ["node"], 85 | * 86 | * // supply additional arguments to the packager 87 | * extraPackagerArgs: [] 88 | * ] 89 | */ 90 | 91 | project.ext.react = [ 92 | enableHermes: false, // clean and rebuild if changing 93 | ] 94 | 95 | apply from: "../../node_modules/react-native/react.gradle" 96 | 97 | /** 98 | * Set this to true to create two separate APKs instead of one: 99 | * - An APK that only works on ARM devices 100 | * - An APK that only works on x86 devices 101 | * The advantage is the size of the APK is reduced by about 4MB. 102 | * Upload all the APKs to the Play Store and people will download 103 | * the correct one based on the CPU architecture of their device. 104 | */ 105 | def enableSeparateBuildPerCPUArchitecture = false 106 | 107 | /** 108 | * Run Proguard to shrink the Java bytecode in release builds. 109 | */ 110 | def enableProguardInReleaseBuilds = false 111 | 112 | /** 113 | * The preferred build flavor of JavaScriptCore. 114 | * 115 | * For example, to use the international variant, you can use: 116 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 117 | * 118 | * The international variant includes ICU i18n library and necessary data 119 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 120 | * give correct results when using with locales other than en-US. Note that 121 | * this variant is about 6MiB larger per architecture than default. 122 | */ 123 | def jscFlavor = 'org.webkit:android-jsc:+' 124 | 125 | /** 126 | * Whether to enable the Hermes VM. 127 | * 128 | * This should be set on project.ext.react and that value will be read here. If it is not set 129 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode 130 | * and the benefits of using Hermes will therefore be sharply reduced. 131 | */ 132 | def enableHermes = project.ext.react.get("enableHermes", false); 133 | 134 | /** 135 | * Architectures to build native code for in debug. 136 | */ 137 | def nativeArchitectures = project.getProperties().get("reactNativeDebugArchitectures") 138 | 139 | android { 140 | ndkVersion rootProject.ext.ndkVersion 141 | 142 | compileSdkVersion rootProject.ext.compileSdkVersion 143 | 144 | defaultConfig { 145 | applicationId "com.kristhdev.maketasks" 146 | minSdkVersion rootProject.ext.minSdkVersion 147 | targetSdkVersion rootProject.ext.targetSdkVersion 148 | versionCode 1 149 | versionName "1.0" 150 | } 151 | splits { 152 | abi { 153 | reset() 154 | enable enableSeparateBuildPerCPUArchitecture 155 | universalApk false // If true, also generate a universal APK 156 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" 157 | } 158 | } 159 | signingConfigs { 160 | debug { 161 | storeFile file('debug.keystore') 162 | storePassword 'android' 163 | keyAlias 'androiddebugkey' 164 | keyPassword 'android' 165 | } 166 | // release { 167 | // if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) { 168 | // storeFile file(MYAPP_UPLOAD_STORE_FILE) 169 | // storePassword MYAPP_UPLOAD_STORE_PASSWORD 170 | // keyAlias MYAPP_UPLOAD_KEY_ALIAS 171 | // keyPassword MYAPP_UPLOAD_KEY_PASSWORD 172 | // } 173 | // } 174 | } 175 | buildTypes { 176 | debug { 177 | signingConfig signingConfigs.debug 178 | if (nativeArchitectures) { 179 | ndk { 180 | abiFilters nativeArchitectures.split(',') 181 | } 182 | } 183 | } 184 | release { 185 | // Caution! In production, you need to generate your own keystore file. 186 | // see https://reactnative.dev/docs/signed-apk-android. 187 | signingConfig signingConfigs.debug 188 | minifyEnabled enableProguardInReleaseBuilds 189 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 190 | } 191 | } 192 | 193 | // applicationVariants are e.g. debug, release 194 | applicationVariants.all { variant -> 195 | variant.outputs.each { output -> 196 | // For each separate APK per architecture, set a unique version code as described here: 197 | // https://developer.android.com/studio/build/configure-apk-splits.html 198 | // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc. 199 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] 200 | def abi = output.getFilter(OutputFile.ABI) 201 | if (abi != null) { // null for the universal-debug, universal-release variants 202 | output.versionCodeOverride = 203 | defaultConfig.versionCode * 1000 + versionCodes.get(abi) 204 | } 205 | 206 | } 207 | } 208 | } 209 | 210 | dependencies { 211 | implementation platform('com.google.firebase:firebase-bom:29.1.0') 212 | implementation fileTree(dir: "libs", include: ["*.jar"]) 213 | //noinspection GradleDynamicVersion 214 | implementation "com.facebook.react:react-native:+" // From node_modules 215 | 216 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" 217 | 218 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { 219 | exclude group:'com.facebook.fbjni' 220 | } 221 | 222 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { 223 | exclude group:'com.facebook.flipper' 224 | exclude group:'com.squareup.okhttp3', module:'okhttp' 225 | } 226 | 227 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { 228 | exclude group:'com.facebook.flipper' 229 | } 230 | 231 | if (enableHermes) { 232 | def hermesPath = "../../node_modules/hermes-engine/android/"; 233 | debugImplementation files(hermesPath + "hermes-debug.aar") 234 | releaseImplementation files(hermesPath + "hermes-release.aar") 235 | } else { 236 | implementation jscFlavor 237 | } 238 | } 239 | 240 | // Run this once to be able to run the application with BUCK 241 | // puts all compile dependencies into folder libs for BUCK to use 242 | task copyDownloadableDepsToLibs(type: Copy) { 243 | from configurations.implementation 244 | into 'libs' 245 | } 246 | 247 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) -------------------------------------------------------------------------------- /android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/app/debug.keystore -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /android/app/src/debug/java/com/kristhdev/maketasks/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.kristhdev.maketasks; 8 | 9 | import android.content.Context; 10 | import com.facebook.flipper.android.AndroidFlipperClient; 11 | import com.facebook.flipper.android.utils.FlipperUtils; 12 | import com.facebook.flipper.core.FlipperClient; 13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; 14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; 15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; 16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping; 17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; 18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; 19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; 20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin; 21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; 22 | import com.facebook.react.ReactInstanceManager; 23 | import com.facebook.react.bridge.ReactContext; 24 | import com.facebook.react.modules.network.NetworkingModule; 25 | import okhttp3.OkHttpClient; 26 | 27 | public class ReactNativeFlipper { 28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 29 | if (FlipperUtils.shouldEnableFlipper(context)) { 30 | final FlipperClient client = AndroidFlipperClient.getInstance(context); 31 | 32 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); 33 | client.addPlugin(new ReactFlipperPlugin()); 34 | client.addPlugin(new DatabasesFlipperPlugin(context)); 35 | client.addPlugin(new SharedPreferencesFlipperPlugin(context)); 36 | client.addPlugin(CrashReporterPlugin.getInstance()); 37 | 38 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); 39 | NetworkingModule.setCustomClientBuilder( 40 | new NetworkingModule.CustomClientBuilder() { 41 | @Override 42 | public void apply(OkHttpClient.Builder builder) { 43 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); 44 | } 45 | }); 46 | client.addPlugin(networkFlipperPlugin); 47 | client.start(); 48 | 49 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized 50 | // Hence we run if after all native modules have been initialized 51 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); 52 | if (reactContext == null) { 53 | reactInstanceManager.addReactInstanceEventListener( 54 | new ReactInstanceManager.ReactInstanceEventListener() { 55 | @Override 56 | public void onReactContextInitialized(ReactContext reactContext) { 57 | reactInstanceManager.removeReactInstanceEventListener(this); 58 | reactContext.runOnNativeModulesQueueThread( 59 | new Runnable() { 60 | @Override 61 | public void run() { 62 | client.addPlugin(new FrescoFlipperPlugin()); 63 | } 64 | }); 65 | } 66 | }); 67 | } else { 68 | client.addPlugin(new FrescoFlipperPlugin()); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/kristhdev/maketasks/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.kristhdev.maketasks; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import android.os.Bundle; 5 | import org.devio.rn.splashscreen.SplashScreen; 6 | 7 | public class MainActivity extends ReactActivity { 8 | 9 | /** 10 | * Returns the name of the main component registered from JavaScript. This is used to schedule 11 | * rendering of the component. 12 | */ 13 | @Override 14 | protected String getMainComponentName() { 15 | return "MakeTasks"; 16 | } 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | SplashScreen.show(this); 21 | super.onCreate(savedInstanceState); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/kristhdev/maketasks/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.kristhdev.maketasks; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import com.facebook.react.PackageList; 6 | import com.facebook.react.ReactApplication; 7 | import com.facebook.react.ReactInstanceManager; 8 | import com.facebook.react.ReactNativeHost; 9 | import com.facebook.react.ReactPackage; 10 | import com.facebook.soloader.SoLoader; 11 | import java.lang.reflect.InvocationTargetException; 12 | import java.util.List; 13 | import com.facebook.react.bridge.JSIModulePackage; 14 | import com.swmansion.reanimated.ReanimatedJSIModulePackage; 15 | import org.devio.rn.splashscreen.SplashScreenReactPackage; 16 | 17 | public class MainApplication extends Application implements ReactApplication { 18 | 19 | private final ReactNativeHost mReactNativeHost = 20 | new ReactNativeHost(this) { 21 | @Override 22 | public boolean getUseDeveloperSupport() { 23 | return BuildConfig.DEBUG; 24 | } 25 | 26 | @Override 27 | protected List getPackages() { 28 | @SuppressWarnings("UnnecessaryLocalVariable") 29 | List packages = new PackageList(this).getPackages(); 30 | // Packages that cannot be autolinked yet can be added manually here, for example: 31 | // packages.add(new SplashScreenReactPackage()); 32 | return packages; 33 | } 34 | 35 | @Override 36 | protected String getJSMainModuleName() { 37 | return "index"; 38 | } 39 | 40 | @Override 41 | protected JSIModulePackage getJSIModulePackage() { 42 | return new ReanimatedJSIModulePackage(); // <- add 43 | } 44 | }; 45 | 46 | @Override 47 | public ReactNativeHost getReactNativeHost() { 48 | return mReactNativeHost; 49 | } 50 | 51 | @Override 52 | public void onCreate() { 53 | super.onCreate(); 54 | SoLoader.init(this, /* native exopackage */ false); 55 | initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); 56 | } 57 | 58 | /** 59 | * Loads Flipper in React Native templates. Call this in the onCreate method with something like 60 | * initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); 61 | * 62 | * @param context 63 | * @param reactInstanceManager 64 | */ 65 | private static void initializeFlipper( 66 | Context context, ReactInstanceManager reactInstanceManager) { 67 | if (BuildConfig.DEBUG) { 68 | try { 69 | /* 70 | We use reflection here to pick up the class that initializes Flipper, 71 | since Flipper library is not available in release mode 72 | */ 73 | Class aClass = Class.forName("com.kristhdev.maketasks.ReactNativeFlipper"); 74 | aClass 75 | .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) 76 | .invoke(null, context, reactInstanceManager); 77 | } catch (ClassNotFoundException e) { 78 | e.printStackTrace(); 79 | } catch (NoSuchMethodException e) { 80 | e.printStackTrace(); 81 | } catch (IllegalAccessException e) { 82 | e.printStackTrace(); 83 | } catch (InvocationTargetException e) { 84 | e.printStackTrace(); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/play_store_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/app/src/main/res/drawable/play_store_512.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 18 | 19 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/launch_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MakeTasks 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "30.0.2" 6 | minSdkVersion = 21 7 | compileSdkVersion = 30 8 | targetSdkVersion = 30 9 | ndkVersion = "21.4.7075529" 10 | androidXCore = "1.7.0" 11 | } 12 | repositories { 13 | google() 14 | mavenCentral() 15 | } 16 | dependencies { 17 | classpath("com.android.tools.build:gradle:4.2.2") 18 | classpath 'com.google.gms:google-services:4.3.10' 19 | // NOTE: Do not place your application dependencies here; they belong 20 | // in the individual module build.gradle files 21 | } 22 | } 23 | 24 | allprojects { 25 | repositories { 26 | maven { 27 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 28 | url("$rootDir/../node_modules/react-native/android") 29 | } 30 | maven { 31 | // Android JSC is installed from npm 32 | url("$rootDir/../node_modules/jsc-android/dist") 33 | } 34 | mavenCentral { 35 | // We don't want to fetch react-native from Maven Central as there are 36 | // older versions over there. 37 | content { 38 | excludeGroup "com.facebook.react" 39 | } 40 | } 41 | google() 42 | maven { url 'https://www.jitpack.io' } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.99.0 29 | 30 | MYAPP_UPLOAD_STORE_FILE=archivo_keystore 31 | MYAPP_UPLOAD_KEY_ALIAS=alias_del_archivo_keystore 32 | MYAPP_UPLOAD_STORE_PASSWORD=contraseña_del_archivo_keystore 33 | MYAPP_UPLOAD_KEY_PASSWORD=contrase_del_archivo_keystore 34 | 35 | org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError 36 | org.gradle.daemon=true 37 | org.gradle.parallel=true 38 | org.gradle.configureondemand=true 39 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'MakeTasks' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | 5 | include ':react-native-splash-screen' 6 | project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android') -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MakeTasks", 3 | "displayName": "MakeTasks" 4 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | plugins: [ 4 | 'react-native-reanimated/plugin', 5 | ['module:react-native-dotenv', { 6 | 'envName': 'APP_ENV', 7 | 'moduleName': '@env', 8 | 'path': '.env', 9 | 'blocklist': null, 10 | 'allowlist': null, 11 | 'blacklist': null, // DEPRECATED 12 | 'whitelist': null, // DEPRECATED 13 | 'safe': false, 14 | 'allowUndefined': true, 15 | 'verbose': false 16 | }] 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@env' { 2 | export const ASYNCSTORAGE_ID_TOKEN: string; 3 | export const ASYNCSTORAGE_USER: string; 4 | export const CLOUDINARY_CLOUD_NAME: string; 5 | export const CLOUDINARY_UPLOAD_PRESET: string; 6 | export const FIREBASE_API_KEY: string; 7 | } -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | ASYNCSTORAGE_ID_TOKEN= Es la clave con la que vas a guardar el idToken en la cache, puedes poner lo que quieras. 2 | ASYNCSTORAGE_USER= Es la clave con la que vas a guardar al usuario autenticado en la cache, es a tu discreción. 3 | CLOUDINARY_CLOUD_NAME= Valor del Cloud Name que se encuentra en el dashboard de Cloudinary. 4 | CLOUDINARY_UPLOAD_PRESET= Nombre del preset que creaste en la configuración de Cloudinary. 5 | FIREBASE_API_KEY= Clave de Firebase, la encuentras en tu google-services.json, toma el valor de current_key. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import { AppRegistry } from 'react-native'; 6 | import App from './App'; 7 | import { name as appName } from './app.json'; 8 | import database from '@react-native-firebase/database'; 9 | 10 | database().setPersistenceEnabled(true); 11 | 12 | AppRegistry.registerComponent(appName, () => App); 13 | -------------------------------------------------------------------------------- /ios/MakeTasks.xcodeproj/xcshareddata/xcschemes/NotesAppRN.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /ios/MakeTasks/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : UIResponder 5 | 6 | @property (nonatomic, strong) UIWindow *window; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /ios/MakeTasks/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | #import 5 | #import 6 | 7 | #ifdef FB_SONARKIT_ENABLED 8 | #import 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import 14 | 15 | static void InitializeFlipper(UIApplication *application) { 16 | FlipperClient *client = [FlipperClient sharedClient]; 17 | SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults]; 18 | [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]]; 19 | [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]]; 20 | [client addPlugin:[FlipperKitReactPlugin new]]; 21 | [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]]; 22 | [client start]; 23 | } 24 | #endif 25 | 26 | @implementation AppDelegate 27 | 28 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 29 | { 30 | #ifdef FB_SONARKIT_ENABLED 31 | InitializeFlipper(application); 32 | #endif 33 | 34 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; 35 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge 36 | moduleName:@"MakeTasks" 37 | initialProperties:nil]; 38 | 39 | if (@available(iOS 13.0, *)) { 40 | rootView.backgroundColor = [UIColor systemBackgroundColor]; 41 | } else { 42 | rootView.backgroundColor = [UIColor whiteColor]; 43 | } 44 | 45 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 46 | UIViewController *rootViewController = [UIViewController new]; 47 | rootViewController.view = rootView; 48 | self.window.rootViewController = rootViewController; 49 | [self.window makeKeyAndVisible]; 50 | return YES; 51 | } 52 | 53 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 54 | { 55 | #if DEBUG 56 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 57 | #else 58 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 59 | #endif 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /ios/MakeTasks/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /ios/MakeTasks/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/MakeTasks/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | MakeTasks 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSExceptionDomains 30 | 31 | localhost 32 | 33 | NSExceptionAllowsInsecureHTTPLoads 34 | 35 | 36 | 37 | 38 | NSLocationWhenInUseUsageDescription 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ios/MakeTasks/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ios/MakeTasks/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ios/MakeTasksTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/MakeTasksTests/NotesAppRNTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface MakeTasksTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation MakeTasksTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 38 | if (level >= RCTLogLevelError) { 39 | redboxError = message; 40 | } 41 | }); 42 | #endif 43 | 44 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 45 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 46 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | 48 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 49 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 50 | return YES; 51 | } 52 | return NO; 53 | }]; 54 | } 55 | 56 | #ifdef DEBUG 57 | RCTSetLogFunction(RCTDefaultLogFunction); 58 | #endif 59 | 60 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 61 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 62 | } 63 | 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../node_modules/react-native/scripts/react_native_pods' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | platform :ios, '11.0' 5 | 6 | target 'MakeTasks' do 7 | config = use_native_modules! 8 | 9 | use_react_native!( 10 | :path => config[:reactNativePath], 11 | # to enable hermes on iOS, change `false` to `true` and then install pods 12 | :hermes_enabled => false 13 | ) 14 | 15 | target 'MakeTasksTests' do 16 | inherit! :complete 17 | # Pods for testing 18 | end 19 | 20 | # Enables Flipper. 21 | # 22 | # Note that if you have use_frameworks! enabled, Flipper will not work and 23 | # you should disable the next line. 24 | use_flipper!() 25 | 26 | post_install do |installer| 27 | react_native_post_install(installer) 28 | __apply_Xcode_12_5_M1_post_install_workaround(installer) 29 | end 30 | end -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: true, 14 | }, 15 | }), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maketasks", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "test": "jest", 10 | "lint": "eslint . --ext .js,.jsx,.ts,.tsx" 11 | }, 12 | "dependencies": { 13 | "@react-native-async-storage/async-storage": "^1.16.1", 14 | "@react-native-community/datetimepicker": "^5.1.0", 15 | "@react-native-community/netinfo": "^8.2.0", 16 | "@react-native-firebase/app": "^14.5.0", 17 | "@react-native-firebase/auth": "^14.5.0", 18 | "@react-native-firebase/database": "^14.5.0", 19 | "@react-native-firebase/messaging": "^14.5.0", 20 | "@react-native-masked-view/masked-view": "^0.2.6", 21 | "@react-navigation/drawer": "^6.3.1", 22 | "@react-navigation/native": "^6.0.8", 23 | "@react-navigation/stack": "^6.1.1", 24 | "cache": "^2.3.1", 25 | "clean": "^4.0.2", 26 | "dayjs": "^1.10.7", 27 | "react": "17.0.2", 28 | "react-native": "0.67.2", 29 | "react-native-dotenv": "^3.3.1", 30 | "react-native-gesture-handler": "^2.2.0", 31 | "react-native-image-picker": "^4.7.3", 32 | "react-native-image-viewing": "^0.2.1", 33 | "react-native-keyboard-aware-scroll-view": "^0.9.5", 34 | "react-native-linear-gradient": "^2.5.6", 35 | "react-native-modal-datetime-picker": "^13.0.1", 36 | "react-native-permissions": "^3.3.0", 37 | "react-native-reanimated": "^2.4.1", 38 | "react-native-safe-area-context": "^3.4.1", 39 | "react-native-screens": "^3.12.0", 40 | "react-native-splash-screen": "^3.3.0", 41 | "react-native-uuid": "^2.0.1", 42 | "react-native-vector-icons": "^9.1.0", 43 | "yup": "^0.32.11" 44 | }, 45 | "devDependencies": { 46 | "@babel/core": "^7.12.9", 47 | "@babel/runtime": "^7.12.5", 48 | "@react-native-community/eslint-config": "^2.0.0", 49 | "@types/jest": "^26.0.23", 50 | "@types/react-native": "^0.66.15", 51 | "@types/react-native-vector-icons": "^6.4.10", 52 | "@types/react-test-renderer": "^17.0.1", 53 | "@typescript-eslint/eslint-plugin": "^5.7.0", 54 | "@typescript-eslint/parser": "^5.7.0", 55 | "babel-jest": "^26.6.3", 56 | "eslint": "^7.14.0", 57 | "jest": "^26.6.3", 58 | "metro-react-native-babel-preset": "^0.66.2", 59 | "react-test-renderer": "17.0.2", 60 | "typescript": "^4.4.4" 61 | }, 62 | "resolutions": { 63 | "@types/react": "^17" 64 | }, 65 | "jest": { 66 | "preset": "react-native", 67 | "moduleFileExtensions": [ 68 | "ts", 69 | "tsx", 70 | "js", 71 | "jsx", 72 | "json", 73 | "node" 74 | ] 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/api/tasksApi.ts: -------------------------------------------------------------------------------- 1 | import { FIREBASE_API_KEY } from '@env'; 2 | 3 | /** 4 | * Esta API sirve para hacer la authenticación de usuarios mediante el servicio de Firebase. 5 | * Unicamente se usa para renovar la autenticación puesto que el sdk de Firebase no trae 6 | * una función para hacerlo. 7 | */ 8 | export const authApi = (method: string, endPoint: string, data: any) => { 9 | const baseUrl = 'https://identitytoolkit.googleapis.com/v1/accounts'; 10 | 11 | return fetch(`${ baseUrl }${ endPoint }?key=${ FIREBASE_API_KEY }`, { 12 | method, 13 | body: JSON.stringify(data) 14 | }); 15 | } -------------------------------------------------------------------------------- /src/assets/default-user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristhDev/MakeTasks/694b95506cd17056003eae5b4ca9feb5e8aa364e/src/assets/default-user.jpg -------------------------------------------------------------------------------- /src/components/auth/LoginForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { StyleSheet, useWindowDimensions, View } from 'react-native'; 3 | import { useNavigation } from '@react-navigation/native'; 4 | import { object, string } from 'yup'; 5 | 6 | /* Components */ 7 | import { FormBtn } from '../ui/FormBtn'; 8 | import { FormGroup } from '../ui/FormGroup'; 9 | 10 | /* Hooks */ 11 | import useAuth from '../../hooks/useAuth'; 12 | import useForm from '../../hooks/useForm'; 13 | import useStatus from '../../hooks/useStatus'; 14 | 15 | /* Esquema de validación de los campos del formulario de Login */ 16 | const loginFormSchema = object().shape({ 17 | email: string() 18 | .required('El correo es requerido') 19 | .email('Por favor escriba su correo correctamente'), 20 | password: string() 21 | .required('La contraseña es requerida') 22 | .min(6, 'La contraseña debe tener al menos 6 caracteres') 23 | }); 24 | 25 | /** 26 | * Componente para mostrar el formulario de Login y realizar 27 | * la autenticación del usuario 28 | */ 29 | export const LoginForm = () => { 30 | const { height, width } = useWindowDimensions(); 31 | const navigation = useNavigation(); 32 | 33 | const { signIn } = useAuth(); 34 | const { setMsgError } = useStatus(); 35 | const { form, onChangeField, resetForm } = useForm({ email: '', password: '' }); 36 | 37 | const windowHeight = (height >= 720 && height > width) ? height : 720; 38 | 39 | /* Función para realizar la autenticación */ 40 | const handleSubmit = async () => { 41 | try { 42 | await loginFormSchema.validate(form); 43 | signIn(form.email, form.password); 44 | } 45 | catch (error: any) { 46 | const values = Object.values(error.errors) as string[]; 47 | setMsgError(values[0]); 48 | } 49 | } 50 | 51 | /* useEffect para resetear el formulario al cambiar de pantalla */ 52 | useEffect(() => { 53 | const unSubscribe = navigation.addListener('blur', () => { 54 | resetForm(); 55 | }); 56 | 57 | return unSubscribe; 58 | }, [ navigation ]); 59 | 60 | return ( 61 | width) ? windowHeight * 0.14 : width * 0.05 65 | }} 66 | > 67 | { /* Campo de Correo */ } 68 | onChangeField(text, 'email') } 73 | placeholder="Ingresa tu correo" 74 | type="emailAddress" 75 | keyboardType="email-address" 76 | /> 77 | 78 | { /* Campo de Contraseña */ } 79 | onChangeField(text, 'password') } 84 | placeholder="Ingresa tu contraseña" 85 | type="password" 86 | /> 87 | 88 | { /* Espaciador */ } 89 | 90 | 91 | { /* Botón de inicio de sesión */ } 92 | 96 | 97 | ); 98 | } 99 | 100 | /* Estilos del componente */ 101 | const styles = StyleSheet.create({ 102 | formContainer: { 103 | alignItems: 'center', 104 | flex: 1, 105 | justifyContent: 'flex-end', 106 | zIndex: 3 107 | } 108 | }); -------------------------------------------------------------------------------- /src/components/auth/ProfileForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Image, StyleSheet, TouchableOpacity, useWindowDimensions, View } from 'react-native'; 3 | import { object, string } from 'yup'; 4 | 5 | /* Components */ 6 | import { FormBtn } from '../ui/FormBtn'; 7 | import { FormGroup } from '../ui/FormGroup'; 8 | 9 | /* Hooks */ 10 | import useAuth from '../../hooks/useAuth'; 11 | import useForm from '../../hooks/useForm'; 12 | import useImage from '../../hooks/useImage'; 13 | import useStatus from '../../hooks/useStatus'; 14 | 15 | /* Theme */ 16 | import { colors } from '../../theme/app-theme'; 17 | 18 | /* Esquema de validación de los campos del formulario del perfil */ 19 | const profileFormSchema = object().shape({ 20 | name: string() 21 | .required('El nombre no puede estar vacío'), 22 | email: string() 23 | .required('El correo no puede estar vacío') 24 | .email('El correo no es válido') 25 | }); 26 | 27 | /** 28 | * Componente para mostrar el formulario del perfil y actualizar 29 | * los datos del usuario 30 | */ 31 | export const ProfileForm = () => { 32 | /** 33 | * Primer state para desabilita el botón de guardar cambios cuando 34 | * este cargando 35 | */ 36 | const [ isBtnDisabled, setIsBtnDisabled ] = useState(false); 37 | const { height, width } = useWindowDimensions(); 38 | 39 | const { user, updateProfile } = useAuth(); 40 | const { form, onChangeField } = useForm({ name: user.name, email: user.email }); 41 | const { image, handleTakeImageFromLibrary, setImage } = useImage(); 42 | const { setMsgError } = useStatus(); 43 | 44 | const windowHeight = (height >= 720 && height > width) ? height : 720; 45 | 46 | /* Función para actualizar el perfil del usuario */ 47 | const handleSubmit = async () => { 48 | setIsBtnDisabled(true); 49 | 50 | try { 51 | await profileFormSchema.validate(form); 52 | await updateProfile({ ...form, image: image?.base64 || (user?.image || '') }); 53 | setIsBtnDisabled(false); 54 | } 55 | catch (error: any) { 56 | const values = Object.values(error.errors) as string[]; 57 | setMsgError(values[0]); 58 | setIsBtnDisabled(false); 59 | } 60 | } 61 | 62 | /** 63 | * useEffect para actualizar la imagen del usuario en el state 64 | * del hook useImage 65 | */ 66 | useEffect(() => { 67 | setImage({ uri: user?.image || '' }); 68 | }, [ user ]); 69 | 70 | return ( 71 | width) ? windowHeight * 0.1 : width * 0.05, 75 | }} 76 | > 77 | {/* Campo de Imagen */} 78 | 79 | 84 | 92 | 93 | 94 | 95 | {/* Campo de Nombre */} 96 | onChangeField(text, 'name') } 101 | placeholder="Ingrese su nombre" 102 | type="name" 103 | /> 104 | 105 | {/* Campo de Correo */} 106 | onChangeField(text, 'email') } 111 | placeholder="Ingrese su correo" 112 | type="emailAddress" 113 | keyboardType="email-address" 114 | /> 115 | 116 | {/* Espaciador */} 117 | 118 | 119 | {/* Botón para actualizar el perfil */} 120 | 125 | 126 | ); 127 | } 128 | 129 | /* Estilos del componente */ 130 | const styles = StyleSheet.create({ 131 | profileForm: { 132 | alignItems: 'center', 133 | flex: 1, 134 | justifyContent: 'flex-end', 135 | zIndex: 2, 136 | }, 137 | 138 | profileImage: { 139 | alignItems: 'center', 140 | bottom: 40, 141 | marginBottom: -55 142 | }, 143 | 144 | profileImageContainer: { 145 | alignItems: 'center', 146 | backgroundColor: colors.light, 147 | borderRadius: 999, 148 | borderColor: colors.light, 149 | borderWidth: 8, 150 | elevation: 6, 151 | justifyContent: 'center', 152 | overflow: 'hidden', 153 | shadowColor: '#000', 154 | shadowOffset: { 155 | height: 5, 156 | width: 0, 157 | }, 158 | shadowOpacity: 0.30, 159 | shadowRadius: 6.25, 160 | height: 230, 161 | width: 230 162 | } 163 | }); -------------------------------------------------------------------------------- /src/components/auth/RegisterForm.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, useWindowDimensions, View } from 'react-native'; 3 | import { object, string } from 'yup'; 4 | 5 | /* Components */ 6 | import { FormBtn } from '../ui/FormBtn'; 7 | import { FormGroup } from '../ui/FormGroup'; 8 | 9 | /* Hooks */ 10 | import useAuth from '../../hooks/useAuth'; 11 | import useForm from '../../hooks/useForm'; 12 | import useStatus from '../../hooks/useStatus'; 13 | 14 | /* Esquema de validación de los campos del formulario de Register */ 15 | const registerFormSchema = object().shape({ 16 | name: string() 17 | .required('El nombre es requerido'), 18 | email: string() 19 | .required('El correo es requerido') 20 | .email('Por favor escriba su correo correctamente'), 21 | password: string() 22 | .required('La contraseña es requerida') 23 | .min(6, 'La contraseña debe tener al menos 6 caracteres') 24 | }); 25 | 26 | /** 27 | * Componente para mostrar el formulario de Register y realizar 28 | * tanto registro como autenticación del usuario 29 | */ 30 | export const RegisterForm = () => { 31 | const { height, width } = useWindowDimensions(); 32 | 33 | const { signUp } = useAuth(); 34 | const { setMsgError } = useStatus(); 35 | const { form, onChangeField} = useForm({ name: '', email: '', password: '' }); 36 | 37 | const windowHeight = (height >= 720 && height > width) ? height : 720; 38 | 39 | /** 40 | * Función para realizar el registro y autenticación. 41 | * En este caso no es necesario realizar reseteo del formulario porque el 42 | * regresar al login o autenticarse se resetea el estado del componente, 43 | * no sucede lo mismo en LoginForm poque ese se muestra en la primera pantalla 44 | */ 45 | const handleSubmit = async () => { 46 | try { 47 | await registerFormSchema.validate(form); 48 | signUp({ ...form }); 49 | } 50 | catch (error: any) { 51 | const values = Object.values(error.errors) as string[]; 52 | setMsgError(values[0]); 53 | } 54 | } 55 | 56 | return ( 57 | width) ? windowHeight * 0.09 : width * 0.02, 61 | }} 62 | > 63 | { /* Campo del Nombre */ } 64 | onChangeField(text, 'name') } 69 | inputValue={ form.name } 70 | icon="person-outline" 71 | /> 72 | 73 | { /* Campo del Correo */ } 74 | onChangeField(text, 'email') } 79 | inputValue={ form.email } 80 | icon="mail-outline" 81 | keyboardType="email-address" 82 | /> 83 | 84 | { /* Campo de Contraseña */ } 85 | onChangeField(text, 'password') } 90 | inputValue={ form.password } 91 | icon="key-outline" 92 | /> 93 | 94 | { /* Espaciador */ } 95 | width) ? 55 : 30 }} /> 96 | 97 | { /* Botón de registro */ } 98 | 102 | 103 | ); 104 | } 105 | 106 | /* Estilos del componente */ 107 | const styles = StyleSheet.create({ 108 | formContainer: { 109 | alignItems: 'center', 110 | flex: 1, 111 | justifyContent: 'flex-end', 112 | zIndex: 3 113 | } 114 | }); -------------------------------------------------------------------------------- /src/components/tasks/TaskDeleteModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, TouchableHighlight, View } from 'react-native'; 3 | import { useNavigation } from '@react-navigation/native'; 4 | 5 | /* Hooks */ 6 | import useTasks from '../../hooks/useTasks'; 7 | 8 | /* Interfaces */ 9 | import { TasksStatus } from '../../interfaces/tasks'; 10 | 11 | /* Theme */ 12 | import { colors } from '../../theme/app-theme'; 13 | 14 | /* Propiedades del componente */ 15 | interface Props { 16 | closeModal: () => void; 17 | taskStatus: TasksStatus; 18 | } 19 | 20 | /* Componente para mostrar el modal de confirmación de eliminación de tareas */ 21 | export const TaskDeleteModal = ({ closeModal, taskStatus }: Props) => { 22 | const { navigate } = useNavigation(); 23 | const { removeTask, selectedTask } = useTasks(); 24 | 25 | /* Función para eliminar la tarea seleccionada */ 26 | const handleRemoveTask = async () => { 27 | const deleted = await removeTask(selectedTask.id, taskStatus); 28 | 29 | /* Si se eliminó la tarea, se redirige a la pantalla de inico */ 30 | if (deleted) { 31 | closeModal(); 32 | navigate('HomeScreen' as never); 33 | } 34 | } 35 | 36 | return ( 37 | 38 | { /* Texto que corresponde a la acción a realizar */ } 39 | 40 | ¿Estás seguro de eliminar está tarea? 41 | 42 | 43 | { /* Botones del modal */ } 44 | 45 | { /* Boton de cancelación */ } 46 | 52 | Cancelar 53 | 54 | 55 | { /* Boton de confirmación */ } 56 | 62 | Eliminar 63 | 64 | 65 | 66 | ); 67 | } 68 | 69 | /* Estilos del componente */ 70 | const styles = StyleSheet.create({ 71 | modalTitleContainer: { 72 | marginTop: 20, 73 | }, 74 | 75 | modalTitle: { 76 | color: colors.lightRed, 77 | fontSize: 16 78 | }, 79 | 80 | modalBtnsContainer: { 81 | marginTop: 10, 82 | flexDirection: 'row', 83 | justifyContent: 'flex-end' 84 | }, 85 | 86 | modalBtn: { 87 | backgroundColor: colors.lightBlue, 88 | borderRadius: 20, 89 | marginLeft: 10, 90 | }, 91 | 92 | modalBtnText: { 93 | color: colors.light, 94 | fontSize: 17, 95 | paddingHorizontal: 20, 96 | paddingVertical: 5, 97 | marginBottom: 1 98 | } 99 | }); -------------------------------------------------------------------------------- /src/components/tasks/TaskItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, TouchableHighlight, useWindowDimensions, View } from 'react-native'; 3 | import { useNavigation } from '@react-navigation/native'; 4 | import dayjs from 'dayjs'; 5 | import Icon from 'react-native-vector-icons/Ionicons'; 6 | 7 | /* Hooks */ 8 | import useTasks from '../../hooks/useTasks'; 9 | 10 | /* Interfaces */ 11 | import { Task, TasksStatus } from '../../interfaces/tasks'; 12 | 13 | /* Theme */ 14 | import { colors } from '../../theme/app-theme'; 15 | 16 | /* Propiedades del componente */ 17 | interface Props { 18 | task: Task; 19 | taskStatus: TasksStatus; 20 | } 21 | 22 | /* Componente para mostrar cada Item de la lista de tareas */ 23 | export const TaskItem = ({ task, taskStatus }: Props) => { 24 | const { navigate } = useNavigation(); 25 | const { width } = useWindowDimensions(); 26 | 27 | const { setSelectedTask, toggleTaskCompeted } = useTasks(); 28 | 29 | /** 30 | * Función para seleccionar una tarea y navegar a la pantalla 31 | * de edición de tareas. 32 | */ 33 | const handleGoToEditTask = () => { 34 | setSelectedTask(task); 35 | 36 | navigate( 37 | 'CreateTaskScreen' as never, 38 | { title: 'Editar Tarea', taskStatus } as never 39 | ); 40 | } 41 | 42 | /* Función para marcar como completa e imcompleta una tarea */ 43 | const handleToggleTask = () => toggleTaskCompeted(task, taskStatus); 44 | 45 | return ( 46 | 47 | 48 | { /* Contenedor del titulo de la tarea */ } 49 | 50 | 51 | { /* Boton para ir a la edición de la tarea */ } 52 | 320) ? 60 : 50, 58 | width: (width > 320) ? 60 : 50 59 | }} 60 | underlayColor={ colors.darkRed } 61 | > 62 | 320) ? 30 : 25 } 65 | color={ colors.light } 66 | /> 67 | 68 | 69 | { /* Titulo de la tarea */ } 70 | 320) ? 20 : 17, 74 | }} 75 | > 76 | { (task.title.length > 17) ? task.title.slice(0, 17) + '...' : task.title } 77 | 78 | 79 | { /* Boton para marcar como completa o incompleta una tarea */ } 80 | 90 | 96 | 97 | 98 | 99 | { /* Cuerpo de la tarea */ } 100 | 101 | 102 | { /* Texto del cuerpo de la tarea */ } 103 | 104 | { (task.body.length > 120) ? task.body.slice(0, 120) + '...' : task.body } 105 | 106 | 107 | { /* Contenedor de fechas de la tarea */ } 108 | 109 | 110 | { /* Fecha de finalización de la tarea */ } 111 | 118 | { 119 | (task.completed) 120 | ? 'Entregada' 121 | : `Entregar el ${ dayjs(task.finalDate).format('DD/MM/YYYY') }` 122 | } 123 | 124 | 125 | { /* Fecha de creación de la tarea */ } 126 | { dayjs(task.createdAt).format('DD/MM/YYYY') } 127 | 128 | 129 | 130 | ); 131 | } 132 | 133 | /* Estilos del componente */ 134 | const styles = StyleSheet.create({ 135 | task: { 136 | backgroundColor: colors.lightGray, 137 | borderRadius: 20, 138 | marginBottom: 45, 139 | overflow: 'hidden', 140 | width: '88%', 141 | shadowColor: 'rgba(0, 0, 0, 0.8)', 142 | shadowOffset: { 143 | width: 0, 144 | height: 7, 145 | }, 146 | shadowOpacity: 0.30, 147 | shadowRadius: 6.25, 148 | elevation: 7 149 | }, 150 | 151 | taskTitleContainer: { 152 | flex: 1, 153 | backgroundColor: colors.light, 154 | borderRadius: 20, 155 | flexDirection: 'row', 156 | alignItems: 'center', 157 | justifyContent: 'space-between', 158 | shadowColor: 'rgba(0, 0, 0, 0.4)', 159 | shadowOffset: { 160 | width: 0, 161 | height: 5, 162 | }, 163 | shadowOpacity: 0.30, 164 | shadowRadius: 6.25, 165 | elevation: 5, 166 | zIndex: 1 167 | }, 168 | 169 | taskTitleBox: { 170 | alignItems: 'center', 171 | justifyContent: 'center', 172 | backgroundColor: colors.lightRed, 173 | borderRadius: 20 174 | }, 175 | 176 | taskTitle: { 177 | color: colors.darkBlue, 178 | fontWeight: 'bold', 179 | flex: 1, 180 | marginLeft: 20, 181 | }, 182 | 183 | taskStatus: { 184 | alignItems: 'center', 185 | justifyContent: 'center', 186 | borderRadius: 999, 187 | width: 40, 188 | height: 40, 189 | marginRight: 12 190 | }, 191 | 192 | taskBodyContainer: { 193 | backgroundColor: colors.lightGray, 194 | }, 195 | 196 | taskBody: { 197 | color: '#000', 198 | textAlign: 'justify', 199 | padding: 15, 200 | }, 201 | 202 | taskDates: { 203 | flexDirection: 'row', 204 | justifyContent: 'space-between', 205 | }, 206 | 207 | taskDate: { 208 | paddingTop: 5, 209 | padding: 10, 210 | } 211 | }); -------------------------------------------------------------------------------- /src/components/tasks/TaskOptionBtn.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, TouchableOpacity, useWindowDimensions } from 'react-native'; 3 | 4 | /* Theme */ 5 | import { colors } from '../../theme/app-theme'; 6 | 7 | /* Propiedades del componente */ 8 | interface Props { 9 | text: string; 10 | isAcitve: boolean; 11 | onPress: () => void; 12 | } 13 | 14 | /* Componente que muestra un boton de opción para filtrar las tareas */ 15 | export const TaskOptionBtn = ({ isAcitve, text, onPress, }: Props) => { 16 | const { width } = useWindowDimensions(); 17 | 18 | return ( 19 | 29 | 320) ? 17 : 14, 37 | paddingVertical: (width > 320) ? 15 : 10 38 | } 39 | : { 40 | ...styles.taskOptionsText, 41 | fontSize: (width > 320) ? 17 : 14, 42 | paddingVertical: (width > 320) ? 15 : 10 43 | } 44 | } 45 | > 46 | { text } 47 | 48 | 49 | ); 50 | } 51 | 52 | /* Estilos del componente */ 53 | const styles = StyleSheet.create({ 54 | taskOptionsBtn: { 55 | borderRadius: 20, 56 | flex: 1 57 | }, 58 | 59 | taskOptionsBtnActive: { 60 | backgroundColor: colors.lightMediumGray 61 | }, 62 | 63 | taskOptionsText: { 64 | color: '#000', 65 | marginBottom: 2, 66 | textAlign: 'center' 67 | }, 68 | 69 | taskOptionsTextActive: { 70 | fontWeight: 'bold' 71 | } 72 | }); -------------------------------------------------------------------------------- /src/components/tasks/TasksList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ScrollView, StyleSheet, useWindowDimensions, View } from 'react-native'; 3 | 4 | /* Components */ 5 | import { TaskItem } from './TaskItem'; 6 | 7 | /* Hooks */ 8 | import useTasks from '../../hooks/useTasks'; 9 | 10 | /* Interfaces */ 11 | import { TasksStatus } from '../../interfaces/tasks'; 12 | 13 | /* Propiedades del componente */ 14 | interface Props { 15 | taskStatus: TasksStatus; 16 | tasksType: 'default' | 'searching'; 17 | } 18 | 19 | /** 20 | * Componente que muestra la lista de tareas en donde se coloque, 21 | * muestra tanto las tareas(todas, completas e incompletas) y las buscadas 22 | */ 23 | export const TasksList = ({ taskStatus, tasksType }: Props) => { 24 | const { width, height } = useWindowDimensions(); 25 | 26 | const { selectedTasks, searchingTasks } = useTasks(); 27 | 28 | const windowHeight = (height >= 720 && height > width) ? height : 720; 29 | 30 | /** 31 | * Objeto que contiene los tipos de tareas que seran selccionadas 32 | * por la propiedad tasksType 33 | */ 34 | const tasks = { 35 | default: selectedTasks, 36 | searching: searchingTasks 37 | } 38 | 39 | return ( 40 | 45 | 46 | {/* Espaciador */} 47 | 48 | 49 | { 50 | tasks[tasksType].map((task) => 51 | // Componente para mostrar tarea 52 | 57 | ) 58 | } 59 | 60 | 61 | ); 62 | } 63 | 64 | /* Estilos del componente */ 65 | const styles = StyleSheet.create({ 66 | taskScrollView: { 67 | flex: 1, 68 | zIndex: 1 69 | }, 70 | 71 | tasksLists: { 72 | alignItems: 'center', 73 | flex: 1, 74 | zIndex: 1 75 | } 76 | }); -------------------------------------------------------------------------------- /src/components/tasks/TasksLoader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, ActivityIndicator, StyleSheet, useWindowDimensions } from 'react-native'; 3 | 4 | /* Theme */ 5 | import { colors } from '../../theme/app-theme'; 6 | 7 | /* Componente para mostrar una carga de datos, en este caso tareas */ 8 | export const TasksLoader = () => { 9 | const { height, width } = useWindowDimensions(); 10 | 11 | return ( 12 | width) ? 200 : 0 17 | }} 18 | > 19 | { /* Spiner de carga */ } 20 | 24 | 25 | ); 26 | } 27 | 28 | /* Estilos del componente */ 29 | const styles = StyleSheet.create({ 30 | tasksLoader: { 31 | flex: 1, 32 | justifyContent: 'center', 33 | alignItems: 'center', 34 | zIndex: 1 35 | } 36 | }); -------------------------------------------------------------------------------- /src/components/ui/Fab.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleProp, StyleSheet, TouchableHighlight, TouchableHighlightProps, View, ViewStyle } from 'react-native'; 3 | import Icon from 'react-native-vector-icons/Ionicons'; 4 | 5 | /* Theme */ 6 | import { colors } from '../../theme/app-theme'; 7 | 8 | /* Propieades del componente */ 9 | interface Props { 10 | icon: string; 11 | onPress: () => void; 12 | iconSize?: number; 13 | isDisabled?: boolean; 14 | style?: StyleProp; 15 | buttonStyle?: StyleProp; 16 | iconStyle?: StyleProp; 17 | } 18 | 19 | /* Componente para mostrar un Boton Flotante */ 20 | export const Fab = ({ onPress, icon, iconSize, isDisabled = false, style, buttonStyle, iconStyle }: Props) => { 21 | return ( 22 | 23 | 30 | 36 | 37 | 38 | ); 39 | } 40 | 41 | /* Estilos del componente */ 42 | const styles = StyleSheet.create({ 43 | fabContainer: { 44 | position: 'absolute', 45 | zIndex: 2 46 | }, 47 | 48 | fab: { 49 | alignItems: 'center', 50 | backgroundColor: colors.lightRed, 51 | borderRadius: 20, 52 | justifyContent: 'center', 53 | shadowColor: 'rgba(0, 0, 0, 0.6)', 54 | shadowOffset: { 55 | width: 0, 56 | height: 7, 57 | }, 58 | shadowOpacity: 0.30, 59 | shadowRadius: 6.25, 60 | height: 55, 61 | elevation: 7, 62 | width: 55 63 | } 64 | }); -------------------------------------------------------------------------------- /src/components/ui/FormBtn.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, TouchableHighlight } from 'react-native'; 3 | 4 | /* Theme */ 5 | import { colors } from '../../theme/app-theme'; 6 | 7 | /* Propiedades del componente */ 8 | interface Props { 9 | text: string; 10 | onPress: () => void; 11 | isDisabled?: boolean; 12 | } 13 | 14 | /* Componente para mostrar el boton de los formularios */ 15 | export const FormBtn = ({ text, onPress, isDisabled = false }: Props) => { 16 | return ( 17 | 24 | { text } 25 | 26 | ); 27 | } 28 | 29 | /* Estilos del componente */ 30 | const styles = StyleSheet.create({ 31 | btnSubmit: { 32 | backgroundColor: colors.lightRed, 33 | borderRadius: 50 34 | }, 35 | 36 | btnSubmitText: { 37 | color: colors.light, 38 | fontSize: 20, 39 | marginBottom: 2, 40 | paddingVertical: 8, 41 | paddingHorizontal: 25 42 | }, 43 | }); -------------------------------------------------------------------------------- /src/components/ui/FormGroup.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, TextInput, TextInputProps, useWindowDimensions, View } from 'react-native'; 3 | import Icon from 'react-native-vector-icons/Ionicons'; 4 | 5 | /* Theme */ 6 | import { colors } from '../../theme/app-theme'; 7 | 8 | /* Propiedades del componente */ 9 | interface Props { 10 | label: string; 11 | placeholder: string; 12 | type: TextInputProps['textContentType']; 13 | keyboardType?: TextInputProps['keyboardType']; 14 | inputValue: string; 15 | icon?: string; 16 | onChangeText: (text: string, ...rest: any) => void; 17 | } 18 | 19 | /* Componente para mostrar un campo del formulario */ 20 | export const FormGroup = ({ label, placeholder, onChangeText, type, keyboardType = 'default', inputValue, icon }: Props) => { 21 | const { width, height } = useWindowDimensions(); 22 | 23 | return ( 24 | width) ? width * 0.82 : width * 0.6, 28 | }} 29 | > 30 | { /* Etiqueta del campo */ } 31 | { label } 32 | 33 | { /* Caja de texto para el campo */ } 34 | 35 | onChangeText(text) } 42 | placeholder={ placeholder } 43 | placeholderTextColor={ colors.textGray } 44 | secureTextEntry={ type === 'password' } 45 | style={ styles.formGroupInput } 46 | textContentType={ type } 47 | value={ inputValue } 48 | /> 49 | 50 | { 51 | /* Evaluación para mostrar el icono */ 52 | icon && ( 53 | 58 | ) 59 | } 60 | 61 | 62 | ); 63 | } 64 | 65 | /* Estilos del componente */ 66 | const styles = StyleSheet.create({ 67 | formGroup: { 68 | alignItems: 'center', 69 | marginTop: 30 70 | }, 71 | 72 | formGroupText: { 73 | alignSelf: 'flex-start', 74 | color: colors.light, 75 | fontSize: 20 76 | }, 77 | 78 | inputContainer: { 79 | alignItems: 'center', 80 | backgroundColor: colors.darkBlue, 81 | borderRadius: 10, 82 | elevation: 6, 83 | flexDirection: 'row', 84 | marginTop: 10, 85 | paddingHorizontal: 10, 86 | shadowColor: '#000', 87 | shadowOffset: { 88 | height: 5, 89 | width: 0, 90 | }, 91 | shadowOpacity: 0.30, 92 | shadowRadius: 6.25, 93 | width: '98%', 94 | }, 95 | 96 | formGroupInput: { 97 | color: colors.light, 98 | fontSize: 18, 99 | width: '90%' 100 | } 101 | }); -------------------------------------------------------------------------------- /src/components/ui/ModalPermissions.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, View, TouchableHighlight } from 'react-native'; 3 | 4 | /* Theme */ 5 | import { colors } from '../../theme/app-theme'; 6 | 7 | /* Propiedades del componente */ 8 | interface Props { 9 | title: string; 10 | onPress: () => void; 11 | onCancel: () => void; 12 | } 13 | 14 | /* Componente para mostrar el contenido del modal de los permisos */ 15 | export const ModalPermissions = ({ title, onPress, onCancel }: Props) => { 16 | return ( 17 | 18 | 19 | { /* Título o mensaje acerca de los permisos */ } 20 | 21 | { title } 22 | 23 | 24 | { /* Botón para aceptar los permisos */ } 25 | 26 | 27 | { /* Botón para cancelar los permisos */ } 28 | 37 | Cancelar 38 | 39 | 40 | { /* Botón para aceptar los permisos */ } 41 | 47 | Dar permiso 48 | 49 | 50 | 51 | ); 52 | } 53 | 54 | /* Estilos del componente */ 55 | const styles = StyleSheet.create({ 56 | modalPermissionsContainer: { 57 | flex: 1, 58 | justifyContent: 'space-between', 59 | }, 60 | 61 | modalPermissionsInfoContainer: { 62 | marginTop: 10, 63 | padding: 5 64 | }, 65 | 66 | modalPermissionsInfo: { 67 | color: colors.lightRed, 68 | fontSize: 16, 69 | }, 70 | 71 | modalPermissionsActions: { 72 | marginTop: 10, 73 | flexDirection: 'row', 74 | justifyContent: 'flex-end' 75 | }, 76 | 77 | modalPermissionsBtn: { 78 | backgroundColor: colors.lightBlue, 79 | borderRadius: 20 80 | }, 81 | 82 | modalPermissionsBtnText: { 83 | color: colors.light, 84 | fontSize: 17, 85 | paddingHorizontal: 20, 86 | paddingVertical: 5, 87 | marginBottom: 1 88 | } 89 | }); -------------------------------------------------------------------------------- /src/components/ui/ModalStatus.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, TouchableHighlight, View } from 'react-native'; 3 | 4 | /* Theme */ 5 | import { colors } from '../../theme/app-theme'; 6 | 7 | /* Propiedades del componente */ 8 | interface Props { 9 | msgStatus: string; 10 | onClose: () => void; 11 | } 12 | 13 | /** 14 | * Componente para mostrar el modal de los estados de la app, tanto 15 | * errores como mensajes de exito 16 | */ 17 | export const ModalStatus = ({ msgStatus, onClose }: Props) => { 18 | return ( 19 | 20 | { /* Contenedor del mensaje del modal */ } 21 | 22 | { msgStatus } 23 | 24 | 25 | { /* Contenedor de lo botones del modal */ } 26 | 27 | 33 | Está bien 34 | 35 | 36 | 37 | ); 38 | } 39 | 40 | /* Estilos del componente */ 41 | const styles = StyleSheet.create({ 42 | modalStatusContent: { 43 | marginTop: 10, 44 | padding: 5 45 | }, 46 | 47 | modalStatusContentText: { 48 | color: colors.lightRed, 49 | fontSize: 16 50 | }, 51 | 52 | modalStatusActions: { 53 | marginTop: 10, 54 | flexDirection: 'row', 55 | justifyContent: 'flex-end' 56 | }, 57 | 58 | modalStatusBtn: { 59 | backgroundColor: colors.lightBlue, 60 | borderRadius: 20, 61 | }, 62 | 63 | modalStatusBtnText: { 64 | color: colors.light, 65 | fontSize: 17, 66 | paddingHorizontal: 20, 67 | paddingVertical: 5, 68 | marginBottom: 1 69 | } 70 | }); -------------------------------------------------------------------------------- /src/components/ui/ScreenTitle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle } from 'react-native'; 3 | 4 | /* Theme */ 5 | import { colors } from '../../theme/app-theme'; 6 | 7 | /* Propiedades del componente */ 8 | interface Props { 9 | title: string; 10 | styleTitleContainer?: StyleProp; 11 | styleTitleText?: StyleProp 12 | } 13 | 14 | /* Componente que muestra el título de la pantalla */ 15 | export const ScreenTitle = ({ title, styleTitleContainer, styleTitleText }: Props) => { 16 | return ( 17 | 18 | { title } 19 | 20 | ); 21 | } 22 | 23 | /* Estilos del componente */ 24 | const styles = StyleSheet.create({ 25 | screenTitle: { 26 | width: 350, 27 | height: 300, 28 | transform: [{ rotate: '20deg' }], 29 | top: -120, 30 | left: -20, 31 | borderBottomEndRadius: 999, 32 | backgroundColor: colors.lightRed, 33 | position: 'absolute', 34 | zIndex: 888, 35 | }, 36 | 37 | screenTitleText: { 38 | color: colors.light, 39 | fontSize: 40, 40 | fontWeight: 'bold', 41 | transform: [{ rotate: '-20deg' }], 42 | top: 120, 43 | left: 50 44 | } 45 | }); -------------------------------------------------------------------------------- /src/components/ui/SearchBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, TextInput, StyleSheet, StyleProp, ViewStyle, TouchableOpacity } from 'react-native'; 3 | import Icon from 'react-native-vector-icons/Ionicons'; 4 | 5 | /* Theme */ 6 | import { colors } from '../../theme/app-theme'; 7 | 8 | /* Propiedades del componente */ 9 | interface Props { 10 | value: string; 11 | onTextChange: (text: string) => void; 12 | onSearchPress: () => void; 13 | searchContainerStyle?: StyleProp; 14 | } 15 | 16 | /* Componente para mostrar un barra de busqueda */ 17 | export const SearchBar = ({ value, onSearchPress, onTextChange, searchContainerStyle }: Props) => { 18 | return ( 19 | 20 | { /* Caja de texto para escribir la busqueda */ } 21 | onTextChange(text) } 26 | placeholder="Buscar tarea" 27 | placeholderTextColor="gray" 28 | style={ styles.searchInput } 29 | value={ value } 30 | /> 31 | 32 | { /* Botón para buscar */ } 33 | 38 | 43 | 44 | 45 | ); 46 | } 47 | 48 | /* Estilos del componente */ 49 | const styles = StyleSheet.create({ 50 | searchContainer: { 51 | alignItems: 'center', 52 | backgroundColor: colors.lightGray, 53 | borderRadius: 20, 54 | elevation: 7, 55 | flexDirection: 'row', 56 | height: 55, 57 | justifyContent: 'center', 58 | paddingHorizontal: 5, 59 | shadowColor: 'rgba(0, 0, 0, 0.6)', 60 | shadowOffset: { 61 | height: 7, 62 | width: 0, 63 | }, 64 | shadowOpacity: 0.30, 65 | shadowRadius: 6.25, 66 | width: '90%', 67 | zIndex: 999 68 | }, 69 | 70 | searchInput: { 71 | color: '#000', 72 | fontSize: 18, 73 | width: '85%' 74 | }, 75 | 76 | searchBtn: { 77 | alignItems: 'center', 78 | justifyContent: 'center' 79 | } 80 | }); -------------------------------------------------------------------------------- /src/context/auth/AuthContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | /* Interfaces */ 4 | import { UpdateProfileData, User } from '../../interfaces/auth'; 5 | 6 | /** 7 | * Definicion del tipo para las propiedades del 8 | * Contexto de Autenticación 9 | */ 10 | 11 | /** 12 | * Este contexto se usa para manejar la información de autenticación, 13 | * se conforma de las siguientes propiedades: 14 | * 15 | * isAuthenticated: sirve para saber si el usuario está autenticado o no 16 | * isAuthLoading: sirve manejar la carga de la información de autenticación 17 | * idToken: es el token de autenticación del usuario 18 | * user: es el usuario autenticado 19 | * singUp: es una función para registrar un usuario 20 | * signIn: es una función para iniciar sesión 21 | * signOut: es una función para cerrar sesión 22 | * renewAuth: es una función para renovar la autenticación del usuario 23 | * checkAuth: es una función para verificar la autenticación del usuario 24 | * updateProfile: es una función para actualizar el perfil del usuario 25 | */ 26 | 27 | export type AuthContextProps = { 28 | isAuthenticated: boolean; 29 | isAuthLoading: boolean; 30 | idToken: string; 31 | user: User 32 | signUp: (data: { name: string, email: string, password: string }) => Promise; 33 | signIn: (email: string, password: string) => Promise; 34 | signOut: () => void; 35 | renewAuth: () => Promise; 36 | checkAuth: () => Promise; 37 | updateProfile: (userData: UpdateProfileData) => Promise; 38 | } 39 | 40 | /* Creación del contexto */ 41 | const AuthContext = createContext({} as AuthContextProps); 42 | 43 | export default AuthContext; -------------------------------------------------------------------------------- /src/context/auth/authReducer.ts: -------------------------------------------------------------------------------- 1 | 2 | /* Interfaces */ 3 | import { AuthAction, AuthState } from '../../interfaces/auth'; 4 | 5 | /* Reducer para manejar el state de la autentiación */ 6 | const authReducer = (state: AuthState, action: AuthAction): AuthState => { 7 | switch (action.type) { 8 | 9 | /* Setear el usuario */ 10 | case 'userLogin': 11 | return { 12 | ...state, 13 | isAuthenticated: true, 14 | isAuthLoading: false, 15 | user: action.payload.user, 16 | idToken: action.payload.idToken, 17 | } 18 | 19 | /* Actualizar el usuario */ 20 | case 'userUpdate': 21 | return { 22 | ...state, 23 | user: { ...action.payload.user } 24 | } 25 | 26 | /* Cerrar sesión del usuario */ 27 | case 'userLogout': 28 | return { 29 | ...state, 30 | isAuthenticated: false, 31 | isAuthLoading: false, 32 | user: { 33 | id: '', 34 | name: '', 35 | email: '', 36 | image: '' 37 | }, 38 | idToken: '' 39 | } 40 | 41 | /* Cargando el estado de la autenticación */ 42 | case 'toggleIsAuthenticated': 43 | return { 44 | ...state, 45 | isAuthenticated: !state.isAuthenticated 46 | } 47 | 48 | /* Setear si esta cargando la autenticación */ 49 | case 'toggleIsAuthLoading': 50 | return { 51 | ...state, 52 | isAuthLoading: !state.isAuthLoading 53 | } 54 | 55 | /* Retonar estado por defecto */ 56 | default: 57 | return state; 58 | } 59 | } 60 | 61 | export default authReducer; -------------------------------------------------------------------------------- /src/context/permissions/PermissionsContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | /* Interfaces */ 4 | import { PermissionsState } from '../../interfaces/permissions'; 5 | 6 | /** 7 | * Definicion del tipo para las propiedades del 8 | * Contexto de Permisos 9 | */ 10 | 11 | /** 12 | * Este contexto se usa para manejar la información de permisos, 13 | * se conforma de las siguientes propiedades: 14 | * 15 | * permissions: sirve para almacenar los permisos otorgados 16 | * askPermissions: sirve para pedir los permisos al usuario 17 | * checkPermissions: sirve para verificar los permisos otorgados 18 | */ 19 | 20 | export type PermissionsContextProps = { 21 | permissions: PermissionsState; 22 | askPermissions: () => Promise; 23 | checkPermissions: () => Promise; 24 | permissionsError: string; 25 | setPermissionsError: (error: string) => void; 26 | } 27 | 28 | /* Creación del contexto */ 29 | const PermissionsContext = createContext({} as PermissionsContextProps); 30 | 31 | export default PermissionsContext; -------------------------------------------------------------------------------- /src/context/permissions/PermissionsProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, FC } from 'react'; 2 | import { AppState } from 'react-native'; 3 | import { request, check, PERMISSIONS, openSettings } from 'react-native-permissions'; 4 | import messaging from '@react-native-firebase/messaging'; 5 | 6 | /* Context */ 7 | import PermissionsContext from './PermissionsContext'; 8 | 9 | /* Interfaces */ 10 | import { PermissionsState } from '../../interfaces/permissions'; 11 | 12 | /* Estado inicial del contexto */ 13 | const INITIAL_STATE: PermissionsState = { 14 | camera: 'unavailable', 15 | notifications: -1 // Not determined 16 | } 17 | 18 | /* Provider para dar la información de los permisos */ 19 | const PermissionsProvider: FC = ({ children }) => { 20 | const [ permissions, setPermissions ] = useState(INITIAL_STATE); 21 | const [ permissionsError, setPermissionsError ] = useState(''); 22 | 23 | /* Función para preguntar por los permisos */ 24 | const askPermissions = async () => { 25 | /* Preguntar por permisos */ 26 | const permissionStatusCamera = await request(PERMISSIONS.ANDROID.CAMERA); 27 | const permissionStatusNotifications = await messaging().requestPermission(); 28 | 29 | /* Verificar los resultados */ 30 | if (permissionStatusCamera === 'blocked' || permissionStatusNotifications === 0) { 31 | /* Si el usuario no acepta los permisos, abrir la configuración */ 32 | openSettings(); 33 | } 34 | 35 | /* Setear los permisos */ 36 | setPermissions({ 37 | ...permissions, 38 | camera: permissionStatusCamera, 39 | notifications: permissionStatusNotifications 40 | }); 41 | } 42 | 43 | /* Función para verificar los permisos */ 44 | const checkPermissions = async () => { 45 | /* Verificar permisos */ 46 | const permissionStatusCamera = await check(PERMISSIONS.ANDROID.CAMERA); 47 | const permissionStatusNotifications = await messaging().hasPermission(); 48 | 49 | /* Setear los permisos */ 50 | setPermissions({ 51 | ...permissions, 52 | camera: permissionStatusCamera, 53 | notifications: permissionStatusNotifications 54 | }); 55 | } 56 | 57 | /** 58 | * useEffect para que no más se monte el Provider preguntar por los permiso, 59 | * además se puso un listener para verificar los permisos constantemente 60 | */ 61 | useEffect(() => { 62 | /* Listener para verificar los permisos */ 63 | const unSubscribreAppState = AppState.addEventListener('change', async (state) => { 64 | if (state !== 'active') return; 65 | await checkPermissions(); 66 | }); 67 | 68 | /* Limpieza del listener */ 69 | return () => { 70 | unSubscribreAppState.remove(); 71 | } 72 | }, []); 73 | 74 | return ( 75 | 84 | { children } 85 | 86 | ); 87 | } 88 | 89 | export default PermissionsProvider; -------------------------------------------------------------------------------- /src/context/status/StatusContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | /** 4 | * Definicion del tipo para las propiedades del 5 | * Contexto de Status 6 | */ 7 | 8 | /** 9 | * Este contexto se usa para manejar la información del status, 10 | * se conforma de las siguientes propiedades: 11 | * 12 | * msgSuccess: guarda el mensaje de éxito 13 | * msgError: guarda el mensaje de error 14 | * setMsgSuccess: sirve para setear el mensaje de éxito 15 | * setMsgError: sirve para setear el mensaje de error 16 | * removeMsgSuccess: sirve para remover el mensaje de éxito 17 | * removeMsgError: sirve para remover el mensaje de error 18 | * resetStatus: sirve para resetear los mensajes de éxito y error 19 | */ 20 | 21 | export type StatusContextProps = { 22 | msgSuccess: string; 23 | msgError: string; 24 | setMsgSuccess: (msgSuccess: string) => void; 25 | setMsgError: (msgError: string) => void; 26 | removeMsgSuccess: () => void; 27 | removeMsgError: () => void; 28 | resetStatus: () => void; 29 | } 30 | 31 | /* Creación del contexto */ 32 | const StatusContext = createContext({} as StatusContextProps); 33 | 34 | export default StatusContext; -------------------------------------------------------------------------------- /src/context/status/StatusProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, FC, useEffect } from 'react'; 2 | import { useNetInfo } from '@react-native-community/netinfo'; 3 | import AsyncStorage from '@react-native-async-storage/async-storage'; 4 | import { ASYNCSTORAGE_ID_TOKEN } from '@env'; 5 | 6 | /* Context */ 7 | import StatusContext from './StatusContext'; 8 | 9 | /* Provider para dar la información de los status */ 10 | const StatusProvider: FC = ({ children }) => { 11 | const [ msgSuccess, setMsgSuccess ] = useState(''); 12 | const [ msgError, setMsgError ] = useState(''); 13 | 14 | const { isConnected } = useNetInfo(); 15 | 16 | /* Función para resetear el mensaje de éxito */ 17 | const removeMsgSuccess = () => setMsgSuccess(''); 18 | 19 | /* Función para resetear el mensaje de error */ 20 | const removeMsgError = () => setMsgError(''); 21 | 22 | /* Función para resetear los mensajes de éxito y error */ 23 | const resetStatus = () => { 24 | removeMsgSuccess(); 25 | removeMsgError(); 26 | } 27 | 28 | /* Función para comprobar la conexión a internet */ 29 | const checkWifiConextion = async () => { 30 | /* Obtener el idToken */ 31 | const idToken = await AsyncStorage.getItem(ASYNCSTORAGE_ID_TOKEN) || ''; 32 | 33 | /* Evaluar si hay idToken y si no hay conexión a internet */ 34 | if (idToken && isConnected === false) { 35 | setMsgError('Por favor verifique su conexión. Al aplicación se puede seguir usando, podra realizar acciones pero los cambios se reflejaran cuando se vuelva a conectar a internet; lo único que no puede actualizar es su información de usuario'); 36 | } 37 | } 38 | 39 | /** 40 | * useEffect que ejecuta la función checkWifiConexion cada 41 | * vez que isConnected cambia } 42 | */ 43 | useEffect(() => { 44 | checkWifiConextion(); 45 | }, [ isConnected ]); 46 | 47 | return ( 48 | 59 | { children } 60 | 61 | ); 62 | } 63 | 64 | export default StatusProvider; -------------------------------------------------------------------------------- /src/context/tasks/TasksContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | /* Interfaces */ 4 | import { Task, TasksStatus, NewTask } from '../../interfaces/tasks'; 5 | 6 | /** 7 | * Definicion del tipo para las propiedades del 8 | * Contexto de Tareas 9 | */ 10 | 11 | /** 12 | * Este contexto se usa para manejar la información de tareas, 13 | * se conforma de las siguientes propiedades: 14 | * 15 | * tasks: sirve para guardar las tareas 16 | * selectedTasks: sirve para guardar las tareas seleccionadas 17 | * searchingTasks: sirve para guardar las tareas buscadas 18 | * selectedTask: sirve para guardar la tarea seleccionada 19 | * isTasksLoading: sirve para manejar la carga de las tareas 20 | * loadTasks: función para cargar las tareas 21 | * loadSelectedTasks: función para cargar las tareas seleccionadas 22 | * searchTasks: función para buscar las tareas 23 | * removeSearchingTasks: función para limpiar las tareas buscadas 24 | * setSelectedTask: función para guardar la tarea seleccionada 25 | * removeSelectedTask: función para limpiar la tarea seleccionada 26 | * removeTasks: función para limpiar las tareas 27 | * createTask: función para crear una tarea 28 | * updateTask: función para actualizar una tarea 29 | * toggleTaskCompleted: función para cambiar el estado de completado de una tarea 30 | * removeTask: función para eliminar una tarea 31 | */ 32 | 33 | export type TasksContextProps = { 34 | tasks: Task[]; 35 | selectedTasks: Task[]; 36 | searchingTasks: Task[]; 37 | selectedTask: Task; 38 | isTasksLoading: boolean; 39 | loadTasks: () => Promise; 40 | loadSelectedTasks: (type: TasksStatus) => void; 41 | searchTasks: (term: string) => void; 42 | removeSearchingTasks: () => void; 43 | setSelectedTask: (task: Task) => void; 44 | removeSelectedTask: () => void; 45 | removeTasks: () => void; 46 | createTask: (task: NewTask, type: TasksStatus, image?: string) => Promise; 47 | updateTask: (task: Task, type: TasksStatus, image?: string) => Promise; 48 | toggleTaskCompeted: (task: Task, type: TasksStatus) => Promise; 49 | removeTask: (taskId: string, type: TasksStatus) => Promise; 50 | } 51 | 52 | /* Creación del contexto */ 53 | const TasksContext = createContext({} as TasksContextProps); 54 | 55 | export default TasksContext; -------------------------------------------------------------------------------- /src/context/tasks/tasksReducer.ts: -------------------------------------------------------------------------------- 1 | 2 | /* Interfaces */ 3 | import { Task, TaskAction, TasksState } from '../../interfaces/tasks'; 4 | 5 | /* Reducer para manejar el state de las tareas */ 6 | const tasksReducer = (state: TasksState, action: TaskAction) => { 7 | switch (action.type) { 8 | /* Cargar tareas */ 9 | case 'loadTasks': 10 | return { 11 | ...state, 12 | tasks: [ ...action.payload.tasks ], 13 | selectedTasks: [ ...action.payload.tasks ], 14 | isTasksLoading: false 15 | } 16 | 17 | /* Setear tareas buscadas */ 18 | case 'setSearchingTasks': 19 | const searchingTasks = state.tasks.filter( 20 | t => 21 | t.title.toLowerCase().includes(action.payload.term.toLowerCase()) 22 | || t.body.toLowerCase().includes(action.payload.term.toLowerCase()) 23 | ); 24 | 25 | return { 26 | ...state, 27 | searchingTasks: searchingTasks, 28 | isTasksLoading: false 29 | } 30 | 31 | /* Setear una tarea seleccionada */ 32 | case 'setSelectedTask': 33 | return { 34 | ...state, 35 | selectedTask: { ...action.payload.task } 36 | } 37 | 38 | /* Setear tareas seleccionadas */ 39 | case 'setSelectedTasks': 40 | const tasks: Task[] = []; 41 | 42 | if (action.payload.type === 'all') tasks.push(...state.tasks); 43 | else if (action.payload.type === 'completed') { 44 | tasks.push(...state.tasks.filter(task => task.completed)); 45 | } 46 | else if (action.payload.type === 'pending') { 47 | tasks.push(...state.tasks.filter(task => !task.completed)); 48 | } 49 | 50 | return { 51 | ...state, 52 | selectedTasks: [ ...tasks ], 53 | isTasksLoading: false 54 | } 55 | 56 | /* Remover las taraeas */ 57 | case 'removeTasks': 58 | return { 59 | ...state, 60 | tasks: [], 61 | selectedTasks: [], 62 | searchingTasks: [] 63 | } 64 | 65 | /* Remover las tareas buscadas */ 66 | case 'removeSearchingTasks': 67 | return { 68 | ...state, 69 | searchingTasks: [] 70 | } 71 | 72 | /* Agregar tarea */ 73 | case 'addTask': 74 | return { 75 | ...state, 76 | tasks: [ action.payload.task, ...state.tasks ] 77 | } 78 | 79 | /* Actualizar tarea */ 80 | case 'updateTask': 81 | return { 82 | ...state, 83 | tasks: state.tasks.map( 84 | t => t.id === action.payload.task.id ? action.payload.task : t 85 | ), 86 | selectedTask: { 87 | ...action.payload.task, 88 | } 89 | } 90 | 91 | /* Cambiar tarea de completa a imcompleta o al inversa */ 92 | case 'toggleCompletedTask': 93 | return { 94 | ...state, 95 | tasks: state.tasks.map( 96 | t => t.id === action.payload.taskId 97 | ? { ...t, completed: action.payload.completed } 98 | : t 99 | ) 100 | } 101 | 102 | /* Remover tarea */ 103 | case 'removeTask': 104 | return { 105 | ...state, 106 | tasks: state.tasks.filter(t => t.id !== action.payload.taskId) 107 | } 108 | 109 | /* Remover tarea seleccionada */ 110 | case 'removeSelectedTask': 111 | return { 112 | ...state, 113 | selectedTask: { 114 | id: '', 115 | user: '', 116 | title: '', 117 | body: '', 118 | image: '', 119 | completed: false, 120 | finalDate: 0, 121 | createdAt: Date.now(), 122 | updatedAt: Date.now() 123 | } 124 | } 125 | 126 | /* Mostrar carga de tareas */ 127 | case 'toggleIsTasksLoading': 128 | return { 129 | ...state, 130 | isTasksLoading: !state.isTasksLoading 131 | } 132 | 133 | /* Retornar state por defecto */ 134 | default: 135 | return state; 136 | } 137 | } 138 | 139 | export default tasksReducer; -------------------------------------------------------------------------------- /src/hooks/useAuth.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import AuthContext from '../context/auth/AuthContext'; 3 | 4 | /** 5 | * Hook para devolver todo el state y funciones del Contexto 6 | * de Autenticación 7 | */ 8 | const useAuth = () => useContext(AuthContext); 9 | 10 | export default useAuth; -------------------------------------------------------------------------------- /src/hooks/useForm.ts: -------------------------------------------------------------------------------- 1 | 2 | import { useState } from 'react'; 3 | import { ValueOf } from 'react-native-gesture-handler/lib/typescript/typeUtils'; 4 | 5 | /* Hook para hacer el manejo de los formularios */ 6 | const useForm = (initState: T) => { 7 | /* Estado del formulario */ 8 | const [ state, setState ] = useState(initState); 9 | 10 | /* Función para cambiar un campo del formulario */ 11 | const onChangeField = (value: ValueOf, field: keyof T) => { 12 | setState({ 13 | ...state, 14 | [ field ]: value 15 | }); 16 | } 17 | 18 | /* Función para setear el formulario */ 19 | const setFormValue = (form: T) => { 20 | setState(form); 21 | } 22 | 23 | /* Función para resetear el formulario */ 24 | const resetForm = () => setState(initState); 25 | 26 | return { 27 | form: state, 28 | onChangeField, 29 | setFormValue, 30 | resetForm 31 | } 32 | } 33 | 34 | export default useForm; -------------------------------------------------------------------------------- /src/hooks/useImage.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { useNetInfo } from '@react-native-community/netinfo'; 3 | import { Asset, launchCamera, launchImageLibrary } from 'react-native-image-picker'; 4 | 5 | /* Hooks */ 6 | import usePermissions from './usePermissions'; 7 | import useStatus from './useStatus'; 8 | 9 | /* Hook para hacer el manejo de las imagenes */ 10 | const useImage = () => { 11 | const [ image, setImage ] = useState({}); 12 | const { isConnected } = useNetInfo(); 13 | 14 | const { setMsgError } = useStatus(); 15 | const { permissions, setPermissionsError } = usePermissions(); 16 | 17 | /* Función para verificar los permisos */ 18 | const handleCheckPermissions = async () => { 19 | /** 20 | * Evaluación de permisos de la camara y galeria, además de 21 | * verificar la conexión a internet 22 | */ 23 | if (permissions.camera === 'unavailable') { 24 | setMsgError('Lo sentimos, pero tu dispositivo no soporta la camara o galeria'); 25 | return false; 26 | } 27 | else if (permissions.camera === 'denied' || permissions.camera === 'blocked') { 28 | setPermissionsError('Por favor habilita los permisos de la camara o galeria'); 29 | return false; 30 | } 31 | else if (!isConnected) { 32 | setMsgError('Lo sentimos, pero no tienes conexión a internet'); 33 | return false; 34 | } 35 | 36 | return true; 37 | } 38 | 39 | /* Función para tomar una imagen de la galeria */ 40 | const handleTakeImageFromLibrary = async () => { 41 | const isChecked = await handleCheckPermissions(); 42 | if (!isChecked) return; 43 | 44 | const { didCancel, assets } = await launchImageLibrary({ 45 | mediaType: 'photo', 46 | quality: 0.5, 47 | includeBase64: true 48 | }); 49 | 50 | if (didCancel) return; 51 | if (!assets) return; 52 | 53 | setImage(assets[0]); 54 | } 55 | 56 | /* Función para tomar una foto de la camara */ 57 | const handleTakePhoto = async () => { 58 | const isChecked = await handleCheckPermissions(); 59 | if (!isChecked) return; 60 | 61 | const { didCancel, assets } = await launchCamera({ 62 | mediaType: 'photo', 63 | quality: 0.5, 64 | includeBase64: true 65 | }); 66 | 67 | if (didCancel) return; 68 | if (!assets) return; 69 | 70 | setImage(assets[0]); 71 | } 72 | 73 | /* Función para resetar state de la imagen */ 74 | const removeImage = () => setImage({}); 75 | 76 | return { 77 | image, 78 | setImage, 79 | handleTakePhoto, 80 | handleTakeImageFromLibrary, 81 | removeImage 82 | } 83 | } 84 | 85 | export default useImage; -------------------------------------------------------------------------------- /src/hooks/useKeyboard.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | /* Hook para gestionar el teclado */ 4 | const useKeyboard = () => { 5 | const [ keyboardShow, setKeyboardShow ] = useState(false); 6 | 7 | return { 8 | keyboardShow, 9 | setKeyboardShow 10 | } 11 | }; 12 | 13 | export default useKeyboard; -------------------------------------------------------------------------------- /src/hooks/usePermissions.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import PermissionsContext from '../context/permissions/PermissionsContext'; 3 | 4 | /** 5 | * Hook para devolver todo el state y funciones del Contexto 6 | * de los Permisos 7 | */ 8 | const usePermissions = () => useContext(PermissionsContext); 9 | 10 | export default usePermissions; -------------------------------------------------------------------------------- /src/hooks/useStatus.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import StatusContext from '../context/status/StatusContext'; 3 | 4 | /** 5 | * Hook para devolver todo el state y funciones del Contexto 6 | * del Status 7 | */ 8 | const useStatus = () => useContext(StatusContext); 9 | 10 | export default useStatus; -------------------------------------------------------------------------------- /src/hooks/useTasks.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import TasksContext from '../context/tasks/TasksContext'; 3 | 4 | /** 5 | * Hook para devolver todo el state y funciones del Contexto 6 | * de las Tareas 7 | */ 8 | const useTasks = () => useContext(TasksContext); 9 | 10 | export default useTasks; -------------------------------------------------------------------------------- /src/interfaces/auth.ts: -------------------------------------------------------------------------------- 1 | 2 | /* Interfaz del state del contexto de la Autenticación */ 3 | export interface AuthState { 4 | isAuthenticated: boolean; 5 | isAuthLoading: boolean; 6 | idToken: string; 7 | user: User; 8 | } 9 | 10 | /* Tipo para definir los tipos de acciones que tendra el reducer de Autenticación */ 11 | export type AuthAction = 12 | { type: 'userLogin', payload: { user: User, idToken: string } } 13 | | { type: 'userUpdate', payload: { user: User } } 14 | | { type: 'userLogout' } 15 | | { type: 'toggleIsAuthenticated' } 16 | | { type: 'toggleIsAuthLoading' } 17 | 18 | export interface User { 19 | id: string; 20 | name: string; 21 | email: string; 22 | image: string | null; 23 | } 24 | 25 | export interface UpdateProfileData { 26 | name: string; 27 | email: string; 28 | image: string; 29 | } 30 | 31 | export interface SingInData { 32 | name: string; 33 | email: string; 34 | password: string; 35 | } 36 | 37 | /* Interfaz que para la response al renovar Autenticación */ 38 | export interface RenewAuthResponse { 39 | kind: string; 40 | users: UserRenew[]; 41 | } 42 | 43 | export interface UserRenew { 44 | localId: string; 45 | email: string; 46 | emailVerified: boolean; 47 | displayName: string; 48 | providerUserInfo: ProviderUserInfo[]; 49 | photoUrl: string; 50 | passwordHash: string; 51 | passwordUpdatedAt: number; 52 | validSince: string; 53 | disabled: boolean; 54 | lastLoginAt: string; 55 | createdAt: string; 56 | customAuth: boolean; 57 | } 58 | 59 | export interface ProviderUserInfo { 60 | providerId: string; 61 | displayName: string; 62 | photoUrl: string; 63 | federatedId: string; 64 | email: string; 65 | rawId: string; 66 | screenName: string; 67 | } 68 | -------------------------------------------------------------------------------- /src/interfaces/permissions.ts: -------------------------------------------------------------------------------- 1 | import { PermissionStatus } from 'react-native-permissions'; 2 | import { FirebaseMessagingTypes } from '@react-native-firebase/messaging'; 3 | 4 | /* Interfaz para el state del Contexto de Permisos */ 5 | export interface PermissionsState { 6 | camera: PermissionStatus; 7 | notifications: FirebaseMessagingTypes.AuthorizationStatus; 8 | } -------------------------------------------------------------------------------- /src/interfaces/tasks.ts: -------------------------------------------------------------------------------- 1 | 2 | /* Interfaz para el state del contexto de tareas */ 3 | export interface TasksState { 4 | tasks: Task[]; 5 | selectedTasks: Task[]; 6 | searchingTasks: Task[]; 7 | selectedTask: Task; 8 | isTasksLoading: boolean; 9 | } 10 | 11 | /* Tipo para definir los tipos de acciones del reducer de las tareas */ 12 | export type TaskAction = 13 | { type: 'loadTasks', payload: { tasks: Task[] } } 14 | | { type: 'setSearchingTasks', payload: { term: string } } 15 | | { type: 'setSelectedTasks', payload: { type: TasksStatus } } 16 | | { type: 'removeTasks' } 17 | | { type: 'removeSearchingTasks' } 18 | | { type: 'addTask', payload: { task: Task } } 19 | | { type: 'updateTask', payload: { task: Task } } 20 | | { type: 'toggleCompletedTask', payload: { taskId: string, completed: boolean } } 21 | | { type: 'removeTask', payload: { taskId: string } } 22 | | { type: 'setSelectedTask', payload: { task: Task } } 23 | | { type: 'removeSelectedTask' } 24 | | { type: 'toggleIsTasksLoading' } 25 | 26 | export type TasksStatus = 'all' | 'completed' | 'pending'; 27 | 28 | export interface NewTask { 29 | title: string; 30 | body: string; 31 | finalDate: number; 32 | } 33 | 34 | export interface Task { 35 | id: string; 36 | user: string; 37 | title: string; 38 | body: string; 39 | image?: string; 40 | completed: boolean; 41 | finalDate: number; 42 | createdAt: number; 43 | updatedAt: number; 44 | } -------------------------------------------------------------------------------- /src/interfaces/ui.ts: -------------------------------------------------------------------------------- 1 | 2 | /* Interfaz de la response que da cloudinary al subir una imagen */ 3 | export interface ImageResponse { 4 | access_mode: string; 5 | asset_id: string; 6 | bytes: number; 7 | created_at: string; 8 | etag: string; 9 | format: string; 10 | height: number; 11 | placeholder: boolean; 12 | public_id: string; 13 | resource_type: string; 14 | secure_url: string; 15 | signature: string; 16 | tags: any[]; 17 | type: string; 18 | url: string; 19 | version: number; 20 | version_id: string; 21 | width: number; 22 | } 23 | -------------------------------------------------------------------------------- /src/layout/AuthLayout.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { StyleProp, StyleSheet, Text, TouchableOpacity, useWindowDimensions, View, ViewStyle } from 'react-native'; 3 | 4 | /* Components */ 5 | import { ScreenTitle } from '../components/ui/ScreenTitle'; 6 | 7 | /* Propiedades del Layout */ 8 | interface Props { 9 | title: string; 10 | navigateBtnText: string; 11 | colorBtn: string; 12 | onPressNavigate: () => void; 13 | style?: StyleProp; 14 | } 15 | 16 | /* HOC para la Autenticación */ 17 | const AuthLayout: FC = ({ title, onPressNavigate, navigateBtnText, colorBtn, style, children }) => { 18 | const { height, width } = useWindowDimensions(); 19 | 20 | return ( 21 | 27 | { /* Titulo o nombre de la screen */ } 28 | width) ? -120 : -150, 32 | width: (height > width) ? width * 0.8 : 400 33 | }} 34 | styleTitleText={{ 35 | top: (height > width) ? 120 : 150, 36 | }} 37 | /> 38 | 39 | { /* Boton de la esquina inferior derecha para navegar */ } 40 | 41 | 45 | { navigateBtnText } 46 | 47 | 48 | 49 | { /* Contenido de la pantalla */ } 50 | { children } 51 | 52 | ); 53 | } 54 | 55 | /* Estilos del Layout */ 56 | const styles = StyleSheet.create({ 57 | btnGoToContainer: { 58 | position: 'absolute', 59 | right: 25, 60 | bottom: 20, 61 | zIndex: 999 62 | } 63 | }); 64 | 65 | export default AuthLayout; -------------------------------------------------------------------------------- /src/layout/LinearGradientLayout.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import LinearGradient from 'react-native-linear-gradient'; 3 | import { useNavigation } from '@react-navigation/native'; 4 | 5 | /* Screens */ 6 | import ModalScreen from '../screens/ui/ModalScreen'; 7 | import LoadingScreen from '../screens/ui/LoadingScreen'; 8 | 9 | /* Components */ 10 | import { ModalPermissions } from '../components/ui/ModalPermissions'; 11 | import { ModalStatus } from '../components/ui/ModalStatus'; 12 | 13 | /* Hooks */ 14 | import useAuth from '../hooks/useAuth'; 15 | import usePermissions from '../hooks/usePermissions'; 16 | import useStatus from '../hooks/useStatus'; 17 | import useTasks from '../hooks/useTasks'; 18 | 19 | /* Hooks */ 20 | import { colors } from '../theme/app-theme'; 21 | 22 | /* Layout para mostrar un gradiente vertical */ 23 | const LinearGradientLayout: FC<{ modalOpacity?: number }> = ({ children, modalOpacity }) => { 24 | const { navigate, getState } = useNavigation(); 25 | const state = getState(); 26 | 27 | const { isAuthLoading } = useAuth(); 28 | const { permissionsError, setPermissionsError, askPermissions } = usePermissions(); 29 | const { msgError, msgSuccess, removeMsgError, removeMsgSuccess } = useStatus(); 30 | const { selectedTask } = useTasks(); 31 | 32 | /* Función para el cierre exitoso del modal */ 33 | const handleSuccessOnClose = () => { 34 | removeMsgSuccess(); 35 | 36 | /** 37 | * Evaluacion si hay una tarea seleccionada o si el indice 38 | * de la pantalla es 3 (ProfileScreen) 39 | */ 40 | if (selectedTask.id || state.index === 3) return; 41 | navigate('HomeScreen' as never); 42 | } 43 | 44 | /* Función para el cierre exitoso del modal de permisos */ 45 | const handleAskPermissions = async () => { 46 | setPermissionsError(''); 47 | 48 | /* Preguntar por permisos */ 49 | await askPermissions(); 50 | } 51 | 52 | return ( 53 | 57 | { /* Pantalla del modal */ } 58 | 62 | 63 | { /* Modal de los permisos */ } 64 | setPermissionsError('') } 68 | /> 69 | 70 | 71 | 72 | { /* Pantalla del modal */ } 73 | 77 | 78 | { /* Modal de los mensajes de error y exito */ } 79 | 83 | 84 | 85 | { 86 | /** 87 | * Evaluación para mostrar la pantalla de carga o la 88 | * pantalla que corresponda 89 | */ 90 | isAuthLoading ? : children 91 | } 92 | 93 | ); 94 | } 95 | 96 | export default LinearGradientLayout; -------------------------------------------------------------------------------- /src/layout/TasksLayout.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { StyleProp, StyleSheet, TouchableHighlight, useWindowDimensions, View, ViewStyle } from 'react-native'; 3 | import Icon from 'react-native-vector-icons/Ionicons'; 4 | 5 | /* Components */ 6 | import { ScreenTitle } from '../components/ui/ScreenTitle'; 7 | 8 | /* Theme */ 9 | import { colors } from '../theme/app-theme'; 10 | 11 | /* Propiedades del componente */ 12 | interface Props { 13 | title: string; 14 | openDrawer: () => void; 15 | style?: StyleProp; 16 | headerContainerStyle?: StyleProp; 17 | } 18 | 19 | /** 20 | * Layout de que se muestra en todas las pantallas 21 | * relacionadas con tareas 22 | */ 23 | const TasksLayout: FC = ({ children, title, openDrawer, style, headerContainerStyle }) => { 24 | const { height, width } = useWindowDimensions(); 25 | 26 | const windowHeight = (height >= 720 && height > width) ? height : 720; 27 | 28 | return ( 29 | width) ? windowHeight : width * 0.8, 33 | ...style as ViewStyle 34 | }} 35 | > 36 | width) ? windowHeight * 0.3 : width * 0.2, 40 | ...headerContainerStyle as ViewStyle 41 | }} 42 | > 43 | { /* Titulo o nombre de la pantalla */ } 44 | width) ? -120 : -170, 49 | width: (height > width) ? width * 0.8 : 400, 50 | }} 51 | styleTitleText={{ 52 | top: (height > width) ? 120 : 160 53 | }} 54 | /> 55 | 56 | { /* Botón para abrir el menu */ } 57 | 58 | 64 | 70 | 71 | 72 | 73 | 74 | { /* Contenido de la pantalla */ } 75 | { children } 76 | 77 | ); 78 | } 79 | 80 | /* Estilos del layout */ 81 | const styles = StyleSheet.create({ 82 | headerContainer: { 83 | backgroundColor: colors.light, 84 | flex: 1, 85 | zIndex: 2 86 | }, 87 | 88 | btnGoToContainer: { 89 | position: 'absolute', 90 | right: 15, 91 | top: 15 92 | }, 93 | 94 | btnGoTo: { 95 | backgroundColor: colors.lightGray, 96 | borderRadius: 15, 97 | justifyContent: 'center', 98 | alignItems: 'center', 99 | shadowColor: 'rgba(0, 0, 0, 0.6)', 100 | shadowOffset: { 101 | width: 0, 102 | height: 7, 103 | }, 104 | shadowOpacity: 0.30, 105 | shadowRadius: 6.25, 106 | elevation: 7, 107 | zIndex: 998 108 | }, 109 | 110 | btnGoToIcon: { 111 | padding: 2, 112 | marginLeft: 3 113 | } 114 | }); 115 | 116 | export default TasksLayout; -------------------------------------------------------------------------------- /src/navigation/AuthNavigator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createStackNavigator, TransitionPresets } from '@react-navigation/stack'; 3 | 4 | /* Layouts */ 5 | import LinearGradientLayout from '../layout/LinearGradientLayout'; 6 | 7 | /* Screens */ 8 | import RegisterScreen from '../screens/auth/RegisterScreen'; 9 | import LoginScreen from '../screens/auth/LoginScreen'; 10 | 11 | /* Propiedades de la navegación */ 12 | export type AuthNavigatorParams = { 13 | LoginScreen: undefined; 14 | RegisterScreen: undefined; 15 | } 16 | 17 | const Stack = createStackNavigator(); 18 | 19 | /* Pantallas con su layout */ 20 | const Login = (props: any) => } />; 21 | const Register = (props: any) => } />; 22 | 23 | /* Componente para definir la navegación de autenticación */ 24 | const AuthNavigator = () => { 25 | return ( 26 | 33 | 34 | 35 | 36 | ); 37 | } 38 | 39 | export default AuthNavigator; -------------------------------------------------------------------------------- /src/navigation/Navigator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createStackNavigator } from '@react-navigation/stack'; 3 | 4 | /* Navigators */ 5 | import AuthNavigator from './AuthNavigator'; 6 | import TasksNavigator from './TasksNavigator'; 7 | 8 | /* Hooks */ 9 | import useAuth from '../hooks/useAuth'; 10 | 11 | const Stack = createStackNavigator(); 12 | 13 | /* Componente para definir la navegación principal */ 14 | const Navigator = () => { 15 | const { isAuthenticated } = useAuth(); 16 | 17 | return ( 18 | 23 | { 24 | !isAuthenticated ? ( 25 | /** 26 | * Evaluación si el usuario esta atenticado o no para 27 | * mostrar una navegación 28 | */ 29 | 30 | ) : ( 31 | 32 | ) 33 | } 34 | 35 | ); 36 | } 37 | 38 | export default Navigator; -------------------------------------------------------------------------------- /src/navigation/TasksNavigator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet } from 'react-native'; 3 | import { createDrawerNavigator } from '@react-navigation/drawer'; 4 | import Icon from 'react-native-vector-icons/Ionicons'; 5 | 6 | /* Layouts */ 7 | import LinearGradientLayout from '../layout/LinearGradientLayout'; 8 | 9 | /* Screens */ 10 | import CreateTaskScreen from '../screens/tasks/CreateTaskScreen'; 11 | import HomeScreen from '../screens/tasks/HomeScreen'; 12 | import ProfileScreen from '../screens/auth/ProfileScreen'; 13 | import SearchScreen from '../screens/tasks/SearchScreen'; 14 | 15 | /* Interfaces */ 16 | import { TasksStatus } from '../interfaces/tasks'; 17 | 18 | /* Theme */ 19 | import { colors } from '../theme/app-theme'; 20 | 21 | /* Propiedades para la navegación */ 22 | export type TasksNavigatorParams = { 23 | HomeScreen: undefined; 24 | CreateTaskScreen: { taskStatus: TasksStatus }; 25 | SearchScreen: undefined; 26 | ProfileScreen: undefined; 27 | } 28 | 29 | const Drawer = createDrawerNavigator(); 30 | 31 | /* Pantallas con su layout */ 32 | const CreateTask = (props: any) => } />; 33 | const Home = (props: any) => } />; 34 | const Profile = (props: any) => } />; 35 | const Search = (props: any) => } />; 36 | 37 | /* Componente para definir la navegación de las tareas */ 38 | const TasksNavigator = () => { 39 | return ( 40 | 52 | {/* Pantalla de inicio */} 53 | ( 58 | 59 | ) 60 | }} 61 | component={ Home } 62 | /> 63 | 64 | {/* Pantalla de busqueda */} 65 | ( 70 | 71 | ) 72 | }} 73 | component={ Search } 74 | /> 75 | 76 | {/* Pantalla de creación de tareas */} 77 | ( 82 | 83 | ) 84 | }} 85 | component={ CreateTask } 86 | initialParams={{ taskStatus: 'all' }} 87 | /> 88 | 89 | {/* Pantalla de perfil */} 90 | ( 95 | 96 | ) 97 | }} 98 | component={ Profile } 99 | /> 100 | 101 | ); 102 | } 103 | 104 | /* Estilos del componente */ 105 | const styles = StyleSheet.create({ 106 | drawerStyle: { 107 | backgroundColor: colors.light 108 | }, 109 | 110 | drawerItemStyle: { 111 | backgroundColor: colors.lightGray, 112 | margin: 0, 113 | padding: 0, 114 | }, 115 | 116 | drawerLabelStyle: { 117 | fontSize: 18, 118 | padding: 0, 119 | marginLeft: -20, 120 | margin: 0 121 | }, 122 | }); 123 | 124 | export default TasksNavigator; -------------------------------------------------------------------------------- /src/screens/auth/LoginScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, StyleSheet, useWindowDimensions } from 'react-native'; 3 | import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; 4 | import { StackScreenProps } from '@react-navigation/stack'; 5 | 6 | /* Layouts */ 7 | import AuthLayout from '../../layout/AuthLayout'; 8 | 9 | /* Components */ 10 | import { LoginForm } from '../../components/auth/LoginForm'; 11 | 12 | /* Hooks */ 13 | import useKeyboard from '../../hooks/useKeyboard'; 14 | 15 | /* Theme */ 16 | import { colors } from '../../theme/app-theme'; 17 | 18 | /* Propiedades de la pantalla */ 19 | interface Props extends StackScreenProps{} 20 | 21 | /* Pantalla para que el usuario realice la autenticación */ 22 | const LoginScreen = ({ navigation }: Props) => { 23 | const { height, width } = useWindowDimensions(); 24 | 25 | const { setKeyboardShow, keyboardShow } = useKeyboard(); 26 | 27 | const windowHeight = (height >= 720 && height > width) ? height : 720; 28 | 29 | return ( 30 | setKeyboardShow(true) } 37 | onKeyboardDidHide={ () => setKeyboardShow(false) } 38 | > 39 | navigation.navigate('RegisterScreen') } 44 | style={{ 45 | flexGrow: 1, 46 | minHeight: (height > width) 47 | ? (height >= 720 && height > width) ? '100%' : 720 48 | : width * 0.65, 49 | marginBottom: keyboardShow 50 | ? (height > width) 51 | ? (249 + (249 * 0.2)) : 392 * -0.25 52 | : 0 53 | }} 54 | > 55 | { /* Formulario */ } 56 | 57 | 58 | { /* Fondo */ } 59 | width) ? windowHeight * 0.75 : width * 0.7, 63 | left: (height > width) ? -width * 0.95 : -windowHeight * 0.40, 64 | width: (height > width) ? width * 2.3 : windowHeight * 2.6, 65 | bottom: (height > width) ? -windowHeight * 0.10 : -width * 0.2, 66 | }} 67 | > 68 | { /* Fondo más pequeño */ } 69 | width) ? 110 : 40, 73 | bottom: (height > width) ? -130 : -70 74 | }} 75 | /> 76 | 77 | 78 | 79 | ); 80 | } 81 | 82 | /* Estilos de la pantalla */ 83 | const styles = StyleSheet.create({ 84 | background: { 85 | backgroundColor: colors.lightBlue, 86 | borderTopLeftRadius: 999, 87 | borderTopRightRadius: 999, 88 | position: 'absolute', 89 | zIndex: -1 90 | }, 91 | 92 | backgroundBottom: { 93 | backgroundColor: colors.light, 94 | borderTopLeftRadius: 999, 95 | height: 360, 96 | position: 'absolute', 97 | transform: [{ rotate: '30deg' }], 98 | width: 360, 99 | zIndex: -1 100 | } 101 | }); 102 | 103 | export default LoginScreen -------------------------------------------------------------------------------- /src/screens/auth/ProfileScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, StyleSheet, useWindowDimensions } from 'react-native'; 3 | import { DrawerScreenProps } from '@react-navigation/drawer'; 4 | import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; 5 | 6 | /* Layouts */ 7 | import TasksLayout from '../../layout/TasksLayout'; 8 | 9 | /* Components */ 10 | import { Fab } from '../../components/ui/Fab'; 11 | import { ProfileForm } from '../../components/auth/ProfileForm'; 12 | 13 | /* Hooks */ 14 | import useAuth from '../../hooks/useAuth'; 15 | import useKeyboard from '../../hooks/useKeyboard'; 16 | import useTasks from '../../hooks/useTasks'; 17 | 18 | /* Theme */ 19 | import { colors } from '../../theme/app-theme'; 20 | 21 | /* Propiedades del componente */ 22 | interface Props extends DrawerScreenProps{} 23 | 24 | /* Pantalla para mostrar la información del usuario */ 25 | const ProfileScreen = ({ navigation }: Props) => { 26 | const { width, height } = useWindowDimensions(); 27 | 28 | const { signOut } = useAuth(); 29 | const { setKeyboardShow, keyboardShow } = useKeyboard(); 30 | const { removeTasks, removeSearchingTasks } = useTasks(); 31 | 32 | const windowHeight = (height >= 720 && height > width) ? height : 720; 33 | 34 | /* Función para desloguarse */ 35 | const handleSignOut = () => { 36 | signOut(); 37 | removeTasks(); 38 | removeSearchingTasks(); 39 | } 40 | 41 | return ( 42 | setKeyboardShow(true) } 49 | onKeyboardDidHide={ () => setKeyboardShow(false) } 50 | > 51 | navigation.openDrawer() } 54 | headerContainerStyle={{ backgroundColor: 'transparent' }} 55 | style={{ 56 | flexGrow: 1, 57 | height: (height > width) 58 | ? (height >= 720 && height > width) ? undefined : 720 59 | : width * 0.8, 60 | minHeight: (height > width) 61 | ? (height >= 720 && height > width) ? '100%' : 720 62 | : width * 0.65, 63 | marginBottom: keyboardShow 64 | ? (height > width) 65 | ? (249 - (249 * 0.35)) : windowHeight * -0.2 66 | : 0 67 | }} 68 | > 69 | { /* Formulario */ } 70 | 71 | 72 | { /* Boton para cerrar la sesión */ } 73 | 78 | 79 | { /* Fondo */ } 80 | width) ? windowHeight * 0.88 : width * 0.78, 84 | left: -width * 0.25, 85 | paddingTop: windowHeight * 0.04, 86 | width: width * 1.5, 87 | bottom: -180, 88 | }} 89 | /> 90 | 91 | 92 | ); 93 | } 94 | 95 | /* Estilos de la pantalla */ 96 | const styles = StyleSheet.create({ 97 | background: { 98 | backgroundColor: colors.lightBlue, 99 | borderTopLeftRadius: 999, 100 | borderTopRightRadius: 999, 101 | position: 'absolute', 102 | flex: 1 103 | }, 104 | }); 105 | 106 | export default ProfileScreen; -------------------------------------------------------------------------------- /src/screens/auth/RegisterScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, StyleSheet, useWindowDimensions, Dimensions } from 'react-native'; 3 | import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; 4 | import { StackScreenProps } from '@react-navigation/stack'; 5 | 6 | /* Layouts */ 7 | import AuthLayout from '../../layout/AuthLayout'; 8 | 9 | /* Components */ 10 | import { RegisterForm } from '../../components/auth/RegisterForm'; 11 | 12 | /* Hooks */ 13 | import useKeyboard from '../../hooks/useKeyboard'; 14 | 15 | /* Theme */ 16 | import { colors } from '../../theme/app-theme'; 17 | 18 | /* Propiedades de la pantalla */ 19 | interface Props extends StackScreenProps{} 20 | 21 | /* Pantalla para que el usuario realice el registro */ 22 | const RegisterScreen = ({ navigation }: Props) => { 23 | const { height, width } = useWindowDimensions(); 24 | 25 | const { setKeyboardShow, keyboardShow } = useKeyboard(); 26 | 27 | const windowHeight = (height >= 720 && height > width) ? height : 720; 28 | 29 | return ( 30 | setKeyboardShow(true) } 37 | onKeyboardDidHide={ () => setKeyboardShow(false) } 38 | > 39 | navigation.navigate('LoginScreen') } 44 | style={{ 45 | flexGrow: 1, 46 | minHeight: (height > width) 47 | ? (height >= 720 && height > width) ? '100%' : 720 48 | : width * 0.725, 49 | marginBottom: keyboardShow 50 | ? (height > width) 51 | ? (249 - (249 * 0.1)) : 392 * -0.35 52 | : 0 53 | }} 54 | > 55 | { /* Formulario */ } 56 | 57 | 58 | { /* Fondo */ } 59 | width) ? width * 2 : windowHeight * 2.8, 63 | marginTop: windowHeight * 0.24, 64 | height: (height > width) ? windowHeight * 0.9 : width * 0.75, 65 | left: (height > width) ? -width * 0.3 : -windowHeight * 0.46, 66 | bottom: (height > width) ? -windowHeight * 0.16 : -width * 0.15, 67 | }} 68 | > 69 | { /* Fondo más pequeño */ } 70 | 71 | 79 | 80 | 81 | 82 | ); 83 | } 84 | 85 | /* Estilos de la pantalla */ 86 | const styles = StyleSheet.create({ 87 | background: { 88 | backgroundColor: colors.lightBlue, 89 | borderTopLeftRadius: 999, 90 | borderTopRightRadius: 999, 91 | position: 'absolute', 92 | }, 93 | 94 | backgroundBottom: { 95 | backgroundColor: colors.light, 96 | borderTopRightRadius: 999, 97 | bottom: -110, 98 | height: 320, 99 | position: 'absolute', 100 | left: -50, 101 | transform: [{ rotate: '-45deg' }], 102 | width: 400 103 | } 104 | }); 105 | 106 | export default RegisterScreen -------------------------------------------------------------------------------- /src/screens/tasks/CreateTaskScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, useWindowDimensions, View } from 'react-native'; 3 | import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; 4 | import { DrawerScreenProps } from '@react-navigation/drawer'; 5 | 6 | /* Layouts */ 7 | import TasksLayout from '../../layout/TasksLayout'; 8 | 9 | /* Components */ 10 | import { TaskForm } from '../../components/tasks/TaskForm'; 11 | 12 | /* Hooks */ 13 | import useKeyboard from '../../hooks/useKeyboard'; 14 | import useTasks from '../../hooks/useTasks'; 15 | 16 | /* Navigators params */ 17 | import { TasksNavigatorParams } from '../../navigation/TasksNavigator'; 18 | 19 | /* Theme */ 20 | import { colors } from '../../theme/app-theme'; 21 | 22 | /* Propieades de la pantalla */ 23 | interface Props extends DrawerScreenProps{} 24 | 25 | /* Pantalla para poder crear o editar una tarea */ 26 | const CreateTaskScreen = ({ route, navigation }: Props) => { 27 | /* Extrayendo parametro */ 28 | const { taskStatus } = route.params; 29 | 30 | const { width, height } = useWindowDimensions(); 31 | const { setKeyboardShow, keyboardShow } = useKeyboard(); 32 | const { selectedTask } = useTasks(); 33 | 34 | const windowHeight = (height >= 720 && height > width) ? height : 720; 35 | 36 | return ( 37 | setKeyboardShow(true) } 44 | onKeyboardDidHide={ () => setKeyboardShow(false) } 45 | > 46 | navigation.openDrawer() } 50 | headerContainerStyle={{ backgroundColor: 'transparent' }} 51 | style={{ 52 | flexGrow: 1, 53 | height: (height > width) 54 | ? (height >= 720 && height > width) ? undefined : 720 55 | : width * 0.8, 56 | minHeight: (height > width) 57 | ? (height >= 720 && height > width) ? '100%' : 720 58 | : 392 * 0.65, 59 | marginBottom: keyboardShow 60 | ? (height > width) 61 | ? (249 - (249 * 0.5)) : width * 0.015 62 | : 0 63 | }} 64 | > 65 | { /* Contenedor del formulario */ } 66 | 67 | 68 | { /* Formulario */ } 69 | 73 | 74 | 75 | { /* Fondo */ } 76 | width) ? width * 2 : width * 1.6, 80 | marginLeft: (height > width) ? width * -0.25 : width * 0.08, 81 | marginTop: windowHeight * 0.4, 82 | height: (height > width) ? windowHeight * 0.6 : windowHeight * 1.8 83 | }} 84 | > 85 | { /* Fondo más pequeño */ } 86 | 87 | 88 | 89 | 90 | ); 91 | } 92 | 93 | /* Estilos de la pantalla */ 94 | const styles = StyleSheet.create({ 95 | background: { 96 | flex: 1, 97 | backgroundColor: colors.lightBlue, 98 | bottom: -38, 99 | left: -38, 100 | borderTopLeftRadius: 999, 101 | borderTopRightRadius: 999, 102 | transform: [{ rotate: '-50deg' }], 103 | position: 'absolute' 104 | }, 105 | 106 | backgroundBottom: { 107 | position: 'absolute', 108 | width: 300, 109 | height: 300, 110 | borderTopRightRadius: 999, 111 | backgroundColor: colors.light, 112 | transform: [{ rotate: '30deg' }], 113 | bottom: 100, 114 | left: -50 115 | }, 116 | 117 | formContainer: { 118 | alignItems: 'center', 119 | marginHorizontal: 5, 120 | zIndex: 2 121 | } 122 | }); 123 | 124 | export default CreateTaskScreen; -------------------------------------------------------------------------------- /src/screens/tasks/HomeScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Image, StyleSheet, Text, TouchableOpacity, useWindowDimensions, View } from 'react-native'; 3 | import { DrawerScreenProps } from '@react-navigation/drawer'; 4 | import messaging from '@react-native-firebase/messaging'; 5 | 6 | /* Layouts */ 7 | import TasksLayout from '../../layout/TasksLayout'; 8 | 9 | /* Components */ 10 | import { TasksList } from '../../components/tasks/TasksList'; 11 | import { TasksLoader } from '../../components/tasks/TasksLoader'; 12 | import { TaskOptionBtn } from '../../components/tasks/TaskOptionBtn'; 13 | import { Fab } from '../../components/ui/Fab'; 14 | 15 | /* Hooks */ 16 | import useAuth from '../../hooks/useAuth'; 17 | import useTasks from '../../hooks/useTasks'; 18 | 19 | /* Interfaces */ 20 | import { TasksStatus } from '../../interfaces/tasks'; 21 | 22 | /* Theme */ 23 | import { colors } from '../../theme/app-theme'; 24 | 25 | type Option = 'all' | 'completed' | 'pending'; 26 | 27 | /* Propiedades de la pantalla */ 28 | interface Props extends DrawerScreenProps{} 29 | 30 | /* Pantalla de inicio para mostrar las tareas e interactuar con ellos */ 31 | const HomeScreen = ({ navigation }: Props) => { 32 | /* Primer state para filtrar las tareas */ 33 | const [ optionActive, setOptionActive ] = useState({ all: true, completed: false, pending: false }); 34 | const [ taskStatus, setTaskStatus ] = useState('all'); 35 | 36 | const { height, width } = useWindowDimensions(); 37 | 38 | const { isAuthenticated, user } = useAuth(); 39 | const { loadTasks, loadSelectedTasks, isTasksLoading, selectedTasks } = useTasks(); 40 | 41 | const windowHeight = (height >= 720 && height > width) ? height : 720; 42 | 43 | /* Función para filtrar las tareas */ 44 | const handleSelectOption = (option: Option) => { 45 | setOptionActive({ 46 | all: option === 'all' ? true : false, 47 | completed: option === 'completed' ? true : false, 48 | pending: option === 'pending' ? true : false 49 | }); 50 | 51 | loadSelectedTasks(option); 52 | setTaskStatus(option); 53 | } 54 | 55 | /* useEffect para iniciar cargar de tareas */ 56 | useEffect(() => { 57 | if (isAuthenticated) loadTasks(); 58 | }, []); 59 | 60 | /* useEffect para escuchar los eventos de mensajeria de firebase */ 61 | useEffect(() => { 62 | const foregroundSubscribe = messaging().onMessage(async () => {}); 63 | const backgroundSubcribe = messaging().setBackgroundMessageHandler(async () => {}); 64 | 65 | return () => { 66 | foregroundSubscribe(); 67 | backgroundSubcribe; 68 | } 69 | }, [ ]); 70 | 71 | return ( 72 | navigation.openDrawer() } 75 | headerContainerStyle={{ 76 | borderBottomWidth: (height > width) ? 0 : 2, 77 | borderBottomColor: colors.lightMediumGray, 78 | maxHeight: (height > width) ? windowHeight * 0.3 : width * 0.16 79 | }} 80 | > 81 | { /* Contenedor para mostrar el total de tareas */ } 82 | width) ? 120 : 88, 86 | }} 87 | > 88 | Total de tareas: { selectedTasks.length } 89 | 90 | 91 | { /* Contenedor para los botones de opciones */ } 92 | width) ? colors.light : 'transparent', 96 | borderBottomWidth: (height > width) ? 2 : 0, 97 | marginTop: (height > width) ? windowHeight * 0.21 : windowHeight * 0.3, 98 | top: (height > width) ? 0 : -windowHeight * 0.273, 99 | right: (height > width) ? 0 : 70, 100 | width: (height > width) ? width : width * 0.53, 101 | }} 102 | > 103 | { /* Boton para ir a la pantalla de edición de perfil */ } 104 | 320) ? 55 : 45, 108 | width: (width > 320) ? 55 : 45 109 | }} 110 | activeOpacity={ 0.8 } 111 | onPress={ () => navigation.navigate('ProfileScreen') } 112 | > 113 | 320) ? 55 : 45, 121 | width: (width > 320) ? 55 : 45 122 | }} 123 | /> 124 | 125 | 126 | { /* Botones de opciones */ } 127 | width) ? width * 0.75 : 320, 131 | }} 132 | > 133 | handleSelectOption('all') } 137 | /> 138 | 139 | handleSelectOption('completed') } 143 | /> 144 | 145 | handleSelectOption('pending') } 149 | /> 150 | 151 | 152 | 153 | { /* Boton para ir a crear una tarea */ } 154 | navigation.navigate('CreateTaskScreen', { taskStatus }) } 156 | icon="add-circle-outline" 157 | style={{ right: 20, bottom: 90 }} 158 | /> 159 | 160 | { /* Boton para ir a buscar tareas */ } 161 | navigation.navigate('SearchScreen') } 163 | icon="search-outline" 164 | style={{ right: 20, bottom: 20 }} 165 | /> 166 | 167 | { 168 | /* Evaluación para mostrar la carga o la lista de tareas */ 169 | isTasksLoading 170 | ? 171 | : 172 | } 173 | 174 | { /* Fondo */ } 175 | width) ? windowHeight * 0.65 : windowHeight * 2, 179 | left: (height > width) ? -width * 0.5 : -width * 0.6, 180 | paddingTop: (height > width) ? windowHeight * 0.04 : width * 0.04, 181 | width: (height > width) ? width * 2 : width * 2.2, 182 | bottom: (height > width) ? 0 : -windowHeight * 1.4, 183 | }} 184 | /> 185 | 186 | ); 187 | } 188 | 189 | /* Estilos de la pantalla */ 190 | const styles = StyleSheet.create({ 191 | tasksBackground: { 192 | backgroundColor: colors.lightBlue, 193 | borderTopLeftRadius: 999, 194 | borderTopRightRadius: 999, 195 | position: 'absolute', 196 | flex: 1 197 | }, 198 | 199 | tasksTotal: { 200 | right: 30, 201 | position: 'absolute', 202 | zIndex: 2 203 | }, 204 | 205 | tasksTotalText: { 206 | color: '#000', 207 | fontSize: 16 208 | }, 209 | 210 | tasksOptionsBackground: { 211 | alignItems: 'center', 212 | borderBottomColor: colors.lightMediumGray, 213 | flexDirection: 'row', 214 | paddingBottom: 20, 215 | position: 'absolute', 216 | zIndex: 3 217 | }, 218 | 219 | tasksImageProfile: { 220 | alignItems: 'center', 221 | backgroundColor: colors.light, 222 | borderRadius: 999, 223 | borderColor: colors.light, 224 | borderWidth: 4, 225 | elevation: 6, 226 | justifyContent: 'center', 227 | marginLeft: 16.5, 228 | overflow: 'hidden', 229 | shadowColor: '#000', 230 | shadowOffset: { 231 | height: 5, 232 | width: 0, 233 | }, 234 | shadowOpacity: 0.30, 235 | shadowRadius: 6.25, 236 | }, 237 | 238 | tasksOptions: { 239 | backgroundColor: colors.lightGray, 240 | borderRadius: 20, 241 | flexDirection: 'row', 242 | overflow: 'hidden', 243 | justifyContent: 'center', 244 | marginLeft: 10, 245 | shadowColor: 'rgba(0, 0, 0, 0.6)', 246 | shadowOffset: { 247 | width: 0, 248 | height: 7, 249 | }, 250 | shadowOpacity: 0.30, 251 | shadowRadius: 6.25, 252 | elevation: 7 253 | } 254 | }); 255 | 256 | export default HomeScreen; -------------------------------------------------------------------------------- /src/screens/tasks/SearchScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { View, StyleSheet, useWindowDimensions, Text, Keyboard } from 'react-native'; 3 | import { DrawerScreenProps } from '@react-navigation/drawer'; 4 | 5 | /* Layouts */ 6 | import TasksLayout from '../../layout/TasksLayout'; 7 | 8 | /* Components */ 9 | import { TasksList } from '../../components/tasks/TasksList'; 10 | import { TasksLoader } from '../../components/tasks/TasksLoader'; 11 | import { SearchBar } from '../../components/ui/SearchBar'; 12 | 13 | /* Hooks */ 14 | import useTasks from '../../hooks/useTasks'; 15 | 16 | /* Theme */ 17 | import { colors } from '../../theme/app-theme'; 18 | 19 | /* Propiedades de la pantalla */ 20 | interface Props extends DrawerScreenProps{} 21 | 22 | /* Pantalla para realizar las busquedas de las tareas */ 23 | const SearchScreen = ({ navigation }: Props) => { 24 | const [ term, setTerm ] = useState(''); 25 | 26 | const { height, width } = useWindowDimensions(); 27 | 28 | const { searchTasks, selectedTask, searchingTasks, isTasksLoading, removeSearchingTasks } = useTasks(); 29 | 30 | const windowHeight = (height >= 720 && height > width) ? height : 720; 31 | 32 | /* Función para hacer la busqueda de tareas */ 33 | const handleSearch = () => { 34 | Keyboard.dismiss(); 35 | searchTasks(term); 36 | } 37 | 38 | /** 39 | * useEffect para limpiar la caja de busqueda y remover las 40 | * tareas buscadas cuando se sale de la pantalla 41 | */ 42 | useEffect(() => { 43 | const unSubscribeBlur = navigation.addListener('blur', () => { 44 | setTimeout(() => { 45 | if (!selectedTask.id) { 46 | removeSearchingTasks(); 47 | setTerm(''); 48 | } 49 | }, 1000); 50 | }); 51 | 52 | return unSubscribeBlur; 53 | }, [ navigation, selectedTask ]); 54 | 55 | return ( 56 | navigation.openDrawer() } 59 | headerContainerStyle={{ 60 | borderBottomColor: colors.lightMediumGray, 61 | borderBottomWidth: 2, 62 | maxHeight: (height > width) ? windowHeight * 0.25 : windowHeight * 0.4, 63 | }} 64 | style={{ 65 | flexGrow: 1, 66 | height: (height > width) 67 | ? (height >= 720 && height > width) ? undefined : 720 68 | : width * 0.8, 69 | minHeight: (height > width) 70 | ? (height >= 720 && height > width) ? undefined : 720 71 | : 392 * 0.65 72 | }} 73 | > 74 | 75 | { /* Barra de busquedas */ } 76 | width) ? '-15%' : '-35%' }} 81 | /> 82 | 83 | { /* Contenedor de taras de busquedas */ } 84 | 85 | { 86 | /* Evaluar la carga y mostrarla, de lo contrario mostrar las tareas que hay */ 87 | isTasksLoading 88 | ? 89 | : (searchingTasks.length > 0) 90 | ? 91 | : No hay resultados 92 | } 93 | 94 | 95 | { /* Fondo */ } 96 | width) ? windowHeight * 0.70 : windowHeight, 100 | left: (height > width) ? -width * 0.5 : -width * 0.2, 101 | paddingTop: (height > width) ? windowHeight * 0.04 : width * 0.04, 102 | width: (height > width) ? width * 2 : width * 1.4, 103 | bottom: (height > width) ? 0 : -width * 0.3, 104 | }} 105 | /> 106 | 107 | 108 | ); 109 | } 110 | 111 | /* Estilos de la pantalla */ 112 | const styles = StyleSheet.create({ 113 | container: { 114 | flex: 1, 115 | alignItems: 'center', 116 | zIndex: 999 117 | }, 118 | 119 | tasksBackground: { 120 | backgroundColor: colors.lightBlue, 121 | borderTopLeftRadius: 999, 122 | borderTopRightRadius: 999, 123 | position: 'absolute', 124 | flex: 1 125 | }, 126 | 127 | text: { 128 | color: colors.light, 129 | fontSize: 25, 130 | marginTop: 120, 131 | zIndex: 2 132 | } 133 | }); 134 | 135 | export default SearchScreen; -------------------------------------------------------------------------------- /src/screens/ui/LoadingScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ActivityIndicator, StyleSheet, useWindowDimensions, View } from 'react-native'; 3 | import { ScreenTitle } from '../../components/ui/ScreenTitle'; 4 | 5 | /* Theme */ 6 | import { colors } from '../../theme/app-theme'; 7 | 8 | /* Pantalla para mostrar un indicador de carga */ 9 | const LoadingScreen = () => { 10 | const { height, width } = useWindowDimensions(); 11 | 12 | return ( 13 | <> 14 | { /* Titulo */ } 15 | width) ? -120 : -150, 19 | }} 20 | /> 21 | 22 | { /* Spinner de carga */ } 23 | 28 | 29 | { /* Fondo */ } 30 | 37 | 38 | ); 39 | } 40 | 41 | /* Estilos de la pantalla */ 42 | const styles = StyleSheet.create({ 43 | backgroundBotom: { 44 | backgroundColor: colors.lightBlue, 45 | position: 'absolute', 46 | width: 400, 47 | height: 400, 48 | borderTopLeftRadius: 400, 49 | transform: [{ rotate: '25deg' }], 50 | } 51 | }); 52 | 53 | export default LoadingScreen; -------------------------------------------------------------------------------- /src/screens/ui/ModalScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { Modal, StyleSheet, Text, View, ScrollView } from 'react-native'; 3 | 4 | /* Theme */ 5 | import { colors } from '../../theme/app-theme'; 6 | 7 | /* Propiedades de la pantalla */ 8 | interface Props { 9 | isVisible: boolean; 10 | modalOpacity?: number 11 | } 12 | 13 | /* Pantalla para mostrar todos los modales de la aplicación */ 14 | const ModalScreen: FC = ({ isVisible, modalOpacity = 0.5, children }) => { 15 | return ( 16 | 21 | 27 | 28 | { /* Caja del modal */ } 29 | 30 | 31 | { /* Fondo pequeño superior */ } 32 | 33 | 34 | { /* Título del modal */ } 35 | 36 | MakeTasks 37 | 38 | 39 | { /* Contenido del modal */ } 40 | 41 | { children } 42 | 43 | 44 | 45 | 46 | ); 47 | } 48 | 49 | /* Estilos de la pantalla */ 50 | const styles = StyleSheet.create({ 51 | container: { 52 | flex: 1, 53 | alignItems: 'center', 54 | justifyContent: 'center' 55 | }, 56 | 57 | modal: { 58 | backgroundColor: colors.light, 59 | borderRadius: 20, 60 | padding: 15, 61 | overflow: 'hidden', 62 | width: '75%' 63 | }, 64 | 65 | modalBackgroundTop: { 66 | backgroundColor: colors.lightRed, 67 | borderBottomLeftRadius: 100, 68 | height: 100, 69 | position: 'absolute', 70 | right: 0, 71 | top: -30, 72 | transform: [{ rotate: '-25deg' }], 73 | width: 100, 74 | }, 75 | 76 | modalTitleText: { 77 | color: colors.darkBlue, 78 | fontSize: 19 79 | }, 80 | }); 81 | 82 | export default ModalScreen; -------------------------------------------------------------------------------- /src/theme/app-theme.ts: -------------------------------------------------------------------------------- 1 | 2 | /* Paleta de colores de la aplicación */ 3 | export const colors = { 4 | textGray: '#5B657C', 5 | lightGray: '#E9EBEE', 6 | lightMediumGray: '#d8dbe0', 7 | lightBlue: '#19568C', 8 | light: '#f8f8ff', 9 | lightRed: '#FF0046', 10 | darkRed: '#E5003E', 11 | darkBlue: '#113c62' 12 | } -------------------------------------------------------------------------------- /src/utils/errors.ts: -------------------------------------------------------------------------------- 1 | 2 | /* Mensajes de error de la autenticación */ 3 | export const authErrorMessages = { 4 | 'auth/network-request-failed': 'Por favor revise su conexión a internet', 5 | 'TypeError: Network request failed': 'Por favor revise su conexión a internet', 6 | 'auth/user-not-found': 'El usuario no existe, por favor registrese', 7 | 'auth/wrong-password': 'Correo o contraseña incorrectos', 8 | 'auth/invalid-email': 'Correo o contraseña incorrectos', 9 | 'auth/email-already-in-use': 'Ya existe una cuenta con ese correo', 10 | 'auth/weak-password': 'La contraseña debe tener al menos 6 caracteres', 11 | } 12 | 13 | /* Mensajes de error de la base de datos en tiempo real */ 14 | export const rtdbErrorMessages = { 15 | 'database/permission-denied': 'No tiene permisos para realizar esta acción', 16 | 'database/data-not-found': 'No se encontraron datos', 17 | 'Network request failed': 'Por favor revise su conexión a internet', 18 | } -------------------------------------------------------------------------------- /src/utils/upload.ts: -------------------------------------------------------------------------------- 1 | import { CLOUDINARY_CLOUD_NAME } from '@env'; 2 | 3 | /* Interfaces */ 4 | import { ImageResponse } from '../interfaces/ui'; 5 | 6 | /* Función para subir imagen a cloudinary */ 7 | export const uploadImage = async (image: string, uploadPreset: string) => { 8 | /* FormData a enviar */ 9 | const formData = new FormData(); 10 | formData.append('file', `data:image/jpg;base64,${ image }`); 11 | formData.append('upload_preset', uploadPreset); 12 | 13 | /* Peticion fetch a cloudinary */ 14 | const { secure_url }: ImageResponse = await fetch(`https://api.cloudinary.com/v1_1/${ CLOUDINARY_CLOUD_NAME }/upload`, { 15 | method: 'POST', 16 | headers: { 17 | /* Importante enviar como multipart/form-data */ 18 | 'Content-Type': 'multipart/form-data', 19 | }, 20 | body: formData 21 | }).then(resp => resp.json()); 22 | 23 | return secure_url; 24 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "compilerOptions": { 4 | /* Basic Options */ 5 | "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | "lib": ["es2017"], /* Specify library files to be included in the compilation. */ 8 | "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | "jsx": "react-native", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | // "outDir": "./", /* Redirect output structure to the directory. */ 15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "removeComments": true, /* Do not emit comments to output. */ 17 | "noEmit": true, /* Do not emit outputs. */ 18 | // "incremental": true, /* Enable incremental compilation */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true, /* Enable all strict type-checking options. */ 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | 32 | /* Additional Checks */ 33 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 37 | 38 | /* Module Resolution Options */ 39 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | // "types": [], /* Type declaration files to be included in compilation. */ 45 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 46 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 49 | "resolveJsonModule": true /* Allows importing modules with a ‘.json’ extension, which is a common practice in node projects. */ 50 | 51 | /* Source Map Options */ 52 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 53 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 54 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 55 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 56 | 57 | /* Experimental Options */ 58 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 59 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 60 | }, 61 | "exclude": [ 62 | "node_modules", "babel.config.js", "metro.config.js", "jest.config.js" 63 | ] 64 | } 65 | --------------------------------------------------------------------------------