Merge branch '39-datenbank-schmiert-ab-wenn-schnell-20-eintrage-eingetragen-werden' into 'main'

Resolve "datenbank schmiert ab wenn schnell 20~ einträge eingetragen werden"

Closes #39

See merge request thschleicher/interaktive-systeme!24
This commit is contained in:
jastornig 2023-12-13 10:35:53 +00:00
commit c63bb4a876
13 changed files with 124 additions and 89 deletions

View file

@ -1,10 +1,10 @@
import { Tabs } from "expo-router/tabs"; import { Tabs } from "expo-router/tabs";
import {StyleSheet, View, Text} from "react-native" import { StyleSheet } from "react-native";
import { FontAwesome } from "@expo/vector-icons"; import { FontAwesome } from "@expo/vector-icons";
import {useThemeColor} from "../../hooks/hooks";
import React, { useEffect } from "react";
import { Redirect } from "expo-router"; import { Redirect } from "expo-router";
import React, { useEffect } from "react";
import { useThemeColor } from "../../hooks/useThemeColor";
import { useAuth } from "../contexts/AuthContext"; import { useAuth } from "../contexts/AuthContext";
export default function Layout() { export default function Layout() {
@ -53,6 +53,7 @@ export default function Layout() {
tabBarLabel: "Home", tabBarLabel: "Home",
tabBarIcon: ({size, color}) => ( tabBarIcon: ({size, color}) => (
<FontAwesome name="home" size={size} color={color}/>), <FontAwesome name="home" size={size} color={color}/>),
unmountOnBlur: true
} }
}/> }/>
<Tabs.Screen name="stats/index" options={ <Tabs.Screen name="stats/index" options={

View file

@ -1,20 +1,21 @@
import { useFocusEffect, useRouter } from "expo-router"; import React, { useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { NativeScrollEvent, NativeSyntheticEvent, StyleSheet, View } from 'react-native'; import { NativeScrollEvent, NativeSyntheticEvent, StyleSheet, View } from 'react-native';
import { FlatList } from 'react-native-gesture-handler'; import { FlatList } from 'react-native-gesture-handler';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
import { ExpenseItem, Plus, SearchBar, Welcome } from '../../../components'; import { ExpenseItem, LoadingSymbol, Plus, SearchBar, Welcome } from '../../../components';
import { useThemeColor } from "../../../hooks/hooks"; import useFetch from '../../../hooks/useFetch';
import { addCategory, addExpense, executeQuery } from "../../../services/database"; import { useThemeColor } from "../../../hooks/useThemeColor";
import { addExpense } from "../../../services/database";
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
export default function Page() { export default function Page() {
const backgroundColor = useThemeColor("backgroundColor")
const router = useRouter() //Styles
const {onLogout} = useAuth();
const [plusShow, setPlusShow] = useState(true);
const prevOffset = useRef(0);
const styles = StyleSheet.create({ const styles = StyleSheet.create({
safeAreaViewStyle: {
flex: 1,
backgroundColor: useThemeColor("backgroundColor")
},
container: { container: {
flex: 1, flex: 1,
alignItems: "center", alignItems: "center",
@ -24,9 +25,17 @@ export default function Page() {
color: useThemeColor("primaryText"), color: useThemeColor("primaryText"),
fontSize: 70, fontSize: 70,
fontWeight: "bold" fontWeight: "bold"
},
loading: {
color: "red",
} }
}); });
const {onLogout} = useAuth();
const [plusShow, setPlusShow] = useState(true);
const prevOffset = useRef(0);
const profile = require("../../../assets/images/profile.jpg") const profile = require("../../../assets/images/profile.jpg")
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>)=>{ const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>)=>{
@ -37,43 +46,29 @@ export default function Page() {
setPlusShow(isScrollingUp || isTop) setPlusShow(isScrollingUp || isTop)
} }
const [data, setData] = useState<{[column: string]: any;}[]>([]); const newExpense = async (title: string, category_guid: string, date: string, amount: number) => {
try {
//only for demonstration during phase 1, delete later await addExpense(title, category_guid, date, amount);
useEffect(() => { } catch (error: any) {
addCategory("Test Category", "red", "budget"); console.error("Adding new expense has failed: ", error);
update(); }
}, []);
useFocusEffect(() => {
update();
});
const update = () => {
executeQuery("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;").then((result) => {
if(result === undefined) return;
setData(result);
}).catch((error) => {
throw error;
});
} }
const {data, isLoading, reFetch} = useFetch();
return ( return (
<SafeAreaView style={{flex: 1, backgroundColor: backgroundColor}}> <SafeAreaView style={styles.safeAreaViewStyle}>
{plusShow && <Plus onPress={()=>{ {plusShow && <Plus onPress={()=>{
// router.push("/(tabs)/home/addItem"); // router.push("/(tabs)/home/addItem");
newExpense("Test Title", "3b33b8ac-5fc1-43e5-81fc-cf61628861f7", "69.69.1234", 100).then(() => {
executeQuery("SELECT guid FROM category").then((result) => { reFetch();
if(result === undefined) return;
addExpense("Random Expense", result[0]["guid"], "12.08.2023", 1).then(() => {
update();
});
}).catch((error) => {
throw error;
}); });
}}/>}
}}/>} {isLoading && <LoadingSymbol></LoadingSymbol>}
<FlatList <FlatList
data={data} data={data}
ListHeaderComponent={ ListHeaderComponent={
@ -89,7 +84,6 @@ export default function Page() {
scrollEventThrottle={20} scrollEventThrottle={20}
> >
</FlatList> </FlatList>
</SafeAreaView> </SafeAreaView>
); );
} }

View file

@ -1,10 +1,10 @@
import { View, Text, SafeAreaView, TextInput, Button} from 'react-native'
import React, { useEffect, useState } from 'react'
import { useAuth } from './contexts/AuthContext'
import { Redirect } from 'expo-router';
import { FontAwesome } from "@expo/vector-icons"; import { FontAwesome } from "@expo/vector-icons";
import { Redirect } from 'expo-router';
import React, { useState } from 'react';
import { Button, SafeAreaView } from 'react-native';
import { Input } from '../components'; import { Input } from '../components';
import { useThemeColor } from '../hooks/hooks'; import { useThemeColor } from '../hooks/useThemeColor';
import { useAuth } from './contexts/AuthContext';
export default function login() { export default function login() {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");

View file

@ -1,8 +1,7 @@
import { View, Text, StyleSheet, Platform } from 'react-native'
import React from 'react' import React from 'react'
import { Platform, StyleSheet, View } from 'react-native'
import { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes' import { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes'
import { useThemeColor } from '../../../hooks/hooks' import { useThemeColor } from '../../../hooks/useThemeColor'
import { SHADOWS } from '../../../constants/theme'
function generateBoxShadowStyle( function generateBoxShadowStyle(
xOffset: number, xOffset: number,

View file

@ -0,0 +1,23 @@
import { StyleSheet, View } from "react-native";
const LoadingSymbol = () => {
const color = ["blue", "red", "purple", "green", "yellow", "orange"];
const random = Math.floor(Math.random() * color.length);
const styles = StyleSheet.create({
container: {
backgroundColor: color[random],
width: "100%",
height: "100%",
position: "absolute",
}
});
return (
<View style={styles.container}></View>
);
}
export default LoadingSymbol;

View file

@ -1,7 +1,7 @@
import { AntDesign } from '@expo/vector-icons' import { AntDesign } from '@expo/vector-icons'
import React from 'react' import React from 'react'
import { StyleSheet, TouchableOpacity, ViewProps } from 'react-native' import { StyleSheet, TouchableOpacity, ViewProps } from 'react-native'
import { useThemeColor } from '../../../hooks/hooks' import { useThemeColor } from '../../../hooks/useThemeColor'
type PlusProps = ViewProps & {onPress? : ()=> void | undefined} type PlusProps = ViewProps & {onPress? : ()=> void | undefined}

View file

@ -1,8 +1,8 @@
import { View, Text, ViewProps, StyleSheet, Button, TouchableOpacity, TextInput } from 'react-native'
import React from 'react'
import { AntDesign } from '@expo/vector-icons'; import { AntDesign } from '@expo/vector-icons';
import { useThemeColor } from '../../../hooks/hooks'; import React from 'react';
import { StyleSheet, TextInput, TouchableOpacity, View, ViewProps } from 'react-native';
import { SIZES } from '../../../constants/theme'; import { SIZES } from '../../../constants/theme';
import { useThemeColor } from '../../../hooks/useThemeColor';
type SearchBarProps = {placeholder: string} & ViewProps type SearchBarProps = {placeholder: string} & ViewProps

View file

@ -1,8 +1,8 @@
import { View, Text, ViewProps, Image, GestureResponderEvent } from 'react-native'
import React from 'react' import React from 'react'
import { MARGINS, SIZES, SHADOWS } from '../../../constants/theme' import { Image, Text, View, ViewProps } from 'react-native'
import { TouchableOpacity } from 'react-native-gesture-handler' import { TouchableOpacity } from 'react-native-gesture-handler'
import { useThemeColor } from '../../../hooks/hooks' import { MARGINS, SIZES } from '../../../constants/theme'
import { useThemeColor } from '../../../hooks/useThemeColor'
type WelcomeProps = ViewProps & {name: string, image : any, onPress: () => void | undefined} type WelcomeProps = ViewProps & {name: string, image : any, onPress: () => void | undefined}

View file

@ -1,8 +1,8 @@
import { View, Text, StyleSheet, ColorValue } from 'react-native' import React from 'react';
import React from 'react' import { ColorValue, StyleSheet, Text, View } from 'react-native';
import { useThemeColor } from '../../../hooks/hooks';
import CustomCard from "../../common/customCard/CustomCard";
import { SIZES } from '../../../constants/theme'; import { SIZES } from '../../../constants/theme';
import { useThemeColor } from '../../../hooks/useThemeColor';
import CustomCard from "../../common/customCard/CustomCard";
export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: string, value : string} export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: string, value : string}
export default function ExpenseItem(itemProps : ExpenseItemProps) { export default function ExpenseItem(itemProps : ExpenseItemProps) {

View file

@ -3,6 +3,7 @@ import ExpenseItem from "./home/expenseItem/expenseItem"
import Welcome from "./home/Welcome/Welcome" import Welcome from "./home/Welcome/Welcome"
//common //common
import LoadingSymbol from "./common/loadingSymbol/loadingSymbol"
import Plus from "./common/plus/plus" import Plus from "./common/plus/plus"
import SearchBar from "./common/searchBar/SearchBar" import SearchBar from "./common/searchBar/SearchBar"
@ -11,10 +12,8 @@ import SearchBar from "./common/searchBar/SearchBar"
import Input from "./login/input" import Input from "./login/input"
export { export {
ExpenseItem, ExpenseItem, Input,
Welcome, LoadingSymbol, Plus,
Plus, SearchBar, Welcome
SearchBar,
Input,
} }

29
hooks/useFetch.ts Normal file
View file

@ -0,0 +1,29 @@
import { useEffect, useState } from "react";
import { executeQuery } from "../services/database";
const useFetch = () => {
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState<{[column: string]: any;}[]>([]);
const reFetch = () => {
setIsLoading(true);
executeQuery("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;").then((result) => {
if("rows" in result[0]) {
setData(result[0]["rows"]);
}
}).catch((error: any) => {
console.error("Fetching data from database has failed: ", error);
}).finally(() => {
setIsLoading(false);
});
}
useEffect(() => {
reFetch();
}, [])
return {data, isLoading, reFetch};
}
export default useFetch;

View file

@ -1,7 +1,11 @@
import { useColorScheme } from "react-native"; import { useColorScheme } from "react-native";
import colors from "../constants/colors" import colors from "../constants/colors";
export function useThemeColor(colorName: keyof typeof colors.light & keyof typeof colors.dark): string { export function useThemeColor(colorName: keyof typeof colors.light & keyof typeof colors.dark): string {
const theme = useColorScheme() ?? "light"; const theme = useColorScheme() ?? "light";
return colors[theme][colorName]; return colors[theme][colorName];
}
export function useUpdateData() {
} }

View file

@ -85,15 +85,10 @@ export const deleteExpense = async (guid: string) => {
} }
export const executeQuery = async (query: string) => { export const executeQuery = async (query: string) => {
const sqliteQuary: Query[] = [{sql: query, args: []}]; const sqliteQuary: Query[] = [{sql: query, args: []}];
const result = await runQuery(sqliteQuary); const result = await db.execAsync(sqliteQuary, true);
return result;
if("rows" in result[0]) { }
return result[0]["rows"];
}
console.error("Query could not be executed!");
};
export const deleteExpenses = async () => { export const deleteExpenses = async () => {
try { try {
@ -119,15 +114,6 @@ export const deleteDatabase = () => {
console.log("Database Deleted!") console.log("Database Deleted!")
} }
const runQuery = async (query: Query[]) => {
try {
return await db.execAsync(query, true);
} catch (error) {
console.log("Error running Quary: ", error);
throw(error);
}
};
const closeDatabase = async () => { const closeDatabase = async () => {
try { try {
db.closeAsync(); db.closeAsync();