Some checks failed
continuous-integration/drone/push Build encountered an error
Add Drone CI with server build/test and format check pipelines. Add unit tests for password utils and recurrenceExpander. Add check_format script, fix Jest config to ignore dist/, remove dead CaldavService.test.ts, apply Prettier formatting.
252 lines
6.5 KiB
TypeScript
252 lines
6.5 KiB
TypeScript
import { ActivityIndicator, Text, View } from "react-native";
|
|
import BaseBackground from "../../components/BaseBackground";
|
|
import BaseButton, { BaseButtonProps } from "../../components/BaseButton";
|
|
import { useThemeStore } from "../../stores/ThemeStore";
|
|
import { AuthService } from "../../services/AuthService";
|
|
import { router } from "expo-router";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { SimpleHeader } from "../../components/Header";
|
|
import { THEMES } from "../../Themes";
|
|
import CustomTextInput from "../../components/CustomTextInput";
|
|
import { useCallback, useRef, useState } from "react";
|
|
import { CaldavConfigService } from "../../services/CaldavConfigService";
|
|
import { useCaldavConfigStore } from "../../stores";
|
|
|
|
const handleLogout = async () => {
|
|
await AuthService.logout();
|
|
router.replace("/login");
|
|
};
|
|
|
|
const SettingsButton = (props: BaseButtonProps) => {
|
|
return (
|
|
<BaseButton
|
|
onPress={props.onPress}
|
|
solid={props.solid}
|
|
className={"w-11/12"}
|
|
>
|
|
{props.children}
|
|
</BaseButton>
|
|
);
|
|
};
|
|
|
|
type CaldavTextInputProps = {
|
|
title: string;
|
|
value: string;
|
|
onValueChange: (text: string) => void;
|
|
secureTextEntry?: boolean;
|
|
};
|
|
|
|
const CaldavTextInput = ({
|
|
title,
|
|
value,
|
|
onValueChange,
|
|
secureTextEntry,
|
|
}: CaldavTextInputProps) => {
|
|
const { theme } = useThemeStore();
|
|
return (
|
|
<View className="flex flex-row items-center py-1">
|
|
<Text className="ml-4 w-24" style={{ color: theme.textPrimary }}>
|
|
{title}:
|
|
</Text>
|
|
<CustomTextInput
|
|
className="flex-1 mr-4 px-3 py-2"
|
|
text={value}
|
|
onValueChange={onValueChange}
|
|
secureTextEntry={secureTextEntry}
|
|
/>
|
|
</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 { theme } = useThemeStore();
|
|
const { config, setConfig } = useCaldavConfigStore();
|
|
|
|
const [serverUrl, setServerUrl] = useState(config?.serverUrl ?? "");
|
|
const [username, setUsername] = useState(config?.username ?? "");
|
|
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 () => {
|
|
showFeedback(
|
|
setSaveFeedback,
|
|
saveTimer,
|
|
"Speichere Konfiguration...",
|
|
false,
|
|
true,
|
|
);
|
|
try {
|
|
const saved = await CaldavConfigService.saveConfig(
|
|
serverUrl,
|
|
username,
|
|
password,
|
|
);
|
|
setConfig(saved);
|
|
showFeedback(
|
|
setSaveFeedback,
|
|
saveTimer,
|
|
"Konfiguration wurde gespeichert",
|
|
false,
|
|
);
|
|
} catch {
|
|
showFeedback(
|
|
setSaveFeedback,
|
|
saveTimer,
|
|
"Fehler beim Speichern der Konfiguration",
|
|
true,
|
|
);
|
|
}
|
|
};
|
|
|
|
const sync = async () => {
|
|
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 (
|
|
<>
|
|
<View>
|
|
<Text
|
|
className="text-center text-2xl"
|
|
style={{ color: theme.textPrimary }}
|
|
>
|
|
Caldav Config
|
|
</Text>
|
|
</View>
|
|
<View>
|
|
<View className="pb-1">
|
|
<CaldavTextInput
|
|
title="url"
|
|
value={serverUrl}
|
|
onValueChange={setServerUrl}
|
|
/>
|
|
<CaldavTextInput
|
|
title="username"
|
|
value={username}
|
|
onValueChange={setUsername}
|
|
/>
|
|
<CaldavTextInput
|
|
title="password"
|
|
value={password}
|
|
onValueChange={setPassword}
|
|
secureTextEntry
|
|
/>
|
|
</View>
|
|
<View className="flex flex-row">
|
|
<BaseButton className="mx-4 w-1/5" solid={true} onPress={saveConfig}>
|
|
Save
|
|
</BaseButton>
|
|
<BaseButton className="w-1/5" solid={true} onPress={sync}>
|
|
Sync
|
|
</BaseButton>
|
|
</View>
|
|
<FeedbackRow feedback={saveFeedback} />
|
|
<FeedbackRow feedback={syncFeedback} />
|
|
</View>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const Settings = () => {
|
|
const { theme, setTheme } = useThemeStore();
|
|
|
|
return (
|
|
<BaseBackground>
|
|
<SimpleHeader text="Settings" />
|
|
<View className="flex items-center mt-4">
|
|
<SettingsButton onPress={handleLogout} solid={true}>
|
|
<Ionicons name="log-out-outline" size={24} color={theme.primeFg} />{" "}
|
|
Logout
|
|
</SettingsButton>
|
|
<View>
|
|
<Text
|
|
className="text-center text-2xl"
|
|
style={{ color: theme.textPrimary }}
|
|
>
|
|
Select Theme
|
|
</Text>
|
|
</View>
|
|
<SettingsButton
|
|
solid={theme == THEMES.defaultLight}
|
|
onPress={() => {
|
|
setTheme("defaultLight");
|
|
}}
|
|
>
|
|
Default Light
|
|
</SettingsButton>
|
|
<SettingsButton
|
|
solid={theme == THEMES.defaultDark}
|
|
onPress={() => {
|
|
setTheme("defaultDark");
|
|
}}
|
|
>
|
|
Default Dark
|
|
</SettingsButton>
|
|
</View>
|
|
<CaldavSettings />
|
|
</BaseBackground>
|
|
);
|
|
};
|
|
|
|
export default Settings;
|