.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 |
--------------------------------------------------------------------------------