diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index eca288f..7fc1436 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,5 +1,6 @@ import AsyncStorage from "@react-native-async-storage/async-storage"; import * as LocalAuthentication from "expo-local-authentication"; +import * as Location from "expo-location"; import { useRouter } from "expo-router"; import * as SQLite from "expo-sqlite"; import React, { useEffect, useState } from "react"; @@ -22,6 +23,10 @@ interface User { email: string; firstName: string; lastName: string; + residence: string; + workplace: string; + latitude: string; + longitude: string; createdAt: string; } @@ -37,6 +42,10 @@ export default function AuthScreen() { const [password, setPassword] = useState(""); const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); + const [residence, setResidence] = useState(""); + const [workplace, setWorkplace] = useState(""); + const [latitude, setLatitude] = useState(null); + const [longitude, setLongitude] = useState(null); const [biometricSupported, setBiometricSupported] = useState(false); const [showCalendar, setShowCalendar] = useState(false); @@ -61,6 +70,10 @@ export default function AuthScreen() { firstName TEXT NOT NULL, lastName TEXT NOT NULL, password TEXT NOT NULL, + residence TEXT, + workplace TEXT, + latitude TEXT, + longitude TEXT, createdAt TEXT NOT NULL ) `); @@ -85,7 +98,14 @@ export default function AuthScreen() { }; const handleRegister = async () => { - if (!email || !password || !firstName || !lastName) { + if ( + !email || + !password || + !firstName || + !lastName || + !residence || + !workplace + ) { Alert.alert("Fehler", "Bitte füllen Sie alle Felder aus."); return; } @@ -105,8 +125,18 @@ export default function AuthScreen() { const createdAt = new Date().toISOString(); const result = await db.runAsync( - "INSERT INTO users (email, firstName, lastName, password, createdAt) VALUES (?, ?, ?, ?, ?)", - [email, firstName, lastName, password, createdAt] + "INSERT INTO users (email, firstName, lastName, password, residence, workplace, latitude, longitude, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", + [ + email, + firstName, + lastName, + password, + residence, + workplace, + latitude ?? "", + longitude ?? "", + createdAt, + ] ); const newUser: User = { @@ -114,6 +144,10 @@ export default function AuthScreen() { email, firstName, lastName, + residence, + workplace, + latitude: latitude ?? "", + longitude: longitude ?? "", createdAt, }; @@ -162,6 +196,10 @@ export default function AuthScreen() { email: result.email, firstName: result.firstName, lastName: result.lastName, + residence: result.residence, + workplace: result.workplace, + latitude: result.latitude, + longitude: result.longitude, createdAt: result.createdAt, }; @@ -202,6 +240,10 @@ export default function AuthScreen() { email: user.email, firstName: user.firstName, lastName: user.lastName, + residence: user.residence, + workplace: user.workplace, + latitude: user.latitude, + longitude: user.longitude, createdAt: user.createdAt, }; @@ -227,11 +269,37 @@ export default function AuthScreen() { setPassword(""); setFirstName(""); setLastName(""); + setResidence(""); + setWorkplace(""); + setLatitude(null); + setLongitude(null); } catch (error) { Alert.alert("Fehler", "Abmeldung fehlgeschlagen."); } }; + const handleUseLocation = async () => { + try { + const { status } = await Location.requestForegroundPermissionsAsync(); + if (status !== "granted") { + Alert.alert("Fehler", "Standortberechtigung nicht erteilt."); + return; + } + const loc = await Location.getCurrentPositionAsync({}); + setLatitude(loc.coords.latitude.toString()); + setLongitude(loc.coords.longitude.toString()); + const addr = await Location.reverseGeocodeAsync({ + latitude: loc.coords.latitude, + longitude: loc.coords.longitude, + }); + if (addr && addr.length > 0) { + setResidence(addr[0].city || addr[0].region || ""); + } + } catch (e) { + Alert.alert("Fehler", "Standort konnte nicht ermittelt werden."); + } + }; + if (loading) { return ( @@ -305,6 +373,30 @@ export default function AuthScreen() { onChangeText={setLastName} autoCapitalize="words" /> + + + + + + Aktuellen Standort verwenden + + )} diff --git a/package-lock.json b/package-lock.json index 9aa10fe..c5b10bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@expo/vector-icons": "^14.1.0", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-picker/picker": "2.11.1", + "@react-native-voice/voice": "^3.2.4", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/elements": "^2.3.8", "@react-navigation/native": "^7.1.6", @@ -24,6 +25,7 @@ "expo-image": "~2.3.2", "expo-linking": "~7.1.7", "expo-local-authentication": "~16.0.5", + "expo-location": "~18.1.6", "expo-router": "~5.1.3", "expo-splash-screen": "~0.30.10", "expo-sqlite": "~15.2.14", @@ -38,6 +40,7 @@ "react-native-reanimated": "~3.17.4", "react-native-safe-area-context": "5.4.0", "react-native-screens": "~4.11.1", + "react-native-voice": "^0.3.0", "react-native-web": "~0.20.0", "react-native-webview": "13.13.5" }, @@ -3529,6 +3532,153 @@ "react-native": "*" } }, + "node_modules/@react-native-voice/voice": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@react-native-voice/voice/-/voice-3.2.4.tgz", + "integrity": "sha512-4i3IpB/W5VxCI7BQZO5Nr2VB0ecx0SLvkln2Gy29cAQKqgBl+1ZsCwUBChwHlPbmja6vA3tp/+2ADQGwB1OhHg==", + "dependencies": { + "@expo/config-plugins": "^2.0.0", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react-native": ">= 0.60.2" + } + }, + "node_modules/@react-native-voice/voice/node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@react-native-voice/voice/node_modules/@expo/config-plugins": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-2.0.4.tgz", + "integrity": "sha512-JGt/X2tFr7H8KBQrKfbGo9hmCubQraMxq5sj3bqDdKmDOLcE1a/EDCP9g0U4GHsa425J8VDIkQUHYz3h3ndEXQ==", + "dependencies": { + "@expo/config-types": "^41.0.0", + "@expo/json-file": "8.2.30", + "@expo/plist": "0.0.13", + "debug": "^4.3.1", + "find-up": "~5.0.0", + "fs-extra": "9.0.0", + "getenv": "^1.0.0", + "glob": "7.1.6", + "resolve-from": "^5.0.0", + "slash": "^3.0.0", + "xcode": "^3.0.1", + "xml2js": "^0.4.23" + } + }, + "node_modules/@react-native-voice/voice/node_modules/@expo/config-types": { + "version": "41.0.0", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-41.0.0.tgz", + "integrity": "sha512-Ax0pHuY5OQaSrzplOkT9DdpdmNzaVDnq9VySb4Ujq7UJ4U4jriLy8u93W98zunOXpcu0iiKubPsqD6lCiq0pig==" + }, + "node_modules/@react-native-voice/voice/node_modules/@expo/json-file": { + "version": "8.2.30", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-8.2.30.tgz", + "integrity": "sha512-vrgGyPEXBoFI5NY70IegusCSoSVIFV3T3ry4tjJg1MFQKTUlR7E0r+8g8XR6qC705rc2PawaZQjqXMAVtV6s2A==", + "dependencies": { + "@babel/code-frame": "~7.10.4", + "fs-extra": "9.0.0", + "json5": "^1.0.1", + "write-file-atomic": "^2.3.0" + } + }, + "node_modules/@react-native-voice/voice/node_modules/@expo/plist": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.0.13.tgz", + "integrity": "sha512-zGPSq9OrCn7lWvwLLHLpHUUq2E40KptUFXn53xyZXPViI0k9lbApcR9KlonQZ95C+ELsf0BQ3gRficwK92Ivcw==", + "dependencies": { + "base64-js": "^1.2.3", + "xmlbuilder": "^14.0.0", + "xmldom": "~0.5.0" + } + }, + "node_modules/@react-native-voice/voice/node_modules/getenv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz", + "integrity": "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-voice/voice/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@react-native-voice/voice/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/@react-native-voice/voice/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/@react-native-voice/voice/node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/@react-native-voice/voice/node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@react-native-voice/voice/node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@react-native-voice/voice/node_modules/xmlbuilder": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz", + "integrity": "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==", + "engines": { + "node": ">=8.0" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.79.5", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.5.tgz", @@ -5237,6 +5387,14 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -7891,6 +8049,14 @@ "expo": "*" } }, + "node_modules/expo-location": { + "version": "18.1.6", + "resolved": "https://registry.npmjs.org/expo-location/-/expo-location-18.1.6.tgz", + "integrity": "sha512-l5dQQ2FYkrBgNzaZN1BvSmdhhcztFOUucu2kEfDBMV4wSIuTIt/CKsho+F3RnAiWgvui1wb1WTTf80E8zq48hA==", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-module-scripts": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/expo-module-scripts/-/expo-module-scripts-4.1.9.tgz", @@ -8436,6 +8602,28 @@ "node": ">= 0.6" } }, + "node_modules/fs-extra": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", + "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", @@ -10759,6 +10947,25 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -12990,6 +13197,15 @@ "react-native": "*" } }, + "node_modules/react-native-voice": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/react-native-voice/-/react-native-voice-0.3.0.tgz", + "integrity": "sha512-puSrXCNn1MM03UZkY8q+GW931OqBCEBCiupNK4vD8i49esGqRDDmzv8sRYyEgXj5ScVDKe6lnlKCmJSn5Wo2UA==", + "deprecated": "This module has been renamed to @react-native-community/voice. Updates will no longer be pushed to this package.", + "peerDependencies": { + "react-native": ">=0.40.0" + } + }, "node_modules/react-native-web": { "version": "0.20.0", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.20.0.tgz", @@ -15853,6 +16069,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xmldom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.5.0.tgz", + "integrity": "sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index de8df35..45c894e 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@expo/vector-icons": "^14.1.0", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-picker/picker": "2.11.1", + "@react-native-voice/voice": "^3.2.4", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/elements": "^2.3.8", "@react-navigation/native": "^7.1.6", @@ -41,8 +42,10 @@ "react-native-reanimated": "~3.17.4", "react-native-safe-area-context": "5.4.0", "react-native-screens": "~4.11.1", + "react-native-voice": "^0.3.0", "react-native-web": "~0.20.0", - "react-native-webview": "13.13.5" + "react-native-webview": "13.13.5", + "expo-location": "~18.1.6" }, "devDependencies": { "@babel/core": "^7.25.2",