Compare commits
3 Commits
868e1ba68d
...
cbf123ddd6
| Author | SHA1 | Date | |
|---|---|---|---|
| cbf123ddd6 | |||
| 3ad4a77951 | |||
| aabce1a5b0 |
14
CLAUDE.md
14
CLAUDE.md
@@ -77,7 +77,7 @@ src/
|
|||||||
│ │ ├── _layout.tsx # Tab bar configuration (themed)
|
│ │ ├── _layout.tsx # Tab bar configuration (themed)
|
||||||
│ │ ├── chat.tsx # Chat screen (AI conversation)
|
│ │ ├── chat.tsx # Chat screen (AI conversation)
|
||||||
│ │ ├── calendar.tsx # Calendar overview
|
│ │ ├── calendar.tsx # Calendar overview
|
||||||
│ │ └── settings.tsx # Settings screen (theme switcher, logout)
|
│ │ └── settings.tsx # Settings screen (theme switcher, logout, CalDAV config with feedback)
|
||||||
│ ├── editEvent.tsx # Event edit screen (dual-mode: calendar/chat)
|
│ ├── editEvent.tsx # Event edit screen (dual-mode: calendar/chat)
|
||||||
│ ├── event/
|
│ ├── event/
|
||||||
│ │ └── [id].tsx # Event detail screen (dynamic route)
|
│ │ └── [id].tsx # Event detail screen (dynamic route)
|
||||||
@@ -98,7 +98,7 @@ src/
|
|||||||
│ ├── EventConfirmDialog.tsx # AI-proposed event confirmation modal (skeleton)
|
│ ├── EventConfirmDialog.tsx # AI-proposed event confirmation modal (skeleton)
|
||||||
│ ├── ProposedEventCard.tsx # Chat event proposal (uses EventCardBase + confirm/reject/edit buttons)
|
│ ├── ProposedEventCard.tsx # Chat event proposal (uses EventCardBase + confirm/reject/edit buttons)
|
||||||
│ ├── DeleteEventModal.tsx # Delete confirmation modal (uses ModalBase)
|
│ ├── DeleteEventModal.tsx # Delete confirmation modal (uses ModalBase)
|
||||||
│ ├── CustomTextInput.tsx # Themed text input with focus border (used in CaldavSettings)
|
│ ├── CustomTextInput.tsx # Themed text input with focus border (used in login, register, CaldavSettings, editEvent)
|
||||||
│ ├── DateTimePicker.tsx # Date and time picker components
|
│ ├── DateTimePicker.tsx # Date and time picker components
|
||||||
│ └── ScrollableDropdown.tsx # Scrollable dropdown component
|
│ └── ScrollableDropdown.tsx # Scrollable dropdown component
|
||||||
├── Themes.tsx # Theme definitions: THEMES object with defaultLight/defaultDark, Theme type
|
├── Themes.tsx # Theme definitions: THEMES object with defaultLight/defaultDark, Theme type
|
||||||
@@ -602,8 +602,8 @@ NODE_ENV=development # development = pretty logs, production = JSON
|
|||||||
- `AuthService`: login(), register(), logout() - calls backend API
|
- `AuthService`: login(), register(), logout() - calls backend API
|
||||||
- `ApiClient`: Automatically injects X-User-Id header for authenticated requests, handles empty responses (204)
|
- `ApiClient`: Automatically injects X-User-Id header for authenticated requests, handles empty responses (204)
|
||||||
- `AuthGuard`: Reusable component that wraps protected routes - loads user, preloads app data (events + CalDAV config) into stores before dismissing spinner, triggers CalDAV sync, shows loading, redirects if unauthenticated. Exports `preloadAppData()` (also called by `login.tsx`)
|
- `AuthGuard`: Reusable component that wraps protected routes - loads user, preloads app data (events + CalDAV config) into stores before dismissing spinner, triggers CalDAV sync, shows loading, redirects if unauthenticated. Exports `preloadAppData()` (also called by `login.tsx`)
|
||||||
- Login screen: Supports email OR userName login, preloads app data + triggers CalDAV sync after successful login
|
- Login screen: Supports email OR userName login, uses CustomTextInput with focus border, preloads app data + triggers CalDAV sync after successful login
|
||||||
- Register screen: Email validation, checks for existing email/userName
|
- Register screen: Email validation, checks for existing email/userName, uses CustomTextInput with focus border
|
||||||
- `AuthButton`: Reusable button component with themed shadow
|
- `AuthButton`: Reusable button component with themed shadow
|
||||||
- `Header`: Themed header component (logout moved to Settings)
|
- `Header`: Themed header component (logout moved to Settings)
|
||||||
- `(tabs)/_layout.tsx`: Wraps tabs with AuthGuard for protected access
|
- `(tabs)/_layout.tsx`: Wraps tabs with AuthGuard for protected access
|
||||||
@@ -612,7 +612,7 @@ NODE_ENV=development # development = pretty logs, production = JSON
|
|||||||
- `ThemeStore`: Zustand store with theme state and setTheme()
|
- `ThemeStore`: Zustand store with theme state and setTheme()
|
||||||
- `Themes.tsx`: THEMES object with defaultLight/defaultDark variants
|
- `Themes.tsx`: THEMES object with defaultLight/defaultDark variants
|
||||||
- All components use `useThemeStore()` for reactive theme colors
|
- All components use `useThemeStore()` for reactive theme colors
|
||||||
- Settings screen with theme switcher (light/dark) and CalDAV configuration (url, username, password with save/sync buttons, loads existing config on mount)
|
- Settings screen with theme switcher (light/dark) and CalDAV configuration (url, username, password with save/sync buttons, loads existing config on mount). Save/Sync buttons show independent feedback via `FeedbackRow` component: spinner + loading text during request, then success (green) or error (red) message that auto-clears after 3s. Both feedbacks can be visible simultaneously.
|
||||||
- `BaseButton`: Reusable themed button component
|
- `BaseButton`: Reusable themed button component
|
||||||
- Tab navigation (Chat, Calendar, Settings) implemented with themed UI
|
- Tab navigation (Chat, Calendar, Settings) implemented with themed UI
|
||||||
- Calendar screen fully functional:
|
- Calendar screen fully functional:
|
||||||
@@ -634,12 +634,12 @@ NODE_ENV=development # development = pretty logs, production = JSON
|
|||||||
- Tracks conversationId for message continuity across sessions
|
- Tracks conversationId for message continuity across sessions
|
||||||
- ChatStore with addMessages() for bulk loading, chatMessageToMessageData() helper
|
- ChatStore with addMessages() for bulk loading, chatMessageToMessageData() helper
|
||||||
- KeyboardAvoidingView for proper keyboard handling (iOS padding, Android height)
|
- KeyboardAvoidingView for proper keyboard handling (iOS padding, Android height)
|
||||||
- Auto-scroll to end on new messages and keyboard show
|
- Auto-scroll to end on new messages and keyboard show; initial load uses `onContentSizeChange` with `animated: false` to start at bottom without visible scrolling
|
||||||
- keyboardDismissMode="interactive" and keyboardShouldPersistTaps="handled"
|
- keyboardDismissMode="interactive" and keyboardShouldPersistTaps="handled"
|
||||||
- `EventService`: getAll(), getById(), getByDateRange(), create(), update(), delete(mode, occurrenceDate) - fully implemented with recurring delete modes
|
- `EventService`: getAll(), getById(), getByDateRange(), create(), update(), delete(mode, occurrenceDate) - fully implemented with recurring delete modes
|
||||||
- `ChatService`: sendMessage(), confirmEvent(deleteMode, occurrenceDate), rejectEvent(), getConversations(), getConversation(), updateProposalEvent() - fully implemented with cursor pagination, recurring delete support, and proposal editing
|
- `ChatService`: sendMessage(), confirmEvent(deleteMode, occurrenceDate), rejectEvent(), getConversations(), getConversation(), updateProposalEvent() - fully implemented with cursor pagination, recurring delete support, and proposal editing
|
||||||
- `CaldavConfigService`: saveConfig(), getConfig(), deleteConfig(), pull(), pushAll(), sync() - CalDAV config management and sync trigger
|
- `CaldavConfigService`: saveConfig(), getConfig(), deleteConfig(), pull(), pushAll(), sync() - CalDAV config management and sync trigger
|
||||||
- `CustomTextInput`: Themed text input component with focus border highlight, supports controlled value via `text` prop
|
- `CustomTextInput`: Themed text input with focus border highlight. Props: `text`, `onValueChange`, `placeholder`, `placeholderTextColor`, `secureTextEntry`, `autoCapitalize`, `keyboardType`, `className`, `multiline`. No default padding — callers must set padding via `className` (e.g., `px-3 py-2` or `p-4`). When not focused, cursor is reset to start (`selection={{ start: 0 }}`) to avoid text appearing scrolled to the end.
|
||||||
- `CardBase`: Reusable card component with header (title/subtitle), content area, and optional footer button - configurable padding, border, text size via props, ScrollView uses `nestedScrollEnabled` for Android
|
- `CardBase`: Reusable card component with header (title/subtitle), content area, and optional footer button - configurable padding, border, text size via props, ScrollView uses `nestedScrollEnabled` for Android
|
||||||
- `ModalBase`: Reusable modal wrapper with backdrop (absolute-positioned behind card), uses CardBase internally - provides click-outside-to-close, Android back button support, and proper scrolling on Android
|
- `ModalBase`: Reusable modal wrapper with backdrop (absolute-positioned behind card), uses CardBase internally - provides click-outside-to-close, Android back button support, and proper scrolling on Android
|
||||||
- `EventCardBase`: Event card with date/time/recurring icons - uses CardBase for structure. Accepts `recurrenceRule` string (not boolean) and displays German-formatted recurrence via `formatRecurrenceRule()`
|
- `EventCardBase`: Event card with date/time/recurring icons - uses CardBase for structure. Accepts `recurrenceRule` string (not boolean) and displays German-formatted recurrence via `formatRecurrenceRule()`
|
||||||
|
|||||||
@@ -64,11 +64,11 @@ const Chat = () => {
|
|||||||
string | undefined
|
string | undefined
|
||||||
>();
|
>();
|
||||||
const [hasLoadedMessages, setHasLoadedMessages] = useState(false);
|
const [hasLoadedMessages, setHasLoadedMessages] = useState(false);
|
||||||
|
const needsInitialScroll = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const keyboardDidShow = Keyboard.addListener(
|
const keyboardDidShow = Keyboard.addListener("keyboardDidShow", () =>
|
||||||
"keyboardDidShow",
|
scrollToEnd(),
|
||||||
scrollToEnd,
|
|
||||||
);
|
);
|
||||||
return () => keyboardDidShow.remove();
|
return () => keyboardDidShow.remove();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -90,7 +90,7 @@ const Chat = () => {
|
|||||||
await ChatService.getConversation(conversationId);
|
await ChatService.getConversation(conversationId);
|
||||||
const clientMessages = serverMessages.map(chatMessageToMessageData);
|
const clientMessages = serverMessages.map(chatMessageToMessageData);
|
||||||
addMessages(clientMessages);
|
addMessages(clientMessages);
|
||||||
scrollToEnd();
|
needsInitialScroll.current = true;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load messages:", error);
|
console.error("Failed to load messages:", error);
|
||||||
@@ -102,9 +102,9 @@ const Chat = () => {
|
|||||||
}, [isAuthLoading, isAuthenticated, hasLoadedMessages]),
|
}, [isAuthLoading, isAuthenticated, hasLoadedMessages]),
|
||||||
);
|
);
|
||||||
|
|
||||||
const scrollToEnd = () => {
|
const scrollToEnd = (animated = true) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
listRef.current?.scrollToEnd({ animated: true });
|
listRef.current?.scrollToEnd({ animated });
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -277,6 +277,12 @@ const Chat = () => {
|
|||||||
keyExtractor={(item) => item.id}
|
keyExtractor={(item) => item.id}
|
||||||
keyboardDismissMode="interactive"
|
keyboardDismissMode="interactive"
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
|
onContentSizeChange={() => {
|
||||||
|
if (needsInitialScroll.current) {
|
||||||
|
needsInitialScroll.current = false;
|
||||||
|
listRef.current?.scrollToEnd({ animated: false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
ListFooterComponent={
|
ListFooterComponent={
|
||||||
isWaitingForResponse ? <TypingIndicator /> : null
|
isWaitingForResponse ? <TypingIndicator /> : null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Text, View } from "react-native";
|
import { ActivityIndicator, Text, View } from "react-native";
|
||||||
import BaseBackground from "../../components/BaseBackground";
|
import BaseBackground from "../../components/BaseBackground";
|
||||||
import BaseButton, { BaseButtonProps } from "../../components/BaseButton";
|
import BaseButton, { BaseButtonProps } from "../../components/BaseButton";
|
||||||
import { useThemeStore } from "../../stores/ThemeStore";
|
import { useThemeStore } from "../../stores/ThemeStore";
|
||||||
@@ -8,7 +8,7 @@ import { Ionicons } from "@expo/vector-icons";
|
|||||||
import { SimpleHeader } from "../../components/Header";
|
import { SimpleHeader } from "../../components/Header";
|
||||||
import { THEMES } from "../../Themes";
|
import { THEMES } from "../../Themes";
|
||||||
import CustomTextInput from "../../components/CustomTextInput";
|
import CustomTextInput from "../../components/CustomTextInput";
|
||||||
import { useState } from "react";
|
import { useCallback, useRef, useState } from "react";
|
||||||
import { CaldavConfigService } from "../../services/CaldavConfigService";
|
import { CaldavConfigService } from "../../services/CaldavConfigService";
|
||||||
import { useCaldavConfigStore } from "../../stores";
|
import { useCaldavConfigStore } from "../../stores";
|
||||||
|
|
||||||
@@ -33,25 +33,54 @@ type CaldavTextInputProps = {
|
|||||||
title: string;
|
title: string;
|
||||||
value: string;
|
value: string;
|
||||||
onValueChange: (text: string) => void;
|
onValueChange: (text: string) => void;
|
||||||
|
secureTextEntry?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CaldavTextInput = ({
|
const CaldavTextInput = ({
|
||||||
title,
|
title,
|
||||||
value,
|
value,
|
||||||
onValueChange,
|
onValueChange,
|
||||||
|
secureTextEntry,
|
||||||
}: CaldavTextInputProps) => {
|
}: CaldavTextInputProps) => {
|
||||||
|
const { theme } = useThemeStore();
|
||||||
return (
|
return (
|
||||||
<View className="flex flex-row items-center py-1">
|
<View className="flex flex-row items-center py-1">
|
||||||
<Text className="ml-4 w-24">{title}:</Text>
|
<Text className="ml-4 w-24" style={{ color: theme.textPrimary }}>{title}:</Text>
|
||||||
<CustomTextInput
|
<CustomTextInput
|
||||||
className="flex-1 mr-4"
|
className="flex-1 mr-4 px-3 py-2"
|
||||||
text={value}
|
text={value}
|
||||||
onValueChange={onValueChange}
|
onValueChange={onValueChange}
|
||||||
|
secureTextEntry={secureTextEntry}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Feedback = { text: string; isError: boolean; loading: boolean };
|
||||||
|
|
||||||
|
const FeedbackRow = ({ feedback }: { feedback: Feedback | null }) => {
|
||||||
|
const { theme } = useThemeStore();
|
||||||
|
if (!feedback) return null;
|
||||||
|
return (
|
||||||
|
<View className="flex flex-row items-center justify-center mt-2 mx-4 gap-2">
|
||||||
|
{feedback.loading && (
|
||||||
|
<ActivityIndicator size="small" color={theme.textMuted} />
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: feedback.loading
|
||||||
|
? theme.textMuted
|
||||||
|
: feedback.isError
|
||||||
|
? theme.rejectButton
|
||||||
|
: theme.confirmButton,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{feedback.text}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const CaldavSettings = () => {
|
const CaldavSettings = () => {
|
||||||
const { theme } = useThemeStore();
|
const { theme } = useThemeStore();
|
||||||
const { config, setConfig } = useCaldavConfigStore();
|
const { config, setConfig } = useCaldavConfigStore();
|
||||||
@@ -59,18 +88,51 @@ const CaldavSettings = () => {
|
|||||||
const [serverUrl, setServerUrl] = useState(config?.serverUrl ?? "");
|
const [serverUrl, setServerUrl] = useState(config?.serverUrl ?? "");
|
||||||
const [username, setUsername] = useState(config?.username ?? "");
|
const [username, setUsername] = useState(config?.username ?? "");
|
||||||
const [password, setPassword] = useState(config?.password ?? "");
|
const [password, setPassword] = useState(config?.password ?? "");
|
||||||
|
const [saveFeedback, setSaveFeedback] = useState<Feedback | null>(null);
|
||||||
|
const [syncFeedback, setSyncFeedback] = useState<Feedback | null>(null);
|
||||||
|
const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
const syncTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
|
const showFeedback = useCallback(
|
||||||
|
(
|
||||||
|
setter: typeof setSaveFeedback,
|
||||||
|
timer: typeof saveTimer,
|
||||||
|
text: string,
|
||||||
|
isError: boolean,
|
||||||
|
loading = false,
|
||||||
|
) => {
|
||||||
|
if (timer.current) clearTimeout(timer.current);
|
||||||
|
setter({ text, isError, loading });
|
||||||
|
if (!loading) {
|
||||||
|
timer.current = setTimeout(() => setter(null), 3000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const saveConfig = async () => {
|
const saveConfig = async () => {
|
||||||
const saved = await CaldavConfigService.saveConfig(
|
showFeedback(setSaveFeedback, saveTimer, "Speichere Konfiguration...", false, true);
|
||||||
serverUrl,
|
try {
|
||||||
username,
|
const saved = await CaldavConfigService.saveConfig(
|
||||||
password,
|
serverUrl,
|
||||||
);
|
username,
|
||||||
setConfig(saved);
|
password,
|
||||||
|
);
|
||||||
|
setConfig(saved);
|
||||||
|
showFeedback(setSaveFeedback, saveTimer, "Konfiguration wurde gespeichert", false);
|
||||||
|
} catch {
|
||||||
|
showFeedback(setSaveFeedback, saveTimer, "Fehler beim Speichern der Konfiguration", true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sync = async () => {
|
const sync = async () => {
|
||||||
await CaldavConfigService.sync();
|
showFeedback(setSyncFeedback, syncTimer, "Synchronisiere...", false, true);
|
||||||
|
try {
|
||||||
|
await CaldavConfigService.sync();
|
||||||
|
showFeedback(setSyncFeedback, syncTimer, "Synchronisierung erfolgreich", false);
|
||||||
|
} catch {
|
||||||
|
showFeedback(setSyncFeedback, syncTimer, "Fehler beim Synchronisieren", true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -99,6 +161,7 @@ const CaldavSettings = () => {
|
|||||||
title="password"
|
title="password"
|
||||||
value={password}
|
value={password}
|
||||||
onValueChange={setPassword}
|
onValueChange={setPassword}
|
||||||
|
secureTextEntry
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View className="flex flex-row">
|
<View className="flex flex-row">
|
||||||
@@ -109,6 +172,8 @@ const CaldavSettings = () => {
|
|||||||
Sync
|
Sync
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
</View>
|
</View>
|
||||||
|
<FeedbackRow feedback={saveFeedback} />
|
||||||
|
<FeedbackRow feedback={syncFeedback} />
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const EditEventTextField = (props: EditEventTextFieldProps) => {
|
|||||||
{props.titel}
|
{props.titel}
|
||||||
</Text>
|
</Text>
|
||||||
<CustomTextInput
|
<CustomTextInput
|
||||||
className="flex-1"
|
className="flex-1 px-3 py-2"
|
||||||
text={props.text}
|
text={props.text}
|
||||||
multiline={props.multiline}
|
multiline={props.multiline}
|
||||||
onValueChange={props.onValueChange}
|
onValueChange={props.onValueChange}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { View, Text, TextInput, Pressable } from "react-native";
|
import { View, Text, Pressable } from "react-native";
|
||||||
import { Link, router } from "expo-router";
|
import { Link, router } from "expo-router";
|
||||||
import BaseBackground from "../components/BaseBackground";
|
import BaseBackground from "../components/BaseBackground";
|
||||||
import AuthButton from "../components/AuthButton";
|
import AuthButton from "../components/AuthButton";
|
||||||
|
import CustomTextInput from "../components/CustomTextInput";
|
||||||
import { AuthService } from "../services";
|
import { AuthService } from "../services";
|
||||||
import { CaldavConfigService } from "../services/CaldavConfigService";
|
import { CaldavConfigService } from "../services/CaldavConfigService";
|
||||||
import { preloadAppData } from "../components/AuthGuard";
|
import { preloadAppData } from "../components/AuthGuard";
|
||||||
@@ -59,34 +60,22 @@ const LoginScreen = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<TextInput
|
<CustomTextInput
|
||||||
placeholder="E-Mail oder Benutzername"
|
placeholder="E-Mail oder Benutzername"
|
||||||
placeholderTextColor={theme.textMuted}
|
placeholderTextColor={theme.textMuted}
|
||||||
value={identifier}
|
text={identifier}
|
||||||
onChangeText={setIdentifier}
|
onValueChange={setIdentifier}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
className="w-full rounded-lg p-4 mb-4"
|
className="w-full rounded-lg p-4 mb-4"
|
||||||
style={{
|
|
||||||
backgroundColor: theme.secondaryBg,
|
|
||||||
color: theme.textPrimary,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.borderPrimary,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<CustomTextInput
|
||||||
placeholder="Passwort"
|
placeholder="Passwort"
|
||||||
placeholderTextColor={theme.textMuted}
|
placeholderTextColor={theme.textMuted}
|
||||||
value={password}
|
text={password}
|
||||||
onChangeText={setPassword}
|
onValueChange={setPassword}
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
className="w-full rounded-lg p-4 mb-6"
|
className="w-full rounded-lg p-4 mb-6"
|
||||||
style={{
|
|
||||||
backgroundColor: theme.secondaryBg,
|
|
||||||
color: theme.textPrimary,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.borderPrimary,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AuthButton
|
<AuthButton
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { View, Text, TextInput, Pressable } from "react-native";
|
import { View, Text, Pressable } from "react-native";
|
||||||
import { Link, router } from "expo-router";
|
import { Link, router } from "expo-router";
|
||||||
import BaseBackground from "../components/BaseBackground";
|
import BaseBackground from "../components/BaseBackground";
|
||||||
import AuthButton from "../components/AuthButton";
|
import AuthButton from "../components/AuthButton";
|
||||||
|
import CustomTextInput from "../components/CustomTextInput";
|
||||||
import { AuthService } from "../services";
|
import { AuthService } from "../services";
|
||||||
import { useThemeStore } from "../stores/ThemeStore";
|
import { useThemeStore } from "../stores/ThemeStore";
|
||||||
|
|
||||||
@@ -59,50 +60,32 @@ const RegisterScreen = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<TextInput
|
<CustomTextInput
|
||||||
placeholder="E-Mail"
|
placeholder="E-Mail"
|
||||||
placeholderTextColor={theme.textMuted}
|
placeholderTextColor={theme.textMuted}
|
||||||
value={email}
|
text={email}
|
||||||
onChangeText={setEmail}
|
onValueChange={setEmail}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
keyboardType="email-address"
|
keyboardType="email-address"
|
||||||
className="w-full rounded-lg p-4 mb-4"
|
className="w-full rounded-lg p-4 mb-4"
|
||||||
style={{
|
|
||||||
backgroundColor: theme.secondaryBg,
|
|
||||||
color: theme.textPrimary,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.borderPrimary,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<CustomTextInput
|
||||||
placeholder="Benutzername"
|
placeholder="Benutzername"
|
||||||
placeholderTextColor={theme.textMuted}
|
placeholderTextColor={theme.textMuted}
|
||||||
value={userName}
|
text={userName}
|
||||||
onChangeText={setUserName}
|
onValueChange={setUserName}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
className="w-full rounded-lg p-4 mb-4"
|
className="w-full rounded-lg p-4 mb-4"
|
||||||
style={{
|
|
||||||
backgroundColor: theme.secondaryBg,
|
|
||||||
color: theme.textPrimary,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.borderPrimary,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<CustomTextInput
|
||||||
placeholder="Passwort"
|
placeholder="Passwort"
|
||||||
placeholderTextColor={theme.textMuted}
|
placeholderTextColor={theme.textMuted}
|
||||||
value={password}
|
text={password}
|
||||||
onChangeText={setPassword}
|
onValueChange={setPassword}
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
className="w-full rounded-lg p-4 mb-6"
|
className="w-full rounded-lg p-4 mb-6"
|
||||||
style={{
|
|
||||||
backgroundColor: theme.secondaryBg,
|
|
||||||
color: theme.textPrimary,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: theme.borderPrimary,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AuthButton
|
<AuthButton
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { TextInput } from "react-native";
|
import { TextInput, TextInputProps } from "react-native";
|
||||||
import { useThemeStore } from "../stores/ThemeStore";
|
import { useThemeStore } from "../stores/ThemeStore";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
@@ -8,6 +8,11 @@ export type CustomTextInputProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
multiline?: boolean;
|
multiline?: boolean;
|
||||||
onValueChange?: (text: string) => void;
|
onValueChange?: (text: string) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
placeholderTextColor?: string;
|
||||||
|
secureTextEntry?: boolean;
|
||||||
|
autoCapitalize?: TextInputProps["autoCapitalize"];
|
||||||
|
keyboardType?: TextInputProps["keyboardType"];
|
||||||
};
|
};
|
||||||
|
|
||||||
const CustomTextInput = (props: CustomTextInputProps) => {
|
const CustomTextInput = (props: CustomTextInputProps) => {
|
||||||
@@ -16,10 +21,16 @@ const CustomTextInput = (props: CustomTextInputProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
className={`border border-solid rounded-2xl px-3 py-2 h-11/12 ${props.className}`}
|
className={`border border-solid rounded-2xl ${props.className}`}
|
||||||
onChangeText={props.onValueChange}
|
onChangeText={props.onValueChange}
|
||||||
value={props.text}
|
value={props.text}
|
||||||
multiline={props.multiline}
|
multiline={props.multiline}
|
||||||
|
placeholder={props.placeholder}
|
||||||
|
placeholderTextColor={props.placeholderTextColor}
|
||||||
|
secureTextEntry={props.secureTextEntry}
|
||||||
|
autoCapitalize={props.autoCapitalize}
|
||||||
|
keyboardType={props.keyboardType}
|
||||||
|
selection={!focused ? { start: 0 } : undefined}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.messageBorderBg,
|
backgroundColor: theme.messageBorderBg,
|
||||||
color: theme.textPrimary,
|
color: theme.textPrimary,
|
||||||
|
|||||||
Reference in New Issue
Block a user