├── .buckconfig ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc.js ├── .vscode └── settings.json ├── .watchmanconfig ├── LICENSE ├── README.md ├── __mocks__ ├── @react-native-async-storage │ └── async-storage │ │ └── index.js ├── database.ts ├── entries.json ├── react-i18next.js └── react-native-fs.js ├── android ├── app │ ├── _BUCK │ ├── build.gradle │ ├── build_defs.bzl │ ├── debug.keystore │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── logfinance │ │ │ └── ReactNativeFlipper.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ └── fonts │ │ │ ├── AntDesign.ttf │ │ │ ├── Entypo.ttf │ │ │ ├── EvilIcons.ttf │ │ │ ├── Feather.ttf │ │ │ ├── FontAwesome.ttf │ │ │ ├── FontAwesome5_Brands.ttf │ │ │ ├── FontAwesome5_Regular.ttf │ │ │ ├── FontAwesome5_Solid.ttf │ │ │ ├── Fontisto.ttf │ │ │ ├── Foundation.ttf │ │ │ ├── Ionicons.ttf │ │ │ ├── MaterialCommunityIcons.ttf │ │ │ ├── MaterialIcons.ttf │ │ │ ├── Octicons.ttf │ │ │ ├── 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 │ │ │ ├── SimpleLineIcons.ttf │ │ │ └── Zocial.ttf │ │ ├── java │ │ └── com │ │ │ └── logfinance │ │ │ ├── MainActivity.java │ │ │ └── MainApplication.java │ │ └── res │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_launcher_round_adaptive_back.png │ │ └── ic_launcher_round_adaptive_fore.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_launcher_round_adaptive_back.png │ │ └── ic_launcher_round_adaptive_fore.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_launcher_round_adaptive_back.png │ │ └── ic_launcher_round_adaptive_fore.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_launcher_round_adaptive_back.png │ │ └── ic_launcher_round_adaptive_fore.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_launcher_round_adaptive_back.png │ │ └── ic_launcher_round_adaptive_fore.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── app.json ├── babel.config.js ├── index.js ├── ios ├── LogFinance.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── LogFinance.xcscheme ├── LogFinance.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── LogFinance │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── 100.png │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 128.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 16.png │ │ │ ├── 167.png │ │ │ ├── 172.png │ │ │ ├── 180.png │ │ │ ├── 196.png │ │ │ ├── 20.png │ │ │ ├── 216.png │ │ │ ├── 256.png │ │ │ ├── 29.png │ │ │ ├── 32.png │ │ │ ├── 40.png │ │ │ ├── 48.png │ │ │ ├── 50.png │ │ │ ├── 512.png │ │ │ ├── 55.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 64.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ ├── 88.png │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Info.plist │ ├── LaunchScreen.storyboard │ └── main.m ├── LogFinanceTests │ ├── Info.plist │ └── LogFinanceTests.m ├── Podfile └── Podfile.lock ├── jest.config.js ├── jest.setup.js ├── metro.config.js ├── package-lock.json ├── package.json ├── react-native.config.js ├── src ├── App.tsx ├── __tests__ │ ├── App.test.tsx │ └── __snapshots__ │ │ └── App.test.tsx.snap ├── assets │ └── fonts │ │ └── poppins │ │ ├── OFL.txt │ │ ├── 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 ├── components │ ├── Button │ │ ├── Button.tsx │ │ ├── index.ts │ │ └── styles.ts │ ├── CategoryIcon │ │ ├── CategoryIcon.tsx │ │ ├── __tests__ │ │ │ ├── CategoryIcon.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── CategoryIcon.test.tsx.snap │ │ ├── icons.ts │ │ ├── index.ts │ │ └── styles.ts │ ├── CategoryPick │ │ ├── CategoryPick.tsx │ │ ├── __tests__ │ │ │ └── CategoryPick.test.tsx │ │ ├── index.ts │ │ └── styles.ts │ ├── Currency │ │ ├── Currency.tsx │ │ ├── __tests__ │ │ │ ├── Currency.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── Currency.test.tsx.snap │ │ └── index.ts │ ├── Dismiss │ │ ├── Dismiss.tsx │ │ ├── __tests__ │ │ │ ├── Dismiss.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── Dismiss.test.tsx.snap │ │ ├── index.ts │ │ └── styles.ts │ ├── EntriesList │ │ ├── EntriesList.tsx │ │ ├── __tests__ │ │ │ ├── EntriesList.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── EntriesList.test.tsx.snap │ │ ├── index.ts │ │ └── styles.ts │ ├── EntryItem │ │ ├── EntryItem.tsx │ │ ├── __tests__ │ │ │ ├── EntryItem.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── EntryItem.test.tsx.snap │ │ ├── index.ts │ │ └── styles.ts │ ├── InputNumber │ │ ├── InputNumber.tsx │ │ ├── __tests__ │ │ │ ├── InputNumber.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── InputNumber.test.tsx.snap │ │ ├── index.ts │ │ └── styles.ts │ ├── List │ │ ├── Item.tsx │ │ ├── List.tsx │ │ ├── index.ts │ │ └── styles.ts │ ├── NumberKeyboard │ │ ├── NumberKeyboard.tsx │ │ ├── __tests__ │ │ │ ├── NumberKeyboard.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── NumberKeyboard.test.tsx.snap │ │ ├── index.ts │ │ └── styles.ts │ ├── ProgressBar │ │ ├── ProgressBar.tsx │ │ ├── __tests__ │ │ │ ├── ProgressBar.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── ProgressBar.test.tsx.snap │ │ ├── index.ts │ │ └── styles.ts │ ├── Row │ │ ├── Row.tsx │ │ ├── index.ts │ │ └── styles.ts │ ├── Strap │ │ ├── Strap.tsx │ │ ├── __tests__ │ │ │ ├── Strap.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── Strap.test.tsx.snap │ │ ├── index.ts │ │ └── styles.ts │ ├── SwipeRemoveButton │ │ ├── SwipeRemoveButton.tsx │ │ ├── __tests__ │ │ │ ├── SwipeRemoveButton.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── SwipeRemoveButton.test.tsx.snap │ │ ├── index.ts │ │ └── styles.ts │ ├── Toolbar │ │ ├── Toolbar.tsx │ │ ├── index.ts │ │ └── styles.ts │ └── index.ts ├── contexts │ ├── BudgetContext.tsx │ └── index.ts ├── database │ ├── categories.json │ ├── collections.ts │ ├── index.ts │ ├── migrations.ts │ ├── modelClasses.ts │ ├── schema.ts │ └── seeds.ts ├── hooks │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── useEntry.test.tsx.snap │ │ └── useEntry.test.tsx │ ├── index.ts │ ├── useCategory.tsx │ ├── useDialog.ts │ └── useEntry.tsx ├── interfaces │ ├── IBudget.ts │ ├── ICategory.ts │ ├── IEntry.ts │ └── index.ts ├── locales │ ├── index.ts │ └── translations │ │ ├── en_US.json │ │ └── pt_BR.json ├── models │ ├── Budget.ts │ ├── Category.ts │ ├── Entry.ts │ └── index.ts ├── repositories │ ├── BudgetRepository.ts │ ├── CategoryRepository.ts │ ├── EntryRepository.ts │ └── index.ts ├── routes │ ├── StacksRoute.tsx │ ├── TabsRoute.tsx │ ├── components │ │ ├── BottomTabs │ │ │ ├── BottomTabs.tsx │ │ │ ├── index.ts │ │ │ └── styles.ts │ │ ├── TabIcon │ │ │ ├── TabIcon.tsx │ │ │ ├── __tests__ │ │ │ │ ├── TabIcon.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── TabIcon.test.tsx.snap │ │ │ ├── index.ts │ │ │ └── styles.ts │ │ └── index.ts │ └── index.tsx ├── screens │ ├── AddBudget │ │ ├── AddBudgetScreen.tsx │ │ ├── index.ts │ │ └── styles.ts │ ├── AddEntry │ │ ├── AddEntryScreen.tsx │ │ ├── __tests__ │ │ │ ├── AddEntryScreen.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── AddEntryScreen.test.tsx.snap │ │ ├── components │ │ │ ├── EntryCategory │ │ │ │ ├── EntryCategory.tsx │ │ │ │ ├── __tests__ │ │ │ │ │ ├── EntryCategory.test.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── EntryCategory.test.tsx.snap │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ ├── EntryDate │ │ │ │ ├── EntryDate.tsx │ │ │ │ ├── __tests__ │ │ │ │ │ ├── EntryDate.test.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── EntryDate.test.tsx.snap │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ ├── EntryDescription │ │ │ │ ├── EntryDescription.tsx │ │ │ │ ├── __tests__ │ │ │ │ │ ├── EntryDescription.test.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── EntryDescription.test.tsx.snap │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ ├── MenuEntryType │ │ │ │ ├── MenuEntryType.tsx │ │ │ │ ├── __tests__ │ │ │ │ │ ├── MenuEntryType.test.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── MenuEntryType.test.tsx.snap │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── styles.ts │ ├── Budget │ │ ├── BudgetScreen.tsx │ │ ├── components │ │ │ ├── BudgetList │ │ │ │ ├── BudgetItem.tsx │ │ │ │ ├── BudgetList.tsx │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ └── ListEmpty │ │ │ │ ├── LICENSE.md │ │ │ │ ├── ListEmpty.tsx │ │ │ │ ├── __tests__ │ │ │ │ ├── ListEmpty.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── ListEmpty.test.tsx.snap │ │ │ │ ├── empty.json │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ ├── index.tsx │ │ └── styles.ts │ ├── Categories │ │ ├── CategoriesScreen.tsx │ │ ├── __tests__ │ │ │ ├── CategoriesScreen.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── CategoriesScreen.test.tsx.snap │ │ ├── components │ │ │ ├── CategoryItem │ │ │ │ ├── CategoryItem.tsx │ │ │ │ ├── __tests__ │ │ │ │ │ ├── CategoryItem.test.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── CategoryItem.test.tsx.snap │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ ├── CategoryList │ │ │ │ ├── CategoryList.tsx │ │ │ │ ├── __tests__ │ │ │ │ │ ├── CategoryList.test.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── CategoryList.test.tsx.snap │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── styles.ts │ ├── Currency │ │ ├── CurrencyScreen.tsx │ │ ├── currencies.ts │ │ ├── index.tsx │ │ └── styles.ts │ ├── Historic │ │ ├── HistoricScreen.tsx │ │ ├── components │ │ │ ├── HistoricList │ │ │ │ ├── HistoricHeader.tsx │ │ │ │ ├── HistoricItem.tsx │ │ │ │ ├── HistoricList.tsx │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ └── ListEmpty │ │ │ │ ├── LICENSE.md │ │ │ │ ├── ListEmpty.tsx │ │ │ │ ├── empty.json │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ ├── index.tsx │ │ └── styles.ts │ ├── Home │ │ ├── HomeScreen.tsx │ │ ├── __tests__ │ │ │ ├── HomeScreen.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── HomeScreen.test.tsx.snap │ │ ├── components │ │ │ ├── Header │ │ │ │ ├── Header.tsx │ │ │ │ ├── __tests__ │ │ │ │ │ ├── Header.test.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── Header.test.tsx.snap │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ ├── ListEmpty │ │ │ │ ├── LICENSE.md │ │ │ │ ├── ListEmpty.tsx │ │ │ │ ├── __tests__ │ │ │ │ │ ├── ListEmpty.test.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── ListEmpty.test.tsx.snap │ │ │ │ ├── empty.json │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── styles.ts │ ├── Language │ │ ├── LanguageScreen.tsx │ │ ├── index.tsx │ │ └── styles.ts │ ├── Reports │ │ ├── ReportsScreen.tsx │ │ ├── __tests__ │ │ │ ├── ReportsScreen.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── ReportsScreen.test.tsx.snap │ │ ├── components │ │ │ ├── FilterPeriod │ │ │ │ ├── FilterPeriod.tsx │ │ │ │ ├── Item.tsx │ │ │ │ ├── __tests__ │ │ │ │ │ ├── FilterPeriod.test.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── FilterPeriod.test.tsx.snap │ │ │ │ ├── data.ts │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ ├── LegendList │ │ │ │ ├── LegendItem.tsx │ │ │ │ ├── LegendList.tsx │ │ │ │ ├── __tests__ │ │ │ │ │ ├── LegendItem.test.tsx │ │ │ │ │ ├── LegendList.test.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ ├── LegendItem.test.tsx.snap │ │ │ │ │ │ └── LegendList.test.tsx.snap │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ └── ListEmpty │ │ │ │ ├── LICENSE.md │ │ │ │ ├── ListEmpty.tsx │ │ │ │ ├── __tests__ │ │ │ │ ├── ListEmpty.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── ListEmpty.test.tsx.snap │ │ │ │ ├── index.ts │ │ │ │ ├── reports.json │ │ │ │ └── styles.ts │ │ ├── index.ts │ │ └── styles.ts │ └── Settings │ │ ├── SettingsScreen.tsx │ │ ├── index.ts │ │ └── styles.ts ├── services │ ├── currency.ts │ ├── database.ts │ ├── index.ts │ ├── language.ts │ ├── reports.ts │ └── storage.ts ├── styles │ ├── colors.ts │ ├── index.ts │ ├── layout.ts │ ├── mixins.ts │ ├── shadow.ts │ ├── spacing.ts │ ├── styled.d.ts │ ├── theme.tsx │ └── typography.ts └── utils │ ├── dates.ts │ ├── index.ts │ ├── strings.ts │ └── test-utils.tsx ├── tsconfig.json └── yarn.lock /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Windows files 2 | [*.bat] 3 | end_of_line = crlf 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | es2021: true, 4 | node: true, 5 | jest: true, 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:react/recommended', 10 | 'plugin:react-hooks/recommended', 11 | 'plugin:@typescript-eslint/eslint-recommended', 12 | 'plugin:@typescript-eslint/recommended', 13 | ], 14 | parser: '@typescript-eslint/parser', 15 | parserOptions: { 16 | project: './tsconfig.json', 17 | ecmaFeatures: { 18 | jsx: true, 19 | }, 20 | ecmaVersion: 12, 21 | sourceType: 'module', 22 | }, 23 | plugins: ['react', 'react-hooks', '@typescript-eslint', 'prettier'], 24 | 25 | rules: { 26 | 'no-tabs': ['error', {allowIndentationTabs: true}], 27 | quotes: ['error', 'single', {avoidEscape: true}], 28 | semi: 'off', 29 | 'no-empty-function': 'off', 30 | '@typescript-eslint/no-empty-function': 'off', 31 | 'react/display-name': 'off', 32 | 'react/prop-types': 'off', 33 | 'prettier/prettier': 'error', 34 | '@typescript-eslint/unbound-method': 'error', 35 | '@typescript-eslint/semi': ['error'], 36 | }, 37 | overrides: [ 38 | { 39 | files: ['*.jsx', '*.tsx'], 40 | rules: { 41 | indent: ['error', 2, {SwitchCase: 1}], 42 | '@typescript-eslint/explicit-module-boundary-types': ['off'], 43 | }, 44 | }, 45 | ], 46 | settings: { 47 | react: { 48 | version: 'detect', 49 | }, 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Windows files should use crlf line endings 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | *.bat text eol=crlf 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | *.hprof 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | !debug.keystore 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://docs.fastlane.tools/best-practices/source-control/ 51 | 52 | */fastlane/report.xml 53 | */fastlane/Preview.html 54 | */fastlane/screenshots 55 | 56 | # Bundle artifact 57 | *.jsbundle 58 | 59 | # CocoaPods 60 | /ios/Pods/ 61 | 62 | android/gradle.properties 63 | 64 | android/app/release/app-release.apk 65 | 66 | android/app/release/output-metadata.json 67 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | ios 3 | android -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | singleQuote: true, 4 | trailingComma: 'all', 5 | arrowParens: 'avoid', 6 | }; 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll": true, 4 | "source.fixAll.eslint": true, 5 | "source.organizeImports": false 6 | }, 7 | "editor.formatOnSave": false, 8 | "editor.folding": false 9 | } 10 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Felipe Rosas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__mocks__/@react-native-async-storage/async-storage/index.js: -------------------------------------------------------------------------------- 1 | let cache = {}; 2 | export default { 3 | setItem: (key, value) => { 4 | return new Promise((resolve, reject) => { 5 | return typeof key !== 'string' || typeof value !== 'string' 6 | ? reject(new Error('key and value must be string')) 7 | : resolve((cache[key] = value)); 8 | }); 9 | }, 10 | getItem: (key, value) => { 11 | return new Promise(resolve => { 12 | return cache.hasOwnProperty(key) ? resolve(cache[key]) : resolve(null); 13 | }); 14 | }, 15 | removeItem: key => { 16 | return new Promise((resolve, reject) => { 17 | return cache.hasOwnProperty(key) 18 | ? resolve(delete cache[key]) 19 | : reject('No such key!'); 20 | }); 21 | }, 22 | clear: key => { 23 | return new Promise((resolve, reject) => resolve((cache = {}))); 24 | }, 25 | 26 | getAllKeys: key => { 27 | return new Promise((resolve, reject) => resolve(Object.keys(cache))); 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /__mocks__/database.ts: -------------------------------------------------------------------------------- 1 | import { Database } from '@nozbe/watermelondb'; 2 | import LokiJSAdapter, { LokiAdapterOptions } from '@nozbe/watermelondb/adapters/lokijs' 3 | 4 | import { schema, migrations, modelClasses } from 'database'; 5 | 6 | let database: Database; 7 | 8 | export function getDatabase(): Database { 9 | if (!database) { 10 | const adapterConfig: LokiAdapterOptions = { 11 | schema, 12 | migrations, 13 | useWebWorker: false, 14 | useIncrementalIndexedDB: true, 15 | extraLokiOptions: { 16 | autosave: false 17 | } 18 | }; 19 | 20 | const adapter = new LokiJSAdapter(adapterConfig); 21 | 22 | database = new Database({ 23 | adapter, 24 | modelClasses, 25 | }); 26 | } 27 | return database; 28 | } 29 | 30 | export function runSeeds(): void { 31 | jest.fn(); 32 | } 33 | 34 | export default getDatabase; 35 | -------------------------------------------------------------------------------- /__mocks__/entries.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": 1, 3 | "description": "Salário", 4 | "type": "earning", 5 | "value": 10000, 6 | "category": { 7 | "id": 1, 8 | "description": "Salario", 9 | "key": "money" 10 | } 11 | }, { 12 | "id": 2, 13 | "description": "Pizza de carne seca", 14 | "type": "expense", 15 | "value": 49.90, 16 | "category": { 17 | "id": 1, 18 | "description": "Restaurante", 19 | "key": "food" 20 | } 21 | }, 22 | { 23 | "id": 3, 24 | "description": "Gasolina", 25 | "type": "expense", 26 | "value": 100, 27 | "category": { 28 | "id": 1, 29 | "description": "Conveniência", 30 | "key": "others" 31 | } 32 | 33 | } 34 | ] -------------------------------------------------------------------------------- /__mocks__/react-native-fs.js: -------------------------------------------------------------------------------- 1 | jest.mock('react-native-fs', () => { 2 | return { 3 | mkdir: jest.fn(), 4 | moveFile: jest.fn(), 5 | copyFile: jest.fn(), 6 | pathForBundle: jest.fn(), 7 | pathForGroup: jest.fn(), 8 | getFSInfo: jest.fn(), 9 | getAllExternalFilesDirs: jest.fn(), 10 | unlink: jest.fn(), 11 | exists: jest.fn(), 12 | stopDownload: jest.fn(), 13 | resumeDownload: jest.fn(), 14 | isResumable: jest.fn(), 15 | stopUpload: jest.fn(), 16 | completeHandlerIOS: jest.fn(), 17 | readDir: jest.fn(), 18 | readDirAssets: jest.fn(), 19 | existsAssets: jest.fn(), 20 | readdir: jest.fn(), 21 | setReadable: jest.fn(), 22 | stat: jest.fn(), 23 | readFile: jest.fn(), 24 | read: jest.fn(), 25 | readFileAssets: jest.fn(), 26 | hash: jest.fn(), 27 | copyFileAssets: jest.fn(), 28 | copyFileAssetsIOS: jest.fn(), 29 | copyAssetsVideoIOS: jest.fn(), 30 | writeFile: jest.fn(), 31 | appendFile: jest.fn(), 32 | write: jest.fn(), 33 | downloadFile: jest.fn(), 34 | uploadFiles: jest.fn(), 35 | touch: jest.fn(), 36 | MainBundlePath: jest.fn(), 37 | CachesDirectoryPath: jest.fn(), 38 | DocumentDirectoryPath: jest.fn(), 39 | ExternalDirectoryPath: jest.fn(), 40 | ExternalStorageDirectoryPath: jest.fn(), 41 | TemporaryDirectoryPath: jest.fn(), 42 | LibraryDirectoryPath: jest.fn(), 43 | PicturesDirectoryPath: jest.fn(), 44 | }; 45 | }); 46 | -------------------------------------------------------------------------------- /android/app/_BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.logfinance", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.logfinance", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /android/app/build_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/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/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 | 12 | -keep class com.facebook.hermes.unicode.** { *; } 13 | -keep class com.facebook.jni.** { *; } 14 | 15 | -keep class com.swmansion.reanimated.** { *; } 16 | -keep class com.facebook.react.turbomodule.** { *; } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 13 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/AntDesign.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/AntDesign.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Entypo.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/EvilIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/EvilIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Feather.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Feather.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Fontisto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Fontisto.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Foundation.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Foundation.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Ionicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/MaterialIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/MaterialIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Octicons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-Black.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-BlackItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-Bold.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-BoldItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-ExtraBold.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-ExtraLight.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-Italic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-Light.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-LightItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-Medium.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-MediumItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-Regular.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-SemiBold.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-Thin.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Poppins-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Poppins-ThinItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/SimpleLineIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/SimpleLineIcons.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Zocial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/assets/fonts/Zocial.ttf -------------------------------------------------------------------------------- /android/app/src/main/java/com/logfinance/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.logfinance; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. This is used to schedule 9 | * rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "LogFinance"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/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/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round_adaptive_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-hdpi/ic_launcher_round_adaptive_back.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round_adaptive_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-hdpi/ic_launcher_round_adaptive_fore.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/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/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round_adaptive_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-mdpi/ic_launcher_round_adaptive_back.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round_adaptive_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-mdpi/ic_launcher_round_adaptive_fore.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/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/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round_adaptive_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round_adaptive_back.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round_adaptive_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round_adaptive_fore.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/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/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_adaptive_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_adaptive_back.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_adaptive_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round_adaptive_fore.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/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/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_adaptive_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_adaptive_back.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_adaptive_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round_adaptive_fore.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | LogFinance 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "30.0.2" 6 | minSdkVersion = 21 7 | compileSdkVersion = 30 8 | targetSdkVersion = 30 9 | ndkVersion = "21.4.7075529" 10 | kotlinVersion = '1.3.50' 11 | } 12 | repositories { 13 | google() 14 | mavenCentral() 15 | } 16 | dependencies { 17 | classpath("com.android.tools.build:gradle:4.2.2") 18 | // NOTE: Do not place your application dependencies here; they belong 19 | // in the individual module build.gradle files 20 | } 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | mavenCentral() 26 | mavenLocal() 27 | maven { 28 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 29 | url("$rootDir/../node_modules/react-native/android") 30 | } 31 | maven { 32 | // Android JSC is installed from npm 33 | url("$rootDir/../node_modules/jsc-android/dist") 34 | } 35 | 36 | google() 37 | maven { url 'https://www.jitpack.io' } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /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: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.99.0 29 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/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-6.9-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'LogFinance' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LogFinance", 3 | "displayName": "LogFinance" 4 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | plugins: [ 4 | 'react-native-reanimated/plugin', 5 | ['@babel/plugin-proposal-decorators', {legacy: true}], 6 | [ 7 | 'module-resolver', 8 | { 9 | cwd: 'babelrc', 10 | root: ['./src'], 11 | extensions: [ 12 | '.ts', 13 | '.tsx', 14 | '.js', 15 | '.jsx', 16 | '.ios.js', 17 | 'android.js', 18 | '.json', 19 | ], 20 | }, 21 | ], 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import {AppRegistry, LogBox} from 'react-native'; 2 | 3 | import App from './src/App'; 4 | import {name as appName} from './app.json'; 5 | import 'react-native-gesture-handler'; 6 | 7 | LogBox.ignoreLogs([ 8 | 'new NativeEventEmitter', 9 | 'Non-serializable values were found in the navigation state', 10 | ]); 11 | 12 | AppRegistry.registerComponent(appName, () => App); 13 | -------------------------------------------------------------------------------- /ios/LogFinance.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/LogFinance.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/LogFinance/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : UIResponder 5 | 6 | @property (nonatomic, strong) UIWindow *window; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/172.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/172.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/196.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/216.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/216.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/48.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/55.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/64.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/AppIcon.appiconset/88.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/ios/LogFinance/Images.xcassets/AppIcon.appiconset/88.png -------------------------------------------------------------------------------- /ios/LogFinance/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/LogFinance/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ios/LogFinanceTests/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/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../node_modules/react-native/scripts/react_native_pods' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | platform :ios, '11.0' 5 | 6 | target 'LogFinance' do 7 | config = use_native_modules! 8 | 9 | use_react_native!( 10 | :path => config[:reactNativePath], 11 | # to enable hermes on iOS, change `false` to `true` and then install pods 12 | :hermes_enabled => false 13 | ) 14 | 15 | pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi', :modular_headers => true 16 | pod 'simdjson', path: '../node_modules/@nozbe/simdjson' 17 | 18 | target 'LogFinanceTests' do 19 | inherit! :complete 20 | # Pods for testing 21 | end 22 | 23 | # Enables Flipper. 24 | # 25 | # Note that if you have use_frameworks! enabled, Flipper will not work and 26 | # you should disable the next line. 27 | use_flipper!() 28 | 29 | post_install do |installer| 30 | react_native_post_install(installer) 31 | __apply_Xcode_12_5_M1_post_install_workaround(installer) 32 | end 33 | end -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | verbose: true, 4 | setupFilesAfterEnv: [ 5 | '@testing-library/react-hooks/dont-cleanup-after-each.js', 6 | ], 7 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'], 8 | setupFiles: [ 9 | './node_modules/react-native-gesture-handler/jestSetup.js', 10 | '/jest.setup.js', 11 | ], 12 | transformIgnorePatterns: [ 13 | 'node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|rollbar-react-native|@fortawesome|@react-native|@react-navigation)', 14 | ], 15 | moduleDirectories: ['node_modules', 'utils', __dirname], 16 | }; 17 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | import 'react-native-gesture-handler/jestSetup'; 2 | jest.runAllTimers(); 3 | 4 | jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); 5 | jest.mock('react-native-vector-icons/MaterialIcons', () => 'Icon'); 6 | jest.mock('react-native-reanimated', () => { 7 | // eslint-disable-next-line @typescript-eslint/no-var-requires 8 | const Reanimated = require('react-native-reanimated/mock'); 9 | 10 | // The mock for `call` immediately calls the callback which is incorrect 11 | // So we override it with a no-op 12 | Reanimated.default.call = () => {}; 13 | 14 | return Reanimated; 15 | }); 16 | 17 | jest.mock('react-i18next', () => ({ 18 | useTranslation: () => ({ 19 | t: (key: any) => key, 20 | i18n: { 21 | changeLanguage: () => new Promise(() => {}), 22 | }, 23 | }), 24 | })); 25 | 26 | jest.mock('services/database', () => ({ 27 | getDatabase: () => ({ 28 | collections: { 29 | get: jest.fn(() => ({ 30 | query: jest.fn(() => ({ 31 | fetch: jest.fn(() => Promise.resolve(true)), 32 | })), 33 | })), 34 | }, 35 | }), 36 | })); 37 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: true, 14 | }, 15 | }), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /react-native.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | project: { 3 | ios: {}, 4 | android: {}, 5 | }, 6 | assets: ['./src/assets/fonts/poppins'], 7 | }; 8 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, {Suspense, useEffect} from 'react'; 2 | import {ActivityIndicator} from 'react-native'; 3 | import DatabaseProvider from '@nozbe/watermelondb/DatabaseProvider'; 4 | import Routes from 'routes'; 5 | import Theme from 'styles/theme'; 6 | 7 | import {getDatabase, runSeeds} from 'services/database'; 8 | 9 | import 'locales'; 10 | 11 | const App = () => { 12 | const database = getDatabase(); 13 | 14 | useEffect(() => runSeeds(), []); 15 | 16 | return ( 17 | 18 | 19 | }> 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | export default App; 28 | -------------------------------------------------------------------------------- /src/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | import React from 'react'; 3 | import {render} from '@testing-library/react-native'; 4 | import App from '../App'; 5 | 6 | describe('App', () => { 7 | it('Test match snapshot App', () => { 8 | // given 9 | const props = {}; 10 | 11 | // when 12 | const {toJSON} = render(); 13 | 14 | // then 15 | expect(toJSON()).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/App.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`App Test match snapshot App 1`] = ``; 4 | -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-Black.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-BlackItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-BoldItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-ExtraBold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-ExtraLight.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-Italic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-Light.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-LightItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-Medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-MediumItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-SemiBold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-Thin.ttf -------------------------------------------------------------------------------- /src/assets/fonts/poppins/Poppins-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/assets/fonts/poppins/Poppins-ThinItalic.ttf -------------------------------------------------------------------------------- /src/components/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {Title, PrimaryButton, DangerButton, InverseButton} from './styles'; 4 | 5 | export type ButtonType = 'primary' | 'danger'; 6 | 7 | interface Props { 8 | title: string; 9 | disabled?: boolean; 10 | inverse?: boolean; 11 | type?: ButtonType; 12 | onPress: () => void; 13 | } 14 | 15 | const Button = ({ 16 | title, 17 | onPress, 18 | disabled = false, 19 | inverse = false, 20 | type, 21 | }: Props): JSX.Element => { 22 | const Label = ( 23 | 24 | {title} 25 | 26 | ); 27 | 28 | if (inverse) { 29 | return ( 30 | 31 | {Label} 32 | 33 | ); 34 | } 35 | 36 | if (type === 'danger') { 37 | return ( 38 | 39 | {Label} 40 | 41 | ); 42 | } 43 | 44 | return ( 45 | 46 | {Label} 47 | 48 | ); 49 | }; 50 | 51 | export default Button; 52 | -------------------------------------------------------------------------------- /src/components/Button/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './Button'; 2 | -------------------------------------------------------------------------------- /src/components/Button/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import Colors from 'styles/colors'; 3 | import {Text, TouchableOpacity} from 'styles/layout'; 4 | import {ButtonType} from './Button'; 5 | 6 | interface ButtonProps { 7 | disabled?: boolean; 8 | inverse?: boolean; 9 | type?: ButtonType; 10 | } 11 | 12 | export const BaseButton = styled(TouchableOpacity)` 13 | margin: ${({theme}) => theme.spacing.small}px; 14 | padding: ${({theme}) => theme.spacing.great}px 15 | ${({theme}) => theme.spacing.tall}px; 16 | border-radius: ${({theme}) => theme.spacing.tall}px; 17 | flex-direction: row; 18 | justify-content: center; 19 | align-items: center; 20 | min-width: 120px; 21 | border: 2px solid transparent; 22 | `; 23 | 24 | export const PrimaryButton = styled(BaseButton)` 25 | background: ${({disabled}) => 26 | disabled ? `${Colors.seconday}40` : Colors.seconday}; 27 | `; 28 | 29 | export const InverseButton = styled(BaseButton)` 30 | background: transparent; 31 | border: 2px solid ${Colors.seconday}; 32 | `; 33 | 34 | export const DangerButton = styled(BaseButton)` 35 | background: ${({disabled}) => 36 | disabled ? `${Colors.danger}40` : Colors.danger}; 37 | `; 38 | 39 | export const Title = styled(Text)` 40 | color: ${({theme, inverse = false}) => 41 | inverse ? theme.colors.seconday : theme.colors.white}; 42 | font-size: ${({theme}) => theme.fontSizes.medium}; 43 | font-weight: ${({theme}) => theme.fontWeight.bold}; 44 | text-align: center; 45 | `; 46 | -------------------------------------------------------------------------------- /src/components/CategoryIcon/CategoryIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import icons from './icons'; 3 | 4 | interface Props { 5 | name?: string; 6 | contained?: boolean; 7 | } 8 | 9 | import {IconItem, Contained} from './styles'; 10 | 11 | const CategoryIcon = ({ 12 | name = 'others', 13 | contained = false, 14 | }: Props): JSX.Element => { 15 | const iconOption = name in icons ? icons[name] : icons.others; 16 | 17 | if (contained) { 18 | return ( 19 | 20 | 21 | 22 | ); 23 | } 24 | 25 | return ; 26 | }; 27 | 28 | export default CategoryIcon; 29 | -------------------------------------------------------------------------------- /src/components/CategoryIcon/__tests__/CategoryIcon.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | import React from 'react'; 3 | import {render} from 'utils/test-utils'; 4 | import CategoryIcon from '../CategoryIcon'; 5 | 6 | describe('CategoryIcon', () => { 7 | it('Test match snapshot CategoryIcon', () => { 8 | // given 9 | const props = { 10 | name: 'food', 11 | }; 12 | 13 | // when 14 | const {toJSON} = render(); 15 | 16 | // then 17 | expect(toJSON()).toMatchSnapshot(); 18 | }); 19 | 20 | it('Test match snapshot CategoryIcon default value', () => { 21 | // given 22 | const props = {}; 23 | 24 | // when 25 | const {toJSON} = render(); 26 | 27 | // then 28 | expect(toJSON()).toMatchSnapshot(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/CategoryIcon/__tests__/__snapshots__/CategoryIcon.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`CategoryIcon Test match snapshot CategoryIcon 1`] = ` 4 | 14 | `; 15 | 16 | exports[`CategoryIcon Test match snapshot CategoryIcon default value 1`] = ` 17 | 27 | `; 28 | -------------------------------------------------------------------------------- /src/components/CategoryIcon/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './CategoryIcon'; 2 | -------------------------------------------------------------------------------- /src/components/CategoryIcon/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 4 | import Colors from 'styles/colors'; 5 | 6 | const CONTAINED_SIZE = 30; 7 | const EXTRA_SIZE_FOR_LARGE = 20; 8 | 9 | export const Contained = styled.View<{color?: string; large?: boolean}>` 10 | background: ${({color}) => (color ? color : Colors.seconday)}; 11 | width: ${({large = false}) => 12 | large ? CONTAINED_SIZE + EXTRA_SIZE_FOR_LARGE : CONTAINED_SIZE}px; 13 | height: ${({large = false}) => 14 | large ? CONTAINED_SIZE + EXTRA_SIZE_FOR_LARGE : CONTAINED_SIZE}px; 15 | 16 | border-radius: ${({large = false}) => 17 | (large ? CONTAINED_SIZE + EXTRA_SIZE_FOR_LARGE : CONTAINED_SIZE) / 2}px; 18 | justify-content: center; 19 | align-items: center; 20 | `; 21 | 22 | export const IconItem = styled(MaterialIcons).attrs( 23 | ({large, color}: {large: boolean; color: string}) => ({ 24 | size: !large ? 24 : 32, 25 | color: color ?? Colors.gray, 26 | }), 27 | )``; 28 | -------------------------------------------------------------------------------- /src/components/CategoryPick/CategoryPick.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {useNavigation} from '@react-navigation/native'; 4 | import {useTranslation} from 'react-i18next'; 5 | import {CategoriesNavigationProp} from 'routes/StacksRoute'; 6 | 7 | import {CategoryIcon, Row} from 'components'; 8 | 9 | import {Label, Title, Touchable} from './styles'; 10 | import {Category} from 'models'; 11 | 12 | interface CategoryPickProps { 13 | category?: Category; 14 | setCategory: (value: Category) => void; 15 | } 16 | 17 | const CategoryPick = ({ 18 | category, 19 | setCategory, 20 | }: CategoryPickProps): JSX.Element => { 21 | const navigation = useNavigation(); 22 | const {t} = useTranslation('categories'); 23 | 24 | const handleCategories = () => { 25 | navigation.navigate('CategoriesStack', { 26 | screen: 'Categories', 27 | params: {setCategory}, 28 | }); 29 | }; 30 | 31 | return ( 32 | 33 | 34 | 35 | 36 | {category?.key ? t(category.key) : t('category-default')} 37 | 38 | 39 | ); 40 | }; 41 | 42 | export default CategoryPick; 43 | -------------------------------------------------------------------------------- /src/components/CategoryPick/__tests__/CategoryPick.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import CategoryPick from '../CategoryPick'; 6 | 7 | jest.mock('react-i18next', () => ({ 8 | useTranslation: () => ({t: (key: any) => key}), 9 | })); 10 | 11 | describe('CategoryPick', () => { 12 | it('Test match snapshot CategoryPick', () => { 13 | // given 14 | const props = {}; 15 | 16 | // when 17 | const {toJSON} = render(); 18 | 19 | // then 20 | expect(toJSON()).toMatchSnapshot(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/components/CategoryPick/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './CategoryPick'; 2 | -------------------------------------------------------------------------------- /src/components/CategoryPick/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | import {Text, TouchableOpacity} from 'styles/layout'; 4 | 5 | export const Label = styled(Text)` 6 | color: ${({theme}) => theme.colors.white}50; 7 | font-size: ${({theme}) => theme.fontSizes.small}; 8 | `; 9 | 10 | export const Title = styled(Label)` 11 | color: ${({theme}) => theme.colors.white}; 12 | font-size: ${({theme}) => theme.fontSizes.small}; 13 | padding-left: ${({theme}) => theme.spacing.great}px; 14 | font-weight: ${({theme}) => theme.fontWeight.bold}; 15 | `; 16 | 17 | export const Touchable = styled(TouchableOpacity)` 18 | margin-top: 5px; 19 | flex-direction: row; 20 | justify-content: center; 21 | align-items: center; 22 | `; 23 | -------------------------------------------------------------------------------- /src/components/Currency/__tests__/Currency.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | import React from 'react'; 3 | import {Text} from 'react-native'; 4 | import {render} from 'utils/test-utils'; 5 | 6 | import Currency from '../Currency'; 7 | 8 | describe('Currency', () => { 9 | it('Test match snapshot Currency', () => { 10 | // given 11 | const props = { 12 | value: 100.28, 13 | render: (text: string) => {text}, 14 | }; 15 | 16 | // when 17 | const {toJSON} = render(); 18 | 19 | // then 20 | expect(toJSON()).toMatchSnapshot(); 21 | }); 22 | 23 | it('Test if the value renders correctly', () => { 24 | // given 25 | const props = { 26 | value: 99.45, 27 | render: (text: string) => {text}, 28 | }; 29 | 30 | // when 31 | const {getByText} = render(); 32 | 33 | // then 34 | expect(getByText('R$ 99,45')).toBeTruthy(); 35 | expect(() => getByText(/20,45/i)).toThrow('Unable to find an element'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/components/Currency/__tests__/__snapshots__/Currency.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Currency Test match snapshot Currency 1`] = ` 4 | 5 | R$ 100,28 6 | 7 | `; 8 | -------------------------------------------------------------------------------- /src/components/Currency/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './Currency'; 2 | -------------------------------------------------------------------------------- /src/components/Dismiss/Dismiss.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useNavigation} from '@react-navigation/core'; 3 | 4 | import {Container, DismissIcon} from './styles'; 5 | 6 | const Dismiss = (): JSX.Element => { 7 | const {goBack} = useNavigation(); 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default Dismiss; 16 | -------------------------------------------------------------------------------- /src/components/Dismiss/__tests__/Dismiss.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import Dismiss from '../Dismiss'; 6 | 7 | describe('Dismiss', () => { 8 | it('Test match snapshot Dismiss', () => { 9 | // given 10 | const props = {}; 11 | 12 | // when 13 | const {toJSON} = render(); 14 | 15 | // then 16 | expect(toJSON()).toMatchSnapshot(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/Dismiss/__tests__/__snapshots__/Dismiss.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Dismiss Test match snapshot Dismiss 1`] = ` 4 | 22 | 42 | 󰬦 43 | 44 | 45 | `; 46 | -------------------------------------------------------------------------------- /src/components/Dismiss/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './Dismiss'; 2 | -------------------------------------------------------------------------------- /src/components/Dismiss/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; 3 | import Colors from 'styles/colors'; 4 | 5 | export const Container = styled.TouchableOpacity``; 6 | 7 | export const DismissIcon = styled(Icon).attrs({ 8 | name: 'chevron-down-circle', 9 | color: `${Colors.white}50`, 10 | size: 32, 11 | })``; 12 | -------------------------------------------------------------------------------- /src/components/EntriesList/EntriesList.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | import {useNavigation} from '@react-navigation/native'; 3 | 4 | import EntryItem from 'components/EntryItem'; 5 | import {AddEntryNavigationProp} from 'routes/StacksRoute'; 6 | 7 | import {useEntry} from 'hooks/useEntry'; 8 | import {FlatList} from './styles'; 9 | import {Entry} from 'models'; 10 | interface Props { 11 | entries: Entry[]; 12 | } 13 | 14 | const EntriesList = ({entries}: Props): JSX.Element => { 15 | const navigation = useNavigation(); 16 | const {removeEntry} = useEntry(); 17 | 18 | const onPressEntry = useCallback( 19 | (entry?: Entry) => { 20 | navigation.navigate('AddEntryStack', { 21 | screen: 'AddEntry', 22 | params: {entry}, 23 | }); 24 | }, 25 | [navigation], 26 | ); 27 | 28 | const onPressRemoveEntry = useCallback( 29 | (entry: Entry) => { 30 | removeEntry(entry); 31 | }, 32 | [removeEntry], 33 | ); 34 | 35 | const ListKeyExtractor = useCallback(item => item.id, []); 36 | 37 | const renderItem = useCallback( 38 | ({item}) => ( 39 | onPressEntry(item)} 42 | onPressRemoveEntry={onPressRemoveEntry} 43 | /> 44 | ), 45 | [onPressRemoveEntry, onPressEntry], 46 | ); 47 | 48 | return ( 49 | 55 | ); 56 | }; 57 | 58 | export default EntriesList; 59 | -------------------------------------------------------------------------------- /src/components/EntriesList/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './EntriesList'; 2 | -------------------------------------------------------------------------------- /src/components/EntriesList/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const FlatList = styled.FlatList``; 4 | -------------------------------------------------------------------------------- /src/components/EntryItem/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './EntryItem'; 2 | -------------------------------------------------------------------------------- /src/components/EntryItem/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | import {Text, TouchableOpacity} from 'styles/layout'; 4 | 5 | export const Container = styled(TouchableOpacity)` 6 | background: ${({theme}) => theme.colors.white}; 7 | margin: ${({theme}) => theme.spacing.small}px 8 | ${({theme}) => theme.spacing.tall}px; 9 | padding: ${({theme}) => theme.spacing.tall}px 10 | ${({theme}) => theme.spacing.great}px; 11 | border-radius: ${({theme}) => theme.spacing.great}px; 12 | flex-direction: row; 13 | justify-content: space-between; 14 | align-items: center; 15 | `; 16 | 17 | export const CategoryContainer = styled.View` 18 | margin-right: ${({theme}) => theme.spacing.great}px; 19 | `; 20 | export const Description = styled.View` 21 | flex: 1; 22 | justify-content: center; 23 | `; 24 | 25 | export const BaseText = styled(Text)` 26 | color: ${({theme}) => theme.colors.paragraph}; 27 | font-size: ${({theme}) => theme.fontSizes.small}; 28 | `; 29 | 30 | export const Title = styled(BaseText)` 31 | font-size: ${({theme}) => theme.fontSizes.medium}; 32 | `; 33 | export const SubTitle = styled(BaseText)``; 34 | 35 | export const Value = styled(BaseText)<{isExpense: boolean}>` 36 | color: ${({theme, isExpense = true}) => 37 | isExpense ? theme.colors.danger : theme.colors.success}; 38 | font-weight: ${({theme}) => theme.fontWeight.bold}; 39 | `; 40 | -------------------------------------------------------------------------------- /src/components/InputNumber/InputNumber.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | 3 | import {Container, Input, InputValue, Touchable, BackspaceIcon} from './styles'; 4 | import {Currency} from 'components'; 5 | 6 | interface Props { 7 | setShowKeyboard: () => void; 8 | value?: number; 9 | setValue: (value: number) => void; 10 | } 11 | 12 | const InputNumber = ({ 13 | value = 0, 14 | setValue, 15 | setShowKeyboard, 16 | }: Props): JSX.Element => { 17 | const [showBackSpace, setShowBackSpace] = useState(false); 18 | 19 | const onBackSpace = () => { 20 | if (value === 0) return; 21 | setValue(Number(value.toString().slice(0, -1))); 22 | }; 23 | 24 | useEffect(() => { 25 | setShowBackSpace(value > 0); 26 | }, [value]); 27 | 28 | return ( 29 | <> 30 | 31 | {text}} 34 | /> 35 | 36 | {!!showBackSpace && ( 37 | 38 | 39 | 40 | )} 41 | 42 | 43 | 44 | ); 45 | }; 46 | 47 | export default InputNumber; 48 | -------------------------------------------------------------------------------- /src/components/InputNumber/__tests__/InputNumber.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import InputNumber from '../InputNumber'; 6 | 7 | jest.mock('react-i18next', () => ({ 8 | useTranslation: () => ({t: (key: any) => key}), 9 | })); 10 | 11 | describe('InputNumber', () => { 12 | it('Test match snapshot InputNumber', () => { 13 | // given 14 | const props = { 15 | setShowKeyboard: jest.fn(), 16 | }; 17 | 18 | // when 19 | const {toJSON} = render(); 20 | 21 | // then 22 | expect(toJSON()).toMatchSnapshot(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/InputNumber/__tests__/__snapshots__/InputNumber.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`InputNumber Test match snapshot InputNumber 1`] = ` 4 | Array [ 5 | 16 | 28 | R$ 0,00 29 | 30 | , 31 | , 57 | ] 58 | `; 59 | -------------------------------------------------------------------------------- /src/components/InputNumber/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './InputNumber'; 2 | -------------------------------------------------------------------------------- /src/components/InputNumber/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 3 | import {TouchableOpacity, Colors, Text} from 'styles/layout'; 4 | import {width} from 'styles/mixins'; 5 | 6 | export const Container = styled.View` 7 | align-self: flex-end; 8 | flex-direction: row; 9 | margin-right: ${({theme}) => theme.spacing.great}px; 10 | `; 11 | 12 | export const Input = styled(Text)` 13 | font-family: ${({theme}) => theme.fontFamily.regular}; 14 | font-size: ${({theme}) => theme.fontSizes.extraBig}; 15 | font-weight: ${({theme}) => theme.fontWeight.regular}; 16 | color: ${({theme}) => theme.colors.white}; 17 | `; 18 | 19 | export const InputValue = styled.TouchableOpacity.attrs({})<{ 20 | addSpaceRight: boolean; 21 | }>` 22 | background: transparent; 23 | width: ${({addSpaceRight = false}) => (addSpaceRight ? width - 40 : width)}px; 24 | height: 60px; 25 | position: absolute; 26 | left: 0px; 27 | right: 60px; 28 | top: 100px; 29 | bottom: 0px; 30 | `; 31 | 32 | export const Touchable = styled(TouchableOpacity)` 33 | justify-content: center; 34 | `; 35 | export const BackspaceIcon = styled(MaterialIcons).attrs({ 36 | name: 'backspace', 37 | size: 24, 38 | color: Colors.white, 39 | })` 40 | margin-left: ${({theme}) => theme.spacing.great}px; 41 | `; 42 | -------------------------------------------------------------------------------- /src/components/List/Item.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ListItemProps} from './List'; 3 | 4 | import { 5 | Container, 6 | Content, 7 | Description, 8 | Title, 9 | Selected, 10 | SubTitle, 11 | BaseIcon, 12 | } from './styles'; 13 | 14 | const Item = ({ 15 | title, 16 | subtitle, 17 | selected, 18 | icon, 19 | onPress, 20 | }: ListItemProps): JSX.Element => { 21 | return ( 22 | 23 | {icon && } 24 | 25 | 26 | {title} 27 | {subtitle && {subtitle}} 28 | 29 | {selected} 30 | 31 | 32 | ); 33 | }; 34 | 35 | export default Item; 36 | -------------------------------------------------------------------------------- /src/components/List/List.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | 3 | import {FlatList} from './styles'; 4 | import Item from './Item'; 5 | 6 | export interface ListItemProps { 7 | id: number; 8 | title: string; 9 | subtitle?: string; 10 | selected?: string; 11 | icon?: string; 12 | onPress: () => void; 13 | } 14 | 15 | interface Props { 16 | items: ListItemProps[]; 17 | } 18 | 19 | const List = ({items}: Props): JSX.Element => { 20 | const ListKeyExtractor = useCallback(item => item.id, []); 21 | const renderItem = useCallback(({item}) => , []); 22 | 23 | return ( 24 | 31 | ); 32 | }; 33 | 34 | export default List; 35 | -------------------------------------------------------------------------------- /src/components/List/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './List'; 2 | 3 | export * from './List'; 4 | -------------------------------------------------------------------------------- /src/components/List/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import {Text, TouchableOpacity, Colors} from 'styles/layout'; 3 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 4 | 5 | export const FlatList = styled.FlatList``; 6 | 7 | export const Container = styled(TouchableOpacity)` 8 | background: ${({theme}) => theme.colors.white}; 9 | padding: ${({theme}) => theme.spacing.tall}px 10 | ${({theme}) => theme.spacing.great}px; 11 | 12 | border-bottom-width: 1px; 13 | border-color: ${({theme}) => theme.colors.grayLight}; 14 | flex-direction: row; 15 | justify-content: space-between; 16 | align-items: center; 17 | `; 18 | 19 | export const Content = styled.View` 20 | justify-content: space-between; 21 | flex-direction: row; 22 | flex: 1; 23 | align-items: center; 24 | padding-left: ${({theme}) => theme.spacing.small}px; 25 | `; 26 | 27 | export const Description = styled.View``; 28 | 29 | export const Title = styled(Text)` 30 | color: ${({theme}) => theme.colors.paragraph}; 31 | font-size: ${({theme}) => theme.fontSizes.small}; 32 | `; 33 | 34 | export const SubTitle = styled(Title)` 35 | font-size: ${({theme}) => theme.fontSizes.tiny}; 36 | `; 37 | export const Selected = styled(Title)``; 38 | 39 | export const BaseIcon = styled(MaterialIcons).attrs( 40 | ({name}: {name?: string}) => ({ 41 | size: 24, 42 | color: Colors.paragraph, 43 | name, 44 | }), 45 | )<{name?: string}>``; 46 | -------------------------------------------------------------------------------- /src/components/NumberKeyboard/__tests__/NumberKeyboard.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import NumberKeyboard from '../NumberKeyboard'; 6 | 7 | jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper'); 8 | jest.mock('react-native-vector-icons/MaterialIcons', () => 'Icon'); 9 | 10 | jest.mock('react-i18next', () => ({ 11 | useTranslation: () => ({t: (key: any) => key}), 12 | })); 13 | 14 | const MOCK = { 15 | valueDefault: 0, 16 | onDismiss: jest.fn(), 17 | onDone: jest.fn(), 18 | }; 19 | 20 | describe('NumberKeyboard', () => { 21 | it('Test match snapshot NumberKeyboard', () => { 22 | // given 23 | const props = MOCK; 24 | 25 | // when 26 | const {toJSON} = render(); 27 | 28 | // then 29 | expect(toJSON()).toMatchSnapshot(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/components/NumberKeyboard/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './NumberKeyboard'; 2 | -------------------------------------------------------------------------------- /src/components/ProgressBar/ProgressBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Container, Progress} from './styles'; 3 | 4 | interface ProgressBarProps { 5 | percent: number; 6 | color?: string; 7 | } 8 | 9 | const ProgressBar = ({percent, color}: ProgressBarProps): JSX.Element => { 10 | return ( 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default ProgressBar; 18 | -------------------------------------------------------------------------------- /src/components/ProgressBar/__tests__/ProgressBar.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import ProgressBar from '../ProgressBar'; 6 | 7 | describe('ProgressBar', () => { 8 | it('Test match snapshot ProgressBar', () => { 9 | // given 10 | const props = { 11 | percent: 10, 12 | }; 13 | 14 | // when 15 | const {toJSON} = render(); 16 | 17 | // then 18 | expect(toJSON()).toMatchSnapshot(); 19 | }); 20 | 21 | it('Test match snapshot ProgressBar with color', () => { 22 | // given 23 | const props = { 24 | percent: 45, 25 | color: "#0000000" 26 | }; 27 | 28 | // when 29 | const {toJSON} = render(); 30 | 31 | // then 32 | expect(toJSON()).toMatchSnapshot(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/components/ProgressBar/__tests__/__snapshots__/ProgressBar.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ProgressBar Test match snapshot ProgressBar 1`] = ` 4 | 14 | 27 | 28 | `; 29 | 30 | exports[`ProgressBar Test match snapshot ProgressBar with color 1`] = ` 31 | 41 | 55 | 56 | `; 57 | -------------------------------------------------------------------------------- /src/components/ProgressBar/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './ProgressBar'; 2 | -------------------------------------------------------------------------------- /src/components/ProgressBar/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | const patternHexColor = /^#([0-9a-f]{3}){1,2}$/i; 4 | 5 | export const Container = styled.View` 6 | background: ${({theme}) => theme.colors.accent}; 7 | border-radius: ${({theme}) => theme.spacing.small}px; 8 | `; 9 | 10 | export const Progress = styled.View<{percent?: number; color?: string}>` 11 | background: ${({theme, color}) => 12 | color && patternHexColor.test(color) ? color : theme.colors.seconday}50; 13 | border-radius: ${({theme}) => theme.spacing.small}px; 14 | width: ${({percent = 0}) => (percent > 100 ? 100 : percent)}%; 15 | height: 10px; 16 | `; 17 | export const Title = styled.Text` 18 | font-size: 16px; 19 | `; 20 | -------------------------------------------------------------------------------- /src/components/Row/Row.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {Container, Content, Icon} from './styles'; 4 | 5 | interface Props { 6 | icon?: string; 7 | children?: React.ReactNode; 8 | } 9 | const Row = ({icon, children}: Props): JSX.Element => { 10 | return ( 11 | 12 | {!!icon && } 13 | {children} 14 | 15 | ); 16 | }; 17 | 18 | export default Row; 19 | -------------------------------------------------------------------------------- /src/components/Row/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './Row'; 2 | -------------------------------------------------------------------------------- /src/components/Row/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 3 | 4 | import Colors from 'styles/colors'; 5 | 6 | export const Container = styled.View` 7 | margin: ${({theme}) => theme.spacing.great}px 8 | ${({theme}) => theme.spacing.tall}px 0px 9 | ${({theme}) => theme.spacing.tall}px; 10 | flex-direction: row; 11 | align-items: center; 12 | 13 | padding-bottom: ${({theme}) => theme.spacing.tall}px; 14 | border-bottom-width: 1px; 15 | border-bottom-color: ${({theme}) => theme.colors.grayLight}10; 16 | `; 17 | 18 | export const Content = styled.View` 19 | justify-content: center; 20 | `; 21 | 22 | export const Icon = styled(MaterialIcons).attrs(({name}: {name: string}) => ({ 23 | name, 24 | size: 24, 25 | color: Colors.white, 26 | }))` 27 | margin-right: ${({theme}) => theme.spacing.great}px; 28 | `; 29 | -------------------------------------------------------------------------------- /src/components/Strap/Strap.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {Container} from './styles'; 4 | 5 | const Strap = (): JSX.Element => { 6 | return ; 7 | }; 8 | 9 | export default Strap; 10 | -------------------------------------------------------------------------------- /src/components/Strap/__tests__/Strap.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import Strap from '../Strap'; 6 | 7 | describe('Strap', () => { 8 | it('Test match snapshot Strap', () => { 9 | // given 10 | const props = {}; 11 | 12 | // when 13 | const {toJSON} = render(); 14 | 15 | // then 16 | expect(toJSON()).toMatchSnapshot(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/Strap/__tests__/__snapshots__/Strap.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Strap Test match snapshot Strap 1`] = ` 4 | 20 | `; 21 | -------------------------------------------------------------------------------- /src/components/Strap/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './Strap'; 2 | -------------------------------------------------------------------------------- /src/components/Strap/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const Container = styled.View` 4 | height: 5px; 5 | width: 100px; 6 | border-radius: 2.5px; 7 | margin: 10px 0px; 8 | background: ${({theme}) => theme.colors.gray}10; 9 | `; 10 | -------------------------------------------------------------------------------- /src/components/SwipeRemoveButton/SwipeRemoveButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | 4 | import {Container, RemoveIcon, Label} from './styles'; 5 | 6 | interface Props { 7 | onPressRemove: () => void; 8 | } 9 | 10 | const SwipeRemoveButton = ({onPressRemove}: Props): JSX.Element => { 11 | const {t} = useTranslation('global'); 12 | 13 | return ( 14 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | export default SwipeRemoveButton; 28 | -------------------------------------------------------------------------------- /src/components/SwipeRemoveButton/__tests__/__snapshots__/SwipeRemoveButton.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`SwipeRemoveButton Test match snapshot SwipeRemoveButton 1`] = ` 4 | 38 | 48 | 60 | Remover 61 | 62 | 63 | `; 64 | -------------------------------------------------------------------------------- /src/components/SwipeRemoveButton/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './SwipeRemoveButton'; 2 | -------------------------------------------------------------------------------- /src/components/SwipeRemoveButton/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 3 | 4 | import {Text, TouchableOpacity} from 'styles/layout'; 5 | import Colors from 'styles/colors'; 6 | 7 | export const Container = styled(TouchableOpacity)` 8 | margin: ${({theme}) => theme.spacing.small}px 9 | ${({theme}) => theme.spacing.great}px ${({theme}) => theme.spacing.small}px 10 | 0px; 11 | padding: 0px ${({theme}) => theme.spacing.tall}px; 12 | background: ${({theme}) => theme.colors.danger}; 13 | border-radius: ${({theme}) => theme.spacing.great}px; 14 | justify-content: center; 15 | align-items: center; 16 | flex-direction: row; 17 | `; 18 | export const Label = styled(Text)` 19 | color: ${({theme}) => theme.colors.white}; 20 | font-size: ${({theme}) => theme.fontSizes.small}; 21 | `; 22 | 23 | export const RemoveIcon = styled(MaterialIcons).attrs({ 24 | size: 24, 25 | color: Colors.white, 26 | name: 'delete', 27 | })``; 28 | -------------------------------------------------------------------------------- /src/components/Toolbar/Toolbar.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | 4 | import {Dismiss} from 'components'; 5 | import {Container, Search, Title, Touchable, SearchIcon} from './styles'; 6 | 7 | interface Props { 8 | query?: string; 9 | hideSearch?: boolean; 10 | title?: string; 11 | onChangeText?: (query: string) => void; 12 | } 13 | 14 | const Toolbar = ({ 15 | title, 16 | hideSearch = false, 17 | query, 18 | onChangeText, 19 | }: Props): JSX.Element => { 20 | const {t} = useTranslation('global'); 21 | const [showSearch, setShowSearch] = useState(false); 22 | 23 | return ( 24 | 25 | 26 | 27 | {title && !showSearch && {title}} 28 | 29 | {!hideSearch && onChangeText && ( 30 | <> 31 | {(!title || showSearch) && ( 32 | onChangeText(term)} 39 | /> 40 | )} 41 | 42 | {(title || showSearch) && ( 43 | setShowSearch(prev => !prev)}> 44 | 45 | 46 | )} 47 | 48 | )} 49 | 50 | ); 51 | }; 52 | 53 | export default Toolbar; 54 | -------------------------------------------------------------------------------- /src/components/Toolbar/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './Toolbar'; 2 | -------------------------------------------------------------------------------- /src/components/Toolbar/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import {Text, Colors, TouchableOpacity} from 'styles/layout'; 3 | 4 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 5 | 6 | export const Container = styled.View` 7 | height: 60px; 8 | align-items: center; 9 | padding: 0px ${({theme}) => theme.spacing.tall}px; 10 | background: ${({theme}) => theme.colors.accent}; 11 | flex-direction: row; 12 | justify-content: space-between; 13 | `; 14 | 15 | export const Touchable = styled(TouchableOpacity)``; 16 | 17 | export const Title = styled(Text)` 18 | flex: 1; 19 | color: ${({theme}) => theme.colors.white}; 20 | padding-left: ${({theme}) => theme.spacing.great}px; 21 | `; 22 | 23 | export const Search = styled.TextInput.attrs({ 24 | placeholderTextColor: `${Colors.white}50`, 25 | })` 26 | flex: 1; 27 | background: ${({theme}) => theme.colors.primary}80; 28 | padding: ${({theme}) => theme.spacing.great}px; 29 | margin-left: ${({theme}) => theme.spacing.great}px; 30 | border-radius: ${({theme}) => theme.spacing.great / 2}px; 31 | color: ${({theme}) => theme.colors.white}; 32 | `; 33 | 34 | export const SearchIcon = styled(MaterialIcons).attrs( 35 | ({name}: {name: string}) => ({ 36 | size: 24, 37 | color: Colors.paragraph, 38 | name: name ? name : 'search', 39 | }), 40 | )``; 41 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export {default as CategoryIcon} from './CategoryIcon'; 2 | export {default as EntriesList} from './EntriesList'; 3 | export {default as Currency} from './Currency'; 4 | export {default as Button} from './Button'; 5 | export {default as Dismiss} from './Dismiss'; 6 | export {default as SwipeRemoveButton} from './SwipeRemoveButton'; 7 | export {default as NumberKeyboard} from './NumberKeyboard'; 8 | export {default as Strap} from './Strap'; 9 | export {default as List} from './List'; 10 | export {default as Row} from './Row'; 11 | export {default as Toolbar} from './Toolbar'; 12 | export {default as InputNumber} from './InputNumber'; 13 | export {default as CategoryPick} from './CategoryPick'; 14 | export {default as ProgressBar} from './ProgressBar'; 15 | -------------------------------------------------------------------------------- /src/contexts/index.ts: -------------------------------------------------------------------------------- 1 | export {default as BudgetProvider} from './BudgetContext'; 2 | -------------------------------------------------------------------------------- /src/database/collections.ts: -------------------------------------------------------------------------------- 1 | export enum COLLECTIONS { 2 | ENTRIES = 'entries', 3 | CATEGORIES = 'categories', 4 | BUDGETS = 'budgets', 5 | } 6 | 7 | export default COLLECTIONS; 8 | -------------------------------------------------------------------------------- /src/database/index.ts: -------------------------------------------------------------------------------- 1 | export * from './collections'; 2 | 3 | export {default as migrations} from './migrations'; 4 | export {default as modelClasses} from './modelClasses'; 5 | export {default as schema} from './schema'; 6 | export {default as seeds} from './seeds'; 7 | -------------------------------------------------------------------------------- /src/database/migrations.ts: -------------------------------------------------------------------------------- 1 | import {schemaMigrations} from '@nozbe/watermelondb/Schema/migrations'; 2 | 3 | export const VERSION_MIGRATION = 1; 4 | 5 | export default schemaMigrations({ 6 | migrations: [], 7 | }); 8 | -------------------------------------------------------------------------------- /src/database/modelClasses.ts: -------------------------------------------------------------------------------- 1 | import {Entry, Category, Budget} from 'models'; 2 | 3 | export const modelClasses = [Entry, Category, Budget]; 4 | 5 | export default modelClasses; 6 | -------------------------------------------------------------------------------- /src/database/schema.ts: -------------------------------------------------------------------------------- 1 | import {appSchema, tableSchema} from '@nozbe/watermelondb'; 2 | 3 | import COLLECTIONS from './collections'; 4 | import {VERSION_MIGRATION} from './migrations'; 5 | 6 | import {COLUMNS as EntryColumns} from 'models/Entry'; 7 | import {COLUMNS as CategoryColumns} from 'models/Category'; 8 | import {COLUMNS as BudgetColumns} from 'models/Budget'; 9 | 10 | export const DATABASE_NAME = 'LogFinanceDb'; 11 | 12 | export const dbScheme = appSchema({ 13 | version: VERSION_MIGRATION, 14 | tables: [ 15 | tableSchema({ 16 | name: COLLECTIONS.ENTRIES, 17 | columns: EntryColumns, 18 | }), 19 | tableSchema({ 20 | name: COLLECTIONS.CATEGORIES, 21 | columns: CategoryColumns, 22 | }), 23 | tableSchema({ 24 | name: COLLECTIONS.BUDGETS, 25 | columns: BudgetColumns, 26 | }), 27 | ], 28 | }); 29 | 30 | export default dbScheme; 31 | -------------------------------------------------------------------------------- /src/database/seeds.ts: -------------------------------------------------------------------------------- 1 | import RNFS from 'react-native-fs'; 2 | 3 | import {getDatabase} from 'services/database'; 4 | 5 | import {getCategoryCollection} from 'repositories/CategoryRepository'; 6 | import CATEGORIES from './categories.json'; 7 | 8 | export const run = async (): Promise => { 9 | const path = RNFS.DocumentDirectoryPath + '/seed.json'; 10 | 11 | const alreadyCreated = await RNFS.exists(path); 12 | if (alreadyCreated) return; 13 | 14 | await getDatabase().write(() => getDatabase().unsafeResetDatabase()); 15 | 16 | await getDatabase().write(async () => { 17 | const records = CATEGORIES.map(({description, key, color}) => { 18 | return getCategoryCollection().prepareCreate(record => { 19 | record.description = description; 20 | record.key = key; 21 | record.color = color; 22 | }); 23 | }); 24 | 25 | await getDatabase().batch(...records); 26 | 27 | const datetime = new Date().toISOString(); 28 | await RNFS.writeFile(path, `{"datetime": "${datetime}"}`); 29 | }); 30 | }; 31 | 32 | export default {run}; 33 | -------------------------------------------------------------------------------- /src/hooks/__tests__/__snapshots__/useEntry.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`EntryProvider Test match snapshot useEntry 1`] = ` 4 | 5 | Teste 6 | 7 | `; 8 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export {default as useEntry} from './useEntry'; 2 | export {default as EntryContext} from './useEntry'; 3 | 4 | export {default as useCategory} from './useCategory'; 5 | export {default as useDialog} from './useDialog'; 6 | -------------------------------------------------------------------------------- /src/hooks/useCategory.tsx: -------------------------------------------------------------------------------- 1 | import {useState, useCallback, useEffect} from 'react'; 2 | 3 | import {Category} from 'models'; 4 | import {CategoryRepository} from 'repositories'; 5 | import {Subscription} from 'rxjs'; 6 | 7 | let categoryDefaultSubscription = new Subscription(); 8 | 9 | interface UseCategoryProps { 10 | categoryDefault?: Category; 11 | setCategoryDefault: (value: Category) => void; 12 | } 13 | 14 | export const useCategory = (): UseCategoryProps => { 15 | const [categoryDefault, setCategoryDefault] = useState(); 16 | 17 | const loadCategoryDefault = useCallback(() => { 18 | categoryDefaultSubscription = 19 | CategoryRepository.getDefaultCategory().subscribe(categories => { 20 | if (!categories.length) return; 21 | 22 | const [firstCategory] = categories; 23 | setCategoryDefault(firstCategory); 24 | }); 25 | }, []); 26 | 27 | useEffect(() => { 28 | loadCategoryDefault(); 29 | return categoryDefaultSubscription.unsubscribe(); 30 | }, [loadCategoryDefault]); 31 | 32 | return { 33 | categoryDefault, 34 | setCategoryDefault, 35 | }; 36 | }; 37 | 38 | export default useCategory; 39 | -------------------------------------------------------------------------------- /src/hooks/useDialog.ts: -------------------------------------------------------------------------------- 1 | import {Alert} from 'react-native'; 2 | import {useTranslation} from 'react-i18next'; 3 | 4 | export const DIALOG_OPTIONS = { 5 | YES: 'yes', 6 | NO: 'no', 7 | }; 8 | 9 | interface AlertYesOrNoProps { 10 | title: string; 11 | message: string; 12 | } 13 | 14 | interface UseDialogProps { 15 | dialog: (argments: AlertYesOrNoProps) => void; 16 | alertYesOrNo: (argments: AlertYesOrNoProps) => Promise; 17 | } 18 | 19 | export const useDialog = (): UseDialogProps => { 20 | const {t} = useTranslation('global'); 21 | 22 | const dialog = ({title, message}: AlertYesOrNoProps) => 23 | new Promise(resolve => { 24 | const actions = [ 25 | { 26 | text: t('yes'), 27 | onPress: () => resolve(DIALOG_OPTIONS.YES), 28 | }, 29 | { 30 | text: t('no'), 31 | }, 32 | ]; 33 | Alert.alert(title, message, actions, {cancelable: false}); 34 | }); 35 | 36 | const alertYesOrNo = async ({ 37 | title, 38 | message, 39 | }: AlertYesOrNoProps): Promise => 40 | (await dialog({title, message})) === DIALOG_OPTIONS.YES; 41 | 42 | return { 43 | dialog, 44 | alertYesOrNo, 45 | }; 46 | }; 47 | 48 | export default useDialog; 49 | -------------------------------------------------------------------------------- /src/interfaces/IBudget.ts: -------------------------------------------------------------------------------- 1 | import {Category} from 'models'; 2 | 3 | export default interface IBudget { 4 | description?: string; 5 | category: Category; 6 | value: number; 7 | } 8 | -------------------------------------------------------------------------------- /src/interfaces/ICategory.ts: -------------------------------------------------------------------------------- 1 | export default interface ICategory { 2 | id: number; 3 | description: string; 4 | key: string; 5 | color: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/IEntry.ts: -------------------------------------------------------------------------------- 1 | import {Category} from 'models'; 2 | 3 | export type EntryType = 'expense' | 'earning'; 4 | export default interface IEntry { 5 | description?: string; 6 | type: EntryType; 7 | value: number; 8 | dateAt: Date; 9 | category: Category; 10 | } 11 | -------------------------------------------------------------------------------- /src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export {default as ICategory} from './ICategory'; 2 | export {default as IEntry} from './IEntry'; 3 | export {default as IBudget} from './IBudget'; 4 | -------------------------------------------------------------------------------- /src/locales/index.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import {initReactI18next} from 'react-i18next'; 3 | import {DEFAULT_LANGUAGE, languageDetector, resources} from 'services/language'; 4 | 5 | i18n 6 | .use(languageDetector) 7 | .use(initReactI18next) 8 | .init({ 9 | compatibilityJSON: 'v3', 10 | resources, 11 | fallbackLng: DEFAULT_LANGUAGE, 12 | debug: true, 13 | interpolation: { 14 | escapeValue: false, 15 | }, 16 | }); 17 | 18 | export default i18n; 19 | -------------------------------------------------------------------------------- /src/models/Budget.ts: -------------------------------------------------------------------------------- 1 | import {ColumnSchema, Model, Relation} from '@nozbe/watermelondb'; 2 | import { 3 | field, 4 | text, 5 | relation, 6 | readonly, 7 | date, 8 | } from '@nozbe/watermelondb/decorators'; 9 | 10 | import COLLECTIONS from 'database/collections'; 11 | import {Category} from 'models'; 12 | 13 | export default class Budget extends Model { 14 | static table = COLLECTIONS.BUDGETS; 15 | 16 | @field('value') value!: number; 17 | @text('description') description!: string; 18 | 19 | @relation(COLLECTIONS.CATEGORIES, 'category_id') 20 | category!: Relation; 21 | 22 | @readonly 23 | @date('created_at') 24 | createdAt!: Date; 25 | } 26 | 27 | export const COLUMNS: ColumnSchema[] = [ 28 | {name: 'description', type: 'string', isOptional: true}, 29 | {name: 'value', type: 'number'}, 30 | {name: 'category_id', type: 'string', isIndexed: true}, 31 | {name: 'created_at', type: 'number'}, 32 | ]; 33 | -------------------------------------------------------------------------------- /src/models/Category.ts: -------------------------------------------------------------------------------- 1 | import {ColumnSchema, Model} from '@nozbe/watermelondb'; 2 | import {field, text} from '@nozbe/watermelondb/decorators'; 3 | 4 | import COLLECTIONS from 'database/collections'; 5 | 6 | export default class Category extends Model { 7 | static table = COLLECTIONS.CATEGORIES; 8 | 9 | @text('description') description!: string; 10 | @field('key') key!: string; 11 | 12 | @field('color') color!: string; 13 | } 14 | 15 | export const COLUMNS: ColumnSchema[] = [ 16 | {name: 'description', type: 'string'}, 17 | {name: 'key', type: 'string'}, 18 | {name: 'color', type: 'string'}, 19 | ]; 20 | -------------------------------------------------------------------------------- /src/models/Entry.ts: -------------------------------------------------------------------------------- 1 | import {ColumnSchema, Model, Relation} from '@nozbe/watermelondb'; 2 | import { 3 | field, 4 | text, 5 | date, 6 | readonly, 7 | relation, 8 | } from '@nozbe/watermelondb/decorators'; 9 | 10 | import COLLECTIONS from 'database/collections'; 11 | import {EntryType} from 'interfaces/IEntry'; 12 | import Category from './Category'; 13 | 14 | export default class Entry extends Model { 15 | static table = COLLECTIONS.ENTRIES; 16 | 17 | @text('description') description?: string; 18 | @field('type') type!: EntryType; 19 | @field('value') value!: number; 20 | @date('date_at') dateAt!: Date; 21 | 22 | @relation(COLLECTIONS.CATEGORIES, 'category_id') 23 | category!: Relation; 24 | 25 | @readonly 26 | @date('created_at') 27 | createdAt!: Date; 28 | 29 | @readonly 30 | @date('updated_at') 31 | updatedAt!: Date; 32 | } 33 | 34 | export const COLUMNS: ColumnSchema[] = [ 35 | {name: 'description', type: 'string', isOptional: true}, 36 | {name: 'type', type: 'string'}, 37 | {name: 'value', type: 'number'}, 38 | {name: 'date_at', type: 'number', isIndexed: true}, 39 | {name: 'category_id', type: 'string', isIndexed: true}, 40 | {name: 'created_at', type: 'number'}, 41 | {name: 'updated_at', type: 'number'}, 42 | ]; 43 | -------------------------------------------------------------------------------- /src/models/index.ts: -------------------------------------------------------------------------------- 1 | export {default as Category} from './Category'; 2 | export {default as Entry} from './Entry'; 3 | export {default as Budget} from './Budget'; 4 | -------------------------------------------------------------------------------- /src/repositories/BudgetRepository.ts: -------------------------------------------------------------------------------- 1 | import {Collection} from '@nozbe/watermelondb'; 2 | import {Observable} from 'rxjs'; 3 | import {Budget} from 'models'; 4 | import {getDatabase} from 'services/database'; 5 | import {COLLECTIONS} from 'database'; 6 | import {IBudget} from 'interfaces'; 7 | 8 | const getBudgetCollection = (): Collection => 9 | getDatabase().collections.get(COLLECTIONS.BUDGETS); 10 | 11 | export const getBudgetsObserve = (): Observable => 12 | getBudgetCollection().query().observe(); 13 | 14 | const fill = (record: Budget, data: IBudget) => { 15 | const {description, value, category} = data; 16 | if (description) record.description = description; 17 | record.value = value; 18 | record.category.set(category); 19 | }; 20 | 21 | export const addBudget = async (data: IBudget): Promise => { 22 | return getDatabase().write(async () => { 23 | return getBudgetCollection().create(record => { 24 | fill(record, data); 25 | }); 26 | }); 27 | }; 28 | 29 | export const updateBudget = async ( 30 | budget: Budget, 31 | data: IBudget, 32 | ): Promise => { 33 | return getDatabase().write(async () => { 34 | return budget.update(record => { 35 | fill(record, data); 36 | }); 37 | }); 38 | }; 39 | 40 | export const removeBudget = async (budget: Budget): Promise => { 41 | return getDatabase().write(async () => { 42 | await budget.destroyPermanently(); 43 | }); 44 | }; 45 | 46 | export default {getBudgetsObserve, addBudget, updateBudget, removeBudget}; 47 | -------------------------------------------------------------------------------- /src/repositories/CategoryRepository.ts: -------------------------------------------------------------------------------- 1 | import {Collection, Q} from '@nozbe/watermelondb'; 2 | import {Observable} from 'rxjs'; 3 | import {Category} from 'models'; 4 | import {getDatabase} from '../services/database'; 5 | import {COLLECTIONS} from 'database'; 6 | 7 | export const getCategoryCollection = (): Collection => 8 | getDatabase().collections.get(COLLECTIONS.CATEGORIES); 9 | 10 | export const getCategories = (): Observable => 11 | getCategoryCollection().query().observe(); 12 | 13 | export const getDefaultCategory = (): Observable => 14 | getCategoryCollection().query(Q.where('key', 'others')).observe(); 15 | 16 | export default {getCategoryCollection, getCategories, getDefaultCategory}; 17 | -------------------------------------------------------------------------------- /src/repositories/index.ts: -------------------------------------------------------------------------------- 1 | export {default as CategoryRepository} from './CategoryRepository'; 2 | export {default as EntryRepository} from './EntryRepository'; 3 | export {default as BudgetRepository} from './BudgetRepository'; 4 | -------------------------------------------------------------------------------- /src/routes/components/BottomTabs/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './BottomTabs'; 2 | -------------------------------------------------------------------------------- /src/routes/components/BottomTabs/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import {getBottomSpace} from 'react-native-iphone-x-helper'; 3 | 4 | import {Text, TouchableOpacity} from 'styles/layout'; 5 | import shadow from 'styles/shadow'; 6 | import {isIos, width} from 'styles/mixins'; 7 | 8 | const TAB_BAR_HEIGHT_WITH_TITLE = (isIos ? 40 : 80) + getBottomSpace(); 9 | const TAB_BAR_HEIGHT_WITHOUT_TITLE = (isIos ? 60 : 100) + getBottomSpace(); 10 | 11 | export const Button = styled(TouchableOpacity).attrs( 12 | ({disableActiveOpacity = false}: {disableActiveOpacity: boolean}) => ({ 13 | activeOpacity: disableActiveOpacity ? 1 : 0.8, 14 | }), 15 | )<{disableActiveOpacity: boolean}>` 16 | display: flex; 17 | flex-direction: column; 18 | align-items: center; 19 | width: ${width * 0.2}px; 20 | `; 21 | 22 | export const Label = styled(Text)<{isFocused: boolean}>` 23 | color: ${({theme, isFocused = false}) => 24 | isFocused ? theme.colors.white : theme.colors.paragraph}; 25 | font-size: ${({theme}) => theme.fontSizes.small}; 26 | `; 27 | 28 | export const Container = styled.View<{showTitle: boolean}>` 29 | background-color: ${({theme}) => theme.colors.primary}; 30 | height: ${({showTitle = false}) => 31 | showTitle ? TAB_BAR_HEIGHT_WITH_TITLE : TAB_BAR_HEIGHT_WITHOUT_TITLE}px; 32 | flex-direction: row; 33 | padding-bottom: ${({theme}) => theme.spacing.tall}px; 34 | justify-content: space-around; 35 | align-items: center; 36 | padding-vertical: 10px; 37 | border-top-left-radius: ${({theme}) => theme.spacing.tall}px; 38 | border-top-right-radius: ${({theme}) => theme.spacing.tall}px; 39 | position: absolute; 40 | bottom: 0; 41 | left: 0; 42 | right: 0; 43 | ${shadow} 44 | `; 45 | -------------------------------------------------------------------------------- /src/routes/components/TabIcon/TabIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | TAB_KEY_HOME, 5 | TAB_KEY_ADD_ENTRY, 6 | TAB_KEY_REPORTS, 7 | TAB_KEY_HISTORIC, 8 | TAB_KEY_BUDGET, 9 | } from 'routes/TabsRoute'; 10 | 11 | import Colors from 'styles/colors'; 12 | 13 | import {Icon, AddButtonContainer} from './styles'; 14 | 15 | interface Props { 16 | route: string; 17 | focused?: boolean; 18 | } 19 | 20 | const TabIcon = ({route, focused}: Props): JSX.Element => { 21 | const icons = { 22 | [`${TAB_KEY_HOME}`]: 'home', 23 | [`${TAB_KEY_ADD_ENTRY}`]: 'add', 24 | [`${TAB_KEY_REPORTS}`]: 'assessment', 25 | [`${TAB_KEY_HISTORIC}`]: 'article', 26 | [`${TAB_KEY_BUDGET}`]: 'gps-fixed', 27 | }; 28 | 29 | const icon = icons[route] ?? 'crop-square'; 30 | 31 | if (route === TAB_KEY_ADD_ENTRY) { 32 | return ( 33 | 34 | 35 | 36 | ); 37 | } 38 | 39 | return ; 40 | }; 41 | 42 | export default TabIcon; 43 | -------------------------------------------------------------------------------- /src/routes/components/TabIcon/__tests__/TabIcon.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | import React from 'react'; 3 | 4 | import {render} from 'utils/test-utils'; 5 | 6 | import TabIcon from '../TabIcon'; 7 | 8 | describe('TabIcon', () => { 9 | it('Test match snapshot TabIcon', () => { 10 | // given 11 | const props = { 12 | route: 'Home', 13 | focused: true, 14 | }; 15 | 16 | // when 17 | const {toJSON} = render(); 18 | 19 | // then 20 | expect(toJSON()).toMatchSnapshot(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/routes/components/TabIcon/__tests__/__snapshots__/TabIcon.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`TabIcon Test match snapshot TabIcon 1`] = ` 4 | 14 | `; 15 | -------------------------------------------------------------------------------- /src/routes/components/TabIcon/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './TabIcon'; 2 | -------------------------------------------------------------------------------- /src/routes/components/TabIcon/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 3 | import shadow from 'styles/shadow'; 4 | 5 | export const Icon = styled(MaterialIcons).attrs({ 6 | size: 32, 7 | })``; 8 | 9 | export const AddButtonContainer = styled.View` 10 | background-color: ${({theme}) => theme.colors.seconday}; 11 | padding: ${({theme}) => theme.spacing.tall}px; 12 | justify-content: center; 13 | align-items: center; 14 | position: absolute; 15 | top: -60px; 16 | align-self: center; 17 | z-index: 100; 18 | border-radius: 50px; 19 | elevation: 20; 20 | ${shadow} 21 | `; 22 | -------------------------------------------------------------------------------- /src/routes/components/index.ts: -------------------------------------------------------------------------------- 1 | export {default as BottomTabs} from './BottomTabs'; 2 | -------------------------------------------------------------------------------- /src/screens/AddBudget/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './AddBudgetScreen'; 2 | -------------------------------------------------------------------------------- /src/screens/AddBudget/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import Colors from 'styles/colors'; 3 | import {Text} from 'styles/layout'; 4 | 5 | export const Container = styled.View` 6 | flex: 1; 7 | background: ${({theme}) => theme.colors.gray}; 8 | `; 9 | 10 | export const Content = styled.View` 11 | padding: ${({theme}) => theme.spacing.tall}px 0px; 12 | `; 13 | 14 | export const Title = styled.Text` 15 | font-size: 16px; 16 | `; 17 | 18 | export const Label = styled(Text)` 19 | color: ${({theme}) => theme.colors.white}50; 20 | font-size: ${({theme}) => theme.fontSizes.small}; 21 | `; 22 | 23 | export const Input = styled.TextInput.attrs({ 24 | numberOfLines: 1, 25 | placeholderTextColor: Colors.grayLight, 26 | blurOnSubmit: true, 27 | })` 28 | font-family: ${({theme}) => theme.fontFamily.regular}; 29 | font-size: ${({theme}) => theme.fontSizes.small}; 30 | color: ${({theme}) => theme.colors.grayLight}; 31 | font-weight: ${({theme}) => theme.fontWeight.bold}; 32 | `; 33 | 34 | export const SubmitContainer = styled.View` 35 | width: 100%; 36 | padding: ${({theme}) => theme.spacing.tall}px 37 | ${({theme}) => theme.spacing.venti}px; 38 | `; 39 | -------------------------------------------------------------------------------- /src/screens/AddEntry/__tests__/AddEntryScreen.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import AddEntryScreen from '../AddEntryScreen'; 6 | 7 | jest.mock('react-i18next', () => ({ 8 | useTranslation: () => ({t: (key: any) => key}), 9 | })); 10 | 11 | jest.mock('@react-navigation/native', () => { 12 | const actualNav = jest.requireActual('@react-navigation/native'); 13 | return { 14 | ...actualNav, 15 | useRoute: () => jest.fn(), 16 | }; 17 | }); 18 | 19 | describe('AddEntryScreen', () => { 20 | it('Test match snapshot AddEntryScreen', () => { 21 | // given 22 | const props = {}; 23 | 24 | // when 25 | const {toJSON} = render(); 26 | 27 | // then 28 | expect(toJSON()).toMatchSnapshot(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryCategory/EntryCategory.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {useNavigation} from '@react-navigation/native'; 4 | import {useTranslation} from 'react-i18next'; 5 | import {CategoriesNavigationProp} from 'routes/StacksRoute'; 6 | import {useEntry} from 'hooks/useEntry'; 7 | 8 | import {CategoryIcon, Row} from 'components'; 9 | 10 | import {Label, Title, Touchable} from './styles'; 11 | 12 | const EntryCategory = (): JSX.Element => { 13 | const navigation = useNavigation(); 14 | const {t} = useTranslation('categories'); 15 | const {category, setCategory} = useEntry(); 16 | 17 | const handleCategories = () => { 18 | navigation.navigate('CategoriesStack', { 19 | screen: 'Categories', 20 | params: {setCategory}, 21 | }); 22 | }; 23 | 24 | return ( 25 | 26 | 27 | 28 | 29 | {category?.key ? t(category.key) : t('category-default')} 30 | 31 | 32 | ); 33 | }; 34 | 35 | export default EntryCategory; 36 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryCategory/__tests__/EntryCategory.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import EntryCategory from '../EntryCategory'; 6 | 7 | jest.mock('react-i18next', () => ({ 8 | useTranslation: () => ({t: (key: any) => key}), 9 | })); 10 | 11 | describe('EntryCategory', () => { 12 | it('Test match snapshot EntryCategory', () => { 13 | // given 14 | const props = {}; 15 | 16 | // when 17 | const {toJSON} = render(); 18 | 19 | // then 20 | expect(toJSON()).toMatchSnapshot(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryCategory/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './EntryCategory'; 2 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryCategory/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | import {Text, TouchableOpacity} from 'styles/layout'; 4 | 5 | export const Label = styled(Text)` 6 | color: ${({theme}) => theme.colors.white}50; 7 | font-size: ${({theme}) => theme.fontSizes.small}; 8 | `; 9 | 10 | export const Title = styled(Label)` 11 | color: ${({theme}) => theme.colors.white}; 12 | font-size: ${({theme}) => theme.fontSizes.small}; 13 | padding-left: ${({theme}) => theme.spacing.great}px; 14 | font-weight: ${({theme}) => theme.fontWeight.bold}; 15 | `; 16 | 17 | export const Touchable = styled(TouchableOpacity)` 18 | margin-top: 5px; 19 | flex-direction: row; 20 | justify-content: center; 21 | align-items: center; 22 | `; 23 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryDate/EntryDate.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import DateTimePickerModal from 'react-native-modal-datetime-picker'; 3 | import {useTranslation} from 'react-i18next'; 4 | import {isToday, format} from 'date-fns'; 5 | 6 | import {useEntry} from 'hooks/useEntry'; 7 | import {Row} from 'components'; 8 | 9 | import {Label, Title, Touchable} from './styles'; 10 | 11 | const EntryDate = (): JSX.Element => { 12 | const {t} = useTranslation('add'); 13 | const {dateAt = new Date(), setDateAt} = useEntry(); 14 | const [isDatePickerVisible, setDatePickerVisibility] = useState(false); 15 | 16 | const showDatePicker = () => { 17 | setDatePickerVisibility(true); 18 | }; 19 | 20 | const hideDatePicker = () => { 21 | setDatePickerVisibility(false); 22 | }; 23 | 24 | const handleConfirm = (dateSelected: Date) => { 25 | setDateAt(dateSelected); 26 | hideDatePicker(); 27 | }; 28 | 29 | const prefix = `${isToday(dateAt) ? t('today') + ', ' : t('day')}`; 30 | const formatPattern = `'${prefix}' dd '${t('of')}' MMMM', ${t( 31 | 'at', 32 | )} ' HH:mm'h'`; 33 | 34 | const formattedDate = format(dateAt, formatPattern); 35 | 36 | return ( 37 | <> 38 | 39 | 40 | 41 | {formattedDate} 42 | 43 | 44 | 51 | 52 | ); 53 | }; 54 | 55 | export default EntryDate; 56 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryDate/__tests__/EntryDate.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import EntryDate from '../EntryDate'; 6 | 7 | jest.mock('react-i18next', () => ({ 8 | useTranslation: () => ({t: (key: any) => key}), 9 | })); 10 | 11 | describe('EntryDate', () => { 12 | it('Test match snapshot EntryDate', () => { 13 | // given 14 | const props = {}; 15 | 16 | // when 17 | const {toJSON} = render(); 18 | 19 | // then 20 | expect(toJSON()).toMatchSnapshot(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryDate/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './EntryDate'; 2 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryDate/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | import {Text, TouchableOpacity} from 'styles/layout'; 4 | 5 | export const Label = styled(Text)` 6 | color: ${({theme}) => theme.colors.white}50; 7 | font-size: ${({theme}) => theme.fontSizes.small}; 8 | `; 9 | 10 | export const Touchable = styled(TouchableOpacity)``; 11 | 12 | export const Title = styled(Label)` 13 | color: ${({theme}) => theme.colors.white}; 14 | font-size: ${({theme}) => theme.fontSizes.small}; 15 | font-weight: ${({theme}) => theme.fontWeight.bold}; 16 | `; 17 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryDescription/EntryDescription.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | import {Row} from 'components'; 4 | 5 | import {Label, Input} from './styles'; 6 | import {useEntry} from 'hooks/useEntry'; 7 | 8 | const EntryDescription = (): JSX.Element => { 9 | const {description, setDescription} = useEntry(); 10 | const {t} = useTranslation('add'); 11 | 12 | return ( 13 | 14 | 15 | 20 | 21 | ); 22 | }; 23 | 24 | export default EntryDescription; 25 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryDescription/__tests__/EntryDescription.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import EntryDescription from '../EntryDescription'; 6 | 7 | jest.mock('react-i18next', () => ({ 8 | useTranslation: () => ({t: (key: any) => key}), 9 | })); 10 | 11 | describe('EntryDescription', () => { 12 | it('Test match snapshot EntryDescription', () => { 13 | // given 14 | const props = {}; 15 | 16 | // when 17 | const {toJSON} = render(); 18 | 19 | // then 20 | expect(toJSON()).toMatchSnapshot(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryDescription/__tests__/__snapshots__/EntryDescription.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`EntryDescription Test match snapshot EntryDescription 1`] = ` 4 | 21 | 30 | 42 | description 43 | 44 | 60 | 61 | 62 | `; 63 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryDescription/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './EntryDescription'; 2 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/EntryDescription/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | import Colors from 'styles/colors'; 4 | import {Text} from 'styles/layout'; 5 | 6 | export const Label = styled(Text)` 7 | color: ${({theme}) => theme.colors.white}50; 8 | font-size: ${({theme}) => theme.fontSizes.small}; 9 | `; 10 | 11 | export const Input = styled.TextInput.attrs({ 12 | numberOfLines: 1, 13 | placeholderTextColor: Colors.grayLight, 14 | blurOnSubmit: true, 15 | })` 16 | font-family: ${({theme}) => theme.fontFamily.regular}; 17 | font-size: ${({theme}) => theme.fontSizes.small}; 18 | color: ${({theme}) => theme.colors.grayLight}; 19 | font-weight: ${({theme}) => theme.fontWeight.bold}; 20 | `; 21 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/MenuEntryType/MenuEntryType.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {useTranslation} from 'react-i18next'; 4 | import {useEntry} from 'hooks/useEntry'; 5 | import {Container, Touchable, Label, LineActive} from './styles'; 6 | 7 | const MenuEntryType = (): JSX.Element => { 8 | const {entryType, setEntryType} = useEntry(); 9 | 10 | const {t} = useTranslation('add'); 11 | 12 | const isExpense = entryType === 'expense'; 13 | const isEarning = entryType === 'earning'; 14 | 15 | return ( 16 | 17 | setEntryType('expense')}> 18 | 19 | 20 | 21 | 22 | setEntryType('earning')}> 23 | 24 | 25 | 26 | 27 | ); 28 | }; 29 | 30 | export default MenuEntryType; 31 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/MenuEntryType/__tests__/MenuEntryType.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import MenuEntryType from '../MenuEntryType'; 6 | 7 | jest.mock('react-i18next', () => ({ 8 | useTranslation: () => ({t: (key: any) => key}), 9 | })); 10 | 11 | describe('MenuEntryType', () => { 12 | it('Test match snapshot MenuEntryType', () => { 13 | // given 14 | const props = {}; 15 | 16 | // when 17 | const {toJSON} = render(); 18 | 19 | // then 20 | expect(toJSON()).toMatchSnapshot(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/MenuEntryType/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './MenuEntryType'; 2 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/MenuEntryType/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import {TouchableOpacity, Text} from 'styles/layout'; 3 | 4 | export const Container = styled.View` 5 | flex-direction: row; 6 | padding-top: ${({theme}) => theme.spacing.small}px; 7 | `; 8 | 9 | export const Touchable = styled(TouchableOpacity)` 10 | margin: 0px ${({theme}) => theme.spacing.great}px 11 | ${({theme}) => theme.spacing.tall}px; 12 | `; 13 | 14 | export const LineActive = styled.View<{active: boolean}>` 15 | height: 3px; 16 | width: 30px; 17 | align-self: center; 18 | border-radius: 2px; 19 | margin: 8px 0px 10px; 20 | background: ${({theme, active = false}) => 21 | active ? theme.colors.white : 'transparent'}; 22 | `; 23 | 24 | export const Label = styled(Text)<{active: boolean}>` 25 | font-family: ${({theme}) => theme.fontFamily.regular}; 26 | font-size: ${({theme}) => theme.fontSizes.medium}; 27 | font-weight: ${({theme}) => theme.fontWeight.bold}; 28 | color: ${({theme, active = false}) => 29 | active ? theme.colors.white : theme.colors.gray}; 30 | `; 31 | -------------------------------------------------------------------------------- /src/screens/AddEntry/components/index.ts: -------------------------------------------------------------------------------- 1 | export {default as MenuEntryType} from './MenuEntryType'; 2 | export {default as EntryDescription} from './EntryDescription'; 3 | export {default as EntryCategory} from './EntryCategory'; 4 | export {default as EntryDate} from './EntryDate'; 5 | -------------------------------------------------------------------------------- /src/screens/AddEntry/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './AddEntryScreen'; 2 | -------------------------------------------------------------------------------- /src/screens/AddEntry/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const Screen = styled.TouchableOpacity.attrs({ 4 | activeOpacity: 1, 5 | })` 6 | flex: 1; 7 | background: ${({theme}) => theme.colors.accent}; 8 | `; 9 | 10 | export const Container = styled.View` 11 | flex: 1; 12 | align-items: center; 13 | `; 14 | 15 | export const Content = styled.View` 16 | flex: 1; 17 | width: 100%; 18 | margin-top: ${({theme}) => theme.spacing.great}px; 19 | padding-top: ${({theme}) => theme.spacing.great}px; 20 | background: ${({theme}) => theme.colors.gray}; 21 | border-top-right-radius: ${({theme}) => theme.spacing.great}px; 22 | border-top-left-radius: ${({theme}) => theme.spacing.great}px; 23 | `; 24 | 25 | export const SubmitContainer = styled.View` 26 | width: 100%; 27 | padding: ${({theme}) => theme.spacing.tall}px 28 | ${({theme}) => theme.spacing.venti}px; 29 | `; 30 | -------------------------------------------------------------------------------- /src/screens/Budget/BudgetScreen.tsx: -------------------------------------------------------------------------------- 1 | import {useNavigation} from '@react-navigation/native'; 2 | import React from 'react'; 3 | import {useTranslation} from 'react-i18next'; 4 | import BudgetList from './components/BudgetList/BudgetList'; 5 | 6 | import {AddBudgetNavigationProp} from 'routes/StacksRoute'; 7 | import {Container, Header, Title, Touchable, AddIcon} from './styles'; 8 | 9 | const BudgetScreen = (): JSX.Element => { 10 | const navigation = useNavigation(); 11 | const {t} = useTranslation('budget'); 12 | 13 | const addBudget = () => { 14 | navigation.navigate('AddBudgetStack', { 15 | screen: 'AddBudget', 16 | }); 17 | }; 18 | 19 | return ( 20 | 21 |
22 | {t('title')} 23 | 24 | 25 | 26 |
27 | 28 |
29 | ); 30 | }; 31 | 32 | export default BudgetScreen; 33 | -------------------------------------------------------------------------------- /src/screens/Budget/components/BudgetList/BudgetList.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | import withObservables from '@nozbe/with-observables'; 3 | 4 | import {useBudgetContext} from 'contexts/BudgetContext'; 5 | 6 | import {Budget} from 'models'; 7 | import {BudgetRepository} from 'repositories'; 8 | 9 | import BudgetItem from './BudgetItem'; 10 | import ListEmpty from '../ListEmpty'; 11 | import {Container, List} from './styles'; 12 | 13 | interface BudgetListProps { 14 | budgets: Budget[]; 15 | } 16 | 17 | const BudgetList = ({budgets = []}: BudgetListProps): JSX.Element => { 18 | const {removeBudget} = useBudgetContext(); 19 | 20 | const listKeyExtractor = useCallback(item => item.id, []); 21 | const renderItem = useCallback( 22 | ({item}) => , 23 | [removeBudget], 24 | ); 25 | 26 | return ( 27 | 28 | {!budgets.length && } 29 | 30 | 36 | 37 | ); 38 | }; 39 | 40 | const enhance = withObservables([], () => ({ 41 | budgets: BudgetRepository.getBudgetsObserve(), 42 | })); 43 | 44 | export default enhance(BudgetList); 45 | -------------------------------------------------------------------------------- /src/screens/Budget/components/BudgetList/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eufelipe/log-finance-react-native/5e725c09be4f962bfbdc21b18aa42777db43a1f5/src/screens/Budget/components/BudgetList/index.ts -------------------------------------------------------------------------------- /src/screens/Budget/components/BudgetList/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import {Text} from 'styles/layout'; 3 | 4 | export const Container = styled.View` 5 | flex: 1; 6 | `; 7 | 8 | export const List = styled.FlatList` 9 | margin-top: ${({theme}) => theme.spacing.tall}px; 10 | `; 11 | 12 | export const Title = styled(Text)` 13 | font-family: ${({theme}) => theme.fontFamily.regular}; 14 | font-size: ${({theme}) => theme.fontSizes.medium}; 15 | font-weight: ${({theme}) => theme.fontWeight.regular}; 16 | color: ${({theme}) => theme.colors.white}; 17 | `; 18 | 19 | export const Value = styled(Title)` 20 | font-size: ${({theme}) => theme.fontSizes.medium}; 21 | font-weight: ${({theme}) => theme.fontWeight.bold}; 22 | `; 23 | 24 | export const Card = styled.View<{color?: string}>` 25 | background: ${({theme}) => theme.colors.primary}; 26 | margin: ${({theme}) => theme.spacing.small}px 27 | ${({theme}) => theme.spacing.tall}px; 28 | padding: ${({theme}) => theme.spacing.great}px; 29 | height: 110px; 30 | border-radius: ${({theme}) => theme.spacing.great}px; 31 | justify-content: center; 32 | `; 33 | 34 | export const CardBody = styled.View` 35 | flex-direction: row; 36 | `; 37 | export const CardRow = styled.View` 38 | padding: 10px; 39 | `; 40 | -------------------------------------------------------------------------------- /src/screens/Budget/components/ListEmpty/LICENSE.md: -------------------------------------------------------------------------------- 1 | Eduardo Henrique Costa 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/screens/Budget/components/ListEmpty/ListEmpty.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | 4 | import {Container, EmptyAnimation, Title, SubTitle} from './styles'; 5 | 6 | import EMPTY from './empty.json'; 7 | 8 | const ListEmpty = (): JSX.Element => { 9 | const {t} = useTranslation('budget'); 10 | 11 | return ( 12 | 13 | 14 | 15 | {t('no-entries')} 16 | {t('message-add-new')} 17 | 18 | ); 19 | }; 20 | 21 | export default ListEmpty; 22 | -------------------------------------------------------------------------------- /src/screens/Budget/components/ListEmpty/__tests__/ListEmpty.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import ListEmpty from '../ListEmpty'; 6 | 7 | jest.mock('react-i18next', () => ({ 8 | useTranslation: () => ({t: (key: any) => key}), 9 | })); 10 | 11 | describe('ListEmpty', () => { 12 | it('Test match snapshot ListEmpty', () => { 13 | // given 14 | const props = {}; 15 | 16 | // when 17 | const {toJSON} = render(); 18 | 19 | // then 20 | expect(toJSON()).toMatchSnapshot(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/screens/Budget/components/ListEmpty/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './ListEmpty'; 2 | -------------------------------------------------------------------------------- /src/screens/Budget/components/ListEmpty/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import LottieView from 'lottie-react-native'; 3 | import {Text} from 'styles/layout'; 4 | 5 | export const Container = styled.View` 6 | justify-content: center; 7 | align-items: center; 8 | margin: 30px 20px; 9 | `; 10 | 11 | export const EmptyAnimation = styled(LottieView).attrs({ 12 | autoPlay: true, 13 | loop: true, 14 | })` 15 | width: 300px; 16 | height: 300px; 17 | `; 18 | 19 | export const Title = styled(Text)` 20 | text-align: center; 21 | margin: ${({theme}) => theme.spacing.venti}px 0px; 22 | font-size: ${({theme}) => theme.fontSizes.large}; 23 | font-weight: ${({theme}) => theme.fontWeight.bold}; 24 | color: ${({theme}) => theme.colors.primary}; 25 | `; 26 | export const SubTitle = styled(Text)` 27 | text-align: center; 28 | color: ${({theme}) => theme.colors.gray}; 29 | font-size: ${({theme}) => theme.fontSizes.small}; 30 | `; 31 | -------------------------------------------------------------------------------- /src/screens/Budget/index.tsx: -------------------------------------------------------------------------------- 1 | export {default} from './BudgetScreen'; 2 | -------------------------------------------------------------------------------- /src/screens/Budget/styles.ts: -------------------------------------------------------------------------------- 1 | import {getStatusBarHeight} from 'react-native-iphone-x-helper'; 2 | import styled from 'styled-components/native'; 3 | import {Text, TouchableOpacity} from 'styles/layout'; 4 | 5 | import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; 6 | import Colors from 'styles/colors'; 7 | 8 | export const Container = styled.View` 9 | flex: 1; 10 | `; 11 | 12 | export const Title = styled(Text)` 13 | font-size: ${({theme}) => theme.fontSizes.large}; 14 | color: ${({theme}) => theme.colors.white}; 15 | `; 16 | 17 | export const Header = styled.View` 18 | align-items: center; 19 | justify-content: space-between; 20 | flex-direction: row; 21 | background: ${({theme}) => theme.colors.accent}; 22 | padding: 0px ${({theme}) => theme.spacing.tall}px; 23 | padding-top: ${({theme}) => theme.spacing.tall + getStatusBarHeight()}px; 24 | padding-bottom: ${({theme}) => theme.spacing.great}px; 25 | `; 26 | 27 | export const Touchable = styled(TouchableOpacity)``; 28 | 29 | export const AddIcon = styled(Icon).attrs({ 30 | name: 'plus', 31 | color: `${Colors.white}`, 32 | size: 32, 33 | })``; 34 | -------------------------------------------------------------------------------- /src/screens/Categories/CategoriesScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {RouteProp, useRoute} from '@react-navigation/native'; 3 | import {Container, Loader} from './styles'; 4 | import {CategoryList} from './components'; 5 | import {Category} from 'models'; 6 | 7 | import {StackParamList} from 'routes/StacksRoute'; 8 | 9 | interface CategoriesScreenProps { 10 | categories?: Category[]; 11 | } 12 | 13 | const CategoriesScreen = ({categories}: CategoriesScreenProps): JSX.Element => { 14 | const route = useRoute>(); 15 | 16 | if (!route.params?.setCategory) { 17 | return ; 18 | } 19 | 20 | return ( 21 | 22 | 26 | 27 | ); 28 | }; 29 | 30 | export default CategoriesScreen; 31 | -------------------------------------------------------------------------------- /src/screens/Categories/__tests__/CategoriesScreen.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable @typescript-eslint/unbound-method */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | 6 | import CategoriesScreen from '../CategoriesScreen'; 7 | 8 | jest.mock('react-i18next', () => ({ 9 | useTranslation: () => ({t: (key: any) => key}), 10 | })); 11 | 12 | describe('CategoriesScreen', () => { 13 | it('Test match snapshot CategoriesScreen', () => { 14 | // given 15 | const props = {}; 16 | 17 | // when 18 | const {toJSON} = render(); 19 | 20 | // then 21 | expect(toJSON()).toMatchSnapshot(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/screens/Categories/components/CategoryItem/CategoryItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | import {CategoryIcon} from 'components'; 4 | 5 | import {Container, Title} from './styles'; 6 | import {Category} from 'models'; 7 | 8 | interface Props { 9 | category: Category; 10 | onPressCategory: (category: Category) => void; 11 | } 12 | 13 | const CategoryItem = ({category, onPressCategory}: Props): JSX.Element => { 14 | const {t} = useTranslation('categories'); 15 | 16 | const categoryKey = category.key; 17 | return ( 18 | onPressCategory(category)} 20 | testID="category-item-button" 21 | accessible={true} 22 | accessibilityLabel={`${t('accessibility-button-label')} ${t( 23 | categoryKey, 24 | )}`} 25 | accessibilityHint={`${t('accessibility-button-hint')} ${t(categoryKey)}`} 26 | > 27 | 28 | {t(categoryKey)} 29 | 30 | ); 31 | }; 32 | 33 | export default CategoryItem; 34 | -------------------------------------------------------------------------------- /src/screens/Categories/components/CategoryItem/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './CategoryItem'; 2 | -------------------------------------------------------------------------------- /src/screens/Categories/components/CategoryItem/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import {TouchableOpacity, Text} from 'styles/layout'; 3 | 4 | export const Container = styled(TouchableOpacity)` 5 | margin: ${({theme}) => theme.spacing.great}px 6 | ${({theme}) => theme.spacing.small}px; 7 | align-items: center; 8 | flex-grow: 1; 9 | flex-basis: 0; 10 | `; 11 | 12 | export const Title = styled(Text)` 13 | font-size: ${({theme}) => theme.fontSizes.small}; 14 | color: ${({theme}) => theme.colors.white}; 15 | padding-top: ${({theme}) => theme.spacing.small}px; 16 | `; 17 | -------------------------------------------------------------------------------- /src/screens/Categories/components/CategoryList/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './CategoryList'; 2 | -------------------------------------------------------------------------------- /src/screens/Categories/components/CategoryList/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const FlatList = styled.FlatList.attrs({ 4 | numColumns: 3, 5 | columnWrapperStyle: { 6 | flex: 1, 7 | justifyContent: 'space-between', 8 | }, 9 | })``; 10 | -------------------------------------------------------------------------------- /src/screens/Categories/components/index.ts: -------------------------------------------------------------------------------- 1 | export {default as CategoryList} from './CategoryList'; 2 | export {default as CategoryItem} from './CategoryItem'; 3 | -------------------------------------------------------------------------------- /src/screens/Categories/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './CategoriesScreen'; 2 | -------------------------------------------------------------------------------- /src/screens/Categories/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const Container = styled.View` 4 | flex: 1; 5 | background: ${({theme}) => theme.colors.primary}; 6 | `; 7 | 8 | export const Loader = styled.ActivityIndicator` 9 | margin-top: 20px; 10 | `; 11 | -------------------------------------------------------------------------------- /src/screens/Currency/index.tsx: -------------------------------------------------------------------------------- 1 | export {default} from './CurrencyScreen'; 2 | -------------------------------------------------------------------------------- /src/screens/Currency/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const Container = styled.View` 4 | flex: 1; 5 | `; 6 | 7 | export const Title = styled.Text``; 8 | -------------------------------------------------------------------------------- /src/screens/Historic/components/HistoricList/HistoricHeader.tsx: -------------------------------------------------------------------------------- 1 | import {parseISO, format, isYesterday, isTomorrow} from 'date-fns'; 2 | import {isToday} from 'date-fns/esm'; 3 | import React from 'react'; 4 | import {useTranslation} from 'react-i18next'; 5 | import {DATE_FORMAT_BR} from 'utils/dates'; 6 | 7 | import {Headering} from './styles'; 8 | 9 | interface HistoricHeaderProps { 10 | title: string; 11 | } 12 | 13 | const HistoricHeader = ({title}: HistoricHeaderProps): JSX.Element => { 14 | const {t} = useTranslation('historic'); 15 | 16 | const date = parseISO(title); 17 | 18 | const today = isToday(date); 19 | const yerterday = isYesterday(date); 20 | const tomorow = isTomorrow(date); 21 | 22 | let prefix = 'EEEE'; 23 | 24 | if (today) prefix = `'${t('today')}'`; 25 | else if (yerterday) prefix = `'${t('yerterday')}'`; 26 | else if (tomorow) prefix = `'${t('tomorow')}'`; 27 | 28 | const formated = format(date, `${prefix}, ${DATE_FORMAT_BR}`); 29 | 30 | return {formated}; 31 | }; 32 | 33 | export default HistoricHeader; 34 | -------------------------------------------------------------------------------- /src/screens/Historic/components/HistoricList/HistoricItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import withObservables from '@nozbe/with-observables'; 3 | import {CategoryIcon, Currency} from 'components'; 4 | import {Category, Entry} from 'models'; 5 | 6 | import {getTimeFriendly} from 'utils/dates'; 7 | import { 8 | Container, 9 | Content, 10 | CategoryContainer, 11 | ValueContainer, 12 | Value, 13 | Title, 14 | Time, 15 | Line, 16 | } from './styles'; 17 | 18 | interface HistoricItemProps { 19 | entry: Entry; 20 | category?: Category; 21 | } 22 | 23 | const HistoricItem = ({entry, category}: HistoricItemProps): JSX.Element => { 24 | const {description, value, type} = entry; 25 | const isExpense = type === 'expense'; 26 | 27 | return ( 28 | 29 | {category && ( 30 | <> 31 | 32 | 33 | 34 | 35 | 36 | )} 37 | 38 | 39 | {description && {description}} 40 | {category?.description} 41 | 42 | 43 | 44 | 45 | {text}} 48 | /> 49 | 50 | 51 | ); 52 | }; 53 | 54 | const enhance = withObservables( 55 | ['entry'], 56 | ({entry}: HistoricItemProps) => ({ 57 | category: entry.category.observe(), 58 | }), 59 | ); 60 | 61 | export default enhance(HistoricItem); 62 | -------------------------------------------------------------------------------- /src/screens/Historic/components/HistoricList/HistoricList.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | 3 | import {SectionList} from './styles'; 4 | import Item from './HistoricItem'; 5 | 6 | import {HistoricSection} from 'screens/Historic/HistoricScreen'; 7 | 8 | import HistoricHeader from './HistoricHeader'; 9 | import {SectionListData} from 'react-native'; 10 | 11 | interface Props { 12 | historic: HistoricSection[]; 13 | } 14 | 15 | const HistoricList = ({historic = []}: Props): JSX.Element => { 16 | const listKeyExtractor = useCallback(item => item.id, []); 17 | const renderItem = useCallback(({item}) => , []); 18 | 19 | const renderSectionHeader = ({ 20 | section, 21 | }: { 22 | section: SectionListData; 23 | }): JSX.Element | null => ; 24 | 25 | return ( 26 | 27 | testID="historic-list-items" 28 | sections={historic} 29 | keyExtractor={listKeyExtractor} 30 | renderItem={renderItem} 31 | renderSectionHeader={renderSectionHeader} 32 | /> 33 | ); 34 | }; 35 | 36 | export default HistoricList; 37 | -------------------------------------------------------------------------------- /src/screens/Historic/components/HistoricList/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './HistoricList'; 2 | -------------------------------------------------------------------------------- /src/screens/Historic/components/ListEmpty/LICENSE.md: -------------------------------------------------------------------------------- 1 | musa adanur 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/screens/Historic/components/ListEmpty/ListEmpty.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | 4 | import {Container, EmptyAnimation, Title, SubTitle} from './styles'; 5 | 6 | import EMPTY from './empty.json'; 7 | 8 | const ListEmpty = (): JSX.Element => { 9 | const {t} = useTranslation('historic'); 10 | 11 | return ( 12 | 13 | 14 | 15 | {t('no-entries')} 16 | {t('message-add-new')} 17 | 18 | ); 19 | }; 20 | 21 | export default ListEmpty; 22 | -------------------------------------------------------------------------------- /src/screens/Historic/components/ListEmpty/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './ListEmpty'; 2 | -------------------------------------------------------------------------------- /src/screens/Historic/components/ListEmpty/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import LottieView from 'lottie-react-native'; 3 | import {Text} from 'styles/layout'; 4 | 5 | export const Container = styled.View` 6 | justify-content: center; 7 | align-items: center; 8 | margin-top: 30px; 9 | `; 10 | 11 | export const EmptyAnimation = styled(LottieView).attrs({ 12 | autoPlay: true, 13 | loop: true, 14 | })` 15 | width: 350px; 16 | height: 153px; 17 | margin-top: ${({theme}) => theme.spacing.small}px; 18 | `; 19 | 20 | export const Title = styled(Text)` 21 | text-align: center; 22 | margin: ${({theme}) => theme.spacing.venti}px 0px; 23 | font-size: ${({theme}) => theme.fontSizes.large}; 24 | font-weight: ${({theme}) => theme.fontWeight.bold}; 25 | color: ${({theme}) => theme.colors.primary}; 26 | `; 27 | export const SubTitle = styled(Text)` 28 | text-align: center; 29 | color: ${({theme}) => theme.colors.gray}; 30 | font-size: ${({theme}) => theme.fontSizes.small}; 31 | `; 32 | -------------------------------------------------------------------------------- /src/screens/Historic/index.tsx: -------------------------------------------------------------------------------- 1 | export {default} from './HistoricScreen'; 2 | -------------------------------------------------------------------------------- /src/screens/Historic/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import {getStatusBarHeight} from 'react-native-iphone-x-helper'; 3 | 4 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 5 | import {Text, TouchableOpacity, Colors} from 'styles/layout'; 6 | 7 | export const Container = styled.View` 8 | flex: 1; 9 | background: ${({theme}) => theme.colors.grayLight}; 10 | `; 11 | 12 | export const Loader = styled.ActivityIndicator` 13 | margin-top: 20px; 14 | `; 15 | 16 | export const Header = styled.View` 17 | width: 100%; 18 | height: 150px; 19 | align-items: center; 20 | padding-top: ${({theme}) => theme.spacing.tall + getStatusBarHeight()}px; 21 | background: ${({theme}) => theme.colors.accent}; 22 | `; 23 | 24 | export const PeriodNavigation = styled.View` 25 | flex-direction: row; 26 | `; 27 | 28 | export const Title = styled(Text)` 29 | font-size: ${({theme}) => theme.fontSizes.large}; 30 | color: ${({theme}) => theme.colors.white}; 31 | margin-bottom: ${({theme}) => theme.spacing.tall}px; 32 | `; 33 | 34 | export const Touchable = styled(TouchableOpacity)` 35 | margin: 0px ${({theme}) => theme.spacing.great}px 36 | ${({theme}) => theme.spacing.tall}px; 37 | `; 38 | 39 | export const Label = styled(Text)` 40 | font-family: ${({theme}) => theme.fontFamily.regular}; 41 | font-size: ${({theme}) => theme.fontSizes.medium}; 42 | font-weight: ${({theme}) => theme.fontWeight.regular}; 43 | color: ${({theme}) => theme.colors.grayLight}; 44 | `; 45 | 46 | export const BaseIcon = styled(MaterialIcons).attrs({ 47 | size: 24, 48 | color: Colors.grayLight, 49 | })``; 50 | 51 | export const LeftIcon = styled(BaseIcon).attrs({ 52 | name: 'keyboard-arrow-left', 53 | })``; 54 | 55 | export const RightIcon = styled(BaseIcon).attrs({ 56 | name: 'keyboard-arrow-right', 57 | })``; 58 | -------------------------------------------------------------------------------- /src/screens/Home/HomeScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import withObservables from '@nozbe/with-observables'; 3 | 4 | import {Container, Content, Salutation} from './styles'; 5 | import {useTranslation} from 'react-i18next'; 6 | 7 | import {Header, ListEmpty} from './components'; 8 | import {EntriesList} from 'components'; 9 | 10 | import {Entry} from 'models'; 11 | import EntryRepository from 'repositories/EntryRepository'; 12 | 13 | interface HomeScreenProps { 14 | entries: Entry[]; 15 | } 16 | 17 | const HomeScreen = ({entries = []}: HomeScreenProps): JSX.Element => { 18 | const {t} = useTranslation('home'); 19 | 20 | return ( 21 | 22 |
23 | 24 | {t('today')} 25 | {!!entries.length && } 26 | 27 | {!entries.length && } 28 | 29 | 30 | ); 31 | }; 32 | 33 | const enhance = withObservables(['entries'], () => ({ 34 | entries: EntryRepository.getTodayEntries(), 35 | })); 36 | 37 | export default enhance(HomeScreen); 38 | -------------------------------------------------------------------------------- /src/screens/Home/__tests__/HomeScreen.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable @typescript-eslint/unbound-method */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | 6 | import HomeScreen from '../HomeScreen'; 7 | 8 | jest.runAllTimers(); 9 | 10 | describe('HomeScreen', () => { 11 | it('Test match snapshot HomeScreen', () => { 12 | // given 13 | const props = {}; 14 | 15 | // when 16 | const {toJSON} = render(); 17 | 18 | // then 19 | expect(toJSON()).toMatchSnapshot(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/screens/Home/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback, useState} from 'react'; 2 | import {useFocusEffect, useNavigation} from '@react-navigation/native'; 3 | import {SettingsNavigationProp} from 'routes/StacksRoute'; 4 | import {useTranslation} from 'react-i18next'; 5 | 6 | import {Currency} from 'components'; 7 | import {Entry} from 'models'; 8 | 9 | import { 10 | Container, 11 | Content, 12 | Title, 13 | Description, 14 | SettingTouchable, 15 | SettingIcon, 16 | } from './styles'; 17 | import {useEntry} from 'hooks/useEntry'; 18 | 19 | interface HeaderProps { 20 | entries?: Entry[]; 21 | } 22 | 23 | const Header = ({entries = []}: HeaderProps): JSX.Element => { 24 | const navigation = useNavigation(); 25 | const {t} = useTranslation('home'); 26 | const [balance, setBalance] = useState(0); 27 | 28 | const {calculateCurrentBalance} = useEntry(); 29 | 30 | const handleSettings = () => navigation.navigate('Settings'); 31 | 32 | useFocusEffect( 33 | useCallback(() => { 34 | const sumValues = calculateCurrentBalance(entries); 35 | setBalance(sumValues); 36 | }, [calculateCurrentBalance, entries]), 37 | ); 38 | 39 | return ( 40 | 41 | 42 | {t('balance')} 43 | {value}} /> 44 | 45 | 46 | 47 | 48 | 49 | ); 50 | }; 51 | 52 | export default Header; 53 | -------------------------------------------------------------------------------- /src/screens/Home/components/Header/__tests__/Header.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable @typescript-eslint/unbound-method */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | 6 | import Header from '../Header'; 7 | 8 | jest.mock('react-i18next', () => ({ 9 | useTranslation: () => ({t: (key: any) => key}), 10 | })); 11 | 12 | describe('Header', () => { 13 | it('Test match snapshot Header', () => { 14 | // given 15 | const props = {}; 16 | 17 | // when 18 | const {toJSON} = render(
); 19 | 20 | // then 21 | expect(toJSON()).toMatchSnapshot(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/screens/Home/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './Header'; 2 | -------------------------------------------------------------------------------- /src/screens/Home/components/Header/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import {getStatusBarHeight} from 'react-native-iphone-x-helper'; 3 | import {Text, TouchableOpacity} from 'styles/layout'; 4 | 5 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 6 | import Colors from 'styles/colors'; 7 | 8 | export const Container = styled.View` 9 | background: ${({theme}) => theme.colors.primary}; 10 | padding: 0px ${({theme}) => theme.spacing.great}px; 11 | padding-top: ${({theme}) => theme.spacing.tall + getStatusBarHeight()}px; 12 | justify-content: space-between; 13 | align-items: center; 14 | height: 150px; 15 | flex-direction: row; 16 | `; 17 | 18 | export const Content = styled.View``; 19 | 20 | export const Title = styled(Text)` 21 | font-size: ${({theme}) => theme.fontSizes.big}; 22 | font-weight: ${({theme}) => theme.fontWeight.bold}; 23 | font-family: ${({theme}) => theme.fontFamily.semiBold}; 24 | color: ${({theme}) => theme.colors.white}; 25 | `; 26 | 27 | export const Description = styled(Text)` 28 | font-size: ${({theme}) => theme.fontSizes.small}; 29 | color: ${({theme}) => theme.colors.white}; 30 | `; 31 | 32 | export const SettingTouchable = styled(TouchableOpacity).attrs({})` 33 | padding: 10px; 34 | `; 35 | 36 | export const SettingIcon = styled(MaterialIcons).attrs({ 37 | size: 28, 38 | name: 'settings', 39 | backgroundColor: 'transparent', 40 | color: Colors.white, 41 | })` 42 | padding-right: 0px; 43 | `; 44 | -------------------------------------------------------------------------------- /src/screens/Home/components/ListEmpty/LICENSE.md: -------------------------------------------------------------------------------- 1 | Muhammad Hazwan Bin Mohd Hanizul 2 | 3 | https://lottiefiles.com/user/611359 4 | 5 | https://lottiefiles.com/66934-tumbleweed-rolling 6 | -------------------------------------------------------------------------------- /src/screens/Home/components/ListEmpty/ListEmpty.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | 4 | import {Container, EmptyAnimation, Title, SubTitle} from './styles'; 5 | 6 | import EMPTY from './empty.json'; 7 | 8 | const ListEmpty = (): JSX.Element => { 9 | const {t} = useTranslation('home'); 10 | 11 | return ( 12 | 13 | 14 | 15 | {t('no-entries')} 16 | {t('message-add-new')} 17 | 18 | ); 19 | }; 20 | 21 | export default ListEmpty; 22 | -------------------------------------------------------------------------------- /src/screens/Home/components/ListEmpty/__tests__/ListEmpty.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable @typescript-eslint/unbound-method */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | 6 | import ListEmpty from '../ListEmpty'; 7 | 8 | jest.mock('react-i18next', () => ({ 9 | useTranslation: () => ({t: (key: any) => key}), 10 | })); 11 | 12 | describe('ListEmpty', () => { 13 | it('Test match snapshot ListEmpty', () => { 14 | // given 15 | const props = {}; 16 | 17 | // when 18 | const {toJSON} = render(); 19 | 20 | // then 21 | expect(toJSON()).toMatchSnapshot(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/screens/Home/components/ListEmpty/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './ListEmpty'; 2 | -------------------------------------------------------------------------------- /src/screens/Home/components/ListEmpty/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import LottieView from 'lottie-react-native'; 3 | import {Text} from 'styles/layout'; 4 | 5 | export const Container = styled.View` 6 | justify-content: center; 7 | align-items: center; 8 | `; 9 | 10 | export const EmptyAnimation = styled(LottieView).attrs({ 11 | autoPlay: true, 12 | loop: true, 13 | })` 14 | width: 350px; 15 | height: 153px; 16 | margin-top: ${({theme}) => theme.spacing.small}px; 17 | `; 18 | 19 | export const Title = styled(Text)` 20 | text-align: center; 21 | margin: ${({theme}) => theme.spacing.tall}px 0px; 22 | font-size: ${({theme}) => theme.fontSizes.large}; 23 | font-weight: ${({theme}) => theme.fontWeight.bold}; 24 | color: ${({theme}) => theme.colors.primary}; 25 | `; 26 | export const SubTitle = styled(Text)` 27 | text-align: center; 28 | color: ${({theme}) => theme.colors.gray}; 29 | font-size: ${({theme}) => theme.fontSizes.small}; 30 | `; 31 | -------------------------------------------------------------------------------- /src/screens/Home/components/index.ts: -------------------------------------------------------------------------------- 1 | export {default as Header} from './Header'; 2 | export {default as ListEmpty} from './ListEmpty'; 3 | -------------------------------------------------------------------------------- /src/screens/Home/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './HomeScreen'; 2 | -------------------------------------------------------------------------------- /src/screens/Home/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import {Text} from 'styles/layout'; 3 | 4 | export const Container = styled.View` 5 | background: ${({theme}) => theme.colors.primary}; 6 | flex: 1; 7 | `; 8 | 9 | export const Content = styled.View` 10 | background: ${({theme}) => theme.colors.grayLight}; 11 | border-top-right-radius: ${({theme}) => theme.spacing.tall}px; 12 | border-top-left-radius: ${({theme}) => theme.spacing.tall}px; 13 | flex: 1; 14 | `; 15 | 16 | export const Salutation = styled(Text)` 17 | color: ${({theme}) => theme.colors.paragraph}; 18 | font-size: ${({theme}) => theme.fontSizes.small}; 19 | 20 | margin: ${({theme}) => theme.spacing.tall}px 21 | ${({theme}) => theme.spacing.tall}px ${({theme}) => theme.spacing.small}px; 22 | `; 23 | -------------------------------------------------------------------------------- /src/screens/Language/LanguageScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback, useMemo} from 'react'; 2 | import {useNavigation} from '@react-navigation/native'; 3 | 4 | import {Container} from './styles'; 5 | import {useTranslation} from 'react-i18next'; 6 | 7 | import List, {ListItemProps} from 'components/List'; 8 | import {Toolbar} from 'components'; 9 | 10 | import {LANG_EN_US, LANG_PT_BR} from 'services/language'; 11 | import {sortBy} from 'lodash'; 12 | 13 | const LanguageScreen = (): JSX.Element => { 14 | const navigation = useNavigation(); 15 | 16 | const {t, i18n} = useTranslation('settings'); 17 | 18 | const changeLanguage = useCallback( 19 | (language: string) => { 20 | i18n.changeLanguage(language); 21 | navigation.goBack(); 22 | }, 23 | [i18n, navigation], 24 | ); 25 | 26 | const items: ListItemProps[] = useMemo(() => { 27 | return [ 28 | { 29 | id: 1, 30 | title: t(LANG_PT_BR), 31 | selected: LANG_PT_BR === i18n.language ? t('active') : undefined, 32 | icon: 'chevron-right', 33 | onPress: () => changeLanguage(LANG_PT_BR), 34 | }, 35 | { 36 | id: 2, 37 | title: t(LANG_EN_US), 38 | selected: LANG_EN_US === i18n.language ? t('active') : undefined, 39 | icon: 'chevron-right', 40 | onPress: () => changeLanguage(LANG_EN_US), 41 | }, 42 | ]; 43 | }, [i18n.language, changeLanguage, t]); 44 | 45 | const menuItems = sortBy(items, 'selected'); 46 | 47 | return ( 48 | 49 | 50 | 51 | 52 | ); 53 | }; 54 | 55 | export default LanguageScreen; 56 | -------------------------------------------------------------------------------- /src/screens/Language/index.tsx: -------------------------------------------------------------------------------- 1 | export {default} from './LanguageScreen'; 2 | -------------------------------------------------------------------------------- /src/screens/Language/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const Container = styled.View` 4 | flex: 1; 5 | `; 6 | 7 | export const Title = styled.Text``; 8 | -------------------------------------------------------------------------------- /src/screens/Reports/__tests__/ReportsScreen.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import React from 'react'; 4 | import {render} from 'utils/test-utils'; 5 | import ReportsScreen from '../ReportsScreen'; 6 | 7 | jest.mock('react-i18next', () => ({ 8 | useTranslation: () => ({ 9 | t: (key: any) => key, 10 | i18n: { 11 | changeLanguage: () => new Promise(() => {}), 12 | }, 13 | }), 14 | })); 15 | 16 | jest.mock('@react-navigation/native', () => { 17 | const actualNav = jest.requireActual('@react-navigation/native'); 18 | return { 19 | ...actualNav, 20 | useRoute: () => jest.fn(), 21 | }; 22 | }); 23 | 24 | describe('ReportsScreen', () => { 25 | it('Test match snapshot ReportsScreen', () => { 26 | // given 27 | const props = {}; 28 | 29 | // when 30 | const {toJSON} = render(); 31 | 32 | // then 33 | expect(toJSON()).toMatchSnapshot(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/screens/Reports/components/FilterPeriod/FilterPeriod.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback, useState} from 'react'; 2 | 3 | import Item from './Item'; 4 | 5 | import {Container, FlatList} from './styles'; 6 | 7 | import DATA, {FilterPeriodItem} from './data'; 8 | 9 | interface Props { 10 | onChangeFilter: (filterPeriod: FilterPeriodItem) => void; 11 | } 12 | 13 | const FilterPeriod = ({onChangeFilter}: Props): JSX.Element => { 14 | const [first] = DATA; 15 | 16 | const [data, setData] = useState(DATA ?? []); 17 | 18 | const [, setFilterPeriodSelected] = useState(first); 19 | 20 | const listKeyExtractor = useCallback(item => item.id, []); 21 | 22 | const onPress = useCallback( 23 | (filterPeriod: FilterPeriodItem) => { 24 | setFilterPeriodSelected(filterPeriod); 25 | 26 | const merge = data.map(item => 27 | item.id === filterPeriod.id 28 | ? {...filterPeriod, active: true} 29 | : {...item, active: false}, 30 | ); 31 | 32 | setData(merge); 33 | onChangeFilter(filterPeriod); 34 | }, 35 | [data, onChangeFilter], 36 | ); 37 | 38 | const renderItem = useCallback( 39 | ({item}) => , 40 | [onPress], 41 | ); 42 | 43 | return ( 44 | 45 | 51 | 52 | ); 53 | }; 54 | 55 | export default FilterPeriod; 56 | -------------------------------------------------------------------------------- /src/screens/Reports/components/FilterPeriod/Item.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | import {FilterPeriodItem} from './data'; 4 | 5 | import {Touchable, Label} from './styles'; 6 | 7 | interface ItemProps { 8 | item: FilterPeriodItem; 9 | active?: boolean; 10 | onPress: (item: FilterPeriodItem) => void; 11 | } 12 | 13 | const Item = ({item, onPress}: ItemProps): JSX.Element => { 14 | const {t} = useTranslation('reports'); 15 | 16 | return ( 17 | onPress(item)}> 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default Item; 24 | -------------------------------------------------------------------------------- /src/screens/Reports/components/FilterPeriod/__tests__/FilterPeriod.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable @typescript-eslint/unbound-method */ 3 | import React from 'react'; 4 | 5 | import {render} from 'utils/test-utils'; 6 | import FilterPeriod from '../FilterPeriod'; 7 | 8 | jest.mock('react-i18next', () => ({ 9 | useTranslation: () => ({t: (key: any) => key}), 10 | })); 11 | 12 | const onChangeFilter = jest.fn(); 13 | 14 | describe('FilterPeriod', () => { 15 | it('Test match snapshot FilterPeriod', () => { 16 | // given 17 | const props = { 18 | onChangeFilter, 19 | }; 20 | 21 | // when 22 | const {toJSON} = render(); 23 | 24 | // then 25 | expect(toJSON()).toMatchSnapshot(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/screens/Reports/components/FilterPeriod/data.ts: -------------------------------------------------------------------------------- 1 | import { 2 | addDays, 3 | endOfToday, 4 | endOfYesterday, 5 | startOfMonth, 6 | startOfQuarter, 7 | startOfWeek, 8 | startOfYear, 9 | } from 'date-fns'; 10 | 11 | export interface FilterPeriodItem { 12 | id: string; 13 | active?: boolean; 14 | interval: { 15 | start: Date; 16 | end: Date; 17 | }; 18 | } 19 | 20 | const yerterday = endOfYesterday(); 21 | const today = endOfToday(); 22 | const last15Days = addDays(today, -15); 23 | const month = startOfMonth(today); 24 | const week = startOfWeek(today); 25 | const quarter = startOfQuarter(today); 26 | const semester = addDays(today, -180); 27 | const year = startOfYear(today); 28 | 29 | const DATA: FilterPeriodItem[] = [ 30 | { 31 | id: 'today', 32 | interval: { 33 | start: yerterday, 34 | end: today, 35 | }, 36 | active: true, 37 | }, 38 | { 39 | id: 'week', 40 | interval: { 41 | start: week, 42 | end: today, 43 | }, 44 | }, 45 | { 46 | id: '15-days', 47 | interval: { 48 | start: last15Days, 49 | end: today, 50 | }, 51 | }, 52 | { 53 | id: 'month', 54 | interval: { 55 | start: month, 56 | end: today, 57 | }, 58 | }, 59 | { 60 | id: 'quarter', 61 | interval: { 62 | start: quarter, 63 | end: today, 64 | }, 65 | }, 66 | { 67 | id: 'semester', 68 | interval: { 69 | start: semester, 70 | end: today, 71 | }, 72 | }, 73 | { 74 | id: 'year', 75 | interval: { 76 | start: year, 77 | end: today, 78 | }, 79 | }, 80 | ]; 81 | 82 | export default DATA; 83 | -------------------------------------------------------------------------------- /src/screens/Reports/components/FilterPeriod/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './FilterPeriod'; 2 | -------------------------------------------------------------------------------- /src/screens/Reports/components/FilterPeriod/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import {Text, TouchableOpacity} from 'styles/layout'; 3 | 4 | export const Container = styled.View` 5 | flex-direction: row; 6 | `; 7 | 8 | export const FlatList = styled.FlatList` 9 | padding-top: ${({theme}) => theme.spacing.great}px; 10 | `; 11 | 12 | export const Touchable = styled(TouchableOpacity)` 13 | margin: 0px ${({theme}) => theme.spacing.great}px 14 | ${({theme}) => theme.spacing.tall}px; 15 | `; 16 | 17 | export const Label = styled(Text)<{active: boolean}>` 18 | font-family: ${({theme}) => theme.fontFamily.regular}; 19 | font-size: ${({theme}) => theme.fontSizes.small}; 20 | font-weight: ${({theme}) => theme.fontWeight.bold}; 21 | color: ${({theme, active = false}) => 22 | active ? theme.colors.white : theme.colors.gray}; 23 | `; 24 | -------------------------------------------------------------------------------- /src/screens/Reports/components/LegendList/LegendItem.tsx: -------------------------------------------------------------------------------- 1 | import {Currency} from 'components'; 2 | import React from 'react'; 3 | 4 | import {ReportItem} from 'services/reports'; 5 | 6 | import {Container, Title, SubTitle, Circle} from './styles'; 7 | 8 | const LegendItem = ({text, value, sumed, color}: ReportItem): JSX.Element => { 9 | return ( 10 | 11 | 12 | 13 | 14 | {text} ({sumed}) 15 | 16 | 17 | {currency}} 20 | /> 21 | 22 | ); 23 | }; 24 | 25 | export default LegendItem; 26 | -------------------------------------------------------------------------------- /src/screens/Reports/components/LegendList/LegendList.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | 3 | import {FlatList} from './styles'; 4 | import Item from './LegendItem'; 5 | import {ReportItem} from 'services/reports'; 6 | 7 | interface Props { 8 | items: ReportItem[]; 9 | } 10 | 11 | const LegendList = ({items}: Props): JSX.Element => { 12 | const ListKeyExtractor = useCallback(item => item.id, []); 13 | const renderItem = useCallback(({item}) => , []); 14 | 15 | return ( 16 | 23 | ); 24 | }; 25 | 26 | export default LegendList; 27 | -------------------------------------------------------------------------------- /src/screens/Reports/components/LegendList/__tests__/LegendItem.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable @typescript-eslint/unbound-method */ 3 | import React from 'react'; 4 | 5 | import {render} from 'utils/test-utils'; 6 | import LegendItem from '../LegendItem'; 7 | 8 | const MOCK = { 9 | id: '123abc', 10 | color: 'red', 11 | text: 'Teste', 12 | sumed: 100, 13 | value: 10, 14 | }; 15 | 16 | describe('LegendItem', () => { 17 | it('Test match snapshot LegendItem', () => { 18 | // given 19 | const props = { 20 | ...MOCK, 21 | }; 22 | 23 | // when 24 | const {toJSON} = render(); 25 | 26 | // then 27 | expect(toJSON()).toMatchSnapshot(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/screens/Reports/components/LegendList/__tests__/LegendList.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable @typescript-eslint/unbound-method */ 3 | import React from 'react'; 4 | 5 | import {render} from 'utils/test-utils'; 6 | import LegendList from '../LegendList'; 7 | 8 | const MOCK = { 9 | id: '123abc', 10 | color: 'red', 11 | text: 'Teste', 12 | sumed: 100, 13 | value: 10, 14 | }; 15 | 16 | describe('LegendList', () => { 17 | it('Test match snapshot LegendList', () => { 18 | // given 19 | const props = { 20 | items: [MOCK, MOCK], 21 | }; 22 | 23 | // when 24 | const {toJSON} = render(); 25 | 26 | // then 27 | expect(toJSON()).toMatchSnapshot(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/screens/Reports/components/LegendList/__tests__/__snapshots__/LegendItem.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`LegendItem Test match snapshot LegendItem 1`] = ` 4 | 19 | 33 | 46 | Teste 47 | ( 48 | 100 49 | ) 50 | 51 | 64 | R$ 0,10 65 | 66 | 67 | `; 68 | -------------------------------------------------------------------------------- /src/screens/Reports/components/LegendList/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './LegendList'; 2 | 3 | export * from './LegendList'; 4 | -------------------------------------------------------------------------------- /src/screens/Reports/components/LegendList/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import {Text} from 'styles/layout'; 3 | 4 | const SIZE_CIRCLE = 16; 5 | 6 | export const FlatList = styled.FlatList` 7 | margin: 0px 20px; 8 | `; 9 | 10 | export const Container = styled.View` 11 | flex-direction: row; 12 | width: 50%; 13 | align-items: center; 14 | padding: 5px 2px; 15 | `; 16 | 17 | export const Content = styled.View``; 18 | 19 | export const Title = styled(Text)` 20 | color: ${({theme}) => theme.colors.paragraph}; 21 | font-size: ${({theme}) => theme.fontSizes.tiny}; 22 | font-weight: ${({theme}) => theme.fontWeight.bold}; 23 | margin-right: 5px; 24 | `; 25 | 26 | export const SubTitle = styled(Title)` 27 | font-weight: ${({theme}) => theme.fontWeight.regular}; 28 | `; 29 | 30 | export const Circle = styled.View<{color?: string}>` 31 | background: ${({theme, color = undefined}) => color ?? theme.colors.gray}; 32 | width: ${SIZE_CIRCLE}px; 33 | height: ${SIZE_CIRCLE}px; 34 | margin-right: 5px; 35 | border-radius: ${SIZE_CIRCLE / 2}px; 36 | `; 37 | -------------------------------------------------------------------------------- /src/screens/Reports/components/ListEmpty/LICENSE.md: -------------------------------------------------------------------------------- 1 | Yasmina Luria 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/screens/Reports/components/ListEmpty/ListEmpty.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useTranslation} from 'react-i18next'; 3 | 4 | import {Container, EmptyAnimation, Title, SubTitle} from './styles'; 5 | 6 | import EMPTY from './reports.json'; 7 | 8 | const ListEmpty = (): JSX.Element => { 9 | const {t} = useTranslation('home'); 10 | 11 | return ( 12 | 13 | 14 | 15 | {t('no-entries')} 16 | {t('message-add-new')} 17 | 18 | ); 19 | }; 20 | 21 | export default ListEmpty; 22 | -------------------------------------------------------------------------------- /src/screens/Reports/components/ListEmpty/__tests__/ListEmpty.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable @typescript-eslint/unbound-method */ 3 | import React from 'react'; 4 | 5 | import {render} from 'utils/test-utils'; 6 | import ListEmpty from '../ListEmpty'; 7 | 8 | describe('ListEmpty', () => { 9 | it('Test match snapshot ListEmpty', () => { 10 | // given 11 | const props = {}; 12 | 13 | // when 14 | const {toJSON} = render(); 15 | 16 | // then 17 | expect(toJSON()).toMatchSnapshot(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/screens/Reports/components/ListEmpty/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './ListEmpty'; 2 | -------------------------------------------------------------------------------- /src/screens/Reports/components/ListEmpty/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import LottieView from 'lottie-react-native'; 3 | import {Text} from 'styles/layout'; 4 | 5 | export const Container = styled.View` 6 | flex: 1; 7 | justify-content: center; 8 | align-items: center; 9 | background: ${({theme}) => theme.colors.tertiary}; 10 | `; 11 | 12 | export const EmptyAnimation = styled(LottieView).attrs({ 13 | autoPlay: true, 14 | loop: true, 15 | })` 16 | width: 366px; 17 | height: 366px; 18 | margin-top: ${({theme}) => theme.spacing.small}px; 19 | `; 20 | 21 | export const Title = styled(Text)` 22 | text-align: center; 23 | margin: ${({theme}) => theme.spacing.tall}px 0px; 24 | font-size: ${({theme}) => theme.fontSizes.large}; 25 | font-weight: ${({theme}) => theme.fontWeight.bold}; 26 | color: ${({theme}) => theme.colors.white}; 27 | `; 28 | export const SubTitle = styled(Text)` 29 | text-align: center; 30 | color: ${({theme}) => theme.colors.white}; 31 | font-size: ${({theme}) => theme.fontSizes.small}; 32 | `; 33 | -------------------------------------------------------------------------------- /src/screens/Reports/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './ReportsScreen'; 2 | -------------------------------------------------------------------------------- /src/screens/Reports/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import {getStatusBarHeight} from 'react-native-iphone-x-helper'; 3 | import {Text, TouchableOpacity} from 'styles/layout'; 4 | 5 | export const Container = styled.View` 6 | flex: 1; 7 | background: ${({theme}) => theme.colors.grayLight}; 8 | `; 9 | 10 | export const Header = styled.View` 11 | width: 100%; 12 | height: 500px; 13 | align-items: center; 14 | padding: ${({theme}) => theme.spacing.tall + getStatusBarHeight()}px 0px; 15 | margin-bottom: ${({theme}) => theme.spacing.tall}px; 16 | background: ${({theme}) => theme.colors.accent}; 17 | `; 18 | 19 | export const Title = styled(Text)` 20 | font-size: ${({theme}) => theme.fontSizes.large}; 21 | color: ${({theme}) => theme.colors.white}; 22 | `; 23 | 24 | export const Balance = styled(Title)` 25 | color: ${({theme}) => theme.colors.accent}; 26 | `; 27 | 28 | export const ContainerButtons = styled.View` 29 | flex-direction: row; 30 | `; 31 | 32 | export const Touchable = styled(TouchableOpacity)` 33 | margin: 0px ${({theme}) => theme.spacing.great}px 34 | ${({theme}) => theme.spacing.tall}px; 35 | `; 36 | 37 | export const Label = styled(Text)<{active: boolean}>` 38 | font-family: ${({theme}) => theme.fontFamily.regular}; 39 | font-size: ${({theme}) => theme.fontSizes.medium}; 40 | font-weight: ${({theme}) => theme.fontWeight.bold}; 41 | color: ${({theme, active = false}) => 42 | active ? theme.colors.white : theme.colors.gray}; 43 | `; 44 | -------------------------------------------------------------------------------- /src/screens/Settings/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './SettingsScreen'; 2 | -------------------------------------------------------------------------------- /src/screens/Settings/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | 3 | export const Container = styled.View` 4 | flex: 1; 5 | `; 6 | -------------------------------------------------------------------------------- /src/services/currency.ts: -------------------------------------------------------------------------------- 1 | import storage from './storage'; 2 | 3 | export const KEY_APP_CURRENCY = '@app-currency'; 4 | 5 | export const DEFAULT_CURRENCY = 'BRL'; 6 | 7 | export const saveCurrency = async (value: string): Promise => { 8 | return storage.setItem(KEY_APP_CURRENCY, value); 9 | }; 10 | 11 | export const getCurrency = async (): Promise => { 12 | return storage.getItem(KEY_APP_CURRENCY); 13 | }; 14 | 15 | export default { 16 | saveCurrency, 17 | getCurrency, 18 | }; 19 | -------------------------------------------------------------------------------- /src/services/database.ts: -------------------------------------------------------------------------------- 1 | import {Database} from '@nozbe/watermelondb'; 2 | import SQLiteAdapter, { 3 | SQLiteAdapterOptions, 4 | } from '@nozbe/watermelondb/adapters/sqlite'; 5 | 6 | import {schema, migrations, modelClasses, seeds} from 'database'; 7 | import {DATABASE_NAME} from 'database/schema'; 8 | import {isIos} from 'styles/mixins'; 9 | 10 | let database: Database; 11 | 12 | export function getDatabase(): Database { 13 | if (!database) { 14 | const adapterConfig: SQLiteAdapterOptions = { 15 | dbName: DATABASE_NAME, 16 | schema, 17 | migrations, 18 | jsi: isIos, 19 | }; 20 | 21 | const adapter = new SQLiteAdapter(adapterConfig); 22 | 23 | database = new Database({ 24 | adapter, 25 | modelClasses, 26 | }); 27 | } 28 | return database; 29 | } 30 | 31 | export function runSeeds(): void { 32 | seeds.run(); 33 | } 34 | 35 | export default getDatabase; 36 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export {default as database} from './database'; 2 | export {default as storage} from './storage'; 3 | export {default as currency} from './currency'; 4 | export {default as reports} from './reports'; 5 | -------------------------------------------------------------------------------- /src/services/storage.ts: -------------------------------------------------------------------------------- 1 | import AsyncStorage from '@react-native-async-storage/async-storage'; 2 | 3 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any 4 | const setItem = async (key: string, value: any): Promise => { 5 | if (!value) { 6 | return undefined; 7 | } 8 | 9 | return AsyncStorage.setItem(key, value); 10 | }; 11 | 12 | const getItem = async (key: string): Promise => { 13 | return AsyncStorage.getItem(key) ?? undefined; 14 | }; 15 | 16 | const removeItem = (key: string): Promise => { 17 | return AsyncStorage.removeItem(key); 18 | }; 19 | 20 | const reset = async (): Promise => { 21 | (await AsyncStorage.getAllKeys()).forEach(async key => { 22 | await AsyncStorage.removeItem(key); 23 | }); 24 | }; 25 | 26 | export default { 27 | setItem, 28 | getItem, 29 | removeItem, 30 | reset, 31 | }; 32 | -------------------------------------------------------------------------------- /src/styles/colors.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | primary: '#2B303A', 3 | seconday: '#37AF78', 4 | tertiary: '#6E7B96', 5 | accent: '#181D26', 6 | 7 | black: '#000000', 8 | white: '#ffffff', 9 | 10 | danger: '#E96245', 11 | success: '#50806A', 12 | 13 | gray: '#464E5F', 14 | grayLight: '#E1E3E5', 15 | paragraph: '#808080', 16 | }; 17 | -------------------------------------------------------------------------------- /src/styles/index.ts: -------------------------------------------------------------------------------- 1 | import * as Colors from './colors'; 2 | import * as Typography from './typography'; 3 | import * as Mixins from './mixins'; 4 | import * as Spacing from './spacing'; 5 | import * as Theme from './theme'; 6 | import * as Shadow from './shadow'; 7 | 8 | export {Colors, Typography, Mixins, Spacing, Theme, Shadow}; 9 | -------------------------------------------------------------------------------- /src/styles/layout.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components/native'; 2 | import ThemeColors from './colors'; 3 | 4 | export const TouchableOpacity = styled.TouchableOpacity.attrs({ 5 | activeOpacity: 0.8, 6 | })``; 7 | 8 | export const Text = styled.Text` 9 | font-family: ${({theme}) => theme.fontFamily.regular}; 10 | font-size: ${({theme}) => theme.fontSizes.medium}; 11 | font-weight: ${({theme}) => theme.fontWeight.regular}; 12 | color: ${({theme}) => theme.colors.black}; 13 | `; 14 | 15 | export const Colors = ThemeColors; 16 | -------------------------------------------------------------------------------- /src/styles/mixins.ts: -------------------------------------------------------------------------------- 1 | import {Platform, Dimensions, PixelRatio} from 'react-native'; 2 | 3 | const isAndroid = Platform.OS === 'android'; 4 | const isIos = Platform.OS === 'ios'; 5 | 6 | const scaledSize = Dimensions.get('window'); 7 | 8 | const {width, height} = scaledSize; 9 | 10 | const screenSize = Math.sqrt(width * height) / 100; 11 | 12 | //Guideline sizes are based on standard ~5" screen mobile device 13 | const guidelineBaseWidth = 350; 14 | 15 | const scale = width / guidelineBaseWidth; 16 | 17 | const scaleSize = (size: number): number => (width / guidelineBaseWidth) * size; 18 | 19 | const scaleFont = (size: number): number => size * PixelRatio.getFontScale(); 20 | 21 | const normalize = (size: number): number => { 22 | const newSize = size * scale; 23 | return Math.round(PixelRatio.roundToNearestPixel(newSize)) - 2; 24 | }; 25 | 26 | export { 27 | isAndroid, 28 | isIos, 29 | screenSize, 30 | width, 31 | height, 32 | scaleSize, 33 | scaleFont, 34 | normalize, 35 | }; 36 | -------------------------------------------------------------------------------- /src/styles/shadow.ts: -------------------------------------------------------------------------------- 1 | import {css} from 'styled-components/native'; 2 | 3 | const shadow = css` 4 | shadow-color: ${({theme}) => theme.colors.black}; 5 | shadow-offset: 1px 3px; 6 | shadow-opacity: 0.22; 7 | shadow-radius: 10px; 8 | elevation: 10; 9 | `; 10 | 11 | export default shadow; 12 | -------------------------------------------------------------------------------- /src/styles/spacing.ts: -------------------------------------------------------------------------------- 1 | import {scaleSize} from './mixins'; 2 | 3 | export const SMALL = scaleSize(5); 4 | export const GREAT = scaleSize(10); 5 | export const TALL = scaleSize(20); 6 | export const VENTI = scaleSize(30); 7 | 8 | export default { 9 | small: SMALL, 10 | great: GREAT, 11 | tall: TALL, 12 | venti: VENTI, 13 | }; 14 | -------------------------------------------------------------------------------- /src/styles/styled.d.ts: -------------------------------------------------------------------------------- 1 | import 'styled-components'; 2 | 3 | declare module 'styled-components' { 4 | export interface DefaultTheme { 5 | colors: { 6 | primary: string; 7 | seconday: string; 8 | tertiary: string; 9 | accent: string; 10 | black: string; 11 | white: string; 12 | danger: string; 13 | success: string; 14 | gray: string; 15 | grayLight: string; 16 | paragraph: string; 17 | }; 18 | 19 | fontFamily: { 20 | regular: string; 21 | medium: string; 22 | thin: string; 23 | light: string; 24 | bold: string; 25 | semiBold: string; 26 | }; 27 | 28 | fontLineHeight: { 29 | large: number; 30 | medium: number; 31 | small: number; 32 | }; 33 | 34 | fontWeight: { 35 | regular: string; 36 | bold: string; 37 | }; 38 | 39 | fontSizes: { 40 | tiny: string; 41 | small: string; 42 | medium: string; 43 | large: string; 44 | big: string; 45 | extraBig: string; 46 | }; 47 | 48 | spacing: { 49 | small: number; 50 | great: number; 51 | tall: number; 52 | venti: number; 53 | }; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/styles/theme.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {ThemeProvider, DefaultTheme} from 'styled-components/native'; 4 | 5 | import colors from './colors'; 6 | import {fontFamily, fontLineHeight, fontWeight, fontSizes} from './typography'; 7 | import spacing from './spacing'; 8 | 9 | export const theme: DefaultTheme = { 10 | colors, 11 | fontSizes, 12 | spacing, 13 | fontFamily, 14 | fontLineHeight, 15 | fontWeight, 16 | }; 17 | 18 | interface Props { 19 | children: React.ReactNode; 20 | } 21 | 22 | export const Theme = ({children}: Props): JSX.Element => ( 23 | {children} 24 | ); 25 | 26 | export default Theme; 27 | -------------------------------------------------------------------------------- /src/styles/typography.ts: -------------------------------------------------------------------------------- 1 | import {scaleFont} from './mixins'; 2 | 3 | // FONT FAMILY 4 | const FONT_FAMILY_REGULAR = 'Poppins-Regular'; 5 | const FONT_FAMILY_MEDIUM = 'Poppins-Medium'; 6 | const FONT_FAMILY_THIN = 'Poppins-Thin'; 7 | const FONT_FAMILY_LIGHT = 'Poppins-Light'; 8 | const FONT_FAMILY_BOLD = 'Poppins-Bold'; 9 | const FONT_FAMILY_SEMI_BOLD = 'Poppins-SemiBold'; 10 | 11 | export const fontFamily = { 12 | regular: FONT_FAMILY_REGULAR, 13 | medium: FONT_FAMILY_MEDIUM, 14 | thin: FONT_FAMILY_THIN, 15 | light: FONT_FAMILY_LIGHT, 16 | bold: FONT_FAMILY_BOLD, 17 | semiBold: FONT_FAMILY_SEMI_BOLD, 18 | }; 19 | 20 | // FONT WEIGHT 21 | export const FONT_WEIGHT_REGULAR = '400'; 22 | export const FONT_WEIGHT_BOLD = '700'; 23 | 24 | export const fontWeight = { 25 | regular: FONT_WEIGHT_REGULAR, 26 | bold: FONT_WEIGHT_BOLD, 27 | }; 28 | 29 | // FONT SIZE 30 | const FONT_SIZE_TINY = scaleFont(10); 31 | const FONT_SIZE_SMALL = scaleFont(14); 32 | const FONT_SIZE_MEDIUM = scaleFont(18); 33 | const FONT_SIZE_LARGE = scaleFont(22); 34 | const FONT_SIZE_BIG = scaleFont(30); 35 | const FONT_SIZE_EXTRA_BIG = scaleFont(40); 36 | 37 | export const fontSizes = { 38 | tiny: `${FONT_SIZE_TINY}px`, 39 | small: `${FONT_SIZE_SMALL}px`, 40 | medium: `${FONT_SIZE_MEDIUM}px`, 41 | large: `${FONT_SIZE_LARGE}px`, 42 | big: `${FONT_SIZE_BIG}px`, 43 | extraBig: `${FONT_SIZE_EXTRA_BIG}px`, 44 | }; 45 | 46 | // LINE HEIGHT 47 | export const LINE_HEIGHT_LARGE = scaleFont(36); 48 | export const LINE_HEIGHT_MEDIUM = scaleFont(18); 49 | export const LINE_HEIGHT_SMALL = scaleFont(16); 50 | 51 | export const fontLineHeight = { 52 | large: LINE_HEIGHT_LARGE, 53 | medium: LINE_HEIGHT_MEDIUM, 54 | small: LINE_HEIGHT_SMALL, 55 | }; 56 | 57 | export default { 58 | fontFamily, 59 | fontSizes, 60 | fontWeight, 61 | fontLineHeight, 62 | }; 63 | -------------------------------------------------------------------------------- /src/utils/dates.ts: -------------------------------------------------------------------------------- 1 | import { 2 | format as formatPattern, 3 | isValid, 4 | parseISO as parseISOFNS, 5 | } from 'date-fns'; 6 | 7 | import locale from 'date-fns/locale/pt-BR'; 8 | 9 | export const DATE_FORMAT_ISO = 'yyyy-MM-dd'; 10 | export const DATE_FORMAT_BR = 'dd/MM/yyyy'; 11 | export const DATE_FORMAT_FRIENDLY = " dd/MM 'às' HH:mm"; 12 | export const DATE_FORMAT_FRIENDLY_TIME = "'às' HH:mm"; 13 | 14 | export function parseStringToDate(value: string): Date { 15 | const parsedDate = parseISOFNS(value); 16 | if (!isValid(parsedDate)) { 17 | throw Error('canot parse date'); 18 | } 19 | return parsedDate; 20 | } 21 | export function parseDateToString({ 22 | value, 23 | format = DATE_FORMAT_BR, 24 | }: { 25 | value: Date; 26 | format?: string; 27 | }): string { 28 | return formatPattern(value, format, {locale}); 29 | } 30 | 31 | export const getDate = (date: Date): string => { 32 | return formatPattern(date, DATE_FORMAT_ISO, {locale}); 33 | }; 34 | 35 | export const getDateToday = (): string => { 36 | const today = new Date(); 37 | return formatPattern(today, DATE_FORMAT_ISO, {locale}); 38 | }; 39 | 40 | export const getDateAndHourFriendly = (value: Date): string => { 41 | return formatPattern(value, DATE_FORMAT_FRIENDLY, {locale}); 42 | }; 43 | 44 | export const getTimeFriendly = (value: Date): string => { 45 | return formatPattern(value, DATE_FORMAT_FRIENDLY_TIME, {locale}); 46 | }; 47 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export {default as strings} from './strings'; 2 | -------------------------------------------------------------------------------- /src/utils/strings.ts: -------------------------------------------------------------------------------- 1 | export const sanitizeString = (value: string): string => { 2 | return value 3 | .toLowerCase() 4 | .replace(/á/g, 'a') 5 | .replace(/é/g, 'e') 6 | .replace(/í/g, 'i') 7 | .replace(/ó/g, 'o') 8 | .replace(/ú/g, 'u'); 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/test-utils.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import React from 'react'; 3 | import {render, RenderAPI, RenderOptions} from '@testing-library/react-native'; 4 | import {Database} from '@nozbe/watermelondb'; 5 | import LokiJSAdapter, { 6 | LokiAdapterOptions, 7 | } from '@nozbe/watermelondb/adapters/lokijs'; 8 | 9 | import {theme} from 'styles/theme'; 10 | import {ThemeProvider} from 'styled-components/native'; 11 | 12 | import {NavigationContainer} from '@react-navigation/native'; 13 | import DatabaseProvider from '@nozbe/watermelondb/DatabaseProvider'; 14 | 15 | interface Props { 16 | children: JSX.Element; 17 | } 18 | 19 | import {schema, migrations, modelClasses} from 'database'; 20 | 21 | const AllTheProviders = ({children}: Props) => { 22 | const adapterConfig: LokiAdapterOptions = { 23 | schema, 24 | migrations, 25 | useWebWorker: false, 26 | useIncrementalIndexedDB: true, 27 | extraLokiOptions: { 28 | autosave: false, 29 | }, 30 | }; 31 | const adapter = new LokiJSAdapter(adapterConfig); 32 | const database = new Database({ 33 | adapter, 34 | modelClasses, 35 | }); 36 | 37 | return ( 38 | 39 | 40 | {children} 41 | 42 | 43 | ); 44 | }; 45 | 46 | const customRender = ( 47 | ui: React.ReactElement, 48 | options?: RenderOptions, 49 | ): RenderAPI => render(ui, {wrapper: AllTheProviders, ...options}); 50 | 51 | // re-export everything 52 | export * from '@testing-library/react-native'; 53 | 54 | // override render method 55 | export {customRender as render}; 56 | --------------------------------------------------------------------------------