From ab2a03ba0b56a5c5ffcc2963cd34ca4c4eb5ddb1 Mon Sep 17 00:00:00 2001 From: jastornig Date: Tue, 19 Dec 2023 17:56:06 +0000 Subject: [PATCH 01/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b0ffa0e..afc45b0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,9 +56,9 @@ release_job: - job: build_job rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - when: manual + when: on_success - if: $CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED - when: manual + when: on_success release: tag_name: 'v0.$CI_PIPELINE_IID' description: 'v0.$CI_PIPELINE_IID create by pipeline' From 5a9b76a3ff9cd2281aefb68067767f92ad39b9d2 Mon Sep 17 00:00:00 2001 From: Thomas Schleicher Date: Wed, 20 Dec 2023 18:36:43 +0000 Subject: [PATCH 02/72] Resolve "Budget" --- app/(tabs)/_layout.tsx | 5 +- app/(tabs)/budget/index.tsx | 47 ++++++++++--------- app/(tabs)/home/index.tsx | 4 +- app/(tabs)/stats/index.tsx | 27 ++++++++++- .../common/{customCard => }/CustomCard.tsx | 2 +- .../common/{searchBar => }/SearchBar.tsx | 4 +- .../{loadingSymbol => }/loadingSymbol.tsx | 0 components/common/{plus => }/plus.tsx | 2 +- components/home/expenseItem/expenseItem.tsx | 2 +- 9 files changed, 60 insertions(+), 33 deletions(-) rename components/common/{customCard => }/CustomCard.tsx (95%) rename components/common/{searchBar => }/SearchBar.tsx (94%) rename components/common/{loadingSymbol => }/loadingSymbol.tsx (100%) rename components/common/{plus => }/plus.tsx (93%) diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index ab3ebb5..616b2d3 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -16,10 +16,11 @@ export default function Layout() { const styles = StyleSheet.create({ sceneContainer: { - backgroundColor: useThemeColor("backgroundColor"), + backgroundColor: useThemeColor("containerColor"), }, tabBar: { backgroundColor: useThemeColor("backgroundColor"), + borderTopColor: useThemeColor("backgroundColor"), } }); @@ -42,6 +43,7 @@ export default function Layout() { tabBarLabel: "Budget", tabBarIcon: ({size, color}) => ( ), + unmountOnBlur: true, } }/> ( ), + unmountOnBlur: true, } }/> diff --git a/app/(tabs)/budget/index.tsx b/app/(tabs)/budget/index.tsx index 275373d..62b3162 100644 --- a/app/(tabs)/budget/index.tsx +++ b/app/(tabs)/budget/index.tsx @@ -1,27 +1,28 @@ -import { StyleSheet, Text, View } from 'react-native'; -import { deleteExpenses } from '../../../services/database'; +import { SafeAreaView, StyleSheet, Switch, Text } from 'react-native'; export default function Page() { - return ( - - { - deleteExpenses().then(() => { - console.log("Expenses Deleted!"); - }) - }}>Reset Expenses - ); -} + const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + switch: { + transform: [{ scaleX: 1.3 }, { scaleY: 1.3 }], + }, + text: { + color: "red", + fontSize: 40, + } + }); -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - text: { - textAlign: 'center', - fontSize: 40, - color: "red", - } -}); \ No newline at end of file + // const {data, isLoading, reFetch} = useFetch(); + + return ( + + Hallo wo bin ich?! + + + ); +} \ No newline at end of file diff --git a/app/(tabs)/home/index.tsx b/app/(tabs)/home/index.tsx index 132c2cf..aa1ef7e 100644 --- a/app/(tabs)/home/index.tsx +++ b/app/(tabs)/home/index.tsx @@ -14,8 +14,8 @@ export default function Page() { //Styles const styles = StyleSheet.create({ safeAreaViewStyle: { - flex: 1, - backgroundColor: useThemeColor("backgroundColor") + flex: 1, + backgroundColor: useThemeColor("containerColor"), }, container: { flex: 1, diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/stats/index.tsx index 3efc6c1..85c1636 100644 --- a/app/(tabs)/stats/index.tsx +++ b/app/(tabs)/stats/index.tsx @@ -1,5 +1,28 @@ -import { Text } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; +import { deleteExpenses } from '../../../services/database'; export default function Page() { - return Stats Page; + const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + text: { + textAlign: 'center', + fontSize: 40, + color: "red", + } + }); + + + + return ( + + { + deleteExpenses().then(() => { + console.log("Expenses Deleted!"); + }) + }}>Reset Expenses + ); } diff --git a/components/common/customCard/CustomCard.tsx b/components/common/CustomCard.tsx similarity index 95% rename from components/common/customCard/CustomCard.tsx rename to components/common/CustomCard.tsx index b19eea5..dccc478 100644 --- a/components/common/customCard/CustomCard.tsx +++ b/components/common/CustomCard.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Platform, StyleSheet, View } from 'react-native' import { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes' -import { useThemeColor } from '../../../hooks/useThemeColor' +import { useThemeColor } from '../../hooks/useThemeColor' function generateBoxShadowStyle( xOffset: number, diff --git a/components/common/searchBar/SearchBar.tsx b/components/common/SearchBar.tsx similarity index 94% rename from components/common/searchBar/SearchBar.tsx rename to components/common/SearchBar.tsx index fe538f6..7b7e01f 100644 --- a/components/common/searchBar/SearchBar.tsx +++ b/components/common/SearchBar.tsx @@ -1,8 +1,8 @@ import { AntDesign } from '@expo/vector-icons'; import React from 'react'; import { StyleSheet, TextInput, TouchableOpacity, View, ViewProps } from 'react-native'; -import { SIZES } from '../../../constants/theme'; -import { useThemeColor } from '../../../hooks/useThemeColor'; +import { SIZES } from '../../constants/theme'; +import { useThemeColor } from '../../hooks/useThemeColor'; type SearchBarProps = {placeholder: string} & ViewProps diff --git a/components/common/loadingSymbol/loadingSymbol.tsx b/components/common/loadingSymbol.tsx similarity index 100% rename from components/common/loadingSymbol/loadingSymbol.tsx rename to components/common/loadingSymbol.tsx diff --git a/components/common/plus/plus.tsx b/components/common/plus.tsx similarity index 93% rename from components/common/plus/plus.tsx rename to components/common/plus.tsx index 8ae1b15..374dad8 100644 --- a/components/common/plus/plus.tsx +++ b/components/common/plus.tsx @@ -1,7 +1,7 @@ import { AntDesign } from '@expo/vector-icons' import React from 'react' import { StyleSheet, TouchableOpacity, ViewProps } from 'react-native' -import { useThemeColor } from '../../../hooks/useThemeColor' +import { useThemeColor } from '../../hooks/useThemeColor' type PlusProps = ViewProps & {onPress? : ()=> void | undefined} diff --git a/components/home/expenseItem/expenseItem.tsx b/components/home/expenseItem/expenseItem.tsx index b103132..81ce109 100644 --- a/components/home/expenseItem/expenseItem.tsx +++ b/components/home/expenseItem/expenseItem.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { ColorValue, StyleSheet, Text, View } from 'react-native'; import { SIZES } from '../../../constants/theme'; import { useThemeColor } from '../../../hooks/useThemeColor'; -import CustomCard from "../../common/customCard/CustomCard"; +import CustomCard from "../../common/CustomCard"; export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: string, value : string} export default function ExpenseItem(itemProps : ExpenseItemProps) { From c2d4531e82f2b74c4c7ed8fc8c81c5e729fe2405 Mon Sep 17 00:00:00 2001 From: jastornig Date: Wed, 20 Dec 2023 22:14:24 +0000 Subject: [PATCH 03/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index afc45b0..b868c5b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,9 +25,7 @@ build_job: image: "reactnativecommunity/react-native-android:latest" stage: build # This job runs in the build stage, which runs first. rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - when: manual - - if: $CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push" when: manual script: - echo BUILD_JOB_ID=$CI_JOB_ID >> environment.env @@ -55,9 +53,7 @@ release_job: needs: - job: build_job rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - when: on_success - - if: $CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push" when: on_success release: tag_name: 'v0.$CI_PIPELINE_IID' From 76de109ac7937cbfa4394a6f39984cf913234ec6 Mon Sep 17 00:00:00 2001 From: jastornig Date: Wed, 20 Dec 2023 22:18:04 +0000 Subject: [PATCH 04/72] Resolve "typo in readme (also test pipeline behaviour)" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b0f9a14..b5967dd 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Required: `npm` (node packet manager), `node.js` (recommended latest stable) 5. Alternatively you can use an Emulator using Android SDK or the iOS equivalent. After starting the server, press the prefered Button `a` for Android, `i` for iOS or `w` for Web-View **(Web-View is currently not supported) ** ### Prebuilt apk (Android only) -A more convenient way to run Finanzfuchs is to Download the prebuilt apk which can be found under releases in this GitLab repository. This apk file will be kept up to date. Just download and isntall the most recent version. +A more convenient way to run Finanzfuchs is to download the prebuilt apk which can be found under releases in this GitLab repository. This apk file will be kept up to date. Just download and install the most recent version. ## Usage From b782945ec2457fb1faeb3edc7e50f3f5c1bd646e Mon Sep 17 00:00:00 2001 From: jastornig Date: Wed, 20 Dec 2023 23:51:07 +0000 Subject: [PATCH 05/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b868c5b..0b5a8ce 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,13 +17,29 @@ # https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml -stages: # List of stages for jobs, and their order of execution +stages: + - lint # List of stages for jobs, and their order of execution - build - release +lint_job: + stage: lint + image: node + script: + - npm install + - npm i --save-dev @types/url-parse + - npx tsc + cache: + policy: pull-push + when: on_success + paths: + - "node_modules/" + build_job: image: "reactnativecommunity/react-native-android:latest" stage: build # This job runs in the build stage, which runs first. + needs: + - job: lint_job rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push" when: manual From bc7a08bcea9b5cb9f0d8099ac241ca835a4a55a5 Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Wed, 20 Dec 2023 22:52:17 +0100 Subject: [PATCH 06/72] feat: dataclass for commonly used date formats --- util/SimpleDate.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 util/SimpleDate.ts diff --git a/util/SimpleDate.ts b/util/SimpleDate.ts new file mode 100644 index 0000000..e88d4c9 --- /dev/null +++ b/util/SimpleDate.ts @@ -0,0 +1,40 @@ +type DateParts = {day: string; month: string; year: string; hour: string; minute: string; second: string} +export class SimpleDate{ + private date:Date; + public parts: DateParts; + + + + constructor(date?: Date | undefined){ + this.date = date ?? new Date() + this.parts = { + day: String(this.date.getDate()).padStart(2,"0"), + month: String(this.date.getMonth() + 1).padStart(2,"0"), + year: String(this.date.getFullYear()), + hour: String(this.date.getHours()).padStart(2, "0"), + minute: String(this.date.getMinutes()).padStart(2,"0"), + second: String(this.date.getSeconds()).padStart(2, "0"), + } + } + + public getDate(){ + return this.date + } + + public static now(){ + return new this() + } + + public format(formatString: "YYYY-MM-DD" | "DD.MM.YYYY"){ + switch(formatString){ + case "YYYY-MM-DD": + return `${this.parts.year}-${this.parts.month}-${this.parts.day}` + case "DD.MM.YYYY": + return `${this.parts.day}.${this.parts.month}.${this.parts.year}` + } + } + + public toISOString(){ + return `${this.parts.year}-${this.parts.month}-${this.parts.day} ${this.parts.hour}:${this.parts.minute}:${this.parts.second}` + } +} \ No newline at end of file From d64db4d3167c564bab3cf9a38297a99128b1be4a Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Wed, 20 Dec 2023 22:52:57 +0100 Subject: [PATCH 07/72] dev: populate db with random data --- services/database.ts | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/services/database.ts b/services/database.ts index 386ee09..ec1528b 100644 --- a/services/database.ts +++ b/services/database.ts @@ -4,6 +4,7 @@ import * as SQLite from "expo-sqlite"; import uuid from "react-native-uuid"; import { Query } from "expo-sqlite"; +import { SimpleDate } from "../util/SimpleDate"; const db = SQLite.openDatabase("interactive_systeme.db"); @@ -87,6 +88,9 @@ export const deleteExpense = async (guid: string) => { export const executeQuery = async (query: string) => { const sqliteQuary: Query[] = [{sql: query, args: []}]; const result = await db.execAsync(sqliteQuary, true); + if("error" in result[0]){ + throw result[0].error + } return result; } @@ -101,6 +105,18 @@ export const deleteExpenses = async () => { } } +export const deleteCategories = async () => { + try { + await db.transactionAsync(async (tx: SQLite.SQLTransactionAsync) => { + await tx.executeSqlAsync("DELETE FROM category;"); + }); + console.log("deleteCategories") + } catch(error) { + console.log("Error deleting category: ", error); + throw error; + } +} + export const deleteDatabase = () => { closeDatabase().then(() => { try { @@ -122,3 +138,37 @@ const closeDatabase = async () => { throw error; } } + + +export const DEV_populateDatabase = async () => { + try { + await deleteCategories(); + await deleteExpenses() + + const colors = ["red", "blue", "green", "purple", "yellow"] + + for(let i=0; i < 5; i++){ + let random = Math.floor(Math.random() * colors.length); + await addCategory(`Category ${i}`, colors[random], "budget") + } + const result = await executeQuery("SELECT * from category") + let categories: {[column: string]: any}[]; + if("rows" in result[0]){ + categories = result[0]["rows"] + }else{ + throw new Error("WHATHEFUCK") + } + console.log(categories) + + let date = new Date() + for(let i = 0; i < 15; i++){ + let random = Math.floor(Math.random() * categories.length); + let randomDay = Math.floor(Math.random() * 20) + date.setDate(randomDay) + let string = new SimpleDate(date).toISOString() + await addExpense(`Expense ${i}`, categories[random].guid, string, 15) + } + } catch(e){ + console.error(e) + } +} From 37b38d33fcfa304568e5373bf5f8ea9e08852ce6 Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Wed, 20 Dec 2023 22:54:06 +0100 Subject: [PATCH 08/72] maintainance: applied new Theme context to component --- components/home/Welcome/Welcome.tsx | 4 +++- components/index.tsx | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/components/home/Welcome/Welcome.tsx b/components/home/Welcome/Welcome.tsx index 265cce4..41a52fa 100644 --- a/components/home/Welcome/Welcome.tsx +++ b/components/home/Welcome/Welcome.tsx @@ -3,6 +3,7 @@ import { Image, Text, View, ViewProps } from 'react-native' import { TouchableOpacity } from 'react-native-gesture-handler' import { MARGINS, SIZES } from '../../../constants/theme' import { useThemeColor } from '../../../hooks/useThemeColor' +import { useTheme } from '../../../app/contexts/ThemeContext' type WelcomeProps = ViewProps & {name: string, image : any, onPress: () => void | undefined} @@ -28,12 +29,13 @@ function getTimeOfDay(date: Date) : string { export default function Welcome(props: WelcomeProps) { + const {colors} = useTheme() const date = new Date() const dateString = formatDate(date) const timeOfDay = getTimeOfDay(date) const onpress = props.onPress - const textcolor = useThemeColor("primaryText") + const textcolor = colors.primaryText //const backgroundColor: string = useThemeColor("backgroundColor") return ( diff --git a/components/index.tsx b/components/index.tsx index 7d697da..61d8ef7 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -4,10 +4,10 @@ import Welcome from "./home/Welcome/Welcome" import { ToggleSetting, ButtonSetting } from "./home/userSettings/Setting" //common -import LoadingSymbol from "./common/loadingSymbol/loadingSymbol" -import Plus from "./common/plus/plus" -import SearchBar from "./common/searchBar/SearchBar" -import CustomCard from "./common/customCard/CustomCard" +import LoadingSymbol from "./common/loadingSymbol" +import Plus from "./common/plus" +import SearchBar from "./common/SearchBar" +import CustomCard from "./common/CustomCard" //login From 65c32a9298e7c318c61f7373feb06196d3f3f4fd Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Wed, 20 Dec 2023 22:55:19 +0100 Subject: [PATCH 09/72] feat: basic structure for calendar --- app/(tabs)/home/index.tsx | 23 +++++++-- app/(tabs)/home/userSettings.tsx | 8 ++- components/home/expenseItem/expenseItem.tsx | 14 ++++-- package-lock.json | 56 +++++++++++++++++++++ package.json | 5 +- 5 files changed, 94 insertions(+), 12 deletions(-) diff --git a/app/(tabs)/home/index.tsx b/app/(tabs)/home/index.tsx index aa1ef7e..95a4f54 100644 --- a/app/(tabs)/home/index.tsx +++ b/app/(tabs)/home/index.tsx @@ -1,5 +1,6 @@ import React, { useRef, useState } from 'react'; import { NativeScrollEvent, NativeSyntheticEvent, StyleSheet, View } from 'react-native'; +import { Calendar } from 'react-native-calendars'; import { FlatList } from 'react-native-gesture-handler'; import { SafeAreaView } from 'react-native-safe-area-context'; import { ExpenseItem, LoadingSymbol, Plus, SearchBar, Welcome } from '../../../components'; @@ -8,14 +9,17 @@ import { useThemeColor } from "../../../hooks/useThemeColor"; import { addExpense } from "../../../services/database"; import { useAuth } from '../../contexts/AuthContext'; import { useRouter } from "expo-router"; +import { SimpleDate } from '../../../util/SimpleDate'; +import { useTheme } from '../../contexts/ThemeContext'; export default function Page() { + const { colors, theme } = useTheme() //Styles const styles = StyleSheet.create({ safeAreaViewStyle: { - flex: 1, - backgroundColor: useThemeColor("containerColor"), + flex: 1, + backgroundColor: colors.backgroundColor }, container: { flex: 1, @@ -23,7 +27,7 @@ export default function Page() { justifyContent: "center", }, text: { - color: useThemeColor("primaryText"), + color: colors.primaryText, fontSize: 70, fontWeight: "bold" }, @@ -33,7 +37,6 @@ export default function Page() { }); const router = useRouter(); - const {onLogout} = useAuth(); const [plusShow, setPlusShow] = useState(true); const prevOffset = useRef(0); @@ -75,7 +78,17 @@ export default function Page() { data={data} ListHeaderComponent={ <> - {router.push("/home/userSettings")}}> + {router.push("/home/userSettings")}}/> + + + } diff --git a/app/(tabs)/home/userSettings.tsx b/app/(tabs)/home/userSettings.tsx index ec7a8d4..770892b 100644 --- a/app/(tabs)/home/userSettings.tsx +++ b/app/(tabs)/home/userSettings.tsx @@ -5,7 +5,7 @@ import { SIZES } from '../../../constants/theme' import { SafeAreaView } from 'react-native-safe-area-context' import { ButtonSetting, ToggleSetting } from '../../../components' import { useTheme } from '../../contexts/ThemeContext' -import { deleteExpenses } from '../../../services/database' +import { deleteExpenses, DEV_populateDatabase } from '../../../services/database' import { useAuth } from '../../contexts/AuthContext' import { TouchableOpacity } from 'react-native-gesture-handler' @@ -61,6 +61,12 @@ export default function userSettings() { console.log("Expenses Deleted!"); })}} /> + { + const del = async () => { + await DEV_populateDatabase() + } + del() + }}/> diff --git a/components/home/expenseItem/expenseItem.tsx b/components/home/expenseItem/expenseItem.tsx index 81ce109..baa805e 100644 --- a/components/home/expenseItem/expenseItem.tsx +++ b/components/home/expenseItem/expenseItem.tsx @@ -2,12 +2,18 @@ import React from 'react'; import { ColorValue, StyleSheet, Text, View } from 'react-native'; import { SIZES } from '../../../constants/theme'; import { useThemeColor } from '../../../hooks/useThemeColor'; +import { useTheme } from '../../../app/contexts/ThemeContext'; import CustomCard from "../../common/CustomCard"; +import { SimpleDate } from '../../../util/SimpleDate'; -export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: string, value : string} +type ISOdateString = string + +export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: ISOdateString, value : string} export default function ExpenseItem(itemProps : ExpenseItemProps) { - const textColor = useThemeColor("primaryText"); - const backgroundColor = useThemeColor("backgroundColor") + const {colors} = useTheme() + const textColor = colors.primaryText + const backgroundColor = colors.containerColor + const date: SimpleDate = new SimpleDate(new Date(itemProps.date)) return ( @@ -23,7 +29,7 @@ export default function ExpenseItem(itemProps : ExpenseItemProps) { {itemProps.date} + }} numberOfLines={1}>{date.format("DD.MM.YYYY")} = 4" } }, + "node_modules/recyclerlistview": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/recyclerlistview/-/recyclerlistview-4.2.0.tgz", + "integrity": "sha512-uuBCi0c+ggqHKwrzPX4Z/mJOzsBbjZEAwGGmlwpD/sD7raXixdAbdJ6BTcAmuWG50Cg4ru9p12M94Njwhr/27A==", + "dependencies": { + "lodash.debounce": "4.0.8", + "prop-types": "15.8.1", + "ts-object-utils": "0.0.5" + }, + "peerDependencies": { + "react": ">= 15.2.1", + "react-native": ">= 0.30.0" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -17078,6 +17124,11 @@ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, + "node_modules/ts-object-utils": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ts-object-utils/-/ts-object-utils-0.0.5.tgz", + "integrity": "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==" + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -17626,6 +17677,11 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/xdate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/xdate/-/xdate-0.8.2.tgz", + "integrity": "sha512-sNBlLfOC8S3V0vLDEUianQOXcTsc9j4lfeKU/klHe0RjHAYn0CXsSttumTot8dzalboV8gZbH38B+WcCIBjhFQ==" + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", diff --git a/package.json b/package.json index a625b05..423ac06 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "version": "1.0.0", "scripts": { "start": "expo start", - "android": "expo start --android", - "ios": "expo start --ios", + "android": "expo run:android", + "ios": "expo run:ios", "web": "expo start --web", "test": "jest --watchAll", "start tunnel": "expo start --tunnel", @@ -34,6 +34,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.72.6", + "react-native-calendars": "^1.1303.0", "react-native-gesture-handler": "~2.12.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", From 14d9755b937868f1667fb61e824275a82c5f7548 Mon Sep 17 00:00:00 2001 From: jastornig Date: Wed, 20 Dec 2023 23:25:21 +0000 Subject: [PATCH 10/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0b5a8ce..4d78c17 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,7 +27,6 @@ lint_job: image: node script: - npm install - - npm i --save-dev @types/url-parse - npx tsc cache: policy: pull-push From 7a2cecc96cfe55e837ce11d64afe993985eff35c Mon Sep 17 00:00:00 2001 From: jastornig Date: Wed, 20 Dec 2023 23:40:19 +0000 Subject: [PATCH 11/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4d78c17..3a86181 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,6 +27,7 @@ lint_job: image: node script: - npm install + - npm i --save-dev @types/url-parse - npx tsc cache: policy: pull-push @@ -68,7 +69,7 @@ release_job: needs: - job: build_job rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push" + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push" when: on_success release: tag_name: 'v0.$CI_PIPELINE_IID' From 891f151f36bec79c6c5badb1758df16671520de2 Mon Sep 17 00:00:00 2001 From: jastornig Date: Wed, 20 Dec 2023 23:57:53 +0000 Subject: [PATCH 12/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3a86181..78c6a22 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,6 +25,9 @@ stages: lint_job: stage: lint image: node + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH script: - npm install - npm i --save-dev @types/url-parse From ab6ec44900fa961b2bb87b518c9bdc77e7a6705e Mon Sep 17 00:00:00 2001 From: jastornig Date: Thu, 21 Dec 2023 00:01:00 +0000 Subject: [PATCH 13/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 78c6a22..9a4902c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,6 +27,7 @@ lint_job: image: node rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" + when: manual - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH script: - npm install From cc96a6e6064cbf6b1083d402171f486c37c874c2 Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Thu, 21 Dec 2023 15:19:28 +0100 Subject: [PATCH 14/72] feat: heatmap --- app/(tabs)/home/index.tsx | 35 +++- package-lock.json | 384 ++++++++++++++++++++------------------ package.json | 1 + tsconfig.json | 4 + 4 files changed, 236 insertions(+), 188 deletions(-) diff --git a/app/(tabs)/home/index.tsx b/app/(tabs)/home/index.tsx index 95a4f54..033f573 100644 --- a/app/(tabs)/home/index.tsx +++ b/app/(tabs)/home/index.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from 'react'; +import React, { useRef, useState, useMemo } from 'react'; import { NativeScrollEvent, NativeSyntheticEvent, StyleSheet, View } from 'react-native'; import { Calendar } from 'react-native-calendars'; import { FlatList } from 'react-native-gesture-handler'; @@ -12,6 +12,29 @@ import { useRouter } from "expo-router"; import { SimpleDate } from '../../../util/SimpleDate'; import { useTheme } from '../../contexts/ThemeContext'; + +interface MarkingProps { + dots?:{color:string, selectedColor?:string, key?:string}[]; +} + +type MarkedDates = { + [key: string]: MarkingProps; +} + +const constructMarkedDates = (data : {[column: string]: any}) => { + console.log("entered") + let markedDates: MarkedDates = {}; + data.forEach((value: any) => { + const dateKey: string = String(value["expense_datetime"]).split(" ")[0] + + if(markedDates[dateKey] === undefined){ + markedDates[dateKey] = {dots: []} + } + markedDates[dateKey].dots?.push({color: value["category_color"]}) + }) + return markedDates; +} + export default function Page() { const { colors, theme } = useTheme() @@ -61,6 +84,10 @@ export default function Page() { const {data, isLoading, reFetch} = useFetch(); + const expenseDates = useMemo(()=> + constructMarkedDates(data) + , [data]) + return ( @@ -86,7 +113,11 @@ export default function Page() { calendarBackground: colors.containerColor, arrowColor: colors.accentColor, monthTextColor: colors.accentColor - }}> + + }} + markingType='multi-dot' + markedDates={expenseDates} + > diff --git a/package-lock.json b/package-lock.json index 3abab8d..d597893 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "devDependencies": { "@babel/core": "^7.20.0", "@types/react": "~18.2.14", + "@types/url-parse": "^1.4.11", "jest": "^29.2.1", "jest-expo": "~49.0.0", "react-test-renderer": "18.2.0", @@ -53,9 +54,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", - "integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dependencies": { "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" @@ -65,28 +66,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", - "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", - "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", + "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.3", + "@babel/helpers": "^7.23.6", + "@babel/parser": "^7.23.6", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.3", - "@babel/types": "^7.23.3", + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -102,11 +103,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.4.tgz", - "integrity": "sha512-esuS49Cga3HcThFNebGhlgsrVLkvhqvYDTzgjfFFlHJcIfLe5jFmRRfCQ1KuBfc4Jrtn3ndLgKWAKjBE+IraYQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dependencies": { - "@babel/types": "^7.23.4", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -138,13 +139,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -153,16 +154,16 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", - "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.6.tgz", + "integrity": "sha512-cBXU1vZni/CpGF29iTu4YRbOZt3Wat6zCoMDxRF1MayiEc4URxOj31tT65HUM0CRpMowA3HCJaAOVOUnMf96cw==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-replace-supers": "^7.22.20", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "semver": "^6.3.1" @@ -191,9 +192,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", @@ -377,9 +378,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "engines": { "node": ">=6.9.0" } @@ -398,13 +399,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.4.tgz", - "integrity": "sha512-HfcMizYz10cr3h29VqyfGL6ZWIjTwWfvYBMsBVGwpcbhNGe3wQ1ZXZRPzZoAHhd9OqHadHqjQ89iVKINXnbzuw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", + "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.4", - "@babel/types": "^7.23.4" + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6" }, "engines": { "node": ">=6.9.0" @@ -424,9 +425,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "bin": { "parser": "bin/babel-parser.js" }, @@ -514,13 +515,14 @@ } }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.3.tgz", - "integrity": "sha512-u8SwzOcP0DYSsa++nHd/9exlHb0NAlHCb890qtZZbSwPX2bFv8LBEztxwN7Xg/dS8oAFFidhrI9PBcLBJSkGRQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.6.tgz", + "integrity": "sha512-D7Ccq9LfkBFnow3azZGJvZYgcfeqAw3I1e5LoTpj6UKIFQilh8yqXsIGcRIqbBdsPWIz+Ze7ZZfggSj62Qp+Fg==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-create-class-features-plugin": "^7.23.6", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", "@babel/plugin-syntax-decorators": "^7.23.3" }, @@ -1063,9 +1065,9 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.3.tgz", - "integrity": "sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", + "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-compilation-targets": "^7.22.15", @@ -1203,11 +1205,12 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", - "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1626,9 +1629,9 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.4.tgz", - "integrity": "sha512-ITwqpb6V4btwUG0YJR82o2QvmWrLgDnx/p2A3CTPYGaRgULkDiC0DRA2C4jlRB9uXGUEfaSS/IGHfVW+ohzYDw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.6.tgz", + "integrity": "sha512-kF1Zg62aPseQ11orDhFRw+aPG/eynNQtI+TyY+m33qJa2cJ5EEvza2P2BNTIA9E5MyqFABHEyY6CPHwgdy9aNg==", "dependencies": { "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", @@ -1716,12 +1719,12 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.4.tgz", - "integrity": "sha512-39hCCOl+YUAyMOu6B9SmUTiHUU0t/CxJNUmY3qRdJujbqi+lrQcL11ysYUsAvFWPBdhihrv1z0oRG84Yr3dODQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", + "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-create-class-features-plugin": "^7.23.6", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-typescript": "^7.23.3" }, @@ -1792,14 +1795,14 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.3.tgz", - "integrity": "sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.6.tgz", + "integrity": "sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ==", "dependencies": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", + "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", @@ -1823,25 +1826,25 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.4", "@babel/plugin-transform-async-to-generator": "^7.23.3", "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.3", - "@babel/plugin-transform-classes": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.5", "@babel/plugin-transform-computed-properties": "^7.23.3", "@babel/plugin-transform-destructuring": "^7.23.3", "@babel/plugin-transform-dotall-regex": "^7.23.3", "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.3", - "@babel/plugin-transform-for-of": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", "@babel/plugin-transform-member-expression-literals": "^7.23.3", "@babel/plugin-transform-modules-amd": "^7.23.3", "@babel/plugin-transform-modules-commonjs": "^7.23.3", @@ -1849,15 +1852,15 @@ "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.3", - "@babel/plugin-transform-numeric-separator": "^7.23.3", - "@babel/plugin-transform-object-rest-spread": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.3", - "@babel/plugin-transform-optional-chaining": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", "@babel/plugin-transform-parameters": "^7.23.3", "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", "@babel/plugin-transform-property-literals": "^7.23.3", "@babel/plugin-transform-regenerator": "^7.23.3", "@babel/plugin-transform-reserved-words": "^7.23.3", @@ -1984,9 +1987,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.4.tgz", - "integrity": "sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", + "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2008,19 +2011,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.4.tgz", - "integrity": "sha512-IYM8wSUwunWTB6tFC2dkKZhxbIjHoWemdK+3f8/wq8aKhbUscxD5MX72ubd90fxvFknaLPeGw5ycU84V1obHJg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", + "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", "dependencies": { - "@babel/code-frame": "^7.23.4", - "@babel/generator": "^7.23.4", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.4", - "@babel/types": "^7.23.4", - "debug": "^4.1.0", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -2028,9 +2031,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.4.tgz", - "integrity": "sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -3217,9 +3220,9 @@ } }, "node_modules/@expo/xcpretty": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.2.2.tgz", - "integrity": "sha512-Lke/geldJqUV0Dfxg5/QIOugOzdqZ/rQ9yHKSgGbjZtG1uiSqWyFwWvXmrdd3/sIdX33eykGvIcf+OrvvcXVUw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.3.0.tgz", + "integrity": "sha512-whBbvHZ2Q10T5TNmN0z5NbO6C9ZDw+XUTu8h6vVMnMzQrbGexc9oaCCZfz+L3Q7TEL5vfr+9L86nY62c3Bsm+g==", "dependencies": { "@babel/code-frame": "7.10.4", "chalk": "^4.1.0", @@ -6262,13 +6265,16 @@ "integrity": "sha512-Im93xRJuHHxb1wniGhBMsxLwcfzdYreSZVQGDoMJgkd6+Iky61LInGEHnQCTN0fKNYF1Dvcofb4uMmE1RQHXHQ==" }, "node_modules/@react-native/codegen": { - "version": "0.72.7", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.72.7.tgz", - "integrity": "sha512-O7xNcGeXGbY+VoqBGNlZ3O05gxfATlwE1Q1qQf5E38dK+tXn5BY4u0jaQ9DPjfE8pBba8g/BYI1N44lynidMtg==", + "version": "0.72.8", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.72.8.tgz", + "integrity": "sha512-jQCcBlXV7B7ap5VlHhwIPieYz89yiRgwd2FPUBu+unz+kcJ6pAiB2U8RdLDmyIs8fiWd+Vq1xxaWs4TR329/ng==", "dependencies": { "@babel/parser": "^7.20.0", "flow-parser": "^0.206.0", + "glob": "^7.1.1", + "invariant": "^2.2.4", "jscodeshift": "^0.14.0", + "mkdirp": "^0.5.1", "nullthrows": "^1.1.1" }, "peerDependencies": { @@ -6461,9 +6467,9 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz", - "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, "dependencies": { "@babel/types": "^7.0.0" @@ -6540,9 +6546,9 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/node": { - "version": "20.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz", - "integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==", + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", "dependencies": { "undici-types": "~5.26.4" } @@ -6559,9 +6565,9 @@ "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==" }, "node_modules/@types/react": { - "version": "18.2.39", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", - "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", + "version": "18.2.45", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz", + "integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -6586,6 +6592,12 @@ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, + "node_modules/@types/url-parse": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.11.tgz", + "integrity": "sha512-FKvKIqRaykZtd4n47LbK/W/5fhQQ1X7cxxzG9A48h0BGN+S04NH7ervcCjM8tyR0lyGru83FAHSmw2ObgKoESg==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -6683,9 +6695,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", "dev": true, "engines": { "node": ">=0.4.0" @@ -7122,12 +7134,12 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", - "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", + "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.3", + "@babel/helper-define-polyfill-provider": "^0.4.4", "semver": "^6.3.1" }, "peerDependencies": { @@ -7135,11 +7147,11 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz", - "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==", + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", + "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3", + "@babel/helper-define-polyfill-provider": "^0.4.4", "core-js-compat": "^3.33.1" }, "peerDependencies": { @@ -7147,11 +7159,11 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", - "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", + "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3" + "@babel/helper-define-polyfill-provider": "^0.4.4" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -7413,9 +7425,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "funding": [ { "type": "opencollective", @@ -7431,9 +7443,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -7641,9 +7653,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001565", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001565.tgz", - "integrity": "sha512-xrE//a3O7TP0vaJ8ikzkD2c2NgcVUvsEe2IvFTntV4Yd1Z9FVzh+gW+enX96L0psrbaFMcVcH2l90xNuGDWc8w==", + "version": "1.0.30001570", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz", + "integrity": "sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==", "funding": [ { "type": "opencollective", @@ -8008,11 +8020,11 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/core-js-compat": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.3.tgz", - "integrity": "sha512-cNzGqFsh3Ot+529GIXacjTJ7kegdt5fPXxCBVS1G0iaZpuo/tBz399ymceLJveQhFFZ8qThHiP3fzuoQjKN2ow==", + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.34.0.tgz", + "integrity": "sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==", "dependencies": { - "browserslist": "^4.22.1" + "browserslist": "^4.22.2" }, "funding": { "type": "opencollective", @@ -8221,9 +8233,9 @@ "dev": true }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true }, "node_modules/dag-map": { @@ -8520,9 +8532,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.596", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.596.tgz", - "integrity": "sha512-zW3zbZ40Icb2BCWjm47nxwcFGYlIgdXkAx85XDO7cyky9J4QQfq8t0W19/TLZqq3JPQXtlv8BPIGmfa9Jb4scg==" + "version": "1.4.615", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.615.tgz", + "integrity": "sha512-/bKPPcgZVUziECqDc+0HkT87+0zhaWSZHNXqF8FLd2lQcptpmUFwoCSWjCdOng9Gdq+afKArPdEg/0ZW461Eng==" }, "node_modules/emittery": { "version": "0.13.1", @@ -9153,9 +9165,9 @@ } }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", "dependencies": { "reusify": "^1.0.4" } @@ -10180,11 +10192,11 @@ } }, "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/is-plain-object": { @@ -13476,14 +13488,6 @@ "node": ">=10" } }, - "node_modules/merge-options/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "engines": { - "node": ">=8" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -14565,9 +14569,9 @@ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/node-stream-zip": { "version": "1.15.0", @@ -15220,9 +15224,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", "funding": [ { "type": "opencollective", @@ -15238,7 +15242,7 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -15950,9 +15954,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -16488,6 +16492,14 @@ "node": ">=4" } }, + "node_modules/sort-keys/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -16940,9 +16952,9 @@ } }, "node_modules/terser": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", - "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", + "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -17166,9 +17178,9 @@ } }, "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -17529,9 +17541,9 @@ } }, "node_modules/whatwg-fetch": { - "version": "3.6.19", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", - "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==" + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" }, "node_modules/whatwg-mimetype": { "version": "3.0.0", @@ -17638,9 +17650,9 @@ } }, "node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.15.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.15.1.tgz", + "integrity": "sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 423ac06..7f833d1 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "devDependencies": { "@babel/core": "^7.20.0", "@types/react": "~18.2.14", + "@types/url-parse": "^1.4.11", "jest": "^29.2.1", "jest-expo": "~49.0.0", "react-test-renderer": "18.2.0", diff --git a/tsconfig.json b/tsconfig.json index 75edaf8..ac4abe4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,11 +2,15 @@ "extends": "expo/tsconfig.base", "compilerOptions": { "strict": true, + "skipLibCheck": true, }, "include": [ "**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts" + ], + "exclude": [ + "./node_modules/**" ] } From 74577eea7dfb0e9b22a1987446bf37f8089e8619 Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Thu, 21 Dec 2023 15:19:41 +0100 Subject: [PATCH 15/72] uptate ci --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9a4902c..1f7eecd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,7 +31,6 @@ lint_job: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH script: - npm install - - npm i --save-dev @types/url-parse - npx tsc cache: policy: pull-push From 9de26789222134324cad2e7eae781c1623a9b61a Mon Sep 17 00:00:00 2001 From: Jakob Stornig Date: Sat, 30 Dec 2023 14:35:05 +0100 Subject: [PATCH 16/72] useTheme applied globally --- app/(tabs)/_layout.tsx | 18 +++++++----------- app/(tabs)/home/_layout.tsx | 1 - app/(tabs)/home/index.tsx | 1 - app/(tabs)/home/userSettings.tsx | 10 ++-------- app/login.tsx | 9 +++++---- components/common/CustomCard.tsx | 4 ++-- components/common/SearchBar.tsx | 7 ++++--- components/common/plus.tsx | 7 ++++--- components/home/Welcome/Welcome.tsx | 2 -- components/home/expenseItem/expenseItem.tsx | 1 - components/home/userSettings/Setting.tsx | 4 +--- hooks/useThemeColor.ts | 6 ++++++ 12 files changed, 31 insertions(+), 39 deletions(-) diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 616b2d3..6dc7691 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -4,29 +4,25 @@ import { StyleSheet } from "react-native"; import { FontAwesome } from "@expo/vector-icons"; import { Redirect } from "expo-router"; import React, { useEffect } from "react"; -import { useThemeColor } from "../../hooks/useThemeColor"; +import { useTheme } from "../contexts/ThemeContext"; import { useAuth } from "../contexts/AuthContext"; export default function Layout() { - // const selectedColor: string = useThemeColor( "tabIconSelected"); - // const defaultColor: string = useThemeColor("tabIconDefault"); - // const backgroundColor: string = useThemeColor("backgroundColor"); - // const tabBarColor: string = useThemeColor("tabBarColor"); const {authState} = useAuth() - + const {colors} = useTheme() const styles = StyleSheet.create({ sceneContainer: { - backgroundColor: useThemeColor("containerColor"), + backgroundColor: colors.containerColor, }, tabBar: { - backgroundColor: useThemeColor("backgroundColor"), - borderTopColor: useThemeColor("backgroundColor"), + backgroundColor: colors.backgroundColor, + borderTopColor: colors.backgroundColor } }); const screenOptions = { - tabBarActiveTintColor: useThemeColor( "tabIconSelected"), - tabBarInactiveTintColor: useThemeColor("tabIconDefault"), + tabBarActiveTintColor: colors.tabIconSelected, + tabBarInactiveTintColor: colors.tabIconDefault, headerShown: false, tabBarStyle: styles.tabBar, } diff --git a/app/(tabs)/home/_layout.tsx b/app/(tabs)/home/_layout.tsx index 69dc04d..f6c2a2b 100644 --- a/app/(tabs)/home/_layout.tsx +++ b/app/(tabs)/home/_layout.tsx @@ -2,7 +2,6 @@ import { Stack } from "expo-router"; import { View, Text } from 'react-native' import React from 'react' -import { useThemeColor } from "../../../hooks/useThemeColor"; import { useTheme } from "../../contexts/ThemeContext"; diff --git a/app/(tabs)/home/index.tsx b/app/(tabs)/home/index.tsx index 033f573..4a510a1 100644 --- a/app/(tabs)/home/index.tsx +++ b/app/(tabs)/home/index.tsx @@ -5,7 +5,6 @@ import { FlatList } from 'react-native-gesture-handler'; import { SafeAreaView } from 'react-native-safe-area-context'; import { ExpenseItem, LoadingSymbol, Plus, SearchBar, Welcome } from '../../../components'; import useFetch from '../../../hooks/useFetch'; -import { useThemeColor } from "../../../hooks/useThemeColor"; import { addExpense } from "../../../services/database"; import { useAuth } from '../../contexts/AuthContext'; import { useRouter } from "expo-router"; diff --git a/app/(tabs)/home/userSettings.tsx b/app/(tabs)/home/userSettings.tsx index 770892b..c865110 100644 --- a/app/(tabs)/home/userSettings.tsx +++ b/app/(tabs)/home/userSettings.tsx @@ -1,6 +1,5 @@ import { View, Text, StyleSheet, Image, Appearance } from 'react-native' import React, { useState } from 'react' -import { useThemeColor } from '../../../hooks/useThemeColor' import { SIZES } from '../../../constants/theme' import { SafeAreaView } from 'react-native-safe-area-context' import { ButtonSetting, ToggleSetting } from '../../../components' @@ -9,18 +8,13 @@ import { deleteExpenses, DEV_populateDatabase } from '../../../services/database import { useAuth } from '../../contexts/AuthContext' import { TouchableOpacity } from 'react-native-gesture-handler' -const generateStyles = (): void => { - styles.text = { - color: useThemeColor('primaryText') - } -} export default function userSettings() { const {onLogout} = useAuth(); const {theme, colors, isSystemTheme, applyTheme, applySystemTheme} = useTheme(); - const backgroundColor = useThemeColor("backgroundColor"); - styles.text = {...styles.text, color: useThemeColor("primaryText")} + const backgroundColor = colors.backgroundColor + styles.text = {...styles.text, color: colors.primaryText} const [systemTheme, setSystemTheme] = useState(isSystemTheme!) const [darkMode, setDarkMode] = useState(theme === "dark" ? true : false) diff --git a/app/login.tsx b/app/login.tsx index 35a67e7..0712046 100644 --- a/app/login.tsx +++ b/app/login.tsx @@ -3,16 +3,17 @@ import { Redirect } from 'expo-router'; import React, { useState } from 'react'; import { Button, SafeAreaView } from 'react-native'; import { Input } from '../components'; -import { useThemeColor } from '../hooks/useThemeColor'; +import { useTheme } from "./contexts/ThemeContext"; import { useAuth } from './contexts/AuthContext'; export default function login() { const [email, setEmail] = useState(""); const [password, setPassword] = useState("") const {authState, onLogin} = useAuth() - const backgroundColor = useThemeColor("backgroundColor") - const textColor = useThemeColor("primaryText"); - const elementDefaultColor = useThemeColor("elementDefaultColor") + const {colors} = useTheme() + const backgroundColor = colors.backgroundColor; + const textColor = colors.primaryText + const elementDefaultColor = colors.elementDefaultColor // const {authState, onLogin} = useAuth(); diff --git a/components/common/CustomCard.tsx b/components/common/CustomCard.tsx index dccc478..d81020e 100644 --- a/components/common/CustomCard.tsx +++ b/components/common/CustomCard.tsx @@ -1,7 +1,7 @@ import React from 'react' import { Platform, StyleSheet, View } from 'react-native' import { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes' -import { useThemeColor } from '../../hooks/useThemeColor' +import { useTheme } from '../../app/contexts/ThemeContext' function generateBoxShadowStyle( xOffset: number, @@ -18,7 +18,7 @@ function generateBoxShadowStyle( shadowOffset : {width: xOffset, height: yOffset}, shadowOpacity, shadowRadius, - backgroundColor: useThemeColor("backgroundColor") + backgroundColor: useTheme().colors.backgroundColor } }else if (Platform.OS === 'android'){ styles.boxShadow = { diff --git a/components/common/SearchBar.tsx b/components/common/SearchBar.tsx index 7b7e01f..372d052 100644 --- a/components/common/SearchBar.tsx +++ b/components/common/SearchBar.tsx @@ -2,15 +2,16 @@ import { AntDesign } from '@expo/vector-icons'; import React from 'react'; import { StyleSheet, TextInput, TouchableOpacity, View, ViewProps } from 'react-native'; import { SIZES } from '../../constants/theme'; -import { useThemeColor } from '../../hooks/useThemeColor'; +import { useTheme } from '../../app/contexts/ThemeContext'; type SearchBarProps = {placeholder: string} & ViewProps export default function SearchBar(props: SearchBarProps) { const [isActive, setIsactive] = React.useState(false); + const { colors } = useTheme(); - const textColor = useThemeColor("interactiveText") - const backgroundColor = useThemeColor("elementDefaultColor"); + const textColor = colors + const backgroundColor = colors.elementDefaultColor; const handleChange = (text:string) : void => { if(text !== ""){ diff --git a/components/common/plus.tsx b/components/common/plus.tsx index 374dad8..b10c0a4 100644 --- a/components/common/plus.tsx +++ b/components/common/plus.tsx @@ -1,13 +1,14 @@ import { AntDesign } from '@expo/vector-icons' import React from 'react' import { StyleSheet, TouchableOpacity, ViewProps } from 'react-native' -import { useThemeColor } from '../../hooks/useThemeColor' +import { useTheme } from '../../app/contexts/ThemeContext' type PlusProps = ViewProps & {onPress? : ()=> void | undefined} const Plus = (props : PlusProps) => { - const accentColor = useThemeColor("accentColor"); - const primaryText = useThemeColor("primaryText"); + const {colors} = useTheme() + const accentColor = colors.accentColor; + const primaryText = colors.primaryText; const style = StyleSheet.create({ plus:{ diff --git a/components/home/Welcome/Welcome.tsx b/components/home/Welcome/Welcome.tsx index 41a52fa..4c766b1 100644 --- a/components/home/Welcome/Welcome.tsx +++ b/components/home/Welcome/Welcome.tsx @@ -2,7 +2,6 @@ import React from 'react' import { Image, Text, View, ViewProps } from 'react-native' import { TouchableOpacity } from 'react-native-gesture-handler' import { MARGINS, SIZES } from '../../../constants/theme' -import { useThemeColor } from '../../../hooks/useThemeColor' import { useTheme } from '../../../app/contexts/ThemeContext' type WelcomeProps = ViewProps & {name: string, image : any, onPress: () => void | undefined} @@ -36,7 +35,6 @@ export default function Welcome(props: WelcomeProps) { const onpress = props.onPress const textcolor = colors.primaryText - //const backgroundColor: string = useThemeColor("backgroundColor") return ( Date: Sun, 31 Dec 2023 17:37:02 +0000 Subject: [PATCH 17/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1f7eecd..2607687 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -52,13 +52,13 @@ build_job: - npx expo prebuild --platform android - cd android && ./gradlew assembleRelease - cd $CI_PROJECT_DIR - - cp android/app/build/outputs/apk/release/app-release.apk finanzfuchs-is1-group-g-$CI_COMMIT_REF_NAME.apk - - echo RELEASE_APK=finanzfuchs-is1-group-g-$CI_COMMIT_REF_NAME.apk >> environment.env + - cp android/app/build/outputs/apk/release/app-release.apk finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk + - echo RELEASE_APK=finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk >> environment.env artifacts: - untracked: false + untracked: true name: $CI_PROJECT_NAME-group-g-finanzfuchs-$CI_COMMIT_REF_NAME paths: - - "finanzfuchs-is1-group-g-$CI_COMMIT_REF_NAME.apk" + - "finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk" reports: dotenv: environment.env when: on_success @@ -71,6 +71,7 @@ release_job: image: "registry.gitlab.com/gitlab-org/release-cli:latest" needs: - job: build_job + artifacts: true rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push" when: on_success @@ -82,5 +83,12 @@ release_job: links: - name: APK url: '${CI_PROJECT_URL}/-/jobs/${BUILD_JOB_ID}/artifacts/file/${RELEASE_APK}' + - name: Download APK + url: 'https://www.dropbox.com/scl/fo/dy21dx1cr2y42jmjmpkg0/h?rlkey=bofaz20jaofeqhgptv023lawl&dl=0' script: - - echo "Deploying" \ No newline at end of file + - apk add curl jq + - content=$(curl https://api.dropbox.com/oauth2/token -d grant_type=refresh_token -d refresh_token=$REFRESH_TOKEN -d client_id=$CLIENT_KEY -d client_secret=$CLIENT_SECRET) + - token=$(echo $content | jq -r ".access_token") + - echo 'Dropbox-API-Arg:' {\"path\"':' \"/release/v0_$CI_PIPELINE_IID/$RELEASE_APK\"} > arg.json + - | + curl -X POST https://content.dropboxapi.com/2/files/upload --header "Authorization: Bearer $token" --header "Content-Type: application/octet-stream" --header @arg.json --data-binary @"$RELEASE_APK" \ No newline at end of file From 953d2f2a56e4c86106701ce61ef76f2f52ed8d53 Mon Sep 17 00:00:00 2001 From: jastornig Date: Sun, 31 Dec 2023 18:00:57 +0000 Subject: [PATCH 18/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2607687..b28355c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -48,14 +48,15 @@ build_job: when: manual script: - echo BUILD_JOB_ID=$CI_JOB_ID >> environment.env - - yarn add expo - - npx expo prebuild --platform android - - cd android && ./gradlew assembleRelease - - cd $CI_PROJECT_DIR - - cp android/app/build/outputs/apk/release/app-release.apk finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk + # - yarn add expo + # - npx expo prebuild --platform android + # - cd android && ./gradlew assembleRelease + # - cd $CI_PROJECT_DIR + # - cp android/app/build/outputs/apk/release/app-release.apk finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk - echo RELEASE_APK=finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk >> environment.env + - echo "hello World" >> finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk artifacts: - untracked: true + untracked: false name: $CI_PROJECT_NAME-group-g-finanzfuchs-$CI_COMMIT_REF_NAME paths: - "finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk" From b036fb51a11b241f7af1c57b33991873838b6486 Mon Sep 17 00:00:00 2001 From: jastornig Date: Sun, 31 Dec 2023 18:37:48 +0000 Subject: [PATCH 19/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b28355c..c1fc7f2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -85,11 +85,19 @@ release_job: - name: APK url: '${CI_PROJECT_URL}/-/jobs/${BUILD_JOB_ID}/artifacts/file/${RELEASE_APK}' - name: Download APK - url: 'https://www.dropbox.com/scl/fo/dy21dx1cr2y42jmjmpkg0/h?rlkey=bofaz20jaofeqhgptv023lawl&dl=0' + url: $url script: - apk add curl jq - content=$(curl https://api.dropbox.com/oauth2/token -d grant_type=refresh_token -d refresh_token=$REFRESH_TOKEN -d client_id=$CLIENT_KEY -d client_secret=$CLIENT_SECRET) - token=$(echo $content | jq -r ".access_token") - echo 'Dropbox-API-Arg:' {\"path\"':' \"/release/v0_$CI_PIPELINE_IID/$RELEASE_APK\"} > arg.json - | - curl -X POST https://content.dropboxapi.com/2/files/upload --header "Authorization: Bearer $token" --header "Content-Type: application/octet-stream" --header @arg.json --data-binary @"$RELEASE_APK" \ No newline at end of file + curl -X POST https://content.dropboxapi.com/2/files/upload --header "Authorization: Bearer $token" --header "Content-Type: application/octet-stream" --header @arg.json --data-binary @"$RELEASE_APK" + - echo {\"path\":\"/release/v0_$CI_PIPELINE_IID/$RELEASE_APK\", "settings":{\"access\":\"viewer\", \"allow_download\":true}} >> data.json + - | + content=$(curl -X POST https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings --header "Authorization: Bearer $token" \ + --header "Content-Type: application/json" \ + --data @data.json) + url=$(echo $content | jq -r ".url") + + \ No newline at end of file From 9dceb2e6ae1ed74eda39f3aaec7fc789f0408647 Mon Sep 17 00:00:00 2001 From: jastornig Date: Sun, 31 Dec 2023 18:42:42 +0000 Subject: [PATCH 20/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c1fc7f2..f870c02 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -95,9 +95,7 @@ release_job: curl -X POST https://content.dropboxapi.com/2/files/upload --header "Authorization: Bearer $token" --header "Content-Type: application/octet-stream" --header @arg.json --data-binary @"$RELEASE_APK" - echo {\"path\":\"/release/v0_$CI_PIPELINE_IID/$RELEASE_APK\", "settings":{\"access\":\"viewer\", \"allow_download\":true}} >> data.json - | - content=$(curl -X POST https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings --header "Authorization: Bearer $token" \ - --header "Content-Type: application/json" \ - --data @data.json) + content=$(curl -X POST https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings --header "Authorization: Bearer $token" --header "Content-Type: application/json" --data @data.json) url=$(echo $content | jq -r ".url") \ No newline at end of file From e20991823224b8078035e2c57415c7d55c575c74 Mon Sep 17 00:00:00 2001 From: jastornig Date: Sun, 31 Dec 2023 18:46:05 +0000 Subject: [PATCH 21/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f870c02..982b9b0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -96,6 +96,8 @@ release_job: - echo {\"path\":\"/release/v0_$CI_PIPELINE_IID/$RELEASE_APK\", "settings":{\"access\":\"viewer\", \"allow_download\":true}} >> data.json - | content=$(curl -X POST https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings --header "Authorization: Bearer $token" --header "Content-Type: application/json" --data @data.json) - url=$(echo $content | jq -r ".url") + - echo $content + - url=$(echo $content | jq -r ".url") + - echo url \ No newline at end of file From 645b805aa767d7d5a840842413f87f633f1f5210 Mon Sep 17 00:00:00 2001 From: jastornig Date: Sun, 31 Dec 2023 18:51:01 +0000 Subject: [PATCH 22/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 982b9b0..875d494 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -93,11 +93,9 @@ release_job: - echo 'Dropbox-API-Arg:' {\"path\"':' \"/release/v0_$CI_PIPELINE_IID/$RELEASE_APK\"} > arg.json - | curl -X POST https://content.dropboxapi.com/2/files/upload --header "Authorization: Bearer $token" --header "Content-Type: application/octet-stream" --header @arg.json --data-binary @"$RELEASE_APK" - - echo {\"path\":\"/release/v0_$CI_PIPELINE_IID/$RELEASE_APK\", "settings":{\"access\":\"viewer\", \"allow_download\":true}} >> data.json + - echo {\"path\":\"/release/v0_$CI_PIPELINE_IID/$RELEASE_APK\", \"settings\":{\"access\":\"viewer\", \"allow_download\":true}} >> data.json - | content=$(curl -X POST https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings --header "Authorization: Bearer $token" --header "Content-Type: application/json" --data @data.json) - - echo $content - url=$(echo $content | jq -r ".url") - - echo url \ No newline at end of file From 69610de1004202173007b8eecbf8f3c50944557e Mon Sep 17 00:00:00 2001 From: Thomas Schleicher Date: Tue, 2 Jan 2024 12:49:13 +0000 Subject: [PATCH 23/72] Resolve "Budget" --- app/(tabs)/_layout.tsx | 3 +- app/(tabs)/budget/_layout.tsx | 16 ++++ app/(tabs)/budget/addCategory.tsx | 8 ++ app/(tabs)/budget/index.tsx | 82 +++++++++++++----- app/(tabs)/home/index.tsx | 61 +++++++------- app/(tabs)/stats/index.tsx | 45 ++++++++-- components/budget/budgetHeader.tsx | 84 +++++++++++++++++++ components/budget/categoryItem.tsx | 56 +++++++++++++ components/common/CustomCard.tsx | 2 +- .../home/{userSettings => }/Setting.tsx | 8 +- components/home/{Welcome => }/Welcome.tsx | 12 +-- .../home/{expenseItem => }/expenseItem.tsx | 30 +++---- components/index.tsx | 13 ++- components/login/input.tsx | 4 +- hooks/useFetch.ts | 7 +- hooks/useThemeColor.ts | 2 +- services/database.ts | 34 ++++---- 17 files changed, 346 insertions(+), 121 deletions(-) create mode 100644 app/(tabs)/budget/_layout.tsx create mode 100644 app/(tabs)/budget/addCategory.tsx create mode 100644 components/budget/budgetHeader.tsx create mode 100644 components/budget/categoryItem.tsx rename components/home/{userSettings => }/Setting.tsx (85%) rename components/home/{Welcome => }/Welcome.tsx (87%) rename components/home/{expenseItem => }/expenseItem.tsx (60%) diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 6dc7691..4eb7245 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -34,12 +34,13 @@ export default function Layout() { return ( - ( ), unmountOnBlur: true, + href: "(tabs)/budget" } }/> + + + + ); +} \ No newline at end of file diff --git a/app/(tabs)/budget/addCategory.tsx b/app/(tabs)/budget/addCategory.tsx new file mode 100644 index 0000000..e8edbbc --- /dev/null +++ b/app/(tabs)/budget/addCategory.tsx @@ -0,0 +1,8 @@ + +const addCategory = () => { + return ( + <> + ); +} + +export default addCategory; \ No newline at end of file diff --git a/app/(tabs)/budget/index.tsx b/app/(tabs)/budget/index.tsx index 62b3162..4ca010e 100644 --- a/app/(tabs)/budget/index.tsx +++ b/app/(tabs)/budget/index.tsx @@ -1,28 +1,70 @@ -import { SafeAreaView, StyleSheet, Switch, Text } from 'react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { router } from 'expo-router'; +import { useEffect, useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { FlatList } from 'react-native-gesture-handler'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { BudgetHeader, LoadingSymbol, Plus } from '../../../components'; +import CategoryItem from '../../../components/budget/categoryItem'; +import useFetch from '../../../hooks/useFetch'; +import { useTheme } from '../../contexts/ThemeContext'; export default function Page() { + const {colors} = useTheme() + const containerColor = colors.containerColor; - const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - switch: { - transform: [{ scaleX: 1.3 }, { scaleY: 1.3 }], - }, - text: { - color: "red", - fontSize: 40, + const [selectedPage, setSelectedPage] = useState("noPageLoaded"); + + useEffect(() => { + AsyncStorage.getItem("currentBudgetPage").then((page) => { + if(page === "expenses" || page === "savings") { + setSelectedPage(page); + } + }).catch((error) => { + console.log("Error fetching previous page from Async Storage:", error); + }) + }, []); + + 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 expense e RIGHT JOIN category c ON e.category_guid = c.guid WHERE c.type = ? GROUP BY c.guid", args: selectedPage === "expenses" ? ["expense"] : selectedPage === "savings" ? ["saving"] : []}); + + useEffect(() => { + reFetch(); + }, [selectedPage]); + + const handlePageSelection = (page: string) => { + if(page !== selectedPage) { + setSelectedPage(page); + AsyncStorage.setItem("currentBudgetPage", page); } - }); - - // const {data, isLoading, reFetch} = useFetch(); + }; return ( - - Hallo wo bin ich?! - + + + + { + router.push("/(tabs)/budget/addCategory") + }}/> + + {isLoading ? () : ( + } + keyExtractor={item => item.category_guid} + ItemSeparatorComponent={() => { + return (); + }} + /> + )} ); -} \ No newline at end of file +} + +const styles = StyleSheet.create({ + safeAreaViewStyle: { + flex: 1, + }, + itemSeperatorStyle: { + marginVertical: 5, + }, +}); \ No newline at end of file diff --git a/app/(tabs)/home/index.tsx b/app/(tabs)/home/index.tsx index 4a510a1..eaa0b22 100644 --- a/app/(tabs)/home/index.tsx +++ b/app/(tabs)/home/index.tsx @@ -5,7 +5,8 @@ import { FlatList } from 'react-native-gesture-handler'; import { SafeAreaView } from 'react-native-safe-area-context'; import { ExpenseItem, LoadingSymbol, Plus, SearchBar, Welcome } from '../../../components'; import useFetch from '../../../hooks/useFetch'; -import { addExpense } from "../../../services/database"; + +import { addExpense, executeQuery } from "../../../services/database"; import { useAuth } from '../../contexts/AuthContext'; import { useRouter } from "expo-router"; import { SimpleDate } from '../../../util/SimpleDate'; @@ -37,26 +38,7 @@ const constructMarkedDates = (data : {[column: string]: any}) => { export default function Page() { const { colors, theme } = useTheme() - //Styles - const styles = StyleSheet.create({ - safeAreaViewStyle: { - flex: 1, - backgroundColor: colors.backgroundColor - }, - container: { - flex: 1, - alignItems: "center", - justifyContent: "center", - }, - text: { - color: colors.primaryText, - fontSize: 70, - fontWeight: "bold" - }, - loading: { - color: "red", - } - }); + const router = useRouter(); const [plusShow, setPlusShow] = useState(true); @@ -81,7 +63,7 @@ export default function Page() { } } - const {data, isLoading, reFetch} = useFetch(); + 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;", args: []}); const expenseDates = useMemo(()=> constructMarkedDates(data) @@ -90,15 +72,20 @@ export default function Page() { return ( - + {plusShow && { // router.push("/(tabs)/home/addItem"); - newExpense("Test Title", "3b33b8ac-5fc1-43e5-81fc-cf61628861f7", "69.69.1234", 100).then(() => { - reFetch(); - }); + + executeQuery({sql: "SELECT guid FROM category", args: []}).then((result) => { + if("rows" in result[0]) { + newExpense("Test Title", result[0]["rows"][0]["guid"], "69.69.1234", 100).then(() => { + reFetch(); + }); + } + }) }}/>} - {isLoading && } + {isLoading && } } - renderItem = {({item}) => } + renderItem = {({item}) => } keyExtractor={item => item.expense_guid} - ItemSeparatorComponent={()=>} + ItemSeparatorComponent={() => { + return (); + }} onScroll={handleScroll} scrollEventThrottle={20} - > - + /> ); -} \ No newline at end of file +} + +const styles = StyleSheet.create({ + safeAreaViewStyle: { + flex: 1, + }, + itemSeperatorStyle: { + marginVertical: 5, + } +}); \ No newline at end of file diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/stats/index.tsx index 85c1636..9dd775a 100644 --- a/app/(tabs)/stats/index.tsx +++ b/app/(tabs)/stats/index.tsx @@ -1,22 +1,20 @@ +import { Query } from 'expo-sqlite'; import { StyleSheet, Text, View } from 'react-native'; -import { deleteExpenses } from '../../../services/database'; +import { addCategory, deleteDatabase, deleteExpenses, executeQuery, initDatabase } from '../../../services/database'; export default function Page() { const styles = StyleSheet.create({ container: { flex: 1, - justifyContent: 'center', + justifyContent: 'space-evenly', alignItems: 'center', }, text: { - textAlign: 'center', fontSize: 40, - color: "red", + color: "yellow", } }); - - return ( { @@ -24,5 +22,40 @@ export default function Page() { console.log("Expenses Deleted!"); }) }}>Reset Expenses + + { + deleteDatabase(); + console.log("Database Deleted!"); + }}>Reset Database + + { + initDatabase().then(() => { + console.log("Database Initialized!"); + }); + }}>Init Database + + { + addCategory("Category", "green", "expense", 500).then(() => { + const getCategoryQuery: Query = {sql: "SELECT guid FROM category", args: []}; + executeQuery(getCategoryQuery).then((result) => { + if("rows" in result[0]) { + console.log(result[0]["rows"]); + } + }) + console.log("Category added with success!"); + }) + }}>Add new Category Expense + + { + addCategory("Category", "yellow", "saving", 420).then(() => { + const getCategoryQuery: Query = {sql: "SELECT guid FROM category", args: []}; + executeQuery(getCategoryQuery).then((result) => { + if("rows" in result[0]) { + console.log(result[0]["rows"]); + } + }) + console.log("Category added with success!"); + }) + }}>Add new Category Savings ); } diff --git a/components/budget/budgetHeader.tsx b/components/budget/budgetHeader.tsx new file mode 100644 index 0000000..eed9624 --- /dev/null +++ b/components/budget/budgetHeader.tsx @@ -0,0 +1,84 @@ +import { StyleSheet, Text, TouchableHighlight, View } from "react-native"; +import SearchBar from "../common/SearchBar"; +import { useTheme } from "../../app/contexts/ThemeContext"; + +type BudgetHeaderProperties = { + selectedPage: string, + handlePageSelection: (page: string) => void, +} + +type PageSelectorButtonProperties = { + isSelected: boolean, + onPress: () => void, + label: string, +} + +const BudgetHeader = (properties: BudgetHeaderProperties) => { + const {colors} = useTheme(); + const backgroundColor = colors.backgroundColor; + + return (<> + + { + properties.handlePageSelection("expenses") + }} + /> + { + properties.handlePageSelection("savings"); + }} + /> + + + ); +} + +const PageSelectorButton = (properties: PageSelectorButtonProperties) => { + const {colors} = useTheme(); + + const primaryTextColor = colors.primaryText; + const secondaryTextColor = colors.secondaryText; + const elementSelectedColor = colors.elementSelectedColor; + const elementDefaultColor = colors.elementDefaultColor; + const accentColor = colors.accentColor; + + return ( + + + {properties.label} + + + + ); +} + +export default BudgetHeader; + +const styles = StyleSheet.create({ + headerContainerStyle: { + width: "50%", + borderRadius: 10, + marginHorizontal: 30, + }, + headerTextStyle: { + fontSize: 30, + textAlign: "center", + textAlignVertical: "center", + }, + containerStyle: { + flexDirection: "row", + justifyContent: "space-evenly", + marginHorizontal: 20, + marginBottom: 20, + marginTop: 10, + }, +}); \ No newline at end of file diff --git a/components/budget/categoryItem.tsx b/components/budget/categoryItem.tsx new file mode 100644 index 0000000..e651caf --- /dev/null +++ b/components/budget/categoryItem.tsx @@ -0,0 +1,56 @@ +import { ColorValue, StyleSheet, Text, View } from "react-native"; +import { useTheme } from "../../app/contexts/ThemeContext"; +import CustomCard from "../common/CustomCard"; + +export type CategoryItemProps = { + category: string, + color: ColorValue, + allocated_amount: number, + total_expenses: number, + category_guid: string, +} + +const CategoryItem = (properties: CategoryItemProps) => { + + const { colors } = useTheme(); + + const subText = `${properties.total_expenses} / ${properties.allocated_amount} €`; + + return ( + + + + + {properties.category} + + + {subText} + + + + ); +}; + +export default CategoryItem; + +const styles = StyleSheet.create({ + colorTipStyle: { + width: 25, + borderTopLeftRadius: 10, + borderBottomLeftRadius: 10, + }, + textViewStyle: { + flex: 2, + flexDirection: "column", + paddingVertical: 5, + paddingHorizontal: 10, + alignSelf: "stretch", + }, + categoryNameStyle: { + fontSize: 30, + fontWeight: "bold", + }, + subTextStyle: { + fontSize: 17.5, + } + }) diff --git a/components/common/CustomCard.tsx b/components/common/CustomCard.tsx index d81020e..5358d51 100644 --- a/components/common/CustomCard.tsx +++ b/components/common/CustomCard.tsx @@ -42,7 +42,7 @@ const styles = StyleSheet.create({ flexDirection: "row", alignItems: "stretch", alignContent: "space-between", - borderRadius: 20, + borderRadius: 10, marginHorizontal: 10, }, boxShadow: {}, diff --git a/components/home/userSettings/Setting.tsx b/components/home/Setting.tsx similarity index 85% rename from components/home/userSettings/Setting.tsx rename to components/home/Setting.tsx index 6b480d5..9089f82 100644 --- a/components/home/userSettings/Setting.tsx +++ b/components/home/Setting.tsx @@ -1,7 +1,7 @@ -import { View, Text, StyleSheet, Switch, SwitchProps, useColorScheme, TouchableOpacityProps, TouchableOpacity } from 'react-native' -import React from 'react' -import { SIZES } from '../../../constants/theme' -import { useTheme } from '../../../app/contexts/ThemeContext'; +import React from 'react'; +import { StyleSheet, Switch, SwitchProps, Text, TouchableOpacity, TouchableOpacityProps, View } from 'react-native'; +import { useTheme } from '../../app/contexts/ThemeContext'; +import { SIZES } from '../../constants/theme'; interface ToggleSettingProps extends SwitchProps { settingsTitle: string; diff --git a/components/home/Welcome/Welcome.tsx b/components/home/Welcome.tsx similarity index 87% rename from components/home/Welcome/Welcome.tsx rename to components/home/Welcome.tsx index 4c766b1..2c53e36 100644 --- a/components/home/Welcome/Welcome.tsx +++ b/components/home/Welcome.tsx @@ -1,8 +1,8 @@ import React from 'react' import { Image, Text, View, ViewProps } from 'react-native' import { TouchableOpacity } from 'react-native-gesture-handler' -import { MARGINS, SIZES } from '../../../constants/theme' -import { useTheme } from '../../../app/contexts/ThemeContext' +import { MARGINS, SIZES } from '../../constants/theme' +import { useTheme } from '../../app/contexts/ThemeContext' type WelcomeProps = ViewProps & {name: string, image : any, onPress: () => void | undefined} @@ -28,13 +28,13 @@ function getTimeOfDay(date: Date) : string { export default function Welcome(props: WelcomeProps) { - const {colors} = useTheme() + const { colors } = useTheme(); + const date = new Date() const dateString = formatDate(date) const timeOfDay = getTimeOfDay(date) const onpress = props.onPress - const textcolor = colors.primaryText return ( {dateString} Good {timeOfDay}, {props.name} diff --git a/components/home/expenseItem/expenseItem.tsx b/components/home/expenseItem.tsx similarity index 60% rename from components/home/expenseItem/expenseItem.tsx rename to components/home/expenseItem.tsx index c5146bd..d541d48 100644 --- a/components/home/expenseItem/expenseItem.tsx +++ b/components/home/expenseItem.tsx @@ -1,39 +1,33 @@ import React from 'react'; import { ColorValue, StyleSheet, Text, View } from 'react-native'; -import { SIZES } from '../../../constants/theme'; -import { useTheme } from '../../../app/contexts/ThemeContext'; -import CustomCard from "../../common/CustomCard"; -import { SimpleDate } from '../../../util/SimpleDate'; +import { SIZES } from '../../constants/theme'; +import CustomCard from "../common/CustomCard"; +import { useTheme } from '../../app/contexts/ThemeContext'; -type ISOdateString = string - -export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: ISOdateString, value : string} +export type ExpenseItemProps = {color: ColorValue, category: string, title: string, date: string, value : string} export default function ExpenseItem(itemProps : ExpenseItemProps) { - const {colors} = useTheme() - const textColor = colors.primaryText - const backgroundColor = colors.containerColor - const date: SimpleDate = new SimpleDate(new Date(itemProps.date)) + const { colors } = useTheme(); return ( - + {itemProps.category} {itemProps.title} {date.format("DD.MM.YYYY")} + color: colors.primaryText + }} numberOfLines={1}>{itemProps.date} - + {itemProps.value} diff --git a/components/index.tsx b/components/index.tsx index 61d8ef7..4d74a43 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -1,7 +1,7 @@ //home -import ExpenseItem from "./home/expenseItem/expenseItem" -import Welcome from "./home/Welcome/Welcome" -import { ToggleSetting, ButtonSetting } from "./home/userSettings/Setting" +import { ButtonSetting, ToggleSetting } from "./home/Setting" +import Welcome from "./home/Welcome" +import ExpenseItem from "./home/expenseItem" //common import LoadingSymbol from "./common/loadingSymbol" @@ -11,13 +11,12 @@ import CustomCard from "./common/CustomCard" //login +import BudgetHeader from "./budget/budgetHeader" import Input from "./login/input" export { - ExpenseItem, Input, + BudgetHeader, ButtonSetting, CustomCard, ExpenseItem, Input, LoadingSymbol, Plus, - SearchBar, Welcome, - ToggleSetting, CustomCard, - ButtonSetting + SearchBar, ToggleSetting, Welcome } diff --git a/components/login/input.tsx b/components/login/input.tsx index 75f2ea4..c6c05aa 100644 --- a/components/login/input.tsx +++ b/components/login/input.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import {View, Text, TextInput, StyleSheet} from 'react-native'; -import { NativeSyntheticEvent } from 'react-native/Libraries/Types/CoreEventTypes'; +import { StyleSheet, Text, TextInput, View } from 'react-native'; import { TextInputEndEditingEventData } from 'react-native/Libraries/Components/TextInput/TextInput'; +import { NativeSyntheticEvent } from 'react-native/Libraries/Types/CoreEventTypes'; interface InputProps { label? : string, diff --git a/hooks/useFetch.ts b/hooks/useFetch.ts index 790e4f2..496beba 100644 --- a/hooks/useFetch.ts +++ b/hooks/useFetch.ts @@ -1,20 +1,21 @@ +import { Query } from "expo-sqlite"; import { useEffect, useState } from "react"; import { executeQuery } from "../services/database"; -const useFetch = () => { +const useFetch = (query: Query) => { 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) => { + executeQuery(query).then((result) => { if("rows" in result[0]) { setData(result[0]["rows"]); } }).catch((error: any) => { console.error("Fetching data from database has failed: ", error); - }).finally(() => { + }).then(() => { setIsLoading(false); }); } diff --git a/hooks/useThemeColor.ts b/hooks/useThemeColor.ts index c679987..0a6143c 100644 --- a/hooks/useThemeColor.ts +++ b/hooks/useThemeColor.ts @@ -7,7 +7,7 @@ import colors from "../constants/colors"; * @returns */ export function useThemeColor(colorName: keyof typeof colors.light & keyof typeof colors.dark): string { - console.warn("useThemeColor is depreciated. Use useTheme().colors instead") + console.log("useThemeColor is depreciated. Use useTheme().colors instead") const theme = useColorScheme() ?? "light"; return colors[theme][colorName]; } diff --git a/services/database.ts b/services/database.ts index ec1528b..fbb9b6c 100644 --- a/services/database.ts +++ b/services/database.ts @@ -1,4 +1,3 @@ -//created by thschleicher import * as SQLite from "expo-sqlite"; import uuid from "react-native-uuid"; @@ -6,27 +5,26 @@ import uuid from "react-native-uuid"; import { Query } from "expo-sqlite"; import { SimpleDate } from "../util/SimpleDate"; -const db = SQLite.openDatabase("interactive_systeme.db"); +let db: SQLite.SQLiteDatabase; export const initDatabase = async () => { + db = SQLite.openDatabase("interactive_systeme.db"); try { await db.transactionAsync(async (tx: SQLite.SQLTransactionAsync) => { await tx.executeSqlAsync( - "CREATE TABLE IF NOT EXISTS category (guid VARCHAR(36) PRIMARY KEY, name TEXT, color TEXT, type TEXT);" + "CREATE TABLE IF NOT EXISTS category (guid VARCHAR(36) PRIMARY KEY, name TEXT, color TEXT, type TEXT, allocated_amount DOUBLE);" ); await tx.executeSqlAsync( "CREATE TABLE IF NOT EXISTS expense (guid VARCHAR(36) PRIMARY KEY, name TEXT, category_guid VARCHAR(36), datetime DATETIME, amount DOUBLE, FOREIGN KEY (category_guid) REFERENCES category(guid));" ); - console.log("Successfully initialized Tables!"); }); } catch (error) { - - console.log("Error initializing the Tables!"); + console.log("Error initializing the Database!"); throw (error); } }; -export const addCategory = async (name: string, color: string, type: string) => { +export const addCategory = async (name: string, color: string, type: string, allocated_amount: number) => { //needs user input validation for type and color (should be validated by this function) @@ -34,18 +32,17 @@ export const addCategory = async (name: string, color: string, type: string) => try { await db.transactionAsync(async (tx) => { - await tx.executeSqlAsync("INSERT INTO category (guid, name, color, type) VALUES (?, ?, ?, ?);", - [UUID.toString(), name, color, type] + await tx.executeSqlAsync("INSERT INTO category (guid, name, color, type, allocated_amount) VALUES (?, ?, ?, ?, ?);", + [UUID.toString(), name, color, type, allocated_amount] ); }); - console.log("Category added successfully!"); } catch (error) { console.log("Error adding category: ", error); throw error; } } -export const addExpense = async (name: string, category_guid: string,datetime: string, amount: number) => { +export const addExpense = async (name: string, category_guid: string, datetime: string, amount: number) => { //needs user input validation for type and color (should be validated by this function) @@ -56,7 +53,6 @@ export const addExpense = async (name: string, category_guid: string,datetime: s await tx.executeSqlAsync("INSERT INTO expense (guid, name, category_guid, datetime, amount) VALUES (?, ?, ?, ?, ?);", [expenseUUID.toString(), name, category_guid, datetime, amount] ); }); - console.log("Expense added successfully!"); } catch (error) { console.log("Error adding expense: ", error); throw error; @@ -85,15 +81,15 @@ export const deleteExpense = async (guid: string) => { } } -export const executeQuery = async (query: string) => { - const sqliteQuary: Query[] = [{sql: query, args: []}]; - const result = await db.execAsync(sqliteQuary, true); +export const executeQuery = async (query: Query) => { + const result = await db.execAsync([query], true); if("error" in result[0]){ throw result[0].error } - return result; + return result; } + export const deleteExpenses = async () => { try { await db.transactionAsync(async (tx: SQLite.SQLTransactionAsync) => { @@ -125,9 +121,7 @@ export const deleteDatabase = () => { console.log("Error deleting the Database: ", error); throw error; } - }); - console.log("Database Deleted!") } const closeDatabase = async () => { @@ -149,9 +143,9 @@ export const DEV_populateDatabase = async () => { for(let i=0; i < 5; i++){ let random = Math.floor(Math.random() * colors.length); - await addCategory(`Category ${i}`, colors[random], "budget") + await addCategory(`Category ${i}`, colors[random], "budget", 50) } - const result = await executeQuery("SELECT * from category") + const result = await executeQuery({sql:"SELECT * from category", args:[]}) let categories: {[column: string]: any}[]; if("rows" in result[0]){ categories = result[0]["rows"] From 5ba46cb3b3b4e6f09a3aec95244735ad85ea75b6 Mon Sep 17 00:00:00 2001 From: jastornig Date: Tue, 2 Jan 2024 13:20:31 +0000 Subject: [PATCH 24/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 875d494..6d12529 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,6 +20,7 @@ stages: - lint # List of stages for jobs, and their order of execution - build + - deploy - release lint_job: @@ -65,27 +66,15 @@ build_job: when: on_success expire_in: 60 days - - -release_job: - stage: release - image: "registry.gitlab.com/gitlab-org/release-cli:latest" +deploy_job: + stage: deploy + image: "alpine:latest" needs: - job: build_job artifacts: true rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push" when: on_success - release: - tag_name: 'v0.$CI_PIPELINE_IID' - description: 'v0.$CI_PIPELINE_IID create by pipeline' - ref: '$CI_COMMIT_SHA' - assets: - links: - - name: APK - url: '${CI_PROJECT_URL}/-/jobs/${BUILD_JOB_ID}/artifacts/file/${RELEASE_APK}' - - name: Download APK - url: $url script: - apk add curl jq - content=$(curl https://api.dropbox.com/oauth2/token -d grant_type=refresh_token -d refresh_token=$REFRESH_TOKEN -d client_id=$CLIENT_KEY -d client_secret=$CLIENT_SECRET) @@ -97,5 +86,33 @@ release_job: - | content=$(curl -X POST https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings --header "Authorization: Bearer $token" --header "Content-Type: application/json" --data @data.json) - url=$(echo $content | jq -r ".url") + - echo APK_URL=$url >> urlEnv.env + artifacts: + untracked: false + when: on_success + expire_in: 30 days + reports: + dotenv: urlEnv.env + + +release_job: + stage: release + image: "registry.gitlab.com/gitlab-org/release-cli:latest" + needs: + - job: deploy_job + artifacts: true + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push" + when: on_success + release: + tag_name: 'v0.$CI_PIPELINE_IID' + description: 'v0.$CI_PIPELINE_IID create by pipeline' + ref: '$CI_COMMIT_SHA' + assets: + links: + - name: Download APK + url: $APK_URL + script: + - echo deploying \ No newline at end of file From e0b99b41f39d462202440617a83dcd452f8cc3c8 Mon Sep 17 00:00:00 2001 From: jastornig Date: Tue, 2 Jan 2024 13:24:10 +0000 Subject: [PATCH 25/72] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6d12529..9889a4f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -49,13 +49,13 @@ build_job: when: manual script: - echo BUILD_JOB_ID=$CI_JOB_ID >> environment.env - # - yarn add expo - # - npx expo prebuild --platform android - # - cd android && ./gradlew assembleRelease - # - cd $CI_PROJECT_DIR - # - cp android/app/build/outputs/apk/release/app-release.apk finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk + - yarn add expo + - npx expo prebuild --platform android + - cd android && ./gradlew assembleRelease + - cd $CI_PROJECT_DIR + - cp android/app/build/outputs/apk/release/app-release.apk finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk - echo RELEASE_APK=finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk >> environment.env - - echo "hello World" >> finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk + # - echo "hello World" >> finanzfuchs-is1-group-g-v0.$CI_PIPELINE_IID.apk artifacts: untracked: false name: $CI_PROJECT_NAME-group-g-finanzfuchs-$CI_COMMIT_REF_NAME From 62e71d1b498b8209b1165d71e241282a29ae2eeb Mon Sep 17 00:00:00 2001 From: Walcher Date: Tue, 2 Jan 2024 14:38:47 +0100 Subject: [PATCH 26/72] everything refactored --- app/(tabs)/stats/index.tsx | 108 ++++++++++--------- assets/images/8b14el.jpg | Bin 0 -> 83696 bytes components/stats/BudgetOverview.tsx | 38 +++++++ components/stats/BudgetRemaining.tsx | 38 +++++++ components/stats/CategoryProgressBar.tsx | 69 ++++++++++++ components/stats/CategoryProgressBarList.tsx | 61 +++++++++++ components/stats/PieChart.tsx | 1 + components/stats/Widget.tsx | 63 +++++++++++ constants/colors.ts | 6 ++ services/DebugMenu.tsx | 73 +++++++++++++ 10 files changed, 407 insertions(+), 50 deletions(-) create mode 100644 assets/images/8b14el.jpg create mode 100644 components/stats/BudgetOverview.tsx create mode 100644 components/stats/BudgetRemaining.tsx create mode 100644 components/stats/CategoryProgressBar.tsx create mode 100644 components/stats/CategoryProgressBarList.tsx create mode 100644 components/stats/PieChart.tsx create mode 100644 components/stats/Widget.tsx create mode 100644 services/DebugMenu.tsx diff --git a/app/(tabs)/stats/index.tsx b/app/(tabs)/stats/index.tsx index 9dd775a..ae4c09f 100644 --- a/app/(tabs)/stats/index.tsx +++ b/app/(tabs)/stats/index.tsx @@ -1,61 +1,69 @@ -import { Query } from 'expo-sqlite'; -import { StyleSheet, Text, View } from 'react-native'; -import { addCategory, deleteDatabase, deleteExpenses, executeQuery, initDatabase } from '../../../services/database'; +import React from 'react'; +import { StyleSheet, View, ScrollView } from 'react-native'; +import BudgetOverview from '../../../components/stats/BudgetOverview'; +import { useTheme } from '../../contexts/ThemeContext'; +import Widget from '../../../components/stats/Widget'; +import CategoryProgressBarList from '../../../components/stats/CategoryProgressBarList'; +import BudgetRemaining from '../../../components/stats/BudgetRemaining'; +import DebugMenu from '../../../services/DebugMenu'; export default function Page() { + const { colors } = useTheme(); + + // Mock data #TODO Database einbinden + // what to do when amount too small? + + const spent = 120.75; + const budget = 696.96; + + const BudgetData = [ + { name: 'Utilities', color: '#20B2AA', maxValue: 80, currentValue: 46 }, + { name: 'Food', color: '#FF6347', maxValue: 88, currentValue: 31 }, + ]; + + const SavingsData = [ + { name: 'Education', color: '#FF6347', maxValue: 135, currentValue: 0 }, + { name: 'Rent', color: '#DA70D6', maxValue: 140, currentValue: 96 }, + { name: 'Food', color: '#F08080', maxValue: 84, currentValue: 78 }, + { name: 'Healthcare', color: '#20B2AA', maxValue: 134, currentValue: 48 }, + { name: 'Healthcare', color: '#32CD32', maxValue: 119, currentValue: 69 }, + { name: 'Clothing', color: '#32CD32', maxValue: 115, currentValue: 99 }, + ]; + + const categoryData = [ + { category: 'Food', value: 50 }, + { category: 'Rent', value: 300 }, + { category: 'Utilities', value: 100 }, + ]; + const styles = StyleSheet.create({ container: { flex: 1, - justifyContent: 'space-evenly', + backgroundColor: colors.backgroundColor, + marginTop: 50, alignItems: 'center', }, - text: { - fontSize: 40, - color: "yellow", - } }); return ( - - { - deleteExpenses().then(() => { - console.log("Expenses Deleted!"); - }) - }}>Reset Expenses - - { - deleteDatabase(); - console.log("Database Deleted!"); - }}>Reset Database - - { - initDatabase().then(() => { - console.log("Database Initialized!"); - }); - }}>Init Database - - { - addCategory("Category", "green", "expense", 500).then(() => { - const getCategoryQuery: Query = {sql: "SELECT guid FROM category", args: []}; - executeQuery(getCategoryQuery).then((result) => { - if("rows" in result[0]) { - console.log(result[0]["rows"]); - } - }) - console.log("Category added with success!"); - }) - }}>Add new Category Expense - - { - addCategory("Category", "yellow", "saving", 420).then(() => { - const getCategoryQuery: Query = {sql: "SELECT guid FROM category", args: []}; - executeQuery(getCategoryQuery).then((result) => { - if("rows" in result[0]) { - console.log(result[0]["rows"]); - } - }) - console.log("Category added with success!"); - }) - }}>Add new Category Savings - ); + + + + + + + + + + + + + + + + + + + + ); } diff --git a/assets/images/8b14el.jpg b/assets/images/8b14el.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3e1e0e6f5378914b2994a6a5825194cd45497c15 GIT binary patch literal 83696 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Ø=uk&xpcV0(cw}toFJ+wOn>d!r2bbrG3`~MkK6?X2l57+bPMYQ}sf)1GRJph9n{>KWg1 zCbmd)Ic{cIc*LSz?CFAO-Z>{-qPI%*DIVc7=zyC8lS)MJd((&EQ&~7uAK^)(byQjRJ#9GR3DtUBllBvyKweH3eiGS;&N`Dvx z)~cD*6wZxo6l|QB>bWRn?h^kA>BTer)D2(%77Xq>)9|}$qUk-!cU~bD$0Z)IEN~MN zyQnj5p71-VKEopxZ41tLeKbf8d}MR!^o}f!V+)$+@w4VtXa{GmwX8YPT+34PrTgpa z)al2=Lt9gLBOLv27S@+~D)icE)IKpdrgrIW+~OlCTMX~^M@~GKxZ`s2SBOT`kLI|D z6}se!P6=JUB{}m}V0-2<+2t~2Qxesp`I_0PI}XRaF64Dvm?pz@=fj05eU1Vu0#B#4 zT08YyB@$_RPrG-DK8fNm2)nxHo7vRButR3M8Qnu?AKNCo>D%5N zDN5VkPF}jmXsidX;Qr)TVqsx+0)J|C{dL zFYjw7pD?v-yAM&%;DsR}{&t+R`rN zo^#B|ce5}18u>}3p{E>P3jCNH@tHg6>Nj5QPEEf}x80d^ihEN|b2PV=Kl#pYFQ#p) zyChp?*}S>IW^<~Roi^H|5p8o)x6QOQJo&|wZO+pgClzdu=oE}d^t0BzarNwm*~_jx z4otTcxwk#zcF|jJ7mL2r@rv67Gru?}DY1yiaR2R|Uq98`oA=GV%`+ZW2>$8vit#z+ zDtJ9*^MY>fh%ig0l81&06Hgpda;@YGyt>E#;T8UA#lCFQUcTly+q|jpxaKb9{N0Uq z>jfsV*))7jUMuD26n3{I!}Mm_g{u|`2C3$mDX()J7W*r$bF+6YD62Nr&0e?OkbTAa z%at+{^!^iP>Nd~EzvT3OM_im>bDV2)&xL8XRcrm@zVkgiG9k_J zvch7;JEn>&c|Ee6gJe9#rAlx5b1i*T`geYa$eCKf(Q$Pb4Fjei!o7*{R>M>BGM5y)k!A zYyUpB(S~myGe_pOqH6Wk&wbA1NUdJJCSN_zHm&YUaZxCnGr#Plw`vimw))&RsxdbG zH+9ClqI1?w{@ffJHa?SorKY>;T<~6_cAt%stzWMnTdw+v=eS4m5`#B-kJ}O+&3MV~ zk-XUO;*6J!F7J0_dtU8b^;t1-U$pCQw<#-%qGYByDyq0R{63aw@-X<}o4+v}CZ}2s z9hs4LocF|po7-!qd0p45^4fTIp3jb=Uz1eptkw0VNA&Dg4&g33USz3}G+*V!@+()5 zuV26Au*O@j!>N-c!c;f!+>q0;MN|EIsbtqzw zn9h=jD67d^)uUyOGzChOpGx>wJ->dl2MhOqh6jpUR%TsVWbb#Y<7du7o6eJ}+}B^P zp1pqU&Xf0!6-bDzP*G(qJ8!q{yxQuu3yx;b+P$&oZ*i{qNtqPR6w4J{(`6Y?cYQl` zSi7lx{@J+^8@5bN$y3!*%f4S>9Ccc&+NsJ?UZX$MVE*QXTqUpVJQaWQO*K z?1%8e;<$)s`3#Z_nLLB#_w?*li^2>{FQn~^$%-&cJUQ>TXX2L6u?~6Xj!wKC_IT^tGi7sESPF|+ zY+-ri^YChr7`bFzl#rx~^DBDwsFnjcjc+1}bC^XbTy zS+a*)+I5*H-FSJ+FzcYmk;>rT`dPjgY#yI^=e%}D>H>9vbj?dyCYCmD8q4@B)Z%xF zYF2$$pV;~4muyr(%YvTWZPC-5k3V{R<(uT*O&P1Dx+;2e4(wrEu!r@)8mUbFCS9h2 zY=Iuu&l)pST-Y?SlYE_Z&F0KJtgW}sAI+mYot#7y;W1UZ)UH##HmjmJN~H8ojc`b!pB*ijBx^!l3NO9Jhb&xvv6zZ zD3~9&o~OUJc!wA-x5P_9kIl+4R}~YlO|$f4OS6{=U-MgV%9d?cB@SwNCs|!te)Ylg zmVEwMx9=_4H%ag9tv~ng&P_JURe!|QFfA|l$SW4jj_aEgyCu1Im)jLQPTm^uR_DCv z%{?9(xzldBy>;`RaCA}2mu{X-$7IZs%sZ>RKD0l*kYRG=q_X3(C%?D;%q@vo=j8P0 zyW0{^=gQu&ZN7>gYz>)L&bt|2+4I?5>PnM}$qSWn^({bUQ<5*81(KmmY7u zvgSWS^e65cQzpE5v&QJRhM;uQt-$+Tat(hrip1C{cNgYeFHT>xeXln2?$T35T&Jl~tYRdn$|dz@+P$y8 zyk08av7GBC=N~0;YF>i#o)xn!q63Q}4GNPrWJ^q0gRZWy?+MwZr7BnI;wtdcM_OCf`tU@RqLeu#%Md1sTCT6C2P7?Fh<-B-gvb;w)M_8lE9gMMursPxtYms_9o+U?ZDzu>sd?MJ?<%jDDNv2=}HttG%s{M=kC4Gr*WOKtnlR2R3;HyVZZnB)&bZp()S=TytpLPn1pN|J^gw#H$8toA3oK^Ik zFXrLtd6vb;^$N;tjxIaxa%f8bnvO{vlQ=bHSVV5QYb1Z*xt=N}Qt{f;DywTMdjhL# zkkbt#KBI)20l7DM=43bAnRb`UDa@)=<>dE(vh!xe_KQCLlTGktKw-M)7pa4tAYtiGhsyps{rVT#vl%6yv)Gy{)^%9I)&J+tD^&O(8Y zc8Axj)VdKV>A?2HES zIOYj*<_k6T$e-1|ws7s6T~8|Nj$L^3+pfFgVDH4e-zV=puV(Z(N3L@Ay6UN-8L6A3 zdL8v0X0GrGw{^38XS33{b6Ia%`id)A$-z>+Zi@p_S14UBmuy+<+#~sl)48Kv;E{~; ztO+YUJz_3#PfHVd)uZv1t=?vRq)N)9zjN0_d^~A;TdU?`@y^s$MLlgfnxUq57Q_c! zDda8BGQGRv-CgiJS)bw&3$Q^QSr$z_9l|GNojkN6LN0SnTFmxxhG}!eW48K@+SlGz zowbhJBD%NWZPjDD8*8s`eeIroo$uN@*|jlz_xAf7EL!vKw_Vy^mWvH)F4FI=t-HQ; zbz@wpPO7H)C$rc>N3|Efj&)1vWNch?oD0M#SObEE0)DG?MHlJ^`K^}H0SOeW$yiX6 z=v7hZ!L6w!BXr`Yk&ou~0F$V8-Fte$x4wLG_f;v`vvwxfGWc!g%V!Am%Snl?w?dE4m`Bv)SOjcR+-$|q<0}`w!?;| zl}X&oHkqA~ta+(u(5vIHtWdYi=|`~F??jWcMz>zyy2MoJH#76o3lkYd*F)ZqHdO8q zJh@w{)>V9sl#8uHGRr)Yr)YY{4x9YzA=blTh)my8Wk>wB-mC3f( z$-DBqa87B^!=tIo9$i!Xel=$LoUEB!_?%P1Ojo6Ty6PF^xFGYBs^YfYH=0&X7G~Xc zVx}jb7fZ*J#1~K0-GhaBm3@ElGDVv*_22oecd~Gm+HA+GJ0{w8ruezGoQ^d~zqvMO z(*Ef|3d?t2I=yao!L1Daj&l#aUN8Ekx^MrPy2mX|mpm#zU32p2ep2!!Z^f68jQ1x6 zS6M2VFni?foYJ!C67!y3-NcPYwtX>Gk>oWOWb07b5$wEqeT996%{7PD@6frtcs2WuW*4R9uUZvu zsXY@q6yhdwOU%z%Sy*|-gyWn$`a=8WE6-ar@yOz*OBALp_SR5%H|O@2{|p;CU%ZQ- zw*S=qb#hRY^q^zW|DQ`#WS8lcWosru2}R)HuIbKEsF`xxZ>zy!s58; zebcp;evRvO3lCnHAyuzhX$&byH9gznZof3pI4n5i8X|DJUm z?`H+f>{3|ycE{Z}!NJcQtM7aj|KzFn`u!87s_Lmv8((gJEl_?vey8pcY0(b7Ec5Bz z?BCmV_h@)`d2{ESDBv{_UBkq>KXBbs*3K0V%NvB#5|@49WZfOOV%lO3Tj3)jiSJj; z{cK_BC*-_u@jU7KpJIRim^bg~&x9)7s)gqZ-tW>qBA|6bxo+X+OW^ea5`mn7JGz_q z@)pi1`yu4IKoMLtw(h#gYIR`lNu!6#yo-I0aAfZeRNi~`SYjRj^ea<7+N8}D{j&Cc z=y|>Gd#&gF+-S3V&L@X25})g{zw`fI|3>}jx{s{u#4dgk+8+!ytyzX8$)%Q0T56WJ z|MWnuoC@FAT*=HL*QZ~;ehp9FqIYS=PSw4YSDyZ5EH zzI-0Ka>ZAxFYEK3tIVDH>&89Ro#&oCC^tGjL;ueHcfpSwRit(K^q0+NTyZ6P$=*NC zu}7Sz_({5%b!70@%;Y_ita_+%8Bg zSn`8oN894qBQN_*B+7*Sxn8f9O4s_7A;uhgW_9BcU2Xl>0colgn;-p{{k;52^(B4Y z;}5H%B94E~E8nwdd24gx+Dcv)qn4FlByFN36ee=o_$luYdu))!H~+4(RIl5D9)-iH zD>t}!1~$1%Y~9oxboF1gTJ&Oh|5JxGg2g zs@~J5+^e-;XMS8k(XNv%-kQFSC*xLcPW<*{3(t`S7b?8I#(m!ZV)^NPKR3NqDYp`R za8Px_m)5n5a?e`k@$Ylgo4IYi5Qpf}uYyemX{KyRo-fvd(v&i9$J138?^I>!<<3~z zTw80bI^#%+nS}|TLF;6LUzI|sW{0Xhuk!8P5FE*>Iia<1x^<4Xmiu=e-}C;L#;5mt?Z0V$XFRaUgypaMp0A3>k1T2F z;JnDtBiZXXV_L!<^Cc$A%bgsv{A}7J&lHuvdunuEm&Ij5T3TVE*M_3S+)bw~^<+in zwQtsb>8hUklUeNg+I^<~&wF2@KfT}k&%OPtVy3Z-c7(0ayh^5$DW)@K5Obs=Qh2|ty#P2^rfr)iEoapMLBc4 zQ+T+;v+P{iI+NS0qlHcCW`0$dyJ%$H(&f=_tuHq%&ph0qTj|L1V-jzVha9h%5aT!T zh3hBl2MgAo|1mS`be!+e$JKwj_r0&47v}%v;-B@p8~j%k@j9;i9-$l3_v9VOEh_&m zYyGb3Q*hH=b2(IK-lDv)KQU2#N=0+@geN?3f6DdC@;cw8I{T;>`epN^O#Pg;@e_R^>GaOy7 z@Yv(|mdSf+)svO)<$u(OeY?f~#c4LLD9$VU&*g3tJ``~J^_?^x31cDtH^;+oUda-w z>e>6%d&AnEJ6!)6mP}M!m!egp>se>xrlgv!BC`9g$XS<@hir2^mj2Cu$6YX?^~{DH z#Sc%btXgW8A(dA;?Vek3&*_!ZoePd!TyaJ0>kkX@M=C9UeT8B@-Wz*sUYPlQcg}Ce z{|wciT;rLSZI3Q&-*Msffh*hNJj)$e63*D#)?_5zJruGt@S2~{r0>fm{@pC^Gv+W7 zlMjno_q1SEPq)j&7c;Mac-YC4Dt_L5!3l{Z{-edJC6P_jmxT(*1$C@*-Lrlg_nww} zKc;S1U#pn8|B`0P~^Y_l3q|M%J#u3^4 zp+NtL2fKQ!=A;{%6EBG-TzD(e-MN3==O9!=r7kfQM0F1b}BNP>5zY-j*s<6>++5wk0Jp}51B1} zBHbm+*Uo(3eZ2B}$20F-epSsYcOx@|l&AH+;yKdctF!ToSIw`wNtG`Wjs!U6ZpuvY zoSHq&=h%lEQ#+NEgqLrOmb!E+_x0Nix0M?=tjN*0%DPok?E700o5M&{W7R%jUoTCX%Cv)SDL?k z<*BOZcgnd~`RXzAr+ebqYR|F$`19@GxBPwjlYQN~uTR@-vfRldXWz2l6L=zaK?pUOHVb}eybWSU2$>#oc~Q-`U&-ibdVW8Yf6F242h>7-^p8K^{(bZ1U;E!bS2#XX&6F)M)9JXU zWKsUgKIV&S@AnAi3S3UEIiqqpFyhH&tyM~)?m}`dW%j|9Dz9fgnC^D!cKK}4lp1Hp zWp7P{RrXrmv1jg$ITe|y`SRA)69RXIH>{~E^f>tH<2QkV+v+N_Py0&S?C!~((xw&@ z)@>bn-bZIrw$8CUm#YigZue)zn09y;yojFnF~0wN{`(w}dCP;OCR}MRdvwo5Y4Izi zdx`3Y%|72bxi0nd?g#5^l4Z)?g=iQ)Fmc||y0<;^q^7{4C2td2BOL=~sx3>-s$5az z`9-6!UohV`Q$kGQiO5Tx_SeT{9&t`r)a%(<73Q|;IoqQx2d&pLzwp~O;bx9*+dbao zzCE)l{ElR1J}Ua~uj$^4`2MqZbicgLQB>s@F_x8TciVVzO-Z+`)IpaLkxUlOP9~l7 zFHh_&jTkqp6s7da=DEJq^xCsCM&$C9b0>4OU(Zbw?zLMZ~Yy(c#=}w zRX>>{o$5zkN}Rd+%i_$goZ0>pYOb70zq!)kS7&zGh6~^QK0L8gHhLdjd*sc&xeqU` zDe2}r64%xB;AM=gcJq>gl0LqP2QTiha^-g8Sr++gVbE;Huf2R5nb&%}4&aJ%^ir6q zA~khU)n#7WbIJxQm#)6#d)(-^+XK_!U}N=7t5)}h%BA*Xa7@k5v{L0(m)kZ=KI6`+ zn2eX3wyl|7_;IQvr{x+Iwe>0{-aj64iv4U4>6qE8?Xy^Galu<7t{)#YH5b{+oln2< zw&Uc6M@MhpygSV?Y3XujyN5GgB!(46dSA~9iiuvYxuZ|_#KfDP^1EKUM@rPOn)J9sl}%f& z#x&+k={`^AH{!STzHV?m>E-PecKMdkr1l)Eu$~eRo{3AJ&M!K@dqr*f6+5ft?vV>L z&-Sfc;J+d1=Cxn9;{|Fj+l91lZcbOL5|}xt+Of z&(Ybgr#HyfZHxIB@1LBYa%n&Bx8&K*GZmV%Clsa3UOnMfMTGmZ!)rgCIBH(gGGBP< zPPw;^mwqKxRd4Zlx}Ye^@bVVj8Sc?p_RB9X{(ZX0IZj31SZ%TVt-udrzFFIs?3d$GyY%g0XLPux+aGuvF~k&sc_nSbgHDRa8krp@m9+IcCuJMU{x zi2G5U%1`-E)E9KBcK!P8yTIT=mR$Vgb!w+fEoR#+OK@Gdx>J{T;g%=ob=GvHG!#AQ z+G?#-^8Jp>QJ=|+%WN9%?0U(5V4=%mmoydAMPgoe`pY)#=a}a|>;AplzfvC>{CoE4 z&V-BQvwIiVs@(DByf|mlQ-g?`?^NFW7}e6o;j)#9rsb59tlI}0q+ zns#W}-PvL)21lkmnVNseqgSi6V}9@b1MPh`tu^PK>v*LCs++F#Pjd2~xL>nEH^ad) zQFq_R?Xp|gc6WDjYMPxjnrWo=q^x4Ew8_-@W?@wUNt*u|JUoT8w3kX7&gh#s>E@z! ztCH9GP3*AdS}eWmMCRLw!qd#n_EP(2{i&`w*YNS~;*FNq?ml*x?76O?Q?~bdG558N ztJzjeI&w@+&E23JE9_f%lUlU~2yqx(d6)6H{gcQjld?pE00YNGx$SJ6*f`Bc)0=-+;i=3Kk< z{K%m-p*M}zed<=-oH@tAJB+hv@9&yJCoLB~x1CaLexAqVZm&V!dNW5;>5L;wo-BJK zY5Lox;!VMwSD8mot}k>`e9P4p`BteUn&rT;%{K#{KC;}*>$YpMjGn{lPtM0sZ=E&^F{hN)ATj$K1u|`z>khF!d zO?q~G>-Lv#6XwLl9kR?Rm-L*jWEK#|xwm2U?XDZszRox*s=GJr?8YBUC*ShVTeoZ8 zNpsO?x3zg{e!ALLx@+(LXW-vde4S_8j&+OLr^H`Q)vPiO{=|7HVqI_lk(f?PJ%6!{ zt2+GrYaS|0dSdsvXA<9Wf1$+_+)XDZJ-o#0llX9vkCu|Vk+XZQrfu%Rx|#bG6qXCz zn4INlp0exHB*!Ujx38?3Q*S}13bt=4RpD)G4 zDL8S()>$2b+fw^>CR-oa{-jeiG__B7MzdX??|%j_vDxm+8)K&lOOq>DfPJZw|P&81oxdc)|nfo^*q=k{GTBze7e;7;8QmjT?+cF zf8@jbBW3env&DL!y$gzn{=-GmKW1FbtDb(~^xUkrpjrK{oRj*dJ#LYb!YGr2sn<6Z@(lVEE`NO+2t94#~^zLRkbl_X?kGyOD8MyN=Tf8dH@{W>r@tp8{ z^VW#_>-5=PtIJ&yf6_S3r=?nU#?Hx~o?UyV+oqWNsM^1l)h1DFZcX(5hu{3~x8-sz zJM~`DO?BeqC70)TUdb|*aWPfMV?E)>>HnE&U**wX{cBD;U4J(3KZ9oPlpyI#-LZBt z|2D?@|Kt})Sf}?&$5{T8o%C8^-*YDnFPj?n#9n@s8~^mi)4zI|Iv2Sa&wk93Nzbs{ zreELlpJ7tLwhQNSJC`eOJO8m;sz+XJ@zIZauInEu;;4UeN1^?vHM{ujprt-yG8GrS z`5rTt@fCOZC*>7)I;Oq*=I1@>Kf|OC6WgAOeyKF=yJ&E;^oLk-Z}N}xKdsp%_r><5 zgiQRZ_M=^rxl*xc!i1G+2~SoWnwG|MaQ=CV{ef&k%T!*A(|SKF?;vES+IK z#d2A~9m6w*$1L_ImG5Y^yy37mMa8!0K9Bd(ZLgbdRkNf!>1khWmzPYL z$nO(X4bW1!Uk*Fzt1z9 zVHvafipf3mu85~WnHz82a!X$(T=-eKqP$FWgJerzX7*Xd^9n^CG7GP2XdVs->Qz5u ztfwrK#kQFB$l{6NM%MP5lFE1dKf<6Z$iT$J$jr>b%)rFN3_7ENfsu(>kVQyQKv+RU z$uN-Bz){pl*~uX&ArZW@ix{OqSQVc(vo{ ztW)3ibfz~2-uigSQu?@y@z1MM|L~tY&i%#6wfS2uT@W7%Y^{IRf2{^RA0SFKKD zrN*D+t=PJ9dMulh^*?sr#YQLcW*<`B#n00s^~|-Ozq#1x$iEX;cc#obEc9g6nngF> zJgDJ6TCD6Jr#$&V?<$S2qWdRrb#2|H_4Gsh@>@Twi#rypWoGO5%kb&)Tu!dk(7iM5 znSYk*a+$J^mkdwFC3UBK7wn#V(x2ms{~g00FCXswq#|&+fE%_yUAwK z&a+R}o%wKf>q6V^rtnYIYTb(#f5_atTV?N^P1pW2yz&eF_r;}5bxL@}igo9U^`>M! zQY}7HA;lA8W|w&vmo+k>9?9eZRk{~o)I!+AG*uIUou9Ov5#@?7q- zgoJEX`fcugBfb2@;uEh6`yQ=zSx|MFb={1w*Tmv2XNBIKreZ5oI5C)WkMM<~>o_x( z?Bia%%|f0@;*Z!WL)#eF)s3$fRn3_F;GD#w?rTcE+s<%G#VSv}b2cYg;-UOurJ64% z7uJ0|ZD8{9y+~2#K^3p5iFxhztg|AI6cm2Uw>8SkxOX&VqO8lJs*^%jy|UTZK4g~k zJ=3ZVm71-p-sUQFQrK2w^4g~&wjZ9friBF7o>ca?*=TfX_Z;zz;t!FFr+6L;6zN%i z@nExs$*;K2CSO)9Zt*jkQV_dnn!mg9#gp^;BE_1V|I~D3s07RjjQ%z`()Cv6`6;2x z=Xmul?p7E1=WUXe`ir+c`DY>43W>Pss~t{Qkl$+``_kg z>;0a#7bS11PcJ&WGx5%bcRqLZFZa#(aB$1PW?7?}+4UcidY4_!Y-VHWU3NL+!o^1A z%q_>?dbrfYyXdXS@NjzBh>88RU2fYhYUBtToLJ|jv~60b*r#((wEr^%W=(vxz&%*3 zJn7gcmoojPAK@y=*$1U=>vZ?e^ZBtX=!dDjT0z);DE7WL9K4~4Ax~<$&>k+g3q*tvM1*;=wZGAH3 zYCNlOk^bd#CsX=*|1X{DkL!F=~mGfs((cVmKH^(D93NvR6Vo# z5%cHRfL%KUH)m?gbm`vw%#X&rCgz0>14db!*d5Ry>%u%Gi3t zG1uCn2bVv5@Y60>73Q(~v_^VUX4WmwgsIitCDEtt<=gyLx*S%Fnf*iKV*0euWdTYX&gL!N z7ODH`{H%|cZTwY!PH*MXt9vA+@gOQfNju}1&ZNFaZ%v|3SsU6v>^^Mk8_!iy|3n}E?f9n@I4!32 zL&|ygYv;d6-fpg0n7R6xLD_=q2TKcEj76gEEIt2veW%!cn}oXOCx5(pbYJ~H!`z5n zZkoq!B`2%=XSlrl{}Bd#Xr0f@!o1PyF_dMrhr=>f2;3r6FU52 zSH(-6_s`e%3q}4m>wmK(_rxmMoPfishm<~TzW8P1pXu>|ySHy=k$bWHvb|2}aohYK zYY)w;yCd;he5!(fO z>u-7;&0haz(y4>29ZRnVKIL$;I`28u3@l zm(8np{kPmwzjOIl%kU!?SAA8_DLW|eH9f{`i+jl9{E~l}{~3<_Oa0GqWbu`|m8U*s zy{qbcZ*p(Vi?)+%=AV7{q4m}6J<31*|GK{F|JMIh&PL>wtXaPJ%58u6{=HWc-~27~ z!IhiGI1c{Q+IT4WN}l$Wq?qY(8~mj@lD~Ov7vDPDcj+AI-Xl?l2hE#;6qS~#X4}`{I?;v`?u{RrFk}le;D0cd@C+vGY^hWq5XeImwpyVB?dCk>M;= zrWZvge$Y=_7VTQ><6qP9&%)H?>v6U_Q@yu8+14ZG&U!alx!{;;(;0)1cR9MW^hntE8j+)( zpTb>z`MM^4xw3VVsT03}%d4<;u1nV{UJQ!VZ92}n{Y|RA;fenY7uQ`owKC!2*;|_& z`Wq!*_xbvK))9C(VP4?LQ|5-=+!Jq#3!RRuFugg&%fi)0V0KRBLGD5!;pn3B*}BEj z)!ym{bIgtQ_gH1Gx^L_ncVdF_gSeBnt5w&Re7>~vLF>w&#$q|DabbMNln>sRZED}? zBgT8%SNL9R$2y+n2fcd2)qWg5csJN};t%1P2U98)PZ?xQGnMw5cC|^HrH1?F>S>p3 zzN9_txOa4w+?rq0j;i-e`z*xIvE2Wz$*;WYtRIwP1T~Kz{CMF0`KFhTSni+r&v0UG zf$70|tA&b!AN11}MYleF3{oabqgNd_vp+7Y&1@k}?jgsShKB=(T+&5=v~&$I)UIM2`HQO+`3oMl~T z9jF@O^|Gr{>Y$u&+QvXRn`tp`w_Lg`vR(M_C#wmopM-R+@wn#dnK=DGfLlRy*@+Js z`PG}84vC50%0IF3{rQ-b7$H-sc}IMGH|=xkJDIXLW})nwX*-Qy^RKymVz0wLSFvnm zqXM5PoJXz%YRd)s8n0QqY1^q2d$^pY-}=wswm4{oskybJ#D|3brr-lhykaDcQcga( zIeCj##}y|Dg^tB6;kM?=YCenAp5(bb@rx;K2hMa?QFl4p|ta zzbR7xJ9(CSDfc2(mA*gr=~s?hxa6GN5o;lHN!Ta;n!Y~SKB$i#$D6vUwZjgyzTMgeC92y zB2vSitV>yzC8_wdLecWc<&*nWR1fah`DB|-Uuu!Yj^GomZz z^e_ETFLTk>=8>l{8(Y_PRXY~_%}URFQx0x%a(UW#<5M};gEt=nm)*Brb2)5v#LK4A zm5T!xMm(4|FF;jB@o>+CPoWQ-ejDl68mMUWCW;yPeEG{?f6}dQ*MwIsa!=gc-bkP5 z^O!5RVOpbEw#lg-AC#sk*3OnJZ&YS-*RKwR5JUsQ$6hIlw{_Yk@8Hh`%B+H@jt2SlW=Nn*r(nlPZnj~n$mfir?+9lya$C8_xTzf0X_uI%aJ> zv%h;{-t=D`d8HPhfG;&>==Grr%|5i<@{ou zmuprRF8ZM(>Ri)l5&Gb+wrbO^)`QJTmBI(-Y`Xc@Zdv@stk*T%DHpYTbL*d&oX+8} znfO6(=6BD@f~)F-bo@VUP1>Rn>hK~O#a}LQlV-iwSU?Q zkM>D+c868G=S^O{YeTPUyeIGa6N~TEOm}o?J;hPA_*MTZv6bDy6Ma3!JRP5G587b% z#4X;2v%s^!Q}b1MN!MZJ$Ln~iB^4LCtBR~inVPxL(Ph`(BP%Don0RZFbxuIi+AeEP z+xx)V2AftFk|7U40)rw^GEXPx%KmAF=3Y&kw!0Z+7R2Ih)R^S|_P~i#qS?T-X-K zf3#Y+DDcPZ)Q8h<$I5w^&OH`xw6iN|=I+H4cND4pF!}O6>0v_u?Y(I#HPyO#hNpC| zMcuT$_RmEjtm{aNo0GxKO>Wo9?!Q?kEq;6MY~@LIi+4WGSgLI^`%2C9*H`QXf35%0 z`fBC=BlB0P%czvia}8Lb94XPgBwWdS^G=;7cTSkySBj}!&+;c>-3;}4vrby9QCxpx z;#Mw>KsRokDXA^)$6R_!`X|c&Kf(|$z`)4N#LUdj%EQXe&BDaYz{tSF%p%Atq$sRp zD4<~E7?@ZnVqn}jkxf+DDX6$)5vuBc-Be?5bj5zDT0x%I7_w-WOxh2(e7l!XYzDvLGRXAVRnaxe2 zJ5~#(R-C<-uCrwI)`O{!);L+4mt489`)Eqx6?GfArJBXt=e4XW+InqWtZL3_v(%%} zMpsXFZ77Jm^DuSly4P;+9tj)G6%k5^?j{()d5@7Vw(4Sj{EX7wqB%E~iMod> zXUAN4`5^m*q~x;If29uX-+BCp{+E32f6wI~Y>bF+NL)J6N~ht{rbBN&L@f0+V{CWT zDcK}uJ7o>)vOA8O4!zPc;IWEKS<-pF`elT7U!2O#1f7M4GF~3JdN!xYLh8=j9T#q7 z?Z3E3|M#5wre=mkO;%RUUN_Tnt(nbT-ydGz;546iQP(`fPtnKs%-iBTn>E&EmK=Zb zRMR-IvvXB53rcEz>dOm%Pb~HiT0YIcct`S_7qdJ~CVs2X;buR$V&#fGTTWbmqm*s3 zyY%vgSPp|%W(K#bykp;g)O?fuXzp4|IX&6r8zBOeQymTo(a#nIZJLA$?ezZJU#(cMfulk*5;)nm9vp<+9krENBoT<8W_X7bFx5t?k+uH-a!2-Qg z?%9X^?+X8M3)d;O1cq4Ic!imq_Pd^%JA1*72e6PVQD_hMKzgzrgV2OL*aaH^GpT~QcWVSmVzWz*S-XCM} zHA@mLPbbbj!}9ILhMlieYc`$q6rT5e*So^gN2a#@zTI&(Nc_};w=L&Vzr2f>bMwDL$=T{Dz@MJHp_OC3szSExT@Qc+qweKBg_d3S*;`LXHW&AyN#pENib5|~| z|EuM7TV~B6se7_%&OeHhuQFu)%1W1yJ^mw1(XJ2B`6KGV2 zCwKqG#IpYk<=1~(S{ho|pVINNm>Fptb?oXM2IILFE0?8nN2b61-1mWV&vEVTU7L5B zJlat#x1oQb>o=#vvi}(*?*|<@|7GI0%Gz`F5$3ODCM0bsGrAmn?6rK4`8wNfQ}x%;r}SvcA@vPIF9h zll$;&q0cAHjcmO?WU6A{zV%ivF&dm3wSz&wqxMCk|helKs!{eq*@*pClb-`)lq&CSUdGy`LBcfBc52K=3G85zxR;$A&1h#yAQsN`w^6MV^8n_ z?i=6E9B}-5^r`7LQ|--%eq8*9A0PBb7n<=jg<>j}nXbFX%6bFA6;mc7HA z>*=f~e#_XSvSvzDZ$0~c?efy8ak5wL*s1^eyw}cu3RBk{{!3Xi&e=BXei!ne!CL>e zR{h-Ww`vuvO|`=BW=|HoE&uoMn$?>=eEMG7ruCmeqW;}h=g+ynritG7t^cL+ z>eI`&e`__9h-%(7@$F}`c#gSw2WFlrSG~1=iq$nYt+2a!=93>rmrnh?&hONl(;UA< z>{E2~?w_}Q6|wX`!#($#5&JKy{F)~CMPuKIxaYb5{@q_w6qF>&u4}n0Cuql?fH${( z=WJ0tQ}M{^P$`?(^^UuW6P}lzR1HoD^tc}4Yn)PTbz9=~x4*CT6G9gMcDVo8|wC%gE&V*eS0n*KAK zJN@m{ik~<3UCM7dXxI7O_r$BN9%jBt;j?$XUz%8GXBP1#(CwPxjH}L3*SZssVu?L@n^zHQxi<&=dbj5e8F z-NV**KySvMc+u5sc+4|qrQ7glmGAknG*+T=cZ=}OhWSAyruSYNN-nB6^~`SN(-*4b#hgKaxYI>h(P9IcYt2!nf|ZqBq~I7UlhwGQUe*V9m63 z+0iza#CP3Ol`x3O%1C zeK%b=+nqsMT4!{bk2OYTJ+yf^+$8d#rHlpp5uGEKkMG6;<|7X^Q{rRq0M2R zE-1`@AZ*zeuAOi_<7#%+R;_tW>?Yp9+KGwf+&gZE{;CuGyJr5x@COsFUf}96Pv2TR z@6U3J9ot?lTDD^C;nS<1-Eg1g{^GUpy7xubc60r^=6_<_lpoV1_)9j`xys7#%8HqI z>dnL}2G4qrAO9F#aboL^RhU4OV|Yvd+D<+r=6zKj1n4R+D~a~1L5Hr2WE#_5N;>lFJaN{jE9 zePiCXzN0Jx(>k@}ZrqAJ^yFsjj%i}{KU4m6{@A7-?yMsy*3L2S(_7zNw?ri7$;Y-I z<#jIJ;Hi9g?W?RaZ-TiM>W@zTq*de9FLKz$RB)TyvuWNpzP;1(Q5Kvv%d6{=H231- z?mXYEJKjhB{e0-{&8?F^X@2;1GkH>0dTRAz^W4h@d(_zd!}WTT!)^e?+zofKw4R&CPJVn-_~FXHhkpHu5kF`9Pm49$F*|FWp^pCVNPDII`z#{=Gd!LA zNiF#Cl!I#~+_IceZ1`{gQD4udv%b;`GKzc7DP%w3+Fcj}R=zyVI?z`;D^Kc0+~Xwc zJf^unL_=kKntW%2Vqoj9S>Zo#bAw~>q6l{{xs}u7-vFNzLuUj0CrW-JY@3D5yTQ=|L`!?-JXO+mqy2AHj*LOSdy?vVB zwLj{D?mLIzJxuwU#a)WKro?Y>E7G0GT^O}S`Q?TBJ2LZLyy*9;NT~SFFtzv3={tLQ zx6cpzng8H+efMOMgRa(#C-W3DmcEmdu~_4$?z*KobG|a`y5cRd+rBPOEPZ!fcUG9% z!|QSHH(U+uwqrKVJfPZe>(MK#X(g!%75l5chx7RG=Dxg}`0K37->m&FF4azLspZn& zw4ujuljf4%wy>uiOmCy_Iju~S_YD_Za(aH=wZ5r8BIX?p?)1C9%PL~7@nMSztY1R5 zRHVw5^pb!qPr@Wo_#p!@sF(`;XXMhluO#Li2Ru9zGOx5((=x$NjKKazQoDG ztG=SpzIRtJ-Cl9_lX~e}9tO>;DYls%5NC z-sMd3>GS!1@!)BJTV}r{dfvV(E{#}P-Pd>1#Wr01HHZ0{Ra(pV9m9;}4n(E%^_=DoJb+;=i`zrlZomAMf)4^|h; z)@=FPw(5>qlflpD8QG4dyROVy&wOcQY%YIzccqiuXG2``ewiHn*;wC zYdo<@R_RZjoT03q+2M1e9oD= zZ}f{l{l5KVo_EB-m)mA;_Bi+O=E=_FO+JTCop{vSBjXmgY4zfHXr4eDxZ9l_|G63JGm!P_3w42`lciNU)FzM?MVC*_QD!=F}Yy`LQ-yr6%ODaZ7@Dy~yMCMA1vWA6;%n z+|N)={xLPXan|Cb@6qkYL;o|lO@6Zm+>UIi`Mhsw-w(%srrm$p@;3yFNz^Yr{HvQh3jB9`88^%Ykq+VGXAGBtfxa&yl@}t$0Hyz$c|UD>Td|*m-5+yRMmS?rglT%az}!adw>6KPuE_xbK43vFwlBl`bDQ zp5M;6p5{|vYIe8N1Ea$1+^@}D(L_}1d~?0k~$ol3($NlQ#O zCh>PC=@&UiA5Ajl6BoR6;*pKiwiK<$e=YvDt?_>rknHy9Xrx-3$D;zd?R8q)B%)`& zZ+x2}n|7LK>8g(7Iw2dkz9|=*vqy~Ii}6gX%Djd7N0avn)b}R+XK)Np?NmpLAH`)#8&TCL1@teWSSg&U|nr9Ixy4|Gp;vMbO_#8%~9PS-iBz z`ncAzgR8VQT?=hW>{sr~k6j~}`C2UEsD#QJ?YnY&51khMW&Cfc^tPmL5!=sfeyx>1 zyZp+E{aNw9`+R@-WG?w?8lkOLv8}!9K4o3PK}FYgtQ< zMNQU@WtMW?)OhXTja47AY>b6dwi-UkUeepR_;{sR)uEVoN5zbrHi#cF+Gn?X^V-$_ z8G=kjg#M)2iQC_Z;?i5ZV%{dd6_1x!sDE$Z{P9fV?7VjoiENL|zUg=^+SRo#?OM*K zHxj&?o;@zI4$TwwZB6Q(v}&G5RJ^bbck9;sKFMv{LatVuTLhmu86$n9A#Ync`_cEG z4gZz)gk5>K_1&@~>B5I3rHlFQ+`h-nc0B2DA=~ALvtt*>N*B*Lyt*=Hc1zNCt15~A z3|2Q%?>UJlO6Dbth21;vR$26lad%!y{CcLJXLhE>)LxdJGgguJSA_+) zB>iW&as1~s<$inKBR!@Qj!H`@rzJ#J->(c$oRBzUamuRYHNN_t(^$G|ww$PaarC=G z{=)vxYpn0On1n8x)Mm5H+;Hdnz18{UeOssBn-l*V(`j-@b+#OXZn8c z?XJ~_G7HZ5ZVEZ_;#Rycv!m_qlN064LbraMGBwy}X>WEJFMH#AMLV&F7F+LKx|@{# zJmiLJopi^(i0+dmfm^nR3txM9t4-R~F<$uFn%ZZ*3wKpmEZb^6ai8Y$8Nrh$2Gw2H zF-ZM!vgb`!>8_v&7Z&Z<-QB2V%e>iby^cNmOon5Pi*o$8WvV8~UFEySR>E}4(O-U} zCqwJDtvl4so4w!6dcJWTpLp~{hAmO6Pcr+jjGbV9W6gzqLIz=JPPcz3ToCOp3BO@} zV@+b#hR#)w#D0s#?9!eQ{U$7#;mPLKYw4HG{&8z8&)*oikz4zX)Y?s1m&DdPUCo^l zeq(FIkt>gG%(67U!F7pkZBo{q9zHjbdtdI0+!wLzXSAO9&t_RUuae6=#^8q+zDT^D zl3=5|==-Ma~*Ugr+ z=alPQekL~hZ2Y8uiO=U9IdWb^;F;&EAU;R+tjxErcRQ~d@%!%CR`4sbgfa1wMUBdK zy^k6(x(`=vv6$xeEjDG+hASQwrk=qDo9A6lTd8;}Kh1h+>L>2~TXVI`CG50Hm3KtD zyimQQDYJOy9JxIJcN7#RcfcmC<%Tw=Og*zW*vF!iA6{%XzlCUK4@e$o0a#7oY4bRxVGS2SU>R*47 z^J~BKCrb~8dwB~Dm%QIzvhW!<$L<(YXOY9Va;szbR|?4-AA`7Y-cHG3#> zzU=kXgU^1d$(?y07jW}-{iNAXo6fsRq~;!;KK0W56fYL5^D>p&p52T;zoBer)y{AG z6&w`L)Heifz18z_ZN}Dy@LijBgoxbNe6Z|?(urTiGL7!+zteiYzmNNV^`%Z#vFenM z+d8g$U!L4#9XU%lDe>(6)Y;o79JY3peVxxTz4dq#U-tIhHdpuj$@%Pjlph=%nz0Mc zmz>nEy^?d~dQqV9lYd&LxYSf5o~6K48PPdN5< z&DTjYyM9HhD?*t@zt()}| zek=S?;fviK_UdVgsUW{dw!w##{)1dTj!o*Vv((%9{J37dzNGIVzJz<3xNmfZ_!90* ztg|w2i5&^)UhvF|L;I4x$FA&UZNE0lt~b4VXIJs@uI)uvpXRM=*mc!9Zb6T~_}#cb z--0Wrs?MIYP*kbmS2a@<*6Y=uG;R6~ArX^{At@D7&9}CzWbffuew=x4vcux0jHuV! zH%fl!YL4dC%1=R^s)hPnB-xQVS0#%UYf{ymT=4dGSrofQv!iTc#e5 z4Yj)?^X+O$xMP*+i)(JjD`gKF*=^72o|m&rH7%XbegFQ+>D8xv~?fxx!>u$Zc`zkKn z=Hkx1QA#f&7rti9_j&f~+NDhk_WW3W)gdR6CtFwjlo6S*v(n^_)pXI|$s27v zeXf_viiCbiX|uR@(49}N*za4`-Y-Xs1$^IxPYR08aBZEw-(rFa^Nd)w?GBbR`{j;4 zHU9W2N}=e5VDYRA4*xWtuU=Wf^7vj?_3{Gty=h{bdfeRdo>=Z$$GvEWo=o2d4!Ng_ z?>5ybJY29}XM#<}!<1jT6ARcL<{x;r&T94DtPAVCZ6^M@kU!-u!z;hOWmDRtyZ!}g zq+FW)YCX@H^7WHuwbZ0*%uZYp#FAs7Hp6#!aQfa`0SX`ef{oXR9@pL-_SS1<_S1c@ z=1tk{u&8#;!)r5RgU&bSXX!K@{I<2pqD|eb*Zfp)=F5b;vR&twE7q&_y?YaNS#+=0 zyQfc0na;{EZ3rq?zOl*W@9(?Dny>smIL4mVmDv7TSz^je2ES70bH>$YC=NPQF*8RwDdICJZh8J?<*aWize=FVNCwM^~9uI0;;%{{Mj zBqpBE&ggaNDaie@i*3<@AG+U~LZ6@9)o^Iti~P0I7ubk>HH_TG!mxIFsIKG1r8$aM z-3zbHoZcYQm~-px6Cdte<-AK`H_vR)`j`8U(e!Vl%i-0<%Qh}LxPUKclTD0%O=bdr z=kwat7u@73S|=P&Sh;AyjO{r&>qYZLcS$Qmf6?0zrCcpkrg^W-#nTT_4+-l^fu>!<<B|x(}R7`>w1sl$5@{==v<@A7%O)hnE&AH*n zX?{Tu?&#yUT6e0PVU+X_nicMFxV*G=zq|a#$Sb*5-C50g9)-@{v+4K(-*a_>47x|u z`B{_1llJ)e2o>z=tC~6Mg5<5aZ4VW8n@#6Rzq0AP)XHzm-MLwKjvWq~t5~3C`(j_` z>yyj7AMDQAeEEvm-|i)|XWpsgc8@x%yd^AyW6J4Xj>yAnLN_h=S(3$@I>lFB(^0dRpq-ki?@26D%^2+(cZNwraRcz%D!3c>Gbk$?D-Au z52JosGv)gP-!b3l9360WNx$UO=qP5_eP2#lYBjvgOG)f~VA0Jj`o2{=r6eYxS)}#s zS9PWrC$~qX%G&H)R5ESD8;5xt7Jqz_Y#r`f<@#aLfDqU%y%n5$CvN-gnQ5Ijz48i==Giy>3tnWhBbdAMBG;S5C*1drT1j~{m*r*d zI{fm{GQFIh+`B3N8C0)++F1RSTQ0VG#lrQyKP5_cr3xprM29%PoOS)%a%;|2x|}=O zG=5Ke)znk|$>rYHq_tw*HFs;2bFO~K^(#La*IOcNa&_gy`vQ?AhhFO1T;9VpY3t*5 z{;$#+83!L#Xr~zrG zi+B1|yU$3kJ)nK^!OWSVk59ge2^ZMqnD29Rt#@!)^|i1z{iw6XQ-8TKxi+O9+aACC#Pgg2!autTR6q&@|0#3*Wx*1J~msS=a1Ywy^K! zLRwSKu6ner{?$s^TThP`_ACou zAg}jbmzJ-#dM~XIwf+7Ir+HJ?oe0j-j@&xs^{P{+8P-_J&U|pDf+hR)>218F2b^YZ zIdfOG^Osok#=NWZlkTjH-rV|f*4CM;s}|O4OxVeG+>dW=*4YH+ot~3dtef?6;mqsI zp?c0sulour>~QG4w2#lbHBhIc`=fTh@*YbrJB`nC9|+f6@LxXPW66U6g^#cOs?Rn% z&9ls(=Df2sZ_}dHLUZ~ob5^PCU3Y)2-NJKQubrD+d-p`h^=B!utsE;C=Kij1eP3Wy zYAQ9W$*25c&SEk5gP!+lj<22+T&G$XtMc5#;i8B0mR&FPCS81@Eq>Qi@yE$_@72dX zZM~xOeEZaUHs32)?whXKGJD;tqML^M0_B5FhTQG(Y@1vb@xwC5P%Hi2%NZIAmb5BPs`E7U_mY>zg4@a;3`(!6Jve5Gr` zynDXlQ>~IN^=^%^w9pM;`52!3C2WfBbzRFi>!0sFaHp#p_8;Y)=d|B(*MA1HNm~!y z3;QOjbpOzk=r2aBYxudV4dQ0#YDt8C>$RD-x9MK)wb#s{Y7&00`;unM+9*9<8$aXa zvx2=_IQBkylfUTZv8yv$9(1YiEev~B5F_}(|3mBw?K8g_wi$StL{_YmneO{B(0Kc_ zw_#O}leU;g&Umvl-`pT-c5&xD%awyX99S2>*{k$YZ_=Y33vY0BY5dbNdlvV)>xbj23wJYg!XEAjn-ZB(Df>X+ z>6Qsw560RRTlxl-o_U-q*pWITizDvaBk4`Y7pSjIuU&g@Pd|U_rV}M+^SBm8t&Z3I@^n|J zuk!)kXbtZchMP>Lg)tSMdmUR9_B(Xj!H83O>rxmqmBTi%u37tSz0duuFK554 z_qw0;Wi6OGy@hj$?4!Q44`B}~i?(v6Dg3$?_B7gj&4&+>F}f!YmKT@DtW{gxJN3u% z%qs%VKRh$*k-f2#nLoH|)7G8Z^MCzksC;7V^ME1lTjDxv!G)3c%uns!@u_uTSYi?X z>IvUje%`NHylBozJ>jI(uTDmfw?uNEyHLK`^4-NM_v-K^^Pkt36MBIa(E(^n3M=qSxoHW`s*GdiP+yS$vZezp2iXFVUs8?%Dk+8_T3W?LHXx z>|ep(RLNy^VOwRi`N|%esp#(6wKsld!qH0EwB?@C#i_5OGkbqei})U~S2Mi0dqT-9 zfo(I_Z7%)1<#qVm&$FiH8w6|q`t|2ZFW=^+ce&%dW~{$@DzxU)Zf%$L_(*r|!*U)E zJff?5Q*8I#&YAX_iMRNT!1Cpnx2*Nom-GJoL$@;b?4uo@3S(+cI#jO6V0`haGNt<| z>){D~S3{eFq}fld{JLIiez@G#;%BD+83G^eSZA}R=4Jdc_ur?#?D)@6SvOt&y>?|x z{T|Uh>+J7w!^K#C#nkT+e_8nNL;O zU0#05e((P8O`EfmFMn;CmBE%K{*gO<`XYfJM!U2Z?a~G`^9TOIsI)MvknrtGcu+2swsHrrg}mR1+*)|H+r-F^8{M|`EwyIG$+D_ZMK z!aVAA85%Mc$W|?719zSLIuZY<#bMF4cIq{7}`7 z%GtZUb?3ghu&vV3Z|dx&+_GMRySsLLw&I>z$9d~3%e~}jVN=stcQ2mi^foN4;F-vy z(hP@=1Ds#^Gd5^c8tdjAofqrhby}wUtG?#)&VNUXbZSmqE?jjz>{M;{g0Nn@qY1MW zUOzs6F{{-^Zqrt_zpEcCb&~YGRdpe8>a)`u587$p;VWBpRy;;$vuxM{zx@fm&#Wg0 z%!hjE_&F1r`p4PS~wph#c`24Pm`9)d#_`A0BY_HloZEnrA9}B!L2krCP zJy{2|E~T}i$kck)wQo~S%-D1@Barj(hR+<@er5TF^Ur*Y?Am)RoO`*<_NOZ>-7WNsNl+Usr74tChog~+9x=(Y?!AEm{ z>pl|+-YGTr?B1R0pU2Of(Qo8rRrLB?hHNV1>GgWqMTW++>QnbTFEX{BU6;M{=d3jW zSY}!+n9F{XaJ%M{dgZ`pk@2%?Y>uVY)m>Zo zGq9aEf32i!(6?XLUlvsdrcc>jy_e1I9dD#o+nLn|H42_r?Q9iY#CUw-%6%HevakG~ z+Vp>X!tvSN?C#<-shb|2D|I<`Hp6WeyX*vQL#cI3uk4z(Eu86ST32|4B5T>}-CpN~ z54WrCU`YRu-JAOCjbHuFKk7l(S{#&Z9yE`Cj zf7XA7Q)le9&REMoGx7QD8EI=Kn12my=97%7x>&iYZvUFLtM97Cr|iG6#w1)d>hz*r z+5ualiXumG9lhWmk+NJOP7tg)9=@XY>z zre)dr>pP|Mp2bbQclBlL+wfaD>#qD`*HYgdG%n^abM;3qQ7h@DUz=bH zEXypk&2U%uy!Dy)T8dW^M@6yx1L2=0;gwGw)V!|llDR87t@B2#@wuyia<=tdS+u74 zu+iabUOb(%zRug{@%m}MwM1iIS>Mwvs|A-j*B+VmlvkI>$=&pOaZ^I;62_x!ZZDd5 zy<6*OlDI_OTwIRnzH|G@ zE8E>xoi~eow@d3Ni{<0Pv*#^Kxc79^(w2R9Gmiyto^(fKs`$QL+I-sSEGav=R=s^b zziX2VGkenG(lXw#lRpY8!b@e={GGjK1LvFWRnH9h?%Y~CM=&kh-DlD5xYrpEBI}Nn zMc$S8o3!SZ^w)f$y`PqMYd&&tlJq~cefz}Tu+NIoJNc6TFxko6v-%Wuz39}7H=^&; zR=1|GTP*u5Yx$@`IW+1!N415{Jiklx!=28o%h=a(zr)ONqOr1F(Ag=HRwu(E=RIq% z_Bzc`b9{rT>6a}tqpAaLn_bdlEO7EP-5z=^VlngNuFDgSE9~~NSSDR8X|BuRs`q5& z0_m5}n&dc&N}3+cKi=SecvjThskthVVcsuK)>TigobsdEKVnDV+3)o%8mL2zl5Mf4-5t!p1_eR6r?CF^DAEhL{MVk zPu0}^w9B_sek__gGx>tFQt`2(@49O)n7Msz5Kdgbye98eSjx5Xs@*aC?J5WNtD5mE z*B+1hp<@*kR=H5;qnl)w&a?HG+E#8`mA3bFx~hvG_kA6uvgkFe8|Pk|xJ&!Xy7f=i zJ5JiL<#SU;_Ozc-tOpDH?!Mx_dN%vQgwi!_f^V&yS>iT?_H-|G{ znI+TVzO!QaF}2evW}lmm%3SGH*WNWHf8r8pU5mvkmrNIwE%t2PGB+ni?6OejxmD#t zUlU`cZNj{?j@fydh-pMGS6i}f>cxqF7_O|`$Tg$+LE>h;{chR(8S%f;l$`CnCoZ4Y zbAEdFtZl{2!u_Y5Qz9PT*`ai2SLyQW7xLqSgdeS)b5y&ktM1&=v!_blyWWdEDF4)D zYkHrzNb(Vt^OLtSNLWwFOO}$q)B5K)$Um1?C_X%=Tj(ORS+20c(rfCoqKLZ}=AD{Y zcPMO*rHu7Yv)g{!sT^e?H?!C(!on*4}_S%nefdO|e`R-lyBP;FM zTkg{@w7Kg87Ro8hwr8DX_0GT1dqB6y^b51{v2C1NLYvc7s-%~vnrDe={q_FI-BIXn z{n{V(Rp-3ry7F1~bjgiPG4nLo+>`i4j6d0~y#3fdX>UhTPN6|Azt9%9 zO0Jqo&vGpu=rp9R*LDV@Y@2o#17M(iEG&PCK`>&8>CwYHOvvpAV_B9*<^u#pAlfx?xvJ zb7Q1L`NR@K_VTH-vSJT)&lU@_K4*PEDV#lMjKYW|4 ze0Gb^e1Qf}bJ>9GWCrtM#tGP=F!)x;S) z(>hW=ge_lD$hJoIP`IZRI>*=6g!#QLtp52G732dyMW$;-bgT9jcFX@pu(0 zv)#?Q$?J9q^F|I=mP+UF+-rM1F1RWPZg{VD;AX^y3tLy@ugtX4o#c>XQW&l9=gi~M zvJ>K-OEX$!%x~G;tfb?^2WCvORM^Be`AYt=4;mFD?3Vv?^WSFMPSSXV;gbRYB{w#m!i&7B{sjlzZOq z_Zpk#7A@Oke)n&7u7$PUrJ4TB46+$Jk9%vzz3V9JKCyEBw%NV{lQu2=?LK?gi8=3z z?=B9Gx2gPGw*I11JNMG1dCQ)(ux#nwA-1bzw$1`pokQg&a$P^P_Q|dYb?2P2q1uG9$HuBbFk$x~nT>iSiKZ$*aNU&pHEuF{*d(Bx2s+3B^{HPVZ|tiCKN zP%@p%;#TgI*s7qthH-P&`-CskzAX9@#F*F(}&0t*4D2An$!wj$8+m_PntR*@mx;gzKI4quj@Tp zvUYNq^MdecVOtG+V^|8ZHknm7^iMwaeb=pwC978*URu*%Qv#aBd&OXVa{xy8+g`{)Y?QC}iqr4X~FA1&* zXXUBw)+{>BU7P(-#`o9sLvBT_6^~tyKU&wM${fxII=j3(qAHhHuyQ`RQmWSx zE3iwkfV&|^ob{l!^}$DO?Ofid5IUHxWmyEO7li+)p|@$?PKA9CfEJh*1^^m3Cu%iZdU*(+zh z2{&YWHS1CQvc;8Yi`JE=cA2;M?O80cd~(X=3s+4iOgrS(qTg(JbwPKC%G_(wvybM# zYZZ@F^CZX1;sl)WlSA+vU_Mt2tB6oMsCy;jjK46}9*C`g_$sOulNfD$hT3VwymX zf7Fl4!~Ut)ytthHGhExWXER6R8|U1R?^V~%8Rb-mHmz>*lT~OkbX*swc|Lj5w5gJ6 zzl!3*+xkAu(ehTUd>8GbU}X@{ap?K42`3pd-YVv#UB9-aL06f}P}yBnJ2G3_x@+nQ zvtOTl{vBJsd+VL`AD-H2x2v;W;9jsx^HwRVx)Adq9-l+w`k&z3Tva!2h)!j>{PF>TXpaA9`hagt&8V&Hy`{EH|gHe ze2vu0+Ov&)^fzc;Zr{>mo)a3ak{9!C-^vS2+;bOopGwQlxsq^rvC;mW`8zKh3Cr5G z=0vXaT(-l07TTr@G`VB8OssO@EPW%=*!McjXzgF8dkWS^<2W~0U3KYyw7qY8^pYa$ z!&C2XJzw4sSmai7?DyoE(w@G2k9rJueX@3azqMMAZP5$UMF)y9Plx&Q&W!W<=^Q`j z7_9j!=)|9N^Wh8&UaNFvmp@LGZ?$(Y%#|⁣))bGPRS=T|16d=zQ7H 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 Q{5c = ({ spent, budget }) => { + const { colors } = useTheme(); + + const styles = StyleSheet.create({ + container: { + margin: 10, + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center', + }, + text: { + fontSize: 26, + color: colors.primaryText, + }, + boldText: { + fontWeight: 'bold', + }, + }); + + return ( + + + You have spent {spent.toFixed(2)}€ out of your budget of {budget.toFixed(2)}€. + + + ); +}; + +export default BudgetOverview diff --git a/components/stats/BudgetRemaining.tsx b/components/stats/BudgetRemaining.tsx new file mode 100644 index 0000000..1eb038e --- /dev/null +++ b/components/stats/BudgetRemaining.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Text, StyleSheet } from 'react-native'; +import { useTheme } from '../../app/contexts/ThemeContext'; + +interface BudgetRemainingProps { + budget: number; + spent: number; +} + +const BudgetRemaining: React.FC = ({ budget, spent }) => { + const { colors, theme } = useTheme(); + + const remaining = budget - spent; + + const styles = StyleSheet.create({ + container: { + margin: 10, + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center', + }, + text: { + fontSize: 26, + color: colors.primaryText, + }, + boldText: { + fontWeight: 'bold', + }, + }); + + return ( + + You have {remaining.toFixed(2)}€ left. + + ); +}; + +export default BudgetRemaining; diff --git a/components/stats/CategoryProgressBar.tsx b/components/stats/CategoryProgressBar.tsx new file mode 100644 index 0000000..4e49975 --- /dev/null +++ b/components/stats/CategoryProgressBar.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { useTheme } from '../../app/contexts/ThemeContext'; + +interface CategoryProgressBarProps { + categoryName: string; + color?: string; + maxValue: number; + currentValue: number; +} + +const CategoryProgressBar: React.FC = ({ + categoryName, + color, + maxValue, + currentValue, +}) => { + const { colors } = useTheme(); + + const progress = (currentValue / maxValue) * 100; + const progressText = `${currentValue}€ / ${maxValue}€`; + + const dynamicStyles = StyleSheet.create({ + progressBarFill: { + height: '100%', + width: `${progress}%`, + backgroundColor: color || colors.accentColor, + alignItems: 'center', + justifyContent: 'center', + }, + progressText: { + color: colors.primaryText, + fontSize: 20, + fontWeight: 'bold', + }, + }); + + const styles = StyleSheet.create({ + container: { + padding: 10, + }, + progressBarContainer: { + flexDirection: 'row', + height: 50, + backgroundColor: colors.elementSelectedColor, + borderRadius: 15, + overflow: 'hidden', + marginTop: 4, + }, + categoryName: { + color: colors.primaryText, + fontSize: 20, + fontWeight: 'bold', + }, + }); + + return ( + + {categoryName} + + + {progressText} + + + + ); +}; + +export default CategoryProgressBar; diff --git a/components/stats/CategoryProgressBarList.tsx b/components/stats/CategoryProgressBarList.tsx new file mode 100644 index 0000000..566f352 --- /dev/null +++ b/components/stats/CategoryProgressBarList.tsx @@ -0,0 +1,61 @@ +import React, { useState } from 'react'; +import { View, Text, Button, StyleSheet, TouchableOpacity } from 'react-native'; +import CategoryProgressBar from './CategoryProgressBar'; +import { useTheme } from '../../app/contexts/ThemeContext'; + +interface CategoryItem { + name: string; + color: string; + maxValue: number; + currentValue: number; +} + +interface CategoryProgressBarListProps { + categories: CategoryItem[]; +} + +const MAX_VISIBLE_BARS = 4; + +const CategoryProgressBarList: React.FC = ({ categories }) => { + const [visibleBars, setVisibleBars] = useState(MAX_VISIBLE_BARS); + + const showMore = () => { + setVisibleBars(prevVisibleBars => prevVisibleBars + MAX_VISIBLE_BARS); + }; + + return ( + + {categories.slice(0, visibleBars).map((category, index) => ( + + ))} + {visibleBars < categories.length && ( + + Show More + + )} + + ); +}; + +const styles = StyleSheet.create({ + showMoreButton: { + backgroundColor: '#EF6C00', + padding: 10, + borderRadius: 5, + margin: 10, + alignItems: 'center', + }, + buttonText: { + color: 'white', + fontSize: 16, + fontWeight: 'bold', + }, +}); + +export default CategoryProgressBarList; \ No newline at end of file diff --git a/components/stats/PieChart.tsx b/components/stats/PieChart.tsx new file mode 100644 index 0000000..f3fe67e --- /dev/null +++ b/components/stats/PieChart.tsx @@ -0,0 +1 @@ +//honestly just fuck graphs \ No newline at end of file diff --git a/components/stats/Widget.tsx b/components/stats/Widget.tsx new file mode 100644 index 0000000..b43a64f --- /dev/null +++ b/components/stats/Widget.tsx @@ -0,0 +1,63 @@ +import React, { ReactNode } from 'react'; +import { View, StyleSheet, Text, Image } from 'react-native'; // Add the missing import statement for Image +import { useTheme } from '../../app/contexts/ThemeContext'; + +interface WidgetProps { + title?: string; + text?: string; + children?: ReactNode; + image?: string; +} + +const Widget: React.FC = ({ title, text, children, image }) => { // Add the 'image' prop to the destructuring + const { colors } = useTheme(); + + const styles = StyleSheet.create({ + widgetContainer: { + backgroundColor: colors.widgetBackgroundColor, + borderColor: colors.widgetBorderColor, + borderRadius: 5, + padding: 16, + marginVertical: 8, + marginHorizontal: 10, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 1, + }, + shadowOpacity: 0.22, + shadowRadius: 2.22, + elevation: 3, + }, + widgetTitle: { + color: colors.primaryText, + fontSize: 26, + fontWeight: 'bold', + marginBottom: 8, + textAlign: 'center', + }, + widgetText: { + color: colors.primaryText, + fontSize: 16, + marginBottom: 8, + textAlign: 'center', + }, + imageStyle: { + width: '100%', + height: 200, + borderRadius: 5, + marginBottom: 8, + }, + }); + + return ( + + {!!title && {title}} + {!!text && {text}} + {!!image && } + {children} + + ); +}; + +export default Widget; \ No newline at end of file diff --git a/constants/colors.ts b/constants/colors.ts index 6b8937d..a707f73 100644 --- a/constants/colors.ts +++ b/constants/colors.ts @@ -12,6 +12,9 @@ export default { elementDefaultColor: "#E0E0E0", elementSelectedColor: "#9E9E9E", accentColor: "#EF6C00", + + widgetBackgroundColor: "#F7F7F7", + widgetBorderColor: "#E0E0E0", }, dark: { primaryText: "#FFFFFF", @@ -26,5 +29,8 @@ export default { elementDefaultColor: "#535353", elementSelectedColor: "#B3B3B3", accentColor: "#EF6C00", + + widgetBackgroundColor: "#252525", + widgetBorderColor: "#535353", } } \ No newline at end of file diff --git a/services/DebugMenu.tsx b/services/DebugMenu.tsx new file mode 100644 index 0000000..896a667 --- /dev/null +++ b/services/DebugMenu.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { View, Button, Alert } from 'react-native'; +import { addCategory, addExpense, deleteExpenses, deleteCategories, DEV_populateDatabase } from './database'; +import uuid from 'react-native-uuid'; + +const randomColors = ["red", "blue", "green", "purple", "yellow"]; + +const getRandomColor = () => { + return randomColors[Math.floor(Math.random() * randomColors.length)]; +}; + +const getRandomName = () => { + return `RandomName-${Math.floor(Math.random() * 1000)}`; +}; + +const getRandomNumber = () => { + return Math.floor(Math.random() * 1000); +}; + +const DebugMenu = () => { + + const handleNukeDatabase = () => { + return deleteExpenses(), deleteCategories() + }; + + const handlePopulateDatabase = () => { + return DEV_populateDatabase() + }; + + const handleDeleteExpenses = () => { + return deleteExpenses(); + } + const handleDeleteCategories = () => { + return deleteCategories(); + } + +//for some reason this function does not work + const handleAddCategory = () => { + const name = getRandomName(); + const color = getRandomColor(); + const allocated_amount = getRandomNumber(); + const type = "expense"; + + addCategory(name, color, type, allocated_amount) + .then(() => Alert.alert("Category Added", `Name: ${name}, Color: ${color}`)) + .catch((error: any) => console.error("Error adding category: ", error)); + }; + +//for some reason this function does not work + const handleAddExpense = () => { + const name = getRandomName(); + const categoryGuid = uuid.v4().toString(); + const datetime = new Date().toISOString(); + const amount = Math.floor(Math.random() * 1000); + + addExpense(name, categoryGuid, datetime, amount) + .then(() => Alert.alert("Expense Added", `Name: ${name}, Amount: ${amount}`)) + .catch((error: any) => console.error("Error adding expense: ", error)); + }; + + return ( + +