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:
commit
c63bb4a876
13 changed files with 124 additions and 89 deletions
|
|
@ -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={
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -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("");
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
23
components/common/loadingSymbol/loadingSymbol.tsx
Normal file
23
components/common/loadingSymbol/loadingSymbol.tsx
Normal 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;
|
||||||
|
|
@ -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}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
29
hooks/useFetch.ts
Normal 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;
|
||||||
|
|
@ -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() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
Reference in a new issue