├── .eslintrc.js ├── .github ├── CODEOWNERS ├── auto_assign.yml └── pull_request_template.md ├── .gitignore ├── .prettierrc.js ├── .woloxci ├── Dockerfile └── config.yml ├── CHANGELOG.md ├── Jenkinsfile ├── KICKOFF.md ├── LICENSE ├── README.md ├── generators ├── app │ ├── constants.js │ ├── index.js │ ├── tasks │ │ ├── appSetup │ │ │ ├── coreFiles │ │ │ │ ├── androidProjectSetup.js │ │ │ │ ├── appIcons.js │ │ │ │ ├── babelConfigSetup.js │ │ │ │ ├── baseFilesTemplate.js │ │ │ │ ├── cleanTargetsFromPods.js │ │ │ │ ├── createDotEnvFilesLocally.js │ │ │ │ ├── iosProjectSetup.js │ │ │ │ ├── multipleEnvIos │ │ │ │ │ ├── createSchemes.js │ │ │ │ │ └── schemeBase.js │ │ │ │ ├── packageJsonScripts.js │ │ │ │ ├── prettierrcConfigSetup.js │ │ │ │ ├── splashScreenSetup.js │ │ │ │ └── tabletSetup.js │ │ │ ├── featuresFiles │ │ │ │ ├── crashlyticsFeatureFiles.js │ │ │ │ ├── disableLandscapeOrientation.js │ │ │ │ ├── firebaseAnalyticsFeatureFiles.js │ │ │ │ ├── firebaseCoreFeatureFiles.js │ │ │ │ ├── firebasePerformanceSetup.js │ │ │ │ ├── loginAndSignUpFeatureFiles.js │ │ │ │ └── onBoardingFeatureFiles.js │ │ │ ├── files.js │ │ │ ├── index.js │ │ │ └── utils.js │ │ ├── configuringTasks │ │ │ ├── addFilesToGitIgnore.js │ │ │ ├── configureFastlane.js │ │ │ ├── installDependencies.js │ │ │ └── reactNativeInit.js │ │ ├── installTasks │ │ │ ├── bundleInstall.js │ │ │ ├── chmodFirebaseScript.js │ │ │ ├── configureIosProject.js │ │ │ ├── editBundleIdentifier.js │ │ │ ├── gitInitialization.js │ │ │ ├── installPods.js │ │ │ ├── linkAppAssets.js │ │ │ ├── lintFixProject.js │ │ │ └── scriptIosConfig.rb │ │ ├── nextSteps.js │ │ └── runCommand.js │ └── templates │ │ ├── .eslintignore │ │ ├── .eslintrc.ejs │ │ ├── .woloxci │ │ ├── Dockerfile │ │ └── config.ejs │ │ ├── App.js │ │ ├── Jenkinsfile │ │ ├── README.ejs │ │ ├── __mocks__ │ │ ├── @react-native-async-storage │ │ │ └── async-storage.js │ │ ├── fileMock.js │ │ ├── i18next.js │ │ ├── react-redux.js │ │ └── setup.js │ │ ├── __tests__ │ │ ├── redux │ │ │ ├── auth │ │ │ │ ├── actions.js │ │ │ │ └── reducer.js │ │ │ ├── store.js │ │ │ └── utils.js │ │ └── responses │ │ │ └── examples.js │ │ ├── android │ │ ├── launch_screen.xml │ │ └── version.gradle │ │ ├── assets │ │ └── fonts │ │ │ ├── Nunito-Bold.ttf │ │ │ ├── Nunito-BoldItalic.ttf │ │ │ ├── Nunito-Italic.ttf │ │ │ ├── Nunito-SemiBold.ttf │ │ │ ├── Nunito-SemiBoldItalic.ttf │ │ │ └── Nunito.ttf │ │ ├── googleServicesConfig │ │ ├── GoogleService-Info.plist │ │ ├── firebaseFilesScript.sh │ │ └── google-services.json │ │ ├── icons │ │ ├── androidIcons │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ └── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ └── iosIcons │ │ │ ├── Contents.json │ │ │ ├── Icon-20.png │ │ │ ├── Icon-20@2x.png │ │ │ ├── Icon-20@3x.png │ │ │ ├── Icon-29.png │ │ │ ├── Icon-29@2x.png │ │ │ ├── Icon-29@3x.png │ │ │ ├── Icon-40.png │ │ │ ├── Icon-40@2x.png │ │ │ ├── Icon-40@3x.png │ │ │ ├── Icon-60@2x.png │ │ │ ├── Icon-60@3x.png │ │ │ ├── Icon-76.png │ │ │ ├── Icon-76@2x.png │ │ │ ├── Icon-83.5@2x.png │ │ │ └── iTunesArtwork@2x.png │ │ ├── index.d.ejs │ │ ├── index.ejs │ │ ├── jest.config.js │ │ ├── pull_request_template.md │ │ ├── react-native.config.js │ │ ├── src │ │ ├── app │ │ │ ├── components │ │ │ │ ├── AppNavigator │ │ │ │ │ ├── helper.ts │ │ │ │ │ ├── index.ejs │ │ │ │ │ └── navigator.ejs │ │ │ │ ├── CustomButton │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.ts │ │ │ │ ├── CustomStatusBar │ │ │ │ │ └── index.js │ │ │ │ ├── CustomText │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.ts │ │ │ │ ├── CustomTextInput │ │ │ │ │ ├── components │ │ │ │ │ │ ├── InputLabel │ │ │ │ │ │ │ ├── constants.ts │ │ │ │ │ │ │ ├── i18n.ts │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ └── styles.ts │ │ │ │ │ │ └── ShowPassword │ │ │ │ │ │ │ ├── assets │ │ │ │ │ │ │ ├── ic_visibility.png │ │ │ │ │ │ │ ├── ic_visibility@2x.png │ │ │ │ │ │ │ ├── ic_visibility@3x.png │ │ │ │ │ │ │ ├── ic_visibility_off.png │ │ │ │ │ │ │ ├── ic_visibility_off@2x.png │ │ │ │ │ │ │ └── ic_visibility_off@3x.png │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ ├── interface.ts │ │ │ │ │ │ │ └── styles.ts │ │ │ │ │ ├── controller.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── interface.ts │ │ │ │ │ └── styles.ts │ │ │ │ ├── ErrorBoundary │ │ │ │ │ ├── ErrorFallBack.tsx │ │ │ │ │ ├── ExceptionHandler.tsx │ │ │ │ │ ├── i18n.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.ts │ │ │ │ └── Loadable │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.ts │ │ │ ├── hooks │ │ │ │ └── useRequest.ts │ │ │ ├── i18n.ejs │ │ │ ├── index.tsx │ │ │ └── screens │ │ │ │ ├── Auth │ │ │ │ ├── constants.ts │ │ │ │ └── screens │ │ │ │ │ ├── Login │ │ │ │ │ ├── i18n.ts │ │ │ │ │ ├── index.test.js │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.ts │ │ │ │ │ └── SignUp │ │ │ │ │ ├── i18n.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.ts │ │ │ │ ├── Home │ │ │ │ ├── index.ejs │ │ │ │ └── styles.ts │ │ │ │ └── OnBoarding │ │ │ │ ├── components │ │ │ │ └── Swiper │ │ │ │ │ ├── components │ │ │ │ │ ├── FirstScreen │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── styles.ts │ │ │ │ │ ├── Footer │ │ │ │ │ │ ├── buttonsInfo.ts │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── interface.ts │ │ │ │ │ │ └── styles.ts │ │ │ │ │ ├── SecondScreen │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── styles.ts │ │ │ │ │ └── ThirdScreen │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── styles.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── screens.tsx │ │ │ │ │ └── styles.ts │ │ │ │ ├── i18n.ts │ │ │ │ └── index.tsx │ │ ├── config │ │ │ ├── api.ts │ │ │ ├── fonts.ts │ │ │ ├── i18n.ts │ │ │ ├── index.ts │ │ │ ├── navigation.ejs │ │ │ └── reactotronConfig.ejs │ │ ├── constants │ │ │ ├── colors.ts │ │ │ ├── fonts.ts │ │ │ ├── platform.ts │ │ │ ├── routes.ejs │ │ │ ├── routesParamList.ejs │ │ │ ├── serializers.ts │ │ │ └── statusBar.ts │ │ ├── interfaces │ │ │ ├── authInterfaces.ts │ │ │ ├── globalInterfaces.ts │ │ │ ├── navigation.ts │ │ │ ├── reactotron.ts │ │ │ └── reduxInterfaces.ejs │ │ ├── redux │ │ │ ├── auth │ │ │ │ ├── actions.ejs │ │ │ │ └── reducer.ejs │ │ │ ├── middlewares │ │ │ │ └── analyticsMiddleware.ts │ │ │ └── store.ejs │ │ ├── services │ │ │ └── AuthService.ts │ │ └── utils │ │ │ ├── arrayUtils.ts │ │ │ ├── fontUtils.ts │ │ │ ├── navUtils.tsx │ │ │ ├── scalingUtils.ts │ │ │ ├── styleUtils.ts │ │ │ └── validations │ │ │ ├── i18n.ts │ │ │ └── validateUtils.ts │ │ └── tsconfig.json └── bitrise │ ├── apis │ ├── bitbucketApiConfig.js │ ├── bitriseApiConfig.js │ ├── githubApiConfig.js │ └── gitlabApiConfig.js │ ├── bitrise.js │ ├── bitriseInfo.json │ ├── bitriseREADME.md │ ├── bitriseUtils.js │ ├── constants.js │ ├── defaultBitrise.yml │ ├── tasks │ ├── bitriseInitialization.js │ ├── completeREADME.js │ ├── createBitriseApp.js │ └── nextSteps.js │ └── templates │ └── bitrise.ejs ├── package.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['wolox-react-native'], 3 | rules: { 4 | complexity: 'off', 5 | 'import/order': ['error', { 'newlines-between': 'always' }], 6 | 'no-nested-ternary': 'off', 7 | 'no-magic-numbers': 'off', 8 | 'new-cap': 'off', 9 | 'max-statements': 'off', 10 | 'no-console': 'off' 11 | }, 12 | // Ignore .ejs files for now. TODO: Search for eslint plugin for ejs 13 | ignorePatterns: ['*.ejs'] 14 | }; 15 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @guidoprinc @sfernandez11 -------------------------------------------------------------------------------- /.github/auto_assign.yml: -------------------------------------------------------------------------------- 1 | # Set to true to add reviewers to pull requests 2 | addReviewers: true 3 | 4 | # Set to true to add assignees to pull requests 5 | addAssignees: true 6 | 7 | # A list of reviewers to be added to pull requests (GitHub user name) 8 | reviewers: 9 | - sfernandez11 10 | - guidoprinc 11 | 12 | # A number of reviewers added to the pull request 13 | # Set 0 to add all the reviewers (default: 0) 14 | numberOfReviewers: 0 15 | 16 | # A list of assignees, overrides reviewers if set 17 | assignees: 18 | - sfernandez11 19 | - guidoprinc 20 | 21 | # A number of assignees to add to the pull request 22 | # Set to 0 to add all of the assignees. 23 | # Uses numberOfReviewers if unset. 24 | numberOfAssignees: 0 25 | 26 | # A list of keywords to be skipped the process that add reviewers if pull requests include it 27 | skipKeywords: 28 | - WIP 29 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | [Change!] Describe your feature, problems you had, notes, improvements and others. 4 | 5 | ## Screenshots 6 | 7 | [Change!] Show the screenshots or GIFs of the views you modified. 8 | 9 | ### Android 10 | 11 | [Change!] Add the screenshots or GIFs of the views you modified on Android. 12 | 13 | ### Ios 14 | 15 | [Change!] Add the screenshots or GIFs of the views you modified on iOS. 16 | 17 | ## Trello Card 18 | 19 | [Change!] Link to the associated Trello card. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | 5 | # VS Code tools 6 | .history 7 | .vscode -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: true, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'none', 6 | arrowParens: 'avoid' 7 | }; 8 | -------------------------------------------------------------------------------- /.woloxci/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.10.0-alpine 2 | 3 | WORKDIR /home/node 4 | 5 | COPY package.json . 6 | 7 | RUN yarn install 8 | ENV PATH /home/node/node_modules/.bin:$PATH 9 | 10 | WORKDIR /home/node/app 11 | -------------------------------------------------------------------------------- /.woloxci/config.yml: -------------------------------------------------------------------------------- 1 | config: 2 | dockerfile: .woloxci/Dockerfile 3 | project_name: wolmo-bootstrap-react-native 4 | 5 | steps: 6 | lint: 7 | - ln -sfn /home/node/node_modules node_modules 8 | - yarn run lint 9 | 10 | environment: 11 | GIT_COMMITTER_NAME: a 12 | GIT_COMMITTER_EMAIL: b 13 | LANG: C.UTF-8 14 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | @Library('wolox-ci') _ 2 | 3 | node { 4 | 5 | checkout scm 6 | 7 | woloxCi('.woloxci/config.yml'); 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © Wolox S.A. All rights reserved 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /generators/app/constants.js: -------------------------------------------------------------------------------- 1 | module.exports.GENERATOR_FEATURES = [ 2 | 'Drawer', 3 | 'Firebase Analytics', 4 | 'Firebase Crashlytics', 5 | 'Firebase Performance', 6 | 'Login and SignUp', 7 | 'OnBoarding', 8 | 'Tabs' 9 | ]; 10 | -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | const Generator = require('yeoman-generator'); 2 | 3 | // CONFIGURING 4 | const reactNativeInit = require('./tasks/configuringTasks/reactNativeInit'); 5 | const installDependencies = require('./tasks/configuringTasks/installDependencies'); 6 | const configureFastlane = require('./tasks/configuringTasks/configureFastlane'); 7 | const addFilesToGitIgnore = require('./tasks/configuringTasks/addFilesToGitIgnore'); 8 | // WRITING 9 | const appSetup = require('./tasks/appSetup'); 10 | // INSTALL 11 | const bundleInstall = require('./tasks/installTasks/bundleInstall'); 12 | const configureIosProject = require('./tasks/installTasks/configureIosProject'); 13 | const installPods = require('./tasks/installTasks/installPods'); 14 | const linkAppAssets = require('./tasks/installTasks/linkAppAssets'); 15 | const editBundleIdentifier = require('./tasks/installTasks/editBundleIdentifier'); 16 | const lintFixProject = require('./tasks/installTasks/lintFixProject'); 17 | const chmodFirebaseScript = require('./tasks/installTasks/chmodFirebaseScript'); 18 | const gitInitialization = require('./tasks/installTasks/gitInitialization'); 19 | // END 20 | const nextSteps = require('./tasks/nextSteps'); 21 | const { GENERATOR_FEATURES } = require('./constants'); 22 | 23 | class ReactNativeBootstrap extends Generator { 24 | constructor(args, opts) { 25 | super(args, opts); 26 | this.option('verbose', { 27 | desc: 'Turns on verbose logging', 28 | alias: 'v', 29 | type: Boolean, 30 | default: false 31 | }); 32 | this.conflicter.force = true; 33 | } 34 | 35 | prompting() { 36 | return this.prompt([ 37 | { 38 | type: 'input', 39 | name: 'name', 40 | message: "What's your project name?", 41 | validate: val => 42 | String(val).match(/^[$A-Z_][0-9A-Z_$]*$/i) 43 | ? true 44 | : `${val} is not a valid name for a project. Please use a valid identifier name (alphanumeric).` 45 | }, 46 | { 47 | type: 'checkbox', 48 | name: 'features', 49 | message: "What's features should this project include?", 50 | choices: GENERATOR_FEATURES, 51 | filter: values => 52 | values.reduce((answer, val) => { 53 | answer[val.replace(/ /g, '').toLowerCase()] = true; 54 | return answer; 55 | }, {}) 56 | }, 57 | { 58 | type: 'confirm', 59 | name: 'landscape', 60 | message: "Would you like to enable landscape orientation? Psst! You probably don't want this!", 61 | default: false 62 | } 63 | ]).then(({ features, landscape, name }) => { 64 | this.projectName = name; 65 | this.features = features; 66 | this.features.landscape = landscape; 67 | this.features.hasFirebase = 68 | features.firebasecrashlytics || features.firebaseanalytics || features.firebaseperformance; 69 | return this.prompt([ 70 | { 71 | type: 'input', 72 | name: 'bundleId', 73 | message: 'Enter the bundle id for your ios app', 74 | default: `com.wolox.${this.projectName}` 75 | } 76 | ]).then(answer => { 77 | this.bundleId = answer.bundleId; 78 | }); 79 | }); 80 | } 81 | 82 | configuring() { 83 | return Promise.resolve() 84 | .then(() => reactNativeInit.bind(this)()) 85 | .then(() => installDependencies.bind(this)()) 86 | .then(() => configureFastlane.bind(this)()) 87 | .then(() => addFilesToGitIgnore.bind(this)()); 88 | } 89 | 90 | writing() { 91 | appSetup.bind(this)(); 92 | } 93 | 94 | install() { 95 | return Promise.resolve() 96 | .then(() => bundleInstall.bind(this)()) 97 | .then(() => configureIosProject.bind(this)()) 98 | .then(() => installPods.bind(this)()) 99 | .then(() => linkAppAssets.bind(this)()) 100 | .then(() => editBundleIdentifier.bind(this)()) 101 | .then(() => lintFixProject.bind(this)()) 102 | .then(() => this.features.hasFirebase && chmodFirebaseScript.bind(this)()) 103 | .then(() => gitInitialization.bind(this)()); 104 | } 105 | 106 | end() { 107 | nextSteps.bind(this)(); 108 | } 109 | } 110 | 111 | module.exports = ReactNativeBootstrap; 112 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/coreFiles/appIcons.js: -------------------------------------------------------------------------------- 1 | function iosAppIcons() { 2 | const iosFiles = [ 3 | 'Contents.json', 4 | 'Icon-29.png', 5 | 'Icon-29@2x.png', 6 | 'Icon-29@3x.png', 7 | 'Icon-20.png', 8 | 'Icon-20@2x.png', 9 | 'Icon-20@3x.png', 10 | 'Icon-40.png', 11 | 'Icon-40@2x.png', 12 | 'Icon-40@3x.png', 13 | 'Icon-60@2x.png', 14 | 'Icon-60@3x.png', 15 | 'Icon-76.png', 16 | 'Icon-76@2x.png', 17 | 'Icon-83.5@2x.png', 18 | 'iTunesArtwork@2x.png' 19 | ]; 20 | 21 | iosFiles.forEach(fileName => { 22 | this.fs.copy( 23 | this.templatePath('icons/iosIcons', fileName), 24 | this.destinationPath( 25 | this.projectName, 26 | 'ios', 27 | this.projectName, 28 | 'Images.xcassets', 29 | 'AppIcon.appiconset', 30 | fileName 31 | ) 32 | ); 33 | }); 34 | } 35 | 36 | function androidAppIcons() { 37 | this.fs.copy( 38 | this.templatePath('icons/androidIcons'), 39 | this.destinationPath(this.projectName, 'android', 'app', 'src', 'main', 'res') 40 | ); 41 | } 42 | 43 | module.exports = function appIcons() { 44 | iosAppIcons.bind(this)(); 45 | androidAppIcons.bind(this)(); 46 | }; 47 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/coreFiles/babelConfigSetup.js: -------------------------------------------------------------------------------- 1 | module.exports = function babelConfigSetup() { 2 | const contentBabelConfig = this.fs.read(`${this.projectName}/babel.config.js`); 3 | const babelContent = `presets: ['module:metro-react-native-babel-preset'], 4 | plugins: [ 5 | 'react-native-reanimated/plugin', 6 | 'import-glob', 7 | [ 8 | 'module-resolver', 9 | { 10 | root: ['.'], 11 | extensions: ['.ios.js', '.android.js', '.js', '.jsx', '.ts', '.tsx', '.json'], 12 | alias: { 13 | '@app': './src/app', 14 | '@authScreens': './src/app/screens/Auth/screens', 15 | '@components': './src/app/components', 16 | '@config': './src/config', 17 | '@constants': './src/constants', 18 | '@hooks': './src/app/hooks', 19 | '@interfaces': './src/interfaces', 20 | '@navigationHelper': './src/app/components/AppNavigator/helper', 21 | '@redux': './src/redux', 22 | '@screens': './src/app/screens', 23 | '@services': './src/services', 24 | '@utils': './src/utils' 25 | } 26 | } 27 | ] 28 | ]`; 29 | const updatedBabelConfig = contentBabelConfig.replace( 30 | "presets: ['module:metro-react-native-babel-preset'],", 31 | babelContent 32 | ); 33 | this.fs.write(`${this.projectName}/babel.config.js`, updatedBabelConfig); 34 | }; 35 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/coreFiles/baseFilesTemplate.js: -------------------------------------------------------------------------------- 1 | const { copyFile, copyTemplateFile } = require('../utils'); 2 | const { 3 | UTILS_PATH, 4 | GLOBAL_INTERFACES, 5 | NAVIGATION_INTERFACES, 6 | REACTOTRON_INTERFACES, 7 | REACT_NATIVE_CONFIG, 8 | JENKINS_FILE, 9 | DOCKER_FILE, 10 | CI_CONFIG_FILE, 11 | PULL_REQUEST_TEMPLATE, 12 | FONTS, 13 | API_CONFIG, 14 | CONFIG, 15 | MAIN, 16 | APP_NAVIGATOR_NAVIGATOR, 17 | CUSTOM_TEXT_PATH, 18 | CUSTOM_BUTTON_PATH, 19 | CUSTOM_TEXT_INPUT_PATH, 20 | LOADABLE_PATH, 21 | ERROR_BOUNDARY, 22 | PLATFORM_CONSTANTS, 23 | SERIALIZERS_CONSTANTS, 24 | COLORS_CONSTANTS, 25 | ROUTES_CONSTANTS, 26 | ROUTES_PARAM_LIST_CONSTANTS, 27 | FONTS_CONSTANTS, 28 | STATUS_BAR_CONSTANTS, 29 | FONTS_CONFIG, 30 | HOOKS_PATH, 31 | HOME, 32 | HOME_STYLES, 33 | README, 34 | REDUX_STORE, 35 | REACTOTRON_CONFIG, 36 | I18N_CONFIG, 37 | INDEX, 38 | APP, 39 | NAVIGATION_CONFIG, 40 | APP_I18N, 41 | MOCKS, 42 | TESTS_STORE, 43 | TESTS_RESPONSES_PATH, 44 | TSCONFIG_FILE, 45 | INDEX_D_FILE, 46 | ESLINTRC_FILE, 47 | ESLINT_IGNORE_FILE, 48 | JEST_CONFIG_FILE, 49 | NAVIGATION_HELPER, 50 | APP_NAVIGATOR, 51 | AUTH_ACTIONS, 52 | AUTH_REDUCER, 53 | CUSTOM_STATUS_BAR, 54 | REDUX_INTERFACES 55 | } = require('../files'); 56 | 57 | const FILES = [ 58 | UTILS_PATH, 59 | GLOBAL_INTERFACES, 60 | NAVIGATION_INTERFACES, 61 | REACTOTRON_INTERFACES, 62 | REACT_NATIVE_CONFIG, 63 | JENKINS_FILE, 64 | DOCKER_FILE, 65 | PULL_REQUEST_TEMPLATE, 66 | FONTS, 67 | API_CONFIG, 68 | CONFIG, 69 | CUSTOM_TEXT_PATH, 70 | CUSTOM_BUTTON_PATH, 71 | CUSTOM_TEXT_INPUT_PATH, 72 | LOADABLE_PATH, 73 | ERROR_BOUNDARY, 74 | PLATFORM_CONSTANTS, 75 | SERIALIZERS_CONSTANTS, 76 | COLORS_CONSTANTS, 77 | FONTS_CONSTANTS, 78 | STATUS_BAR_CONSTANTS, 79 | I18N_CONFIG, 80 | FONTS_CONFIG, 81 | HOOKS_PATH, 82 | HOME_STYLES, 83 | APP, 84 | MAIN, 85 | MOCKS, 86 | TESTS_STORE, 87 | TESTS_RESPONSES_PATH, 88 | TSCONFIG_FILE, 89 | ESLINT_IGNORE_FILE, 90 | JEST_CONFIG_FILE, 91 | NAVIGATION_HELPER, 92 | CUSTOM_STATUS_BAR 93 | ]; 94 | 95 | const TEMPLATE_FILES = [ 96 | README, 97 | REDUX_STORE, 98 | ROUTES_CONSTANTS, 99 | ROUTES_PARAM_LIST_CONSTANTS, 100 | REACTOTRON_CONFIG, 101 | INDEX, 102 | APP_NAVIGATOR, 103 | APP_NAVIGATOR_NAVIGATOR, 104 | HOME, 105 | NAVIGATION_CONFIG, 106 | APP_I18N, 107 | CI_CONFIG_FILE, 108 | INDEX_D_FILE, 109 | ESLINTRC_FILE, 110 | AUTH_ACTIONS, 111 | AUTH_REDUCER, 112 | REDUX_INTERFACES 113 | ]; 114 | 115 | module.exports = function baseFilesTemplate() { 116 | TEMPLATE_FILES.forEach(copyTemplateFile.bind(this)); 117 | FILES.forEach(copyFile.bind(this)); 118 | }; 119 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/coreFiles/cleanTargetsFromPods.js: -------------------------------------------------------------------------------- 1 | module.exports = function cleanTargetsFromPods() { 2 | let podfileContent = this.fs.read(`${this.projectName}/ios/Podfile`); 3 | let numberLineTargetTest = null; 4 | podfileContent = podfileContent.split('\n'); 5 | podfileContent.forEach((line, index) => { 6 | if (line.includes(`target '${this.projectName}Tests'`)) { 7 | numberLineTargetTest = index; 8 | } 9 | }); 10 | podfileContent = [ 11 | ...podfileContent.slice(0, numberLineTargetTest), 12 | ...podfileContent.slice(numberLineTargetTest + 4) 13 | ].join('\n'); 14 | this.fs.write(`${this.projectName}/ios/Podfile`, podfileContent); 15 | this.fs.delete(`${this.projectName}/ios/${this.projectName}Tests/`); 16 | }; 17 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/coreFiles/createDotEnvFilesLocally.js: -------------------------------------------------------------------------------- 1 | module.exports = function createDotEnvFilesLocally() { 2 | this.fs.write(`${this.projectName}/.dev.env`, 'EMPTY_VARIABLE=DEVELOPMENT'); 3 | this.fs.write(`${this.projectName}/.stage.env`, 'EMPTY_VARIABLE=STAGE'); 4 | this.fs.write(`${this.projectName}/.production.env`, 'EMPTY_VARIABLE=PRODUCTION'); 5 | }; 6 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/coreFiles/iosProjectSetup.js: -------------------------------------------------------------------------------- 1 | const createSchemes = require('./multipleEnvIos/createSchemes'); 2 | 3 | function fixBundleIndentifier() { 4 | const iosProjectContent = this.fs.read( 5 | `${this.projectName}/ios/${this.projectName}.xcodeproj/project.pbxproj` 6 | ); 7 | const replaceRegex = new RegExp(`PRODUCT_NAME = ${this.projectName};`, 'g'); 8 | const fixedProjectContent = iosProjectContent.replace( 9 | replaceRegex, 10 | `PRODUCT_NAME = ${this.projectName};\n\t\t\t\tTARGETED_DEVICE_FAMILY = "1,2";` 11 | ); 12 | this.fs.write(`${this.projectName}/ios/${this.projectName}.xcodeproj/project.pbxproj`, fixedProjectContent); 13 | createSchemes.bind(this)(); 14 | } 15 | 16 | function disableDarkMode() { 17 | const infoPlistContent = this.fs.read(`${this.projectName}/ios/${this.projectName}/Info.plist`); 18 | const updatedInfoPlistContent = infoPlistContent.replace( 19 | 'UIViewControllerBasedStatusBarAppearance', 20 | 'UIUserInterfaceStyle\n\tLight\n\tUIViewControllerBasedStatusBarAppearance' 21 | ); 22 | this.fs.write(`${this.projectName}/ios/${this.projectName}/Info.plist`, updatedInfoPlistContent); 23 | } 24 | 25 | module.exports = function iosProjectSetup() { 26 | fixBundleIndentifier.bind(this)(); 27 | disableDarkMode.bind(this)(); 28 | }; 29 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/coreFiles/multipleEnvIos/createSchemes.js: -------------------------------------------------------------------------------- 1 | const schemeBase = require('./schemeBase'); 2 | 3 | module.exports = function createSchemes() { 4 | const commonPath = schemeName => 5 | `${this.projectName}/ios/${this.projectName}.xcodeproj/xcshareddata/xcschemes/${schemeName}.xcscheme`; 6 | this.fs.write(commonPath('qa'), schemeBase.bind(this)('dev', 'QA')); 7 | this.fs.write(commonPath('dev'), schemeBase.bind(this)('dev', 'Debug')); 8 | this.fs.write(commonPath('stage'), schemeBase.bind(this)('stage', 'Stage')); 9 | this.fs.write(commonPath('production'), schemeBase.bind(this)('production', 'Production')); 10 | this.fs.delete(commonPath(this.projectName)); 11 | }; 12 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/coreFiles/packageJsonScripts.js: -------------------------------------------------------------------------------- 1 | module.exports = function packgeJsonScripts() { 2 | const packageJson = this.fs.readJSON(this.destinationPath(this.projectName, 'package.json')); 3 | packageJson.scripts = packageJson.scripts || {}; 4 | packageJson.scripts.start = 'npx react-native start'; 5 | packageJson.scripts.android = 'npx react-native run-android --variant=qaDebug'; 6 | packageJson.scripts.ios = 'npx react-native run-ios --scheme qa'; 7 | packageJson.scripts.test = 'jest --passWithNoTests'; 8 | packageJson.scripts.clean = 'rm -rf $TMPDIR/react-* && watchman watch-del-all && yarn cache clean'; 9 | packageJson.scripts['force-clean'] = 10 | 'yarn run android:clean && yarn run clean && rm -rf ios/build && rm -rf ios/Pods && rm -rf node_modules/ && yarn install && cd ios/ && pod install'; 11 | packageJson.scripts['ios:clean'] = 'cd ios/ && rm -rf build && rm -rf Pods'; 12 | packageJson.scripts['android:clean'] = 'cd android/ && ./gradlew clean'; 13 | packageJson.scripts['android:build.qa'] = 'cd android && ./gradlew clean && ./gradlew assembleQaRelease'; 14 | packageJson.scripts['android:build.stage'] = 15 | 'cd android && ./gradlew clean && ./gradlew assembleStageRelease'; 16 | packageJson.scripts['android:build.production'] = 17 | 'cd android && ./gradlew clean && ./gradlew assembleProductionRelease'; 18 | packageJson.scripts['test:watch'] = 'jest --watch'; 19 | packageJson.scripts['test:debug'] = 'node --inspect node_modules/.bin/jest --runInBand'; 20 | packageJson.scripts.lint = 'eslint src --ext .js,.ts,.jsx,.tsx'; 21 | packageJson.scripts['lint-fix'] = 'eslint src --ext .js,.ts,.jsx,.tsx --fix'; 22 | packageJson.scripts['lint-diff'] = 23 | 'git diff --staged --name-only --relative --diff-filter=ACM | grep -E "\\.(ts|tsx|js|jsx)$" | xargs eslint'; 24 | packageJson.scripts['check-types'] = 'tsc'; 25 | packageJson.scripts['test:generate-coverage-file'] = 'node ./node_modules/@wolox/js-test-coverage-script'; 26 | packageJson.scripts['test:coverage'] = 27 | 'jest --env=jsdom --coverage --passWithNoTests --watchAll=false --silent --coverageThreshold "{}" --coverageReporters="json-summary" && yarn run test:generate-coverage-file'; 28 | packageJson.husky = packageJson.husky || {}; 29 | packageJson.husky.hooks = packageJson.husky.hooks || {}; 30 | packageJson.husky.hooks['pre-commit'] = 31 | 'yarn run lint-diff && yarn run check-types && yarn run test:coverage'; 32 | 33 | this.fs.writeJSON(this.destinationPath(this.projectName, 'package.json'), packageJson); 34 | }; 35 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/coreFiles/prettierrcConfigSetup.js: -------------------------------------------------------------------------------- 1 | module.exports = function babelConfigSetup() { 2 | const contentPrettierConfig = this.fs.read(`${this.projectName}/.prettierrc.js`); 3 | 4 | const beforeContentPrettier = `bracketSpacing: false, 5 | jsxBracketSameLine: true, 6 | singleQuote: true, 7 | trailingComma: 'all', 8 | arrowParens: 'avoid',`; 9 | 10 | const newContentPrettier = `bracketSpacing: true, 11 | jsxBracketSameLine: true, 12 | singleQuote: true, 13 | trailingComma: 'none', 14 | arrowParens: 'avoid'`; 15 | 16 | const updatedPrettierConfig = contentPrettierConfig.replace(beforeContentPrettier, newContentPrettier); 17 | 18 | this.fs.write(`${this.projectName}/.prettierrc.js`, updatedPrettierConfig); 19 | }; 20 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/coreFiles/splashScreenSetup.js: -------------------------------------------------------------------------------- 1 | module.exports = function splashScreenSetup() { 2 | // Update MainActivity 3 | const mainActivityContent = this.fs.read( 4 | `${this.projectName}/android/app/src/main/java/com/${this.projectName}/MainActivity.java` 5 | ); 6 | let updatedMainActivityContent = mainActivityContent.replace( 7 | 'import com.facebook.react.ReactActivity;', 8 | 'import android.os.Bundle;\nimport org.devio.rn.splashscreen.SplashScreen;\nimport com.facebook.react.ReactActivity;' 9 | ); 10 | updatedMainActivityContent = updatedMainActivityContent.replace( 11 | 'public class MainActivity extends ReactActivity {', 12 | 'public class MainActivity extends ReactActivity {\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\tSplashScreen.show(this);\n\t\tsuper.onCreate(savedInstanceState);\n\t}' 13 | ); 14 | 15 | this.fs.write( 16 | `${this.projectName}/android/app/src/main/java/com/${this.projectName}/MainActivity.java`, 17 | updatedMainActivityContent 18 | ); 19 | 20 | // Update AppDelegate 21 | const appDelegateContent = this.fs.read(`${this.projectName}/ios/${this.projectName}/AppDelegate.m`); 22 | let updatedAppDelegateContent = appDelegateContent.replace( 23 | '#import ', 24 | '#import \n#import "RNSplashScreen.h"' 25 | ); 26 | updatedAppDelegateContent = updatedAppDelegateContent.replace( 27 | 'return YES;', 28 | '[RNSplashScreen show];\n\treturn YES;' 29 | ); 30 | this.fs.write(`${this.projectName}/ios/${this.projectName}/AppDelegate.m`, updatedAppDelegateContent); 31 | 32 | // Add launch_screen.xml 33 | this.fs.copy( 34 | this.templatePath('android', 'launch_screen.xml'), 35 | this.destinationPath( 36 | this.projectName, 37 | 'android', 38 | 'app', 39 | 'src', 40 | 'main', 41 | 'res', 42 | 'layout', 43 | 'launch_screen.xml' 44 | ) 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/coreFiles/tabletSetup.js: -------------------------------------------------------------------------------- 1 | module.exports = function enableFullscreen() { 2 | const iosProjectContent = this.fs.read( 3 | `${this.projectName}/ios/${this.projectName}.xcodeproj/project.pbxproj` 4 | ); 5 | const replaceRegex = new RegExp('UISupportedInterfaceOrientations', 'g'); 6 | const fixedProjectContent = iosProjectContent.replace( 7 | replaceRegex, 8 | 'UIRequiresFullScreen\n\t\t\n\t\tUISupportedInterfaceOrientations' 9 | ); 10 | this.fs.write(`${this.projectName}/ios/${this.projectName}.xcodeproj/project.pbxproj`, fixedProjectContent); 11 | }; 12 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/featuresFiles/crashlyticsFeatureFiles.js: -------------------------------------------------------------------------------- 1 | function addConfigToAndroidFiles() { 2 | let buildGradleContent = this.fs.read(`${this.projectName}/android/build.gradle`); 3 | buildGradleContent = buildGradleContent.replace( 4 | '// NOTE: Do not place your application dependencies here; they belong', 5 | "classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'\n\t\t// NOTE: Do not place your application dependencies here; they belong" 6 | ); 7 | this.fs.write(`${this.projectName}/android/build.gradle`, buildGradleContent); 8 | 9 | let appBuildGradleContent = this.fs.read(`${this.projectName}/android/app/build.gradle`); 10 | appBuildGradleContent = appBuildGradleContent.replace( 11 | "apply plugin: 'com.google.gms.google-services'", 12 | "apply plugin: 'com.google.gms.google-services'\napply plugin: 'com.google.firebase.crashlytics'" 13 | ); 14 | this.fs.write(`${this.projectName}/android/app/build.gradle`, appBuildGradleContent); 15 | } 16 | 17 | module.exports = function crashlyticsFeatureFiles() { 18 | addConfigToAndroidFiles.bind(this)(); 19 | }; 20 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/featuresFiles/disableLandscapeOrientation.js: -------------------------------------------------------------------------------- 1 | module.exports = function disableLandscapeOrientation() { 2 | let iosInfoPlistContent = this.fs.read(`${this.projectName}/ios/${this.projectName}/Info.plist`); 3 | iosInfoPlistContent = iosInfoPlistContent.replace( 4 | 'UIInterfaceOrientationLandscapeLeft', 5 | '' 6 | ); 7 | iosInfoPlistContent = iosInfoPlistContent.replace( 8 | 'UIInterfaceOrientationLandscapeRight', 9 | '' 10 | ); 11 | this.fs.write(`${this.projectName}/ios/${this.projectName}/Info.plist`, iosInfoPlistContent); 12 | 13 | let androidManifestContent = this.fs.read(`${this.projectName}/android/app/src/main/AndroidManifest.xml`); 14 | androidManifestContent = androidManifestContent.replace( 15 | 'android:name=".MainActivity"', 16 | 'android:name=".MainActivity"\n\t\t\t\tandroid:screenOrientation="portrait"' 17 | ); 18 | this.fs.write(`${this.projectName}/android/app/src/main/AndroidManifest.xml`, androidManifestContent); 19 | }; 20 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/featuresFiles/firebaseAnalyticsFeatureFiles.js: -------------------------------------------------------------------------------- 1 | const { copyFile } = require('../utils'); 2 | const { ANALYTICS_MIDDLEWARE } = require('../files'); 3 | 4 | const FILES = [ANALYTICS_MIDDLEWARE]; 5 | 6 | module.exports = function firebaseAnalyticsFeatureFiles() { 7 | FILES.forEach(copyFile.bind(this)); 8 | }; 9 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/featuresFiles/firebaseCoreFeatureFiles.js: -------------------------------------------------------------------------------- 1 | function configureGoogleServices() { 2 | const googleServicesAndroidContent = this.fs.read( 3 | this.templatePath('googleServicesConfig', 'google-services.json') 4 | ); 5 | const googleServiceIOSContent = this.fs.read( 6 | this.templatePath('googleServicesConfig', 'GoogleService-Info.plist') 7 | ); 8 | ['qa', 'stage', 'production'].forEach(env => { 9 | const isProd = env === 'production'; 10 | this.fs.write( 11 | `${this.projectName}/android/app/google-services/google-services-${env}.json`, 12 | googleServicesAndroidContent.replace( 13 | 'com.wolmorn', 14 | `com.${this.projectName.toLowerCase()}${isProd ? '' : `.${env}`}` 15 | ) 16 | ); 17 | const capitalizedEnv = env.charAt(0).toUpperCase() + env.substring(1); 18 | this.fs.write( 19 | `${this.projectName}/ios/GoogleServices/GoogleService${capitalizedEnv}-Info.plist`, 20 | googleServiceIOSContent.replace( 21 | 'com.wolox.wolmorn', 22 | `com.wolox.${this.projectName}${isProd ? '' : `.${env}`}` 23 | ) 24 | ); 25 | }); 26 | } 27 | 28 | function copyFirebaseFilesScript() { 29 | const firebaseFilesScriptContent = this.fs.read( 30 | this.templatePath('googleServicesConfig', 'firebaseFilesScript.sh') 31 | ); 32 | this.fs.write( 33 | `${this.projectName}/firebaseFilesScript.sh`, 34 | firebaseFilesScriptContent.replace(/wolmorn/g, `${this.projectName}`) 35 | ); 36 | } 37 | 38 | function addConfigToAndroidFiles() { 39 | let buildGradleContent = this.fs.read(`${this.projectName}/android/build.gradle`); 40 | buildGradleContent = buildGradleContent.replace( 41 | '// NOTE: Do not place your application dependencies here; they belong', 42 | "classpath 'com.google.gms:google-services:4.3.10'\n\t\t// NOTE: Do not place your application dependencies here; they belong" 43 | ); 44 | this.fs.write(`${this.projectName}/android/build.gradle`, buildGradleContent); 45 | 46 | let appBuildGradleContent = this.fs.read(`${this.projectName}/android/app/build.gradle`); 47 | appBuildGradleContent = appBuildGradleContent.replace( 48 | 'apply plugin: "com.android.application"', 49 | 'apply plugin: "com.android.application"\napply plugin: \'com.google.gms.google-services\'' 50 | ); 51 | appBuildGradleContent = appBuildGradleContent.replace( 52 | 'apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)', 53 | 'task setFirebaseFiles() {\n\tif (getGradle().startParameter.taskRequests.size()) {\n\t\texec {\n\t\t\tdef buildArgs = getGradle().startParameter.taskRequests[0].getArgs()\n\t\t\texecutable \'../../firebaseFilesScript.sh\'\n\t\t\tif (buildArgs[1] != null) {\n\t\t\t\targs(buildArgs[1].toString(), "android")\n\t\t\t} else { \n\t\t\t\targs(buildArgs[0].toString(), "android")\n\t\t\t}\n\t\t}\n\t}\n}\n\nbuild.dependsOn setFirebaseFiles\n\napply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)' 54 | ); 55 | this.fs.write(`${this.projectName}/android/app/build.gradle`, appBuildGradleContent); 56 | } 57 | 58 | function addConfigToIosFiles() { 59 | let AppDelegateContent = this.fs.read(`${this.projectName}/ios/${this.projectName}/AppDelegate.m`); 60 | AppDelegateContent = AppDelegateContent.replace( 61 | '#import "AppDelegate.h"', 62 | '#import "AppDelegate.h"\n#import ' 63 | ); 64 | AppDelegateContent = AppDelegateContent.replace( 65 | 'didFinishLaunchingWithOptions:(NSDictionary *)launchOptions\n{', 66 | 'didFinishLaunchingWithOptions:(NSDictionary *)launchOptions\n{\n\t[FIRApp configure];' 67 | ); 68 | this.fs.write(`${this.projectName}/ios/${this.projectName}/AppDelegate.m`, AppDelegateContent); 69 | } 70 | 71 | module.exports = function firebaseCoreFeatureFiles() { 72 | configureGoogleServices.bind(this)(); 73 | copyFirebaseFilesScript.bind(this)(); 74 | addConfigToAndroidFiles.bind(this)(); 75 | addConfigToIosFiles.bind(this)(); 76 | }; 77 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/featuresFiles/firebasePerformanceSetup.js: -------------------------------------------------------------------------------- 1 | module.exports = function firebasePerformanceSetup() { 2 | let buildGradleContent = this.fs.read(`${this.projectName}/android/build.gradle`); 3 | buildGradleContent = buildGradleContent.replace( 4 | '// NOTE: Do not place your application dependencies here; they belong', 5 | "classpath 'com.google.firebase:perf-plugin:1.4.0'\n\t\t// NOTE: Do not place your application dependencies here; they belong" 6 | ); 7 | this.fs.write(`${this.projectName}/android/build.gradle`, buildGradleContent); 8 | 9 | let appBuildGradleContent = this.fs.read(`${this.projectName}/android/app/build.gradle`); 10 | appBuildGradleContent = appBuildGradleContent.replace( 11 | "apply plugin: 'com.google.gms.google-services'", 12 | "apply plugin: 'com.google.gms.google-services'\napply plugin: 'com.google.firebase.firebase-perf'" 13 | ); 14 | this.fs.write(`${this.projectName}/android/app/build.gradle`, appBuildGradleContent); 15 | }; 16 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/featuresFiles/loginAndSignUpFeatureFiles.js: -------------------------------------------------------------------------------- 1 | const { copyFile } = require('../utils'); 2 | const { AUTH_INTERFACES, AUTH_PATH, AUTH_SERVICE, TESTS_AUTH_PATH, TESTS_UTILS } = require('../files'); 3 | 4 | const FILES = [AUTH_INTERFACES, AUTH_PATH, TESTS_AUTH_PATH, TESTS_UTILS, AUTH_SERVICE]; 5 | 6 | module.exports = function loginAndSignUpFeatureFiles() { 7 | FILES.forEach(copyFile.bind(this)); 8 | }; 9 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/featuresFiles/onBoardingFeatureFiles.js: -------------------------------------------------------------------------------- 1 | const { copyFile } = require('../utils'); 2 | const { ONBOARDING_PATH } = require('../files'); 3 | 4 | const FILES = [ONBOARDING_PATH]; 5 | 6 | module.exports = function onBoardingFeatureFiles() { 7 | FILES.forEach(copyFile.bind(this)); 8 | }; 9 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/index.js: -------------------------------------------------------------------------------- 1 | const ora = require('ora'); 2 | 3 | // CORE FILES 4 | const androidProjectSetup = require('./coreFiles/androidProjectSetup'); 5 | const appIcons = require('./coreFiles/appIcons'); 6 | const babelConfigSetup = require('./coreFiles/babelConfigSetup'); 7 | const baseFilesTemplate = require('./coreFiles/baseFilesTemplate'); 8 | const cleanTargetsFromPods = require('./coreFiles/cleanTargetsFromPods'); 9 | const createDotEnvFilesLocally = require('./coreFiles/createDotEnvFilesLocally'); 10 | const enableFullscreen = require('./coreFiles/tabletSetup'); 11 | const iosProjectSetup = require('./coreFiles/iosProjectSetup'); 12 | const packageJsonScripts = require('./coreFiles/packageJsonScripts'); 13 | const prettierrcConfigSetup = require('./coreFiles/prettierrcConfigSetup'); 14 | const splashScreenSetup = require('./coreFiles/splashScreenSetup'); 15 | // FEATURES FILES 16 | const crashlyticsFeatureFiles = require('./featuresFiles/crashlyticsFeatureFiles'); 17 | const disableLandscapeOrientation = require('./featuresFiles/disableLandscapeOrientation'); 18 | const firebaseAnalyticsFeatureFiles = require('./featuresFiles/firebaseAnalyticsFeatureFiles'); 19 | const firebaseCoreFeatureFiles = require('./featuresFiles/firebaseCoreFeatureFiles'); 20 | const firebasePerformanceSetup = require('./featuresFiles/firebasePerformanceSetup'); 21 | const loginAndSignUpFeatureFiles = require('./featuresFiles/loginAndSignUpFeatureFiles'); 22 | const onBoardingFeatureFiles = require('./featuresFiles/onBoardingFeatureFiles'); 23 | 24 | module.exports = function index() { 25 | const spinner = ora({ 26 | spinner: 'bouncingBall', 27 | text: 'Creating project boilerplate' 28 | }).start(); 29 | 30 | // -------------------- APP CORE FILES --------------------- 31 | createDotEnvFilesLocally.bind(this)(); 32 | packageJsonScripts.bind(this)(); 33 | baseFilesTemplate.bind(this)(); 34 | appIcons.bind(this)(); 35 | babelConfigSetup.bind(this)(); 36 | prettierrcConfigSetup.bind(this)(); 37 | 38 | // ---------------- Android project configurationn ---------------- 39 | androidProjectSetup.bind(this)(); 40 | 41 | // ---------------- iOS project configuration ---------------- 42 | iosProjectSetup.bind(this)(); 43 | cleanTargetsFromPods.bind(this)(); 44 | 45 | // ---------------- Disable Landscape orientiation ---------------- 46 | if (!this.features.landscape) { 47 | disableLandscapeOrientation.bind(this)(); 48 | } 49 | 50 | // ---------------- Splash Screen ---------------- 51 | splashScreenSetup.bind(this)(); 52 | 53 | // ---------------- Features: Login and SignUp ---------------- 54 | if (this.features.loginandsignup) { 55 | loginAndSignUpFeatureFiles.bind(this)(); 56 | } 57 | 58 | // ---------------- Features: OnBoarding ---------------- 59 | if (this.features.onboarding) { 60 | onBoardingFeatureFiles.bind(this)(); 61 | } 62 | 63 | // ---------------- Features: Firebase ---------------- 64 | if (this.features.hasFirebase) { 65 | firebaseCoreFeatureFiles.bind(this)(); 66 | 67 | if (this.features.firebasecrashlytics) { 68 | crashlyticsFeatureFiles.bind(this)(); 69 | } 70 | if (this.features.firebaseanalytics) { 71 | firebaseAnalyticsFeatureFiles.bind(this)(); 72 | } 73 | if (this.features.firebaseperformance) { 74 | firebasePerformanceSetup.bind(this)(); 75 | } 76 | } 77 | 78 | // --------------- Enables fullscreen on iPad ---------------------------- 79 | enableFullscreen.bind(this)(); 80 | 81 | spinner.succeed('Boilerplate ready!'); 82 | }; 83 | -------------------------------------------------------------------------------- /generators/app/tasks/appSetup/utils.js: -------------------------------------------------------------------------------- 1 | module.exports.copyFile = function copyFile(filepath) { 2 | if (!this.fs) { 3 | throw new Error('File utils functions needs to be binded to the generator context'); 4 | } 5 | 6 | this.fs.copy( 7 | this.templatePath(...filepath.split('/')), 8 | this.destinationPath(this.projectName, ...filepath.split('/')) 9 | ); 10 | }; 11 | 12 | module.exports.copyTemplateFile = function copyTemplateFile(filepath) { 13 | if (!this.fs) { 14 | throw new Error('File utils functions needs to be binded to the generator context'); 15 | } 16 | 17 | const filepathWithoutExtension = filepath.substring(0, filepath.lastIndexOf('.')); 18 | const templatePath = `${filepathWithoutExtension}.ejs`; 19 | 20 | this.fs.copyTpl( 21 | this.templatePath(...templatePath.split('/')), 22 | this.destinationPath(this.projectName, ...filepath.split('/')), 23 | { projectName: this.projectName, features: this.features } 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /generators/app/tasks/configuringTasks/addFilesToGitIgnore.js: -------------------------------------------------------------------------------- 1 | module.exports = function addFilesToGitIgnore() { 2 | const gitIgnoreFile = this.fs.read(`${this.projectName}/.gitignore`); 3 | let updatedGitIgnoreFile = gitIgnoreFile.concat('\n# History (vs code)\n.history\n'); 4 | updatedGitIgnoreFile = updatedGitIgnoreFile.concat('\n# Env\n/*.env\n'); 5 | updatedGitIgnoreFile = updatedGitIgnoreFile.concat('\n# Tests Coverage\n/coverage\n'); 6 | if (this.features.hasFirebase) { 7 | updatedGitIgnoreFile = updatedGitIgnoreFile.concat( 8 | '\n# Google Services files\nandroid/app/google-services/\ngoogle-services.json\nios/GoogleServices\nGoogleService-Info.plist\n' 9 | ); 10 | } 11 | this.fs.write(`${this.projectName}/.gitignore`, updatedGitIgnoreFile); 12 | }; 13 | -------------------------------------------------------------------------------- /generators/app/tasks/configuringTasks/reactNativeInit.js: -------------------------------------------------------------------------------- 1 | const runCommand = require('../runCommand'); 2 | 3 | module.exports = function reactNativeInit() { 4 | return runCommand({ 5 | command: ['npx', ['react-native', 'init', this.projectName]], 6 | loadingMessage: 'Initializing react-native', 7 | context: this.options 8 | }).then(({ spinner }) => 9 | runCommand({ 10 | command: ['rm', ['-rf', '__tests__'], { cwd: `${process.cwd()}/${this.projectName}` }], 11 | context: this.options 12 | }) 13 | .then(() => { 14 | spinner.succeed('react-native ready!'); 15 | }) 16 | .catch(() => { 17 | spinner.fail('react-native set up failed. Turn verbose mode on for detailed logging'); 18 | }) 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /generators/app/tasks/installTasks/bundleInstall.js: -------------------------------------------------------------------------------- 1 | const runCommand = require('../runCommand'); 2 | 3 | module.exports = function bundleInstall() { 4 | return runCommand({ 5 | command: ['gem', ['install', 'bundler'], { cwd: `${process.cwd()}/${this.projectName}/ios` }], 6 | loadingMessage: 'Installing bundler', 7 | successMessage: 'bundler ready!', 8 | failureMessage: '"gem install bundler" failed. Turn verbose mode on for detailed logging', 9 | context: this.options 10 | }).then(() => 11 | runCommand({ 12 | command: ['bundle', ['install'], { cwd: `${process.cwd()}/${this.projectName}/ios` }], 13 | loadingMessage: 'Installing fastlane gems', 14 | successMessage: 'Fastlane ready!', 15 | failureMessage: '"bundle install" failed. Turn verbose mode on for detailed logging', 16 | context: this.options 17 | }) 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /generators/app/tasks/installTasks/chmodFirebaseScript.js: -------------------------------------------------------------------------------- 1 | const runCommand = require('../runCommand'); 2 | 3 | module.exports = function chmodFirebaseScript() { 4 | return runCommand({ 5 | command: ['chmod', ['u+x', 'firebaseFilesScript.sh'], { cwd: `${process.cwd()}/${this.projectName}` }], 6 | loadingMessage: 'Changing permissions of the firebaseFilesScript file...', 7 | successMessage: 'Permissions to run the firebaseFilesScript ready!', 8 | failureMessage: 9 | 'Permissions of the firebaseFilesScript file could not be changed. Try running "chmod u+x firebaseFilesScript.sh" in the root folder of your project', 10 | context: this.options 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /generators/app/tasks/installTasks/configureIosProject.js: -------------------------------------------------------------------------------- 1 | const runCommand = require('../runCommand'); 2 | 3 | module.exports = function configureIosProject() { 4 | return runCommand({ 5 | command: [ 6 | 'ruby', 7 | ['scriptIosConfig.rb', this.projectName, process.cwd(), this.features.hasFirebase], 8 | { cwd: `${this.templatePath()}/../tasks/installTasks` } 9 | ], 10 | loadingMessage: 'Deleting targets and generating build configurations...', 11 | successMessage: 'Ios project configured!', 12 | failureMessage: 'Ios project was not configured', 13 | context: this.options 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /generators/app/tasks/installTasks/editBundleIdentifier.js: -------------------------------------------------------------------------------- 1 | const runCommand = require('../runCommand'); 2 | 3 | function revertActionModifications() { 4 | return runCommand({ 5 | command: [ 6 | 'mv', 7 | ['.envCopy', '.env'], 8 | { cwd: `${process.cwd()}/${this.projectName}/ios/fastlane/config` } 9 | ], 10 | context: this.options 11 | }).then(() => 12 | runCommand({ 13 | command: [ 14 | 'mv', 15 | ['project_name_copy', 'project_name.rb'], 16 | { cwd: `${process.cwd()}/${this.projectName}/ios/fastlane/actions` } 17 | ], 18 | context: this.options 19 | }) 20 | ); 21 | } 22 | 23 | module.exports = function editBundleIdentifier() { 24 | return runCommand({ 25 | command: [ 26 | 'bundle', 27 | [ 28 | 'exec', 29 | 'fastlane', 30 | 'ios', 31 | 'update_bundle_identifier', 32 | `project_name:${this.projectName}`, 33 | `bundle_identifier:${this.bundleId}` 34 | ], 35 | { cwd: `${process.cwd()}/${this.projectName}/ios` } 36 | ], 37 | loadingMessage: 'Updating bundle identifier...', 38 | successMessage: 'Bundle identifier updated!', 39 | failureMessage: 'Bundle identifier update failed. Turn verbose mode on for detailed logging', 40 | context: this.options 41 | }).then(() => revertActionModifications.bind(this)()); 42 | }; 43 | -------------------------------------------------------------------------------- /generators/app/tasks/installTasks/gitInitialization.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-nested-callbacks */ 2 | const runCommand = require('../runCommand'); 3 | 4 | module.exports = function gitInitialization() { 5 | // git init 6 | return runCommand({ 7 | command: ['git', ['init'], { cwd: `${process.cwd()}/${this.projectName}` }], 8 | loadingMessage: 'Doing some git stuff...', 9 | context: this.options 10 | }) 11 | .then(({ spinner }) => 12 | // git add . 13 | runCommand({ 14 | command: ['git', ['add', '.'], { cwd: `${process.cwd()}/${this.projectName}` }], 15 | context: this.options 16 | }) 17 | .then(() => 18 | // git commit -m 'Kickoff!' 19 | runCommand({ 20 | command: ['git', ['commit', '-m', '"Kickoff!"'], { cwd: `${process.cwd()}/${this.projectName}` }], 21 | context: this.options 22 | }).then(() => { 23 | // check if the user wants to initialiaze the remote repository too 24 | spinner.stop(); 25 | return this.prompt([ 26 | { 27 | type: 'confirm', 28 | name: 'pushToRepo', 29 | message: 'Do you want to initialize your git remote repository? (i.e. github, bitbucket, etc)' 30 | } 31 | // eslint-disable-next-line consistent-return 32 | ]).then(({ pushToRepo }) => { 33 | if (pushToRepo) { 34 | // ask for the repository url 35 | return this.prompt([ 36 | { 37 | type: 'input', 38 | name: 'repoUrl', 39 | message: "What's your repository url? (ssh or https)", 40 | validate: val => (val ? true : 'Repository url is required to initialize it') 41 | } 42 | ]).then(({ repoUrl }) => { 43 | this.repoUrl = repoUrl; 44 | spinner.start(); 45 | // git remote add origin 46 | return runCommand({ 47 | command: [ 48 | 'git', 49 | ['remote', 'add', 'origin', repoUrl], 50 | { cwd: `${process.cwd()}/${this.projectName}` } 51 | ], 52 | context: this.options 53 | }).then(() => 54 | // git push origin master 55 | runCommand({ 56 | command: [ 57 | 'git', 58 | ['push', 'origin', 'master'], 59 | { cwd: `${process.cwd()}/${this.projectName}` } 60 | ], 61 | context: this.options 62 | }).then(() => { 63 | spinner.succeed('git ready'); 64 | }) 65 | ); 66 | }); 67 | } 68 | spinner.succeed('git ready'); 69 | }); 70 | }) 71 | ) 72 | .catch(() => { 73 | spinner.fail('Some git command failed. Turn verbose mode on for detailed logging'); 74 | }) 75 | ) 76 | .catch(spinner => { 77 | spinner.fail('Some git command failed. Turn verbose mode on for detailed logging'); 78 | }); 79 | }; 80 | -------------------------------------------------------------------------------- /generators/app/tasks/installTasks/installPods.js: -------------------------------------------------------------------------------- 1 | const runCommand = require('../runCommand'); 2 | 3 | module.exports = function installPods() { 4 | return runCommand({ 5 | command: [ 6 | 'npx', 7 | ['pod-install', 'ios', '--non-interactive'], 8 | { cwd: `${process.cwd()}/${this.projectName}` } 9 | ], 10 | loadingMessage: 'Installing Pods...', 11 | successMessage: 'Pods ready!', 12 | failureMessage: 'Pod install failed. Turn verbose mode on for detailed logging', 13 | context: this.options 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /generators/app/tasks/installTasks/linkAppAssets.js: -------------------------------------------------------------------------------- 1 | const runCommand = require('../runCommand'); 2 | 3 | module.exports = function linkAppAssets() { 4 | return runCommand({ 5 | command: ['npx', ['react-native', 'link'], { cwd: `${process.cwd()}/${this.projectName}` }], 6 | loadingMessage: 'Linking app assets...', 7 | successMessage: 'App assets ready!', 8 | failureMessage: 'Linking app assets failed. Turn verbose mode on for detailed logging', 9 | context: this.options 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /generators/app/tasks/installTasks/lintFixProject.js: -------------------------------------------------------------------------------- 1 | const runCommand = require('../runCommand'); 2 | 3 | module.exports = function lintFixProject() { 4 | return runCommand({ 5 | command: ['yarn', ['lint', '--fix'], { cwd: `${process.cwd()}/${this.projectName}` }], 6 | loadingMessage: 'Fixing linter problems...', 7 | successMessage: 'Linter problems fixed!', 8 | failureMessage: 'Error fixing some linter problems', 9 | context: this.options 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /generators/app/tasks/installTasks/scriptIosConfig.rb: -------------------------------------------------------------------------------- 1 | require 'xcodeproj' 2 | project_name = ARGV[0] 3 | total_path = ARGV[1] 4 | googleServices = ARGV[2] == 'true' 5 | project_path = total_path + '/' + project_name + '/ios/' + project_name + '.xcodeproj' 6 | project = Xcodeproj::Project.open(project_path) 7 | release_base_config_file = nil 8 | release_build_settings = nil 9 | 10 | # Delete unused targets 11 | def delete_targets_from_project(project, project_name) 12 | project.targets.each do |target| 13 | if target.name != project_name 14 | target_atts_obj = project.root_object.attributes['TargetAttributes'] 15 | target_atts_obj.delete(target.uuid) 16 | target.remove_from_project 17 | end 18 | end 19 | end 20 | 21 | delete_targets_from_project(project, project_name) 22 | delete_targets_from_project(project, project_name) 23 | 24 | project.targets.each do |target| 25 | if target.name == project_name 26 | target.build_configurations.each do |config| 27 | # Copy Release Build Configuration for Target 28 | if (config.name == 'Release') 29 | release_base_config_file = config.base_configuration_reference 30 | release_build_settings = config.build_settings 31 | # Delete Release Build Configuration from Target 32 | config.remove_from_project 33 | end 34 | end 35 | # Add new Build Configurations to Target 36 | target.add_build_configuration('QA', :release) 37 | target.add_build_configuration('Stage', :release) 38 | target.add_build_configuration('Production', :release) 39 | target.build_configurations.each do |config| 40 | # Copy Release Build Configuration to new configs 41 | if (config.name == 'Stage' || config.name == 'QA' || config.name == 'Production') 42 | config.base_configuration_reference=(release_base_config_file) 43 | config.build_settings=(release_build_settings) 44 | end 45 | end 46 | end 47 | end 48 | 49 | project.build_configurations.each do |config| 50 | # Copy Release Build Configuration for Project 51 | if (config.name == 'Release') 52 | release_base_config_file = config.base_configuration_reference 53 | release_build_settings = config.build_settings 54 | # Delete Release Build Configuration from Project 55 | config.remove_from_project 56 | end 57 | end 58 | # Add new Build Configurations to Project 59 | project.add_build_configuration('QA', :release) 60 | project.add_build_configuration('Stage', :release) 61 | project.add_build_configuration('Production', :release) 62 | project.build_configurations.each do |config| 63 | # Copy Release Build Configuration to new configs 64 | if (config.name == 'Stage' || config.name == 'QA' || config.name == 'Production') 65 | config.base_configuration_reference=(release_base_config_file) 66 | config.build_settings=(release_build_settings) 67 | end 68 | end 69 | 70 | # Google Services Script 71 | if googleServices 72 | project.targets.each do |target| 73 | if target.name == project_name 74 | if !target.shell_script_build_phases.find { |bp| bp.name == 'Google Services Script' } 75 | phase = target.new_shell_script_build_phase("Google Services Script") 76 | phase.shell_script = "\"$SRCROOT/../firebaseFilesScript.sh\" \"${PRODUCT_BUNDLE_IDENTIFIER}\" \"ios\"\ncp $SRCROOT/GoogleService-Info.plist $\{BUILT_PRODUCTS_DIR}/$\{PRODUCT_NAME}.app/GoogleService-Info.plist\n" 77 | end 78 | end 79 | end 80 | end 81 | 82 | project.save 83 | -------------------------------------------------------------------------------- /generators/app/tasks/nextSteps.js: -------------------------------------------------------------------------------- 1 | require('colors'); 2 | 3 | module.exports = function nextSteps() { 4 | console.log(`\n ${'NEXT STEPS!'.bold.underline.white} \n`); 5 | 6 | // Introduction message 7 | console.log( 8 | `● Your project folder has been created and it already includes the kickoff commit. ${ 9 | `cd ${this.projectName}/`.italic 10 | }`.cyan 11 | ); 12 | 13 | // Remote repository initialized 14 | if (this.repoUrl) { 15 | console.log(`● All this boilerplate has already been pushed to ${this.repoUrl}`.cyan); 16 | } 17 | 18 | // Firebase 19 | if (this.features.hasFirebase) { 20 | console.log( 21 | '● Remember to ask your team lead for the google services files, these files must be replaced for the corresponding application of https://console.firebase.google.com' 22 | .cyan 23 | ); 24 | console.log(`● Remember to check the Firebase packages version in the Android's build.gradle file`.cyan); 25 | } 26 | 27 | // Login and SignUp next steps 28 | if (this.features.loginandsignup) { 29 | console.log('● A basic login and signup feature has been added. The probably next steps there are:'.cyan); 30 | console.log(' Add proper validations and styles to the login and signup forms'.cyan); 31 | console.log(' Integrate login, logout and signup with API'.cyan); 32 | } 33 | 34 | console.log('\n\n'); 35 | }; 36 | -------------------------------------------------------------------------------- /generators/app/tasks/runCommand.js: -------------------------------------------------------------------------------- 1 | require('colors'); 2 | 3 | const { spawn } = require('child_process'); 4 | 5 | const ora = require('ora'); 6 | 7 | /** 8 | * @param {obj} options Receives only one argument which is an object of options: 9 | * - (mandatory) command {array}: list of paramaters to send to child_process.spawn 10 | * - loadingMessage {string}: Message shown while the command is running 11 | * - successMessage {string}: Message shown when the command finishes successfuly 12 | * - failureMessage {string}: Message shown when the command fails 13 | * - context {obj}: Yeoman context options and arguments 14 | * - timeout {int}: Time in millis that will be waited after the last console output to kill the process 15 | * 16 | * @returns {Promise} Returns a promise that resolves to the loading spinner if the loading message is present 17 | */ 18 | module.exports = function runCommand(options) { 19 | const spinner = 20 | options.loadingMessage && ora({ spinner: 'bouncingBall', text: options.loadingMessage }).start(); 21 | 22 | return new Promise((resolve, reject) => { 23 | const command = spawn(...options.command); 24 | const result = []; 25 | 26 | // eslint-disable-next-line init-declarations 27 | let killTimeout; 28 | let processKilled = false; 29 | 30 | function killProcess() { 31 | command.kill(); 32 | processKilled = true; 33 | clearTimeout(killTimeout); 34 | } 35 | function refreshKillTimeout() { 36 | clearTimeout(killTimeout); 37 | killTimeout = setTimeout(killProcess, options.timeout); 38 | } 39 | 40 | if (options.timeout) { 41 | refreshKillTimeout(); 42 | } 43 | 44 | command.stdout.on('data', data => { 45 | if (options.timeout) { 46 | refreshKillTimeout(); 47 | } 48 | if (data) { 49 | result.push(data); 50 | if (options.context && options.context.verbose) { 51 | console.log(data.toString()); 52 | } 53 | } 54 | }); 55 | 56 | command.stderr.on('data', data => { 57 | if (options.timeout) { 58 | refreshKillTimeout(); 59 | } 60 | if (options.context && options.context.verbose && data) { 61 | const msg = data.toString(); 62 | console.log(/warning/.test(msg) ? msg.yellow : msg.red); 63 | } 64 | }); 65 | 66 | command.on('close', code => { 67 | if (options.timeout) { 68 | clearTimeout(killTimeout); 69 | } 70 | if (code === 0 || processKilled) { 71 | if (spinner && options.successMessage) { 72 | spinner.succeed(options.successMessage); 73 | } 74 | resolve({ spinner, result: result.join('\n') }); 75 | } else { 76 | if (spinner && options.failureMessage) { 77 | spinner.fail(options.failureMessage); 78 | } 79 | reject(spinner); 80 | } 81 | }); 82 | }); 83 | }; 84 | -------------------------------------------------------------------------------- /generators/app/templates/.eslintignore: -------------------------------------------------------------------------------- 1 | # Folder created by circle ci when installing fastlane gems 2 | vendor/* 3 | -------------------------------------------------------------------------------- /generators/app/templates/.eslintrc.ejs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['wolox-react-native'], 3 | rules: { 4 | complexity: 'off', 5 | 'import/order': ['error', { 'newlines-between': 'always' }], 6 | 'no-nested-ternary': 'off', 7 | 'no-magic-numbers': 'off', 8 | 'new-cap': 'off' 9 | }, 10 | settings: { 11 | 'import/ignore': ['node_modules'], 12 | 'import/resolver': { 13 | node: { 14 | paths: ['src'], 15 | settings: { 16 | 'import/resolver': { 17 | node: { 18 | paths: ['src'], 19 | extensions: ['.ios.js', '.android.js', '.js', '.jsx', '.ts', '.tsx', '.json'] 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /generators/app/templates/.woloxci/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10.11.0-alpine 2 | 3 | WORKDIR /home/node 4 | 5 | COPY package.json . 6 | 7 | RUN yarn install 8 | ENV PATH /home/node/node_modules/.bin:$PATH 9 | 10 | WORKDIR /home/node/app 11 | -------------------------------------------------------------------------------- /generators/app/templates/.woloxci/config.ejs: -------------------------------------------------------------------------------- 1 | config: 2 | dockerfile: .woloxci/Dockerfile 3 | project_name: <%= projectName %> 4 | 5 | steps: 6 | lint: 7 | - ln -sfn /home/node/node_modules node_modules 8 | - yarn run lint 9 | - yarn run check-types 10 | - yarn run test 11 | 12 | environment: 13 | GIT_COMMITTER_NAME: a 14 | GIT_COMMITTER_EMAIL: b 15 | LANG: C.UTF-8 16 | -------------------------------------------------------------------------------- /generators/app/templates/App.js: -------------------------------------------------------------------------------- 1 | import 'react-native-gesture-handler'; 2 | import React from 'react'; 3 | import { Provider } from 'react-redux'; 4 | import { persistStore } from 'redux-persist'; 5 | import { PersistGate } from 'redux-persist/integration/react'; 6 | import '@config'; 7 | import store from '@redux/store'; 8 | 9 | import App from './src/app'; 10 | 11 | const persistor = persistStore(store); 12 | 13 | export default function index() { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /generators/app/templates/Jenkinsfile: -------------------------------------------------------------------------------- 1 | @Library('wolox-ci') _ 2 | 3 | node { 4 | 5 | checkout scm 6 | 7 | woloxCi('.woloxci/config.yml'); 8 | } 9 | -------------------------------------------------------------------------------- /generators/app/templates/__mocks__/@react-native-async-storage/async-storage.js: -------------------------------------------------------------------------------- 1 | let cache = {}; 2 | 3 | export const changeCache = object => { 4 | cache = object; 5 | }; 6 | export default { 7 | setItem: (key, value) => 8 | new Promise((resolve, reject) => 9 | typeof key !== 'string' || typeof value !== 'string' 10 | ? reject(new Error('key and value must be string')) 11 | : resolve((cache[key] = value)) 12 | ), 13 | getItem: key => 14 | new Promise(resolve => 15 | Object.prototype.hasOwnProperty.call(cache, key) ? resolve(cache[key]) : resolve(null) 16 | ), 17 | removeItem: key => 18 | new Promise((resolve, reject) => 19 | Object.prototype.hasOwnProperty.call(cache, key) 20 | ? resolve(delete cache[key]) 21 | : reject(new Error('No such key!')) 22 | ), 23 | clear: () => new Promise(resolve => resolve((cache = {}))), 24 | 25 | getAllKeys: () => new Promise(resolve => resolve(Object.keys(cache))) 26 | }; 27 | -------------------------------------------------------------------------------- /generators/app/templates/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // Mocks every media file to return its filename. Makes it possible to test that 4 | // the correct images are loaded for components. 5 | 6 | module.exports = { 7 | process: (_, filename) => `module.exports = '${JSON.stringify(path.basename(filename))}';` 8 | }; 9 | -------------------------------------------------------------------------------- /generators/app/templates/__mocks__/i18next.js: -------------------------------------------------------------------------------- 1 | const resources = { 2 | en: {}, 3 | es: {} 4 | }; 5 | 6 | const DEFAULT_LOCALE = 'es'; 7 | 8 | export default { 9 | addResources: (lang, ns, translations) => 10 | Object.keys(translations).forEach(t => { 11 | resources[lang][`${ns}:${t}`] = translations[t]; 12 | }), 13 | t: str => resources[DEFAULT_LOCALE][str] || str 14 | }; 15 | -------------------------------------------------------------------------------- /generators/app/templates/__mocks__/react-redux.js: -------------------------------------------------------------------------------- 1 | export const useDispatch = jest.fn(); 2 | export const useSelector = jest.fn(); 3 | -------------------------------------------------------------------------------- /generators/app/templates/__mocks__/setup.js: -------------------------------------------------------------------------------- 1 | jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper'); 2 | global.window = {}; 3 | global.window = global; 4 | -------------------------------------------------------------------------------- /generators/app/templates/__tests__/redux/auth/actions.js: -------------------------------------------------------------------------------- 1 | import { actionCreators, actions } from '@redux/auth/actions'; 2 | import api from '@config/api'; 3 | 4 | import { store } from '../store'; 5 | import { mapActionsToTypes } from '../utils'; 6 | 7 | describe('testing auth actions', () => { 8 | beforeEach(() => { 9 | store.clearActions(); 10 | }); 11 | test('test AUTH_INIT', async () => { 12 | const expectedActions = [actions.AUTH_INIT]; 13 | await store.dispatch(actionCreators.init()); 14 | expect(mapActionsToTypes(store.getActions())).toEqual(expectedActions); 15 | }); 16 | test('test LOGIN SUCCESS AND INJECTIONS', async () => { 17 | api.setHeader = jest.fn(); 18 | const expectedActions = [actions.LOGIN, actions.LOGIN_SUCCESS]; 19 | await store.dispatch(actionCreators.login({})); 20 | expect(mapActionsToTypes(store.getActions())).toEqual(expectedActions); 21 | expect(api.setHeader.mock.calls).toHaveLength(1); 22 | }); 23 | test('test LOGOUT', async () => { 24 | const expectedActions = [actions.LOGOUT, actions.LOGOUT_SUCCESS]; 25 | await store.dispatch(actionCreators.logout()); 26 | expect(mapActionsToTypes(store.getActions())).toEqual(expectedActions); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /generators/app/templates/__tests__/redux/auth/reducer.js: -------------------------------------------------------------------------------- 1 | import { actions } from '@redux/auth/actions'; 2 | import authReducer, { initialState } from '@redux/auth/reducer'; 3 | 4 | describe('testing reducers', () => { 5 | test('test reducer with AUTH_INIT action', async () => { 6 | const action = { 7 | type: actions.AUTH_INIT, 8 | payload: { id: '', email: '' }, 9 | target: 'currentUser' 10 | }; 11 | await expect(authReducer(undefined, action)).toEqual({ 12 | ...initialState, 13 | initialLoading: false, 14 | currentUser: { id: '', email: '' } 15 | }); 16 | }); 17 | test('test reducer with LOGOUT action', async () => { 18 | const action = { 19 | type: actions.LOGOUT_SUCCESS, 20 | target: 'currentUser' 21 | }; 22 | await expect(authReducer(undefined, action)).toEqual({ 23 | ...initialState, 24 | currentUserLoading: false, 25 | currentUserError: null, 26 | currentUser: undefined 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /generators/app/templates/__tests__/redux/store.js: -------------------------------------------------------------------------------- 1 | import configureStore from 'redux-mock-store'; 2 | import thunk from 'redux-thunk'; 3 | import { fetchMiddleware } from 'redux-recompose'; 4 | 5 | function waitForInjections() { 6 | return new Promise(resolve => setImmediate(resolve)); 7 | } 8 | 9 | function createWaitForInjections() { 10 | return () => next => async action => { 11 | const result = await next(action); 12 | await waitForInjections(); 13 | return result; 14 | }; 15 | } 16 | 17 | const waitForInjectionsMiddleware = createWaitForInjections(); 18 | 19 | const middlewares = [waitForInjectionsMiddleware, thunk, fetchMiddleware]; 20 | 21 | export const mockStore = configureStore(middlewares); 22 | const initialState = { auth: { currentUser: null } }; 23 | export const store = mockStore(initialState); 24 | -------------------------------------------------------------------------------- /generators/app/templates/__tests__/redux/utils.js: -------------------------------------------------------------------------------- 1 | export const mapActionsToTypes = actions => actions.map(action => action.type); 2 | -------------------------------------------------------------------------------- /generators/app/templates/__tests__/responses/examples.js: -------------------------------------------------------------------------------- 1 | export const internalServerError = new Promise(resolve => 2 | resolve({ 3 | data: { 4 | status: 500, 5 | error: 'Internal Server Error' 6 | }, 7 | ok: false, 8 | status: 500, 9 | problem: 'SERVER_ERROR' 10 | }) 11 | ); 12 | export const successApiCall = new Promise(resolve => 13 | resolve({ 14 | data: { 15 | data: { 16 | attributes: { 17 | email: '' 18 | } 19 | } 20 | }, 21 | ok: true, 22 | status: 200, 23 | problem: null, 24 | headers: { 25 | 'access-token': '', 26 | client: '', 27 | uid: '' 28 | } 29 | }) 30 | ); 31 | -------------------------------------------------------------------------------- /generators/app/templates/android/launch_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /generators/app/templates/android/version.gradle: -------------------------------------------------------------------------------- 1 | def String generateVersionName() { 2 | if (project.hasProperty('versionName')) { 3 | return project.property('versionName'); 4 | } 5 | String versionName = VERSION_NAME 6 | versionName 7 | } 8 | 9 | def Integer generateVersionCode() { 10 | if (project.hasProperty('versionCode')) { 11 | return project.property('versionCode') as int; 12 | } 13 | String versionCode = VERSION_CODE 14 | versionCode.toInteger() 15 | } 16 | 17 | private void save(keyName, keyValue) { 18 | ant.propertyfile(file: "../gradle.properties") { 19 | entry(key: keyName, value: keyValue) 20 | } 21 | } 22 | 23 | private void saveVersionName(major, minor, patch) { 24 | save("VERSION_NAME", "${major}.${minor}.${patch}".toString()) 25 | } 26 | 27 | private void saveVersionCode(versionCode) { 28 | save("VERSION_CODE", versionCode) 29 | } 30 | 31 | task bumperVersionCode { 32 | group = 'bumper' 33 | doLast { 34 | saveVersionCode(versionCode) 35 | } 36 | } 37 | 38 | task bumperVersionName { 39 | group = 'bumper' 40 | doLast { 41 | save("VERSION_NAME" ,versionName) 42 | } 43 | } 44 | 45 | ext { 46 | generateVersionCode = this.&generateVersionCode 47 | generateVersionName = this.&generateVersionName 48 | } 49 | -------------------------------------------------------------------------------- /generators/app/templates/assets/fonts/Nunito-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/assets/fonts/Nunito-Bold.ttf -------------------------------------------------------------------------------- /generators/app/templates/assets/fonts/Nunito-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/assets/fonts/Nunito-BoldItalic.ttf -------------------------------------------------------------------------------- /generators/app/templates/assets/fonts/Nunito-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/assets/fonts/Nunito-Italic.ttf -------------------------------------------------------------------------------- /generators/app/templates/assets/fonts/Nunito-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/assets/fonts/Nunito-SemiBold.ttf -------------------------------------------------------------------------------- /generators/app/templates/assets/fonts/Nunito-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/assets/fonts/Nunito-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /generators/app/templates/assets/fonts/Nunito.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/assets/fonts/Nunito.ttf -------------------------------------------------------------------------------- /generators/app/templates/googleServicesConfig/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 1048568404151-camksnsehgk30fn2fckt1fu9iis8jc3m.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.1048568404151-camksnsehgk30fn2fckt1fu9iis8jc3m 9 | API_KEY 10 | AIzaSyB9gVWZZWAZUzT11CoAxDDrSxxMUfBjqVE 11 | GCM_SENDER_ID 12 | 1048568404151 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | com.wolox.wolmorn 17 | PROJECT_ID 18 | wolmobootstraprn 19 | STORAGE_BUCKET 20 | wolmobootstraprn.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:1048568404151:ios:47fdb4dc9eab1de0cd5532 33 | DATABASE_URL 34 | https://wolmobootstraprn.firebaseio.com 35 | 36 | 37 | -------------------------------------------------------------------------------- /generators/app/templates/googleServicesConfig/firebaseFilesScript.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -x 4 | 5 | echo "Platform:" $2 6 | echo "Variant:" $1 7 | echo "Replacing google files..." 8 | 9 | # ANDROID 10 | if [ $2 = "android" ] 11 | then 12 | if [[ $1 == *"Qa"* ]]; then 13 | cp -f google-services/google-services-qa.json ./google-services.json 14 | elif [[ $1 == *"Stage"* ]]; then 15 | cp -f google-services/google-services-stage.json ./google-services.json 16 | elif [[ $1 == *"Prod"* ]]; then 17 | cp -f google-services/google-services-production.json ./google-services.json 18 | else 19 | echo "No valid variant detected" 20 | fi 21 | fi 22 | 23 | # IOS 24 | if [ $2 = "ios" ] 25 | then 26 | if [ $1 = "com.wolox.wolmorn.dev" ]; then 27 | cp -f GoogleServices/GoogleServiceQa-Info.plist GoogleService-Info.plist 28 | elif [ $1 = "com.wolox.wolmorn.stage" ]; then 29 | cp -f GoogleServices/GoogleServiceStage-Info.plist GoogleService-Info.plist 30 | elif [ $1 = "com.wolox.wolmorn" ]; then 31 | cp -f GoogleServices/GoogleServiceProduction-Info.plist GoogleService-Info.plist 32 | else 33 | echo "No valid variant detected" 34 | fi 35 | fi 36 | 37 | echo "Replacing google files finished." 38 | -------------------------------------------------------------------------------- /generators/app/templates/googleServicesConfig/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "1048568404151", 4 | "firebase_url": "https://wolmobootstraprn.firebaseio.com", 5 | "project_id": "wolmobootstraprn", 6 | "storage_bucket": "wolmobootstraprn.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:1048568404151:android:59c730a1cafc7acdcd5532", 12 | "android_client_info": { 13 | "package_name": "com.wolmorn" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "1048568404151-9fln80in54jih46ip4uu8psdhlg8r7q1.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyCnFOLCB-o72Te7YclqFnQs_Y1hiwUMuGI" 25 | } 26 | ], 27 | "services": { 28 | "appinvite_service": { 29 | "other_platform_oauth_client": [ 30 | { 31 | "client_id": "1048568404151-9fln80in54jih46ip4uu8psdhlg8r7q1.apps.googleusercontent.com", 32 | "client_type": 3 33 | }, 34 | { 35 | "client_id": "1048568404151-camksnsehgk30fn2fckt1fu9iis8jc3m.apps.googleusercontent.com", 36 | "client_type": 2, 37 | "ios_info": { 38 | "bundle_id": "com.wolox.wolmorn" 39 | } 40 | } 41 | ] 42 | } 43 | } 44 | } 45 | ], 46 | "configuration_version": "1" 47 | } 48 | -------------------------------------------------------------------------------- /generators/app/templates/icons/androidIcons/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/androidIcons/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /generators/app/templates/icons/androidIcons/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/androidIcons/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /generators/app/templates/icons/androidIcons/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/androidIcons/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /generators/app/templates/icons/androidIcons/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/androidIcons/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /generators/app/templates/icons/androidIcons/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/androidIcons/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /generators/app/templates/icons/androidIcons/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/androidIcons/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /generators/app/templates/icons/androidIcons/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/androidIcons/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /generators/app/templates/icons/androidIcons/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/androidIcons/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /generators/app/templates/icons/androidIcons/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/androidIcons/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /generators/app/templates/icons/androidIcons/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/androidIcons/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon-20@2x.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "Icon-20@3x.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "Icon-29@2x.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "Icon-29@3x.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "Icon-40@2x.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "Icon-40@3x.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "Icon-60@2x.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "Icon-60@3x.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "Icon-20.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "20x20" 56 | }, 57 | { 58 | "filename" : "Icon-20@2x.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "Icon-29.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "29x29" 68 | }, 69 | { 70 | "filename" : "Icon-29@2x.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "Icon-40.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "Icon-40@2x.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "Icon-76.png", 89 | "idiom" : "ipad", 90 | "scale" : "1x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "Icon-76@2x.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "Icon-83.5@2x.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "83.5x83.5" 104 | }, 105 | { 106 | "filename" : "iTunesArtwork@2x.png", 107 | "idiom" : "ios-marketing", 108 | "scale" : "1x", 109 | "size" : "1024x1024" 110 | } 111 | ], 112 | "info" : { 113 | "author" : "xcode", 114 | "version" : 1 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-20.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-20@2x.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-20@3x.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-29.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-29@2x.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-29@3x.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-40.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-40@2x.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-40@3x.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-60@2x.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-60@3x.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-76.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-76@2x.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/Icon-83.5@2x.png -------------------------------------------------------------------------------- /generators/app/templates/icons/iosIcons/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/icons/iosIcons/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /generators/app/templates/index.d.ejs: -------------------------------------------------------------------------------- 1 | declare module '*.jpg'; 2 | declare module '*.json'; 3 | declare module '*.png'; 4 | declare module 'cerealizr'; 5 | declare module 'reactotron-apisauce'; 6 | declare module 'reactotron-react-native/dist/flipper'; 7 | declare module 'redux-persist-seamless-immutable'; 8 | declare module 'redux-recompose'; 9 | <%_ if(features.loginandsignup) { _%> 10 | declare module 'react-native-keyboard-spacer'; 11 | <%_ } _%> 12 | -------------------------------------------------------------------------------- /generators/app/templates/index.ejs: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | 3 | import App from './App'; 4 | 5 | AppRegistry.registerComponent('<%= projectName %>', () => App); 6 | -------------------------------------------------------------------------------- /generators/app/templates/jest.config.js: -------------------------------------------------------------------------------- 1 | const { defaults } = require('jest-config'); 2 | 3 | module.exports = { 4 | preset: 'react-native', 5 | moduleFileExtensions: [...defaults.moduleFileExtensions, 'png', 'jpg'], 6 | setupFilesAfterEnv: [ 7 | '/__mocks__/setup.js', 8 | './node_modules/react-native-gesture-handler/jestSetup.js', 9 | '@testing-library/jest-native/extend-expect' 10 | ], 11 | transformIgnorePatterns: [ 12 | '/node_modules/@react-native-async-storage/async-storage/(?!(lib))', 13 | 'node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@react-native-firebase/app)' 14 | ], 15 | moduleNameMapper: { 16 | '^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 17 | '/__mocks__/fileMock.js' 18 | }, 19 | testPathIgnorePatterns: [ 20 | '/node_modules/', 21 | '/.history/', 22 | '/__tests__/redux/store.js', 23 | '/__tests__/redux/utils.js', 24 | '/__tests__/responses/' 25 | ], 26 | collectCoverage: true, 27 | coverageThreshold: { 28 | global: { 29 | branches: 40, 30 | functions: 35, 31 | lines: 50, 32 | statements: 45 33 | } 34 | }, 35 | collectCoverageFrom: [ 36 | '**/*.{js,jsx,ts,tsx}', 37 | '!**/console.js', 38 | '!**/node_modules/**', 39 | '!**/.history/**', 40 | '!**/build/**', 41 | '!**/migrations/**', 42 | '!**/config/**', 43 | '!**/scripts/**', 44 | '!**/app/models/**', 45 | '!**/test/**', 46 | '!**/__tests__/**', 47 | '!**/__mocks__/**', 48 | '!**/coverage/**', 49 | '!**/server.js', 50 | '!**/app/middlewares/apiInfo**' 51 | ] 52 | }; 53 | -------------------------------------------------------------------------------- /generators/app/templates/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | [Change!] Describe your feature, problems you had, notes, improvements and others. 4 | 5 | ## Screenshots 6 | 7 | [Change!] Show the screenshots of the views you modified. 8 | 9 | ### Android 10 | 11 | #### Galaxy S (or similar) 12 | 13 | #### Pixel 2 (or similar) 14 | 15 | #### Pixel 3 (or similar) 16 | 17 | #### Pixel 3XL (or similar) 18 | 19 | ### iOS 20 | 21 | #### iPhone SE (or 5s) 22 | 23 | #### iPhone 6/7/8 24 | 25 | #### iPhone X/XS 26 | 27 | #### iPhone XS Max 28 | 29 | ## Jira Card 30 | 31 | [Change!] Link to the associated Jira card. 32 | -------------------------------------------------------------------------------- /generators/app/templates/react-native.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | project: { 3 | ios: {}, 4 | android: {} 5 | }, 6 | assets: ['./assets/fonts/'] 7 | }; 8 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/AppNavigator/helper.ts: -------------------------------------------------------------------------------- 1 | import { createRef } from 'react'; 2 | import { RouteProp, NavigationContainerRef } from '@react-navigation/native'; 3 | 4 | export const navigationRef = createRef>(); 5 | 6 | export const getRoute = (state: any): RouteProp => { 7 | const route = state.routes[state.index]; 8 | return route.state ? getRoute(route.state) : route; 9 | }; 10 | 11 | export const getActiveRoute = () => getRoute(navigationRef.current!.getRootState()); 12 | 13 | const useNavigation = () => navigationRef.current; 14 | export default useNavigation; 15 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/AppNavigator/index.ejs: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | <%_ if(features.firebaseanalytics) { _%> 4 | import analytics from '@react-native-firebase/analytics'; 5 | <%_ } _%> 6 | import { NavigationContainer, NavigationState } from '@react-navigation/native'; 7 | import { navigationRef, getActiveRoute, getRoute } from '@navigationHelper'; 8 | import CustomStatusBar from '@components/CustomStatusBar'; 9 | import withLoadable from '@components/Loadable'; 10 | <%_ if(features.firebaseanalytics) { _%> 11 | import { snakeCaseSerializer } from '@constants/serializers'; 12 | <%_ } _%> 13 | import { Nullable } from '@interfaces/globalInterfaces'; 14 | import { State } from '@interfaces/reduxInterfaces'; 15 | 16 | import Navigator from './navigator'; 17 | 18 | const AppNavigator = () => { 19 | const [routeName, setRouteName] = useState>(null); 20 | 21 | <%_ if(features.firebaseanalytics) { _%> 22 | const onStateChange = async (state?: NavigationState) => { 23 | <%_ } else { _%> 24 | const onStateChange = (state?: NavigationState) => { 25 | <%_ } _%> 26 | const previousRouteName = routeName; 27 | const currentRouteName = getRoute(state).name; 28 | if (previousRouteName !== currentRouteName) { 29 | <%_ if(features.firebaseanalytics) { _%> 30 | await analytics().logScreenView(snakeCaseSerializer.serialize({ screenClass: currentRouteName, screenName: currentRouteName })); 31 | <%_ } _%> 32 | setRouteName(currentRouteName); 33 | } 34 | }; 35 | 36 | return ( 37 | { 40 | setRouteName(getActiveRoute().name); 41 | }} 42 | onStateChange={onStateChange}> 43 | 44 | 45 | 46 | ); 47 | }; 48 | 49 | export default withLoadable(() => useSelector((state: State) => state.auth.initialLoading))(AppNavigator); 50 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/AppNavigator/navigator.ejs: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createStackNavigator } from '@react-navigation/stack'; 3 | <%_ if(features.tabs) { _%> 4 | import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; 5 | <%_ } _%> 6 | <%_ if(features.drawer) { _%> 7 | import { createDrawerNavigator } from '@react-navigation/drawer'; 8 | <%_ } _%> 9 | <%_ if(features.onboarding || features.loginandsignup) { _%> 10 | import { useSelector } from 'react-redux'; 11 | import { State } from '@interfaces/reduxInterfaces'; 12 | <%_ } _%> 13 | import Routes from '@constants/routes'; 14 | import { RoutesParamList } from '@constants/routesParamList'; 15 | import { <%_ if(features.loginandsignup) { _%>authStackNavConfig,<%_ } _%> appStackNavConfig<%_ if(features.drawer) { _%>, drawerStackNavConfig <%_ }_%><%_ if(features.tabs) { _%>, tabNavConfig <%_ }_%>} from '@config/navigation'; 16 | import { inferRoute } from '@utils/navUtils'; 17 | <%_ if(features.loginandsignup) { _%> 18 | import Login from '@authScreens/Login'; 19 | import SignUp from '@authScreens/SignUp'; 20 | <%_ } _%> 21 | <%_ if(features.onboarding) { _%> 22 | import OnBoarding from '@screens/OnBoarding'; 23 | <%_ } _%> 24 | import Home from '@screens/Home'; 25 | 26 | const Stack = createStackNavigator(); 27 | <%_ if(features.loginandsignup) { _%> 28 | 29 | const AuthStack = () => ( 30 | <> 31 | {inferRoute(Stack)(Routes.Login, Login)} 32 | {inferRoute(Stack)(Routes.SignUp, SignUp)} 33 | 34 | ); 35 | <%_ } _%> 36 | <%_ if(features.tabs) { _%> 37 | 38 | const Tab = createBottomTabNavigator(); 39 | function HomeTabs() { 40 | return ( 41 | 42 | {inferRoute(Tab)(Routes.Tab1, Home)} 43 | {inferRoute(Tab)(Routes.Tab2, Home)} 44 | 45 | ); 46 | } 47 | <%_ } _%> 48 | <%_ if(features.drawer) { _%> 49 | 50 | const Drawer = createDrawerNavigator(); 51 | function DrawerNavigator() { 52 | return ( 53 | {inferRoute(Drawer)(Routes.Home, <%_ if(features.tabs) { _%>HomeTabs<%_ }else{ _%>Home <%_ } _%>)} 54 | ); 55 | } 56 | 57 | function AppStack() { 58 | return <>{inferRoute(Stack)(Routes.Home, DrawerNavigator)}; 59 | } 60 | <%_ } else { _%> 61 | 62 | function AppStack() { 63 | return <>{inferRoute(Stack)(Routes.Home, <%_ if(features.tabs) { _%>HomeTabs<%_ } else { _%>Home <%_ } _%>)}; 64 | } 65 | <%_ } _%> 66 | 67 | <%_ if(features.onboarding) { _%> 68 | const OnBoardingStack = () => (<>{inferRoute(Stack)(Routes.OnBoarding, OnBoarding)}); 69 | 70 | const Navigator = () => { 71 | const hasAccessOnBoarding = useSelector((state: State) => state.auth.hasAccessOnBoarding); 72 | <%_ if(features.loginandsignup) { _%> 73 | const currentUser = useSelector((state: State) => state.auth.currentUser); 74 | const defaultStackConfig = currentUser ? appStackNavConfig : authStackNavConfig; 75 | return ( 76 | 77 | {currentUser ? (hasAccessOnBoarding ? AppStack() : OnBoardingStack()) : AuthStack()} 78 | 79 | ); 80 | <%_ } else { _%> 81 | return ( 82 | 83 | {hasAccessOnBoarding ? AppStack() : OnBoardingStack()} 84 | 85 | ); 86 | <%_ }_%> 87 | }; 88 | <%_ } else { _%> 89 | const Navigator = () => { 90 | <%_ if(features.loginandsignup) { _%> 91 | const currentUser = useSelector((state: State) => state.auth.currentUser); 92 | const defaultStackConfig = currentUser ? appStackNavConfig : authStackNavConfig; 93 | return {currentUser ? AppStack() : AuthStack()}; 94 | <%_ } else { _%> 95 | return {AppStack()}; 96 | <%_ } _%> 97 | }; 98 | <%_ } _%> 99 | 100 | export default Navigator; 101 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomButton/constants.ts: -------------------------------------------------------------------------------- 1 | import { TouchableOpacityProps, ViewStyle, ImageStyle, TextStyle } from 'react-native'; 2 | import { VariantsInterface as CustomTextVariants } from '@components/CustomText/constants'; 3 | 4 | export const VARIANTS = ['borderless', 'radial', 'black', 'green', 'white', 'gray']; 5 | 6 | interface CustomButtonVariants { 7 | borderless?: boolean; 8 | radial?: boolean; 9 | black?: boolean; 10 | } 11 | 12 | export interface CustomButtonProps extends CustomTextVariants, CustomButtonVariants { 13 | onPress: TouchableOpacityProps['onPress']; 14 | activeOpacity?: number; 15 | disabled?: boolean; 16 | icon?: number; 17 | iconStyle?: ImageStyle; 18 | style?: ViewStyle; 19 | textStyle?: TextStyle; 20 | title?: string; 21 | } 22 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomButton/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, memo } from 'react'; 2 | import { TouchableOpacity, Image } from 'react-native'; 3 | import { getCustomStyles } from '@utils/styleUtils'; 4 | 5 | import CustomText from '../CustomText'; 6 | 7 | import { VARIANTS, CustomButtonProps } from './constants'; 8 | import styles from './styles'; 9 | 10 | const CustomButton = (props: CustomButtonProps) => { 11 | const customStyles = useCallback(() => getCustomStyles(VARIANTS, props, styles), [props]); 12 | const customTextStyles = useCallback(() => getCustomStyles(VARIANTS, props, styles, 'Content'), [props]); 13 | const { onPress, style, activeOpacity, icon, title, iconStyle, disabled, textStyle, ...textProps } = props; 14 | return ( 15 | 20 | {icon && } 21 | {title && ( 22 | 23 | {title} 24 | 25 | )} 26 | 27 | ); 28 | }; 29 | 30 | CustomButton.defaultProps = { 31 | activeOpacity: 0.8 32 | }; 33 | 34 | export default memo(CustomButton); 35 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomButton/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { transparent, black, green, gray, white } from '@constants/colors'; 3 | 4 | const iconSize = 20; 5 | 6 | const borderlessStyle = { 7 | borderWidth: 0, 8 | backgroundColor: transparent 9 | }; 10 | 11 | export default StyleSheet.create({ 12 | container: { 13 | flexDirection: 'row', 14 | alignItems: 'center', 15 | justifyContent: 'center', 16 | padding: 8 17 | }, 18 | icon: { 19 | height: iconSize, 20 | width: iconSize 21 | }, 22 | borderless: borderlessStyle, 23 | radial: { 24 | borderRadius: 100 25 | }, 26 | black: { 27 | backgroundColor: black 28 | }, 29 | blackContent: { 30 | color: white 31 | }, 32 | white: { 33 | backgroundColor: white 34 | }, 35 | whiteContent: { 36 | color: black 37 | }, 38 | gray: { 39 | backgroundColor: gray 40 | }, 41 | grayContent: { 42 | color: black 43 | }, 44 | borderlessContent: { 45 | color: gray 46 | }, 47 | green: { 48 | backgroundColor: green 49 | }, 50 | greenContent: { 51 | color: white 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomStatusBar/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StatusBar } from 'react-native'; 3 | import { statusBarStyles } from '@config/navigation'; 4 | 5 | const CustomStatusBar = ({ routeName }) => { 6 | const statusBarProps = statusBarStyles[routeName] || statusBarStyles.default; 7 | return ; 8 | }; 9 | 10 | export default CustomStatusBar; 11 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomText/constants.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { TextProps, TextStyle, StyleProp } from 'react-native'; 3 | 4 | /* 5 | ** TODO: You can add styles to Base like Family Font to be the Text styles base! 6 | ** if you want to add a custom style, you need to add it here and in VARIANTS 7 | */ 8 | 9 | export const VARIANTS = [ 10 | 'semiBold', 11 | 'bold', 12 | 'italic', 13 | 'center', 14 | 'justify', 15 | 'right', 16 | 'blue', 17 | 'gray', 18 | 'green', 19 | 'white', 20 | 'xxsmall', 21 | 'xsmall', 22 | 'small', 23 | 'medium', 24 | 'xmedium', 25 | 'big', 26 | 'xbig', 27 | 'error' 28 | ]; 29 | 30 | export interface VariantsInterface { 31 | semiBold?: boolean; 32 | bold?: boolean; 33 | italic?: boolean; 34 | center?: boolean; 35 | justify?: boolean; 36 | right?: boolean; 37 | blue?: boolean; 38 | gray?: boolean; 39 | green?: boolean; 40 | white?: boolean; 41 | xxsmall?: boolean; 42 | xsmall?: boolean; 43 | small?: boolean; 44 | medium?: boolean; 45 | xmedium?: boolean; 46 | big?: boolean; 47 | xbig?: boolean; 48 | error?: boolean; 49 | } 50 | 51 | export interface CustomTextProps extends VariantsInterface { 52 | children: ReactNode; 53 | textProps?: TextProps; 54 | style?: StyleProp; 55 | } 56 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomText/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, memo } from 'react'; 2 | import { Text } from 'react-native'; 3 | import { getCustomStyles } from '@utils/styleUtils'; 4 | 5 | import { VARIANTS, CustomTextProps } from './constants'; 6 | import styles from './styles'; 7 | 8 | const CustomText = (props: CustomTextProps) => { 9 | const customStyles = useCallback(() => getCustomStyles(VARIANTS, props, styles), [props]); 10 | const { textProps, style, children } = props; 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | }; 17 | 18 | CustomText.defaultProps = { 19 | textProps: {} 20 | }; 21 | 22 | export default memo(CustomText); 23 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomText/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import fonts from '@config/fonts'; 3 | import { blue, white, green, gray, transparent, red } from '@constants/colors'; 4 | import { SIZES } from '@constants/fonts'; 5 | import { moderateScale } from '@utils/scalingUtils'; 6 | import { StringObject, NumberObject } from '@interfaces/globalInterfaces'; 7 | 8 | const getColors = (colorsObj: StringObject) => 9 | Object.keys(colorsObj).reduce( 10 | (colors, color) => ({ ...colors, ...{ [color]: { color: colorsObj[color] } } }), 11 | {} 12 | ); 13 | 14 | const getSizes = (sizesObj: NumberObject) => 15 | Object.keys(sizesObj).reduce( 16 | (sizes, size) => ({ ...sizes, ...{ [size]: { fontSize: moderateScale(sizesObj[size]) } } }), 17 | {} 18 | ); 19 | 20 | export default StyleSheet.create({ 21 | base: { 22 | ...fonts.baseFont, 23 | backgroundColor: transparent 24 | }, 25 | semiBold: fonts.semiBoldFont, 26 | bold: fonts.boldFont, 27 | italic: fonts.baseItalicFont, 28 | center: { 29 | textAlign: 'center' 30 | }, 31 | justify: { 32 | textAlign: 'justify' 33 | }, 34 | right: { 35 | textAlign: 'right' 36 | }, 37 | error: { 38 | color: red 39 | }, 40 | // Colors 41 | ...getColors({ blue, gray, green, white }), 42 | // Sizes 43 | ...getSizes({ 44 | xxsmall: SIZES.XXSMALL, 45 | xsmall: SIZES.XSMALL, 46 | small: SIZES.SMALL, 47 | medium: SIZES.MEDIUM, 48 | xmedium: SIZES.XMEDIUM, 49 | big: SIZES.BIG, 50 | xbig: SIZES.XBIG 51 | }) 52 | }); 53 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/InputLabel/constants.ts: -------------------------------------------------------------------------------- 1 | import { SIZES } from '@constants/fonts'; 2 | 3 | export const TOP_DEGRADE = -25; 4 | export const LABEL_SIZE = SIZES.SMALL; 5 | export const LABEL_SIZE_DEGRADE = SIZES.XSMALL; 6 | export const LEFT_DEGRADE = 5; 7 | export const LEFT_SMALL_DEGRADE = 0; 8 | export const ANIMATION_DURATION = 200; 9 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/InputLabel/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18next from 'i18next'; 2 | 3 | i18next.addResources('es', 'INPUT_LABEL', { 4 | OPTIONAL: 'Opcional' 5 | }); 6 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/InputLabel/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | import { Animated, TextStyle } from 'react-native'; 3 | import i18next from 'i18next'; 4 | import CustomText from '@components/CustomText'; 5 | import { gray, black } from '@constants/colors'; 6 | 7 | import { 8 | ANIMATION_DURATION, 9 | LABEL_SIZE, 10 | LABEL_SIZE_DEGRADE, 11 | LEFT_DEGRADE, 12 | LEFT_SMALL_DEGRADE, 13 | TOP_DEGRADE 14 | } from './constants'; 15 | import './i18n'; 16 | import styles from './styles'; 17 | 18 | export interface Props { 19 | animated?: boolean; 20 | hasValue: boolean; 21 | isFocused?: boolean; 22 | isOptional?: boolean; 23 | label: string; 24 | labelStyle?: TextStyle; 25 | } 26 | 27 | const InputLabel = ({ animated, hasValue, isFocused, isOptional, label, labelStyle }: Props) => { 28 | const animatedIsFocused = useRef(new Animated.Value(hasValue ? 1 : 0)).current; 29 | useEffect(() => { 30 | if (animated) { 31 | const realValue = isFocused || hasValue ? 1 : 0; 32 | Animated.timing(animatedIsFocused, { 33 | toValue: realValue, 34 | duration: ANIMATION_DURATION, 35 | useNativeDriver: false 36 | }).start(); 37 | } 38 | }, [animated, animatedIsFocused, isFocused, hasValue]); 39 | const animatedLabelStyle = { 40 | top: animatedIsFocused.interpolate({ 41 | inputRange: [0, 1], 42 | outputRange: [0, TOP_DEGRADE] 43 | }), 44 | left: animatedIsFocused.interpolate({ 45 | inputRange: [0, 1], 46 | outputRange: [LEFT_DEGRADE, LEFT_SMALL_DEGRADE] 47 | }), 48 | fontSize: animatedIsFocused.interpolate({ 49 | inputRange: [0, 1], 50 | outputRange: [LABEL_SIZE, LABEL_SIZE_DEGRADE] 51 | }), 52 | color: animatedIsFocused.interpolate({ 53 | inputRange: [0, 1], 54 | outputRange: [gray, black] 55 | }) 56 | }; 57 | const formattedLabel = `${label}${isOptional ? ` (${i18next.t('INPUT_LABEL:OPTIONAL')})` : ''}`; 58 | return animated ? ( 59 | 62 | {formattedLabel} 63 | 64 | ) : ( 65 | 66 | {formattedLabel} 67 | 68 | ); 69 | }; 70 | 71 | export default InputLabel; 72 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/InputLabel/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import { transparent } from '@constants/colors'; 3 | import { SIZES } from '@constants/fonts'; 4 | import fonts from '@config/fonts'; 5 | 6 | export default StyleSheet.create({ 7 | label: { 8 | ...fonts.baseFont, 9 | backgroundColor: transparent, 10 | fontSize: SIZES.XSMALL, 11 | marginTop: 5 12 | }, 13 | animatedlabel: { 14 | left: 5, 15 | position: 'absolute' 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/assets/ic_visibility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/assets/ic_visibility.png -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/assets/ic_visibility@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/assets/ic_visibility@2x.png -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/assets/ic_visibility@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/assets/ic_visibility@3x.png -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/assets/ic_visibility_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/assets/ic_visibility_off.png -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/assets/ic_visibility_off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/assets/ic_visibility_off@2x.png -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/assets/ic_visibility_off@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wolox/wolmo-bootstrap-react-native/b42ea13cc172c8e77eaea10b451dcec4298da1fa/generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/assets/ic_visibility_off@3x.png -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TouchableOpacity, Image } from 'react-native'; 3 | 4 | import icVisible from './assets/ic_visibility.png'; 5 | import icVisibleOff from './assets/ic_visibility_off.png'; 6 | 7 | import { ShowPasswordProps } from './interface'; 8 | import styles from './styles'; 9 | 10 | const ShowPassword = ({ onShowPassword, passwordVisible }: ShowPasswordProps) => ( 11 | 12 | 13 | 14 | ); 15 | 16 | export default ShowPassword; 17 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/interface.ts: -------------------------------------------------------------------------------- 1 | import { TouchableOpacityProps } from 'react-native'; 2 | 3 | export interface ShowPasswordProps { 4 | onShowPassword: TouchableOpacityProps['onPress']; 5 | passwordVisible: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/components/ShowPassword/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | export default StyleSheet.create({ 4 | container: { 5 | display: 'flex', 6 | flexDirection: 'row', 7 | alignItems: 'center' 8 | }, 9 | icon: { 10 | width: 20, 11 | marginLeft: 8 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/controller.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react'; 2 | import { TextInput } from 'react-native'; 3 | import { Controller, ControllerProps, Control } from 'react-hook-form'; 4 | import CustomTextInput from '@components/CustomTextInput'; 5 | import { CustomTextInputProps } from '@components/CustomTextInput/interface'; 6 | 7 | type Props = CustomTextInputProps & Omit & { control: Control }; 8 | 9 | const CustomTextInputController = forwardRef(function CustomTextInputController( 10 | { name, control, defaultValue = '', rules, ...props }, 11 | ref 12 | ) { 13 | return ( 14 | ( 20 | { 25 | onBlur(); 26 | if (props.onBlur) props.onBlur(e); 27 | }} 28 | onChange={e => { 29 | onChange(e); 30 | if (props.onChange) props.onChange(e); 31 | }} 32 | ref={ref} 33 | /> 34 | )} 35 | /> 36 | ); 37 | }); 38 | 39 | export default CustomTextInputController; 40 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState, memo, forwardRef } from 'react'; 2 | import { TextInput, View } from 'react-native'; 3 | import CustomText from '@components/CustomText'; 4 | import { transparent } from '@constants/colors'; 5 | 6 | import InputLabel from './components/InputLabel'; 7 | import ShowPassword from './components/ShowPassword'; 8 | import { CustomTextInputProps as Props } from './interface'; 9 | import styles from './styles'; 10 | 11 | const CustomTextInput = forwardRef(function CustomTextInput( 12 | { 13 | animated, 14 | autoCompleteType, 15 | disabled, 16 | error, 17 | errorContainerStyle, 18 | errorStyle, 19 | inputContainerStyle, 20 | inputTextStyles, 21 | isOptional, 22 | label, 23 | labelStyle, 24 | multiline, 25 | onBlur, 26 | onChange, 27 | onFocus, 28 | placeholder, 29 | placeholderColor, 30 | secureTextEntry, 31 | showError, 32 | showEye, 33 | style, 34 | value, 35 | ...props 36 | }, 37 | ref 38 | ) { 39 | const [showPassword, setShowPassword] = useState(false); 40 | const [isFocused, setIsFocused] = useState(false); 41 | 42 | const handleShowPassword = () => setShowPassword(prevShowPassword => !prevShowPassword); 43 | const handleFocus = useCallback( 44 | e => { 45 | setIsFocused(true); 46 | if (onFocus) onFocus(e); 47 | }, 48 | [onFocus] 49 | ); 50 | const handleBlur = useCallback( 51 | e => { 52 | setIsFocused(false); 53 | if (onBlur) onBlur(e); 54 | }, 55 | [onBlur] 56 | ); 57 | 58 | const borderColorStyle = () => { 59 | if (disabled) return styles.bottomBorderLightGray; 60 | if (isFocused) return styles.bottomBorderBlue; 61 | if (showError) return styles.bottomBorderRed; 62 | return {}; 63 | }; 64 | 65 | return ( 66 | 67 | {label && ( 68 | 76 | )} 77 | 83 | 99 | {secureTextEntry && showEye && ( 100 | 101 | )} 102 | 103 | 104 | {!isFocused && error && ( 105 | 106 | {error} 107 | 108 | )} 109 | 110 | 111 | ); 112 | }); 113 | 114 | CustomTextInput.defaultProps = { 115 | allowFontScaling: false, 116 | animated: false, 117 | autoCapitalize: 'none', 118 | autoCompleteType: 'off', 119 | autoCorrect: false, 120 | clearButtonMode: 'never', 121 | disabled: false, 122 | keyboardType: 'default', 123 | multiline: false, 124 | placeholder: '', 125 | returnKeyType: 'done', 126 | underlineColorAndroid: transparent 127 | }; 128 | 129 | const MyCustomTextInput = memo(CustomTextInput); 130 | 131 | export default MyCustomTextInput; 132 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/interface.ts: -------------------------------------------------------------------------------- 1 | import { TextProps, TextInputProps, TextStyle, ViewProps } from 'react-native'; 2 | 3 | export interface CustomTextInputProps extends TextInputProps, TextProps { 4 | animated?: boolean; 5 | autoCompleteType?: TextInputProps['autoCompleteType']; 6 | disabled?: boolean; 7 | error?: boolean | string; 8 | errorContainerStyle?: ViewProps['style']; 9 | errorStyle?: TextStyle; 10 | inputContainerStyle?: ViewProps['style']; 11 | inputTextStyles?: TextStyle; 12 | isOptional?: boolean; 13 | label?: string; 14 | labelStyle?: TextStyle; 15 | multiline?: boolean; 16 | onBlur?: TextInputProps['onBlur']; 17 | onChange?(e: any): any; 18 | onFocus?: TextInputProps['onFocus']; 19 | placeholder?: string; 20 | placeholderColor?: string; 21 | secureTextEntry?: boolean; 22 | showError?: boolean; 23 | showEye?: boolean; 24 | style?: ViewProps['style']; 25 | value?: string; 26 | } 27 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/CustomTextInput/styles.ts: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | import fonts from '@config/fonts'; 3 | import { blue, gray, darkGray, red } from '@constants/colors'; 4 | import { SIZES } from '@constants/fonts'; 5 | 6 | export default StyleSheet.create({ 7 | container: { 8 | marginBottom: 5 9 | }, 10 | withAnimatedLabel: { 11 | marginTop: 20 12 | }, 13 | multilineContainer: { 14 | borderColor: darkGray, 15 | borderWidth: 1, 16 | height: 75, 17 | paddingHorizontal: 5 18 | }, 19 | inputContainer: { 20 | alignItems: 'center', 21 | borderBottomWidth: 1, 22 | borderColor: darkGray, 23 | flexDirection: 'row', 24 | height: 25, 25 | justifyContent: 'space-between' 26 | }, 27 | bottomBorderBlue: { 28 | borderColor: blue 29 | }, 30 | bottomBorderRed: { 31 | borderColor: red 32 | }, 33 | bottomBorderLightGray: { 34 | borderColor: gray 35 | }, 36 | inputStyle: { 37 | ...fonts.baseFont, 38 | fontSize: SIZES.SMALL, 39 | color: darkGray, 40 | padding: 0, 41 | margin: 0 42 | }, 43 | singleInput: { 44 | flex: 1 45 | }, 46 | errorContainer: { 47 | height: 15, 48 | marginTop: 3 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/ErrorBoundary/ErrorFallBack.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, SafeAreaView } from 'react-native'; 3 | import { FallbackProps } from 'react-error-boundary'; 4 | import i18next from 'i18next'; 5 | import CustomText from '@components/CustomText'; 6 | import CustomButton from '@components/CustomButton'; 7 | 8 | import styles from './styles'; 9 | import './i18n'; 10 | 11 | /* 12 | In this functional component we have two props by FallbackProps 13 | error: String[], message with errorDetail 14 | resetErrorBoundary: Function => this can reset the error 15 | */ 16 | export default function ErrorFallBack({ error, resetErrorBoundary }: FallbackProps) { 17 | return ( 18 | 19 | 20 | 21 | {i18next.t('ERRORBOUNDARY:TITLE')} 22 | 23 | 24 | 25 | {i18next.t('ERRORBOUNDARY:TRY_AGAIN')} 26 | 32 | 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/ErrorBoundary/ExceptionHandler.tsx: -------------------------------------------------------------------------------- 1 | import { Alert } from 'react-native'; 2 | import { 3 | setJSExceptionHandler, 4 | getJSExceptionHandler 5 | } from 'react-native-exception-handler'; 6 | import i18next from 'i18next'; 7 | 8 | import './i18n'; 9 | /* 10 | ErrorHandler was called when JSexception ocurred, in this case is an Alert, you can change this if you need. 11 | */ 12 | const ErrorHandler = (error: Error, isFatal: boolean) => { 13 | const fatal: string = isFatal ? 'Fatal' : ''; 14 | Alert.alert( 15 | `${i18next.t('ERRORBOUNDARY:ERROR_ALERT')}`, 16 | `${i18next.t('ERRORBOUNDARY:TYPE_ERROR', { 17 | fatal 18 | })}, ${error}${i18next.t('ERRORBOUNDARY:ERROR_MESSAGE')} 19 | `, 20 | [ 21 | { 22 | text: 'Ok' 23 | } 24 | ] 25 | ); 26 | }; 27 | 28 | /* 29 | we need to use this in index app, to catch errors correctly, 30 | setJSExceptionHandler have two calls ErrorHandler was called in error, 31 | in second param 'allowInDevMode' you can configure if you need or not see this handler. if false you see the classic redScreen error else return errorHandler with Alert 32 | */ 33 | export const ExceptionHandler = () => { 34 | getJSExceptionHandler(); 35 | setJSExceptionHandler(ErrorHandler, true); 36 | }; 37 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/ErrorBoundary/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18next from 'i18next'; 2 | 3 | i18next.addResources('es', 'ERRORBOUNDARY', { 4 | TITLE: '¡Oh no! Ha ocurrido un error', 5 | TRY_AGAIN: 'Intenta nuevamente', 6 | BACK_TO_HOME: 'Volver al inicio', 7 | ERROR_ALERT: 'Ocurrió un error inesperado', 8 | TYPE_ERROR: `Error: {{fatal}}`, 9 | ERROR_MESSAGE: 10 | '\n\n¡Hemos informado de esto a nuestro equipo! Cierre la aplicación e inicie nuevamente' 11 | }); 12 | -------------------------------------------------------------------------------- /generators/app/templates/src/app/components/ErrorBoundary/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, ReactElement } from 'react'; 2 | import { ErrorBoundary, FallbackProps } from 'react-error-boundary'; 3 | 4 | import ErrorFallBack from './ErrorFallBack'; 5 | 6 | /* 7 | in this function we can use or save the error on cloud with others libraries like Sentry 8 | import * as Sentry from '@sentry/react-native'; 9 | */ 10 | const myErrorHandler = (error: Error) => { 11 | // Example: Sentry.captureException(error); 12 | }; 13 | 14 | // this function receive a children: ReactElement and return the children component or fallbackComponent, in this case and if was an error onError was called 15 | export const ErrorHandler = ({ 16 | children, 17 | component 18 | }: { 19 | children: ReactElement; 20 | component?: FunctionComponent; 21 | }) => { 22 | return ( 23 | 24 | {children} 25 | 26 | ); 27 | }; 28 | 29 | /* 30 | how to test error: 31 | tsx: 32 | const [statusOk, setStatusOk] = useState(true); 33 | useEffect(() => { 34 | if (!statusOk) throw new Error('new error coming'); 35 | }, [statusOk]); 36 | const handleError = useCallback(() => { 37 | setStatusOk(false); 38 | }, [Error]); 39 | return