├── .expo-shared └── assets.json ├── .github └── FUNDING.yml ├── .gitignore ├── .idea ├── .gitignore ├── modules.xml ├── muffinMobileApp.iml └── vcs.xml ├── App.js ├── Funds ├── Costs.js ├── Earnings.js ├── Login.js ├── Settings.js └── Stats.js ├── README.md ├── app.json ├── assets ├── adaptive-icon.png ├── favicon.png ├── icon.png └── splash.png ├── babel.config.js ├── context └── context.js ├── package-lock.json └── package.json /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: devpew 4 | patreon: johenews 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | 12 | # macOS 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/muffinMobileApp.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import { StatusBar } from 'expo-status-bar'; 2 | import React, {useEffect, useState, useMemo} from 'react'; 3 | import {ActivityIndicator, SafeAreaView, StyleSheet, Text, View} from 'react-native'; 4 | 5 | import { createDrawerNavigator } from '@react-navigation/drawer'; 6 | import { NavigationContainer } from '@react-navigation/native'; 7 | 8 | import AsyncStorage from '@react-native-async-storage/async-storage'; 9 | 10 | import {AuthContext} from "./context/context"; 11 | 12 | import Earnings from "./Funds/Earnings"; 13 | import Costs from "./Funds/Costs"; 14 | import Login from './Funds/Login' 15 | import Settings from "./Funds/Settings"; 16 | import Stats from './Funds/Stats' 17 | 18 | const Drawer = createDrawerNavigator(); 19 | 20 | export default function App() { 21 | const [token, setToken] = useState(null) 22 | const [isLoading, setIsLoading] = useState(true) 23 | 24 | const authContent = useMemo(() => ({ 25 | signIn: () => { 26 | setIsLoading(false) 27 | setToken('') 28 | }, 29 | signOut: () => { 30 | setIsLoading(false) 31 | setToken(null) 32 | } 33 | }), []) 34 | 35 | useEffect(() => { 36 | AsyncStorage.getItem('token').then((value) => { 37 | if (value) { 38 | setToken(value) 39 | } 40 | }) 41 | }, []) 42 | 43 | useEffect(() => { 44 | setTimeout(() => { 45 | setIsLoading(false) 46 | }, 800) 47 | }, []) 48 | 49 | if (isLoading) { 50 | return ( 51 | 52 | 53 | 54 | ) 55 | } 56 | 57 | return ( 58 | 59 | 60 | 61 | {token !== null ? ( 62 | <> 63 | 64 | 65 | 66 | 67 | 68 | ) : ( 69 | <> 70 | 71 | 72 | )} 73 | 74 | 75 | 76 | 77 | ); 78 | } 79 | 80 | const styles = StyleSheet.create({ 81 | container: { 82 | flex: 1, 83 | backgroundColor: '#fff', 84 | alignItems: 'center', 85 | justifyContent: 'center', 86 | }, 87 | }); 88 | -------------------------------------------------------------------------------- /Funds/Costs.js: -------------------------------------------------------------------------------- 1 | import { StatusBar } from 'expo-status-bar'; 2 | import React, {useEffect, useState} from "react"; 3 | import {Text, View, SafeAreaView, Button, StyleSheet, TouchableOpacity, TouchableHighlight, TextInput} from 'react-native' 4 | import dayjs from 'dayjs' 5 | 6 | import AsyncStorage from '@react-native-async-storage/async-storage'; 7 | import { Picker } from "@react-native-picker/picker"; 8 | import { SwipeListView } from 'react-native-swipe-list-view'; 9 | 10 | export default function Costs() { 11 | const [dataArray, setDataArray] = useState([]) 12 | const [isLoading, setIsLoading] = useState(false) 13 | const [token, setToken] = useState('') 14 | 15 | const [currentAmount, setCurrentAmount] = useState('') 16 | const [currentCategory, setCurrentCategory] = useState('Еда') 17 | const [currentSource, setCurrentSource] = useState('TINK') 18 | 19 | const renderItem = ({item}) => ( 20 | console.log('You touched me')} 22 | style={styles.rowFront} 23 | underlayColor={'#AAA'} 24 | > 25 | 26 | {item.amount} 27 | {item.source} 28 | {item.category} 29 | {dayjs(item.datetime).format('DD MMM YYYY')} 30 | 31 | 32 | ) 33 | 34 | const closeRow = (rowMap, rowKey) => { 35 | if (rowMap[rowKey]) { 36 | rowMap[rowKey].closeRow(); 37 | } 38 | }; 39 | 40 | const deleteRow = (rowMap, rowKey) => { 41 | closeRow(rowMap, rowKey); 42 | const newData = [...dataArray]; 43 | const prevIndex = dataArray.findIndex(item => item.id === rowKey); 44 | newData.splice(prevIndex, 1); 45 | setDataArray(newData); 46 | deleteData(rowKey) 47 | }; 48 | 49 | const renderHiddenItem = (data, rowMap) => ( 50 | 51 | deleteRow(rowMap, data.item.id)} 54 | > 55 | Delete 56 | 57 | 58 | ); 59 | 60 | useEffect(() => { 61 | AsyncStorage.getItem('token').then((value) => { 62 | if (value) { 63 | setToken(value) 64 | } 65 | }) 66 | }, []) 67 | 68 | useEffect(() => { 69 | if (token !== '') { 70 | getData() 71 | } 72 | }, [token]) 73 | 74 | const getData = () => { 75 | setIsLoading(true) 76 | let URL = 'http://127.0.0.1:8000/costs' 77 | fetch(URL, { 78 | headers: { 79 | 'Token': token 80 | } 81 | }).then(res => res.json()).then(res => { 82 | setDataArray(res) 83 | }).finally(() => setIsLoading(false)) 84 | } 85 | 86 | function deleteData(id) { 87 | const requestOptions = { 88 | method: 'DELETE', 89 | headers: { 90 | 'Token': token 91 | }, 92 | body: JSON.stringify({ 93 | "id": id 94 | }) 95 | } 96 | 97 | fetch('http://127.0.0.1:8000/costs/'+id, requestOptions).then((res) => { 98 | return res.json(); 99 | }).then((res) => { 100 | getData() 101 | //console.log('DELETE COSTS OK') 102 | }).catch(function(error) { 103 | console.log('delete costs error: ', error) 104 | }) 105 | } 106 | 107 | function sendData() { 108 | const requestOptions = { 109 | method: 'POST', 110 | headers: { 111 | 'Token': token 112 | }, 113 | body: JSON.stringify({ 114 | "datetime": dayjs(Date.now()).format(), 115 | "amount": Number(currentAmount), 116 | "source": currentSource, 117 | "category": currentCategory 118 | }) 119 | } 120 | if (currentAmount !== '') { 121 | fetch('http://127.0.0.1:8000/costs', requestOptions).then((res) => { 122 | return res.json(); 123 | }).then((res) => { 124 | getData() 125 | setCurrentAmount('') 126 | //console.log("sendData OK") 127 | }).catch(function (error) { 128 | console.log('sendData POST ERROR: ', error) 129 | }) 130 | } 131 | } 132 | 133 | return ( 134 | 135 | 136 | setCurrentAmount(text)} 139 | keyboardType="numeric" 140 | defaultValue={currentAmount} 141 | /> 142 | 145 | setCurrentCategory(itemValue) 146 | }> 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |