├── .watchmanconfig
├── .eslintignore
├── .gitattributes
├── app.json
├── android
├── .settings
│ └── org.eclipse.buildship.core.prefs
├── app
│ ├── debug.keystore
│ ├── .settings
│ │ └── org.eclipse.buildship.core.prefs
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ ├── colors.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── drawable
│ │ │ │ │ ├── icon.png
│ │ │ │ │ └── bootsplash.xml
│ │ │ │ ├── drawable-hdpi
│ │ │ │ │ └── icon.png
│ │ │ │ ├── drawable-ldpi
│ │ │ │ │ └── icon.png
│ │ │ │ ├── drawable-mdpi
│ │ │ │ │ └── icon.png
│ │ │ │ ├── drawable-xhdpi
│ │ │ │ │ └── icon.png
│ │ │ │ ├── drawable-xxhdpi
│ │ │ │ │ └── icon.png
│ │ │ │ └── drawable-xxxhdpi
│ │ │ │ │ └── icon.png
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── cabenomeubolso
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── MainApplication.java
│ │ └── debug
│ │ │ └── AndroidManifest.xml
│ ├── .classpath
│ ├── proguard-rules.pro
│ ├── .project
│ ├── build_defs.bzl
│ ├── _BUCK
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── settings.gradle
├── .project
├── gradle.properties
├── build.gradle
├── gradlew.bat
└── gradlew
├── src
├── pages
│ ├── CabeSave
│ │ ├── components
│ │ │ ├── index.ts
│ │ │ └── FloatingBottomContainer
│ │ │ │ ├── styles.ts
│ │ │ │ └── index.tsx
│ │ ├── CabeName
│ │ │ ├── styles.ts
│ │ │ └── index.tsx
│ │ ├── CabeValue
│ │ │ ├── styles.ts
│ │ │ └── index.tsx
│ │ ├── CabeItemsList
│ │ │ ├── styles.ts
│ │ │ └── index.tsx
│ │ ├── CabeSaveContext.tsx
│ │ └── index.tsx
│ ├── Main
│ │ └── index.tsx
│ ├── CabeProcess
│ │ ├── CabeItemQuantity
│ │ │ ├── styles.ts
│ │ │ └── index.tsx
│ │ ├── CabeItemValue
│ │ │ ├── styles.ts
│ │ │ └── index.tsx
│ │ ├── CabeItems
│ │ │ ├── styles.ts
│ │ │ └── index.tsx
│ │ └── index.tsx
│ ├── Intro
│ │ ├── styles.ts
│ │ └── index.tsx
│ ├── FinalizedCabeView
│ │ ├── styles.ts
│ │ └── index.tsx
│ └── CabesList
│ │ ├── styles.ts
│ │ └── index.tsx
├── typings
│ ├── console.d.ts
│ └── redux.d.ts
├── store
│ ├── modules
│ │ ├── rootSaga.ts
│ │ ├── meta
│ │ │ ├── actions.ts
│ │ │ └── reducer.ts
│ │ ├── rootReducer.ts
│ │ └── cabes
│ │ │ ├── reducer.ts
│ │ │ ├── actions.ts
│ │ │ └── sagas.ts
│ ├── createStore.ts
│ ├── persistReducers.ts
│ └── index.ts
├── styles
│ └── colors.ts
├── models
│ ├── CabeItem.ts
│ └── Cabe.ts
├── components
│ ├── Header
│ │ ├── styles.ts
│ │ └── index.tsx
│ ├── index.ts
│ ├── ShimmerLoading
│ │ ├── index.tsx
│ │ └── styles.ts
│ ├── NumericKeyboard
│ │ ├── styles.ts
│ │ └── index.tsx
│ ├── Button
│ │ └── index.tsx
│ └── CabeItemInput
│ │ └── index.tsx
├── services
│ ├── bd
│ │ ├── schemas
│ │ │ ├── CabeItemSchema.ts
│ │ │ └── CabeSchema.ts
│ │ └── index.ts
│ └── NavigationService.ts
├── config
│ └── ReactotronConfig.ts
├── routes.tsx
└── index.tsx
├── .prettierrc.js
├── ios
├── cabenomeubolso
│ ├── Images.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── AppDelegate.h
│ ├── main.m
│ ├── AppDelegate.m
│ ├── Info.plist
│ └── Base.lproj
│ │ └── LaunchScreen.xib
├── cabenomeubolso.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── cabenomeubolsoTests
│ ├── Info.plist
│ └── cabenomeubolsoTests.m
├── cabenomeubolso-tvOSTests
│ └── Info.plist
├── cabenomeubolso-tvOS
│ └── Info.plist
├── Podfile
├── cabenomeubolso.xcodeproj
│ └── xcshareddata
│ │ └── xcschemes
│ │ ├── cabenomeubolso.xcscheme
│ │ └── cabenomeubolso-tvOS.xcscheme
└── Podfile.lock
├── jest.config.js
├── .buckconfig
├── .editorconfig
├── index.js
├── __tests__
└── App-test.tsx
├── metro.config.js
├── privacy.md
├── .gitignore
├── babel.config.js
├── README.md
├── .eslintrc.js
├── package.json
└── tsconfig.json
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*.d.ts
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cabenomeubolso",
3 | "displayName": "cabenomeubolso"
4 | }
--------------------------------------------------------------------------------
/android/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/android/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italomlp/cabenomeubolso/HEAD/android/app/debug.keystore
--------------------------------------------------------------------------------
/android/app/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=..
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/src/pages/CabeSave/components/index.ts:
--------------------------------------------------------------------------------
1 | export { default as FloatingBottomContainer } from './FloatingBottomContainer';
2 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | trailingComma: 'es5',
4 | tabWidth: 2,
5 | printWidth: 80,
6 | };
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Cabe no Meu Bolso
3 |
4 |
--------------------------------------------------------------------------------
/ios/cabenomeubolso/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italomlp/cabenomeubolso/HEAD/android/app/src/main/res/drawable/icon.png
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italomlp/cabenomeubolso/HEAD/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'react-native',
3 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
4 | };
5 |
--------------------------------------------------------------------------------
/.buckconfig:
--------------------------------------------------------------------------------
1 |
2 | [android]
3 | target = Google Inc.:Google APIs:23
4 |
5 | [maven_repositories]
6 | central = https://repo1.maven.org/maven2
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italomlp/cabenomeubolso/HEAD/android/app/src/main/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italomlp/cabenomeubolso/HEAD/android/app/src/main/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italomlp/cabenomeubolso/HEAD/android/app/src/main/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italomlp/cabenomeubolso/HEAD/android/app/src/main/res/drawable-xhdpi/icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italomlp/cabenomeubolso/HEAD/android/app/src/main/res/drawable-xxhdpi/icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/italomlp/cabenomeubolso/HEAD/android/app/src/main/res/drawable-xxxhdpi/icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #fff
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = false
8 | insert_final_newline = false
--------------------------------------------------------------------------------
/src/typings/console.d.ts:
--------------------------------------------------------------------------------
1 | import Reactotron from 'reactotron-react-native';
2 |
3 | declare global {
4 | interface Console {
5 | tron: Reactotron;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/store/modules/rootSaga.ts:
--------------------------------------------------------------------------------
1 | import { all } from 'redux-saga/effects';
2 |
3 | import cabes from './cabes/sagas';
4 |
5 | export default function* rootSaga() {
6 | return yield all([cabes]);
7 | }
8 |
--------------------------------------------------------------------------------
/src/typings/redux.d.ts:
--------------------------------------------------------------------------------
1 | import { Action as DefaultAction } from 'redux';
2 |
3 | declare module 'redux' {
4 | export interface Action extends DefaultAction {
5 | payload?: P;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/styles/colors.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | c100: '#2088D0',
3 | c200: '#FFA348',
4 | c300: '#D4328C',
5 | c400: '#4B20AF',
6 | c500: '#5CE7AD',
7 |
8 | n100: '#fff',
9 | n900: '#000',
10 | };
11 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'cabenomeubolso'
2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
3 | include ':app'
4 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import {AppRegistry} from 'react-native';
6 | import App from './src';
7 | import {name as appName} from './app.json';
8 |
9 | AppRegistry.registerComponent(appName, () => App);
10 |
--------------------------------------------------------------------------------
/src/models/CabeItem.ts:
--------------------------------------------------------------------------------
1 | export interface CanonCabeItem {
2 | id: string;
3 | quantity: number;
4 | name: string;
5 | }
6 |
7 | export interface CabeItem extends CanonCabeItem {
8 | value: number;
9 | done: boolean;
10 | }
11 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/src/store/modules/meta/actions.ts:
--------------------------------------------------------------------------------
1 | const PREFIX = '@meta';
2 |
3 | export const TYPES = {
4 | markIntroAsViewed: `${PREFIX}/MARK_INTRO_AS_VIEWED`,
5 | };
6 |
7 | export function markIntroAsViewed() {
8 | return {
9 | type: TYPES.markIntroAsViewed,
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/Header/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/native';
2 |
3 | import colors from 'styles/colors';
4 |
5 | export const HeaderTitleContainer = styled.Text.attrs({
6 | numberOfLines: 1,
7 | })`
8 | font-size: 20px;
9 | font-weight: bold;
10 | color: ${colors.n100};
11 | `;
12 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Header } from './Header';
2 | export { default as Button } from './Button';
3 | export { default as NumericKeyboard } from './NumericKeyboard';
4 | export { default as ShimmerLoading } from './ShimmerLoading';
5 | export { default as CabeItemInput } from './CabeItemInput';
6 |
--------------------------------------------------------------------------------
/ios/cabenomeubolso.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/cabenomeubolso.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/models/Cabe.ts:
--------------------------------------------------------------------------------
1 | import { CabeItem } from './CabeItem';
2 |
3 | export interface CanonCabe {
4 | name: string;
5 | id: string;
6 | value: number;
7 | items: CabeItem[];
8 | }
9 |
10 | export interface Cabe extends CanonCabe {
11 | createdAt: Date;
12 | finalized: boolean;
13 | finalizedAt?: Date;
14 | }
15 |
--------------------------------------------------------------------------------
/__tests__/App-test.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import 'react-native';
6 | import React from 'react';
7 | // Note: test renderer must be required after react-native.
8 | import renderer from 'react-test-renderer';
9 |
10 | import App from '../src';
11 |
12 | it('renders correctly', () => {
13 | renderer.create();
14 | });
15 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/bootsplash.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/pages/CabeSave/components/FloatingBottomContainer/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/native';
2 |
3 | export const Container = styled.KeyboardAvoidingView.attrs({
4 | contentContainerStyle: { flex: 1 },
5 | })`
6 | position: absolute;
7 | width: 100%;
8 | padding-bottom: 10px;
9 | bottom: 0;
10 | background-color: #fff;
11 | `;
12 |
--------------------------------------------------------------------------------
/android/app/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/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: false,
14 | },
15 | }),
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/src/store/createStore.ts:
--------------------------------------------------------------------------------
1 | import { createStore, compose, applyMiddleware } from 'redux';
2 |
3 | export default (reducers: any, middlewares: any) => {
4 | const composer =
5 | process.env.NODE_ENV === 'development'
6 | ? compose(console.tron.createEnhancer(), applyMiddleware(...middlewares))
7 | : compose(applyMiddleware(...middlewares));
8 |
9 | return createStore(reducers, composer);
10 | };
11 |
--------------------------------------------------------------------------------
/src/store/modules/rootReducer.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import cabes, { State as CabesState } from './cabes/reducer';
4 | import meta, { State as MetaState } from './meta/reducer';
5 |
6 | const reducers = combineReducers({
7 | cabes,
8 | meta,
9 | });
10 |
11 | export type RootStore = {
12 | cabes: CabesState;
13 | meta: MetaState;
14 | };
15 |
16 | export default reducers;
17 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/store/persistReducers.ts:
--------------------------------------------------------------------------------
1 | import { persistReducer } from 'redux-persist';
2 | import { Reducer } from 'redux';
3 | import AsyncStorage from '@react-native-community/async-storage';
4 |
5 | export default (reducers: Reducer) => {
6 | const persistedReducer = persistReducer(
7 | {
8 | key: 'cabenomeubolso',
9 | storage: AsyncStorage,
10 | whitelist: ['meta'],
11 | },
12 | reducers
13 | );
14 | return persistedReducer;
15 | };
16 |
--------------------------------------------------------------------------------
/src/services/bd/schemas/CabeItemSchema.ts:
--------------------------------------------------------------------------------
1 | import { ObjectSchema } from 'realm';
2 |
3 | export default class CabeItemSchema {
4 | static schema: ObjectSchema = {
5 | name: 'CabeItem',
6 | primaryKey: 'id',
7 | properties: {
8 | id: { type: 'string', indexed: true },
9 | name: 'string',
10 | quantity: 'int',
11 | value: { type: 'float', default: 0 },
12 | done: { type: 'bool', default: false },
13 | },
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/ios/cabenomeubolso/AppDelegate.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (nonatomic, strong) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/ios/cabenomeubolso/main.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 |
10 | #import "AppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | @autoreleasepool {
14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
--------------------------------------------------------------------------------
/android/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | cabenomeubolso
4 | Project android created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/config/ReactotronConfig.ts:
--------------------------------------------------------------------------------
1 | import Reactotron from 'reactotron-react-native';
2 | import { reactotronRedux } from 'reactotron-redux';
3 | import sagaPlugin from 'reactotron-redux-saga';
4 |
5 | if (__DEV__) {
6 | const tron = Reactotron.configure()
7 | .useReactNative()
8 | .use(reactotronRedux())
9 | .use(sagaPlugin({}))
10 | .connect();
11 |
12 | tron.clear();
13 |
14 | console.tron = tron;
15 | } else {
16 | console.tron = {
17 | log: () => {},
18 | error: () => {},
19 | logImportant: () => {},
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/ShimmerLoading/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Shimmer from 'react-native-shimmer';
3 |
4 | import { Container, Title, Description, TextContainer } from './styles';
5 |
6 | const ShimmerLoading: React.FC = () => {
7 | return (
8 |
9 |
10 |
11 | Cabe no Meu Bolso
12 | Carregando...
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default ShimmerLoading;
20 |
--------------------------------------------------------------------------------
/src/store/modules/meta/reducer.ts:
--------------------------------------------------------------------------------
1 | import produce from 'immer';
2 | import { Action } from 'redux';
3 |
4 | import { TYPES } from './actions';
5 |
6 | export type State = {
7 | introViewed: boolean;
8 | };
9 |
10 | const INITIAL_STATE: State = {
11 | introViewed: false,
12 | };
13 |
14 | export default function meta(state = INITIAL_STATE, action: Action) {
15 | return produce(state, draft => {
16 | switch (action.type) {
17 | case TYPES.markIntroAsViewed:
18 | draft.introViewed = true;
19 | break;
20 | default:
21 | }
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/src/services/bd/schemas/CabeSchema.ts:
--------------------------------------------------------------------------------
1 | import { ObjectSchema } from 'realm';
2 |
3 | export default class CabeSchema {
4 | static schema: ObjectSchema = {
5 | name: 'Cabe',
6 | primaryKey: 'id',
7 | properties: {
8 | id: { type: 'string', indexed: true },
9 | createdAt: { type: 'date', default: new Date() },
10 | name: 'string',
11 | value: 'float',
12 | usedValue: { type: 'float', default: 0 },
13 | items: 'CabeItem[]',
14 | finalized: { type: 'bool', default: false },
15 | finalizedAt: { type: 'date', optional: true },
16 | },
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/CabeSave/components/FloatingBottomContainer/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Platform, SafeAreaView } from 'react-native';
3 |
4 | import { Container } from './styles';
5 |
6 | type Props = {
7 | children: React.ReactChild | React.ReactChildren | React.ReactElement;
8 | };
9 |
10 | function FloatingBottomContainer({ children }: Props) {
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | }
17 |
18 | export default memo(FloatingBottomContainer);
19 |
--------------------------------------------------------------------------------
/src/routes.tsx:
--------------------------------------------------------------------------------
1 | import { createAppContainer } from 'react-navigation';
2 | import { createStackNavigator } from 'react-navigation-stack';
3 |
4 | import Main from 'pages/Main';
5 | import CabeSave from 'pages/CabeSave';
6 | import CabeProcess from 'pages/CabeProcess';
7 | import FinalizedCabeView from 'pages/FinalizedCabeView';
8 |
9 | const Routes = createAppContainer(
10 | createStackNavigator(
11 | {
12 | Main,
13 | CabeSave,
14 | CabeProcess,
15 | FinalizedCabeView,
16 | },
17 | {
18 | headerMode: 'none',
19 | }
20 | )
21 | );
22 |
23 | export default Routes;
24 |
--------------------------------------------------------------------------------
/src/services/NavigationService.ts:
--------------------------------------------------------------------------------
1 | import {
2 | NavigationActions,
3 | NavigationParams,
4 | NavigationContainerComponent,
5 | } from 'react-navigation';
6 |
7 | let navigator: NavigationContainerComponent;
8 |
9 | function setTopLevelNavigator(navigatorRef: NavigationContainerComponent) {
10 | navigator = navigatorRef;
11 | }
12 |
13 | function navigate(routeName: string, params?: NavigationParams) {
14 | if (navigator) {
15 | navigator.dispatch(
16 | NavigationActions.navigate({
17 | routeName,
18 | params,
19 | })
20 | );
21 | }
22 | }
23 |
24 | export default {
25 | navigate,
26 | setTopLevelNavigator,
27 | };
28 |
--------------------------------------------------------------------------------
/src/pages/Main/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useSelector } from 'react-redux';
3 | import { RootStore } from 'store/modules/rootReducer';
4 | import Intro from 'pages/Intro';
5 | import CabesList from 'pages/CabesList';
6 | import RNBootSplash from 'react-native-bootsplash';
7 |
8 | // import { Container } from './styles';
9 |
10 | export default function Main() {
11 | const introViewed = useSelector((state: RootStore) => state.meta.introViewed);
12 |
13 | useEffect(() => {
14 | RNBootSplash.hide({ duration: 250 });
15 | }, []);
16 |
17 | if (introViewed) {
18 | return ;
19 | }
20 |
21 | return ;
22 | }
23 |
--------------------------------------------------------------------------------
/android/app/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | app
4 | Project app created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.buildship.core.gradleprojectbuilder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.buildship.core.gradleprojectnature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/pages/CabeSave/CabeName/styles.ts:
--------------------------------------------------------------------------------
1 | import { Input as DefaultInput } from 'react-native-elements';
2 | import styled from 'styled-components/native';
3 | import Color from 'color';
4 |
5 | import colors from 'styles/colors';
6 |
7 | export const DescriptionContainer = styled.View`
8 | margin: 15px;
9 | padding-bottom: 15px;
10 | border-bottom-width: 1px;
11 | border-bottom-color: ${Color(colors.c200)
12 | .lighten(0.3)
13 | .hex()};
14 | `;
15 |
16 | export const DescriptionText = styled.Text`
17 | color: ${Color(colors.n100)
18 | .darken(0.7)
19 | .hex()};
20 | font-size: 16px;
21 | margin-bottom: 5px;
22 | text-align: center;
23 | font-weight: bold;
24 | `;
25 |
26 | export const Input = styled(DefaultInput).attrs({})``;
27 |
--------------------------------------------------------------------------------
/src/pages/CabeSave/CabeValue/styles.ts:
--------------------------------------------------------------------------------
1 | import { Input as DefaultInput } from 'react-native-elements';
2 | import styled from 'styled-components/native';
3 |
4 | import Color from 'color';
5 |
6 | import colors from 'styles/colors';
7 |
8 | export const DescriptionContainer = styled.View`
9 | margin: 15px;
10 | padding-bottom: 15px;
11 | border-bottom-width: 1px;
12 | border-bottom-color: ${Color(colors.c200)
13 | .lighten(0.3)
14 | .hex()};
15 | `;
16 |
17 | export const DescriptionText = styled.Text`
18 | color: ${Color(colors.n100)
19 | .darken(0.7)
20 | .hex()};
21 | font-size: 16px;
22 | margin-bottom: 5px;
23 | `;
24 |
25 | export const Input = styled(DefaultInput).attrs({
26 | inputStyle: {
27 | textAlign: 'right',
28 | },
29 | })``;
30 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import createSagaMiddleware from 'redux-saga';
2 | import { persistStore } from 'redux-persist';
3 |
4 | import createStore from './createStore';
5 | import persistReducers from './persistReducers';
6 |
7 | import rootReducer from './modules/rootReducer';
8 | import rootSaga from './modules/rootSaga';
9 |
10 | const sagaMonitor =
11 | process.env.NODE_ENV === 'development'
12 | ? console.tron.createSagaMonitor()
13 | : null;
14 |
15 | const sagaMiddleware = createSagaMiddleware({ sagaMonitor });
16 |
17 | const middlewares = [sagaMiddleware];
18 |
19 | const store: any = createStore(persistReducers(rootReducer), middlewares);
20 | const persistor = persistStore(store);
21 |
22 | sagaMiddleware.run(rootSaga);
23 |
24 | export { store, persistor };
25 |
--------------------------------------------------------------------------------
/src/pages/CabeProcess/CabeItemQuantity/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/native';
2 | import Color from 'color';
3 | import { Input as DefaultInput } from 'react-native-elements';
4 |
5 | import colors from 'styles/colors';
6 |
7 | export const DescriptionContainer = styled.View`
8 | margin: 15px;
9 | padding-bottom: 15px;
10 | border-bottom-width: 1px;
11 | border-bottom-color: ${Color(colors.c200)
12 | .lighten(0.3)
13 | .hex()};
14 | `;
15 |
16 | export const DescriptionText = styled.Text`
17 | color: ${Color(colors.n100)
18 | .darken(0.7)
19 | .hex()};
20 | font-size: 16px;
21 | margin-bottom: 5px;
22 | `;
23 |
24 | export const Input = styled(DefaultInput).attrs({
25 | inputStyle: {
26 | textAlign: 'right',
27 | },
28 | })``;
29 |
--------------------------------------------------------------------------------
/ios/cabenomeubolso/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/src/components/ShimmerLoading/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/native';
2 |
3 | import colors from 'styles/colors';
4 |
5 | export const Container = styled.View`
6 | flex: 1;
7 | background-color: #fff;
8 | justify-content: center;
9 | align-items: center;
10 | position: absolute;
11 | top: 0;
12 | left: 0;
13 | right: 0;
14 | bottom: 0;
15 | z-index: 999;
16 | `;
17 |
18 | export const TextContainer = styled.View`
19 | justify-content: center;
20 | align-items: center;
21 | `;
22 |
23 | export const Title = styled.Text`
24 | font-size: 24px;
25 | color: ${colors.c400};
26 | text-align: center;
27 | `;
28 |
29 | export const Description = styled.Text`
30 | font-size: 14px;
31 | color: ${colors.n900};
32 | text-align: center;
33 | padding-top: 30px;
34 | `;
35 |
--------------------------------------------------------------------------------
/ios/cabenomeubolsoTests/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/cabenomeubolso-tvOSTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
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 |
--------------------------------------------------------------------------------
/privacy.md:
--------------------------------------------------------------------------------
1 | ## Política de privacidade para o app **Cabe no Meu Bolso**
2 |
3 | Todas as suas informações pessoais recolhidas, quando forem, serão usadas para ajudar a tornar o seu uso do nosso app o mais produtivo e agradável possível.
4 |
5 | A garantia da confidencialidade dos dados pessoais dos utilizadores do nosso app é importante para o Cabe no Meu Bolso.
6 |
7 | Todas as informações pessoais relativas a membros, assinantes, clientes ou visitantes que usem o Cabe no Meu Bolso serão tratadas em concordância com a Lei da Proteção de Dados Pessoais de 26 de outubro de 1998 (Lei n.º 67/98).
8 |
9 | A informação pessoal recolhida pode incluir o seu nome, e-mail e/ou data de nascimento.
10 |
11 | O uso do Cabe no Meu Bolso pressupõe a aceitação deste Acordo de privacidade. A equipe do Cabe no Meu Bolso reserva-se ao direito de alterar este acordo sem aviso prévio. Deste modo, recomendamos que consulte a nossa política de privacidade com regularidade de forma a estar sempre atualizado.
12 |
--------------------------------------------------------------------------------
/src/pages/Intro/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/native';
2 | import LinearGradient from 'react-native-linear-gradient';
3 | import colors from 'styles/colors';
4 |
5 | export const SlideContainer = styled(LinearGradient)`
6 | flex: 1;
7 | `;
8 |
9 | export const SlideContent = styled.SafeAreaView`
10 | flex: 1;
11 | justify-content: center;
12 | align-items: center;
13 | padding: 40px;
14 | `;
15 |
16 | export const SlideTitle = styled.Text`
17 | font-size: 22px;
18 | color: ${colors.n100};
19 | font-weight: 700;
20 | text-align: center;
21 | margin-bottom: 10px;
22 | `;
23 |
24 | export const SlideSubtitle = styled.Text`
25 | font-size: 18px;
26 | color: ${colors.n100};
27 | font-weight: 700;
28 | text-align: center;
29 | margin-bottom: 10px;
30 | opacity: 0.9;
31 | `;
32 |
33 | export const SlideDescription = styled.Text`
34 | font-size: 16px;
35 | color: ${colors.n100};
36 | font-weight: 400;
37 | text-align: center;
38 | opacity: 0.9;
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 | android.useAndroidX=true
21 | android.enableJetifier=true
22 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import 'react-native-gesture-handler';
4 | import 'config/ReactotronConfig';
5 | import 'react-native-get-random-values';
6 |
7 | import { Provider } from 'react-redux';
8 | import { ThemeProvider } from 'react-native-elements';
9 | import CodePush from 'react-native-code-push';
10 | import { PersistGate } from 'redux-persist/integration/react';
11 |
12 | import { store, persistor } from 'store';
13 | import Routes from 'routes';
14 | import NavigationService from 'services/NavigationService';
15 |
16 | const App = () => (
17 |
18 |
19 |
20 | {
22 | if (navigatorRef) {
23 | NavigationService.setTopLevelNavigator(navigatorRef);
24 | }
25 | }}
26 | />
27 |
28 |
29 |
30 | );
31 |
32 | export default CodePush({
33 | checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
34 | })(App);
35 |
--------------------------------------------------------------------------------
/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 = "28.0.3"
6 | minSdkVersion = 16
7 | compileSdkVersion = 28
8 | targetSdkVersion = 28
9 | }
10 | repositories {
11 | google()
12 | jcenter()
13 | }
14 | dependencies {
15 | classpath("com.android.tools.build:gradle:3.4.2")
16 |
17 | // NOTE: Do not place your application dependencies here; they belong
18 | // in the individual module build.gradle files
19 | }
20 | }
21 |
22 | allprojects {
23 | repositories {
24 | mavenLocal()
25 | maven {
26 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
27 | url("$rootDir/../node_modules/react-native/android")
28 | }
29 | maven {
30 | // Android JSC is installed from npm
31 | url("$rootDir/../node_modules/jsc-android/dist")
32 | }
33 |
34 | google()
35 | jcenter()
36 | maven { url 'https://jitpack.io' }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/.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 | project.xcworkspace
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 |
33 | # Visual Studio Code
34 | #
35 | .vscode/
36 |
37 | # node.js
38 | #
39 | node_modules/
40 | npm-debug.log
41 | yarn-error.log
42 |
43 | # BUCK
44 | buck-out/
45 | \.buckd/
46 | *.keystore
47 | !debug.keystore
48 |
49 | # fastlane
50 | #
51 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
52 | # screenshots whenever they are needed.
53 | # For more information about the recommended setup visit:
54 | # https://docs.fastlane.tools/best-practices/source-control/
55 |
56 | */fastlane/report.xml
57 | */fastlane/Preview.html
58 | */fastlane/screenshots
59 |
60 | # Bundle artifact
61 | *.jsbundle
62 |
63 | # CocoaPods
64 | /ios/Pods/
65 |
66 | .vscode/
67 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | // TODO fix bug: this code is breaking development environment and
4 | // is needed for release builds
5 | // if (process.env.NODE_ENV !== 'development') {
6 | // console.log('passa aqui')
7 | // return {
8 | // presets: ['module:metro-react-native-babel-preset'],
9 | // plugins: [
10 | // 'transform-remove-console',
11 | // 'ignite-ignore-reactotron',
12 | // [
13 | // 'module-resolver',
14 | // {
15 | // root: ['./src'],
16 | // extensions: [
17 | // '.ios.js',
18 | // '.android.js',
19 | // '.js',
20 | // '.ts',
21 | // '.tsx',
22 | // '.json',
23 | // ],
24 | // },
25 | // ],
26 | // ],
27 | // };
28 | // }
29 | return {
30 | presets: ['module:metro-react-native-babel-preset'],
31 | plugins: [
32 | [
33 | 'module-resolver',
34 | {
35 | root: ['./src'],
36 | extensions: ['.ios.js', '.android.js', '.js', '.ts', '.tsx', '.json'],
37 | },
38 | ],
39 | ],
40 | };
41 | };
42 |
--------------------------------------------------------------------------------
/src/pages/CabeProcess/CabeItemValue/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/native';
2 | import Color from 'color';
3 | import { Input as DefaultInput } from 'react-native-elements';
4 |
5 | import colors from 'styles/colors';
6 |
7 | export const DescriptionContainer = styled.View`
8 | margin: 15px;
9 | padding-bottom: 15px;
10 | border-bottom-width: 1px;
11 | border-bottom-color: ${Color(colors.c200)
12 | .lighten(0.3)
13 | .hex()};
14 | `;
15 |
16 | export const DescriptionText = styled.Text`
17 | color: ${Color(colors.n100)
18 | .darken(0.7)
19 | .hex()};
20 | font-size: 16px;
21 | margin-bottom: 5px;
22 | `;
23 |
24 | export const Input = styled(DefaultInput).attrs({
25 | inputStyle: {
26 | textAlign: 'right',
27 | },
28 | })``;
29 |
30 | export const QuantityText = styled.Text`
31 | margin-top: 5px;
32 | text-align: center;
33 | font-size: 12px;
34 | font-weight: 400;
35 | color: ${Color(colors.n100)
36 | .darken(0.8)
37 | .hex()};
38 | `;
39 |
40 | export const TotalValue = styled.Text`
41 | text-align: center;
42 | margin-top: 15px;
43 | padding: 5px 0;
44 | font-size: 16px;
45 | font-weight: 500;
46 | color: ${Color(colors.n100)
47 | .darken(0.8)
48 | .hex()};
49 | `;
50 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
13 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/pages/CabeSave/CabeItemsList/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/native';
2 | import Color from 'color';
3 | import colors from 'styles/colors';
4 |
5 | export const DescriptionContainer = styled.View`
6 | margin: 15px;
7 | padding-bottom: 15px;
8 | border-bottom-color: ${Color(colors.c200)
9 | .lighten(0.3)
10 | .hex()};
11 | border-bottom-width: 1px;
12 | `;
13 |
14 | export const DescriptionLine = styled.Text`
15 | color: ${Color(colors.n100)
16 | .darken(0.7)
17 | .hex()};
18 | font-size: 16px;
19 | margin-bottom: 5px;
20 | `;
21 |
22 | export const EmptyList = styled.Text`
23 | text-align: center;
24 | margin-top: 15px;
25 | font-size: 18px;
26 | `;
27 |
28 | export const SwipeableContainer = styled.View`
29 | height: 100%;
30 | flex-direction: row;
31 | justify-content: space-between;
32 | align-items: center;
33 | `;
34 |
35 | export const LeftSwipeableItem = styled.View`
36 | flex: 1;
37 | align-items: flex-start;
38 | background-color: green;
39 | `;
40 |
41 | export const SwipeableItemContent = styled.View`
42 | height: 100%;
43 | width: 75;
44 | /* backgroundColor: 'green', */
45 | align-items: center;
46 | justify-content: center;
47 | `;
48 |
49 | export const RightSwipeableItem = styled.View`
50 | flex: 1;
51 | align-items: flex-end;
52 | background-color: red;
53 | `;
54 |
--------------------------------------------------------------------------------
/src/components/NumericKeyboard/styles.ts:
--------------------------------------------------------------------------------
1 | import DefaultLinearGradient from 'react-native-linear-gradient';
2 | import styled from 'styled-components/native';
3 |
4 | import Color from 'color';
5 |
6 | import colors from 'styles/colors';
7 |
8 | export const Container = styled.View`
9 | background-color: ${Color(colors.c200)
10 | .lighten(0.4)
11 | .hex()};
12 | max-height: 40%;
13 | padding-bottom: 1px;
14 | `;
15 |
16 | export const Row = styled.View`
17 | flex-direction: row;
18 | margin-bottom: 1px;
19 | margin-top: 1px;
20 | height: 24.5%;
21 |
22 | &:last-child {
23 | margin-bottom: 0;
24 | }
25 | &:first-child {
26 | margin-top: 0;
27 | }
28 | `;
29 |
30 | export const Key = styled.TouchableOpacity.attrs({
31 | activeOpacity: 0.7,
32 | })`
33 | flex: 1;
34 | margin-right: 1px;
35 | margin-left: 1px;
36 |
37 | &:last-child {
38 | margin-right: 0;
39 | }
40 | &:first-child {
41 | margin-left: 0;
42 | }
43 | `;
44 |
45 | export const LinearGradient = styled(DefaultLinearGradient).attrs({
46 | colors: [colors.c300, colors.c200],
47 | start: { x: -0.5, y: 0 },
48 | end: { x: 1, y: 1 },
49 | })`
50 | flex: 1;
51 | justify-content: center;
52 | align-items: center;
53 | `;
54 |
55 | export const KeyText = styled.Text`
56 | color: ${colors.n100};
57 | font-weight: bold;
58 | font-size: 26px;
59 | `;
60 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/cabenomeubolso/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.cabenomeubolso;
2 |
3 | import android.os.Bundle;
4 |
5 | import com.facebook.react.ReactActivity;
6 | import com.facebook.react.ReactActivityDelegate;
7 | import com.facebook.react.ReactRootView;
8 | import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
9 | import com.zoontek.rnbootsplash.RNBootSplash;
10 |
11 | public class MainActivity extends ReactActivity {
12 |
13 | /**
14 | * Returns the name of the main component registered from JavaScript. This is
15 | * used to schedule rendering of the component.
16 | */
17 | @Override
18 | protected String getMainComponentName() {
19 | return "cabenomeubolso";
20 | }
21 |
22 | @Override
23 | protected ReactActivityDelegate createReactActivityDelegate() {
24 | return new ReactActivityDelegate(this, getMainComponentName()) {
25 | @Override
26 | protected ReactRootView createRootView() {
27 | return new RNGestureHandlerEnabledRootView(MainActivity.this);
28 | }
29 | };
30 | }
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | RNBootSplash.show(R.drawable.bootsplash, MainActivity.this); // <- display the "bootsplash" xml view over our
36 | // MainActivity
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cabe no Meu Bolso
2 |
3 | O Cabe no Meu bolso é um aplicativo que te ajuda a fazer compras dentro do seu orçamento.
4 |
5 | Nele, você cria listas de compras para se guiar no momento da compra, com quantidades de itens, preços e uma interface simples, amigável, fácil de utilizar e intuitiva.
6 |
7 | Utilizando o Cabe no Meu Bolso, você não tem mais surpresas na conta do caixa.
8 |
9 | ## Motivação
10 |
11 | Costumeiramente, acontecia de haver a necessidade de fazer compras baseado em uma lista de itens, e ter um orçamento fixo. Ex: Dada uma lista de compras, posso gastar R$ 400. Na hora da compra, preciso ficar fazendo as contas para garantir que o total não ultrapassasse esse valor. Além disso, precisava saber se poderia colocar mais itens ou maiores quantidades de um dado item. Assim nasceu a ideia do Cabe no Meu Bolso.
12 |
13 | ## Onde baixar?
14 |
15 | Baixe agora nosso app:
16 |
17 | [Download para Android](https://play.google.com/store/apps/details?id=com.cabenomeubolso)
18 |
19 | ## ROADMAP
20 |
21 | O que vem por aí?
22 |
23 | - [x] Adição da tela de boas-vindas/intro
24 | - [ ] Melhorias de design, como inclusão de modo dark, melhoramentos no logotipo, etc.
25 | - [ ] Criação de API backend para sincronização de listas na conta de usuário
26 | - [ ] Inserção de itens na hora da compra
27 | - [ ] Lixeira para listas apagadas
28 | - [ ] Listas de compras recorrentes
29 | - e mais...
30 |
31 | ## Política de privacidade
32 |
33 | Para ver nossa política de privacidade, [clique aqui](PRIVACY.md).
34 |
--------------------------------------------------------------------------------
/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.cabenomeubolso",
39 | )
40 |
41 | android_resource(
42 | name = "res",
43 | package = "com.cabenomeubolso",
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 |
--------------------------------------------------------------------------------
/ios/cabenomeubolso/AppDelegate.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import "AppDelegate.h"
9 |
10 | #import
11 | #import
12 | #import
13 |
14 | @implementation AppDelegate
15 |
16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
17 | {
18 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
19 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
20 | moduleName:@"cabenomeubolso"
21 | initialProperties:nil];
22 |
23 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
24 |
25 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
26 | UIViewController *rootViewController = [UIViewController new];
27 | rootViewController.view = rootView;
28 | self.window.rootViewController = rootViewController;
29 | [self.window makeKeyAndVisible];
30 | return YES;
31 | }
32 |
33 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
34 | {
35 | #if DEBUG
36 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
37 | #else
38 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
39 | #endif
40 | }
41 |
42 | @end
43 |
--------------------------------------------------------------------------------
/src/pages/CabeSave/CabeName/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Button } from 'components';
4 |
5 | import { FloatingBottomContainer } from '../components';
6 |
7 | import { DescriptionContainer, DescriptionText, Input } from './styles';
8 | import { useCabeSave } from '../CabeSaveContext';
9 |
10 | type Props = {
11 | nextStep: () => void;
12 | backStep: () => void;
13 | };
14 |
15 | export default function CabeName({ nextStep, backStep }: Props) {
16 | const {
17 | cabeValue: { name },
18 | setName,
19 | } = useCabeSave();
20 | return (
21 | <>
22 |
23 | Dê um nome a esse Cabe
24 |
25 |
26 |
35 |
36 |
37 | <>
38 | {!!name && (
39 |
46 | )}
47 |
53 | >
54 |
55 | >
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/ios/cabenomeubolso-tvOS/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 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | NSAppTransportSecurity
26 |
27 | NSExceptionDomains
28 |
29 | localhost
30 |
31 | NSExceptionAllowsInsecureHTTPLoads
32 |
33 |
34 |
35 |
36 | NSLocationWhenInUseUsageDescription
37 |
38 | UILaunchStoryboardName
39 | LaunchScreen
40 | UIRequiredDeviceCapabilities
41 |
42 | armv7
43 |
44 | UISupportedInterfaceOrientations
45 |
46 | UIInterfaceOrientationPortrait
47 | UIInterfaceOrientationLandscapeLeft
48 | UIInterfaceOrientationLandscapeRight
49 |
50 | UIViewControllerBasedStatusBarAppearance
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | env: {
4 | es6: true,
5 | jest: true,
6 | },
7 | extends: [
8 | 'airbnb',
9 | 'plugin:react-native/all',
10 | 'prettier',
11 | 'prettier/react',
12 | 'prettier/@typescript-eslint',
13 | ],
14 | globals: {
15 | Atomics: 'readonly',
16 | SharedArrayBuffer: 'readonly',
17 | __DEV__: true,
18 | },
19 | parserOptions: {
20 | ecmaFeatures: {
21 | jsx: true,
22 | },
23 | ecmaVersion: 2018,
24 | sourceType: 'module',
25 | project: './tsconfig.json',
26 | },
27 | plugins: [
28 | 'react',
29 | 'react-native',
30 | '@typescript-eslint',
31 | 'jsx-a11y',
32 | 'import',
33 | 'prettier',
34 | ],
35 | rules: {
36 | 'prettier/prettier': 'error',
37 | 'react/jsx-filename-extension': [
38 | 'error',
39 | { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
40 | ],
41 | 'import/prefer-default-export': 'off',
42 | 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
43 | 'react/jsx-one-expression-per-line': 'off',
44 | 'react-native/no-color-literals': 'off',
45 | 'react-native/sort-styles': 'off',
46 | 'global-require': 'off',
47 | 'react-native/no-raw-text': 'off',
48 | 'no-console': 'off',
49 | 'react-native/no-inline-styles': 'warn',
50 | 'no-param-reassign': 'off',
51 | 'no-unused-vars': 'off',
52 | '@typescript-eslint/no-unused-vars': [
53 | 'error',
54 | {
55 | vars: 'all',
56 | args: 'after-used',
57 | ignoreRestSiblings: false,
58 | },
59 | ],
60 | 'react/jsx-props-no-spreading': 'off',
61 | },
62 | settings: {
63 | 'import/resolver': {
64 | 'babel-module': {},
65 | },
66 | },
67 | };
68 |
--------------------------------------------------------------------------------
/src/pages/CabeProcess/CabeItems/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/native';
2 | import Color from 'color';
3 | import colors from 'styles/colors';
4 | import { DefaultTheme } from 'styled-components';
5 | import { ListItem as DefaultListItem } from 'react-native-elements';
6 |
7 | export const ListHeaderContainer = styled.View`
8 | margin-bottom: 10px;
9 | `;
10 |
11 | export const ValuesContainer = styled.View`
12 | padding: 15px 0;
13 | margin: 15px;
14 | margin-bottom: 0;
15 | flex-direction: row;
16 | border-bottom-width: 1px;
17 | border-bottom-color: ${Color(colors.c200)
18 | .lighten(0.3)
19 | .hex()};
20 | `;
21 |
22 | export const ValueItemContainer = styled.View`
23 | flex: 1;
24 | align-items: center;
25 | `;
26 |
27 | export const ValueItemTitle = styled.Text`
28 | font-size: 12px;
29 | color: ${Color(colors.n100)
30 | .darken(0.5)
31 | .hex()};
32 | `;
33 |
34 | export const ValueItemText = styled.Text<
35 | DefaultTheme & {
36 | healthy: boolean;
37 | }
38 | >`
39 | font-size: 28px;
40 | color: ${props =>
41 | props.healthy
42 | ? Color(colors.c500)
43 | .darken(0.4)
44 | .hex()
45 | : colors.c300};
46 | `;
47 |
48 | export const ValueRestText = styled.Text`
49 | margin-top: 5px;
50 | text-align: center;
51 | font-size: 12px;
52 | font-weight: 400;
53 | color: ${Color(colors.n100)
54 | .darken(0.8)
55 | .hex()};
56 | `;
57 |
58 | export const SectionHeader = styled.Text`
59 | font-size: 14px;
60 | color: ${colors.c100};
61 | font-weight: bold;
62 | text-transform: uppercase;
63 | padding-left: 15px;
64 | margin-top: 20px;
65 | `;
66 |
67 | export const ItemRow = styled.View`
68 | flex-direction: row;
69 | justify-content: space-between;
70 | `;
71 |
72 | export const ItemText = styled.Text`
73 | font-size: 18px;
74 | `;
75 |
76 | export const ListItem = styled(DefaultListItem)<
77 | DefaultTheme & {
78 | done?: boolean;
79 | }
80 | >`
81 | opacity: ${props => (props.done ? 0.4 : 1)};
82 | `;
83 |
--------------------------------------------------------------------------------
/src/components/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | HeaderProps,
4 | Header as DefaultHeader,
5 | Icon,
6 | } from 'react-native-elements';
7 | import LinearGradient from 'react-native-linear-gradient';
8 | import { TouchableOpacity } from 'react-native-gesture-handler';
9 | import { Platform } from 'react-native';
10 |
11 | import colors from 'styles/colors';
12 |
13 | import { HeaderTitleContainer } from './styles';
14 |
15 | type IconProp = { name: string; onPress?: (_?: any) => any };
16 |
17 | type Props = HeaderProps & {
18 | title?: string;
19 | leftIcon?: IconProp;
20 | rightIcon?: IconProp;
21 | };
22 |
23 | export default function Header({
24 | title,
25 | leftIcon,
26 | rightIcon,
27 | ...props
28 | }: Props) {
29 | return (
30 |
52 |
53 |
54 | )
55 | }
56 | rightComponent={
57 | rightIcon && (
58 |
62 |
63 |
64 | )
65 | }
66 | centerComponent={{title}}
67 | />
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/src/pages/FinalizedCabeView/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/native';
2 | import Color from 'color';
3 | import colors from 'styles/colors';
4 | import { DefaultTheme } from 'styled-components';
5 | import { ListItem as DefaultListItem } from 'react-native-elements';
6 |
7 | export const ListHeaderContainer = styled.View`
8 | margin-bottom: 10px;
9 | `;
10 |
11 | export const FinalizedAtText = styled.Text`
12 | margin-top: 10px;
13 | text-align: center;
14 | font-size: 14px;
15 | font-weight: 500;
16 | color: ${Color(colors.n100)
17 | .darken(0.8)
18 | .hex()};
19 | `;
20 |
21 | export const ValuesContainer = styled.View`
22 | padding: 15px 0;
23 | margin: 0 15px;
24 | flex-direction: row;
25 | border-bottom-width: 1px;
26 | border-bottom-color: ${Color(colors.c200)
27 | .lighten(0.3)
28 | .hex()};
29 | `;
30 |
31 | export const ValueItemContainer = styled.View`
32 | flex: 1;
33 | align-items: center;
34 | `;
35 |
36 | export const ValueItemTitle = styled.Text`
37 | font-size: 12px;
38 | color: ${Color(colors.n100)
39 | .darken(0.5)
40 | .hex()};
41 | `;
42 |
43 | export const ValueItemText = styled.Text<
44 | DefaultTheme & {
45 | healthy: boolean;
46 | }
47 | >`
48 | font-size: 28px;
49 | color: ${props =>
50 | props.healthy
51 | ? Color(colors.c500)
52 | .darken(0.4)
53 | .hex()
54 | : colors.c300};
55 | `;
56 |
57 | export const ValueRestText = styled.Text`
58 | margin-top: 5px;
59 | text-align: center;
60 | font-size: 12px;
61 | font-weight: 400;
62 | color: ${Color(colors.n100)
63 | .darken(0.8)
64 | .hex()};
65 | `;
66 |
67 | export const SectionHeader = styled.Text`
68 | font-size: 14px;
69 | color: ${colors.c100};
70 | font-weight: bold;
71 | text-transform: uppercase;
72 | padding-left: 15px;
73 | margin-top: 20px;
74 | `;
75 |
76 | export const ItemRow = styled.View`
77 | flex-direction: row;
78 | justify-content: space-between;
79 | `;
80 |
81 | export const ItemText = styled.Text`
82 | font-size: 18px;
83 | `;
84 |
85 | export const ListItem = styled(DefaultListItem)<
86 | DefaultTheme & {
87 | done?: boolean;
88 | }
89 | >`
90 | opacity: ${props => (props.done ? 1 : 0.4)};
91 | `;
92 |
--------------------------------------------------------------------------------
/src/store/modules/cabes/reducer.ts:
--------------------------------------------------------------------------------
1 | import produce from 'immer';
2 | import { Action } from 'redux';
3 |
4 | import { Cabe } from 'models/Cabe';
5 |
6 | import { TYPES } from './actions';
7 |
8 | export type State = {
9 | list: Cabe[];
10 | current: Cabe | null;
11 | loading: boolean;
12 | };
13 |
14 | const INITIAL_STATE: State = {
15 | list: [],
16 | current: null,
17 | loading: false,
18 | };
19 |
20 | export default function cabes(state = INITIAL_STATE, action: Action) {
21 | return produce(state, draft => {
22 | switch (action.type) {
23 | case TYPES.listCabesRequest:
24 | draft.loading = true;
25 | break;
26 | case TYPES.listCabesSuccess:
27 | draft.list = action.payload.cabes;
28 | draft.loading = false;
29 | break;
30 | case TYPES.cabesFailure:
31 | draft.loading = false;
32 | break;
33 | case TYPES.getCabeRequest:
34 | draft.loading = true;
35 | if (draft.current && draft.current.id !== action.payload.id) {
36 | draft.current = null;
37 | }
38 | break;
39 | case TYPES.getCabeSuccess:
40 | draft.loading = false;
41 | draft.current = action.payload.cabe;
42 | break;
43 | case TYPES.createCabeRequest:
44 | draft.loading = true;
45 | break;
46 | case TYPES.createCabeSuccess:
47 | draft.loading = false;
48 | draft.list.push(action.payload.cabe);
49 | break;
50 | case TYPES.updateCabeRequest:
51 | draft.loading = true;
52 | break;
53 | case TYPES.updateCabeSuccess: {
54 | draft.loading = false;
55 | const index = draft.list.findIndex(
56 | c => c.id === action.payload.cabe.id
57 | );
58 | draft.list[index] = { ...draft.list[index], ...action.payload.cabe };
59 | break;
60 | }
61 | case TYPES.removeCabeRequest:
62 | draft.loading = true;
63 | break;
64 | case TYPES.removeCabeSuccess: {
65 | draft.loading = false;
66 | const index = draft.list.findIndex(c => c.id === action.payload.id);
67 | draft.list.splice(index, 1);
68 | draft.current = null;
69 | break;
70 | }
71 | default:
72 | }
73 | });
74 | }
75 |
--------------------------------------------------------------------------------
/ios/cabenomeubolsoTests/cabenomeubolsoTests.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 | #import
10 |
11 | #import
12 | #import
13 |
14 | #define TIMEOUT_SECONDS 600
15 | #define TEXT_TO_LOOK_FOR @"Welcome to React"
16 |
17 | @interface cabenomeubolsoTests : XCTestCase
18 |
19 | @end
20 |
21 | @implementation cabenomeubolsoTests
22 |
23 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
24 | {
25 | if (test(view)) {
26 | return YES;
27 | }
28 | for (UIView *subview in [view subviews]) {
29 | if ([self findSubviewInView:subview matching:test]) {
30 | return YES;
31 | }
32 | }
33 | return NO;
34 | }
35 |
36 | - (void)testRendersWelcomeScreen
37 | {
38 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
39 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
40 | BOOL foundElement = NO;
41 |
42 | __block NSString *redboxError = nil;
43 | #ifdef DEBUG
44 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
45 | if (level >= RCTLogLevelError) {
46 | redboxError = message;
47 | }
48 | });
49 | #endif
50 |
51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
54 |
55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
57 | return YES;
58 | }
59 | return NO;
60 | }];
61 | }
62 |
63 | #ifdef DEBUG
64 | RCTSetLogFunction(RCTDefaultLogFunction);
65 | #endif
66 |
67 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
68 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
69 | }
70 |
71 |
72 | @end
73 |
--------------------------------------------------------------------------------
/src/store/modules/cabes/actions.ts:
--------------------------------------------------------------------------------
1 | import { Cabe, CanonCabe } from 'models/Cabe';
2 |
3 | const PREFIX = '@cabes';
4 |
5 | export const TYPES = {
6 | listCabesRequest: `${PREFIX}/LIST_CABES_REQUEST`,
7 | listCabesSuccess: `${PREFIX}/LIST_CABES_SUCCESS`,
8 |
9 | getCabeRequest: `${PREFIX}/GET_CABE_REQUEST`,
10 | getCabeSuccess: `${PREFIX}/GET_CABE_SUCCESS`,
11 |
12 | createCabeRequest: `${PREFIX}/CREATE_CABE_REQUEST`,
13 | createCabeSuccess: `${PREFIX}/CREATE_CABE_SUCCESS`,
14 |
15 | updateCabeRequest: `${PREFIX}/UPDATE_CABE_REQUEST`,
16 | updateCabeSuccess: `${PREFIX}/UPDATE_CABE_SUCCESS`,
17 |
18 | removeCabeRequest: `${PREFIX}/REMOVE_CABE_REQUEST`,
19 | removeCabeSuccess: `${PREFIX}/REMOVE_CABE_SUCCESS`,
20 |
21 | cabesFailure: `${PREFIX}/CABES_FAILURE`,
22 | };
23 |
24 | export function listCabesRequest() {
25 | return {
26 | type: TYPES.listCabesRequest,
27 | };
28 | }
29 |
30 | export function listCabesSuccess(cabes: Cabe[]) {
31 | return {
32 | type: TYPES.listCabesSuccess,
33 | payload: { cabes },
34 | };
35 | }
36 |
37 | export function cabesFailure() {
38 | return {
39 | type: TYPES.cabesFailure,
40 | };
41 | }
42 |
43 | export function getCabeRequest(id: string) {
44 | return {
45 | type: TYPES.getCabeRequest,
46 | payload: { id },
47 | };
48 | }
49 |
50 | export function getCabeSuccess(cabe: Cabe) {
51 | return {
52 | type: TYPES.getCabeSuccess,
53 | payload: { cabe },
54 | };
55 | }
56 |
57 | export function createCabeRequest(cabe: CanonCabe, onCreateSuccess?: Function) {
58 | return {
59 | type: TYPES.createCabeRequest,
60 | payload: { cabe, onCreateSuccess },
61 | };
62 | }
63 |
64 | export function createCabeSuccess(cabe: Cabe) {
65 | return {
66 | type: TYPES.createCabeSuccess,
67 | payload: { cabe },
68 | };
69 | }
70 |
71 | export function updateCabeRequest(cabe: CanonCabe) {
72 | return {
73 | type: TYPES.updateCabeRequest,
74 | payload: { cabe },
75 | };
76 | }
77 |
78 | export function updateCabeSuccess(cabe: Cabe) {
79 | return {
80 | type: TYPES.updateCabeSuccess,
81 | payload: { cabe },
82 | };
83 | }
84 |
85 | export function removeCabeRequest(id: string) {
86 | return {
87 | type: TYPES.removeCabeRequest,
88 | payload: { id },
89 | };
90 | }
91 |
92 | export function removeCabeSuccess(id: string) {
93 | return {
94 | type: TYPES.removeCabeSuccess,
95 | payload: { id },
96 | };
97 | }
98 |
--------------------------------------------------------------------------------
/src/pages/CabeSave/CabeValue/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TextInputMask, MaskService } from 'react-native-masked-text';
3 |
4 | import { Button } from 'components';
5 |
6 | import { FloatingBottomContainer } from '../components';
7 |
8 | import { DescriptionContainer, DescriptionText, Input } from './styles';
9 | import { useCabeSave } from '../CabeSaveContext';
10 |
11 | type Props = {
12 | nextStep: () => void;
13 | backStep: () => void;
14 | };
15 |
16 | export default function CabeValue({ nextStep, backStep }: Props) {
17 | const {
18 | cabeValue: { value },
19 | setValue,
20 | } = useCabeSave();
21 | const handleSetValue = (valueToChange?: string) => {
22 | let parsedValue = Number.parseFloat(valueToChange || '');
23 |
24 | if (!parsedValue) {
25 | parsedValue = 0;
26 | }
27 |
28 | setValue(parsedValue);
29 | };
30 |
31 | return (
32 | <>
33 |
34 |
35 | Quanto você pode gastar nesse Cabe?
36 |
37 |
38 |
39 | Esse é o valor alvo, que você não poderá ultrapassar enquanto estiver
40 | fazendo o Cabe.
41 |
42 |
43 |
44 | handleSetValue(rawValue)}
58 | />
59 |
60 |
61 | <>
62 | {!!value && (
63 |
70 | )}
71 |
77 | >
78 |
79 | >
80 | );
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/Button/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo } from 'react';
2 | import { Button as DefaultButton, ButtonProps } from 'react-native-elements';
3 | import LinearGradient from 'react-native-linear-gradient';
4 |
5 | import colors from 'styles/colors';
6 |
7 | const gradients = {
8 | primary: {
9 | colors: [colors.c200, colors.c300],
10 | start: { x: 0, y: 0 },
11 | end: { x: 1, y: 0 },
12 | },
13 | secondary: {
14 | colors: [colors.c100, colors.c500],
15 | start: { x: 0, y: 0 },
16 | end: { x: 1, y: 0 },
17 | },
18 | tertiary: {
19 | colors: [colors.c300, colors.c400],
20 | start: { x: 0, y: 0 },
21 | end: { x: 1, y: 0 },
22 | },
23 | quaternary: {
24 | colors: [colors.c200, colors.c100],
25 | start: { x: 0, y: 0 },
26 | end: { x: 1, y: 0 },
27 | },
28 | };
29 |
30 | type Props = ButtonProps & {
31 | color?: keyof typeof colors;
32 | iconButton?: boolean;
33 | iconButtonSize?: number;
34 | gradient?: 'primary' | 'secondary' | 'tertiary' | 'quaternary';
35 | };
36 |
37 | function Button({
38 | color,
39 | iconButton,
40 | buttonStyle,
41 | titleStyle,
42 | icon,
43 | iconButtonSize,
44 | gradient,
45 | disabledStyle,
46 | disabledTitleStyle,
47 | ...props
48 | }: Props) {
49 | const style = color ? { backgroundColor: colors[color] } : undefined;
50 | const textStyle = color
51 | ? {
52 | color: '#fff',
53 | }
54 | : undefined;
55 |
56 | const getGradientProps = () => gradient && gradients[gradient];
57 |
58 | return (
59 |
87 | );
88 | }
89 |
90 | export default memo(Button);
91 |
--------------------------------------------------------------------------------
/ios/cabenomeubolso/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | cabenomeubolso
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSAllowsArbitraryLoads
30 |
31 | NSExceptionDomains
32 |
33 | localhost
34 |
35 | NSExceptionAllowsInsecureHTTPLoads
36 |
37 |
38 |
39 |
40 | NSLocationWhenInUseUsageDescription
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIRequiredDeviceCapabilities
45 |
46 | armv7
47 |
48 | UISupportedInterfaceOrientations
49 |
50 | UIInterfaceOrientationPortrait
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 | LSApplicationCategoryType
55 |
56 | UIAppFonts
57 |
58 | AntDesign.ttf
59 | Entypo.ttf
60 | EvilIcons.ttf
61 | Feather.ttf
62 | FontAwesome.ttf
63 | FontAwesome5_Brands.ttf
64 | FontAwesome5_Regular.ttf
65 | FontAwesome5_Solid.ttf
66 | Foundation.ttf
67 | Ionicons.ttf
68 | MaterialIcons.ttf
69 | MaterialCommunityIcons.ttf
70 | SimpleLineIcons.ttf
71 | Octicons.ttf
72 | Zocial.ttf
73 |
74 | UIViewControllerBasedStatusBarAppearance
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/pages/CabeSave/CabeSaveContext.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | createContext,
3 | useState,
4 | useCallback,
5 | useContext,
6 | useMemo,
7 | } from 'react';
8 |
9 | import { CabeItem } from 'models/CabeItem';
10 |
11 | type CabeSaveContextType = {
12 | cabeValue: {
13 | name: string;
14 | value: number;
15 | items: CabeItem[];
16 | };
17 | addItem: (item: CabeItem) => void;
18 | editItem: (i: number, item: CabeItem) => void;
19 | removeItem: (i: number) => void;
20 | setName: (newName: string) => void;
21 | setValue: (newValue: number) => void;
22 | setItems: (newItems: CabeItem[]) => void;
23 | };
24 |
25 | const CabeSaveContext = createContext(
26 | {} as CabeSaveContextType
27 | );
28 |
29 | type Props = {
30 | children: React.ReactElement;
31 | };
32 |
33 | const CabeSaveProvider: React.FC = ({ children }: Props) => {
34 | const [name, setName] = useState('');
35 | const [value, setValue] = useState(0);
36 | const [items, setItems] = useState([]);
37 |
38 | const addItem = useCallback(
39 | (item: CabeItem) => {
40 | setItems([...items, item]);
41 | },
42 | [items]
43 | );
44 |
45 | const editItem = useCallback(
46 | (i: number, item: CabeItem) => {
47 | if (i >= 0) {
48 | const itemsCopy = [...items];
49 | itemsCopy[i] = item;
50 | setItems(itemsCopy);
51 | }
52 | },
53 | [items]
54 | );
55 |
56 | const removeItem = useCallback(
57 | (i: number) => {
58 | if (i >= 0) {
59 | const itemsCopy = [...items];
60 | itemsCopy.splice(i, 1);
61 | setItems(itemsCopy);
62 | }
63 | },
64 | [items]
65 | );
66 |
67 | const contextValue = useMemo(
68 | () => ({
69 | cabeValue: {
70 | name,
71 | value,
72 | items,
73 | },
74 | addItem,
75 | removeItem,
76 | editItem,
77 | setName,
78 | setValue,
79 | setItems,
80 | }),
81 | [name, value, items, addItem, removeItem, editItem]
82 | );
83 |
84 | return (
85 |
86 | {children}
87 |
88 | );
89 | };
90 |
91 | const useCabeSave = (): CabeSaveContextType => {
92 | const context = useContext(CabeSaveContext);
93 |
94 | if (!context) {
95 | throw new Error('useCabeSave must be used within a CabeSaveProvider');
96 | }
97 |
98 | return context;
99 | };
100 |
101 | export { CabeSaveProvider, useCabeSave };
102 |
--------------------------------------------------------------------------------
/src/store/modules/cabes/sagas.ts:
--------------------------------------------------------------------------------
1 | import { all, takeLatest, call, put } from 'redux-saga/effects';
2 | import { Action } from 'redux';
3 |
4 | import NavigationService from 'services/NavigationService';
5 | import RealmAPI from 'services/bd';
6 |
7 | import {
8 | TYPES,
9 | listCabesSuccess,
10 | cabesFailure,
11 | getCabeSuccess,
12 | createCabeSuccess,
13 | updateCabeSuccess,
14 | removeCabeSuccess,
15 | } from './actions';
16 |
17 | export function* listCabes() {
18 | try {
19 | const response = yield call(RealmAPI.getAllCabes);
20 |
21 | // console.tron.log('response', response);
22 |
23 | yield put(listCabesSuccess(response));
24 | } catch (error) {
25 | // console.tron.log(error.message);
26 | yield put(cabesFailure());
27 | }
28 | }
29 |
30 | export function* getCabe({ payload }: Action) {
31 | try {
32 | const { id } = payload;
33 |
34 | const response = yield call(RealmAPI.getCabeById, id);
35 |
36 | yield put(getCabeSuccess(response));
37 | } catch (error) {
38 | // console.tron.log('error', error.message);
39 | yield put(cabesFailure());
40 | }
41 | }
42 |
43 | export function* updateCabe({ payload }: Action) {
44 | try {
45 | const { cabe } = payload;
46 |
47 | const response = yield call(RealmAPI.updateCabe, cabe);
48 |
49 | yield put(updateCabeSuccess(response));
50 | NavigationService.navigate('Main');
51 | } catch (error) {
52 | // console.tron.log('error', error.message);
53 | yield put(cabesFailure());
54 | }
55 | }
56 |
57 | export function* createCabe({ payload }: Action) {
58 | try {
59 | const { cabe } = payload;
60 |
61 | const response = yield call(RealmAPI.createCabe, cabe);
62 |
63 | yield put(createCabeSuccess(response));
64 | if (payload && payload.onCreateSuccess) {
65 | payload.onCreateSuccess(response);
66 | } else {
67 | NavigationService.navigate('Main');
68 | }
69 | } catch (error) {
70 | yield put(cabesFailure());
71 | }
72 | }
73 |
74 | export function* removeCabe({ payload }: Action) {
75 | try {
76 | const { id } = payload;
77 |
78 | yield call(RealmAPI.deleteCabe, id);
79 |
80 | yield put(removeCabeSuccess(id));
81 | } catch (error) {
82 | // console.tron.log('error', error, error.message);
83 | yield put(cabesFailure());
84 | }
85 | }
86 |
87 | export default all([
88 | takeLatest(TYPES.listCabesRequest, listCabes),
89 | takeLatest(TYPES.getCabeRequest, getCabe),
90 | takeLatest(TYPES.createCabeRequest, createCabe),
91 | takeLatest(TYPES.updateCabeRequest, updateCabe),
92 | takeLatest(TYPES.removeCabeRequest, removeCabe),
93 | ]);
94 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/cabenomeubolso/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.cabenomeubolso;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import com.facebook.react.PackageList;
6 | import com.facebook.react.ReactApplication;
7 | import com.facebook.react.ReactNativeHost;
8 | import com.facebook.react.ReactPackage;
9 | import com.facebook.soloader.SoLoader;
10 | import java.lang.reflect.InvocationTargetException;
11 | import java.util.List;
12 | import com.microsoft.codepush.react.CodePush;
13 |
14 | public class MainApplication extends Application implements ReactApplication {
15 |
16 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
17 | @Override
18 | public boolean getUseDeveloperSupport() {
19 | return BuildConfig.DEBUG;
20 | }
21 |
22 | @Override
23 | protected List getPackages() {
24 | @SuppressWarnings("UnnecessaryLocalVariable")
25 | List packages = new PackageList(this).getPackages();
26 | // Packages that cannot be autolinked yet can be added manually here, for
27 | // example:
28 | // packages.add(new MyReactNativePackage());
29 | return packages;
30 | }
31 |
32 | @Override
33 | protected String getJSMainModuleName() {
34 | return "index";
35 | }
36 |
37 | @Override
38 | protected String getJSBundleFile() {
39 | return CodePush.getJSBundleFile();
40 | }
41 | };
42 |
43 | @Override
44 | public ReactNativeHost getReactNativeHost() {
45 | return mReactNativeHost;
46 | }
47 |
48 | @Override
49 | public void onCreate() {
50 | super.onCreate();
51 | SoLoader.init(this, /* native exopackage */ false);
52 | initializeFlipper(this); // Remove this line if you don't want Flipper enabled
53 | }
54 |
55 | /**
56 | * Loads Flipper in React Native templates.
57 | *
58 | * @param context
59 | */
60 | private static void initializeFlipper(Context context) {
61 | if (BuildConfig.DEBUG) {
62 | try {
63 | /*
64 | * We use reflection here to pick up the class that initializes Flipper, since
65 | * Flipper library is not available in release mode
66 | */
67 | Class> aClass = Class.forName("com.facebook.flipper.ReactNativeFlipper");
68 | aClass.getMethod("initializeFlipper", Context.class).invoke(null, context);
69 | } catch (ClassNotFoundException e) {
70 | e.printStackTrace();
71 | } catch (NoSuchMethodException e) {
72 | e.printStackTrace();
73 | } catch (IllegalAccessException e) {
74 | e.printStackTrace();
75 | } catch (InvocationTargetException e) {
76 | e.printStackTrace();
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/pages/Intro/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AppIntroSlider from 'react-native-app-intro-slider';
3 | import { useDispatch } from 'react-redux';
4 | import { markIntroAsViewed } from 'store/modules/meta/actions';
5 | import colors from 'styles/colors';
6 |
7 | import { Icon } from 'react-native-elements';
8 | import { StatusBar } from 'react-native';
9 | import {
10 | SlideContainer,
11 | SlideContent,
12 | SlideTitle,
13 | SlideSubtitle,
14 | SlideDescription,
15 | } from './styles';
16 |
17 | const slides = [
18 | {
19 | key: 'buylist',
20 | title: 'Listas de Compras',
21 | subtitle: 'Faça e organize suas listas de compras',
22 | text:
23 | 'Desde feira de casa a uma lista de roupas, tenha tudo em mãos facilmente',
24 | icon: 'format-list-bulleted',
25 | colors: [colors.c200, colors.c100],
26 | },
27 | {
28 | key: 'buytotal',
29 | title: 'Quanto está gastando',
30 | subtitle: 'Acompanhe o quanto está gastando no momento da compra',
31 | text:
32 | 'Insira os valores ao marcar itens como concluídos e tenha o total de gastos instantaneamente',
33 | icon: 'monetization-on',
34 | colors: [colors.c400, colors.c300],
35 | },
36 | {
37 | key: 'buysurprise',
38 | title: 'Sem surpresas',
39 | subtitle: 'Não tenha surpresas na hora do caixa',
40 | text:
41 | 'Com o total sendo exibido no app, você sabe o que esperar na hora do caixa, e ainda pode escolher adicionar ou remover alguns itens',
42 | icon: 'local-atm',
43 | colors: [colors.c500, colors.c400],
44 | },
45 | ];
46 |
47 | export default function Intro() {
48 | const dispatch = useDispatch();
49 | const onDone = () => {
50 | dispatch(markIntroAsViewed());
51 | };
52 |
53 | return (
54 | <>
55 |
60 | (
62 |
67 |
68 |
69 | {item.title}
70 | {item.subtitle}
71 | {item.text}
72 |
73 |
74 | )}
75 | slides={slides}
76 | onDone={onDone}
77 | nextLabel="Próximo"
78 | prevLabel="Anterior"
79 | doneLabel="Concluir"
80 | skipLabel="Pular"
81 | showPrevButton
82 | showSkipButton
83 | />
84 | >
85 | );
86 | }
87 |
--------------------------------------------------------------------------------
/src/pages/CabeProcess/CabeItemQuantity/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import {
3 | View,
4 | SafeAreaView,
5 | KeyboardAvoidingView,
6 | Platform,
7 | Text,
8 | } from 'react-native';
9 |
10 | import { Button } from 'components';
11 |
12 | import { ScrollView } from 'react-native-gesture-handler';
13 | import { DescriptionContainer, DescriptionText, Input } from './styles';
14 |
15 | type Props = {
16 | initialQuantity: number;
17 | itemName: string;
18 | nextStep: (quantity: number) => void;
19 | previousStep: () => void;
20 | };
21 |
22 | export default function CabeItemQuantity({
23 | nextStep,
24 | previousStep,
25 | initialQuantity,
26 | itemName,
27 | }: Props) {
28 | const [quantity, setQuantity] = useState(initialQuantity);
29 |
30 | return (
31 |
41 |
46 |
47 |
48 |
49 |
50 | Caso a quantidade do item{' '}
51 | {itemName} seja
52 | diferente do que você informou inicialmente, coloque o novo
53 | valor aqui
54 |
55 |
56 | {
63 | const qtt = Number.parseInt(value, 10);
64 | if (!Number.isNaN(qtt)) {
65 | setQuantity(qtt);
66 | } else {
67 | setQuantity(0);
68 | }
69 | }}
70 | />
71 |
72 |
73 |
79 |
86 |
87 |
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/src/components/NumericKeyboard/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Icon } from 'react-native-elements';
4 |
5 | import colors from 'styles/colors';
6 |
7 | import { Container, Row, Key, KeyText, LinearGradient } from './styles';
8 |
9 | type Props = {
10 | value?: string;
11 | onChangeText: (value: string) => any;
12 | };
13 |
14 | export default function NumericKeyboard({ value, onChangeText }: Props) {
15 | const onKeyClick = (key?: string) => {
16 | if (value) {
17 | const newValue = key ? `${value}${key}` : value.slice(0, -1);
18 | onChangeText(newValue);
19 | } else if (key) {
20 | onChangeText(key);
21 | }
22 | };
23 |
24 | return (
25 |
26 |
27 | onKeyClick('7')}>
28 |
29 | 7
30 |
31 |
32 | onKeyClick('8')}>
33 |
34 | 8
35 |
36 |
37 | onKeyClick('9')}>
38 |
39 | 9
40 |
41 |
42 |
43 |
44 | onKeyClick('4')}>
45 |
46 | 4
47 |
48 |
49 | onKeyClick('5')}>
50 |
51 | 5
52 |
53 |
54 | onKeyClick('6')}>
55 |
56 | 6
57 |
58 |
59 |
60 |
61 | onKeyClick('1')}>
62 |
63 | 1
64 |
65 |
66 | onKeyClick('2')}>
67 |
68 | 2
69 |
70 |
71 | onKeyClick('3')}>
72 |
73 | 3
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | onKeyClick('0')}>
84 |
85 | 0
86 |
87 |
88 | onKeyClick()}>
89 |
90 |
91 |
92 |
93 |
94 |
95 | );
96 | }
97 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '9.0'
2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
3 |
4 | target 'cabenomeubolso' do
5 | # Pods for cabenomeubolso
6 | pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
7 | pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
8 | pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
9 | pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
10 | pod 'React', :path => '../node_modules/react-native/'
11 | pod 'React-Core', :path => '../node_modules/react-native/'
12 | pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
13 | pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
14 | pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
15 | pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
16 | pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
17 | pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
18 | pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
19 | pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
20 | pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
21 | pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
22 | pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
23 | pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'
24 |
25 | pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
26 | pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
27 | pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
28 | pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
29 | pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon"
30 | pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
31 | pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
32 |
33 | pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
34 | pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
35 | pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
36 |
37 | target 'cabenomeubolsoTests' do
38 | inherit! :search_paths
39 | # Pods for testing
40 | end
41 |
42 | use_native_modules!
43 | end
44 |
45 | target 'cabenomeubolso-tvOS' do
46 | # Pods for cabenomeubolso-tvOS
47 |
48 | target 'cabenomeubolso-tvOSTests' do
49 | inherit! :search_paths
50 | # Pods for testing
51 | end
52 |
53 | end
54 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem http://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/src/pages/CabeProcess/CabeItems/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { SectionList } from 'react-native';
3 | import { MaskService } from 'react-native-masked-text';
4 |
5 | import { CabeItem } from 'models/CabeItem';
6 |
7 | import {
8 | ValueItemContainer,
9 | ValueItemText,
10 | ValueItemTitle,
11 | ValuesContainer,
12 | SectionHeader,
13 | ItemRow,
14 | ItemText,
15 | ListItem,
16 | ValueRestText,
17 | ListHeaderContainer,
18 | } from './styles';
19 |
20 | type Props = {
21 | items: CabeItem[];
22 | maxValue: number;
23 | currentValue: number;
24 | onClickItem: (item: CabeItem) => void;
25 | };
26 |
27 | export default function CabeItems({
28 | onClickItem,
29 | items,
30 | maxValue,
31 | currentValue,
32 | }: Props) {
33 | const makeSections = () => [
34 | {
35 | title: 'Não finalizados',
36 | data: items.filter(i => i.done === false),
37 | done: false,
38 | },
39 | {
40 | title: 'Finalizados',
41 | data: items.filter(i => i.done === true),
42 | done: true,
43 | },
44 | ];
45 |
46 | const getRestText = () => {
47 | const diff = maxValue - currentValue;
48 | if (diff > 0) {
49 | return `Restam: ${MaskService.toMask('money', diff.toFixed(2))}`;
50 | }
51 |
52 | if (diff === 0) {
53 | return 'Você gastou exatamente o valor previsto';
54 | }
55 |
56 | return `Você gastou ${MaskService.toMask(
57 | 'money',
58 | diff.toFixed(2)
59 | )} a mais que o previsto`;
60 | };
61 |
62 | return (
63 | <>
64 |
68 |
69 |
70 | Pode Gastar
71 |
72 | {MaskService.toMask('money', maxValue.toFixed(2))}
73 |
74 |
75 |
76 | Já gastou
77 |
78 | {MaskService.toMask('money', currentValue.toFixed(2))}
79 |
80 |
81 |
82 | {getRestText()}
83 |
84 | }
85 | data={items}
86 | keyExtractor={(item: any) => item.id}
87 | sections={makeSections()}
88 | renderSectionHeader={({ section: { title, data } }) =>
89 | data && data.length ? {title} : null
90 | }
91 | renderItem={({ item, section: { done } }) => (
92 | onClickItem(item)}
94 | done={done}
95 | title={
96 |
97 |
98 | {item.quantity}x {item.name}
99 |
100 | {done && (
101 |
102 | {MaskService.toMask('money', item.value.toFixed(2))}
103 |
104 | )}
105 |
106 | }
107 | />
108 | )}
109 | />
110 | >
111 | );
112 | }
113 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cabenomeubolso",
3 | "version": "1.2.0",
4 | "private": true,
5 | "scripts": {
6 | "android": "react-native run-android",
7 | "ios": "react-native run-ios",
8 | "start": "react-native start",
9 | "test": "jest",
10 | "tsc": "tsc"
11 | },
12 | "dependencies": {
13 | "@react-native-community/async-storage": "^1.7.1",
14 | "color": "^3.1.2",
15 | "date-fns": "^2.8.1",
16 | "eslint-import-resolver-babel-module": "^5.1.0",
17 | "immer": "^5.0.0",
18 | "react": "16.9.0",
19 | "react-native": "0.61.4",
20 | "react-native-app-intro-slider": "^3.0.0",
21 | "react-native-bootsplash": "^1.0.3",
22 | "react-native-code-push": "^6.0.0",
23 | "react-native-collapsible": "^1.5.1",
24 | "react-native-elements": "^1.2.7",
25 | "react-native-gesture-handler": "^1.5.2",
26 | "react-native-get-random-values": "^1.4.0",
27 | "react-native-linear-gradient": "^2.5.6",
28 | "react-native-masked-text": "^1.13.0",
29 | "react-native-reanimated": "^1.4.0",
30 | "react-native-screens": "^1.0.0-alpha.23",
31 | "react-native-shimmer": "^0.6.0",
32 | "react-native-swipe-list-view": "^2.0.6",
33 | "react-native-vector-icons": "^6.6.0",
34 | "react-navigation": "^4.0.10",
35 | "react-navigation-backhandler": "^1.3.2",
36 | "react-navigation-hooks": "^1.1.0",
37 | "react-navigation-stack": "^1.10.3",
38 | "react-redux": "^7.1.3",
39 | "reactotron-react-native": "^4.0.2",
40 | "reactotron-redux": "^3.1.2",
41 | "reactotron-redux-saga": "^4.2.3",
42 | "realm": "^3.5.0",
43 | "redux": "^4.0.4",
44 | "redux-persist": "^6.0.0",
45 | "redux-saga": "^1.1.3",
46 | "styled-components": "^4.4.1",
47 | "uuid": "^8.1.0"
48 | },
49 | "devDependencies": {
50 | "@babel/core": "^7.6.2",
51 | "@babel/preset-typescript": "^7.7.4",
52 | "@babel/runtime": "^7.6.2",
53 | "@react-native-community/eslint-config": "^0.0.5",
54 | "@types/color": "^3.0.0",
55 | "@types/jest": "^24.0.18",
56 | "@types/react": "^16.9.14",
57 | "@types/react-native": "^0.60.22",
58 | "@types/react-native-app-intro-slider": "^3.0.0",
59 | "@types/react-redux": "^7.1.5",
60 | "@types/react-test-renderer": "16.9.0",
61 | "@types/styled-components": "^4.4.0",
62 | "@types/uuid": "^8.0.0",
63 | "@typescript-eslint/eslint-plugin": "^2.10.0",
64 | "@typescript-eslint/parser": "^2.10.0",
65 | "babel-eslint": "^10.0.3",
66 | "babel-jest": "^24.9.0",
67 | "babel-plugin-ignite-ignore-reactotron": "^0.3.0",
68 | "babel-plugin-module-resolver": "^3.2.0",
69 | "babel-plugin-transform-remove-console": "^6.9.4",
70 | "eslint": "^6.7.2",
71 | "eslint-config-airbnb": "^18.0.1",
72 | "eslint-config-prettier": "^6.7.0",
73 | "eslint-plugin-import": "^2.18.2",
74 | "eslint-plugin-jsx-a11y": "^6.2.3",
75 | "eslint-plugin-prettier": "^3.1.1",
76 | "eslint-plugin-react": "^7.17.0",
77 | "eslint-plugin-react-native": "^3.8.1",
78 | "husky": "^3.1.0",
79 | "jest": "^24.9.0",
80 | "lint-staged": "^9.5.0",
81 | "metro-react-native-babel-preset": "^0.56.0",
82 | "prettier": "^1.19.1",
83 | "react-test-renderer": "16.9.0",
84 | "typescript": "^3.6.3"
85 | },
86 | "jest": {
87 | "preset": "react-native",
88 | "moduleFileExtensions": [
89 | "ts",
90 | "tsx",
91 | "js",
92 | "jsx",
93 | "json",
94 | "node"
95 | ]
96 | },
97 | "husky": {
98 | "hooks": {
99 | "pre-commit": "lint-staged && tsc"
100 | }
101 | },
102 | "lint-staged": {
103 | "src/**/*.ts{x}": [
104 | "prettier --write",
105 | "eslint --fix",
106 | "git add"
107 | ]
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/services/bd/index.ts:
--------------------------------------------------------------------------------
1 | import { CabeItem } from 'models/CabeItem';
2 | import { Cabe } from 'models/Cabe';
3 | import Realm from 'realm';
4 |
5 | import CabeItemSchema from './schemas/CabeItemSchema';
6 | import CabeSchema from './schemas/CabeSchema';
7 |
8 | class RealmAPI {
9 | realmInstance: Realm;
10 |
11 | constructor() {
12 | this.realmInstance = new Realm({
13 | schema: [CabeItemSchema, CabeSchema],
14 | schemaVersion: 2,
15 | migration: (oldRealm, newRealm) => {
16 | if (oldRealm.schemaVersion < 2) {
17 | const oldCabeItems = oldRealm.objects(
18 | 'CabeItem'
19 | );
20 | const newCabeItems = newRealm.objects('CabeItem');
21 |
22 | for (let i = 0; i < oldCabeItems.length; i += 1) {
23 | newCabeItems[i].id = String(oldCabeItems[i].id);
24 | }
25 |
26 | const oldCabes = oldRealm.objects('Cabe');
27 | const newCabes = newRealm.objects('Cabe');
28 |
29 | for (let i = 0; i < oldCabes.length; i += 1) {
30 | newCabes[i].id = String(oldCabes[i].id);
31 | }
32 | }
33 | },
34 | });
35 | // console.tron.log('realm', this.realmInstance, this.realmInstance.path);
36 | }
37 |
38 | getAllCabes = () => {
39 | const list = this.realmInstance.objects('Cabe');
40 | return [
41 | ...list.map(
42 | ({
43 | id,
44 | name,
45 | items,
46 | value,
47 | createdAt,
48 | finalized,
49 | finalizedAt,
50 | }: Cabe) => ({
51 | id,
52 | name,
53 | items,
54 | value,
55 | createdAt,
56 | finalized,
57 | finalizedAt,
58 | })
59 | ),
60 | ];
61 | };
62 |
63 | getCabeById = (idToSearch: string): Cabe => {
64 | const {
65 | id,
66 | name,
67 | items,
68 | value,
69 | createdAt,
70 | finalized,
71 | finalizedAt,
72 | } = this.realmInstance
73 | .objects('Cabe')
74 | .filtered(`id == "${idToSearch}"`)[0];
75 | return { id, name, items, value, createdAt, finalized, finalizedAt };
76 | };
77 |
78 | createCabe = (c: Cabe) => {
79 | let returnCabe;
80 | this.realmInstance.write(() => {
81 | const {
82 | id,
83 | name,
84 | items,
85 | value,
86 | createdAt,
87 | finalized,
88 | finalizedAt,
89 | } = this.realmInstance.create('Cabe', c);
90 | returnCabe = {
91 | id,
92 | name,
93 | items,
94 | value,
95 | createdAt,
96 | finalized,
97 | finalizedAt,
98 | };
99 | });
100 | return returnCabe;
101 | };
102 |
103 | updateCabe = (c: Cabe) => {
104 | let returnCabe;
105 | this.realmInstance.write(() => {
106 | const {
107 | id,
108 | name,
109 | items,
110 | value,
111 | createdAt,
112 | finalized,
113 | finalizedAt,
114 | } = this.realmInstance.create('Cabe', c, Realm.UpdateMode.Modified);
115 | returnCabe = {
116 | id,
117 | name,
118 | items,
119 | value,
120 | createdAt,
121 | finalized,
122 | finalizedAt,
123 | };
124 | });
125 | return returnCabe;
126 | };
127 |
128 | deleteCabe = (id: string) => {
129 | const c = this.realmInstance
130 | .objects('Cabe')
131 | .filtered(`id == "${id}"`)[0];
132 | this.realmInstance.write(() => {
133 | this.realmInstance.delete(c);
134 | });
135 | };
136 | }
137 |
138 | export default new RealmAPI();
139 |
--------------------------------------------------------------------------------
/ios/cabenomeubolso/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/pages/CabesList/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components/native';
2 | import { SwipeListView } from 'react-native-swipe-list-view';
3 | import LinearGradient from 'react-native-linear-gradient';
4 | import Color from 'color';
5 |
6 | import colors from 'styles/colors';
7 |
8 | export const FloatingButtonContainer = styled.SafeAreaView`
9 | position: absolute;
10 | z-index: 98;
11 | bottom: 0;
12 | width: 100%;
13 | justify-content: flex-end;
14 | align-items: center;
15 | padding-right: 10px;
16 | padding-left: 10px;
17 | padding-bottom: 20px;
18 | `;
19 |
20 | export const List = styled(SwipeListView).attrs(() => ({
21 | contentContainerStyle: { padding: 10, paddingBottom: 80 },
22 | }))``;
23 |
24 | export const ListItemTitleContainer = styled.View`
25 | flex-direction: row;
26 | justify-content: space-between;
27 | align-items: center;
28 | `;
29 |
30 | export const SubTitleContainer = styled.Text`
31 | text-align: right;
32 | `;
33 |
34 | export const SwipeableContainer = styled.View`
35 | height: 100%;
36 | flex-direction: row;
37 | justify-content: space-between;
38 | align-items: center;
39 | `;
40 |
41 | export const SwipeableItemContent = styled.TouchableOpacity`
42 | height: 100%;
43 | width: 75;
44 | align-items: center;
45 | justify-content: center;
46 | `;
47 |
48 | export const RightSwipeableItem = styled(LinearGradient).attrs({
49 | colors: [
50 | colors.c500,
51 | Color(colors.c500)
52 | .darken(0.2)
53 | .hex(),
54 | colors.c300,
55 | ],
56 | start: { x: 0, y: 0 },
57 | end: { x: 1, y: 0 },
58 | locations: [0.1, 0.7, 1],
59 | })`
60 | flex: 1;
61 | height: 100%;
62 | justify-content: flex-end;
63 | flex-direction: row;
64 | `;
65 |
66 | export const EmptyListText = styled.Text`
67 | font-size: 18px;
68 | text-align: center;
69 | font-weight: 500;
70 | margin-top: auto;
71 | margin-bottom: auto;
72 | padding: 20px 0;
73 | `;
74 |
75 | export const FinalizedHeader = styled(LinearGradient).attrs({
76 | colors: [
77 | Color(colors.c300)
78 | .lighten(0.5)
79 | .hex(),
80 | Color(colors.c400)
81 | .lighten(0.5)
82 | .hex(),
83 | ],
84 | start: { x: 0, y: 0 },
85 | end: { x: 1, y: 0 },
86 | })<{
87 | show: boolean;
88 | }>`
89 | margin-top: 20px;
90 | margin-bottom: -1px;
91 | padding: 5px;
92 | border-top-left-radius: 5px;
93 | border-top-right-radius: 5px;
94 | border-bottom-left-radius: ${props => (props.show ? 0 : '5px')};
95 | border-bottom-right-radius: ${props => (props.show ? 0 : '5px')};
96 | opacity: ${props => (props.show ? 1 : 0.6)};
97 | `;
98 |
99 | export const FinalizedHeaderText = styled.Text<{
100 | show: boolean;
101 | }>`
102 | text-align: center;
103 | color: ${colors.n100};
104 | font-size: 16px;
105 | font-weight: ${props => (props.show ? 'bold' : '500')};
106 | `;
107 |
108 | export const FinalizedContent = styled(LinearGradient).attrs({
109 | colors: [
110 | Color(colors.c300)
111 | .lighten(0.5)
112 | .hex(),
113 | Color(colors.c400)
114 | .lighten(0.5)
115 | .hex(),
116 | ],
117 | start: { x: 0, y: 0 },
118 | end: { x: 1, y: 0 },
119 | })`
120 | padding: 5px;
121 | border-bottom-left-radius: 5px;
122 | border-bottom-right-radius: 5px;
123 | `;
124 |
125 | export const FinalizedContentItem = styled.TouchableOpacity<{ last?: boolean }>`
126 | flex-direction: row;
127 | align-items: center;
128 | justify-content: space-between;
129 | padding: 10px 0;
130 | margin: 0 10px;
131 | border-style: solid;
132 | border-bottom-color: ${colors.n100};
133 | border-bottom-width: ${props => (props.last ? '0' : '1px')};
134 | `;
135 |
136 | export const FinalizedContentItemText = styled.Text`
137 | color: ${colors.n100};
138 | font-size: 14px;
139 | font-weight: 500;
140 | `;
141 |
--------------------------------------------------------------------------------
/src/pages/CabeSave/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useMemo, useCallback } from 'react';
2 | import { useNavigation, useNavigationParam } from 'react-navigation-hooks';
3 | import { Alert } from 'react-native';
4 | import { useDispatch, useSelector } from 'react-redux';
5 | import { AndroidBackHandler } from 'react-navigation-backhandler';
6 | import { v4 as uuidv4 } from 'uuid';
7 |
8 | import {
9 | createCabeRequest,
10 | getCabeRequest,
11 | updateCabeRequest,
12 | } from 'store/modules/cabes/actions';
13 | import { RootStore } from 'store/modules/rootReducer';
14 | import { Header, ShimmerLoading } from 'components';
15 |
16 | import CabeItemsList from './CabeItemsList';
17 | import CabeName from './CabeName';
18 | import CabeValue from './CabeValue';
19 | import { CabeSaveProvider, useCabeSave } from './CabeSaveContext';
20 |
21 | // import { Container } from './styles';
22 |
23 | function CabeSave() {
24 | const { goBack } = useNavigation();
25 | const cabeId = useNavigationParam('cabeId');
26 | const [currentStep, setCurrentStep] = useState(0);
27 | const [loading, setLoading] = useState(true);
28 | const dispatch = useDispatch();
29 | const {
30 | cabeValue: { items, name, value },
31 | setName,
32 | setValue,
33 | setItems,
34 | } = useCabeSave();
35 | const cabe = useSelector((state: RootStore) => state.cabes.current);
36 |
37 | const isEditing = useMemo(() => cabeId && cabe && cabe.id === cabeId, [
38 | cabeId,
39 | cabe,
40 | ]);
41 |
42 | useEffect(() => {
43 | if (cabeId) {
44 | setLoading(true);
45 | dispatch(getCabeRequest(cabeId));
46 | } else {
47 | setTimeout(() => setLoading(false), 400);
48 | }
49 | }, []);
50 |
51 | useEffect(() => {
52 | if (isEditing && cabe) {
53 | setName(cabe.name);
54 | setValue(cabe.value);
55 | setItems(cabe.items);
56 | setTimeout(() => setLoading(false), 400);
57 | }
58 | }, [isEditing, cabe]);
59 |
60 | const saveCabe = useCallback(() => {
61 | if (isEditing) {
62 | dispatch(updateCabeRequest({ name, value, items, id: cabeId }));
63 | } else {
64 | dispatch(
65 | createCabeRequest({
66 | name,
67 | value,
68 | items,
69 | id: uuidv4(),
70 | })
71 | );
72 | }
73 | }, [dispatch, isEditing, name, value, items, cabeId]);
74 |
75 | const cancel = useCallback(() => {
76 | let shouldShowAlert = false;
77 | if (isEditing && cabe) {
78 | if (name !== cabe.name || cabe.items !== items || cabe.value !== value) {
79 | shouldShowAlert = true;
80 | }
81 | } else {
82 | shouldShowAlert = true;
83 | }
84 |
85 | if (shouldShowAlert) {
86 | Alert.alert(
87 | 'Cancelar Cabe?',
88 | `Deseja cancelar a ${isEditing ? 'edição' : 'criação'} do seu Cabe?`,
89 | [{ text: 'Não' }, { text: 'Sim', onPress: goBack }]
90 | );
91 | } else {
92 | goBack();
93 | }
94 | return true;
95 | }, [isEditing, cabe, name, value, items, goBack]);
96 |
97 | if (loading) {
98 | return ;
99 | }
100 |
101 | return (
102 | <>
103 |
107 | cancel()} />
108 | {currentStep === 0 && (
109 | setCurrentStep(1)} />
110 | )}
111 | {currentStep === 1 && (
112 | setCurrentStep(2)}
114 | backStep={() => setCurrentStep(0)}
115 | />
116 | )}
117 | {currentStep === 2 && (
118 | setCurrentStep(1)} />
119 | )}
120 | >
121 | );
122 | }
123 |
124 | export default () => {
125 | return (
126 |
127 |
128 |
129 | );
130 | };
131 |
--------------------------------------------------------------------------------
/src/components/CabeItemInput/index.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | useState,
3 | useCallback,
4 | useMemo,
5 | useEffect,
6 | useRef,
7 | } from 'react';
8 | import { Input } from 'react-native-elements';
9 |
10 | import colors from 'styles/colors';
11 |
12 | const emptyValue = {
13 | name: '',
14 | quantity: (undefined as unknown) as number,
15 | };
16 |
17 | type Props = {
18 | onClickSave: (data: typeof emptyValue) => void;
19 | customValue: typeof emptyValue | null;
20 | };
21 |
22 | const CabeItemInput: React.FC = ({
23 | onClickSave,
24 | customValue,
25 | }: Props) => {
26 | const [currentStep, setCurrentStep] = useState(0);
27 | const [inputValue, setInputValue] = useState<{
28 | name: string;
29 | quantity: number;
30 | }>(emptyValue);
31 | const inputRef = useRef(null);
32 |
33 | useEffect(() => {
34 | if (customValue) {
35 | setInputValue(customValue);
36 | setCurrentStep(0);
37 | if (inputRef.current) {
38 | if (!inputRef.current.isFocused()) {
39 | inputRef.current.focus();
40 | }
41 | }
42 | }
43 | }, [customValue, inputRef.current]);
44 |
45 | const handleChangeText = useCallback(
46 | value => {
47 | let newInputValue;
48 | if (currentStep === 0) newInputValue = { ...inputValue, name: value };
49 | else {
50 | const quantity = Number.parseInt(value, 10);
51 | if (value && !quantity) {
52 | return;
53 | }
54 | newInputValue = {
55 | ...inputValue,
56 | quantity,
57 | };
58 | }
59 | setInputValue(newInputValue);
60 | },
61 | [currentStep, inputValue]
62 | );
63 |
64 | const addButtonDisabled = useMemo(() => {
65 | if (currentStep === 0) {
66 | return !inputValue.name;
67 | }
68 |
69 | return !inputValue.quantity;
70 | }, [currentStep, inputValue]);
71 |
72 | const inputCurrentValue = useMemo(() => {
73 | if (currentStep === 0) {
74 | return inputValue.name;
75 | }
76 |
77 | if (inputValue.quantity && inputValue.quantity.toString()) {
78 | return inputValue.quantity.toString();
79 | }
80 |
81 | return '';
82 | }, [inputValue, currentStep]);
83 |
84 | const handleSave = useCallback(() => {
85 | const value = { ...inputValue };
86 | onClickSave(value);
87 | setInputValue(emptyValue);
88 | setCurrentStep(0);
89 | }, [onClickSave, inputValue]);
90 |
91 | return (
92 | {
104 | setCurrentStep(0);
105 | },
106 | containerStyle: {
107 | width: 30,
108 | height: 30,
109 | borderRadius: 15,
110 | backgroundColor: colors.c400,
111 | justifyContent: 'center',
112 | alignItems: 'center',
113 | },
114 | iconStyle: {
115 | color: colors.n100,
116 | },
117 | }}
118 | leftIconContainerStyle={{
119 | display: currentStep === 0 ? 'none' : 'flex',
120 | marginLeft: 0,
121 | paddingLeft: 0,
122 | marginRight: 10,
123 | }}
124 | rightIcon={{
125 | name: currentStep === 0 ? 'add' : 'check',
126 | onPress: () => {
127 | if (currentStep === 0) {
128 | setCurrentStep(1);
129 | } else {
130 | handleSave();
131 | }
132 | },
133 | containerStyle: {
134 | width: 30,
135 | height: 30,
136 | borderRadius: 15,
137 | backgroundColor: addButtonDisabled ? 'transparent' : colors.c200,
138 | justifyContent: 'center',
139 | alignItems: 'center',
140 | },
141 | disabled: addButtonDisabled,
142 | disabledStyle: {
143 | backgroundColor: 'transparent',
144 | opacity: 0.2,
145 | },
146 | iconStyle: {
147 | color: addButtonDisabled ? undefined : colors.n100,
148 | },
149 | }}
150 | />
151 | );
152 | };
153 |
154 | export default CabeItemInput;
155 |
--------------------------------------------------------------------------------
/src/pages/CabeSave/CabeItemsList/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback, memo } from 'react';
2 | import { ListItem, Icon } from 'react-native-elements';
3 | import { SwipeListView, SwipeRow } from 'react-native-swipe-list-view';
4 | import { v4 as uuidv4 } from 'uuid';
5 |
6 | import { CabeItem } from 'models/CabeItem';
7 | import { Button, CabeItemInput } from 'components';
8 |
9 | import { FloatingBottomContainer } from '../components';
10 |
11 | import {
12 | DescriptionContainer,
13 | DescriptionLine,
14 | EmptyList,
15 | SwipeableContainer,
16 | LeftSwipeableItem,
17 | RightSwipeableItem,
18 | SwipeableItemContent,
19 | } from './styles';
20 | import { useCabeSave } from '../CabeSaveContext';
21 |
22 | type Props = {
23 | nextStep: () => void;
24 | };
25 |
26 | function CabeItemsList({ nextStep }: Props) {
27 | const [editingValue, setEditingValue] = useState(null);
28 | const {
29 | addItem,
30 | editItem,
31 | removeItem,
32 | cabeValue: { items },
33 | } = useCabeSave();
34 |
35 | const handleAddItem = useCallback(
36 | (name, quantity) => {
37 | addItem({ name, quantity, done: false, value: 0, id: uuidv4() });
38 | },
39 | [addItem]
40 | );
41 |
42 | const handleEditItem = useCallback(
43 | (name, quantity) => {
44 | if (editingValue) {
45 | const i = items.findIndex(e => e.id === editingValue.id);
46 | if (i >= 0) {
47 | editItem(i, { ...editingValue, name, quantity });
48 | setEditingValue(null);
49 | }
50 | }
51 | },
52 | [editingValue, items, editItem]
53 | );
54 |
55 | const handleSaveItem = useCallback(
56 | (name, quantity) => {
57 | if (editingValue) {
58 | handleEditItem(name, quantity);
59 | } else {
60 | handleAddItem(name, quantity);
61 | }
62 | },
63 | [editingValue, handleEditItem, handleAddItem]
64 | );
65 |
66 | const handleRemoveItem = useCallback(
67 | (item: CabeItem) => {
68 | const i = items.findIndex(e => e.id === item.id);
69 | if (i >= 0) {
70 | removeItem(i);
71 | }
72 | },
73 | [items, removeItem]
74 | );
75 |
76 | const renderItem = useCallback(
77 | ({ item }: any) => (
78 |
84 |
85 |
86 |
87 | setEditingValue(item)}
91 | />
92 |
93 |
94 |
95 |
96 | handleRemoveItem(item)}
100 | />
101 |
102 |
103 |
104 |
105 |
106 | ),
107 | [handleRemoveItem]
108 | );
109 |
110 | return (
111 | <>
112 | Ainda não há itens}
115 | ListHeaderComponent={
116 |
117 |
118 | - Adicione itens ao seu Cabe digitando abaixo.
119 |
120 |
121 | - Remova arrastando o item para o lado esquerdo.
122 |
123 |
124 | - Edite arrastando o item para o lado direito.
125 |
126 |
127 | - Ao finalizar a lista, clique em Avançar.
128 |
129 |
130 | }
131 | data={items}
132 | keyExtractor={(item, index) => index.toString()}
133 | renderItem={renderItem}
134 | />
135 |
136 | <>
137 | {!!items.length && (
138 |
151 | >
152 | );
153 | }
154 |
155 | export default memo(CabeItemsList);
156 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
6 | "lib": [
7 | "es6"
8 | ] /* Specify library files to be included in the compilation. */,
9 | "allowJs": true /* Allow javascript files to be compiled. */,
10 | // "checkJs": true, /* Report errors in .js files. */
11 | "jsx": "react-native" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
12 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
13 | // "sourceMap": true, /* Generates corresponding '.map' file. */
14 | // "outFile": "./", /* Concatenate and emit output to single file. */
15 | // "outDir": "./", /* Redirect output structure to the directory. */
16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
17 | // "removeComments": true, /* Do not emit comments to output. */
18 | "noEmit": true /* Do not emit outputs. */,
19 | "incremental": true /* Enable incremental compilation */,
20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
22 | "isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */,
23 |
24 | /* Strict Type-Checking Options */
25 | "strict": true /* Enable all strict type-checking options. */,
26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
27 | // "strictNullChecks": true, /* Enable strict null checks. */
28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
32 |
33 | /* Additional Checks */
34 | // "noUnusedLocals": true, /* Report errors on unused locals. */
35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
38 |
39 | /* Module Resolution Options */
40 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
41 | "baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
42 | "paths": {
43 | "*": ["src/*"]
44 | } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */,
45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
46 | // "typeRoots": [], /* List of folders to include type definitions from. */
47 | // "types": [], /* Type declaration files to be included in compilation. */
48 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
51 |
52 | /* Source Map Options */
53 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
54 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
57 |
58 | /* Experimental Options */
59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
61 | "skipLibCheck": true
62 | },
63 |
64 | "exclude": [
65 | "node_modules",
66 | "babel.config.js",
67 | "metro.config.js",
68 | "jest.config.js"
69 | ]
70 | }
71 |
--------------------------------------------------------------------------------
/src/pages/CabeProcess/CabeItemValue/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import {
3 | View,
4 | SafeAreaView,
5 | KeyboardAvoidingView,
6 | Platform,
7 | Text,
8 | ScrollView,
9 | } from 'react-native';
10 | import { TextInputMask, MaskService } from 'react-native-masked-text';
11 |
12 | import { Button } from 'components';
13 |
14 | import {
15 | DescriptionContainer,
16 | DescriptionText,
17 | Input,
18 | QuantityText,
19 | TotalValue,
20 | } from './styles';
21 |
22 | type Props = {
23 | quantity: number;
24 | itemName: string;
25 | initialValue?: number;
26 | nextStep: (value: number) => void;
27 | previousStep: () => void;
28 | };
29 |
30 | export default function CabeItemValue({
31 | nextStep,
32 | previousStep,
33 | quantity,
34 | itemName,
35 | initialValue,
36 | }: Props) {
37 | const [value, setValue] = useState({
38 | toShow: initialValue
39 | ? MaskService.toMask('money', initialValue.toFixed(2))
40 | : '0',
41 | toUse: initialValue || 0,
42 | });
43 |
44 | return (
45 |
55 |
60 |
61 |
62 |
63 |
64 | Informe o valor unitário do item{' '}
65 | {itemName}, que nós
66 | vamos calcular o total para você!
67 |
68 |
69 | {
81 | const newValue =
82 | Number.parseFloat(
83 | MaskService.toRawValue('money', newV)
84 | ).toFixed(2) || '0';
85 | const v = Number.parseFloat(newValue || '');
86 |
87 | if (!Number.isNaN(v)) {
88 | const realValue = MaskService.toMask('money', newValue);
89 |
90 | setValue({
91 | toShow: realValue,
92 | toUse: Number.parseFloat(
93 | MaskService.toRawValue('money', realValue) // we need to make this transform again to ensure that we have correct float precision
94 | ),
95 | });
96 | } else {
97 | setValue({
98 | toShow: '0',
99 | toUse: 0,
100 | });
101 | }
102 | }}
103 | />
104 | Quantidade: {quantity}
105 |
106 | Total baseado no valor que você digitou:{' '}
107 |
108 | {MaskService.toMask(
109 | 'money',
110 | (value.toUse * quantity).toFixed(2)
111 | )}
112 |
113 |
114 |
115 |
116 |
122 |
129 |
130 | {/* {
137 | const v = Number.parseFloat(newValue || '');
138 |
139 | if (!Number.isNaN(v)) {
140 | const realValue = MaskService.toMask('money', newValue);
141 |
142 | setValue({
143 | toShow: realValue,
144 | toUse: Number.parseFloat(
145 | MaskService.toRawValue('money', realValue) // we need to make this transform again to ensure that we have correct float precision
146 | ),
147 | });
148 | } else {
149 | setValue({
150 | toShow: '0',
151 | toUse: 0,
152 | });
153 | }
154 | }}
155 | /> */}
156 |
157 |
158 | );
159 | }
160 |
--------------------------------------------------------------------------------
/ios/cabenomeubolso.xcodeproj/xcshareddata/xcschemes/cabenomeubolso.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
61 |
67 |
68 |
69 |
70 |
71 |
77 |
78 |
79 |
80 |
81 |
82 |
92 |
94 |
100 |
101 |
102 |
103 |
104 |
105 |
111 |
113 |
119 |
120 |
121 |
122 |
124 |
125 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/ios/cabenomeubolso.xcodeproj/xcshareddata/xcschemes/cabenomeubolso-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
61 |
67 |
68 |
69 |
70 |
71 |
77 |
78 |
79 |
80 |
81 |
82 |
92 |
94 |
100 |
101 |
102 |
103 |
104 |
105 |
111 |
113 |
119 |
120 |
121 |
122 |
124 |
125 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/src/pages/CabesList/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { ListItem, Text, Icon } from 'react-native-elements';
3 | import { useNavigation } from 'react-navigation-hooks';
4 | import { useDispatch, useSelector } from 'react-redux';
5 | import { MaskService } from 'react-native-masked-text';
6 | import { SwipeRow } from 'react-native-swipe-list-view';
7 | import { Alert } from 'react-native';
8 | import Accordion from 'react-native-collapsible/Accordion';
9 |
10 | import {
11 | listCabesRequest,
12 | removeCabeRequest,
13 | } from 'store/modules/cabes/actions';
14 | import { RootStore } from 'store/modules/rootReducer';
15 | import { Header, Button } from 'components';
16 | import colors from 'styles/colors';
17 | import { Cabe } from 'models/Cabe';
18 |
19 | import {
20 | FloatingButtonContainer,
21 | List,
22 | ListItemTitleContainer,
23 | SubTitleContainer,
24 | RightSwipeableItem,
25 | SwipeableContainer,
26 | SwipeableItemContent,
27 | EmptyListText,
28 | FinalizedHeader,
29 | FinalizedHeaderText,
30 | FinalizedContent,
31 | FinalizedContentItem,
32 | FinalizedContentItemText,
33 | } from './styles';
34 |
35 | export default function CabesList() {
36 | const { navigate } = useNavigation();
37 | const dispatch = useDispatch();
38 | const [list] = useSelector((state: RootStore) => [state.cabes.list]);
39 | const [showFinalized, setShowFinalized] = useState(false);
40 |
41 | const getCabes = () => {
42 | dispatch(listCabesRequest());
43 | };
44 |
45 | const removeCabe = (cabe: Cabe) => {
46 | Alert.alert('Remover Cabe?', `Deseja remover o Cabe ${cabe.name}?`, [
47 | { text: 'Não' },
48 | { text: 'Sim', onPress: () => dispatch(removeCabeRequest(cabe.id)) },
49 | ]);
50 | };
51 |
52 | const getFinalized = () => list.filter(c => c.finalized === true);
53 |
54 | useEffect(() => {
55 | getCabes();
56 | }, []);
57 |
58 | return (
59 | <>
60 |
61 |
62 |
73 |
76 | Ainda não há nenhum Cabe. Adicione um clicando no botão abaixo.
77 |
78 | }
79 | ListFooterComponent={
80 | getFinalized().length ? (
81 | setShowFinalized(!showFinalized)}
84 | renderHeader={section => (
85 |
86 |
87 | {section.title}
88 |
89 |
90 | )}
91 | sections={[
92 | {
93 | title: 'Finalizados',
94 | content: {
95 | items: getFinalized(),
96 | },
97 | },
98 | ]}
99 | renderContent={section => (
100 |
101 | {section.content.items.map((cabe, index) => (
102 |
105 | navigate('FinalizedCabeView', { cabeId: cabe.id })
106 | }
107 | last={index === section.content.items.length - 1}
108 | >
109 |
110 | {cabe.name}
111 |
112 |
113 | {MaskService.toMask('money', cabe.value.toFixed(2))}
114 |
115 |
116 | ))}
117 |
118 | )}
119 | />
120 | ) : null
121 | }
122 | data={list.filter(c => c.finalized === false)}
123 | keyExtractor={(item: any) => item.id.toString()}
124 | renderItem={({ item }: any) => (
125 |
130 |
131 |
132 | navigate('CabeSave', { cabeId: item.id })}
134 | >
135 |
136 |
137 | removeCabe(item)}>
138 |
139 |
140 |
141 |
142 |
145 | {item.name}
146 |
147 | }
148 | onPress={() => navigate('CabeProcess', { id: item.id })}
149 | bottomDivider
150 | subtitle={
151 |
152 | {MaskService.toMask('money', item.value)}
153 |
154 | }
155 | />
156 |
157 | )}
158 | />
159 | >
160 | );
161 | }
162 |
--------------------------------------------------------------------------------
/src/pages/CabeProcess/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useNavigation, useNavigationParam } from 'react-navigation-hooks';
3 | import { useDispatch, useSelector } from 'react-redux';
4 | import { SafeAreaView, Alert } from 'react-native';
5 | import { AndroidBackHandler } from 'react-navigation-backhandler';
6 |
7 | import { CabeItem } from 'models/CabeItem';
8 | import { getCabeRequest, updateCabeRequest } from 'store/modules/cabes/actions';
9 | import { RootStore } from 'store/modules/rootReducer';
10 | import { Header, Button, ShimmerLoading } from 'components';
11 |
12 | import { Cabe } from 'models/Cabe';
13 | import CabeItems from './CabeItems';
14 | import CabeItemQuantity from './CabeItemQuantity';
15 | import CabeItemValue from './CabeItemValue';
16 |
17 | // import { Container } from './styles';
18 |
19 | export default function CabeProcess() {
20 | const id = useNavigationParam('id');
21 | const { goBack } = useNavigation();
22 | const [step, setStep] = useState(0);
23 | const [currentItem, setCurrentItem] = useState(null);
24 | const [items, setItems] = useState([]);
25 | const dispatch = useDispatch();
26 | const [loading, setLoading] = useState(true);
27 | const cabe = useSelector((state: RootStore) => state.cabes.current);
28 |
29 | const saveItem = () => {
30 | if (!currentItem) {
31 | return;
32 | }
33 |
34 | const index = items.findIndex(i => i.id === currentItem.id);
35 | if (index >= 0) {
36 | const itemsCopy = [...items];
37 | itemsCopy[index] = currentItem;
38 | setItems(itemsCopy);
39 | }
40 | };
41 |
42 | const getCurrentValue = () => {
43 | return items.reduce(
44 | (previousValue, currentValue) =>
45 | currentValue.value * currentValue.quantity + previousValue,
46 | 0
47 | );
48 | };
49 |
50 | const notFinalizedItemsCount = () =>
51 | items.filter(i => i.done === false).length;
52 |
53 | const saveCabe = () => {
54 | if (cabe) {
55 | const updatedCabe: Cabe = {
56 | ...cabe,
57 | items,
58 | finalized: true,
59 | finalizedAt: new Date(),
60 | };
61 | dispatch(updateCabeRequest(updatedCabe));
62 | }
63 | };
64 |
65 | const handleSaveCabe = () => {
66 | if (notFinalizedItemsCount()) {
67 | Alert.alert(
68 | 'Finalizar Cabe?',
69 | 'Vemos que você não concluiu todos os itens deste Cabe. Deseja finalizar mesmo assim?',
70 | [{ text: 'Não' }, { text: 'Sim', onPress: saveCabe }]
71 | );
72 | } else {
73 | saveCabe();
74 | }
75 | };
76 |
77 | const backFromCabe = () => {
78 | switch (step) {
79 | case 0:
80 | if (cabe && cabe.items.length !== notFinalizedItemsCount()) {
81 | Alert.alert(
82 | 'Cancelar Cabe?',
83 | 'Vemos que você começou esse Cabe, mas não finalizou. Seu progresso será perdido caso volte. Deseja voltar mesmo assim?',
84 | [{ text: 'Não' }, { text: 'Sim', onPress: goBack }]
85 | );
86 | } else {
87 | goBack();
88 | }
89 | break;
90 | case 1:
91 | case 2:
92 | setStep(step - 1);
93 | break;
94 | default:
95 | break;
96 | }
97 | return true;
98 | };
99 |
100 | useEffect(() => {
101 | setLoading(true);
102 | dispatch(getCabeRequest(id));
103 | }, []);
104 |
105 | useEffect(() => {
106 | if (cabe) {
107 | setTimeout(() => setLoading(false), 400);
108 | setItems(cabe.items);
109 | }
110 | }, [cabe]);
111 |
112 | useEffect(() => {
113 | saveItem();
114 | }, [currentItem]);
115 |
116 | if (loading) {
117 | return ;
118 | }
119 |
120 | return (
121 | <>
122 |
128 | backFromCabe()} />
129 | {step === 0 && (
130 | <>
131 | {
136 | setCurrentItem(item);
137 | setStep(1);
138 | }}
139 | />
140 | {notFinalizedItemsCount() < items.length && (
141 |
149 |
156 |
157 | )}
158 | >
159 | )}
160 | {step === 1 && (
161 | {
165 | setStep(2);
166 | if (currentItem && quantity !== currentItem.quantity) {
167 | setCurrentItem({ ...currentItem, quantity });
168 | }
169 | }}
170 | previousStep={() => {
171 | setStep(0);
172 | }}
173 | />
174 | )}
175 | {step === 2 && (
176 | {
181 | if (currentItem && value !== currentItem.value) {
182 | setCurrentItem({ ...currentItem, value, done: true });
183 | }
184 | setStep(0);
185 | }}
186 | previousStep={() => {
187 | setStep(1);
188 | }}
189 | />
190 | )}
191 | >
192 | );
193 | }
194 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin, switch paths to Windows format before running java
129 | if $cygwin ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=$((i+1))
158 | done
159 | case $i in
160 | (0) set -- ;;
161 | (1) set -- "$args0" ;;
162 | (2) set -- "$args0" "$args1" ;;
163 | (3) set -- "$args0" "$args1" "$args2" ;;
164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=$(save "$@")
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185 | cd "$(dirname "$0")"
186 | fi
187 |
188 | exec "$JAVACMD" "$@"
189 |
--------------------------------------------------------------------------------
/src/pages/FinalizedCabeView/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useMemo, useCallback, useState } from 'react';
2 | import { SectionList, Alert } from 'react-native';
3 | import { MaskService } from 'react-native-masked-text';
4 | import { useDispatch, useSelector } from 'react-redux';
5 | import { useNavigationParam, useNavigation } from 'react-navigation-hooks';
6 | import { format } from 'date-fns';
7 | import { v4 as uuidv4 } from 'uuid';
8 |
9 | import {
10 | getCabeRequest,
11 | removeCabeRequest,
12 | createCabeRequest,
13 | } from 'store/modules/cabes/actions';
14 | import { RootStore } from 'store/modules/rootReducer';
15 | import { Header, Button, ShimmerLoading } from 'components';
16 |
17 | import {
18 | ValueItemContainer,
19 | ValueItemText,
20 | ValueItemTitle,
21 | ValuesContainer,
22 | SectionHeader,
23 | ItemRow,
24 | ItemText,
25 | ListItem,
26 | ValueRestText,
27 | ListHeaderContainer,
28 | FinalizedAtText,
29 | } from './styles';
30 |
31 | export default function FinalizedCabeView() {
32 | const dispatch = useDispatch();
33 | const cabeId = useNavigationParam('cabeId');
34 | const { goBack, navigate } = useNavigation();
35 | const cabe = useSelector((state: RootStore) => state.cabes.current);
36 | const [loading, setLoading] = useState(true);
37 |
38 | useEffect(() => {
39 | setLoading(true);
40 | dispatch(getCabeRequest(cabeId));
41 | }, []);
42 |
43 | useEffect(() => {
44 | if (cabe) {
45 | setTimeout(() => setLoading(false), 400);
46 | }
47 | }, [cabe]);
48 |
49 | const makeSections = useMemo(() => {
50 | if (cabe) {
51 | return [
52 | {
53 | title: 'Finalizados',
54 | data: cabe.items.filter(i => i.done === true),
55 | done: true,
56 | },
57 | {
58 | title: 'Não finalizados',
59 | data: cabe.items.filter(i => i.done === false),
60 | done: false,
61 | },
62 | ];
63 | }
64 |
65 | return [];
66 | }, [cabe]);
67 |
68 | const newCabeFromThis = useCallback(() => {
69 | if (cabe) {
70 | setLoading(true);
71 | const items = cabe.items.map(item => ({
72 | ...item,
73 | done: false,
74 | value: 0,
75 | id: uuidv4(),
76 | }));
77 | const newCabeId = uuidv4();
78 | dispatch(
79 | createCabeRequest(
80 | {
81 | name: `${cabe.name} Novo`,
82 | value: cabe.value,
83 | items,
84 | id: newCabeId,
85 | },
86 | () => {
87 | navigate('CabeSave', { cabeId: newCabeId });
88 | }
89 | )
90 | );
91 | setTimeout(() => setLoading(false), 400);
92 | }
93 | }, [cabe]);
94 |
95 | const removeCabe = () => {
96 | if (cabe) {
97 | Alert.alert(
98 | 'Remover Cabe?',
99 | `Deseja remover o Cabe ${cabe.name}? Esta ação não pode ser desfeita.`,
100 | [
101 | { text: 'Não' },
102 | {
103 | text: 'Sim',
104 | onPress: () => {
105 | dispatch(removeCabeRequest(cabe.id));
106 | goBack();
107 | },
108 | },
109 | ]
110 | );
111 | }
112 | };
113 |
114 | const getMaxValue = () => (cabe ? cabe.value : 0);
115 | const getCurrentValue = () =>
116 | cabe
117 | ? cabe.items.reduce(
118 | (previousValue, currentValue) =>
119 | currentValue.value * currentValue.quantity + previousValue,
120 | 0
121 | )
122 | : 0;
123 |
124 | if (loading) {
125 | return ;
126 | }
127 |
128 | return (
129 | <>
130 |
134 |
137 | {!!cabe && cabe.finalizedAt && (
138 |
139 | Finalizado em: {format(cabe.finalizedAt, 'dd/MM/yyyy')}
140 |
141 | )}
142 |
143 |
144 | Podia Gastar
145 |
146 | {MaskService.toMask('money', getMaxValue().toFixed(2))}
147 |
148 |
149 |
150 | Gastou
151 |
152 | {MaskService.toMask('money', getCurrentValue().toFixed(2))}
153 |
154 |
155 |
156 |
157 | Quanto restou:{' '}
158 | {MaskService.toMask(
159 | 'money',
160 | (getMaxValue() - getCurrentValue()).toFixed(2)
161 | )}
162 |
163 |