docs: README.md and docs
This commit is contained in:
140
README.md
140
README.md
@@ -1,50 +1,118 @@
|
|||||||
# Welcome to your Expo app 👋
|
# 📱 Heval Light – Personal Assistant App
|
||||||
|
|
||||||
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
|
**Heval Light** ist eine persönliche Assistenten-App für iOS/Android, entwickelt mit **React Native + Expo**.
|
||||||
|
Die App soll langfristig ein smarter Begleiter für Studierende, Arbeitnehmer und Privatpersonen werden – mit **Kalender-, Erinnerungs- und KI-Funktionen**.
|
||||||
|
|
||||||
## Get started
|
---
|
||||||
|
|
||||||
1. Install dependencies
|
## ✨ Features (aktueller Stand)
|
||||||
|
|
||||||
```bash
|
✅ **Benutzerregistrierung & Login**
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Start the app
|
- Vorname, Nachname, E-Mail, Passwort
|
||||||
|
- Speicherung in **SQLite**
|
||||||
|
- **Biometrische Anmeldung** via Face ID/Touch ID
|
||||||
|
|
||||||
```bash
|
✅ **Kalender & Erinnerungen**
|
||||||
npx expo start
|
|
||||||
```
|
|
||||||
|
|
||||||
In the output, you'll find options to open the app in a
|
- Zugriff auf **iOS Kalender/Erinnerungen** via `expo-calendar`
|
||||||
|
- Speicherung von Events in **lokaler SQLite-Datenbank**
|
||||||
|
- **Modal-Formular** für manuelles Hinzufügen von Terminen
|
||||||
|
|
||||||
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
|
✅ **Lokale Speicherung & Sessions**
|
||||||
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
|
|
||||||
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
|
|
||||||
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
|
|
||||||
|
|
||||||
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
|
- Benutzer-Session wird sicher in **AsyncStorage** gehalten
|
||||||
|
- Passwort & Benutzerdaten lokal in SQLite gespeichert
|
||||||
|
|
||||||
## Get a fresh project
|
✅ **Zukunftsorientierte Funktionen (geplant)**
|
||||||
|
|
||||||
When you're ready, run:
|
- **GPS-Standort** automatisch übernehmen (Wohnort, Arbeitsplatz/Uni)
|
||||||
|
- **Sprachbasierte Eingabe** (Speech-to-Text für Erinnerungen)
|
||||||
|
- **KI-Assistent (ChatGPT)** zur Organisation
|
||||||
|
- **Push-Notifications** für Erinnerungen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗺️ Roadmap
|
||||||
|
|
||||||
|
- [ ] **Registrierung erweitern**: Wohnort/Arbeitsort per GPS automatisch erkennen
|
||||||
|
- [ ] **Speech-to-Text** für neue Erinnerungen/Termine
|
||||||
|
- [ ] **Push-Notifications** für wichtige Events
|
||||||
|
- [ ] **AI-Integration** (ChatGPT für intelligente Terminplanung)
|
||||||
|
- [ ] **Mehrsprachigkeit** (Deutsch/Englisch)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Technologie-Stack
|
||||||
|
|
||||||
|
- **React Native + Expo (Managed Workflow)**
|
||||||
|
- **SQLite** für lokale Benutzer- & Eventdaten
|
||||||
|
- **AsyncStorage** für Sitzungen
|
||||||
|
- **expo-local-authentication** für Face ID/Touch ID
|
||||||
|
- **expo-calendar** für Kalender- und Erinnerungszugriff
|
||||||
|
- **expo-location** (geplant) für GPS-Standort
|
||||||
|
- **react-native-voice** (geplant) für Spracheingabe
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📂 Projektstruktur
|
||||||
|
|
||||||
|
app/
|
||||||
|
├─ index.tsx # AuthScreen: Registrierung, Login, FaceID
|
||||||
|
├─ calendar.tsx # Kalender & Erinnerungen, SQLite-Speicherung
|
||||||
|
├─ (geplant) reminders.tsx # Sprachbasierte Erinnerungen
|
||||||
|
└─ ...
|
||||||
|
|
||||||
|
yaml
|
||||||
|
Copy
|
||||||
|
Edit
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Installation & Start
|
||||||
|
|
||||||
|
### Voraussetzungen
|
||||||
|
|
||||||
|
- Node.js (>= 18)
|
||||||
|
- Expo CLI
|
||||||
|
- Git
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run reset-project
|
# Repository klonen
|
||||||
|
git clone <repo-url>
|
||||||
|
cd heval-light
|
||||||
|
|
||||||
|
# Abhängigkeiten installieren
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# App starten
|
||||||
|
npx expo start
|
||||||
|
Auf iOS: Expo Go App installieren und QR-Code scannen
|
||||||
|
|
||||||
|
Auf Android: ebenfalls Expo Go oder Emulator verwenden
|
||||||
|
|
||||||
|
🔐 Sicherheit
|
||||||
|
Benutzer- und Kalenderdaten bleiben lokal auf dem Gerät
|
||||||
|
|
||||||
|
Kein externer Server nötig
|
||||||
|
|
||||||
|
Geplant: Verschlüsselte Speicherung (SecureStore)
|
||||||
|
|
||||||
|
🧭 Ziel der App
|
||||||
|
Heval Light soll zu einem intelligenten Assistenten werden, der dir hilft bei:
|
||||||
|
|
||||||
|
Kalender- & Erinnerungsverwaltung
|
||||||
|
|
||||||
|
Standortbasierten Aufgaben (Wohnort, Arbeitsplatz, Uni)
|
||||||
|
|
||||||
|
Sprach- und KI-gestützter Organisation
|
||||||
|
|
||||||
|
🤝 Mitwirken
|
||||||
|
Dieses Projekt ist noch in der aktiven Entwicklung.
|
||||||
|
Ideen, Feedback oder Pull Requests sind willkommen!
|
||||||
|
|
||||||
|
📜 Lizenz
|
||||||
|
MIT License – frei nutzbar für private & kommerzielle Zwecke.
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
|
|
||||||
|
|
||||||
## Learn more
|
|
||||||
|
|
||||||
To learn more about developing your project with Expo, look at the following resources:
|
|
||||||
|
|
||||||
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
|
|
||||||
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
|
|
||||||
|
|
||||||
## Join the community
|
|
||||||
|
|
||||||
Join our community of developers creating universal apps.
|
|
||||||
|
|
||||||
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
|
|
||||||
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
|
import { Picker } from "@react-native-picker/picker";
|
||||||
import * as LocalAuthentication from "expo-local-authentication";
|
import * as LocalAuthentication from "expo-local-authentication";
|
||||||
import * as Location from "expo-location";
|
import * as Location from "expo-location";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
@@ -10,6 +11,7 @@ import {
|
|||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
Platform,
|
Platform,
|
||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
@@ -24,7 +26,12 @@ interface User {
|
|||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
residence: string;
|
residence: string;
|
||||||
|
residenceStreet: string;
|
||||||
|
residenceNumber: string;
|
||||||
workplace: string;
|
workplace: string;
|
||||||
|
workplaceStreet: string;
|
||||||
|
workplaceNumber: string;
|
||||||
|
transportMode: string;
|
||||||
latitude: string;
|
latitude: string;
|
||||||
longitude: string;
|
longitude: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@@ -43,11 +50,18 @@ export default function AuthScreen() {
|
|||||||
const [firstName, setFirstName] = useState("");
|
const [firstName, setFirstName] = useState("");
|
||||||
const [lastName, setLastName] = useState("");
|
const [lastName, setLastName] = useState("");
|
||||||
const [residence, setResidence] = useState("");
|
const [residence, setResidence] = useState("");
|
||||||
|
const [residenceStreet, setResidenceStreet] = useState("");
|
||||||
|
const [residenceNumber, setResidenceNumber] = useState("");
|
||||||
const [workplace, setWorkplace] = useState("");
|
const [workplace, setWorkplace] = useState("");
|
||||||
|
const [workplaceStreet, setWorkplaceStreet] = useState("");
|
||||||
|
const [workplaceNumber, setWorkplaceNumber] = useState("");
|
||||||
const [latitude, setLatitude] = useState<string | null>(null);
|
const [latitude, setLatitude] = useState<string | null>(null);
|
||||||
const [longitude, setLongitude] = useState<string | null>(null);
|
const [longitude, setLongitude] = useState<string | null>(null);
|
||||||
const [biometricSupported, setBiometricSupported] = useState(false);
|
const [biometricSupported, setBiometricSupported] = useState(false);
|
||||||
const [showCalendar, setShowCalendar] = useState(false);
|
const [showCalendar, setShowCalendar] = useState(false);
|
||||||
|
const [transportMode, setTransportMode] = useState("auto");
|
||||||
|
const [editMode, setEditMode] = useState(false);
|
||||||
|
const [showOnlyName, setShowOnlyName] = useState(false);
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
const [db, setDb] = useState<SQLite.SQLiteDatabase | null>(null);
|
const [db, setDb] = useState<SQLite.SQLiteDatabase | null>(null);
|
||||||
@@ -71,7 +85,12 @@ export default function AuthScreen() {
|
|||||||
lastName TEXT NOT NULL,
|
lastName TEXT NOT NULL,
|
||||||
password TEXT NOT NULL,
|
password TEXT NOT NULL,
|
||||||
residence TEXT,
|
residence TEXT,
|
||||||
|
residenceStreet TEXT,
|
||||||
|
residenceNumber TEXT,
|
||||||
workplace TEXT,
|
workplace TEXT,
|
||||||
|
workplaceStreet TEXT,
|
||||||
|
workplaceNumber TEXT,
|
||||||
|
transportMode TEXT,
|
||||||
latitude TEXT,
|
latitude TEXT,
|
||||||
longitude TEXT,
|
longitude TEXT,
|
||||||
createdAt TEXT NOT NULL
|
createdAt TEXT NOT NULL
|
||||||
@@ -104,7 +123,11 @@ export default function AuthScreen() {
|
|||||||
!firstName ||
|
!firstName ||
|
||||||
!lastName ||
|
!lastName ||
|
||||||
!residence ||
|
!residence ||
|
||||||
!workplace
|
!residenceStreet ||
|
||||||
|
!residenceNumber ||
|
||||||
|
!workplace ||
|
||||||
|
!workplaceStreet ||
|
||||||
|
!workplaceNumber
|
||||||
) {
|
) {
|
||||||
Alert.alert("Fehler", "Bitte füllen Sie alle Felder aus.");
|
Alert.alert("Fehler", "Bitte füllen Sie alle Felder aus.");
|
||||||
return;
|
return;
|
||||||
@@ -125,14 +148,19 @@ export default function AuthScreen() {
|
|||||||
|
|
||||||
const createdAt = new Date().toISOString();
|
const createdAt = new Date().toISOString();
|
||||||
const result = await db.runAsync(
|
const result = await db.runAsync(
|
||||||
"INSERT INTO users (email, firstName, lastName, password, residence, workplace, latitude, longitude, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
"INSERT INTO users (email, firstName, lastName, password, residence, residenceStreet, residenceNumber, workplace, workplaceStreet, workplaceNumber, transportMode, latitude, longitude, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
[
|
[
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
password,
|
password,
|
||||||
residence,
|
residence,
|
||||||
|
residenceStreet,
|
||||||
|
residenceNumber,
|
||||||
workplace,
|
workplace,
|
||||||
|
workplaceStreet,
|
||||||
|
workplaceNumber,
|
||||||
|
transportMode,
|
||||||
latitude ?? "",
|
latitude ?? "",
|
||||||
longitude ?? "",
|
longitude ?? "",
|
||||||
createdAt,
|
createdAt,
|
||||||
@@ -145,7 +173,12 @@ export default function AuthScreen() {
|
|||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
residence,
|
residence,
|
||||||
|
residenceStreet,
|
||||||
|
residenceNumber,
|
||||||
workplace,
|
workplace,
|
||||||
|
workplaceStreet,
|
||||||
|
workplaceNumber,
|
||||||
|
transportMode,
|
||||||
latitude: latitude ?? "",
|
latitude: latitude ?? "",
|
||||||
longitude: longitude ?? "",
|
longitude: longitude ?? "",
|
||||||
createdAt,
|
createdAt,
|
||||||
@@ -201,6 +234,11 @@ export default function AuthScreen() {
|
|||||||
latitude: result.latitude,
|
latitude: result.latitude,
|
||||||
longitude: result.longitude,
|
longitude: result.longitude,
|
||||||
createdAt: result.createdAt,
|
createdAt: result.createdAt,
|
||||||
|
residenceStreet: result.residenceStreet,
|
||||||
|
residenceNumber: result.residenceNumber,
|
||||||
|
workplaceStreet: result.workplaceStreet,
|
||||||
|
workplaceNumber: result.workplaceNumber,
|
||||||
|
transportMode: result.transportMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save session
|
// Save session
|
||||||
@@ -245,6 +283,11 @@ export default function AuthScreen() {
|
|||||||
latitude: user.latitude,
|
latitude: user.latitude,
|
||||||
longitude: user.longitude,
|
longitude: user.longitude,
|
||||||
createdAt: user.createdAt,
|
createdAt: user.createdAt,
|
||||||
|
residenceStreet: user.residenceStreet,
|
||||||
|
residenceNumber: user.residenceNumber,
|
||||||
|
workplaceStreet: user.workplaceStreet,
|
||||||
|
workplaceNumber: user.workplaceNumber,
|
||||||
|
transportMode: user.transportMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
await AsyncStorage.setItem(
|
await AsyncStorage.setItem(
|
||||||
@@ -300,6 +343,44 @@ export default function AuthScreen() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSaveEdit = async () => {
|
||||||
|
if (!currentUser || !db) return;
|
||||||
|
await db.runAsync(
|
||||||
|
`UPDATE users SET firstName=?, lastName=?, residence=?, residenceStreet=?, residenceNumber=?, workplace=?, workplaceStreet=?, workplaceNumber=?, transportMode=?, latitude=?, longitude=? WHERE id=?`,
|
||||||
|
[
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
residence,
|
||||||
|
residenceStreet,
|
||||||
|
residenceNumber,
|
||||||
|
workplace,
|
||||||
|
workplaceStreet,
|
||||||
|
workplaceNumber,
|
||||||
|
transportMode,
|
||||||
|
latitude ?? "",
|
||||||
|
longitude ?? "",
|
||||||
|
currentUser.id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
setEditMode(false);
|
||||||
|
setShowOnlyName(true);
|
||||||
|
setCurrentUser({
|
||||||
|
...currentUser,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
residence,
|
||||||
|
residenceStreet,
|
||||||
|
residenceNumber,
|
||||||
|
workplace,
|
||||||
|
workplaceStreet,
|
||||||
|
workplaceNumber,
|
||||||
|
transportMode,
|
||||||
|
latitude: latitude ?? "",
|
||||||
|
longitude: longitude ?? "",
|
||||||
|
});
|
||||||
|
Alert.alert("Erfolg", "Daten gespeichert.");
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
@@ -314,33 +395,141 @@ export default function AuthScreen() {
|
|||||||
if (isLoggedIn && currentUser) {
|
if (isLoggedIn && currentUser) {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<View style={styles.welcomeContainer}>
|
<ScrollView
|
||||||
<Text style={styles.welcomeTitle}>Willkommen zurück!</Text>
|
contentContainerStyle={styles.scrollContent}
|
||||||
<Text style={styles.userName}>
|
keyboardShouldPersistTaps="handled"
|
||||||
{currentUser.firstName} {currentUser.lastName}
|
>
|
||||||
</Text>
|
<View style={styles.welcomeContainer}>
|
||||||
|
<Text style={styles.welcomeTitle}>Willkommen zurück!</Text>
|
||||||
<View style={styles.userInfo}>
|
<Text style={styles.userName}>
|
||||||
<Text style={styles.infoLabel}>E-Mail:</Text>
|
{currentUser.firstName} {currentUser.lastName}
|
||||||
<Text style={styles.infoValue}>{currentUser.email}</Text>
|
|
||||||
|
|
||||||
<Text style={styles.infoLabel}>Mitglied seit:</Text>
|
|
||||||
<Text style={styles.infoValue}>
|
|
||||||
{new Date(currentUser.createdAt).toLocaleDateString("de-DE")}
|
|
||||||
</Text>
|
</Text>
|
||||||
|
<View style={styles.userInfo}>
|
||||||
|
{showOnlyName ? (
|
||||||
|
<></>
|
||||||
|
) : editMode ? (
|
||||||
|
<>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={firstName}
|
||||||
|
onChangeText={setFirstName}
|
||||||
|
placeholder="Vorname"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={lastName}
|
||||||
|
onChangeText={setLastName}
|
||||||
|
placeholder="Nachname"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={residenceStreet}
|
||||||
|
onChangeText={setResidenceStreet}
|
||||||
|
placeholder="Straße (Wohnort)"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={residenceNumber}
|
||||||
|
onChangeText={setResidenceNumber}
|
||||||
|
placeholder="Hausnummer (Wohnort)"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={residence}
|
||||||
|
onChangeText={setResidence}
|
||||||
|
placeholder="Wohnort"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={workplaceStreet}
|
||||||
|
onChangeText={setWorkplaceStreet}
|
||||||
|
placeholder="Straße (Arbeit/Schule/Uni)"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={workplaceNumber}
|
||||||
|
onChangeText={setWorkplaceNumber}
|
||||||
|
placeholder="Hausnummer (Arbeit/Schule/Uni)"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={workplace}
|
||||||
|
onChangeText={setWorkplace}
|
||||||
|
placeholder="Arbeitsplatz / Uni / Schule"
|
||||||
|
/>
|
||||||
|
<View style={{ marginBottom: 15 }}>
|
||||||
|
<Text style={{ marginBottom: 5, fontWeight: "600" }}>
|
||||||
|
Verkehrsmittel
|
||||||
|
</Text>
|
||||||
|
<Picker
|
||||||
|
selectedValue={transportMode}
|
||||||
|
onValueChange={setTransportMode}
|
||||||
|
style={{ backgroundColor: "#fff" }}
|
||||||
|
>
|
||||||
|
<Picker.Item label="Auto" value="auto" />
|
||||||
|
<Picker.Item label="Zug" value="zug" />
|
||||||
|
<Picker.Item label="Bus" value="bus" />
|
||||||
|
<Picker.Item label="Fahrrad" value="fahrrad" />
|
||||||
|
<Picker.Item label="Zu Fuß" value="zufuss" />
|
||||||
|
</Picker>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.submitButton, { marginBottom: 10 }]}
|
||||||
|
onPress={handleSaveEdit}
|
||||||
|
>
|
||||||
|
<Text style={styles.submitButtonText}>Speichern</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.logoutButton}
|
||||||
|
onPress={() => setEditMode(false)}
|
||||||
|
>
|
||||||
|
<Text style={styles.logoutButtonText}>Abbrechen</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Text style={styles.infoLabel}>E-Mail:</Text>
|
||||||
|
<Text style={styles.infoValue}>{currentUser.email}</Text>
|
||||||
|
<Text style={styles.infoLabel}>Wohnort:</Text>
|
||||||
|
<Text style={styles.infoValue}>
|
||||||
|
{currentUser.residenceStreet} {currentUser.residenceNumber},{" "}
|
||||||
|
{currentUser.residence}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.infoLabel}>Arbeit/Schule/Uni:</Text>
|
||||||
|
<Text style={styles.infoValue}>
|
||||||
|
{currentUser.workplaceStreet} {currentUser.workplaceNumber},{" "}
|
||||||
|
{currentUser.workplace}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.infoLabel}>Verkehrsmittel:</Text>
|
||||||
|
<Text style={styles.infoValue}>
|
||||||
|
{currentUser.transportMode}
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.submitButton,
|
||||||
|
{ marginTop: 10, marginBottom: 10 },
|
||||||
|
]}
|
||||||
|
onPress={() => setEditMode(true)}
|
||||||
|
>
|
||||||
|
<Text style={styles.submitButtonText}>Bearbeiten</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.logoutButton}
|
||||||
|
onPress={handleLogout}
|
||||||
|
>
|
||||||
|
<Text style={styles.logoutButtonText}>Abmelden</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.submitButton, { marginTop: 20 }]}
|
||||||
|
onPress={() => router.push("/calendar")}
|
||||||
|
>
|
||||||
|
<Text style={styles.submitButtonText}>Zum Kalender</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
</ScrollView>
|
||||||
<TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
|
|
||||||
<Text style={styles.logoutButtonText}>Abmelden</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<TouchableOpacity
|
|
||||||
style={[styles.submitButton, { marginTop: 20 }]}
|
|
||||||
onPress={() => router.push("/calendar")}
|
|
||||||
>
|
|
||||||
<Text style={styles.submitButtonText}>Zum Kalender</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -351,111 +540,156 @@ export default function AuthScreen() {
|
|||||||
style={styles.keyboardView}
|
style={styles.keyboardView}
|
||||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||||
>
|
>
|
||||||
<View style={styles.authContainer}>
|
<ScrollView
|
||||||
<Text style={styles.title}>
|
contentContainerStyle={styles.scrollContent}
|
||||||
{authMode === "login" ? "Anmelden" : "Registrieren"}
|
keyboardShouldPersistTaps="handled"
|
||||||
</Text>
|
>
|
||||||
|
<View style={styles.authContainer}>
|
||||||
|
<Text style={styles.title}>
|
||||||
|
{authMode === "login" ? "Anmelden" : "Registrieren"}
|
||||||
|
</Text>
|
||||||
|
|
||||||
{authMode === "register" && (
|
{authMode === "register" && (
|
||||||
<>
|
<>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
placeholder="Vorname"
|
placeholder="Vorname"
|
||||||
value={firstName}
|
value={firstName}
|
||||||
onChangeText={setFirstName}
|
onChangeText={setFirstName}
|
||||||
autoCapitalize="words"
|
autoCapitalize="words"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
placeholder="Nachname"
|
placeholder="Nachname"
|
||||||
value={lastName}
|
value={lastName}
|
||||||
onChangeText={setLastName}
|
onChangeText={setLastName}
|
||||||
autoCapitalize="words"
|
autoCapitalize="words"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
placeholder="Wohnort"
|
placeholder="Straße (Wohnort)"
|
||||||
value={residence}
|
value={residenceStreet}
|
||||||
onChangeText={setResidence}
|
onChangeText={setResidenceStreet}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.input}
|
style={styles.input}
|
||||||
placeholder="Arbeitsplatz / Uni / Schule"
|
placeholder="Hausnummer (Wohnort)"
|
||||||
value={workplace}
|
value={residenceNumber}
|
||||||
onChangeText={setWorkplace}
|
onChangeText={setResidenceNumber}
|
||||||
/>
|
/>
|
||||||
<TouchableOpacity
|
<TextInput
|
||||||
style={[
|
style={styles.input}
|
||||||
styles.submitButton,
|
placeholder="Wohnort"
|
||||||
{ backgroundColor: "#34C759", marginBottom: 10 },
|
value={residence}
|
||||||
]}
|
onChangeText={setResidence}
|
||||||
onPress={handleUseLocation}
|
/>
|
||||||
>
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Straße (Arbeit/Schule/Uni)"
|
||||||
|
value={workplaceStreet}
|
||||||
|
onChangeText={setWorkplaceStreet}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Hausnummer (Arbeit/Schule/Uni)"
|
||||||
|
value={workplaceNumber}
|
||||||
|
onChangeText={setWorkplaceNumber}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Arbeitsplatz / Uni / Schule"
|
||||||
|
value={workplace}
|
||||||
|
onChangeText={setWorkplace}
|
||||||
|
/>
|
||||||
|
<View style={{ marginBottom: 15 }}>
|
||||||
|
<Text style={{ marginBottom: 5, fontWeight: "600" }}>
|
||||||
|
Verkehrsmittel
|
||||||
|
</Text>
|
||||||
|
<Picker
|
||||||
|
selectedValue={transportMode}
|
||||||
|
onValueChange={setTransportMode}
|
||||||
|
style={{ backgroundColor: "#fff" }}
|
||||||
|
>
|
||||||
|
<Picker.Item label="Auto" value="auto" />
|
||||||
|
<Picker.Item label="Zug" value="zug" />
|
||||||
|
<Picker.Item label="Bus" value="bus" />
|
||||||
|
<Picker.Item label="Fahrrad" value="fahrrad" />
|
||||||
|
<Picker.Item label="Zu Fuß" value="zufuss" />
|
||||||
|
</Picker>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.submitButton,
|
||||||
|
{ backgroundColor: "#34C759", marginBottom: 10 },
|
||||||
|
]}
|
||||||
|
onPress={handleUseLocation}
|
||||||
|
>
|
||||||
|
<Text style={styles.submitButtonText}>
|
||||||
|
Aktuellen Standort verwenden
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="E-Mail"
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
keyboardType="email-address"
|
||||||
|
autoCapitalize="none"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Passwort"
|
||||||
|
value={password}
|
||||||
|
onChangeText={setPassword}
|
||||||
|
secureTextEntry
|
||||||
|
autoCapitalize="none"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.submitButton}
|
||||||
|
onPress={authMode === "login" ? handleLogin : handleRegister}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<ActivityIndicator color="white" />
|
||||||
|
) : (
|
||||||
<Text style={styles.submitButtonText}>
|
<Text style={styles.submitButtonText}>
|
||||||
Aktuellen Standort verwenden
|
{authMode === "login" ? "Anmelden" : "Registrieren"}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{biometricSupported && authMode === "login" && (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.biometricButton}
|
||||||
|
onPress={handleBiometricAuth}
|
||||||
|
>
|
||||||
|
<Text style={styles.biometricButtonText}>
|
||||||
|
Mit Face ID / Touch ID anmelden
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
style={styles.input}
|
|
||||||
placeholder="E-Mail"
|
|
||||||
value={email}
|
|
||||||
onChangeText={setEmail}
|
|
||||||
keyboardType="email-address"
|
|
||||||
autoCapitalize="none"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
style={styles.input}
|
|
||||||
placeholder="Passwort"
|
|
||||||
value={password}
|
|
||||||
onChangeText={setPassword}
|
|
||||||
secureTextEntry
|
|
||||||
autoCapitalize="none"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.submitButton}
|
|
||||||
onPress={authMode === "login" ? handleLogin : handleRegister}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<ActivityIndicator color="white" />
|
|
||||||
) : (
|
|
||||||
<Text style={styles.submitButtonText}>
|
|
||||||
{authMode === "login" ? "Anmelden" : "Registrieren"}
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
{biometricSupported && authMode === "login" && (
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.biometricButton}
|
style={styles.switchButton}
|
||||||
onPress={handleBiometricAuth}
|
onPress={() =>
|
||||||
|
setAuthMode(authMode === "login" ? "register" : "login")
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Text style={styles.biometricButtonText}>
|
<Text style={styles.switchButtonText}>
|
||||||
Mit Face ID / Touch ID anmelden
|
{authMode === "login"
|
||||||
|
? "Noch kein Konto? Registrieren"
|
||||||
|
: "Bereits ein Konto? Anmelden"}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
</View>
|
||||||
|
</ScrollView>
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.switchButton}
|
|
||||||
onPress={() =>
|
|
||||||
setAuthMode(authMode === "login" ? "register" : "login")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Text style={styles.switchButtonText}>
|
|
||||||
{authMode === "login"
|
|
||||||
? "Noch kein Konto? Registrieren"
|
|
||||||
: "Bereits ein Konto? Anmelden"}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
@@ -469,10 +703,16 @@ const styles = StyleSheet.create({
|
|||||||
keyboardView: {
|
keyboardView: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
|
scrollContent: {
|
||||||
|
flexGrow: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
paddingVertical: 30,
|
||||||
|
},
|
||||||
authContainer: {
|
authContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
paddingHorizontal: 30,
|
paddingHorizontal: 30,
|
||||||
|
paddingBottom: 30,
|
||||||
},
|
},
|
||||||
loadingContainer: {
|
loadingContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|||||||
140
docs/requirements.md
Normal file
140
docs/requirements.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# 📄 Heval Light – Requirements Document
|
||||||
|
|
||||||
|
## 1. Überblick
|
||||||
|
|
||||||
|
**Heval Light** ist eine persönliche Assistenten-App für iOS und Android, die Nutzern hilft, ihren Alltag zu organisieren.
|
||||||
|
Die App bietet **Kalender- und Erinnerungsfunktionen**, erkennt **Wohn- und Arbeitsorte**, und soll langfristig durch **Sprachsteuerung und KI-Integration** zu einem intelligenten Begleiter werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Zielgruppe
|
||||||
|
|
||||||
|
- **Studierende** → Verwaltung von Uni-Terminen, Vorlesungen, Deadlines
|
||||||
|
- **Arbeitnehmer:innen** → Meetings, Arbeitswege, Standortinformationen
|
||||||
|
- **Privatpersonen** → Erinnerungen, Einkaufslisten, persönliche Termine
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Ziele der App
|
||||||
|
|
||||||
|
- Einen **zentralen Assistenten** für Termine, Erinnerungen & Standortinfos bieten
|
||||||
|
- Daten **lokal & sicher** speichern (kein Zwang zu Cloud-Diensten)
|
||||||
|
- Einfacher Login & **biometrische Authentifizierung**
|
||||||
|
- Integration von **Standort, Spracheingabe & KI-Unterstützung**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Benutzerrollen
|
||||||
|
|
||||||
|
1. **Registrierter Nutzer**
|
||||||
|
- Kann ein Profil erstellen (Name, E-Mail, Wohnort, Arbeitsplatz/Schule/Uni)
|
||||||
|
- Kann Kalender & Erinnerungen verwalten
|
||||||
|
- Kann optional **Sprachbefehle** nutzen
|
||||||
|
- Kann Face ID / Touch ID für Login verwenden
|
||||||
|
|
||||||
|
2. **Später: KI-Assistent / Hintergrundlogik**
|
||||||
|
- Erkennt wiederkehrende Muster (z. B. Standort, Termine)
|
||||||
|
- Schlägt automatische Aktionen vor
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Muss-Features (Minimum Viable Product)
|
||||||
|
|
||||||
|
- ✅ **Registrierung & Login**
|
||||||
|
- Vorname, Nachname, E-Mail, Passwort
|
||||||
|
- Speicherung in **SQLite**
|
||||||
|
- Biometrische Authentifizierung (Face ID/Touch ID)
|
||||||
|
|
||||||
|
- ✅ **Kalender & Erinnerungen**
|
||||||
|
- Zugriff auf **iOS Kalender/Erinnerungen**
|
||||||
|
- Manuelles Hinzufügen von Terminen in SQLite
|
||||||
|
- Anzeige in einer FlatList
|
||||||
|
|
||||||
|
- ✅ **Benutzer-Session**
|
||||||
|
- Speicherung via **AsyncStorage**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Soll-/Kann-Features (geplante Erweiterungen)
|
||||||
|
|
||||||
|
- **GPS-Integration (expo-location)**
|
||||||
|
- Automatische Erkennung von Wohnort & Arbeitsplatz
|
||||||
|
- Reverse Geocoding → Adresse automatisch eintragen
|
||||||
|
|
||||||
|
- **Sprachsteuerung (react-native-voice)**
|
||||||
|
- Termine & Erinnerungen per Sprache hinzufügen
|
||||||
|
- Sprachausgabe für wichtige Benachrichtigungen
|
||||||
|
|
||||||
|
- **Push-Notifications**
|
||||||
|
- Erinnerungen & Kalenderbenachrichtigungen
|
||||||
|
|
||||||
|
- **AI-Integration (OpenAI API)**
|
||||||
|
- Chatbot für Terminorganisation & Erinnerungen
|
||||||
|
- Intelligente Vorschläge (z. B. „Soll ich dich morgen an den Termin erinnern?“)
|
||||||
|
|
||||||
|
- **Mehrsprachigkeit (Deutsch/Englisch)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Use Cases
|
||||||
|
|
||||||
|
### UC1: Registrierung mit Standort
|
||||||
|
|
||||||
|
1. Nutzer öffnet die App und wählt _Registrieren_
|
||||||
|
2. Gibt Name, E-Mail, Passwort ein
|
||||||
|
3. Optional → Klickt auf **„Aktuellen Standort übernehmen“** → App füllt Wohnort automatisch
|
||||||
|
4. Optional → Gibt Arbeitsplatz/Uni/Schule ein
|
||||||
|
5. Daten werden in SQLite gespeichert
|
||||||
|
|
||||||
|
### UC2: Termin/Erinnerung hinzufügen
|
||||||
|
|
||||||
|
1. Nutzer öffnet den Kalender
|
||||||
|
2. Klickt auf „+“ → Modal öffnet sich
|
||||||
|
3. Trägt Titel, Start-/Endzeit ein → Speichern in SQLite
|
||||||
|
4. Termin wird in Liste angezeigt
|
||||||
|
|
||||||
|
### UC3: Termin per Spracheingabe
|
||||||
|
|
||||||
|
1. Nutzer klickt auf Mikrofon-Icon
|
||||||
|
2. Spricht „Meeting mit Max am Freitag 15 Uhr“
|
||||||
|
3. App erkennt Text & speichert automatisch als neuen Termin
|
||||||
|
|
||||||
|
### UC4: Login mit Face ID
|
||||||
|
|
||||||
|
1. Nutzer öffnet die App
|
||||||
|
2. App fragt automatisch Face ID/Touch ID an
|
||||||
|
3. Nach erfolgreicher Authentifizierung wird der Nutzer direkt eingeloggt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Technische Anforderungen
|
||||||
|
|
||||||
|
- **Framework:** React Native + Expo (Managed Workflow)
|
||||||
|
- **Datenbank:** SQLite (lokal)
|
||||||
|
- **Geräte-Features:**
|
||||||
|
- Face ID / Touch ID → `expo-local-authentication`
|
||||||
|
- Kalenderzugriff → `expo-calendar`
|
||||||
|
- Standort → `expo-location`
|
||||||
|
- Spracheingabe → `react-native-voice`
|
||||||
|
- **Speicher:** AsyncStorage für Sessions
|
||||||
|
- **Geplant:** KI-Integration über OpenAI API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Sicherheits- und Datenschutzaspekte
|
||||||
|
|
||||||
|
- Alle Daten bleiben **lokal auf dem Gerät**
|
||||||
|
- Keine Cloud-Verbindung notwendig
|
||||||
|
- Geplant: Verschlüsselte Speicherung für sensible Daten (SecureStore)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Erfolgskriterien
|
||||||
|
|
||||||
|
- Nutzer kann ohne Internet registrieren & anmelden
|
||||||
|
- Termine & Erinnerungen lassen sich manuell hinzufügen
|
||||||
|
- Standort kann automatisch erfasst werden
|
||||||
|
- Biometrische Anmeldung funktioniert zuverlässig
|
||||||
|
- Später: Spracheingabe & KI-Assistent integrierbar
|
||||||
|
|
||||||
|
---
|
||||||
23
docs/sequence-registration.md
Normal file
23
docs/sequence-registration.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 📊 Heval Light – Sequence Diagram: Registrierung mit Standort
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
|
||||||
|
sequenceDiagram
|
||||||
|
participant U as Benutzer
|
||||||
|
participant App as Heval Light App
|
||||||
|
participant GPS as GPS/Location API
|
||||||
|
participant DB as SQLite
|
||||||
|
|
||||||
|
U->>App: Öffnet Registrierungsformular
|
||||||
|
U->>App: Klickt "Aktuellen Standort übernehmen"
|
||||||
|
App->>GPS: Anfrage Standortberechtigung
|
||||||
|
GPS-->>App: Liefert Koordinaten & Adresse
|
||||||
|
App->>U: Zeigt automatisch erkannten Wohnort
|
||||||
|
|
||||||
|
U->>App: Gibt Name, E-Mail, Passwort, Arbeitsplatz ein
|
||||||
|
U->>App: Klickt "Registrieren"
|
||||||
|
App->>DB: Speichert Benutzer + Wohnort/Arbeitsort
|
||||||
|
DB-->>App: Bestätigung
|
||||||
|
App->>U: Zeigt "Registrierung erfolgreich"
|
||||||
|
|
||||||
|
```
|
||||||
53
docs/usecase-diagram.md
Normal file
53
docs/usecase-diagram.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# 📊 Heval Light – Use Case Diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {'theme': 'neutral'}}%%
|
||||||
|
flowchart LR
|
||||||
|
%% Akteure %%
|
||||||
|
user((👤 Benutzer))
|
||||||
|
|
||||||
|
%% Systemcontainer %%
|
||||||
|
subgraph System [Heval Light App]
|
||||||
|
direction TB
|
||||||
|
UC1([Registrieren mit Name, E-Mail, Passwort])
|
||||||
|
UC2([Wohnort automatisch per GPS übernehmen])
|
||||||
|
UC3([Arbeitsort/Schule/Uni eintragen])
|
||||||
|
UC4([Anmelden mit Face ID / Touch ID])
|
||||||
|
UC5([Kalender-Events aus iPhone lesen])
|
||||||
|
UC6([Neue Erinnerung/Termin speichern])
|
||||||
|
UC7([Erinnerung per Sprache hinzufügen])
|
||||||
|
UC8([Push-Notification senden])
|
||||||
|
UC9([Später: KI-Assistent nutzen])
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Externe Systeme %%
|
||||||
|
gps[(📍 GPS / Location API)]
|
||||||
|
sqlite[(🗄 SQLite Datenbank)]
|
||||||
|
iosCal[(📅 iOS Calendar API)]
|
||||||
|
biometrics[(🔐 Face ID / Touch ID)]
|
||||||
|
voiceApi[(🎤 Speech-to-Text API)]
|
||||||
|
pushApi[(🔔 Push Notification Service)]
|
||||||
|
aiApi[(🤖 KI-API OpenAI)]
|
||||||
|
|
||||||
|
%% Beziehungen Benutzer -> Use Cases %%
|
||||||
|
user --> UC1
|
||||||
|
user --> UC2
|
||||||
|
user --> UC3
|
||||||
|
user --> UC4
|
||||||
|
user --> UC5
|
||||||
|
user --> UC6
|
||||||
|
user --> UC7
|
||||||
|
user --> UC8
|
||||||
|
user --> UC9
|
||||||
|
|
||||||
|
%% Beziehungen Use Cases -> Externe Systeme %%
|
||||||
|
UC2 --> gps
|
||||||
|
UC4 --> biometrics
|
||||||
|
UC5 --> iosCal
|
||||||
|
UC6 --> sqlite
|
||||||
|
UC7 --> voiceApi
|
||||||
|
UC8 --> pushApi
|
||||||
|
UC9 --> aiApi
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user