Anmeldung und Kalendar
This commit is contained in:
240
app/(tabs)/calendar.tsx
Normal file
240
app/(tabs)/calendar.tsx
Normal file
@@ -0,0 +1,240 @@
|
||||
import * as Calendar from "expo-calendar";
|
||||
import * as SQLite from "expo-sqlite";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
FlatList,
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
|
||||
export interface EventItem {
|
||||
id: string;
|
||||
title: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}
|
||||
|
||||
const db = SQLite.openDatabaseSync("events.db");
|
||||
|
||||
export default function CalendarTab() {
|
||||
const [source, setSource] = useState<"sqlite" | "iphone">("sqlite");
|
||||
const [events, setEvents] = useState<EventItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedYear, setSelectedYear] = useState<number>(
|
||||
new Date().getFullYear()
|
||||
);
|
||||
const [permissionError, setPermissionError] = useState<string | null>(null);
|
||||
const [calendarType, setCalendarType] = useState<"calendar" | "reminder">(
|
||||
"calendar"
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (source === "sqlite") {
|
||||
loadEventsFromDB();
|
||||
} else {
|
||||
loadEventsFromDevice();
|
||||
}
|
||||
}, [source, selectedYear, calendarType]);
|
||||
|
||||
const loadEventsFromDB = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const stmt = await db.prepareAsync("SELECT * FROM events;");
|
||||
const loadedEvents: EventItem[] = [];
|
||||
const result = await stmt.executeAsync([]);
|
||||
for await (const row of result) {
|
||||
loadedEvents.push(row as EventItem);
|
||||
}
|
||||
setEvents(loadedEvents);
|
||||
await stmt.finalizeAsync();
|
||||
} catch (e) {
|
||||
setEvents([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadEventsFromDevice = async () => {
|
||||
setLoading(true);
|
||||
setPermissionError(null);
|
||||
try {
|
||||
let perm;
|
||||
if (calendarType === "reminder") {
|
||||
perm = await Calendar.requestRemindersPermissionsAsync();
|
||||
} else {
|
||||
perm = await Calendar.requestCalendarPermissionsAsync();
|
||||
}
|
||||
if (!perm.granted) {
|
||||
setEvents([]);
|
||||
setPermissionError(
|
||||
`Keine Berechtigung für ${
|
||||
calendarType === "reminder" ? "Erinnerungen" : "Kalender"
|
||||
}-Zugriff. Bitte in den iOS-Einstellungen aktivieren.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
let calendars = await Calendar.getCalendarsAsync();
|
||||
console.log(
|
||||
"Gefundene Kalender:",
|
||||
calendars.map((c) => ({ id: c.id, title: c.title, type: c.type }))
|
||||
);
|
||||
if (calendarType === "calendar") {
|
||||
calendars = calendars.filter((c) => String(c.type) === "calendar");
|
||||
} else {
|
||||
calendars = calendars.filter((c) => String(c.type) === "reminder");
|
||||
}
|
||||
const start = new Date(selectedYear, 0, 1);
|
||||
const end = new Date(selectedYear, 11, 31, 23, 59, 59);
|
||||
let allEvents: EventItem[] = [];
|
||||
for (const cal of calendars) {
|
||||
const calEvents = await Calendar.getEventsAsync([cal.id], start, end);
|
||||
console.log(
|
||||
`Events für Kalender ${cal.title}:`,
|
||||
calEvents.map((e) => ({
|
||||
id: e.id,
|
||||
title: e.title,
|
||||
start: e.startDate,
|
||||
end: e.endDate,
|
||||
}))
|
||||
);
|
||||
allEvents = allEvents.concat(
|
||||
calEvents.map((e) => ({
|
||||
id: e.id,
|
||||
title: e.title || "(Kein Titel)",
|
||||
startDate:
|
||||
typeof e.startDate === "string"
|
||||
? e.startDate
|
||||
: new Date(e.startDate).toISOString(),
|
||||
endDate:
|
||||
typeof e.endDate === "string"
|
||||
? e.endDate
|
||||
: new Date(e.endDate).toISOString(),
|
||||
}))
|
||||
);
|
||||
}
|
||||
setEvents(allEvents);
|
||||
} catch (e: any) {
|
||||
setEvents([]);
|
||||
setPermissionError(
|
||||
"Fehler beim Zugriff auf " +
|
||||
(calendarType === "reminder" ? "Erinnerungen" : "Kalender") +
|
||||
": " +
|
||||
(e?.message || JSON.stringify(e))
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderItem = ({ item }: { item: EventItem }) => (
|
||||
<View style={styles.item}>
|
||||
<Text style={styles.title}>{item.title}</Text>
|
||||
<Text style={styles.time}>
|
||||
{new Date(item.startDate).toLocaleString()} -{" "}
|
||||
{new Date(item.endDate).toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.switchRow}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.switchButton,
|
||||
source === "sqlite" && styles.activeButton,
|
||||
]}
|
||||
onPress={() => setSource("sqlite")}
|
||||
>
|
||||
<Text style={styles.switchText}>SQLite</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.switchButton,
|
||||
source === "iphone" && styles.activeButton,
|
||||
]}
|
||||
onPress={() => setSource("iphone")}
|
||||
>
|
||||
<Text style={styles.switchText}>
|
||||
iPhone ({calendarType === "calendar" ? "Kalender" : "Erinnerungen"})
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{source === "iphone" && (
|
||||
<View style={styles.switchRow}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.switchButton,
|
||||
calendarType === "calendar" && styles.activeButton,
|
||||
]}
|
||||
onPress={() => setCalendarType("calendar")}
|
||||
>
|
||||
<Text style={styles.switchText}>Kalender</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.switchButton,
|
||||
calendarType === "reminder" && styles.activeButton,
|
||||
]}
|
||||
onPress={() => setCalendarType("reminder")}
|
||||
>
|
||||
<Text style={styles.switchText}>Erinnerungen</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
<Text style={styles.header}>Kalender Events ({selectedYear})</Text>
|
||||
<FlatList
|
||||
data={events}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={
|
||||
permissionError ? (
|
||||
<Text style={{ textAlign: "center", color: "red" }}>
|
||||
{permissionError}
|
||||
</Text>
|
||||
) : (
|
||||
<Text style={{ textAlign: "center" }}>Keine Events gefunden.</Text>
|
||||
)
|
||||
}
|
||||
refreshing={loading}
|
||||
onRefresh={
|
||||
source === "sqlite" ? loadEventsFromDB : loadEventsFromDevice
|
||||
}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: "#fff", padding: 10 },
|
||||
header: {
|
||||
fontSize: 20,
|
||||
fontWeight: "bold",
|
||||
marginBottom: 16,
|
||||
textAlign: "center",
|
||||
},
|
||||
item: {
|
||||
marginBottom: 12,
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
backgroundColor: "#f2f2f2",
|
||||
},
|
||||
title: { fontSize: 16, fontWeight: "600" },
|
||||
time: { fontSize: 14, color: "#555" },
|
||||
switchRow: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
marginBottom: 16,
|
||||
},
|
||||
switchButton: {
|
||||
padding: 10,
|
||||
borderRadius: 8,
|
||||
backgroundColor: "#eee",
|
||||
marginHorizontal: 8,
|
||||
},
|
||||
activeButton: { backgroundColor: "#007AFF" },
|
||||
switchText: { color: "#333", fontWeight: "bold" },
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
import { Image } from 'expo-image';
|
||||
import { Platform, StyleSheet } from 'react-native';
|
||||
|
||||
import { Collapsible } from '@/components/Collapsible';
|
||||
import { ExternalLink } from '@/components/ExternalLink';
|
||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
|
||||
export default function TabTwoScreen() {
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
|
||||
headerImage={
|
||||
<IconSymbol
|
||||
size={310}
|
||||
color="#808080"
|
||||
name="chevron.left.forwardslash.chevron.right"
|
||||
style={styles.headerImage}
|
||||
/>
|
||||
}>
|
||||
<ThemedView style={styles.titleContainer}>
|
||||
<ThemedText type="title">Explore</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedText>This app includes example code to help you get started.</ThemedText>
|
||||
<Collapsible title="File-based routing">
|
||||
<ThemedText>
|
||||
This app has two screens:{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
|
||||
</ThemedText>
|
||||
<ThemedText>
|
||||
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
|
||||
sets up the tab navigator.
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/router/introduction">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Android, iOS, and web support">
|
||||
<ThemedText>
|
||||
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
|
||||
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
|
||||
</ThemedText>
|
||||
</Collapsible>
|
||||
<Collapsible title="Images">
|
||||
<ThemedText>
|
||||
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
|
||||
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
|
||||
different screen densities
|
||||
</ThemedText>
|
||||
<Image source={require('@/assets/images/react-logo.png')} style={{ alignSelf: 'center' }} />
|
||||
<ExternalLink href="https://reactnative.dev/docs/images">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Custom fonts">
|
||||
<ThemedText>
|
||||
Open <ThemedText type="defaultSemiBold">app/_layout.tsx</ThemedText> to see how to load{' '}
|
||||
<ThemedText style={{ fontFamily: 'SpaceMono' }}>
|
||||
custom fonts such as this one.
|
||||
</ThemedText>
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/versions/latest/sdk/font">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Light and dark mode components">
|
||||
<ThemedText>
|
||||
This template has light and dark mode support. The{' '}
|
||||
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
|
||||
what the user's current color scheme is, and so you can adjust UI colors accordingly.
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Animations">
|
||||
<ThemedText>
|
||||
This template includes an example of an animated component. The{' '}
|
||||
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
|
||||
the powerful <ThemedText type="defaultSemiBold">react-native-reanimated</ThemedText>{' '}
|
||||
library to create a waving hand animation.
|
||||
</ThemedText>
|
||||
{Platform.select({
|
||||
ios: (
|
||||
<ThemedText>
|
||||
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
|
||||
component provides a parallax effect for the header image.
|
||||
</ThemedText>
|
||||
),
|
||||
})}
|
||||
</Collapsible>
|
||||
</ParallaxScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
headerImage: {
|
||||
color: '#808080',
|
||||
bottom: -90,
|
||||
left: -35,
|
||||
position: 'absolute',
|
||||
},
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
},
|
||||
});
|
||||
@@ -1,75 +1,501 @@
|
||||
import { Image } from 'expo-image';
|
||||
import { Platform, StyleSheet } from 'react-native';
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import * as LocalAuthentication from "expo-local-authentication";
|
||||
import * as SQLite from "expo-sqlite";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
import CalendarSync from "../../components/CalendarSync";
|
||||
|
||||
import { HelloWave } from '@/components/HelloWave';
|
||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
// Types
|
||||
interface User {
|
||||
id: number;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
// Simple Auth Component
|
||||
export default function AuthScreen() {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(null);
|
||||
const [authMode, setAuthMode] = useState<"login" | "register">("login");
|
||||
|
||||
// Form states
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [firstName, setFirstName] = useState("");
|
||||
const [lastName, setLastName] = useState("");
|
||||
const [biometricSupported, setBiometricSupported] = useState(false);
|
||||
const [showCalendar, setShowCalendar] = useState(false);
|
||||
|
||||
// Database
|
||||
const [db, setDb] = useState<SQLite.SQLiteDatabase | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
initApp();
|
||||
}, []);
|
||||
|
||||
const initApp = async () => {
|
||||
try {
|
||||
// Initialize database
|
||||
const database = await SQLite.openDatabaseAsync("AuthApp.db");
|
||||
setDb(database);
|
||||
|
||||
await database.execAsync(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
firstName TEXT NOT NULL,
|
||||
lastName TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
createdAt TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
// Check biometric support
|
||||
const compatible = await LocalAuthentication.hasHardwareAsync();
|
||||
const enrolled = await LocalAuthentication.isEnrolledAsync();
|
||||
setBiometricSupported(compatible && enrolled);
|
||||
|
||||
// Check if user is logged in
|
||||
const session = await AsyncStorage.getItem("@user_session");
|
||||
if (session) {
|
||||
const userData = JSON.parse(session);
|
||||
setCurrentUser(userData.user);
|
||||
setIsLoggedIn(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("App initialization failed:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRegister = async () => {
|
||||
if (!email || !password || !firstName || !lastName) {
|
||||
Alert.alert("Fehler", "Bitte füllen Sie alle Felder aus.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
Alert.alert(
|
||||
"Fehler",
|
||||
"Das Passwort muss mindestens 6 Zeichen lang sein."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!db) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const createdAt = new Date().toISOString();
|
||||
const result = await db.runAsync(
|
||||
"INSERT INTO users (email, firstName, lastName, password, createdAt) VALUES (?, ?, ?, ?, ?)",
|
||||
[email, firstName, lastName, password, createdAt]
|
||||
);
|
||||
|
||||
const newUser: User = {
|
||||
id: result.lastInsertRowId,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
createdAt,
|
||||
};
|
||||
|
||||
// Save session
|
||||
await AsyncStorage.setItem(
|
||||
"@user_session",
|
||||
JSON.stringify({ user: newUser })
|
||||
);
|
||||
|
||||
setCurrentUser(newUser);
|
||||
setIsLoggedIn(true);
|
||||
Alert.alert("Erfolg", "Registrierung erfolgreich!");
|
||||
} catch (error: any) {
|
||||
if (error.message?.includes("UNIQUE constraint failed")) {
|
||||
Alert.alert(
|
||||
"Fehler",
|
||||
"Ein Benutzer mit dieser E-Mail existiert bereits."
|
||||
);
|
||||
} else {
|
||||
Alert.alert("Fehler", "Registrierung fehlgeschlagen.");
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (!email || !password) {
|
||||
Alert.alert("Fehler", "Bitte füllen Sie alle Felder aus.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!db) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const result = (await db.getFirstAsync(
|
||||
"SELECT * FROM users WHERE email = ? AND password = ?",
|
||||
[email, password]
|
||||
)) as any;
|
||||
|
||||
if (result) {
|
||||
const user: User = {
|
||||
id: result.id,
|
||||
email: result.email,
|
||||
firstName: result.firstName,
|
||||
lastName: result.lastName,
|
||||
createdAt: result.createdAt,
|
||||
};
|
||||
|
||||
// Save session
|
||||
await AsyncStorage.setItem("@user_session", JSON.stringify({ user }));
|
||||
|
||||
setCurrentUser(user);
|
||||
setIsLoggedIn(true);
|
||||
Alert.alert("Erfolg", "Anmeldung erfolgreich!");
|
||||
} else {
|
||||
Alert.alert("Fehler", "Ungültige E-Mail oder Passwort.");
|
||||
}
|
||||
} catch (error) {
|
||||
Alert.alert("Fehler", "Anmeldung fehlgeschlagen.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBiometricAuth = async () => {
|
||||
try {
|
||||
const result = await LocalAuthentication.authenticateAsync({
|
||||
promptMessage: "Biometrische Authentifizierung",
|
||||
cancelLabel: "Abbrechen",
|
||||
fallbackLabel: "Passcode verwenden",
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
// For demo, auto-login first user
|
||||
if (!db) return;
|
||||
|
||||
const user = (await db.getFirstAsync(
|
||||
"SELECT * FROM users LIMIT 1"
|
||||
)) as any;
|
||||
if (user) {
|
||||
const userData: User = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
createdAt: user.createdAt,
|
||||
};
|
||||
|
||||
await AsyncStorage.setItem(
|
||||
"@user_session",
|
||||
JSON.stringify({ user: userData })
|
||||
);
|
||||
setCurrentUser(userData);
|
||||
setIsLoggedIn(true);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Alert.alert("Fehler", "Biometrische Authentifizierung fehlgeschlagen.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await AsyncStorage.removeItem("@user_session");
|
||||
setCurrentUser(null);
|
||||
setIsLoggedIn(false);
|
||||
setEmail("");
|
||||
setPassword("");
|
||||
setFirstName("");
|
||||
setLastName("");
|
||||
} catch (error) {
|
||||
Alert.alert("Fehler", "Abmeldung fehlgeschlagen.");
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color="#007AFF" />
|
||||
<Text style={styles.loadingText}>Lade App...</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoggedIn && currentUser) {
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.welcomeContainer}>
|
||||
<Text style={styles.welcomeTitle}>Willkommen zurück!</Text>
|
||||
<Text style={styles.userName}>
|
||||
{currentUser.firstName} {currentUser.lastName}
|
||||
</Text>
|
||||
|
||||
<View style={styles.userInfo}>
|
||||
<Text style={styles.infoLabel}>E-Mail:</Text>
|
||||
<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>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
|
||||
<Text style={styles.logoutButtonText}>Abmelden</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.submitButton, { marginTop: 20 }]}
|
||||
onPress={() => setShowCalendar((prev) => !prev)}
|
||||
>
|
||||
<Text style={styles.submitButtonText}>
|
||||
{showCalendar ? "Kalender ausblenden" : "Kalender anzeigen"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{showCalendar && <CalendarSync />}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomeScreen() {
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
|
||||
headerImage={
|
||||
<Image
|
||||
source={require('@/assets/images/partial-react-logo.png')}
|
||||
style={styles.reactLogo}
|
||||
/>
|
||||
}>
|
||||
<ThemedView style={styles.titleContainer}>
|
||||
<ThemedText type="title">Welcome!</ThemedText>
|
||||
<HelloWave />
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.stepContainer}>
|
||||
<ThemedText type="subtitle">Step 1: Try it</ThemedText>
|
||||
<ThemedText>
|
||||
Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes.
|
||||
Press{' '}
|
||||
<ThemedText type="defaultSemiBold">
|
||||
{Platform.select({
|
||||
ios: 'cmd + d',
|
||||
android: 'cmd + m',
|
||||
web: 'F12',
|
||||
})}
|
||||
</ThemedText>{' '}
|
||||
to open developer tools.
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.stepContainer}>
|
||||
<ThemedText type="subtitle">Step 2: Explore</ThemedText>
|
||||
<ThemedText>
|
||||
{`Tap the Explore tab to learn more about what's included in this starter app.`}
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.stepContainer}>
|
||||
<ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText>
|
||||
<ThemedText>
|
||||
{`When you're ready, run `}
|
||||
<ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '}
|
||||
<ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{' '}
|
||||
<ThemedText type="defaultSemiBold">app</ThemedText> to{' '}
|
||||
<ThemedText type="defaultSemiBold">app-example</ThemedText>.
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
</ParallaxScrollView>
|
||||
<SafeAreaView style={styles.container}>
|
||||
<KeyboardAvoidingView
|
||||
style={styles.keyboardView}
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
>
|
||||
<View style={styles.authContainer}>
|
||||
<Text style={styles.title}>
|
||||
{authMode === "login" ? "Anmelden" : "Registrieren"}
|
||||
</Text>
|
||||
|
||||
{authMode === "register" && (
|
||||
<>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Vorname"
|
||||
value={firstName}
|
||||
onChangeText={setFirstName}
|
||||
autoCapitalize="words"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="Nachname"
|
||||
value={lastName}
|
||||
onChangeText={setLastName}
|
||||
autoCapitalize="words"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<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
|
||||
style={styles.biometricButton}
|
||||
onPress={handleBiometricAuth}
|
||||
>
|
||||
<Text style={styles.biometricButtonText}>
|
||||
Mit Face ID / Touch ID anmelden
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
<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>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: "#f8f9fa",
|
||||
},
|
||||
stepContainer: {
|
||||
gap: 8,
|
||||
marginBottom: 8,
|
||||
keyboardView: {
|
||||
flex: 1,
|
||||
},
|
||||
reactLogo: {
|
||||
height: 178,
|
||||
width: 290,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
authContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
paddingHorizontal: 30,
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
loadingText: {
|
||||
marginTop: 10,
|
||||
fontSize: 16,
|
||||
color: "#666",
|
||||
},
|
||||
welcomeContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
paddingHorizontal: 30,
|
||||
},
|
||||
welcomeTitle: {
|
||||
fontSize: 28,
|
||||
fontWeight: "bold",
|
||||
color: "#333",
|
||||
textAlign: "center",
|
||||
marginBottom: 10,
|
||||
},
|
||||
userName: {
|
||||
fontSize: 20,
|
||||
color: "#666",
|
||||
textAlign: "center",
|
||||
marginBottom: 40,
|
||||
},
|
||||
userInfo: {
|
||||
backgroundColor: "white",
|
||||
padding: 20,
|
||||
borderRadius: 10,
|
||||
marginBottom: 30,
|
||||
elevation: 2,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
},
|
||||
infoLabel: {
|
||||
fontSize: 14,
|
||||
color: "#666",
|
||||
marginTop: 10,
|
||||
},
|
||||
infoValue: {
|
||||
fontSize: 16,
|
||||
color: "#333",
|
||||
fontWeight: "500",
|
||||
marginBottom: 5,
|
||||
},
|
||||
title: {
|
||||
fontSize: 32,
|
||||
fontWeight: "bold",
|
||||
color: "#333",
|
||||
textAlign: "center",
|
||||
marginBottom: 40,
|
||||
},
|
||||
input: {
|
||||
backgroundColor: "white",
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 15,
|
||||
borderRadius: 10,
|
||||
fontSize: 16,
|
||||
marginBottom: 15,
|
||||
elevation: 2,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
},
|
||||
submitButton: {
|
||||
backgroundColor: "#007AFF",
|
||||
paddingVertical: 15,
|
||||
borderRadius: 10,
|
||||
alignItems: "center",
|
||||
marginTop: 10,
|
||||
},
|
||||
submitButtonText: {
|
||||
color: "white",
|
||||
fontSize: 18,
|
||||
fontWeight: "600",
|
||||
},
|
||||
biometricButton: {
|
||||
backgroundColor: "#34C759",
|
||||
paddingVertical: 15,
|
||||
borderRadius: 10,
|
||||
marginTop: 15,
|
||||
alignItems: "center",
|
||||
},
|
||||
biometricButtonText: {
|
||||
color: "white",
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
},
|
||||
switchButton: {
|
||||
marginTop: 20,
|
||||
alignItems: "center",
|
||||
},
|
||||
switchButtonText: {
|
||||
color: "#007AFF",
|
||||
fontSize: 16,
|
||||
},
|
||||
logoutButton: {
|
||||
backgroundColor: "#FF3B30",
|
||||
paddingVertical: 15,
|
||||
borderRadius: 10,
|
||||
alignItems: "center",
|
||||
},
|
||||
logoutButtonText: {
|
||||
color: "white",
|
||||
fontSize: 18,
|
||||
fontWeight: "600",
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user