From 1beee68bffe6719623cfd07be14051966de4576a Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Sat, 20 Jan 2024 11:54:25 +0100 Subject: [PATCH] Major refactoring: navigation does not break anymore. the user can now navigate between the tabs without loosing context --- app/(tabs)/{budget => (budget)}/_layout.tsx | 0 .../{budget => (budget)}/addCategory.tsx | 0 app/(tabs)/{budget => (budget)}/category.tsx | 19 ++++++++--- .../{budget => (budget)}/editCategory.tsx | 0 app/(tabs)/{budget => (budget)}/index.tsx | 13 +++++--- app/(tabs)/{home => (home)}/_layout.tsx | 3 -- app/(tabs)/{home => (home)}/index.tsx | 32 ++++++++++++++----- app/(tabs)/{home => (home)}/userSettings.tsx | 0 app/(tabs)/{stats => (stats)}/index.tsx | 0 app/(tabs)/_layout.tsx | 11 ++----- app/_layout.tsx | 31 ++++++++++++++---- app/{(tabs)/home => }/expense/[expense].tsx | 21 ++++++------ app/{(tabs)/home => }/expense/_layout.tsx | 2 +- app/{(tabs)/home => }/expense/new.tsx | 17 +++++----- app/index.tsx | 2 +- components/budget/budgetHeader.tsx | 3 +- components/common/CustomCard.tsx | 1 - .../addItem => expense}/CategorySelector.tsx | 6 ++-- .../CategorySelectorModal.tsx | 12 +++---- .../DateSelectorButton.tsx | 6 ++-- components/index.tsx | 6 ++-- hooks/useFetch.ts | 29 ++++++++++++++--- services/database.ts | 3 +- 23 files changed, 137 insertions(+), 80 deletions(-) rename app/(tabs)/{budget => (budget)}/_layout.tsx (100%) rename app/(tabs)/{budget => (budget)}/addCategory.tsx (100%) rename app/(tabs)/{budget => (budget)}/category.tsx (84%) rename app/(tabs)/{budget => (budget)}/editCategory.tsx (100%) rename app/(tabs)/{budget => (budget)}/index.tsx (88%) rename app/(tabs)/{home => (home)}/_layout.tsx (90%) rename app/(tabs)/{home => (home)}/index.tsx (83%) rename app/(tabs)/{home => (home)}/userSettings.tsx (100%) rename app/(tabs)/{stats => (stats)}/index.tsx (100%) rename app/{(tabs)/home => }/expense/[expense].tsx (91%) rename app/{(tabs)/home => }/expense/_layout.tsx (93%) rename app/{(tabs)/home => }/expense/new.tsx (89%) rename components/{home/addItem => expense}/CategorySelector.tsx (89%) rename components/{home/addItem => expense}/CategorySelectorModal.tsx (89%) rename components/{home/addItem => expense}/DateSelectorButton.tsx (87%) diff --git a/app/(tabs)/budget/_layout.tsx b/app/(tabs)/(budget)/_layout.tsx similarity index 100% rename from app/(tabs)/budget/_layout.tsx rename to app/(tabs)/(budget)/_layout.tsx diff --git a/app/(tabs)/budget/addCategory.tsx b/app/(tabs)/(budget)/addCategory.tsx similarity index 100% rename from app/(tabs)/budget/addCategory.tsx rename to app/(tabs)/(budget)/addCategory.tsx diff --git a/app/(tabs)/budget/category.tsx b/app/(tabs)/(budget)/category.tsx similarity index 84% rename from app/(tabs)/budget/category.tsx rename to app/(tabs)/(budget)/category.tsx index 3a88f32..c2e3ea9 100644 --- a/app/(tabs)/budget/category.tsx +++ b/app/(tabs)/(budget)/category.tsx @@ -6,9 +6,12 @@ import { ExpenseItem, LoadingSymbol, TextInputBar } from "../../../components"; import useFetch from "../../../hooks/useFetch"; import { useTheme } from "../../contexts/ThemeContext"; import { SafeAreaView } from "react-native-safe-area-context"; +import { useNavigation } from "expo-router/src/useNavigation"; +import { useEffect } from "react"; export default function Page() { const router = useRouter(); + const navigation = useNavigation(); const {colors} = useTheme(); const {category_guid} = useLocalSearchParams(); @@ -17,13 +20,21 @@ export default function Page() { const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color FROM expense e JOIN category c ON e.category_guid = c.guid WHERE c.guid = ? ORDER BY expense_datetime desc;", args: [category_guid]}); const handleEditCategory = () => { - router.push({pathname: "/budget/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); + router.push({pathname: "/editCategory", params: {category_guid: category_guid, category_color: category_color, category_amount: category_amount, category_name: category_name, category_type: category_type}}); } const handleBackButton = () => { router.back(); } + useEffect(() => { + console.log("useEffect called") + const unsubscribe = navigation.addListener("focus", () => { + reFetch(); + }) + return unsubscribe; + }, [navigation]) + return ( @@ -40,7 +51,7 @@ export default function Page() { {isLoading ? () : ( - router.push(`/home/expense/${item.guid}`)} + onPress={()=>router.push(`/expense/${item.expense_guid}`)} />} - keyExtractor={item => item.guid} + keyExtractor={item => item.expense_guid} ItemSeparatorComponent={() => { return (); }} diff --git a/app/(tabs)/budget/editCategory.tsx b/app/(tabs)/(budget)/editCategory.tsx similarity index 100% rename from app/(tabs)/budget/editCategory.tsx rename to app/(tabs)/(budget)/editCategory.tsx diff --git a/app/(tabs)/budget/index.tsx b/app/(tabs)/(budget)/index.tsx similarity index 88% rename from app/(tabs)/budget/index.tsx rename to app/(tabs)/(budget)/index.tsx index 6615367..b03fc12 100644 --- a/app/(tabs)/budget/index.tsx +++ b/app/(tabs)/(budget)/index.tsx @@ -1,5 +1,5 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; -import { router } from 'expo-router'; +import { router, useNavigation } from 'expo-router'; import { useEffect, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; @@ -11,7 +11,7 @@ import { useTheme } from '../../contexts/ThemeContext'; export default function Page() { const {colors} = useTheme() const containerColor = colors.containerColor; - + const navigation = useNavigation(); const [selectedPage, setSelectedPage] = useState("noPageLoaded"); useEffect(() => { @@ -27,9 +27,10 @@ export default function Page() { const {data, isLoading, reFetch} = useFetch({sql: "SELECT c.guid AS category_guid, c.name AS category_name, c.color AS category_color, c.type AS category_type, SUM(e.amount) as total_expenses, c.allocated_amount as allocated_amount FROM category c LEFT JOIN expense e ON e.category_guid = c.guid WHERE c.type = ? GROUP BY c.guid", args: selectedPage === "expenses" ? ["expense"] : selectedPage === "savings" ? ["saving"] : []}); useEffect(() => { - reFetch(); + reFetch() }, [selectedPage]); + const handlePageSelection = (page: string) => { if(page !== selectedPage) { setSelectedPage(page); @@ -38,15 +39,16 @@ export default function Page() { }; const handleCategoryPress = (item: {[column: string]: any;}) => { - router.push({pathname: "/(tabs)/budget/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) + router.push({pathname: "/category", params: {category_guid: item.category_guid, category_name: item.category_name}}) } return ( + { - router.push({pathname: '/(tabs)/budget/addCategory'}); + router.push({pathname: '/addCategory'}); }}/> {isLoading ? () : ( @@ -67,6 +69,7 @@ export default function Page() { }} /> )} + ); } diff --git a/app/(tabs)/home/_layout.tsx b/app/(tabs)/(home)/_layout.tsx similarity index 90% rename from app/(tabs)/home/_layout.tsx rename to app/(tabs)/(home)/_layout.tsx index ba7964c..8e16222 100644 --- a/app/(tabs)/home/_layout.tsx +++ b/app/(tabs)/(home)/_layout.tsx @@ -24,9 +24,6 @@ export default function _Layout() { title: "test", headerShown: false, }}/> - { export default function Page() { const { colors, theme } = useTheme() - - + const navigation = useNavigation(); const router = useRouter(); const [plusShow, setPlusShow] = useState(true); const prevOffset = useRef(0); @@ -62,17 +62,24 @@ export default function Page() { } const {data, isLoading, reFetch} = useFetch({sql: "SELECT e.guid AS expense_guid, c.guid AS category_guid, e.name AS expense_name, c.name AS category_name, e.datetime AS expense_datetime, e.amount AS expense_amount, c.color AS category_color, c.type AS category_type FROM expense e JOIN category c ON e.category_guid = c.guid ORDER BY expense_datetime desc;", args: []}); - + const expenseDates = useMemo(()=> constructMarkedDates(data) , [data]) + useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + console.log("focus event triggered") + reFetch(); + }); + return unsubscribe; + }, [navigation]); return ( {plusShow && { - router.push("/(tabs)/home/expense/new"); + router.push("/expense/new"); // executeQuery({sql: "SELECT guid FROM category", args: []}).then((result) => { // if("rows" in result[0]) { @@ -89,7 +96,7 @@ export default function Page() { data={data} ListHeaderComponent={ <> - {router.push("/home/userSettings")}}/> + {router.push("/userSettings")}}/> } - renderItem = {({item}) => {router.push(`/(tabs)/home/expense/${guid}`)}}/>} - keyExtractor={item => item.expense_guid} + renderItem = {({item}) => + {router.push(`/expense/${guid}`)}} + />} + keyExtractor={item => item.expense_guid } ItemSeparatorComponent={() => { return (); }} diff --git a/app/(tabs)/home/userSettings.tsx b/app/(tabs)/(home)/userSettings.tsx similarity index 100% rename from app/(tabs)/home/userSettings.tsx rename to app/(tabs)/(home)/userSettings.tsx diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/(stats)/index.tsx similarity index 100% rename from app/(tabs)/stats/index.tsx rename to app/(tabs)/(stats)/index.tsx diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 79ff582..4c74a39 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -35,30 +35,25 @@ export default function Layout() { return ( - ( ), - unmountOnBlur: true, - href: "(tabs)/budget" } }/> - ( ), - unmountOnBlur: true, - href: "(tabs)/home/" } }/> - ( ), - unmountOnBlur: true, } }/> diff --git a/app/_layout.tsx b/app/_layout.tsx index f34a4ea..e99a541 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,20 +1,37 @@ -import { Slot } from 'expo-router'; +import { SplashScreen, Stack } from 'expo-router'; import React, { useEffect } from 'react'; import { addCategory, initDatabase } from '../services/database'; import { AuthProvider } from './contexts/AuthContext'; import { ThemeProvider } from './contexts/ThemeContext'; +import { useTheme } from './contexts/ThemeContext'; + export default function _layout() { - + const {colors} = useTheme(); useEffect(() => { initDatabase(); + }, []); + console.log(colors.backgroundColor) return ( - - - - - + + + + + + + + ) } \ No newline at end of file diff --git a/app/(tabs)/home/expense/[expense].tsx b/app/expense/[expense].tsx similarity index 91% rename from app/(tabs)/home/expense/[expense].tsx rename to app/expense/[expense].tsx index 53e38ae..1eb15ff 100644 --- a/app/(tabs)/home/expense/[expense].tsx +++ b/app/expense/[expense].tsx @@ -1,15 +1,15 @@ import { View, Text, Alert, StyleSheet } from 'react-native' import React, { useEffect, useState } from 'react' import { router, useLocalSearchParams, useRouter } from 'expo-router' -import useFetch from '../../../../hooks/useFetch'; -import { Category, Expense } from '../../../../types/dbItems'; -import { CategorySelectorModal, AutoDecimalInput, CategorySelector, TextInputBar, DateSelectorButton, RoundedButton } from '../../../../components'; -import colors from '../../../../constants/colors'; -import { addExpense, deleteExpense, executeQuery, updateExpense } from '../../../../services/database'; -import { SimpleDate } from '../../../../util/SimpleDate'; +import useFetch from '../../hooks/useFetch'; +import { Category, Expense } from '../../types/dbItems'; +import { CategorySelectorModal, AutoDecimalInput, CategorySelector, TextInputBar, DateSelectorButton, RoundedButton } from '../../components'; +import colors from '../../constants/colors'; +import { addExpense, deleteExpense, executeQuery, updateExpense } from '../../services/database'; +import { SimpleDate } from '../../util/SimpleDate'; import DateTimePicker from '@react-native-community/datetimepicker'; -import { SIZES } from '../../../../constants/theme'; -import { useTheme } from '../../../contexts/ThemeContext'; +import { SIZES } from '../../constants/theme'; +import { useTheme } from '../contexts/ThemeContext'; export default function Page() { const router = useRouter(); @@ -25,8 +25,6 @@ export default function Page() { const [datePickerShown, setDatePickerShown] = useState(false); const [selectedDate, setSelectedDate] = useState(new Date()); - - console.log(router.canGoBack()) useEffect(()=>{ const entry = data[0]; console.log(entry) @@ -69,8 +67,7 @@ export default function Page() { await updateExpense(selectedExpense!.guid!, expenseName, selectedCategory!.guid!, new SimpleDate(selectedDate).format("YYYY-MM-DD"), Number(formatedValue)) } if(validateInput()){ - insert(); - router.back(); + insert().then( () => router.back()) }else { Alert.alert("Invalid input", "One of the Props is not properly defined") } diff --git a/app/(tabs)/home/expense/_layout.tsx b/app/expense/_layout.tsx similarity index 93% rename from app/(tabs)/home/expense/_layout.tsx rename to app/expense/_layout.tsx index ef1ee81..f867c8a 100644 --- a/app/(tabs)/home/expense/_layout.tsx +++ b/app/expense/_layout.tsx @@ -1,7 +1,7 @@ import { StyleSheet, Text, View } from 'react-native' import { Stack } from 'expo-router' import React from 'react' -import { useTheme } from '../../../contexts/ThemeContext' +import { useTheme } from '../contexts/ThemeContext' const _layout = () => { const {colors} = useTheme(); diff --git a/app/(tabs)/home/expense/new.tsx b/app/expense/new.tsx similarity index 89% rename from app/(tabs)/home/expense/new.tsx rename to app/expense/new.tsx index f2525c2..7619489 100644 --- a/app/(tabs)/home/expense/new.tsx +++ b/app/expense/new.tsx @@ -1,12 +1,12 @@ import { View, Text, StyleSheet, Alert } from 'react-native' import React, { useRef, useState } from 'react' -import { SIZES } from '../../../../constants/theme' -import { useTheme } from '../../../contexts/ThemeContext' -import { AutoDecimalInput, CategorySelector, CategorySelectorModal, DateSelectorButton, RoundedButton, TextInputBar } from '../../../../components' -import { Category } from '../../../../types/dbItems' +import { SIZES } from '../../constants/theme' +import { useTheme } from '../contexts/ThemeContext' +import { AutoDecimalInput, CategorySelector, CategorySelectorModal, DateSelectorButton, RoundedButton, TextInputBar } from '../../components' +import { Category } from '../../types/dbItems' import DateTimePicker from '@react-native-community/datetimepicker'; -import { addExpense } from '../../../../services/database' -import { SimpleDate } from '../../../../util/SimpleDate' +import { addExpense } from '../../services/database' +import { SimpleDate } from '../../util/SimpleDate' import { useRouter } from 'expo-router' export default function AddItem() { @@ -41,8 +41,9 @@ export default function AddItem() { await addExpense(expenseName, selectedCategory?.guid!, new SimpleDate(selectedDate).format("YYYY-MM-DD"), Number(formatedValue)) } if(validateInput()){ - insert(); - router.back(); + insert().then(() => { + router.back(); + }) }else { Alert.alert("Invalid input", "One of the Props is not properly defined") } diff --git a/app/index.tsx b/app/index.tsx index 265d68b..6b489fe 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -7,7 +7,7 @@ export default function index() { const {authState} = useAuth() return ( - + ) } \ No newline at end of file diff --git a/components/budget/budgetHeader.tsx b/components/budget/budgetHeader.tsx index bca2e9b..54df035 100644 --- a/components/budget/budgetHeader.tsx +++ b/components/budget/budgetHeader.tsx @@ -77,12 +77,11 @@ const styles = StyleSheet.create({ containerStyle: { flexDirection: "row", justifyContent: "space-evenly", - marginHorizontal: 20, marginBottom: 20, + marginHorizontal: 10, marginTop: 10, }, searchBarStyle: { marginBottom: 20, - marginHorizontal: 10, } }); \ No newline at end of file diff --git a/components/common/CustomCard.tsx b/components/common/CustomCard.tsx index a866d8c..87aae81 100644 --- a/components/common/CustomCard.tsx +++ b/components/common/CustomCard.tsx @@ -40,7 +40,6 @@ export default function CustomCard(props : ViewProps) { const styles = StyleSheet.create({ container:{ borderRadius: 20, - marginHorizontal: 10, }, boxShadow: {}, }) \ No newline at end of file diff --git a/components/home/addItem/CategorySelector.tsx b/components/expense/CategorySelector.tsx similarity index 89% rename from components/home/addItem/CategorySelector.tsx rename to components/expense/CategorySelector.tsx index 27d5cfe..59e5edf 100644 --- a/components/home/addItem/CategorySelector.tsx +++ b/components/expense/CategorySelector.tsx @@ -1,8 +1,8 @@ import { StyleSheet, Text, View, TouchableOpacity } from 'react-native' import React, { useState } from 'react' -import { useTheme } from '../../../app/contexts/ThemeContext' -import { SIZES } from '../../../constants/theme'; -import { Category } from '../../../types/dbItems'; +import { useTheme } from '../../app/contexts/ThemeContext' +import { SIZES } from '../../constants/theme'; +import { Category } from '../../types/dbItems'; import CategorySelectorModal from './CategorySelectorModal'; interface CategorySelectorProps { diff --git a/components/home/addItem/CategorySelectorModal.tsx b/components/expense/CategorySelectorModal.tsx similarity index 89% rename from components/home/addItem/CategorySelectorModal.tsx rename to components/expense/CategorySelectorModal.tsx index 26f3a94..56ba20f 100644 --- a/components/home/addItem/CategorySelectorModal.tsx +++ b/components/expense/CategorySelectorModal.tsx @@ -1,11 +1,11 @@ import { Modal, NativeSyntheticEvent, StyleSheet, Text, View, FlatList } from 'react-native' import React, { useEffect, useMemo, useState } from 'react' -import { Category } from '../../../types/dbItems'; -import { useTheme } from '../../../app/contexts/ThemeContext'; -import CategoryListItem from '../../common/CategoryListItem'; -import { SIZES } from '../../../constants/theme'; -import useFetch from '../../../hooks/useFetch'; -import TextInputBar from '../../common/TextInputBar'; +import { Category } from '../../types/dbItems'; +import { useTheme } from '../../app/contexts/ThemeContext'; +import CategoryListItem from '../common/CategoryListItem'; +import { SIZES } from '../../constants/theme'; +import useFetch from '../../hooks/useFetch'; +import TextInputBar from '../common/TextInputBar'; interface CategorySelectorModalProps{ diff --git a/components/home/addItem/DateSelectorButton.tsx b/components/expense/DateSelectorButton.tsx similarity index 87% rename from components/home/addItem/DateSelectorButton.tsx rename to components/expense/DateSelectorButton.tsx index 4f07297..ec6916d 100644 --- a/components/home/addItem/DateSelectorButton.tsx +++ b/components/expense/DateSelectorButton.tsx @@ -1,8 +1,8 @@ import { View, Text, StyleSheet, TouchableOpacity, TouchableOpacityProps } from 'react-native' import React, { useState } from 'react' -import { useTheme } from '../../../app/contexts/ThemeContext'; -import { SIZES } from '../../../constants/theme'; -import { SimpleDate } from '../../../util/SimpleDate'; +import { useTheme } from '../../app/contexts/ThemeContext'; +import { SIZES } from '../../constants/theme'; +import { SimpleDate } from '../../util/SimpleDate'; import { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes'; interface DateSelectorProps extends ViewProps { diff --git a/components/index.tsx b/components/index.tsx index b20c5fd..29167eb 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -4,9 +4,9 @@ import Welcome from "./home/Welcome" import ExpenseItem from "./home/expenseItem" //home/addItem -import CategorySelector from "./home/addItem/CategorySelector" -import CategorySelectorModal from "./home/addItem/CategorySelectorModal" -import DateSelectorButton from "./home/addItem/DateSelectorButton" +import CategorySelector from "./expense/CategorySelector" +import CategorySelectorModal from "./expense/CategorySelectorModal" +import DateSelectorButton from "./expense/DateSelectorButton" //common import CustomCard from "./common/CustomCard" diff --git a/hooks/useFetch.ts b/hooks/useFetch.ts index cec238d..e089750 100644 --- a/hooks/useFetch.ts +++ b/hooks/useFetch.ts @@ -4,11 +4,31 @@ import { executeQuery } from "../services/database"; const useFetch = (query: Query) => { - const [isLoading, setIsLoading] = useState(false); - const [data, setData] = useState<{[column: string]: any;}[]>([]); - const [isEmptyResult, setIsEmptyResult] = useState(undefined); + const [fetchState, setFetchState] = useState<{ + data: {[column: string]: any;}[]; + isLoading: boolean; + isEmptyResult: boolean | undefined; + }>({ + data: [], + isLoading: false, + isEmptyResult: undefined + }); + + const setIsLoading = (isLoading: boolean) => { + setFetchState((prevState) => ( {...prevState, isLoading} )); + } + + const setData = (data: {[column: string]: any;}[]) => { + setFetchState((prevState) => ( {...prevState, data} )); + } + + const setIsEmptyResult = (isEmptyResult: boolean) => { + setFetchState((prevState) => ( {...prevState, isEmptyResult} )); + } + const reFetch = () => { + console.log("refetch called") setIsLoading(true); executeQuery(query).then((result) => { if("rows" in result[0]) { @@ -16,6 +36,7 @@ const useFetch = (query: Query) => { if(result[0]["rows"].length == 0){ setIsEmptyResult(true); } + console.log("len", result[0]["rows"].length) } }).catch((error: any) => { console.error("Fetching data from database has failed: ", error); @@ -28,7 +49,7 @@ const useFetch = (query: Query) => { reFetch(); }, []) - return {data, isLoading, reFetch, isEmptyResult}; + return {...fetchState, reFetch}; } export default useFetch; \ No newline at end of file diff --git a/services/database.ts b/services/database.ts index 7fe3921..ec5f656 100644 --- a/services/database.ts +++ b/services/database.ts @@ -57,7 +57,7 @@ export const addCategory = async (name: string, color: string, type: string, all export const updateExpense = async (guid: string, name: string, category_guid: string, datetime: string, amount: number) => { //needs user input validation for type and color (should be validated by this function) - + console.log("update expense called") try { await db.transactionAsync(async (tx) => { await tx.executeSqlAsync("UPDATE expense SET name = ?, category_guid = ?, datetime = ?, amount = ? WHERE guid = ?", [name, category_guid, datetime, amount, guid] @@ -67,6 +67,7 @@ export const updateExpense = async (guid: string, name: string, category_guid: s console.log("Error updating expense: ", error); throw error; } + console.log("update expense finished") }; export const addExpense = async (name: string, category_guid: string, datetime: string, amount: number) => {