├── .bundle └── config ├── .env.development ├── .env.production ├── .env.staging ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .husky └── pre-commit ├── .nvmrc ├── .prettierrc.js ├── .vscode └── settings.json ├── .watchmanconfig ├── .yarnrc ├── Gemfile ├── LICENSE ├── README.md ├── __mocks__ ├── react-native-config.js └── svgMock.js ├── __tests__ └── app-test.tsx ├── android ├── app │ ├── build.gradle │ ├── build_defs.bzl │ ├── debug.keystore │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── starter │ │ │ └── ReactNativeFlipper.java │ │ ├── development │ │ └── res │ │ │ └── values │ │ │ └── strings.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── starter │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ └── res │ │ │ ├── drawable │ │ │ └── rn_edit_text_material.xml │ │ │ ├── font │ │ │ ├── poppins.xml │ │ │ ├── poppins_black.ttf │ │ │ ├── poppins_blackitalic.ttf │ │ │ ├── poppins_bold.ttf │ │ │ ├── poppins_bolditalic.ttf │ │ │ ├── poppins_extrabold.ttf │ │ │ ├── poppins_extrabolditalic.ttf │ │ │ ├── poppins_extralight.ttf │ │ │ ├── poppins_extralightitalic.ttf │ │ │ ├── poppins_italic.ttf │ │ │ ├── poppins_light.ttf │ │ │ ├── poppins_lightitalic.ttf │ │ │ ├── poppins_medium.ttf │ │ │ ├── poppins_mediumitalic.ttf │ │ │ ├── poppins_regular.ttf │ │ │ ├── poppins_semibold.ttf │ │ │ ├── poppins_semibolditalic.ttf │ │ │ ├── poppins_thin.ttf │ │ │ └── poppins_thinitalic.ttf │ │ │ ├── mipmap-hdpi │ │ │ ├── bootsplash_logo.png │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── bootsplash_logo.png │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── bootsplash_logo.png │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── bootsplash_logo.png │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── bootsplash_logo.png │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── release │ │ └── java │ │ │ └── starter │ │ │ └── ReactNativeFlipper.java │ │ └── staging │ │ └── res │ │ └── values │ │ └── strings.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── link-assets-manifest.json └── settings.gradle ├── app.json ├── assets ├── bootsplash_logo.png ├── bootsplash_logo@1,5x.png ├── bootsplash_logo@2x.png ├── bootsplash_logo@3x.png ├── bootsplash_logo@4x.png └── bootsplash_logo_original.png ├── babel.config.js ├── bin └── podInstall.js ├── dist └── demo.gif ├── docs ├── 01-how-to-change-app-logo.md ├── 02-how-to-change-splash-screen.md └── 03-how-to-keep-code-changed-from-node-modules.md ├── index.js ├── ios ├── .xcode.env ├── Config.xcconfig ├── DevelopmentExportOptions.plist ├── Podfile ├── Podfile.lock ├── ProductionExportOptions.plist ├── StagingExportOptions.plist ├── development │ └── Info.plist ├── link-assets-manifest.json ├── staging │ └── Info.plist ├── starter.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── development.xcscheme │ │ ├── production.xcscheme │ │ └── staging.xcscheme ├── starter.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── starter │ ├── AppDelegate.h │ ├── AppDelegate.mm │ ├── BootSplash.storyboard │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── BootSplashLogo.imageset │ │ │ ├── Contents.json │ │ │ ├── bootsplash_logo.png │ │ │ ├── bootsplash_logo@2x.png │ │ │ └── bootsplash_logo@3x.png │ │ └── Contents.json │ ├── Info.plist │ ├── LaunchScreen.storyboard │ └── main.m └── starterTests │ ├── Info.plist │ └── starterTests.m ├── jest.config.js ├── jest └── setup.js ├── metro.config.js ├── package.json ├── patches └── .gitignore ├── react-native.config.js ├── scripts ├── deploy.sh └── fixfonts.sh ├── src ├── assets │ ├── fonts │ │ ├── Poppins-Black.ttf │ │ ├── Poppins-BlackItalic.ttf │ │ ├── Poppins-Bold.ttf │ │ ├── Poppins-BoldItalic.ttf │ │ ├── Poppins-ExtraBold.ttf │ │ ├── Poppins-ExtraBoldItalic.ttf │ │ ├── Poppins-ExtraLight.ttf │ │ ├── Poppins-ExtraLightItalic.ttf │ │ ├── Poppins-Italic.ttf │ │ ├── Poppins-Light.ttf │ │ ├── Poppins-LightItalic.ttf │ │ ├── Poppins-Medium.ttf │ │ ├── Poppins-MediumItalic.ttf │ │ ├── Poppins-Regular.ttf │ │ ├── Poppins-SemiBold.ttf │ │ ├── Poppins-SemiBoldItalic.ttf │ │ ├── Poppins-Thin.ttf │ │ └── Poppins-ThinItalic.ttf │ ├── images │ │ └── index.ts │ ├── index.ts │ └── jsons │ │ ├── index.ts │ │ └── spinner.json ├── common │ ├── config │ │ └── index.ts │ ├── constants │ │ ├── dimension.ts │ │ ├── index.ts │ │ ├── platform.ts │ │ ├── styles.ts │ │ └── transitions.ts │ ├── hooks │ │ ├── __tests__ │ │ │ └── useDisclosure.test.ts │ │ ├── index.ts │ │ ├── useBarStyle.ts │ │ ├── useDisclosure.ts │ │ ├── usePrevious.ts │ │ ├── useTranslation.ts │ │ └── useWhyYouUpdate.ts │ ├── stores │ │ ├── __tests__ │ │ │ └── notifications.test.ts │ │ ├── index.ts │ │ ├── useNotificationStore.ts │ │ └── useThemeStore.ts │ ├── themes │ │ ├── color.ts │ │ ├── index.ts │ │ ├── palette.ts │ │ ├── spacing.ts │ │ ├── theme.ts │ │ ├── timing.ts │ │ └── typography.ts │ ├── types │ │ ├── api.ts │ │ ├── function.ts │ │ ├── index.ts │ │ └── ui.ts │ └── utils │ │ ├── axios.ts │ │ ├── dialog.tsx │ │ ├── event-register.ts │ │ ├── global-props.ts │ │ ├── helpers.ts │ │ ├── logger.ts │ │ ├── magic-memo.ts │ │ ├── mmvk.ts │ │ ├── navigation-utilities.ts │ │ ├── never-rerender.ts │ │ ├── refresh-token-multi-request.ts │ │ ├── responsive.ts │ │ └── storage.ts ├── components │ ├── forms │ │ └── index.ts │ ├── modals │ │ ├── confirm │ │ │ ├── confirm.tsx │ │ │ └── index.ts │ │ └── index.ts │ ├── tabs │ │ ├── tab-animation-fixed.tsx │ │ └── tab-animation-scrollable.tsx │ └── widgets │ │ ├── align │ │ ├── align.tsx │ │ └── index.ts │ │ ├── app-bar │ │ ├── app-bar.tsx │ │ └── index.ts │ │ ├── box │ │ ├── box.tsx │ │ └── index.ts │ │ ├── button │ │ ├── button.tsx │ │ └── index.ts │ │ ├── card │ │ ├── card.tsx │ │ └── index.ts │ │ ├── center │ │ ├── center.tsx │ │ └── index.ts │ │ ├── col │ │ ├── col.tsx │ │ └── index.ts │ │ ├── fade-view │ │ ├── fade-view.tsx │ │ └── index.ts │ │ ├── if │ │ ├── if.tsx │ │ └── index.ts │ │ ├── image │ │ ├── image.tsx │ │ └── index.ts │ │ ├── index.ts │ │ ├── input-field │ │ ├── index.ts │ │ └── input-field.tsx │ │ ├── moti-color │ │ ├── index.ts │ │ └── moti-color.tsx │ │ ├── positioned │ │ ├── index.ts │ │ └── positioned.tsx │ │ ├── row │ │ ├── index.ts │ │ └── row.tsx │ │ ├── screen │ │ ├── index.ts │ │ └── screen.tsx │ │ ├── space │ │ ├── index.ts │ │ └── space.tsx │ │ ├── spinner │ │ ├── index.ts │ │ └── spinner.tsx │ │ ├── stack │ │ ├── index.ts │ │ └── stack.tsx │ │ ├── switch │ │ ├── index.ts │ │ └── switch.tsx │ │ ├── text-button │ │ ├── index.ts │ │ └── text-button.tsx │ │ ├── text-input │ │ ├── index.ts │ │ └── text-input.tsx │ │ ├── text │ │ ├── index.ts │ │ └── text.tsx │ │ ├── touchable │ │ ├── index.ts │ │ └── touchable.tsx │ │ └── wrap │ │ ├── index.ts │ │ └── wrap.tsx ├── localization │ ├── en │ │ ├── auth.ts │ │ ├── common.ts │ │ ├── index.ts │ │ └── navigate.ts │ ├── i18n.ts │ ├── language.ts │ └── vi │ │ ├── auth.ts │ │ ├── common.ts │ │ ├── index.ts │ │ └── navigate.ts ├── modules │ ├── auth │ │ ├── api │ │ │ ├── auth.ts │ │ │ └── index.ts │ │ ├── assets │ │ │ └── index.ts │ │ ├── components │ │ │ ├── index.ts │ │ │ ├── layout │ │ │ │ ├── index.ts │ │ │ │ └── layout.tsx │ │ │ ├── login-form │ │ │ │ ├── index.ts │ │ │ │ ├── login-form.tsx │ │ │ │ └── useLoginForm.ts │ │ │ └── register-form │ │ │ │ ├── index.ts │ │ │ │ ├── register-form.tsx │ │ │ │ └── useRegisterForm.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ └── useAuth.ts │ │ ├── index.ts │ │ ├── routes │ │ │ └── index.tsx │ │ ├── screens │ │ │ ├── index.ts │ │ │ ├── landing.tsx │ │ │ ├── login.tsx │ │ │ └── register.tsx │ │ ├── stores │ │ │ ├── index.ts │ │ │ └── useAuthStore.ts │ │ ├── types │ │ │ ├── api.ts │ │ │ ├── index.ts │ │ │ ├── models │ │ │ │ ├── index.ts │ │ │ │ └── user.ts │ │ │ └── payload.ts │ │ └── utils │ │ │ └── index.tsx │ ├── error │ │ ├── components │ │ │ ├── error-boundary │ │ │ │ ├── error-boundary.tsx │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ └── screens │ │ │ ├── crash.tsx │ │ │ └── index.ts │ └── home │ │ ├── components │ │ ├── index.ts │ │ └── layout │ │ │ ├── index.ts │ │ │ └── layout.tsx │ │ ├── routes │ │ └── index.tsx │ │ └── screens │ │ ├── home.tsx │ │ └── index.ts ├── providers │ └── index.tsx ├── routes │ ├── index.tsx │ └── router-name.ts └── starter.tsx ├── templates ├── form │ ├── NAME.tsx.ejs │ └── index.ts.ejs ├── modal │ ├── NAME.tsx.ejs │ └── index.ts.ejs ├── module │ ├── api │ │ ├── demo.ts │ │ └── index.ts │ ├── assets │ │ └── index.ts │ ├── components │ │ └── index.ts │ ├── hooks │ │ └── index.ts │ ├── index.ts │ ├── routes │ │ └── index.tsx │ ├── screens │ │ └── index.ts │ ├── stores │ │ ├── index.ts │ │ └── useDemoStore.ts │ ├── types │ │ ├── api.ts │ │ ├── index.ts │ │ ├── models │ │ │ ├── demo.ts │ │ │ └── index.ts │ │ └── payload.ts │ └── utils │ │ └── index.tsx ├── screen │ └── NAME.tsx.ejs ├── store │ └── useNAME.tsx.ejs └── widget │ ├── NAME.tsx.ejs │ └── index.ts.ejs ├── tsconfig.json ├── tsconfig.paths.json ├── types └── declarations.d.ts └── yarn.lock /.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | API_URL=https://myapi.com-dev 2 | GOOGLE_MAPS_API_KEY=abcdefgh 3 | APP_NAME=Starter Development -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | API_URL=https://myapi.com-prod 2 | GOOGLE_MAPS_API_KEY=abcdefgh 3 | APP_NAME=Starter 4 | -------------------------------------------------------------------------------- /.env.staging: -------------------------------------------------------------------------------- 1 | API_URL=https://myapi.com-staging 2 | GOOGLE_MAPS_API_KEY=abcdefgh 3 | APP_NAME=Starter Staging 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.js 2 | **/*.js 3 | bin 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'airbnb', 4 | 'airbnb/hooks', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'prettier', 7 | 'plugin:prettier/recommended', 8 | ], 9 | plugins: ['@typescript-eslint', 'react', 'prettier', 'simple-import-sort'], 10 | parser: '@typescript-eslint/parser', 11 | parserOptions: { 12 | ecmaFeatures: { 13 | jsx: true, 14 | }, 15 | ecmaVersion: 'latest', 16 | sourceType: 'module', 17 | project: './tsconfig.json', 18 | }, 19 | rules: { 20 | 'import/no-unresolved': 0, 21 | 'react/jsx-filename-extension': [ 22 | 1, 23 | { 24 | extensions: ['.ts', '.tsx'], 25 | }, 26 | ], 27 | 'no-use-before-define': 0, 28 | '@typescript-eslint/no-use-before-define': ['error'], 29 | 'import/extensions': [ 30 | 'error', 31 | 'never', 32 | { 33 | json: 'always', 34 | }, 35 | ], 36 | 'react/prop-types': 0, 37 | 'no-shadow': 0, 38 | '@typescript-eslint/no-shadow': 2, 39 | 'import/prefer-default-export': 0, 40 | 'react/require-default-props': 0, 41 | 'react/jsx-props-no-spreading': 0, 42 | 'func-names': 0, 43 | '@typescript-eslint/no-explicit-any': 0, 44 | 'no-restricted-imports': 0, 45 | 'prettier/prettier': ['error', { bracketSpacing: true }], 46 | 'react/jsx-no-bind': 2, 47 | 'no-empty-function': 0, 48 | 'no-extra-semi': 0, 49 | 'no-console': 0, 50 | 'simple-import-sort/imports': [ 51 | 'error', 52 | { 53 | groups: [['^react-?'], ['^@?react-?'], ['^@?\\w']], 54 | }, 55 | ], 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | ios/.xcode.env.local 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | .cxx/ 34 | *.keystore 35 | !debug.keystore 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | 43 | # fastlane 44 | # 45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 46 | # screenshots whenever they are needed. 47 | # For more information about the recommended setup visit: 48 | # https://docs.fastlane.tools/best-practices/source-control/ 49 | 50 | **/fastlane/report.xml 51 | **/fastlane/Preview.html 52 | **/fastlane/screenshots 53 | **/fastlane/test_output 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # Ruby / CocoaPods 59 | /ios/Pods/ 60 | /vendor/bundle/ 61 | 62 | # Temporary files created by Metro to check the health of the file watcher 63 | .metro-health-check* 64 | # testing 65 | /coverage 66 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | yarn validate 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.17.0 -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: true, 4 | bracketSpacing: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | printWidth: 100, 8 | tabWidth: 2, 9 | useTabs: false, 10 | semi: false, 11 | }; 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": "explicit" 5 | }, 6 | "eslint.validate": ["javascript", "typescript"] 7 | } 8 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | 1.22.19 -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby '2.7.5' 5 | 6 | gem 'cocoapods', '~> 1.11', '>= 1.11.2' 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 bonnguyenitc 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. -------------------------------------------------------------------------------- /__mocks__/react-native-config.js: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /__mocks__/svgMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'SvgMock' 2 | module.exports.ReactComponent = 'SvgMock' 3 | -------------------------------------------------------------------------------- /__tests__/app-test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import React from 'react' 6 | 7 | import { render } from '@testing-library/react-native' 8 | 9 | import 'react-native' 10 | import { Starter } from '../src/starter' 11 | 12 | // Note: test renderer must be required after react-native. 13 | 14 | it('renders correctly', () => { 15 | render() 16 | }) 17 | -------------------------------------------------------------------------------- /android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/debug.keystore -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -keep class com.starter.BuildConfig { *; } 12 | 13 | -keep class com.swmansion.reanimated.** { *; } 14 | -keep class com.facebook.react.turbomodule.** { *; } 15 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /android/app/src/debug/java/com/starter/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.starter; 8 | 9 | import android.content.Context; 10 | import com.facebook.flipper.android.AndroidFlipperClient; 11 | import com.facebook.flipper.android.utils.FlipperUtils; 12 | import com.facebook.flipper.core.FlipperClient; 13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; 14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; 15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; 16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping; 17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; 18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; 19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; 20 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; 21 | import com.facebook.react.ReactInstanceEventListener; 22 | import com.facebook.react.ReactInstanceManager; 23 | import com.facebook.react.bridge.ReactContext; 24 | import com.facebook.react.modules.network.NetworkingModule; 25 | import okhttp3.OkHttpClient; 26 | 27 | /** 28 | * Class responsible of loading Flipper inside your React Native application. This is the debug 29 | * flavor of it. Here you can add your own plugins and customize the Flipper setup. 30 | */ 31 | public class ReactNativeFlipper { 32 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 33 | if (FlipperUtils.shouldEnableFlipper(context)) { 34 | final FlipperClient client = AndroidFlipperClient.getInstance(context); 35 | 36 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); 37 | client.addPlugin(new DatabasesFlipperPlugin(context)); 38 | client.addPlugin(new SharedPreferencesFlipperPlugin(context)); 39 | client.addPlugin(CrashReporterPlugin.getInstance()); 40 | 41 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); 42 | NetworkingModule.setCustomClientBuilder( 43 | new NetworkingModule.CustomClientBuilder() { 44 | @Override 45 | public void apply(OkHttpClient.Builder builder) { 46 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); 47 | } 48 | }); 49 | client.addPlugin(networkFlipperPlugin); 50 | client.start(); 51 | 52 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized 53 | // Hence we run if after all native modules have been initialized 54 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); 55 | if (reactContext == null) { 56 | reactInstanceManager.addReactInstanceEventListener( 57 | new ReactInstanceEventListener() { 58 | @Override 59 | public void onReactContextInitialized(ReactContext reactContext) { 60 | reactInstanceManager.removeReactInstanceEventListener(this); 61 | reactContext.runOnNativeModulesQueueThread( 62 | new Runnable() { 63 | @Override 64 | public void run() { 65 | client.addPlugin(new FrescoFlipperPlugin()); 66 | } 67 | }); 68 | } 69 | }); 70 | } else { 71 | client.addPlugin(new FrescoFlipperPlugin()); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /android/app/src/development/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Starter Dev 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/starter/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.starter; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import com.facebook.react.ReactActivityDelegate; 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate; 7 | import android.os.Bundle; 8 | import com.zoontek.rnbootsplash.RNBootSplash; 9 | 10 | public class MainActivity extends ReactActivity { 11 | 12 | /** 13 | * Returns the name of the main component registered from JavaScript. This is used to schedule 14 | * rendering of the component. 15 | */ 16 | @Override 17 | protected String getMainComponentName() { 18 | return "starter"; 19 | } 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | RNBootSplash.init(this, R.style.BootTheme); // ⬅️ initialize the splash screen 24 | super.onCreate(savedInstanceState); // or super.onCreate(null) with react-native-screens 25 | } 26 | 27 | /** 28 | * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link 29 | * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React 30 | * (aka React 18) with two boolean flags. 31 | */ 32 | @Override 33 | protected ReactActivityDelegate createReactActivityDelegate() { 34 | return new DefaultReactActivityDelegate( 35 | this, 36 | getMainComponentName(), 37 | // If you opted-in for the New Architecture, we enable the Fabric Renderer. 38 | DefaultNewArchitectureEntryPoint.getFabricEnabled()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/starter/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.starter; 2 | 3 | import android.app.Application; 4 | import com.facebook.react.PackageList; 5 | import com.facebook.react.ReactApplication; 6 | import com.facebook.react.ReactNativeHost; 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 9 | import com.facebook.react.defaults.DefaultReactNativeHost; 10 | import com.facebook.soloader.SoLoader; 11 | import java.util.List; 12 | 13 | import com.facebook.react.views.text.ReactFontManager; 14 | 15 | public class MainApplication extends Application implements ReactApplication { 16 | 17 | private final ReactNativeHost mReactNativeHost = 18 | new DefaultReactNativeHost(this) { 19 | @Override 20 | public boolean getUseDeveloperSupport() { 21 | return BuildConfig.DEBUG; 22 | } 23 | 24 | @Override 25 | protected List getPackages() { 26 | @SuppressWarnings("UnnecessaryLocalVariable") 27 | List packages = new PackageList(this).getPackages(); 28 | // Packages that cannot be autolinked yet can be added manually here, for example: 29 | // packages.add(new MyReactNativePackage()); 30 | return packages; 31 | } 32 | 33 | @Override 34 | protected String getJSMainModuleName() { 35 | return "index"; 36 | } 37 | 38 | @Override 39 | protected boolean isNewArchEnabled() { 40 | return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; 41 | } 42 | 43 | @Override 44 | protected Boolean isHermesEnabled() { 45 | return BuildConfig.IS_HERMES_ENABLED; 46 | } 47 | }; 48 | 49 | @Override 50 | public ReactNativeHost getReactNativeHost() { 51 | return mReactNativeHost; 52 | } 53 | 54 | @Override 55 | public void onCreate() { 56 | super.onCreate(); 57 | ReactFontManager.getInstance().addCustomFont(this, "Poppins", R.font.poppins); 58 | SoLoader.init(this, /* native exopackage */ false); 59 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 60 | // If you opted-in for the New Architecture, we load the native entry point for this app. 61 | DefaultNewArchitectureEntryPoint.load(); 62 | } 63 | ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_black.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_blackitalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_blackitalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_bold.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_bolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_bolditalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_extrabold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_extrabold.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_extrabolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_extrabolditalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_extralight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_extralight.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_extralightitalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_extralightitalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_italic.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_light.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_lightitalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_lightitalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_medium.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_mediumitalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_mediumitalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_regular.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_semibold.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_semibolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_semibolditalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_thin.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/font/poppins_thinitalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/font/poppins_thinitalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/bootsplash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-hdpi/bootsplash_logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/bootsplash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-mdpi/bootsplash_logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/bootsplash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-xhdpi/bootsplash_logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/bootsplash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-xxhdpi/bootsplash_logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/bootsplash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-xxxhdpi/bootsplash_logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | #F5FCFF 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Starter 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /android/app/src/release/java/starter/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.starter; 8 | 9 | import android.content.Context; 10 | import com.facebook.react.ReactInstanceManager; 11 | 12 | /** 13 | * Class responsible of loading Flipper inside your React Native application. This is the release 14 | * flavor of it so it's empty as we don't want to load Flipper. 15 | */ 16 | public class ReactNativeFlipper { 17 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 18 | // Do nothing as we don't want to initialize Flipper on Release. 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /android/app/src/staging/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Starter Staging 3 | 4 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | import org.apache.tools.ant.taskdefs.condition.Os 2 | 3 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 4 | 5 | buildscript { 6 | ext { 7 | buildToolsVersion = "33.0.0" 8 | minSdkVersion = 23 9 | compileSdkVersion = 33 10 | targetSdkVersion = 33 11 | // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. 12 | ndkVersion = "23.1.7779620" 13 | } 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | dependencies { 19 | classpath("com.android.tools.build:gradle") 20 | classpath("com.facebook.react:react-native-gradle-plugin") 21 | } 22 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.182.0 29 | 30 | # Use this property to specify which architecture you want to build. 31 | # You can also override it from the CLI using 32 | # ./gradlew -PreactNativeArchitectures=x86_64 33 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 34 | 35 | # Use this property to enable support to the new architecture. 36 | # This will allow you to use TurboModules and the Fabric render in 37 | # your application. You should enable this flag either if you want 38 | # to write custom TurboModules/Fabric components OR use libraries that 39 | # are providing them. 40 | newArchEnabled=false 41 | 42 | # Use this property to enable or disable the Hermes JS engine. 43 | # If set to false, you will be using JSC instead. 44 | hermesEnabled=true 45 | 46 | # development keystore 47 | DEBUG_STORE_FILE=debug.keystore 48 | DEBUG_KEY_ALIAS=androiddebugkey 49 | DEBUG_STORE_PASSWORD=android 50 | DEBUG_KEY_PASSWORD=android 51 | # staging keystore 52 | STAGING_STORE_FILE=debug.keystore 53 | STAGING_KEY_ALIAS=androiddebugkey 54 | STAGING_STORE_PASSWORD=android 55 | STAGING_KEY_PASSWORD=android 56 | # product keystore 57 | PRODUCT_STORE_FILE=debug.keystore 58 | PRODUCT_KEY_ALIAS=androiddebugkey 59 | PRODUCT_STORE_PASSWORD=android 60 | PRODUCT_KEY_PASSWORD=android 61 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /android/link-assets-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "migIndex": 1, 3 | "data": [ 4 | { 5 | "path": "src/assets/fonts/Poppins-Black.ttf", 6 | "sha1": "645e04c53c6b5b35bce654a811ebce16af8aa721" 7 | }, 8 | { 9 | "path": "src/assets/fonts/Poppins-BlackItalic.ttf", 10 | "sha1": "be4ef2aac7775a4b0179c0ecfce6cea8f7783f63" 11 | }, 12 | { 13 | "path": "src/assets/fonts/Poppins-Bold.ttf", 14 | "sha1": "875cf0cecd647bcf22e79d633d868c1b1ec98dfa" 15 | }, 16 | { 17 | "path": "src/assets/fonts/Poppins-BoldItalic.ttf", 18 | "sha1": "6de8952ca08d27ee77f57347d2cef4b4e26aa78f" 19 | }, 20 | { 21 | "path": "src/assets/fonts/Poppins-ExtraBold.ttf", 22 | "sha1": "4b5c0750f073abd576413a0898d3b95adaf199c8" 23 | }, 24 | { 25 | "path": "src/assets/fonts/Poppins-ExtraBoldItalic.ttf", 26 | "sha1": "94d3ee3acdbd00e37a715a25f2b5237666b5b8a3" 27 | }, 28 | { 29 | "path": "src/assets/fonts/Poppins-ExtraLight.ttf", 30 | "sha1": "85af6582a7e6155917c605f9d3fed68c02b23b06" 31 | }, 32 | { 33 | "path": "src/assets/fonts/Poppins-ExtraLightItalic.ttf", 34 | "sha1": "6fa0418f91032680d2d8fe4843009f2c80faa61d" 35 | }, 36 | { 37 | "path": "src/assets/fonts/Poppins-Italic.ttf", 38 | "sha1": "e2bfcd56016c3c915221ef1c5fc1a17b0776eb91" 39 | }, 40 | { 41 | "path": "src/assets/fonts/Poppins-Light.ttf", 42 | "sha1": "e247a92158e112f8bf7b638c8d95381d66b00dbb" 43 | }, 44 | { 45 | "path": "src/assets/fonts/Poppins-LightItalic.ttf", 46 | "sha1": "f76102b361e705751a83b7b22fe954daf1f27dd4" 47 | }, 48 | { 49 | "path": "src/assets/fonts/Poppins-Medium.ttf", 50 | "sha1": "283f21b44efbdbf276ba802be2d949a36bbc4233" 51 | }, 52 | { 53 | "path": "src/assets/fonts/Poppins-MediumItalic.ttf", 54 | "sha1": "b592c62fdf88c40cfd0f031bba6423b1d4430472" 55 | }, 56 | { 57 | "path": "src/assets/fonts/Poppins-Regular.ttf", 58 | "sha1": "fdd3002e7d814ee47c1c1b8487c72c6bbb3a2d00" 59 | }, 60 | { 61 | "path": "src/assets/fonts/Poppins-SemiBold.ttf", 62 | "sha1": "8a4ace9392d06bcb7f8ea2f5169b07e4c383a90d" 63 | }, 64 | { 65 | "path": "src/assets/fonts/Poppins-SemiBoldItalic.ttf", 66 | "sha1": "0c8bed0a5942b2388d36b11c115685d9f01700b0" 67 | }, 68 | { 69 | "path": "src/assets/fonts/Poppins-Thin.ttf", 70 | "sha1": "09ba4dcd5509c8085bf88c665dcc51cbdfced27b" 71 | }, 72 | { 73 | "path": "src/assets/fonts/Poppins-ThinItalic.ttf", 74 | "sha1": "fb8fccca21550e71135f92bbaf8a2e8a12c90465" 75 | } 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'starter' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | includeBuild('../node_modules/@react-native/gradle-plugin') -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starter", 3 | "displayName": "starter" 4 | } 5 | -------------------------------------------------------------------------------- /assets/bootsplash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/assets/bootsplash_logo.png -------------------------------------------------------------------------------- /assets/bootsplash_logo@1,5x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/assets/bootsplash_logo@1,5x.png -------------------------------------------------------------------------------- /assets/bootsplash_logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/assets/bootsplash_logo@2x.png -------------------------------------------------------------------------------- /assets/bootsplash_logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/assets/bootsplash_logo@3x.png -------------------------------------------------------------------------------- /assets/bootsplash_logo@4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/assets/bootsplash_logo@4x.png -------------------------------------------------------------------------------- /assets/bootsplash_logo_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/assets/bootsplash_logo_original.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | plugins: [ 4 | [ 5 | 'module-resolver', 6 | { 7 | root: ['./src'], 8 | alias: { 9 | /** 10 | * Regular expression is used to match all files inside `./src` directory and map each `.src/folder/[..]` to `~folder/[..]` path 11 | */ 12 | '@': './src', 13 | }, 14 | extensions: ['.tsx', '.ts'], 15 | }, 16 | ], 17 | 'react-native-reanimated/plugin', 18 | ], 19 | env: { 20 | production: { 21 | plugins: ['transform-remove-console'], 22 | }, 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /bin/podInstall.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Husky install 4 | run('npx husky install'); 5 | 6 | // Patch any packages that need patching 7 | run('npx patch-package'); 8 | 9 | // Kill the metro bundler if it's running. 10 | if (['darwin', 'linux'].includes(process.platform)) { 11 | run('pkill -f "cli.js start" || set exit 0'); 12 | } 13 | 14 | // Make sure our Android native modules are androidX-happy 15 | run('npx jetify'); 16 | 17 | // On iOS, make sure CocoaPods are installed 18 | if (process.platform === 'darwin') { 19 | run('if [ -d "ios" ]; then cd ios && pod install && cd -; fi'); 20 | } 21 | 22 | // Run baby run 23 | function run(command) { 24 | console.log(`./bin/postInstall script running: ${command}`); 25 | 26 | try { 27 | require('child_process').execSync(command, { stdio: 'inherit' }); 28 | } catch (error) { 29 | console.error(`./bin/postInstall failed on command:\n ${command}`); 30 | process.exit(error.status); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /dist/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/dist/demo.gif -------------------------------------------------------------------------------- /docs/01-how-to-change-app-logo.md: -------------------------------------------------------------------------------- 1 | ## How to change app logo 2 | 3 | 1. Go to https://www.appicon.co/from https://www.appicon.co/ to generate logo for iOS, Android 4 | 2. Go to https://icon.kitchen/ to generate logo rounded for Android 5 | 3. After generate 6 | 7 | #### iOS 8 | 9 | 1. Unzip file from https://www.appicon.co/ 10 | 2. Copy Dowloads/Assets.xcassets/AppIcon.appiconset to react-native-starter/ios/starter/Images.xcassets 11 | 3. Done 12 | 13 | #### Android 14 | 15 | 1. Unzip downloaded file from https://www.appicon.co/ 16 | 2. Copy all Downloads/android/\* to react-native-starter/android/app/src/main/res 17 | 3. Unzip downloaded file from https://icon.kitchen/ 18 | 4. Copy merge folder Downloads/ic_launcher/res/\* to react-native-starter/android/app/src/main/res 19 | 5. Done 20 | -------------------------------------------------------------------------------- /docs/02-how-to-change-splash-screen.md: -------------------------------------------------------------------------------- 1 | ## How to change plash screen logo 2 | 3 | 1. Copy image to assets folder 4 | 2. Fix params and run CMD 5 | 6 | ``` 7 | yarn react-native generate-bootsplash assets/ \ 8 | --platforms=android,ios \ 9 | --background=FFFFFF \ 10 | --logo-width=150 \ 11 | --assets-output=assets \ 12 | --flavor=main \ 13 | ``` 14 | 15 | 3. Done 16 | -------------------------------------------------------------------------------- /docs/03-how-to-keep-code-changed-from-node-modules.md: -------------------------------------------------------------------------------- 1 | ## How to keep code changed from node_modules 2 | 3 | 1. Run cmd 4 | 5 | ``` 6 | npx patch-package 7 | ``` 8 | 9 | 2. Check patch file at react-native-starter/patches 10 | 3. Run cmd to patch 11 | 12 | ``` 13 | yarn patch 14 | ``` 15 | 16 | In project set up auto run patch-package after install package, so can ignore step 3 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import 'react-native-gesture-handler' 2 | import './src/localization/i18n' 3 | import { AppRegistry, LogBox } from 'react-native' 4 | import { name as appName } from './app.json' 5 | import { Starter } from './src/starter' 6 | 7 | const IGNORED_LOGS = ['Warning: Function components cannot be given refs'] 8 | 9 | LogBox.ignoreLogs(IGNORED_LOGS) 10 | 11 | if (__DEV__) { 12 | const withoutIgnored = 13 | logger => 14 | (...args) => { 15 | const output = args.join(' ') 16 | if (!IGNORED_LOGS.some(log => output.includes(log))) { 17 | logger(...args) 18 | } 19 | } 20 | 21 | console.log = withoutIgnored(console.log) 22 | console.info = withoutIgnored(console.info) 23 | console.warn = withoutIgnored(console.warn) 24 | console.error = withoutIgnored(console.error) 25 | } 26 | 27 | AppRegistry.registerComponent(appName, () => Starter) 28 | -------------------------------------------------------------------------------- /ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /ios/Config.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Config.xcconfig 3 | // starter 4 | // 5 | // Created by BAPVN-THOAINT on 10/07/2022. 6 | // 7 | 8 | // Configuration settings file format documentation can be found at: 9 | // https://help.apple.com/xcode/#/dev745c5c974 10 | -------------------------------------------------------------------------------- /ios/DevelopmentExportOptions.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | destination 6 | export 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Resolve react_native_pods.rb with node to allow for hoisting 2 | require Pod::Executable.execute_command('node', ['-p', 3 | 'require.resolve( 4 | "react-native/scripts/react_native_pods.rb", 5 | {paths: [process.argv[1]]}, 6 | )', __dir__]).strip 7 | 8 | platform :ios, min_ios_version_supported 9 | prepare_react_native_project! 10 | 11 | # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. 12 | # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded 13 | # 14 | # To fix this you can also exclude `react-native-flipper` using a `react-native.config.js` 15 | # ```js 16 | # module.exports = { 17 | # dependencies: { 18 | # ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}), 19 | # ``` 20 | flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled 21 | 22 | linkage = ENV['USE_FRAMEWORKS'] 23 | if linkage != nil 24 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 25 | use_frameworks! :linkage => linkage.to_sym 26 | end 27 | 28 | project 'starter', 29 | 'Debug' => :debug, 30 | 'Release' => :release 31 | 32 | abstract_target 'starter' do 33 | config = use_native_modules! 34 | 35 | # Flags change depending on the env values. 36 | flags = get_default_flags() 37 | 38 | use_react_native!( 39 | :path => config[:reactNativePath], 40 | # Hermes is now enabled by default. Disable by setting this flag to false. 41 | :hermes_enabled => flags[:hermes_enabled], 42 | :fabric_enabled => flags[:fabric_enabled], 43 | # Enables Flipper. 44 | # 45 | # Note that if you have use_frameworks! enabled, Flipper will not work and 46 | # you should disable the next line. 47 | :flipper_configuration => flipper_config, 48 | # An absolute path to your application root. 49 | :app_path => "#{Pod::Config.instance.installation_root}/.." 50 | ) 51 | 52 | target 'production' do 53 | end 54 | target 'staging' do 55 | end 56 | target 'development' do 57 | end 58 | 59 | post_install do |installer| 60 | installer.pods_project.targets.each do |target| 61 | target.build_configurations.each do |config| 62 | if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle" 63 | target.build_configurations.each do |config| 64 | config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' 65 | end 66 | end 67 | end 68 | end 69 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 70 | react_native_post_install( 71 | installer, 72 | config[:reactNativePath], 73 | :mac_catalyst_enabled => false 74 | ) 75 | __apply_Xcode_12_5_M1_post_install_workaround(installer) 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /ios/ProductionExportOptions.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | destination 6 | export 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/StagingExportOptions.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | destination 6 | export 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/development/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Starter Development 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSExceptionDomains 30 | 31 | localhost 32 | 33 | NSExceptionAllowsInsecureHTTPLoads 34 | 35 | 36 | 37 | 38 | NSLocationWhenInUseUsageDescription 39 | 40 | UIAppFonts 41 | 42 | Poppins-Black.ttf 43 | Poppins-BlackItalic.ttf 44 | Poppins-Bold.ttf 45 | Poppins-BoldItalic.ttf 46 | Poppins-ExtraBold.ttf 47 | Poppins-ExtraBoldItalic.ttf 48 | Poppins-ExtraLight.ttf 49 | Poppins-ExtraLightItalic.ttf 50 | Poppins-Italic.ttf 51 | Poppins-Light.ttf 52 | Poppins-LightItalic.ttf 53 | Poppins-Medium.ttf 54 | Poppins-MediumItalic.ttf 55 | Poppins-Regular.ttf 56 | Poppins-SemiBold.ttf 57 | Poppins-SemiBoldItalic.ttf 58 | Poppins-Thin.ttf 59 | Poppins-ThinItalic.ttf 60 | 61 | UILaunchStoryboardName 62 | BootSplash 63 | UIRequiredDeviceCapabilities 64 | 65 | armv7 66 | 67 | UISupportedInterfaceOrientations 68 | 69 | UIInterfaceOrientationPortrait 70 | 71 | UISupportedInterfaceOrientations~ipad 72 | 73 | UIInterfaceOrientationLandscapeLeft 74 | UIInterfaceOrientationLandscapeRight 75 | UIInterfaceOrientationPortrait 76 | 77 | UIViewControllerBasedStatusBarAppearance 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ios/link-assets-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "migIndex": 1, 3 | "data": [ 4 | { 5 | "path": "src/assets/fonts/Poppins-Black.ttf", 6 | "sha1": "645e04c53c6b5b35bce654a811ebce16af8aa721" 7 | }, 8 | { 9 | "path": "src/assets/fonts/Poppins-BlackItalic.ttf", 10 | "sha1": "be4ef2aac7775a4b0179c0ecfce6cea8f7783f63" 11 | }, 12 | { 13 | "path": "src/assets/fonts/Poppins-Bold.ttf", 14 | "sha1": "875cf0cecd647bcf22e79d633d868c1b1ec98dfa" 15 | }, 16 | { 17 | "path": "src/assets/fonts/Poppins-BoldItalic.ttf", 18 | "sha1": "6de8952ca08d27ee77f57347d2cef4b4e26aa78f" 19 | }, 20 | { 21 | "path": "src/assets/fonts/Poppins-ExtraBold.ttf", 22 | "sha1": "4b5c0750f073abd576413a0898d3b95adaf199c8" 23 | }, 24 | { 25 | "path": "src/assets/fonts/Poppins-ExtraBoldItalic.ttf", 26 | "sha1": "94d3ee3acdbd00e37a715a25f2b5237666b5b8a3" 27 | }, 28 | { 29 | "path": "src/assets/fonts/Poppins-ExtraLight.ttf", 30 | "sha1": "85af6582a7e6155917c605f9d3fed68c02b23b06" 31 | }, 32 | { 33 | "path": "src/assets/fonts/Poppins-ExtraLightItalic.ttf", 34 | "sha1": "6fa0418f91032680d2d8fe4843009f2c80faa61d" 35 | }, 36 | { 37 | "path": "src/assets/fonts/Poppins-Italic.ttf", 38 | "sha1": "e2bfcd56016c3c915221ef1c5fc1a17b0776eb91" 39 | }, 40 | { 41 | "path": "src/assets/fonts/Poppins-Light.ttf", 42 | "sha1": "e247a92158e112f8bf7b638c8d95381d66b00dbb" 43 | }, 44 | { 45 | "path": "src/assets/fonts/Poppins-LightItalic.ttf", 46 | "sha1": "f76102b361e705751a83b7b22fe954daf1f27dd4" 47 | }, 48 | { 49 | "path": "src/assets/fonts/Poppins-Medium.ttf", 50 | "sha1": "283f21b44efbdbf276ba802be2d949a36bbc4233" 51 | }, 52 | { 53 | "path": "src/assets/fonts/Poppins-MediumItalic.ttf", 54 | "sha1": "b592c62fdf88c40cfd0f031bba6423b1d4430472" 55 | }, 56 | { 57 | "path": "src/assets/fonts/Poppins-Regular.ttf", 58 | "sha1": "fdd3002e7d814ee47c1c1b8487c72c6bbb3a2d00" 59 | }, 60 | { 61 | "path": "src/assets/fonts/Poppins-SemiBold.ttf", 62 | "sha1": "8a4ace9392d06bcb7f8ea2f5169b07e4c383a90d" 63 | }, 64 | { 65 | "path": "src/assets/fonts/Poppins-SemiBoldItalic.ttf", 66 | "sha1": "0c8bed0a5942b2388d36b11c115685d9f01700b0" 67 | }, 68 | { 69 | "path": "src/assets/fonts/Poppins-Thin.ttf", 70 | "sha1": "09ba4dcd5509c8085bf88c665dcc51cbdfced27b" 71 | }, 72 | { 73 | "path": "src/assets/fonts/Poppins-ThinItalic.ttf", 74 | "sha1": "fb8fccca21550e71135f92bbaf8a2e8a12c90465" 75 | } 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /ios/staging/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Starter Staging 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSExceptionDomains 30 | 31 | localhost 32 | 33 | NSExceptionAllowsInsecureHTTPLoads 34 | 35 | 36 | 37 | 38 | NSLocationWhenInUseUsageDescription 39 | 40 | UIAppFonts 41 | 42 | Poppins-Black.ttf 43 | Poppins-BlackItalic.ttf 44 | Poppins-Bold.ttf 45 | Poppins-BoldItalic.ttf 46 | Poppins-ExtraBold.ttf 47 | Poppins-ExtraBoldItalic.ttf 48 | Poppins-ExtraLight.ttf 49 | Poppins-ExtraLightItalic.ttf 50 | Poppins-Italic.ttf 51 | Poppins-Light.ttf 52 | Poppins-LightItalic.ttf 53 | Poppins-Medium.ttf 54 | Poppins-MediumItalic.ttf 55 | Poppins-Regular.ttf 56 | Poppins-SemiBold.ttf 57 | Poppins-SemiBoldItalic.ttf 58 | Poppins-Thin.ttf 59 | Poppins-ThinItalic.ttf 60 | 61 | UILaunchStoryboardName 62 | BootSplash 63 | UIRequiredDeviceCapabilities 64 | 65 | armv7 66 | 67 | UISupportedInterfaceOrientations 68 | 69 | UIInterfaceOrientationPortrait 70 | 71 | UISupportedInterfaceOrientations~ipad 72 | 73 | UIInterfaceOrientationLandscapeLeft 74 | UIInterfaceOrientationLandscapeRight 75 | UIInterfaceOrientationPortrait 76 | 77 | UIViewControllerBasedStatusBarAppearance 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ios/starter.xcodeproj/xcshareddata/xcschemes/development.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 11 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/starter.xcodeproj/xcshareddata/xcschemes/production.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 11 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/starter.xcodeproj/xcshareddata/xcschemes/staging.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 11 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/starter.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/starter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/starter/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : RCTAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/starter/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import "RNBootSplash.h" 3 | 4 | #import 5 | 6 | @implementation AppDelegate 7 | 8 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 9 | { 10 | self.moduleName = @"starter"; 11 | // You can add your custom initial props in the dictionary below. 12 | // They will be passed down to the ViewController used by React Native. 13 | self.initialProps = @{}; 14 | 15 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 16 | } 17 | 18 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 19 | { 20 | #if DEBUG 21 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 22 | #else 23 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 24 | #endif 25 | } 26 | 27 | - (UIView *)createRootViewWithBridge:(RCTBridge *)bridge 28 | moduleName:(NSString *)moduleName 29 | initProps:(NSDictionary *)initProps { 30 | UIView *rootView = [super createRootViewWithBridge:bridge 31 | moduleName:moduleName 32 | initProps:initProps]; 33 | 34 | [RNBootSplash initWithStoryboard:@"BootSplash" rootView:rootView]; // ⬅️ initialize the splash screen 35 | 36 | return rootView; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ios/starter/BootSplash.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/starter/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "iphone", 5 | "scale": "2x", 6 | "size": "20x20" 7 | }, 8 | { 9 | "idiom": "iphone", 10 | "scale": "3x", 11 | "size": "20x20" 12 | }, 13 | { 14 | "idiom": "iphone", 15 | "scale": "2x", 16 | "size": "29x29" 17 | }, 18 | { 19 | "idiom": "iphone", 20 | "scale": "3x", 21 | "size": "29x29" 22 | }, 23 | { 24 | "idiom": "iphone", 25 | "scale": "2x", 26 | "size": "40x40" 27 | }, 28 | { 29 | "idiom": "iphone", 30 | "scale": "3x", 31 | "size": "40x40" 32 | }, 33 | { 34 | "idiom": "iphone", 35 | "scale": "2x", 36 | "size": "60x60" 37 | }, 38 | { 39 | "idiom": "iphone", 40 | "scale": "3x", 41 | "size": "60x60" 42 | }, 43 | { 44 | "idiom": "ios-marketing", 45 | "scale": "1x", 46 | "size": "1024x1024" 47 | } 48 | ], 49 | "info": { 50 | "author": "xcode", 51 | "version": 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ios/starter/Images.xcassets/BootSplashLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "filename": "bootsplash_logo.png", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "universal", 10 | "filename": "bootsplash_logo@2x.png", 11 | "scale": "2x" 12 | }, 13 | { 14 | "idiom": "universal", 15 | "filename": "bootsplash_logo@3x.png", 16 | "scale": "3x" 17 | } 18 | ], 19 | "info": { 20 | "version": 1, 21 | "author": "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/starter/Images.xcassets/BootSplashLogo.imageset/bootsplash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/ios/starter/Images.xcassets/BootSplashLogo.imageset/bootsplash_logo.png -------------------------------------------------------------------------------- /ios/starter/Images.xcassets/BootSplashLogo.imageset/bootsplash_logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/ios/starter/Images.xcassets/BootSplashLogo.imageset/bootsplash_logo@2x.png -------------------------------------------------------------------------------- /ios/starter/Images.xcassets/BootSplashLogo.imageset/bootsplash_logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/ios/starter/Images.xcassets/BootSplashLogo.imageset/bootsplash_logo@3x.png -------------------------------------------------------------------------------- /ios/starter/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/starter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Starter 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSExceptionDomains 30 | 31 | localhost 32 | 33 | NSExceptionAllowsInsecureHTTPLoads 34 | 35 | 36 | 37 | 38 | NSLocationWhenInUseUsageDescription 39 | 40 | UIAppFonts 41 | 42 | Poppins-Black.ttf 43 | Poppins-BlackItalic.ttf 44 | Poppins-Bold.ttf 45 | Poppins-BoldItalic.ttf 46 | Poppins-ExtraBold.ttf 47 | Poppins-ExtraBoldItalic.ttf 48 | Poppins-ExtraLight.ttf 49 | Poppins-ExtraLightItalic.ttf 50 | Poppins-Italic.ttf 51 | Poppins-Light.ttf 52 | Poppins-LightItalic.ttf 53 | Poppins-Medium.ttf 54 | Poppins-MediumItalic.ttf 55 | Poppins-Regular.ttf 56 | Poppins-SemiBold.ttf 57 | Poppins-SemiBoldItalic.ttf 58 | Poppins-Thin.ttf 59 | Poppins-ThinItalic.ttf 60 | 61 | UILaunchStoryboardName 62 | BootSplash 63 | UIRequiredDeviceCapabilities 64 | 65 | armv7 66 | 67 | UISupportedInterfaceOrientations 68 | 69 | UIInterfaceOrientationPortrait 70 | 71 | UISupportedInterfaceOrientations~ipad 72 | 73 | UIInterfaceOrientationLandscapeLeft 74 | UIInterfaceOrientationLandscapeRight 75 | UIInterfaceOrientationPortrait 76 | 77 | UIViewControllerBasedStatusBarAppearance 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ios/starter/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ios/starterTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/starterTests/starterTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface starterTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation starterTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction( 38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 39 | if (level >= RCTLogLevelError) { 40 | redboxError = message; 41 | } 42 | }); 43 | #endif 44 | 45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 48 | 49 | foundElement = [self findSubviewInView:vc.view 50 | matching:^BOOL(UIView *view) { 51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 52 | return YES; 53 | } 54 | return NO; 55 | }]; 56 | } 57 | 58 | #ifdef DEBUG 59 | RCTSetLogFunction(RCTDefaultLogFunction); 60 | #endif 61 | 62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | setupFiles: ['/jest/setup.js'], 4 | setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'], 5 | transformIgnorePatterns: [ 6 | 'node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|@react-native|@react-navigation|moti/.*)', 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /jest/setup.js: -------------------------------------------------------------------------------- 1 | import 'react-native-gesture-handler/jestSetup'; 2 | 3 | jest.mock('react-native-reanimated', () => { 4 | const Reanimated = require('react-native-reanimated/mock'); 5 | 6 | // The mock for `call` immediately calls the callback which is incorrect 7 | // So we override it with a no-op 8 | Reanimated.default.call = () => {}; 9 | 10 | return Reanimated; 11 | }); 12 | 13 | // Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing 14 | jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); 15 | 16 | jest.mock('react-native-bootsplash', () => { 17 | return { 18 | hide: jest.fn().mockResolvedValueOnce(), 19 | getVisibilityStatus: jest.fn().mockResolvedValue('hidden'), 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | const { getDefaultConfig } = require('metro-config') 9 | 10 | module.exports = (async () => { 11 | const { 12 | resolver: { sourceExts, assetExts }, 13 | } = await getDefaultConfig() 14 | return { 15 | transformer: { 16 | getTransformOptions: async () => ({ 17 | transform: { 18 | experimentalImportSupport: false, 19 | inlineRequires: true, 20 | }, 21 | }), 22 | babelTransformerPath: require.resolve('react-native-svg-transformer'), 23 | }, 24 | resolver: { 25 | assetExts: assetExts.filter(ext => ext !== 'svg'), 26 | sourceExts: [...sourceExts, 'svg'], 27 | }, 28 | } 29 | })() 30 | -------------------------------------------------------------------------------- /patches/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/patches/.gitignore -------------------------------------------------------------------------------- /react-native.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | project: { 3 | ios: {}, 4 | android: {}, 5 | }, 6 | assets: ['./src/assets/fonts/'], 7 | }; 8 | -------------------------------------------------------------------------------- /scripts/fixfonts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # fixfonts.sh 3 | # android/app/src/main/res/font 4 | typeset folder="$1" 5 | if [[ -d "$folder" && ! -z "$folder" ]]; then 6 | pushd "$folder"; 7 | for file in *.ttf; do 8 | typeset normalized="${file//-/_}"; 9 | normalized="$(tr [A-Z] [a-z] <<< "$normalized")"; 10 | mv "$file" "$normalized"; 11 | done 12 | popd 13 | fi 14 | -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-Black.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-BlackItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-BoldItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-ExtraBold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-ExtraLight.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-Italic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-Light.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-LightItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-Medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-MediumItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-SemiBold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-Thin.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Poppins-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonnguyenitc/react-native-starter/ead2b4a5b8448ccf3d5baa34974b0d65f8d1f0d6/src/assets/fonts/Poppins-ThinItalic.ttf -------------------------------------------------------------------------------- /src/assets/images/index.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /src/assets/index.ts: -------------------------------------------------------------------------------- 1 | export * from './images' 2 | export * from './jsons' 3 | -------------------------------------------------------------------------------- /src/assets/jsons/index.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /src/assets/jsons/spinner.json: -------------------------------------------------------------------------------- 1 | {"v":"5.5.5","fr":25,"ip":0,"op":79,"w":300,"h":150,"nm":"Loading-2","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"icon 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":39,"s":[-90]},{"t":79,"s":[270]}],"ix":10},"p":{"a":0,"k":[150,75,0],"ix":2},"a":{"a":0,"k":[53,53,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-15.464],[15.464,0],[0,15.464],[-15.464,0]],"o":[[0,15.464],[-15.464,0],[0,-15.464],[15.464,0]],"v":[[28,0],[0,28],[-28,0],[0,-28]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.058823529411764705,0.3843137254901961,0.996078431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[53,53],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":51,"s":[0]},{"t":79,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":39,"s":[0]},{"t":64,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":39,"op":79,"st":39,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"icon","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[-90]},{"t":40,"s":[270]}],"ix":10},"p":{"a":0,"k":[150,75,0],"ix":2},"a":{"a":0,"k":[53,53,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-15.464],[15.464,0],[0,15.464],[-15.464,0]],"o":[[0,15.464],[-15.464,0],[0,-15.464],[15.464,0]],"v":[[28,0],[0,28],[-28,0],[0,-28]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.058823529411764705,0.3843137254901961,0.996078431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[53,53],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":12,"s":[0]},{"t":40,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":25,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":40,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /src/common/config/index.ts: -------------------------------------------------------------------------------- 1 | import Config from 'react-native-config' 2 | 3 | export const { APP_NAME, API_URL } = Config 4 | -------------------------------------------------------------------------------- /src/common/constants/dimension.ts: -------------------------------------------------------------------------------- 1 | import { Dimensions } from 'react-native' 2 | 3 | export const WIDTH = Dimensions.get('window').width 4 | export const HEIGHT = Dimensions.get('window').height 5 | -------------------------------------------------------------------------------- /src/common/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dimension' 2 | export * from './platform' 3 | export * from './styles' 4 | export * from './transitions' 5 | -------------------------------------------------------------------------------- /src/common/constants/platform.ts: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native' 2 | 3 | export const Android = Platform.OS === 'android' 4 | export const iOS = Platform.OS === 'ios' 5 | -------------------------------------------------------------------------------- /src/common/constants/styles.ts: -------------------------------------------------------------------------------- 1 | import { ViewStyle } from 'react-native' 2 | 3 | import { ViewProps } from '../types' 4 | 5 | export type StyleName = 'flex_1' 6 | 7 | export const styles: { [key in StyleName]: ViewStyle } = { 8 | flex_1: { 9 | flex: 1, 10 | }, 11 | } 12 | 13 | export type ShadowName = 'normal' 14 | 15 | export const shadows: { [key in ShadowName]: ViewProps } = { 16 | normal: { 17 | elevation: 5, 18 | shadowColor: 'shadow.default', 19 | shadowOffset: { width: 0, height: 0 }, 20 | shadowOpacity: 0.3, 21 | shadowRadius: 10, 22 | }, 23 | } 24 | 25 | export const borderRadiusSizes = { 26 | tiny: 4, 27 | small: 8, 28 | medium: 12, 29 | large: 24, 30 | xl: 32, 31 | huge: 64, 32 | } 33 | -------------------------------------------------------------------------------- /src/common/constants/transitions.ts: -------------------------------------------------------------------------------- 1 | import { Easing } from 'react-native-reanimated' 2 | 3 | import { MotiTransitionProp } from 'moti' 4 | 5 | export const transitions: { [key: string]: MotiTransitionProp } = { 6 | screen: { type: 'timing', duration: 450, easing: Easing.inOut(Easing.ease) }, 7 | } 8 | -------------------------------------------------------------------------------- /src/common/hooks/__tests__/useDisclosure.test.ts: -------------------------------------------------------------------------------- 1 | import { act, renderHook } from '@testing-library/react-hooks' 2 | 3 | import { useDisclosure } from '../useDisclosure' 4 | 5 | test('should open the state', () => { 6 | const { result } = renderHook(() => useDisclosure()) 7 | 8 | expect(result.current.isOpen).toBe(false) 9 | 10 | act(() => { 11 | result.current.open() 12 | }) 13 | 14 | expect(result.current.isOpen).toBe(true) 15 | }) 16 | 17 | test('should close the state', () => { 18 | const { result } = renderHook(() => useDisclosure()) 19 | 20 | expect(result.current.isOpen).toBe(false) 21 | 22 | act(() => { 23 | result.current.close() 24 | }) 25 | 26 | expect(result.current.isOpen).toBe(false) 27 | }) 28 | 29 | test('should toggle the state', () => { 30 | const { result } = renderHook(() => useDisclosure()) 31 | 32 | expect(result.current.isOpen).toBe(false) 33 | 34 | act(() => { 35 | result.current.toggle() 36 | }) 37 | 38 | expect(result.current.isOpen).toBe(true) 39 | 40 | act(() => { 41 | result.current.toggle() 42 | }) 43 | 44 | expect(result.current.isOpen).toBe(false) 45 | }) 46 | 47 | test('should define initial state', () => { 48 | const { result } = renderHook(() => useDisclosure(true)) 49 | 50 | expect(result.current.isOpen).toBe(true) 51 | 52 | act(() => { 53 | result.current.toggle() 54 | }) 55 | 56 | expect(result.current.isOpen).toBe(false) 57 | }) 58 | -------------------------------------------------------------------------------- /src/common/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useBarStyle' 2 | export * from './useDisclosure' 3 | export * from './usePrevious' 4 | export * from './useTranslation' 5 | export * from './useWhyYouUpdate' 6 | -------------------------------------------------------------------------------- /src/common/hooks/useBarStyle.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { StatusBar } from 'react-native' 3 | 4 | import { useThemeStore } from '@/common/stores' 5 | 6 | export const useBarStyle = () => { 7 | const isDarkMode = useThemeStore(state => state.isDarkMode) 8 | 9 | useEffect(() => { 10 | StatusBar.setBarStyle(isDarkMode ? 'light-content' : 'dark-content') 11 | }, [isDarkMode]) 12 | } 13 | -------------------------------------------------------------------------------- /src/common/hooks/useDisclosure.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react' 2 | 3 | export const useDisclosure = (initial = false) => { 4 | const [isOpen, setIsOpen] = useState(initial) 5 | 6 | const open = useCallback(() => setIsOpen(true), []) 7 | const close = useCallback(() => setIsOpen(false), []) 8 | const toggle = useCallback(() => setIsOpen(state => !state), []) 9 | 10 | return { isOpen, open, close, toggle } 11 | } 12 | -------------------------------------------------------------------------------- /src/common/hooks/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | export const usePrevious = (value: T): T | undefined => { 4 | const previousValue = useRef() 5 | 6 | useEffect(() => { 7 | previousValue.current = value 8 | }, [value]) 9 | 10 | return previousValue.current 11 | } 12 | -------------------------------------------------------------------------------- /src/common/hooks/useTranslation.ts: -------------------------------------------------------------------------------- 1 | import { useTranslation as useTranslationLib } from 'react-i18next' 2 | 3 | export const useTranslation = () => { 4 | const { t, i18n } = useTranslationLib() 5 | return { i18n, t } 6 | } 7 | -------------------------------------------------------------------------------- /src/common/hooks/useWhyYouUpdate.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | import { logger } from '../utils/logger' 4 | 5 | export const useWhyYouUpdate = (name: string, props: any) => { 6 | const previousProps = useRef() 7 | 8 | useEffect(() => { 9 | if (previousProps.current) { 10 | const allKeys = Object.keys({ ...previousProps.current, ...props }) 11 | const changesObj: { [key: string]: any } = {} 12 | allKeys.forEach(key => { 13 | if (previousProps.current[key] !== props[key]) { 14 | changesObj[key] = { 15 | from: previousProps.current[key], 16 | to: props[key], 17 | } 18 | } 19 | }) 20 | if (Object.keys(changesObj).length) { 21 | logger.log('[why-did-you-update]', name, changesObj) 22 | } 23 | } 24 | previousProps.current = props 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /src/common/stores/__tests__/notifications.test.ts: -------------------------------------------------------------------------------- 1 | import { act, renderHook } from '@testing-library/react-hooks' 2 | 3 | import { Notification, useNotificationStore } from '../useNotificationStore' 4 | 5 | test('should add and remove notifications', () => { 6 | const { result } = renderHook(() => useNotificationStore()) 7 | 8 | expect(result.current.notifications.length).toBe(0) 9 | 10 | const notification: Notification = { 11 | id: '123', 12 | title: 'Hello World', 13 | type: 'info', 14 | message: 'This is a notification', 15 | } 16 | 17 | act(() => { 18 | result.current.addNotification(notification) 19 | }) 20 | 21 | expect(result.current.notifications).toContainEqual(notification) 22 | 23 | act(() => { 24 | result.current.dismissNotification(notification.id) 25 | }) 26 | 27 | expect(result.current.notifications).not.toContainEqual(notification) 28 | }) 29 | -------------------------------------------------------------------------------- /src/common/stores/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useNotificationStore' 2 | export * from './useThemeStore' 3 | -------------------------------------------------------------------------------- /src/common/stores/useNotificationStore.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | 3 | export type Notification = { 4 | id: string 5 | type: 'info' | 'warning' | 'success' | 'error' 6 | title: string 7 | message?: string 8 | } 9 | 10 | type State = { 11 | notifications: Notification[] 12 | } 13 | 14 | type Action = { 15 | addNotification: (notification: Omit) => void 16 | dismissNotification: (id: string) => void 17 | } 18 | 19 | export const useNotificationStore = create(set => ({ 20 | notifications: [], 21 | addNotification: notification => 22 | set(state => ({ 23 | notifications: [...state.notifications, { id: Math.random().toString(), ...notification }], 24 | })), 25 | dismissNotification: id => 26 | set(state => ({ 27 | notifications: state.notifications.filter(notification => notification.id !== id), 28 | })), 29 | })) 30 | -------------------------------------------------------------------------------- /src/common/stores/useThemeStore.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | import { createJSONStorage, persist } from 'zustand/middleware' 3 | 4 | import { MMKVStorage } from '@/common/utils/storage' 5 | 6 | export type State = { 7 | isDarkMode: boolean 8 | } 9 | 10 | export type Action = { 11 | toggleMode: () => void 12 | } 13 | 14 | export const useThemeStore = create( 15 | persist( 16 | set => ({ 17 | isDarkMode: false, 18 | toggleMode: () => { 19 | set(state => { 20 | return { 21 | isDarkMode: !state.isDarkMode, 22 | } 23 | }) 24 | }, 25 | }), 26 | { 27 | name: 'theme-store', 28 | storage: createJSONStorage(() => MMKVStorage), 29 | partialize: state => ({ isDarkMode: state.isDarkMode } as State & Action), 30 | }, 31 | ), 32 | ) 33 | -------------------------------------------------------------------------------- /src/common/themes/color.ts: -------------------------------------------------------------------------------- 1 | import { palette } from './palette' 2 | 3 | export type ColorName = 4 | | 'transparent' 5 | | 'background.default' 6 | | 'primary' 7 | | 'secondary' 8 | | 'line' 9 | | 'text.default' 10 | | 'dim' 11 | | 'error' 12 | | 'highlight' 13 | | 'background.switchTrack' 14 | | 'background.switch' 15 | | 'shadow.default' 16 | 17 | export const color: { [key in ColorName]: string } = { 18 | transparent: 'rgba(0, 0, 0, 0)', 19 | 20 | 'background.default': palette.white, 21 | 22 | primary: palette.white, 23 | 24 | secondary: palette.black, 25 | 26 | line: palette.offWhite, 27 | 28 | 'text.default': palette.black, 29 | 30 | dim: palette.lightGrey, 31 | 32 | error: palette.angry, 33 | 34 | highlight: palette.blue, 35 | 36 | 'background.switch': palette.black, 37 | 'background.switchTrack': palette.white, 38 | 'shadow.default': palette.black, 39 | } 40 | -------------------------------------------------------------------------------- /src/common/themes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './color' 2 | export * from './spacing' 3 | export * from './theme' 4 | export * from './timing' 5 | export * from './typography' 6 | -------------------------------------------------------------------------------- /src/common/themes/palette.ts: -------------------------------------------------------------------------------- 1 | type PaletteName = 2 | | 'black' 3 | | 'white' 4 | | 'offWhite' 5 | | 'orange' 6 | | 'orangeDarker' 7 | | 'lightGrey' 8 | | 'lighterGrey' 9 | | 'angry' 10 | | 'deepPurple' 11 | | 'blue' 12 | | 'black05' 13 | 14 | export const palette: { [key in PaletteName]: string } = { 15 | black: '#252525', 16 | white: '#ffffff', 17 | offWhite: '#e6e6e6', 18 | orange: '#FBA928', 19 | orangeDarker: '#EB9918', 20 | lightGrey: '#939AA4', 21 | lighterGrey: '#CDD4DA', 22 | angry: '#dd3333', 23 | deepPurple: '#5D2555', 24 | blue: 'blue', 25 | black05: 'rgba(0,0,0,0.5)', 26 | } 27 | -------------------------------------------------------------------------------- /src/common/themes/spacing.ts: -------------------------------------------------------------------------------- 1 | export type SpacingName = 'tiny' | 'small' | 'medium' | 'large' | 'huge' | '16px' 2 | 3 | export const spacing: { [key in SpacingName]: number } = { 4 | tiny: 4, 5 | small: 8, 6 | medium: 12, 7 | large: 24, 8 | huge: 64, 9 | '16px': 16, 10 | } 11 | -------------------------------------------------------------------------------- /src/common/themes/theme.ts: -------------------------------------------------------------------------------- 1 | import { createTheme, useTheme as useThemeRS } from '@shopify/restyle' 2 | 3 | import { color } from './color' 4 | import { palette } from './palette' 5 | import { spacing } from './spacing' 6 | import { typography } from './typography' 7 | 8 | export const lightTheme = createTheme({ 9 | colors: { 10 | ...color, 11 | }, 12 | spacing: { 13 | ...spacing, 14 | }, 15 | textInputVariants: { 16 | defaults: { 17 | flex: 1, 18 | fontSize: 16, 19 | fontFamily: 'Poppins', 20 | lineHeight: 16 * 1.5, 21 | }, 22 | normal: { 23 | fontSize: 16, 24 | color: 'text.default', 25 | fontFamily: 'Poppins', 26 | fontWeight: 'bold', 27 | }, 28 | }, 29 | textVariants: { 30 | defaults: { 31 | fontSize: 16, 32 | color: 'text.default', 33 | fontFamily: 'Poppins', 34 | }, 35 | ...typography, 36 | }, 37 | breakpoints: { 38 | phone: 0, 39 | tablet: 768, 40 | largeTablet: 1024, 41 | }, 42 | isDark: false, 43 | }) 44 | 45 | export type Theme = typeof lightTheme 46 | 47 | export const darkTheme: Theme = { 48 | ...lightTheme, 49 | isDark: true, 50 | colors: { 51 | ...lightTheme.colors, 52 | 'background.default': palette.black, 53 | highlight: palette.white, 54 | 'text.default': palette.white, 55 | primary: palette.black, 56 | secondary: palette.white, 57 | 'background.switch': palette.white, 58 | 'background.switchTrack': palette.black, 59 | }, 60 | } 61 | 62 | export const useTheme = () => useThemeRS() 63 | -------------------------------------------------------------------------------- /src/common/themes/timing.ts: -------------------------------------------------------------------------------- 1 | export const timing = { 2 | quick: 300, 3 | } 4 | -------------------------------------------------------------------------------- /src/common/themes/typography.ts: -------------------------------------------------------------------------------- 1 | import { TextStyle } from 'react-native' 2 | 3 | export type TypoName = 'emoji' | 'normal' | 'light' | 'h1' | 'h2' | 'subTitle' 4 | 5 | export const typography: { [key in TypoName]: TextStyle } = { 6 | emoji: { 7 | fontSize: 50, 8 | color: 'text.default', 9 | fontFamily: 'Poppins', 10 | }, 11 | normal: { 12 | fontWeight: 'bold', 13 | fontSize: 16, 14 | color: 'text.default', 15 | fontFamily: 'Poppins', 16 | }, 17 | light: { 18 | fontSize: 16, 19 | color: 'text.default', 20 | fontFamily: 'Poppins', 21 | }, 22 | h1: { 23 | fontWeight: 'bold', 24 | fontSize: 28, 25 | color: 'text.default', 26 | fontFamily: 'Poppins', 27 | }, 28 | h2: { 29 | fontSize: 28, 30 | color: 'text.default', 31 | fontFamily: 'Poppins', 32 | }, 33 | subTitle: { 34 | fontSize: 14, 35 | color: 'text.default', 36 | fontFamily: 'Poppins', 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /src/common/types/api.ts: -------------------------------------------------------------------------------- 1 | export type ResponseApi = { 2 | ok: boolean 3 | data?: T 4 | error?: { 5 | message: string 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/common/types/function.ts: -------------------------------------------------------------------------------- 1 | export type CallbackFunction = (param?: T) => V 2 | export type CallbackFunctionRequired = (param: T) => V 3 | export type CallbackFunctionParams = (param1?: T1, param2?: T2) => V 4 | export type CallbackFunctionParamsRequired = (param1: T1, param2: T2) => V 5 | export type AsyncCallbackFunction = (param?: T) => Promise 6 | export type AsyncCallbackFunctionRequired = (param: T) => Promise 7 | export type AsyncCallbackFunctionParams = (arg1?: T1, arg2?: T2) => Promise 8 | export type AsyncCallbackFunctionParamsRequired = (arg1: T1, arg2: T2) => Promise 9 | export type CallbackFunctionVariadicAnyReturn = (...args: any[]) => any 10 | export type CallbackFunctionVariadicAnyReturnAsync = (...args: any[]) => Promise 11 | -------------------------------------------------------------------------------- /src/common/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api' 2 | export * from './function' 3 | export * from './ui' 4 | -------------------------------------------------------------------------------- /src/common/types/ui.ts: -------------------------------------------------------------------------------- 1 | import { StyleProp, ViewProps as ViewPropsRN, ViewStyle } from 'react-native' 2 | 3 | import { 4 | BackgroundColorProps, 5 | BorderProps, 6 | LayoutProps, 7 | PositionProps, 8 | ShadowProps, 9 | SpacingProps, 10 | } from '@shopify/restyle' 11 | 12 | import { Theme } from '@/common/themes' 13 | 14 | export interface ChildrenProps { 15 | children?: React.ReactNode 16 | } 17 | export type ViewProps = { style?: StyleProp } & SpacingProps & 18 | BorderProps & 19 | BackgroundColorProps & 20 | LayoutProps & 21 | PositionProps & 22 | ShadowProps & 23 | ViewPropsRN 24 | -------------------------------------------------------------------------------- /src/common/utils/axios.ts: -------------------------------------------------------------------------------- 1 | import Axios, { AxiosError, AxiosRequestConfig } from 'axios' 2 | 3 | import { ResponseApi } from '../types/api' 4 | import { API_URL } from '@/common/config' 5 | import { useNotificationStore } from '@/common/stores' 6 | import storage from '@/common/utils/storage' 7 | 8 | function authRequestInterceptor(config: AxiosRequestConfig) { 9 | const token = storage.getAccessToken() 10 | const newConfig = config 11 | if (newConfig && newConfig?.headers) { 12 | if (token) { 13 | newConfig.headers.authorization = `${token}` 14 | } 15 | newConfig.headers.Accept = 'application/json' 16 | newConfig.headers['Content-Type'] = 'application/json' 17 | } 18 | return newConfig 19 | } 20 | 21 | export const axios = Axios.create({ 22 | baseURL: API_URL, 23 | timeout: 5000, 24 | }) 25 | 26 | axios.interceptors.request.use(authRequestInterceptor) 27 | axios.interceptors.response.use( 28 | response => { 29 | return response.data 30 | }, 31 | error => { 32 | const message = error.response?.data?.message || error.message 33 | useNotificationStore.getState().addNotification({ 34 | type: 'error', 35 | title: 'Error', 36 | message, 37 | }) 38 | 39 | return Promise.reject(error) 40 | }, 41 | ) 42 | 43 | export const NONE = null 44 | export const CLIENT_ERROR = 'CLIENT_ERROR' 45 | export const SERVER_ERROR = 'SERVER_ERROR' 46 | export const TIMEOUT_ERROR = 'TIMEOUT_ERROR' 47 | export const CONNECTION_ERROR = 'CONNECTION_ERROR' 48 | export const NETWORK_ERROR = 'NETWORK_ERROR' 49 | export const UNKNOWN_ERROR = 'UNKNOWN_ERROR' 50 | export const CANCEL_ERROR = 'CANCEL_ERROR' 51 | 52 | const TIMEOUT_ERROR_CODES = ['ECONNABORTED'] 53 | const NODEJS_CONNECTION_ERROR_CODES = ['ENOTFOUND', 'ECONNREFUSED', 'ECONNRESET'] 54 | 55 | const inRange = (min: number, max: number, value: number): boolean => value >= min && value <= max 56 | 57 | const in200s = (n: number): boolean => inRange(200, 299, n) 58 | const in400s = (n: number): boolean => inRange(400, 499, n) 59 | const in500s = (n: number): boolean => inRange(500, 599, n) 60 | 61 | type StatusCodes = undefined | number | null 62 | 63 | export const getProblemFromStatus = (status: StatusCodes) => { 64 | if (!status) return UNKNOWN_ERROR 65 | if (in200s(status)) return NONE 66 | if (in400s(status)) return CLIENT_ERROR 67 | if (in500s(status)) return SERVER_ERROR 68 | return UNKNOWN_ERROR 69 | } 70 | 71 | export const getProblemFromError = (error: AxiosError | any) => { 72 | if (error.message === 'Network Error') return NETWORK_ERROR 73 | if (Axios.isCancel(error)) return CANCEL_ERROR 74 | if (!error.code) return getProblemFromStatus(error.response ? error.response.status : null) 75 | if (TIMEOUT_ERROR_CODES.includes(error.code)) return TIMEOUT_ERROR 76 | if (NODEJS_CONNECTION_ERROR_CODES.includes(error.code)) return CONNECTION_ERROR 77 | return UNKNOWN_ERROR 78 | } 79 | 80 | export const generateErrorData = (error: any): ResponseApi => { 81 | return { 82 | ok: false, 83 | error: { 84 | problem: getProblemFromError(error), 85 | ...(typeof error?.response?.data === 'string' 86 | ? { message: error?.response?.data } 87 | : error?.response?.data), 88 | }, 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/common/utils/dialog.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, TouchableWithoutFeedback } from 'react-native' 3 | import RootSiblings from 'react-native-root-siblings' 4 | 5 | import { HEIGHT, WIDTH } from '../constants' 6 | import { palette } from '../themes/palette' 7 | import { FadeView, Spinner } from '@/components/widgets' 8 | 9 | const styles = StyleSheet.create({ 10 | backDrop: { 11 | alignItems: 'center', 12 | backgroundColor: palette.black05, 13 | justifyContent: 'center', 14 | position: 'absolute', 15 | }, 16 | }) 17 | 18 | export const siblings: any = [] 19 | 20 | export function hideCustomDialog() { 21 | siblings.forEach((sib: any) => sib?.destroy()) 22 | siblings.length = 0 23 | } 24 | 25 | export function showCustomDialog(component: React.ReactNode, isHide = false, bottom = false) { 26 | const style = { 27 | width: WIDTH, 28 | height: HEIGHT, 29 | } 30 | 31 | const sibling = new RootSiblings( 32 | ( 33 | // eslint-disable-next-line react/jsx-no-useless-fragment 34 | <> 35 | {isHide ? ( 36 | 37 | 46 | {component} 47 | 48 | 49 | ) : ( 50 | 59 | {component} 60 | 61 | )} 62 | 63 | ), 64 | ) 65 | siblings.push(sibling as never) 66 | } 67 | 68 | export function hideLoading() { 69 | const current = siblings.shift() 70 | current?.destroy() 71 | } 72 | 73 | export function showLoading() { 74 | showCustomDialog() 75 | } 76 | -------------------------------------------------------------------------------- /src/common/utils/event-register.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-syntax */ 2 | 3 | import { CallbackFunction } from '../types' 4 | 5 | export class EventRegister { 6 | static listeners: { 7 | count: number 8 | refs: Map> 9 | } = { 10 | count: 0, 11 | refs: new Map(), 12 | } 13 | 14 | static addEventListener(eventName: string, callback: CallbackFunction) { 15 | if (!eventName || !callback || typeof callback !== 'function') return false 16 | EventRegister.listeners.refs.set(eventName, callback) 17 | return true 18 | } 19 | 20 | static removeEventListener(eventName: string) { 21 | if (!eventName) return false 22 | return EventRegister.listeners.refs.delete(eventName) 23 | } 24 | 25 | static removeAllListeners() { 26 | EventRegister.listeners.refs.clear() 27 | } 28 | 29 | static emitEvent(eventName: string, data?: any) { 30 | if (EventRegister.listeners.refs.has(eventName)) { 31 | const callback = EventRegister.listeners.refs.get(eventName) 32 | // eslint-disable-next-line no-unused-expressions 33 | callback && callback(data) 34 | } 35 | } 36 | 37 | static on(eventName: string, callback: CallbackFunction) { 38 | return EventRegister.addEventListener(eventName, callback) 39 | } 40 | 41 | static off(eventName: string) { 42 | return EventRegister.removeEventListener(eventName) 43 | } 44 | 45 | static offAll() { 46 | return EventRegister.removeAllListeners() 47 | } 48 | 49 | static emit(eventName: string, data?: any) { 50 | EventRegister.emitEvent(eventName, data) 51 | } 52 | } 53 | 54 | export const EVENTS = { 55 | EVENT_LOGOUT: '@EVENT_LOGOUT', 56 | } 57 | -------------------------------------------------------------------------------- /src/common/utils/global-props.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-param-reassign */ 2 | import { FlatList, ScrollView } from 'react-native' 3 | 4 | const setCustomProps = (component: any, customProps: any) => { 5 | const Component = component.render 6 | const initialDefaultProps = component.defaultProps 7 | component.defaultProps = { 8 | ...initialDefaultProps, 9 | ...customProps, 10 | } 11 | component.render = function render(props: any) { 12 | const oldProps = props 13 | props = { ...props, style: [customProps.style, props.style] } 14 | try { 15 | // eslint-disable-next-line prefer-rest-params 16 | return Component.apply(this, arguments) 17 | } finally { 18 | props = oldProps 19 | } 20 | } 21 | } 22 | 23 | const indicatorProps = { 24 | showsHorizontalScrollIndicator: false, 25 | showsVerticalScrollIndicator: false, 26 | } 27 | 28 | export const hideIndicatorList = () => { 29 | setCustomProps(ScrollView, indicatorProps) 30 | setCustomProps(FlatList, indicatorProps) 31 | } 32 | -------------------------------------------------------------------------------- /src/common/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | import { Insets } from 'react-native' 2 | 3 | import { AxiosResponse } from 'axios' 4 | 5 | import { ResponseApi } from '../types' 6 | import { generateErrorData } from './axios' 7 | 8 | export const delay = (ms: number) => { 9 | return new Promise(resolve => { 10 | setTimeout(() => resolve(true), ms) 11 | }) 12 | } 13 | 14 | export const generateHitSlop = ({ 15 | bottom = 10, 16 | left = 10, 17 | right = 10, 18 | top = 10, 19 | }: Insets): Insets => { 20 | return { bottom, left, right, top } 21 | } 22 | 23 | type UploadFileToS3Args = { 24 | url: string 25 | data: any 26 | onError: () => void 27 | onLoad: () => void 28 | onProcess: () => void 29 | } 30 | 31 | export const uploadFileToS3 = ({ url, data, onError, onLoad, onProcess }: UploadFileToS3Args) => { 32 | const photo = { 33 | uri: url, 34 | type: 'image/png', 35 | name: url, 36 | } 37 | 38 | const xhr = new XMLHttpRequest() 39 | xhr.open('PUT', data.preSignedURL) 40 | xhr.setRequestHeader('Content-Type', photo.type) 41 | xhr.upload.onprogress = onProcess 42 | xhr.onload = onLoad 43 | xhr.onerror = onError 44 | xhr.send(photo) 45 | } 46 | 47 | export const wrapApiCall = async ( 48 | apiCall: () => Promise>, 49 | ): Promise> => { 50 | try { 51 | const response = await apiCall() 52 | return { 53 | ok: true, 54 | data: response.data, 55 | } 56 | } catch (error) { 57 | // You can customize the error handling as needed 58 | // console.error('API error:', error) 59 | return generateErrorData(error) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/common/utils/logger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable consistent-return */ 2 | /* eslint-disable prefer-rest-params */ 3 | 4 | export const logger = { 5 | debug(...args: any[]) { 6 | const date = new Date().toLocaleTimeString() 7 | Array.prototype.unshift.call(args, `[${date}] ⚡⚡⚡ `) 8 | console.log(...args) 9 | }, 10 | error(...args: any[]) { 11 | if (__DEV__) { 12 | console.error(...args) 13 | } 14 | }, 15 | log(...args: any[]) { 16 | const date = new Date().toLocaleTimeString() 17 | Array.prototype.unshift.call(args, `[${date}]`) 18 | console.log(...args) 19 | }, 20 | warn(...args: any[]) { 21 | console.warn(...args) 22 | }, 23 | info(...args: any[]) { 24 | console.info(...args) 25 | }, 26 | table(...args: any[]) { 27 | console.table(...args) 28 | }, 29 | json(obj: any) { 30 | const date = new Date().toLocaleTimeString() 31 | console.log(`[${date}] JSON ⚡⚡⚡`, JSON.stringify(obj, null, 2)) 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /src/common/utils/magic-memo.ts: -------------------------------------------------------------------------------- 1 | import React, { ComponentProps, ComponentType, MemoExoticComponent } from 'react' 2 | import isEqual from 'react-fast-compare' 3 | 4 | import { pick } from 'lodash' 5 | 6 | type DeepPartial = { 7 | [P in keyof T]?: DeepPartial 8 | } 9 | 10 | export const magicMemo = >( 11 | Component: C, 12 | deps?: string | string[], // This is list keys of props 13 | customComparisonFunc?: (props: DeepPartial>) => boolean, 14 | ): MemoExoticComponent => { 15 | return React.memo(Component, (prev, next) => { 16 | if (!deps) { 17 | if (customComparisonFunc) { 18 | return customComparisonFunc(prev) === customComparisonFunc(next) 19 | } 20 | 21 | return isEqual(prev, next) 22 | } 23 | 24 | const magicDeps = typeof deps === 'string' ? [deps] : deps 25 | const magicPrev = pick(prev, magicDeps) as DeepPartial> 26 | const magicNext = pick(next, magicDeps) as DeepPartial> 27 | 28 | if (customComparisonFunc) { 29 | return customComparisonFunc(magicPrev) === customComparisonFunc(magicNext) 30 | } 31 | 32 | return isEqual(magicPrev, magicNext) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /src/common/utils/mmvk.ts: -------------------------------------------------------------------------------- 1 | import { MMKV } from 'react-native-mmkv' 2 | 3 | export const mmkv = new MMKV() 4 | -------------------------------------------------------------------------------- /src/common/utils/never-rerender.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const alwaysTrue = () => true 4 | 5 | export const neverRerender = (Component: React.FC) => { 6 | return React.memo(Component, alwaysTrue) 7 | } 8 | -------------------------------------------------------------------------------- /src/common/utils/refresh-token-multi-request.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | import axios from 'axios' 3 | 4 | // for multiple requests 5 | let isRefreshing = false 6 | let failedQueue: any[] = [] 7 | 8 | const processQueue = (error: any, token = null) => { 9 | failedQueue.forEach(prom => { 10 | if (error) { 11 | prom.reject(error) 12 | } else { 13 | prom.resolve(token) 14 | } 15 | }) 16 | 17 | failedQueue = [] 18 | } 19 | 20 | export const interceptorOnError = (error: any) => { 21 | const _axios = axios 22 | const originalRequest = error.config 23 | 24 | if (error.response.status === 401 && !originalRequest?._retry) { 25 | if (isRefreshing) { 26 | return new Promise(function (resolve, reject) { 27 | failedQueue.push({ resolve, reject }) 28 | }) 29 | .then(token => { 30 | originalRequest.headers.Authorization = `Bearer ${token}` 31 | return _axios.request(originalRequest) 32 | }) 33 | .catch(err => { 34 | return Promise.reject(err) 35 | }) 36 | } 37 | 38 | originalRequest._retry = true 39 | isRefreshing = true 40 | // call api refresh token 41 | return new Promise((resolve, reject) => { 42 | _axios 43 | .post(`${originalRequest?.baseURL}/refresh`) 44 | .then(({ data }) => { 45 | const newToken = data?.data?.token 46 | // save token to local 47 | processQueue(null, newToken) 48 | resolve(_axios(originalRequest)) 49 | }) 50 | .catch(err => { 51 | processQueue(err, null) 52 | reject(err) 53 | }) 54 | .finally(() => { 55 | isRefreshing = false 56 | }) 57 | }) 58 | } 59 | 60 | return Promise.reject(error) 61 | } 62 | -------------------------------------------------------------------------------- /src/common/utils/responsive.ts: -------------------------------------------------------------------------------- 1 | import { PixelRatio } from 'react-native' 2 | import { RFValue } from 'react-native-responsive-fontsize' 3 | 4 | import { HEIGHT, WIDTH } from '@/common/constants' 5 | 6 | // change it 7 | const DESIGN_SCREEN_WIDTH = 0 8 | const DESIGN_SCREEN_HEIGHT = 0 9 | 10 | const rWidth = (designWidth: number) => { 11 | return PixelRatio.roundToNearestPixel((WIDTH * designWidth) / DESIGN_SCREEN_WIDTH) 12 | } 13 | 14 | const rHeight = (designHeight: number) => { 15 | return PixelRatio.roundToNearestPixel((HEIGHT * designHeight) / DESIGN_SCREEN_HEIGHT) 16 | } 17 | 18 | const rFontSize = (fontDesign: number) => RFValue(fontDesign, DESIGN_SCREEN_HEIGHT) 19 | 20 | export const responsive = { 21 | rWidth, 22 | rHeight, 23 | rFontSize, 24 | } 25 | -------------------------------------------------------------------------------- /src/common/utils/storage.ts: -------------------------------------------------------------------------------- 1 | import { StateStorage } from 'zustand/middleware' 2 | 3 | import { mmkv } from './mmvk' 4 | 5 | const KEYS = { 6 | TOKEN: '@TOKEN', 7 | THEME: '@THEME', 8 | } 9 | 10 | export const MMKVStorage: StateStorage = { 11 | getItem: (name: string) => mmkv.getString(name) || null, 12 | setItem: (name: string, value: string) => mmkv.set(name, value), 13 | removeItem: (name: string) => mmkv.delete(name), 14 | } 15 | 16 | const storage = { 17 | saveAccessToken: (data: string) => { 18 | mmkv.set(KEYS.TOKEN, data) 19 | }, 20 | getAccessToken: () => { 21 | return mmkv.getString(KEYS.TOKEN) 22 | }, 23 | } 24 | 25 | export default storage 26 | -------------------------------------------------------------------------------- /src/components/forms/index.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /src/components/modals/confirm/confirm.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useCallback } from 'react' 2 | 3 | import { borderRadiusSizes, WIDTH } from '@/common/constants' 4 | import { hideCustomDialog } from '@/common/utils/dialog' 5 | import { Button, Col, Row, Space, Text } from '@/components/widgets' 6 | 7 | type Props = { 8 | onClose?: () => void 9 | onConfirm?: () => void 10 | title: string 11 | content: string 12 | labelConfirm?: string 13 | labelCancel?: string 14 | } 15 | 16 | export const ConfirmModal: React.FC = memo(function ({ 17 | onClose, 18 | onConfirm, 19 | title, 20 | content, 21 | labelCancel = 'Cancel', 22 | labelConfirm = 'OK', 23 | }) { 24 | const handleClose = useCallback(() => { 25 | onClose?.() 26 | hideCustomDialog() 27 | }, [onClose]) 28 | 29 | const handleConfirm = useCallback(() => { 30 | onConfirm?.() 31 | hideCustomDialog() 32 | }, [onConfirm]) 33 | 34 | return ( 35 | 40 | 41 | 42 | {title} 43 | 44 | 45 | 46 | 47 | {content} 48 | 49 | 50 | 51 | 61 | 62 | 65 | 66 | 67 | ) 68 | }) 69 | -------------------------------------------------------------------------------- /src/components/modals/confirm/index.ts: -------------------------------------------------------------------------------- 1 | export * from './confirm' 2 | -------------------------------------------------------------------------------- /src/components/modals/index.ts: -------------------------------------------------------------------------------- 1 | export * from './confirm' 2 | -------------------------------------------------------------------------------- /src/components/widgets/align/align.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Box } from '../box' 4 | import { ViewProps } from '@/common/types' 5 | 6 | const Alignment = { 7 | bottomCenter: { 8 | justifyContent: 'flex-end', 9 | alignItems: 'center', 10 | }, 11 | bottomLeft: { 12 | justifyContent: 'flex-end', 13 | alignItems: 'flex-start', 14 | }, 15 | bottomRight: { 16 | justifyContent: 'flex-end', 17 | alignItems: 'flex-end', 18 | }, 19 | center: { 20 | justifyContent: 'center', 21 | alignItems: 'center', 22 | }, 23 | centerLeft: { 24 | justifyContent: 'center', 25 | alignItems: 'flex-start', 26 | }, 27 | centerRight: { 28 | justifyContent: 'center', 29 | alignItems: 'flex-end', 30 | }, 31 | topCenter: { 32 | justifyContent: 'flex-start', 33 | alignItems: 'center', 34 | }, 35 | topLeft: { 36 | justifyContent: 'flex-start', 37 | alignItems: 'flex-start', 38 | }, 39 | topRight: { 40 | justifyContent: 'flex-start', 41 | alignItems: 'flex-end', 42 | }, 43 | } 44 | 45 | type Props = { 46 | alignment?: keyof typeof Alignment 47 | } 48 | 49 | export const Align: React.FC = function ({ 50 | children, 51 | alignment = 'center', 52 | ...props 53 | }) { 54 | return ( 55 | 56 | {children} 57 | 58 | ) 59 | } 60 | -------------------------------------------------------------------------------- /src/components/widgets/align/index.ts: -------------------------------------------------------------------------------- 1 | export * from './align' 2 | -------------------------------------------------------------------------------- /src/components/widgets/app-bar/app-bar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react' 2 | import { useSafeAreaInsets } from 'react-native-safe-area-context' 3 | 4 | import { Center } from '../center' 5 | import { Col } from '../col' 6 | import { Row } from '../row' 7 | import { Space } from '../space' 8 | import { Text } from '../text' 9 | import { shadows, WIDTH } from '@/common/constants' 10 | import { ViewProps } from '@/common/types' 11 | 12 | type Props = { 13 | leading?: React.ReactNode 14 | actions?: React.ReactNode 15 | title?: string 16 | safe?: boolean 17 | } 18 | 19 | export const AppBar: React.FC = function ({ leading, actions, title, safe }) { 20 | const insets = useSafeAreaInsets() 21 | const topSpace = useMemo(() => (!safe ? insets.top : 0), [insets.top, safe]) 22 | return ( 23 | 29 | 30 | 31 | {leading &&

{leading}
} 32 |
33 | 34 | {title} 35 | 36 |
37 | {actions && {actions}} 38 | 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/components/widgets/app-bar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app-bar' 2 | -------------------------------------------------------------------------------- /src/components/widgets/box/box.tsx: -------------------------------------------------------------------------------- 1 | import { createBox } from '@shopify/restyle' 2 | 3 | import { Theme } from '@/common/themes' 4 | 5 | export const Box = createBox() 6 | -------------------------------------------------------------------------------- /src/components/widgets/box/index.ts: -------------------------------------------------------------------------------- 1 | export * from './box' 2 | -------------------------------------------------------------------------------- /src/components/widgets/button/button.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react' 2 | import { StyleProp, TouchableOpacity, ViewStyle } from 'react-native' 3 | 4 | import { MotiPressable } from 'moti/interactions' 5 | 6 | import { Center } from '../center' 7 | import { Text } from '../text' 8 | import { borderRadiusSizes } from '@/common/constants' 9 | import { ColorName, TypoName } from '@/common/themes' 10 | import { palette } from '@/common/themes/palette' 11 | import { ViewProps } from '@/common/types' 12 | 13 | const styleDefault: ViewStyle = { 14 | padding: 8, 15 | paddingHorizontal: 16, 16 | borderRadius: borderRadiusSizes.xl, 17 | } 18 | 19 | const SHADOW: ViewStyle = { 20 | elevation: 5, 21 | shadowColor: palette.black, 22 | shadowOffset: { width: 0, height: 0 }, 23 | shadowOpacity: 0.4, 24 | shadowRadius: 10, 25 | } 26 | 27 | type ButtonProps = { 28 | onPress?: () => void 29 | isModal?: boolean 30 | style?: StyleProp 31 | labelColor?: ColorName 32 | shadow?: boolean 33 | labelVariant?: TypoName 34 | custom?: boolean 35 | disabled?: boolean 36 | } 37 | 38 | export const Button: React.FC = function ({ 39 | children, 40 | style = {}, 41 | onPress, 42 | isModal = false, 43 | labelColor = 'primary', 44 | shadow = false, 45 | labelVariant = 'normal', 46 | custom = false, 47 | disabled = false, 48 | ...props 49 | }) { 50 | if (isModal) { 51 | return ( 52 | 53 |
54 | {typeof children === 'string' ? ( 55 | 56 | {children} 57 | 58 | ) : ( 59 | children 60 | )} 61 |
62 |
63 | ) 64 | } 65 | return ( 66 | 72 | ({ hovered, pressed }) => { 73 | 'worklet' 74 | 75 | return { 76 | opacity: hovered || pressed ? 0.6 : 1, 77 | } 78 | }, 79 | [], 80 | )}> 81 |
82 | {typeof children === 'string' ? ( 83 | 84 | {children} 85 | 86 | ) : ( 87 | children 88 | )} 89 |
90 |
91 | ) 92 | } 93 | -------------------------------------------------------------------------------- /src/components/widgets/button/index.ts: -------------------------------------------------------------------------------- 1 | export * from './button' 2 | -------------------------------------------------------------------------------- /src/components/widgets/card/card.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Box } from '../box' 4 | import { borderRadiusSizes } from '@/common/constants' 5 | import { ViewProps } from '@/common/types' 6 | 7 | export const Card: React.FC = function (props) { 8 | return ( 9 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/widgets/card/index.ts: -------------------------------------------------------------------------------- 1 | export * from './card' 2 | -------------------------------------------------------------------------------- /src/components/widgets/center/center.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Box } from '../box' 4 | import { ViewProps } from '@/common/types' 5 | 6 | export interface CenterProps { 7 | horizontal?: boolean 8 | vertical?: boolean 9 | } 10 | 11 | export const Center: React.FC = function ({ 12 | horizontal = false, 13 | vertical = false, 14 | children = null, 15 | ...props 16 | }) { 17 | return ( 18 | 24 | {children} 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/components/widgets/center/index.ts: -------------------------------------------------------------------------------- 1 | export * from './center' 2 | -------------------------------------------------------------------------------- /src/components/widgets/col/col.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Box } from '../box' 4 | import { ViewProps } from '@/common/types' 5 | 6 | export const Col: React.FC = function ({ children, ...props }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/components/widgets/col/index.ts: -------------------------------------------------------------------------------- 1 | export * from './col' 2 | -------------------------------------------------------------------------------- /src/components/widgets/fade-view/fade-view.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Animated, { 3 | FadeIn, 4 | FadeInDown, 5 | FadeInLeft, 6 | FadeInRight, 7 | FadeInUp, 8 | FadeOut, 9 | FadeOutRight, 10 | SlideInLeft, 11 | SlideOutRight, 12 | } from 'react-native-reanimated' 13 | 14 | import { ViewProps } from '@/common/types' 15 | 16 | type Props = { 17 | // components's props 18 | children: React.ReactNode 19 | type?: 'left' | 'right' | 'bottom' | 'slideLeft' | 'fade' 20 | } 21 | 22 | /** 23 | * Describe your component here 24 | */ 25 | 26 | export const FadeView: React.FC = function ({ 27 | children, 28 | type = 'fade', 29 | ...props 30 | }) { 31 | const entering = React.useMemo(() => { 32 | const anims = { 33 | fade: FadeIn, 34 | left: FadeInLeft, 35 | right: FadeInRight, 36 | bottom: FadeInUp, 37 | slideLeft: SlideInLeft, 38 | } 39 | return anims?.[type] 40 | }, [type]) 41 | 42 | const exiting = React.useMemo(() => { 43 | const anims = { 44 | left: FadeOutRight, 45 | right: FadeOut, 46 | bottom: FadeInDown, 47 | slideLeft: SlideOutRight, 48 | fade: FadeOut, 49 | } 50 | return anims?.[type] 51 | }, [type]) 52 | 53 | return ( 54 | 55 | {children} 56 | 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /src/components/widgets/fade-view/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fade-view' 2 | -------------------------------------------------------------------------------- /src/components/widgets/if/if.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | type Props = { 4 | condition: boolean 5 | component: React.ReactElement 6 | fallback?: React.ReactElement 7 | } 8 | 9 | export const If: React.FC = props => { 10 | const { condition, component, fallback = null } = props 11 | return condition ? component : fallback 12 | } 13 | -------------------------------------------------------------------------------- /src/components/widgets/if/index.ts: -------------------------------------------------------------------------------- 1 | export * from './if' 2 | -------------------------------------------------------------------------------- /src/components/widgets/image/image.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import { 3 | ImageResizeMode, 4 | ImageSourcePropType, 5 | ImageStyle, 6 | StyleProp, 7 | ViewStyle, 8 | } from 'react-native' 9 | import Reanimated, { 10 | Easing, 11 | useAnimatedStyle, 12 | useSharedValue, 13 | withTiming, 14 | } from 'react-native-reanimated' 15 | 16 | import { Box } from '../box' 17 | 18 | type Props = { 19 | style?: StyleProp 20 | source: ImageSourcePropType 21 | resizeMode?: ImageResizeMode 22 | } 23 | 24 | const IMAGE: ImageStyle = { 25 | width: undefined, 26 | height: undefined, 27 | flex: 1, 28 | } 29 | 30 | export const Image: React.FC = function (props) { 31 | const { style, source, resizeMode = 'contain', ...rest } = props 32 | const x = useSharedValue(0) 33 | 34 | const opacity = useAnimatedStyle(() => ({ 35 | opacity: x.value, 36 | })) 37 | 38 | const handleOnLayout = useCallback(() => { 39 | x.value = withTiming(1, { 40 | duration: 1200, 41 | easing: Easing.in(Easing.ease), 42 | }) 43 | }, [x]) 44 | 45 | return ( 46 | 47 | 53 | 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/components/widgets/image/index.ts: -------------------------------------------------------------------------------- 1 | export * from './image' 2 | -------------------------------------------------------------------------------- /src/components/widgets/index.ts: -------------------------------------------------------------------------------- 1 | export * from './align' 2 | export * from './app-bar' 3 | export * from './box' 4 | export * from './button' 5 | export * from './card' 6 | export * from './center' 7 | export * from './col' 8 | export * from './if' 9 | export * from './image' 10 | export * from './positioned' 11 | export * from './row' 12 | export * from './screen' 13 | export * from './space' 14 | export * from './spinner' 15 | export * from './stack' 16 | export * from './switch' 17 | export * from './text' 18 | export * from './text-button' 19 | export * from './text-input' 20 | export * from './touchable' 21 | export * from './wrap' 22 | export * from './moti-color' 23 | export * from './fade-view' 24 | -------------------------------------------------------------------------------- /src/components/widgets/input-field/index.ts: -------------------------------------------------------------------------------- 1 | export * from './input-field' 2 | -------------------------------------------------------------------------------- /src/components/widgets/input-field/input-field.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Control, Controller } from 'react-hook-form' 3 | import { ViewStyle } from 'react-native' 4 | 5 | import { TextProps } from '@shopify/restyle' 6 | 7 | import { borderRadiusSizes } from '@/common/constants' 8 | import { ColorName, spacing, Theme, useTheme } from '@/common/themes' 9 | import { Row, Space, Text, TextInput } from '@/components/widgets' 10 | 11 | type InputFieldProps = { 12 | control?: Control 13 | error?: string | undefined 14 | name: string 15 | styleContainer?: ViewStyle 16 | colorText?: ColorName 17 | } 18 | 19 | const INPUT: ViewStyle = { 20 | width: '100%', 21 | borderWidth: 1, 22 | borderRadius: borderRadiusSizes.small, 23 | height: 56, 24 | paddingHorizontal: spacing.small, 25 | } 26 | 27 | export const InputField: React.FC< 28 | InputFieldProps & React.ComponentProps & TextProps 29 | > = function ({ control, error, name, styleContainer = {}, colorText = 'text.default', ...props }) { 30 | const { colors } = useTheme() 31 | return ( 32 | <> 33 | ( 39 | 40 | 47 | 48 | )} 49 | name={name} 50 | /> 51 | 52 | {!!error && {error}} 53 | 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/components/widgets/moti-color/index.ts: -------------------------------------------------------------------------------- 1 | export * from './moti-color' 2 | -------------------------------------------------------------------------------- /src/components/widgets/moti-color/moti-color.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleProp, ViewStyle } from 'react-native' 3 | 4 | import { MotiView } from 'moti' 5 | 6 | import { transitions } from '@/common/constants' 7 | import { ColorName, useTheme } from '@/common/themes' 8 | import { ViewProps } from '@/common/types' 9 | 10 | type Props = { 11 | // components's props 12 | backgroundColor: ColorName 13 | children?: React.ReactNode 14 | style?: StyleProp 15 | } 16 | 17 | /** 18 | * Describe your component here 19 | */ 20 | 21 | export const MotiColor: React.FC = function ({ 22 | children, 23 | backgroundColor, 24 | style, 25 | }: Props) { 26 | const theme = useTheme() 27 | return ( 28 | 37 | {children} 38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/components/widgets/positioned/index.ts: -------------------------------------------------------------------------------- 1 | export * from './positioned' 2 | -------------------------------------------------------------------------------- /src/components/widgets/positioned/positioned.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Box } from '../box' 4 | import { ViewProps } from '@/common/types' 5 | 6 | export const Positioned: React.FC = function ({ children, ...props }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/components/widgets/row/index.ts: -------------------------------------------------------------------------------- 1 | export * from './row' 2 | -------------------------------------------------------------------------------- /src/components/widgets/row/row.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Box } from '../box' 4 | import { ViewProps } from '@/common/types' 5 | 6 | export const Row: React.FC = function ({ children, ...props }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/components/widgets/screen/index.ts: -------------------------------------------------------------------------------- 1 | export * from './screen' 2 | -------------------------------------------------------------------------------- /src/components/widgets/screen/screen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SafeAreaView } from 'react-native-safe-area-context' 3 | 4 | import { MotiColor } from '../moti-color' 5 | import { styles } from '@/common/constants' 6 | import { useTheme } from '@/common/themes' 7 | 8 | type LayoutProps = { 9 | children: React.ReactNode 10 | safe?: boolean 11 | } 12 | 13 | export const Screen: React.FC = function ({ children, safe = false }) { 14 | const { colors } = useTheme() 15 | if (!safe) 16 | return ( 17 | 18 | {children} 19 | 20 | ) 21 | return ( 22 | 23 | 24 | {children} 25 | 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/components/widgets/space/index.ts: -------------------------------------------------------------------------------- 1 | export * from './space' 2 | -------------------------------------------------------------------------------- /src/components/widgets/space/space.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { FlexAlignType, StyleProp, ViewStyle } from 'react-native' 3 | 4 | import { Box } from '../box/box' 5 | import { ViewProps } from '@/common/types' 6 | 7 | export interface SpaceProps { 8 | style?: StyleProp 9 | height?: number | string 10 | width?: number | string 11 | align?: FlexAlignType 12 | } 13 | 14 | export const Space: React.FC = function ({ 15 | style = {}, 16 | height = 0, 17 | width = '100%', 18 | align = 'flex-start', 19 | ...props 20 | }) { 21 | const styles: any = [style, { height, width }] 22 | 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /src/components/widgets/spinner/index.ts: -------------------------------------------------------------------------------- 1 | export * from './spinner' 2 | -------------------------------------------------------------------------------- /src/components/widgets/spinner/spinner.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | import React from 'react' 3 | 4 | import Lottie from 'lottie-react-native' 5 | 6 | import { Center } from '../center' 7 | 8 | export const Spinner: React.FC = function () { 9 | return ( 10 |
11 | 20 |
21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/components/widgets/stack/index.ts: -------------------------------------------------------------------------------- 1 | export * from './stack' 2 | -------------------------------------------------------------------------------- /src/components/widgets/stack/stack.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Box } from '../box' 4 | import { ViewProps } from '@/common/types' 5 | 6 | export const Stack: React.FC = function ({ children, ...props }) { 7 | return ( 8 | 9 | {React.Children.map(children, (child, index) => { 10 | const length = Array.isArray(children) ? children.length : 0 11 | return React.cloneElement(child as React.ReactElement, { 12 | position: 'absolute', 13 | zIndex: length - index, 14 | }) 15 | })} 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/components/widgets/switch/index.ts: -------------------------------------------------------------------------------- 1 | export * from './switch' 2 | -------------------------------------------------------------------------------- /src/components/widgets/switch/switch.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react' 2 | import { Pressable, View } from 'react-native' 3 | import { Easing } from 'react-native-reanimated' 4 | 5 | import { MotiTransitionProp, MotiView } from 'moti' 6 | 7 | import { MotiColor } from '../moti-color' 8 | import { useTheme } from '@/common/themes' 9 | 10 | type SwitchProps = { 11 | isActive?: boolean 12 | onPress?: () => void 13 | size: number 14 | } 15 | 16 | const transition: MotiTransitionProp = { 17 | type: 'timing', 18 | duration: 300, 19 | easing: Easing.inOut(Easing.ease), 20 | } 21 | 22 | export const Switch: React.FC = function ({ isActive, onPress, size }) { 23 | const trackWitch = useMemo(() => { 24 | return size 25 | }, [size]) 26 | 27 | const trackHeight = useMemo(() => { 28 | return size * 0.5 29 | }, [size]) 30 | 31 | const { colors } = useTheme() 32 | 33 | return ( 34 | 35 | ({ 38 | alignItems: 'center', 39 | justifyContent: 'center', 40 | width: size, 41 | }), 42 | [size], 43 | )}> 44 | ({ 48 | width: trackWitch, 49 | height: trackHeight, 50 | borderRadius: trackHeight / 2, 51 | borderWidth: 1, 52 | borderColor: colors['background.switch'], 53 | }), 54 | [colors, trackHeight, trackWitch], 55 | )} 56 | /> 57 | ({ 61 | translateX: isActive ? trackWitch * 0.25 : -trackWitch * 0.25, 62 | }), 63 | [isActive, trackWitch], 64 | )} 65 | style={useMemo( 66 | () => ({ 67 | position: 'absolute', 68 | width: trackHeight * 0.8, 69 | height: trackHeight * 0.8, 70 | borderRadius: (trackHeight * 0.8) / 2, 71 | backgroundColor: colors['background.switchTrack'], 72 | }), 73 | [colors, trackHeight], 74 | )} 75 | /> 76 | 77 | 78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /src/components/widgets/text-button/index.ts: -------------------------------------------------------------------------------- 1 | export * from './text-button' 2 | -------------------------------------------------------------------------------- /src/components/widgets/text-button/text-button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Text } from '../text/text' 4 | import { Touchable } from '../touchable' 5 | import { ColorName, TypoName } from '@/common/themes' 6 | 7 | type Props = { 8 | label: string 9 | variant?: TypoName 10 | color?: ColorName 11 | onPress: () => void 12 | } 13 | 14 | export const TextButton: React.FC = function ({ 15 | label, 16 | variant = 'normal', 17 | color = 'text.default', 18 | onPress, 19 | }) { 20 | return ( 21 | 22 | 23 | {label} 24 | 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/components/widgets/text-input/index.ts: -------------------------------------------------------------------------------- 1 | export * from './text-input' 2 | -------------------------------------------------------------------------------- /src/components/widgets/text-input/text-input.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TextInput as RNTextInput } from 'react-native' 3 | 4 | import { createRestyleComponent, createVariant, VariantProps } from '@shopify/restyle' 5 | 6 | import { Theme } from '@/common/themes' 7 | 8 | const variant = createVariant({ 9 | themeKey: 'textInputVariants', 10 | }) 11 | 12 | export const TextInput = createRestyleComponent< 13 | VariantProps & React.ComponentProps, 14 | Theme 15 | >([variant], RNTextInput) 16 | -------------------------------------------------------------------------------- /src/components/widgets/text/index.ts: -------------------------------------------------------------------------------- 1 | export * from './text' 2 | -------------------------------------------------------------------------------- /src/components/widgets/text/text.tsx: -------------------------------------------------------------------------------- 1 | import { createText } from '@shopify/restyle' 2 | 3 | import { Theme } from '@/common/themes' 4 | 5 | export const Text = createText() 6 | -------------------------------------------------------------------------------- /src/components/widgets/touchable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './touchable' 2 | -------------------------------------------------------------------------------- /src/components/widgets/touchable/touchable.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Insets, TouchableOpacity } from 'react-native' 3 | 4 | type Props = { 5 | children?: React.ReactNode 6 | onPress?: () => void 7 | hitSlop?: Insets 8 | } 9 | 10 | export const Touchable: React.FC = function ({ onPress, children, hitSlop }) { 11 | return ( 12 | 13 | {children} 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/components/widgets/wrap/index.ts: -------------------------------------------------------------------------------- 1 | export * from './wrap' 2 | -------------------------------------------------------------------------------- /src/components/widgets/wrap/wrap.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Box } from '../box' 4 | import { ViewProps } from '@/common/types' 5 | 6 | type Props = { 7 | children: React.ReactNode 8 | } 9 | 10 | export const Wrap: React.FC = function ({ children, ...props }) { 11 | return ( 12 | 13 | {children} 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/localization/en/auth.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | username: 'Username', 3 | password: 'Password', 4 | login: 'Login', 5 | register: 'Register', 6 | enter_email: 'Enter your email', 7 | enter_password: 'Enter your password', 8 | enter_confirm_password: 'Enter your confirm password', 9 | } 10 | -------------------------------------------------------------------------------- /src/localization/en/common.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | welcome: 'Welcome', 3 | change_language: 'Change Language', 4 | } 5 | -------------------------------------------------------------------------------- /src/localization/en/index.ts: -------------------------------------------------------------------------------- 1 | import auth from './auth' 2 | import common from './common' 3 | import navigate from './navigate' 4 | 5 | export default { 6 | common, 7 | auth, 8 | navigate, 9 | } 10 | -------------------------------------------------------------------------------- /src/localization/en/navigate.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | login: 'Login', 3 | register: 'Register', 4 | } 5 | -------------------------------------------------------------------------------- /src/localization/i18n.ts: -------------------------------------------------------------------------------- 1 | import { initReactI18next } from 'react-i18next' 2 | 3 | import i18n from 'i18next' 4 | 5 | import en from './en' 6 | import vi from './vi' 7 | 8 | const resources = { 9 | en, 10 | vi, 11 | } 12 | 13 | i18n.use(initReactI18next).init({ 14 | compatibilityJSON: 'v3', 15 | resources, 16 | lng: 'en', 17 | interpolation: { 18 | escapeValue: false, 19 | }, 20 | }) 21 | 22 | export default i18n 23 | -------------------------------------------------------------------------------- /src/localization/language.ts: -------------------------------------------------------------------------------- 1 | export const language: { [key: string]: string } = { 2 | english: 'English', 3 | vietnam: 'Việt Nam', 4 | } 5 | -------------------------------------------------------------------------------- /src/localization/vi/auth.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | username: 'Tên người dùng', 3 | password: 'Mật khẩu', 4 | login: 'Đăng nhập', 5 | register: 'Đăng ký', 6 | enter_email: 'Nhập email của bạn', 7 | enter_password: 'Nhập mật khẩu của bạn', 8 | enter_confirm_password: 'Nhập lại mật khẩu của bạn', 9 | } 10 | -------------------------------------------------------------------------------- /src/localization/vi/common.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | welcome: 'Chào mừng', 3 | change_language: 'Thay đổi ngôn ngữ', 4 | } 5 | -------------------------------------------------------------------------------- /src/localization/vi/index.ts: -------------------------------------------------------------------------------- 1 | import auth from './auth' 2 | import common from './common' 3 | import navigate from './navigate' 4 | 5 | export default { 6 | common, 7 | auth, 8 | navigate, 9 | } 10 | -------------------------------------------------------------------------------- /src/localization/vi/navigate.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | login: 'Đăng nhập', 3 | register: 'Đăng ký', 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/auth/api/auth.ts: -------------------------------------------------------------------------------- 1 | import { LoginPayload, RegisterPayload } from '../types' 2 | import { UserResponse } from '../types/api' 3 | import { UserModel } from '../types/models' 4 | import { ResponseApi } from '@/common/types/api' 5 | import { axios } from '@/common/utils/axios' 6 | import { wrapApiCall } from '@/common/utils/helpers' 7 | 8 | export const getUserApi = async (): Promise> => { 9 | return wrapApiCall(() => axios.get('/auth/me')) 10 | } 11 | 12 | export const registerWithEmailAndPasswordApi = ( 13 | data: RegisterPayload, 14 | ): Promise> => { 15 | return wrapApiCall(() => axios.post('/auth/register', data)) 16 | } 17 | 18 | export const loginWithEmailAndPasswordApi = async ( 19 | data: LoginPayload, 20 | ): Promise> => { 21 | return wrapApiCall(() => axios.post('/auth/login', data)) 22 | } 23 | -------------------------------------------------------------------------------- /src/modules/auth/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth' 2 | -------------------------------------------------------------------------------- /src/modules/auth/assets/index.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /src/modules/auth/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './layout' 2 | export * from './login-form' 3 | export * from './register-form' 4 | -------------------------------------------------------------------------------- /src/modules/auth/components/layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './layout' 2 | -------------------------------------------------------------------------------- /src/modules/auth/components/layout/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { useThemeStore } from '@/common/stores' 4 | import { ChildrenProps } from '@/common/types' 5 | import { Center, Row, Screen, Space, Switch, Text } from '@/components/widgets' 6 | 7 | type AuthLayoutProps = { 8 | title?: string 9 | isShowToggleDarkMode?: boolean 10 | safe?: boolean 11 | } 12 | 13 | export const AuthLayout: React.FC = function ({ 14 | children, 15 | title, 16 | isShowToggleDarkMode, 17 | safe = false, 18 | }) { 19 | const { isDarkMode, toggleMode } = useThemeStore() 20 | return ( 21 | 22 | {isShowToggleDarkMode && ( 23 | 24 | 25 | 26 | )} 27 |
28 | {title} 29 | 30 | {children} 31 |
32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/modules/auth/components/login-form/index.ts: -------------------------------------------------------------------------------- 1 | export * from './login-form' 2 | -------------------------------------------------------------------------------- /src/modules/auth/components/login-form/login-form.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | 3 | import { useNavigation } from '@react-navigation/native' 4 | 5 | import { useAuth } from '../../hooks' 6 | import { useLoginForm } from './useLoginForm' 7 | import { useTranslation } from '@/common/hooks' 8 | import { useThemeStore } from '@/common/stores' 9 | import { useTheme } from '@/common/themes' 10 | import { Button, Col, Space } from '@/components/widgets' 11 | import { InputField } from '@/components/widgets/input-field' 12 | import { AppNavigationProp } from '@/routes' 13 | 14 | export const LoginForm: React.FC = function () { 15 | const navigation = useNavigation() 16 | const { login, goToRegister } = useAuth() 17 | const { t } = useTranslation() 18 | const isDarkMode = useThemeStore(state => state.isDarkMode) 19 | const { colors } = useTheme() 20 | 21 | const { control, handleSubmit, errors } = useLoginForm() 22 | 23 | useEffect(() => { 24 | navigation.setOptions({ 25 | headerTitle: t('navigate:login'), 26 | }) 27 | }, [navigation, t]) 28 | 29 | return ( 30 | 31 | 40 | 41 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 61 | 62 | 63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /src/modules/auth/components/login-form/useLoginForm.ts: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form' 2 | 3 | import { yupResolver } from '@hookform/resolvers/yup' 4 | import * as yup from 'yup' 5 | 6 | import { LoginPayload } from '../../types' 7 | 8 | const schema = yup.object({ 9 | email: yup.string().required('Required'), 10 | password: yup.string().required('Required'), 11 | }) 12 | 13 | export const useLoginForm = () => { 14 | const { 15 | control, 16 | handleSubmit, 17 | formState: { errors }, 18 | } = useForm({ 19 | defaultValues: { 20 | email: '', 21 | password: '', 22 | }, 23 | resolver: yupResolver(schema), 24 | }) 25 | 26 | return { 27 | control, 28 | handleSubmit, 29 | errors, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/modules/auth/components/register-form/index.ts: -------------------------------------------------------------------------------- 1 | export * from './register-form' 2 | -------------------------------------------------------------------------------- /src/modules/auth/components/register-form/register-form.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect } from 'react' 2 | 3 | import { useNavigation } from '@react-navigation/native' 4 | 5 | import { useAuth } from '../../hooks' 6 | import { useRegisterForm } from './useRegisterForm' 7 | import { useTranslation } from '@/common/hooks' 8 | import { useThemeStore } from '@/common/stores' 9 | import { useTheme } from '@/common/themes' 10 | import { Button, Col, Space } from '@/components/widgets' 11 | import { InputField } from '@/components/widgets/input-field' 12 | import { AppNavigationProp } from '@/routes' 13 | 14 | export const RegisterForm = function () { 15 | const navigation = useNavigation() 16 | const { register, goToLogin } = useAuth() 17 | const { t } = useTranslation() 18 | const isDarkMode = useThemeStore(state => state.isDarkMode) 19 | const { colors } = useTheme() 20 | 21 | const { control, handleSubmit, errors } = useRegisterForm() 22 | 23 | useEffect(() => { 24 | navigation.setOptions({ 25 | headerTitle: t('navigate:register'), 26 | }) 27 | }, [navigation, t]) 28 | 29 | const onSubmit = useCallback( 30 | (/* data: FormData */) => { 31 | register() 32 | }, 33 | [register], 34 | ) 35 | 36 | return ( 37 | 38 | 47 | 48 | 57 | 58 | 67 | 68 | 69 | 72 | 73 | 74 | 75 | 78 | 79 | 80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /src/modules/auth/components/register-form/useRegisterForm.ts: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form' 2 | 3 | import { yupResolver } from '@hookform/resolvers/yup' 4 | import * as yup from 'yup' 5 | 6 | type FormData = { 7 | email: string 8 | password: string 9 | confirmPassword: string 10 | } 11 | 12 | const schema = yup.object({ 13 | email: yup.string().email().required('Required'), 14 | password: yup.string().required('Required'), 15 | confirmPassword: yup 16 | .string() 17 | .required('Required') 18 | .oneOf([yup.ref('password'), null], 'Passwords must match'), 19 | }) 20 | 21 | export const useRegisterForm = () => { 22 | const { 23 | control, 24 | handleSubmit, 25 | formState: { errors }, 26 | } = useForm({ 27 | defaultValues: { 28 | email: '', 29 | password: '', 30 | confirmPassword: '', 31 | }, 32 | resolver: yupResolver(schema), 33 | }) 34 | return { control, handleSubmit, errors } 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/auth/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useAuth' 2 | -------------------------------------------------------------------------------- /src/modules/auth/hooks/useAuth.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react' 2 | import { Keyboard } from 'react-native' 3 | 4 | import { useNavigation } from '@react-navigation/native' 5 | 6 | import { useAuthStore } from '../stores' 7 | import { LoginPayload } from '../types' 8 | import { AppNavigationProp } from '@/routes' 9 | import { RouterName } from '@/routes/router-name' 10 | 11 | export const useAuth = () => { 12 | const navigation = useNavigation() 13 | const { loginAction, registerAction } = useAuthStore() 14 | 15 | const goToLogin = useCallback(() => { 16 | navigation.navigate(RouterName.login) 17 | }, [navigation]) 18 | 19 | const goToRegister = useCallback(() => { 20 | navigation.navigate(RouterName.register) 21 | }, [navigation]) 22 | 23 | const login = useCallback( 24 | (data: LoginPayload) => { 25 | Keyboard.dismiss() 26 | loginAction(data) 27 | }, 28 | [loginAction], 29 | ) 30 | 31 | const register = useCallback(async () => { 32 | await registerAction() 33 | navigation.navigate(RouterName.login) 34 | }, [registerAction, navigation]) 35 | 36 | return { goToLogin, goToRegister, login, register } 37 | } 38 | -------------------------------------------------------------------------------- /src/modules/auth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './routes' 2 | -------------------------------------------------------------------------------- /src/modules/auth/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { 4 | createStackNavigator, 5 | StackNavigationOptions, 6 | TransitionPresets, 7 | } from '@react-navigation/stack' 8 | 9 | import { Landing, Login, Register } from '../screens' 10 | import { RouterName } from '@/routes/router-name' 11 | 12 | export type AuthStackParamList = { 13 | register: undefined 14 | login: undefined 15 | landing: undefined 16 | } 17 | 18 | const options: StackNavigationOptions = { 19 | headerShown: true, 20 | ...TransitionPresets.SlideFromRightIOS, 21 | } 22 | 23 | const Stack = createStackNavigator() 24 | 25 | export const AuthRoutes: React.FC = function () { 26 | return ( 27 | 28 | 29 | 30 | 37 | 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/auth/screens/index.ts: -------------------------------------------------------------------------------- 1 | export * from './landing' 2 | export * from './login' 3 | export * from './register' 4 | -------------------------------------------------------------------------------- /src/modules/auth/screens/landing.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useMemo, useState } from 'react' 2 | 3 | import { useNavigation } from '@react-navigation/native' 4 | 5 | import { AuthLayout } from '../components/layout/layout' 6 | import { useAuth } from '../hooks' 7 | import { APP_NAME } from '@/common/config' 8 | import { useTranslation } from '@/common/hooks' 9 | import { Button, Space, Text } from '@/components/widgets' 10 | import { language } from '@/localization/language' 11 | import { AppNavigationProp } from '@/routes' 12 | 13 | const languages = [ 14 | // Language List 15 | { code: 'en', label: language.english }, 16 | { code: 'vi', label: language.vietnam }, 17 | ] 18 | 19 | export const Landing: React.FC = function () { 20 | const navigation = useNavigation() 21 | const { goToLogin, goToRegister } = useAuth() 22 | 23 | useEffect(() => { 24 | navigation.setOptions({ 25 | headerTitle: '', 26 | }) 27 | }, [navigation]) 28 | 29 | const { t, i18n } = useTranslation() 30 | const [lang, setLang] = useState(i18n.language) 31 | 32 | const languagesSelector = useMemo( 33 | () => ( 34 | <> 35 | {languages.map(currentLang => { 36 | const selectedLanguage = currentLang.code === lang 37 | return ( 38 | { 43 | setLang(currentLang.code) 44 | i18n.changeLanguage(currentLang.code) // it will change the language through out the app. 45 | }}> 46 | {currentLang.label} 47 | 48 | ) 49 | })} 50 | 51 | ), 52 | [i18n, lang], 53 | ) 54 | 55 | return ( 56 | 57 | 60 | 61 | 64 | 65 | {languagesSelector} 66 | 67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /src/modules/auth/screens/login.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { AuthLayout, LoginForm } from '../components' 4 | import { useTranslation } from '@/common/hooks' 5 | 6 | export const Login: React.FC = function () { 7 | const { t } = useTranslation() 8 | return ( 9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/auth/screens/register.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { AuthLayout, RegisterForm } from '../components' 4 | import { useTranslation } from '@/common/hooks' 5 | 6 | export const Register = function () { 7 | const { t } = useTranslation() 8 | return ( 9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/auth/stores/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useAuthStore' 2 | -------------------------------------------------------------------------------- /src/modules/auth/stores/useAuthStore.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | 3 | import { loginWithEmailAndPasswordApi } from '../api' 4 | import { LoginPayload } from '../types' 5 | import { UserModel } from '../types/models' 6 | import { hideLoading, showLoading } from '@/common/utils/dialog' 7 | import { delay } from '@/common/utils/helpers' 8 | import storage from '@/common/utils/storage' 9 | 10 | type State = { 11 | data: UserModel | undefined 12 | isLoggedIn: boolean 13 | } 14 | 15 | type Action = { 16 | registerAction: () => Promise 17 | checkLoggedInAction: () => void 18 | loginAction: (data: LoginPayload) => Promise 19 | logOutAction: () => Promise 20 | } 21 | 22 | export const useAuthStore = create(set => ({ 23 | isLoggedIn: false, 24 | data: undefined, 25 | loginAction: async data => { 26 | showLoading() 27 | const resp = await loginWithEmailAndPasswordApi(data) 28 | resp.ok = true 29 | if (resp.ok) { 30 | storage.saveAccessToken('token') 31 | set(() => ({ 32 | isLoggedIn: true, 33 | data: resp.data?.user, 34 | })) 35 | } 36 | hideLoading() 37 | }, 38 | registerAction: async () => { 39 | await delay(3000) 40 | }, 41 | checkLoggedInAction: () => { 42 | set({ 43 | isLoggedIn: !!storage.getAccessToken(), 44 | }) 45 | }, 46 | logOutAction: async () => { 47 | showLoading() 48 | await delay(1000) 49 | storage.saveAccessToken('') 50 | set({ 51 | isLoggedIn: false, 52 | data: undefined, 53 | }) 54 | hideLoading() 55 | }, 56 | })) 57 | -------------------------------------------------------------------------------- /src/modules/auth/types/api.ts: -------------------------------------------------------------------------------- 1 | import { UserModel } from './models' 2 | 3 | export type UserResponse = { 4 | jwt: string 5 | user: UserModel 6 | } 7 | -------------------------------------------------------------------------------- /src/modules/auth/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './payload' 2 | -------------------------------------------------------------------------------- /src/modules/auth/types/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user' 2 | -------------------------------------------------------------------------------- /src/modules/auth/types/models/user.ts: -------------------------------------------------------------------------------- 1 | export type UserModel = { 2 | id: string 3 | email: string 4 | firstName: string 5 | lastName: string 6 | bio: string 7 | role: 'ADMIN' | 'USER' 8 | } 9 | -------------------------------------------------------------------------------- /src/modules/auth/types/payload.ts: -------------------------------------------------------------------------------- 1 | export type RegisterPayload = { 2 | email: string 3 | password: string 4 | firstName: string 5 | lastName: string 6 | } 7 | 8 | export type LoginPayload = { 9 | email: string 10 | password: string 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/auth/utils/index.tsx: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /src/modules/error/components/error-boundary/error-boundary.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement, useCallback, useMemo } from 'react' 2 | import ErrorBoundaryLib from 'react-native-error-boundary' 3 | 4 | import { Crash } from '../../screens' 5 | import { logger } from '@/common/utils/logger' 6 | 7 | type Props = { 8 | children: ReactElement 9 | catchErrors: 'always' | 'dev' | 'prod' | 'never' 10 | } 11 | 12 | export const ErrorBoundary: React.FC = function ({ children, catchErrors }) { 13 | const isEnabled = useMemo(() => { 14 | return ( 15 | catchErrors === 'always' || 16 | (catchErrors === 'dev' && __DEV__) || 17 | (catchErrors === 'prod' && !__DEV__) 18 | ) 19 | }, [catchErrors]) 20 | 21 | const errorHandler = useCallback((error: Error, stackTrace: string) => { 22 | // send error to service log 23 | logger.log(error) 24 | logger.log(stackTrace) 25 | }, []) 26 | 27 | return isEnabled ? ( 28 | 29 | {children} 30 | 31 | ) : ( 32 | children 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/modules/error/components/error-boundary/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error-boundary' 2 | -------------------------------------------------------------------------------- /src/modules/error/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error-boundary' 2 | -------------------------------------------------------------------------------- /src/modules/error/screens/crash.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import RNRestart from 'react-native-restart' 3 | 4 | import { Button, Center, Screen, Space, Text } from '@/components/widgets' 5 | 6 | type IProps = { 7 | error: Error 8 | resetError: () => void 9 | } 10 | 11 | export const Crash: React.FC = function ({ error, resetError }) { 12 | const handleResetApp = useCallback(() => { 13 | resetError() 14 | RNRestart.Restart() 15 | }, [resetError]) 16 | 17 | return ( 18 | 19 |
20 | {__DEV__ ? error.toString() : 'Oops!'} 21 | 22 | 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/error/screens/index.ts: -------------------------------------------------------------------------------- 1 | export * from './crash' 2 | -------------------------------------------------------------------------------- /src/modules/home/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './layout' 2 | -------------------------------------------------------------------------------- /src/modules/home/components/layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './layout' 2 | -------------------------------------------------------------------------------- /src/modules/home/components/layout/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ChildrenProps } from '@/common/types' 4 | import { Center, Screen, Space, Text } from '@/components/widgets' 5 | 6 | type HomeLayoutProps = { 7 | title?: string 8 | } 9 | 10 | export const HomeLayout: React.FC = function ({ 11 | children, 12 | title, 13 | }) { 14 | return ( 15 | 16 |
17 | {title} 18 | 19 | {children} 20 |
21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/modules/home/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { 4 | createStackNavigator, 5 | StackNavigationOptions, 6 | TransitionPresets, 7 | } from '@react-navigation/stack' 8 | 9 | import { Home } from '../screens' 10 | import { RouterName } from '@/routes/router-name' 11 | 12 | export type HomeStackParamList = { 13 | home: undefined 14 | } 15 | 16 | const options: StackNavigationOptions = { 17 | headerShown: true, 18 | ...TransitionPresets.SlideFromRightIOS, 19 | } 20 | 21 | const Stack = createStackNavigator() 22 | 23 | export const HomeRoutes: React.FC = function () { 24 | return ( 25 | 26 | 33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/home/screens/home.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | 3 | import { HomeLayout } from '../components/layout' 4 | import { showCustomDialog } from '@/common/utils/dialog' 5 | import { EventRegister, EVENTS } from '@/common/utils/event-register' 6 | import { ConfirmModal } from '@/components/modals' 7 | import { Button, Space } from '@/components/widgets' 8 | import { useAuthStore } from '@/modules/auth/stores' 9 | 10 | export const Home: React.FC = function () { 11 | const logout = useAuthStore(state => state.logOutAction) 12 | 13 | const showModal = useCallback(() => { 14 | showCustomDialog() 15 | }, []) 16 | 17 | const emitEvent = useCallback(() => { 18 | EventRegister.emit(EVENTS.EVENT_LOGOUT) 19 | }, []) 20 | 21 | return ( 22 | 23 | 26 | 27 | 30 | 31 | 34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/home/screens/index.ts: -------------------------------------------------------------------------------- 1 | export * from './home' 2 | -------------------------------------------------------------------------------- /src/providers/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useMemo } from 'react' 2 | import { Alert } from 'react-native' 3 | import { RootSiblingParent } from 'react-native-root-siblings' 4 | import { SafeAreaProvider } from 'react-native-safe-area-context' 5 | 6 | import { ThemeProvider } from '@shopify/restyle' 7 | 8 | import { useBarStyle } from '@/common/hooks' 9 | import { useThemeStore } from '@/common/stores' 10 | import { darkTheme, lightTheme } from '@/common/themes' 11 | import { EventRegister, EVENTS } from '@/common/utils/event-register' 12 | import { useAuthStore } from '@/modules/auth/stores' 13 | import { ErrorBoundary } from '@/modules/error/components' 14 | import { AppRoutes } from '@/routes' 15 | 16 | export const AppProviders: React.FC = function () { 17 | useBarStyle() 18 | const checkLoggedIn = useAuthStore(state => state.checkLoggedInAction) 19 | const isDarkMode = useThemeStore(state => state.isDarkMode) 20 | const theme = useMemo(() => (isDarkMode ? darkTheme : lightTheme), [isDarkMode]) 21 | 22 | useEffect(() => { 23 | checkLoggedIn() 24 | EventRegister.on(EVENTS.EVENT_LOGOUT, () => { 25 | Alert.alert("I'm an event!") 26 | }) 27 | }, [checkLoggedIn]) 28 | 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import RNBootSplash from 'react-native-bootsplash' 3 | 4 | import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native' 5 | import { StackNavigationProp } from '@react-navigation/stack' 6 | 7 | import { useThemeStore } from '@/common/stores' 8 | import { navigationRef } from '@/common/utils/navigation-utilities' 9 | import { AuthRoutes, AuthStackParamList } from '@/modules/auth/routes' 10 | import { useAuthStore } from '@/modules/auth/stores' 11 | import { HomeRoutes, HomeStackParamList } from '@/modules/home/routes' 12 | 13 | export type AppParamList = AuthStackParamList & HomeStackParamList 14 | 15 | export type AppNavigationProp = StackNavigationProp 16 | 17 | export const AppRoutes: React.FC = function () { 18 | const isLoggedIn = useAuthStore(state => state.isLoggedIn) 19 | const isDarkMode = useThemeStore(state => state.isDarkMode) 20 | 21 | return ( 22 | RNBootSplash.hide({ fade: true })}> 26 | {!isLoggedIn ? : } 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/routes/router-name.ts: -------------------------------------------------------------------------------- 1 | import { AppParamList } from '.' 2 | 3 | type Route = { [key in keyof AppParamList]: keyof AppParamList | any } 4 | export const RouterName: Route = { 5 | home: 'home', 6 | landing: 'landing', 7 | register: 'register', 8 | login: 'login', 9 | } 10 | -------------------------------------------------------------------------------- /src/starter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { AppProviders } from './providers' 4 | 5 | export const Starter: React.FC = function () { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /templates/form/NAME.tsx.ejs: -------------------------------------------------------------------------------- 1 | --- 2 | patch: 3 | append: "export * from './<%= props.kebabCaseName %>'\n" 4 | skip: <%= props.skipIndexFile %> 5 | --- 6 | import React, { useCallback } from 'react' 7 | import { useForm } from 'react-hook-form' 8 | 9 | import { yupResolver } from '@hookform/resolvers/yup' 10 | import * as yup from 'yup' 11 | 12 | import { Button, Col, Space } from '@/components/widgets' 13 | import { InputField } from '@/components/widgets/input-field' 14 | 15 | const schema = yup.object({ 16 | email: yup.string().required('Required'), 17 | password: yup.string().required('Required'), 18 | }) 19 | 20 | type DataForm = { 21 | email: string 22 | password: string 23 | } 24 | 25 | type Props = { 26 | // props 27 | } 28 | 29 | /** 30 | * Describe your component here 31 | */ 32 | 33 | export const <%= props.pascalCaseName %>: React.FC = function (props: Props) { 34 | const { 35 | control, 36 | handleSubmit, 37 | formState: { errors }, 38 | } = useForm({ 39 | defaultValues: { 40 | email: '', 41 | password: '', 42 | }, 43 | resolver: yupResolver(schema), 44 | }) 45 | 46 | const onSubmit = useCallback((data: DataForm) => { 47 | // handle 48 | }, []) 49 | 50 | return ( 51 | 52 | 60 | 61 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /templates/form/index.ts.ejs: -------------------------------------------------------------------------------- 1 | export * from './<%= props.kebabCaseName %>' 2 | -------------------------------------------------------------------------------- /templates/modal/NAME.tsx.ejs: -------------------------------------------------------------------------------- 1 | --- 2 | patch: 3 | append: "export * from './<%= props.kebabCaseName %>'\n" 4 | skip: <%= props.skipIndexFile %> 5 | --- 6 | import React, { memo, useCallback } from 'react' 7 | 8 | import { Button, Col, Row, Space, Text } from '@/components/widgets' 9 | import { WIDTH } from '@/shared/constants' 10 | import { hideCustomDialog } from '@/shared/libs/dialog' 11 | 12 | type Props = { 13 | onClose?: () => void 14 | onConfirm?: () => void 15 | title: string 16 | content: string 17 | labelConfirm?: string 18 | labelCancel?: string 19 | } 20 | 21 | /** 22 | * Describe your component here 23 | */ 24 | 25 | export const <%= props.pascalCaseName %>: React.FC = memo(function ({ 26 | onClose, 27 | onConfirm, 28 | title, 29 | content, 30 | labelCancel = 'Cancel', 31 | labelConfirm = 'OK', 32 | }) { 33 | const handleClose = useCallback(() => { 34 | onClose?.() 35 | hideCustomDialog() 36 | }, [onClose]) 37 | 38 | const handleConfirm = useCallback(() => { 39 | onConfirm?.() 40 | hideCustomDialog() 41 | }, [onConfirm]) 42 | 43 | return ( 44 | 45 | 46 | 47 | {title} 48 | 49 | 50 | 51 | 52 | {content} 53 | 54 | 55 | 56 | 66 | 67 | 70 | 71 | 72 | ) 73 | }) 74 | 75 | -------------------------------------------------------------------------------- /templates/modal/index.ts.ejs: -------------------------------------------------------------------------------- 1 | export * from './<%= props.kebabCaseName %>' 2 | -------------------------------------------------------------------------------- /templates/module/api/demo.ts: -------------------------------------------------------------------------------- 1 | import { ResponseApi } from '@/common/types/api' 2 | import { axios } from '@/common/utils/axios' 3 | import { wrapApiCall } from '@/common/utils/helpers' 4 | 5 | export const getDemoApi = async (): Promise> => { 6 | return wrapApiCall(() => axios.get('/demo')) 7 | } 8 | -------------------------------------------------------------------------------- /templates/module/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './demo' 2 | -------------------------------------------------------------------------------- /templates/module/assets/index.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /templates/module/components/index.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /templates/module/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /templates/module/index.ts: -------------------------------------------------------------------------------- 1 | export * from './routes' 2 | -------------------------------------------------------------------------------- /templates/module/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { 4 | createStackNavigator, 5 | StackNavigationOptions, 6 | TransitionPresets, 7 | } from '@react-navigation/stack' 8 | 9 | export type StackParamList = { 10 | demo: undefined 11 | } 12 | 13 | const options: StackNavigationOptions = { 14 | headerShown: true, 15 | ...TransitionPresets.SlideFromRightIOS, 16 | } 17 | 18 | const Stack = createStackNavigator() 19 | 20 | export const AuthRoutes: React.FC = function () { 21 | return ( 22 | 23 | {/* */} 30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /templates/module/screens/index.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /templates/module/stores/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useDemoStore' 2 | -------------------------------------------------------------------------------- /templates/module/stores/useDemoStore.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | 3 | type State = { 4 | data: string | undefined 5 | } 6 | 7 | type Action = { 8 | changeData: (data: string) => Promise 9 | } 10 | 11 | export const useDemoStore = create(set => ({ 12 | data: undefined, 13 | changeData: async data => { 14 | set({ data }) 15 | }, 16 | })) 17 | -------------------------------------------------------------------------------- /templates/module/types/api.ts: -------------------------------------------------------------------------------- 1 | import { DemoModel } from './models' 2 | 3 | export type UserResponse = { 4 | jwt: string 5 | user: DemoModel 6 | } 7 | -------------------------------------------------------------------------------- /templates/module/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './payload' 2 | export * from './api' 3 | -------------------------------------------------------------------------------- /templates/module/types/models/demo.ts: -------------------------------------------------------------------------------- 1 | export type DemoModel = { 2 | id: string 3 | email: string 4 | firstName: string 5 | lastName: string 6 | } 7 | -------------------------------------------------------------------------------- /templates/module/types/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './demo' 2 | -------------------------------------------------------------------------------- /templates/module/types/payload.ts: -------------------------------------------------------------------------------- 1 | export type DemoPayload = { 2 | email: string 3 | password: string 4 | } 5 | -------------------------------------------------------------------------------- /templates/module/utils/index.tsx: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /templates/screen/NAME.tsx.ejs: -------------------------------------------------------------------------------- 1 | --- 2 | patch: 3 | append: "export * from './<%= props.kebabCaseName %>'\n" 4 | skip: <%= props.skipIndexFile %> 5 | --- 6 | import React from 'react' 7 | 8 | import { Center, Screen, Text } from '@/components/widgets' 9 | 10 | export const <%= props.pascalCaseName %>: React.FC = function () { 11 | return ( 12 | 13 |
14 | <%= props.pascalCaseName %> 15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /templates/store/useNAME.tsx.ejs: -------------------------------------------------------------------------------- 1 | --- 2 | patch: 3 | append: "export * from './use<%= props.pascalCaseName %>'\n" 4 | skip: <%= props.skipIndexFile %> 5 | --- 6 | import create from 'zustand' 7 | 8 | type State = { 9 | // 10 | } 11 | 12 | type Action = { 13 | // 14 | } 15 | 16 | export const use<%= props.pascalCaseName %> = create((set, get) => ({ 17 | // 18 | })) 19 | -------------------------------------------------------------------------------- /templates/widget/NAME.tsx.ejs: -------------------------------------------------------------------------------- 1 | --- 2 | patch: 3 | append: "export * from './<%= props.kebabCaseName %>'\n" 4 | skip: <%= props.skipIndexFile %> 5 | --- 6 | import React from 'react' 7 | 8 | import { ViewProps } from '@/shared/types' 9 | 10 | type Props = { 11 | // components's props 12 | } 13 | 14 | /** 15 | * Describe your component here 16 | */ 17 | 18 | export const <%= props.pascalCaseName %>: React.FC = function (props: Props) { 19 | return null 20 | } 21 | -------------------------------------------------------------------------------- /templates/widget/index.ts.ejs: -------------------------------------------------------------------------------- 1 | export * from './<%= props.kebabCaseName %>' 2 | -------------------------------------------------------------------------------- /tsconfig.paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /types/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | import React from 'react' 3 | import { SvgProps } from 'react-native-svg' 4 | 5 | const content: React.FC 6 | export default content 7 | } 8 | --------------------------------------------------------------------------------