├── .babelrc ├── .editorconfig ├── .env.example ├── .flowconfig ├── .gitignore ├── .watchmanconfig ├── App.js ├── README.md ├── __tests__ ├── App.js └── Expenses.js ├── app.json ├── assets └── images │ └── image.jpg ├── package.json ├── src ├── features │ ├── Auth │ │ └── Login.js │ ├── Bitcoin │ │ └── exchange-rate.js │ └── Expenses │ │ ├── form.js │ │ ├── index.js │ │ ├── list-item-header.js │ │ ├── list-item.js │ │ └── list.js ├── layout │ ├── header.js │ ├── navigation.js │ └── package.json ├── repositories │ ├── auth.js │ ├── expenses.js │ └── package.json ├── service │ ├── cryptocoins │ │ ├── index.js │ │ └── package.json │ └── firebase │ │ ├── database.js │ │ └── package.json └── util │ ├── currency.js │ ├── date.js │ └── package.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-expo", "react-native-dotenv"], 3 | "env": { 4 | "development": { 5 | "plugins": ["transform-react-jsx-source"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | 2 | bitvalor_endpoint = "https://api.bitvalor.com/v1/ticker.json" 3 | 4 | # all config variables can be found 5 | # in YOUR firebase project configs 6 | apiKey = '' 7 | authDomain = '' 8 | databaseURL = '' 9 | projectId = '' 10 | storageBucket = '' 11 | messagingSenderId = '' -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore templates for 'react-native init' 6 | /node_modules/react-native/local-cli/templates/.* 7 | 8 | ; Ignore RN jest 9 | /node_modules/react-native/jest/.* 10 | 11 | ; Ignore RNTester 12 | /node_modules/react-native/RNTester/.* 13 | 14 | ; Ignore the website subdir 15 | /node_modules/react-native/website/.* 16 | 17 | ; Ignore the Dangerfile 18 | /node_modules/react-native/danger/dangerfile.js 19 | 20 | ; Ignore Fbemitter 21 | /node_modules/fbemitter/.* 22 | 23 | ; Ignore "BUCK" generated dirs 24 | /node_modules/react-native/\.buckd/ 25 | 26 | ; Ignore unexpected extra "@providesModule" 27 | .*/node_modules/.*/node_modules/fbjs/.* 28 | 29 | ; Ignore polyfills 30 | /node_modules/react-native/Libraries/polyfills/.* 31 | 32 | ; Ignore various node_modules 33 | /node_modules/react-native-gesture-handler/.* 34 | /node_modules/expo/.* 35 | /node_modules/react-navigation/.* 36 | /node_modules/xdl/.* 37 | /node_modules/reqwest/.* 38 | /node_modules/metro-bundler/.* 39 | 40 | [include] 41 | 42 | [libs] 43 | node_modules/react-native/Libraries/react-native/react-native-interface.js 44 | node_modules/react-native/flow/ 45 | node_modules/expo/flow/ 46 | 47 | [options] 48 | emoji=true 49 | 50 | module.system=haste 51 | 52 | module.file_ext=.js 53 | module.file_ext=.jsx 54 | module.file_ext=.json 55 | module.file_ext=.ios.js 56 | 57 | munge_underscores=true 58 | 59 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 60 | 61 | suppress_type=$FlowIssue 62 | suppress_type=$FlowFixMe 63 | suppress_type=$FlowFixMeProps 64 | suppress_type=$FlowFixMeState 65 | suppress_type=$FixMe 66 | 67 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\) 68 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+ 69 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 70 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 71 | 72 | unsafe.enable_getters_and_setters=true 73 | 74 | [version] 75 | ^0.56.0 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # expo 4 | .expo/ 5 | 6 | # dependencies 7 | /node_modules 8 | 9 | # misc 10 | .env 11 | .env.local 12 | .env.development.local 13 | .env.test.local 14 | .env.production.local 15 | 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | 2 | import Firebase from 'firebase' 3 | import React, { Component } from 'react' 4 | import { StyleSheet, View, Text } from 'react-native' 5 | import Login from './src/features/Auth/Login' 6 | import BitcoinExchangeRate from './src/features/Bitcoin/exchange-rate' 7 | import Expenses from './src/features/Expenses' 8 | 9 | export default class SectionListBasics extends Component { 10 | constructor () { 11 | super() 12 | this.authListener() 13 | this.state = { 14 | currentView: 'expenses' 15 | } 16 | } 17 | 18 | /** 19 | * Firebase auth is real-time. 20 | * Every time remote auth state, 21 | * changes this event listener gets executed. 22 | */ 23 | authListener () { 24 | Firebase.auth().onAuthStateChanged(user => { 25 | const currentView = user != null ? 'expenses' : 'signin' 26 | if (this.state.currentView !== currentView) { 27 | this.setState({ currentView }) 28 | } 29 | }) 30 | } 31 | render() { 32 | return ( 33 | 34 | 35 | {this.state.currentView === 'signin' ? : null} 36 | {this.state.currentView === 'expenses' ? : null} 37 | 38 | ) 39 | } 40 | } 41 | 42 | const styles = StyleSheet.create({ 43 | container: { 44 | flex: 1, 45 | paddingTop: 22 46 | }, 47 | }) 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vedovelli Expense Tracker 2 | 3 | [![image.png](https://s20.postimg.org/xpyfbpl8d/image.png)](https://postimg.org/image/h26x97qgp/) 4 | 5 | Este é o projeto base para quando quero aprender uma nova tecnologia. É o meu "TODO list" pessoal. 6 | 7 | Trata-se de uma lista de gastos em dinheiro vivo. 8 | 9 | O projeto é de meu uso pessoal e é uma das apps que mais utilizo em meu smartphone. 10 | 11 | ## Iniciando 12 | 13 | Os dados são armazenados num *realtime database* no [Firebase](https://firebase.com/). 14 | 15 | Crie sua conta e um banco de dados. As informações de acesso devem ser adicionadas no *.env*, que ainda não existe. Basta duplicar o *.env.example*, renomeando para *.env* e adicionar os dados de acesso ao Firebase. 16 | 17 | Também é necessário habilitar lá no Firebase a **autenticação por usuário e senha** e - por fim - criar um usuário. 18 | 19 | ### Pré-requisitos 20 | 21 | Node.js e (NPM ou Yarn). 22 | 23 | **Recomenda-se o uso do Yarn** 24 | 25 | Caso precise intala-lo, basta usar o NPM. 26 | 27 | ``` 28 | npm i -g yarn 29 | ``` 30 | 31 | ### Instalação 32 | 33 | O projeto foi criado com **create-react-native-app** então basta clonar o projeto e, com o Terminal, navegar até a pasta do projeto e executar: 34 | 35 | ``` 36 | yarn && yarn start 37 | ``` 38 | 39 | A instalação das dependências será feita e o projeto será executado. 40 | 41 | Para visualizar basta, em seu smartphone, instalar a app [Expo](https://expo.io/). Ela lhe permitirá escanear o QR Code exibido no Terminal para executar a app. 42 | 43 | Se tudo correu bem, você verá o formulário para login. 44 | 45 | ## Desenvolvido com 46 | 47 | * [React Native](http://facebook.github.io/react-native) - O Framework utilizado 48 | * [Node.js](https://nodejs.org/) - Para as ferramentas de servidor e compilação 49 | * [Yarn](https://yarnpkg.com/en/) - Gerenciador de dependências de projeto 50 | * [Firebase](https://firebase.com/) - Banco de dados 51 | 52 | ## Autor 53 | 54 | * **Fabio Vedovelli** - [Github](https://github.com/vedovelli) 55 | 56 | ## Licença 57 | 58 | Não há licença. Use à vontade. Pode até chamar de seu. 59 | -------------------------------------------------------------------------------- /__tests__/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from '../App'; 3 | 4 | import renderer from 'react-test-renderer'; 5 | 6 | it('renders without crashing', () => { 7 | const rendered = renderer.create().toJSON(); 8 | expect(rendered).toBeTruthy(); 9 | }); 10 | -------------------------------------------------------------------------------- /__tests__/Expenses.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Expenses from 'features/Expenses'; 3 | 4 | import renderer from 'react-test-renderer'; 5 | 6 | it('renders without crashing', () => { 7 | const rendered = renderer.create().toJSON(); 8 | expect(rendered).toBeTruthy(); 9 | }); 10 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Vedovelli Expenses", 4 | "slug": "vedovelli-expenses", 5 | "orientation": "portrait", 6 | "icon": "https://s20.postimg.org/hlh0sejil/icon.png", 7 | "sdkVersion": "23.0.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /assets/images/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vedovelli/expenses-native/7eb5ea9ae8d2e86572d1ea1fde39aae1aa72e139/assets/images/image.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AwesomeProject", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "jest-expo": "23.0.0", 7 | "react-native-scripts": "1.8.1", 8 | "react-test-renderer": "16.0.0" 9 | }, 10 | "main": "./node_modules/react-native-scripts/build/bin/crna-entry.js", 11 | "scripts": { 12 | "start": "react-native-scripts start", 13 | "eject": "react-native-scripts eject", 14 | "android": "react-native-scripts android", 15 | "ios": "react-native-scripts ios", 16 | "test": "node node_modules/jest/bin/jest.js --watch" 17 | }, 18 | "jest": { 19 | "preset": "jest-expo" 20 | }, 21 | "dependencies": { 22 | "accounting": "^0.4.1", 23 | "axios": "^0.17.1", 24 | "expo": "^23.0.4", 25 | "firebase": "^4.8.0", 26 | "lodash": "^4.17.4", 27 | "react": "16.0.0", 28 | "react-native": "0.50.3", 29 | "react-native-dotenv": "^0.1.0", 30 | "react-native-easy-toast": "^1.0.9", 31 | "react-native-swipeout": "^2.3.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/features/Auth/Login.js: -------------------------------------------------------------------------------- 1 | 2 | import Header from 'layout/header' 3 | import React, { Component } from 'react' 4 | import authRepository from 'repositories/auth' 5 | import { Platform, 6 | View, 7 | Text, 8 | StyleSheet, 9 | TouchableWithoutFeedback, 10 | Keyboard, 11 | TextInput, 12 | Button } from 'react-native' 13 | 14 | export default class Login extends Component { 15 | constructor (props) { 16 | super(props) 17 | this.doLogin = this.doLogin.bind(this) 18 | this.state = { 19 | email: '', 20 | password: '' 21 | } 22 | } 23 | componentDidMount () { 24 | emailField.focus() 25 | } 26 | dismiss () { 27 | Keyboard.dismiss() 28 | } 29 | doLogin () { 30 | const { email, password } = this.state 31 | if (email && password) { 32 | const cleanEmail = email.toLowerCase().trim() 33 | authRepository.signin(cleanEmail, password) 34 | } 35 | } 36 | render () { 37 | return ( 38 | 39 |
40 | 41 | 42 | emailField = e} 44 | placeholder="E-mail" 45 | style={styles.textInput} 46 | keyboardType="email-address" 47 | autoCapitalize="none" 48 | onChangeText={(email) => this.setState({email})} 49 | value={this.state.email} /> 50 | passwordField = e} 52 | secureTextEntry={true} 53 | placeholder="Password" 54 | style={styles.textInput} 55 | onChangeText={(password) => this.setState({password})} 56 | value={this.state.password} /> 57 |