feat: add CalDAV synchronization with automatic sync
- Add CaldavService with tsdav/ical.js for CalDAV server communication - Add CaldavController, CaldavRepository, and caldav routes - Add client-side CaldavConfigService with sync(), config CRUD - Add CalDAV settings UI with config load/save in settings screen - Sync on login, auto-login (AuthGuard), periodic timer (calendar), and sync button - Push single events to CalDAV on server-side create/update/delete - Push all events to CalDAV after chat event confirmation - Refactor ChatService to use EventService instead of direct EventRepository - Rename CalDav/calDav to Caldav/caldav for consistent naming - Add Radicale Docker setup for local CalDAV testing - Update PlantUML diagrams and CLAUDE.md with CalDAV architecture
This commit is contained in:
@@ -1,18 +1,106 @@
|
||||
import { Text, View } from "react-native";
|
||||
import BaseBackground from "../../components/BaseBackground";
|
||||
import BaseButton from "../../components/BaseButton";
|
||||
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 { useEffect, useState } from "react";
|
||||
import { CaldavConfigService } from "../../services/CaldavConfigService";
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
const CaldavTextInput = ({ title, value, onValueChange }: CaldavTextInputProps) => {
|
||||
return (
|
||||
<View className="flex flex-row items-center py-1">
|
||||
<Text className="ml-4 w-24">{title}:</Text>
|
||||
<CustomTextInput className="flex-1 mr-4" text={value} onValueChange={onValueChange} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const CaldavSettings = () => {
|
||||
const { theme } = useThemeStore();
|
||||
|
||||
const [serverUrl, setServerUrl] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
const config = await CaldavConfigService.getConfig();
|
||||
setServerUrl(config.serverUrl);
|
||||
setUsername(config.username);
|
||||
setPassword(config.password);
|
||||
} catch {
|
||||
// No config saved yet
|
||||
}
|
||||
};
|
||||
loadConfig();
|
||||
}, []);
|
||||
|
||||
const saveConfig = async () => {
|
||||
await CaldavConfigService.saveConfig(serverUrl, username, password);
|
||||
};
|
||||
|
||||
const sync = async () => {
|
||||
await CaldavConfigService.sync();
|
||||
};
|
||||
|
||||
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} />
|
||||
</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>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Settings = () => {
|
||||
const { theme, setTheme } = useThemeStore();
|
||||
|
||||
@@ -20,10 +108,10 @@ const Settings = () => {
|
||||
<BaseBackground>
|
||||
<SimpleHeader text="Settings" />
|
||||
<View className="flex items-center mt-4">
|
||||
<BaseButton onPress={handleLogout} solid={true}>
|
||||
<SettingsButton onPress={handleLogout} solid={true}>
|
||||
<Ionicons name="log-out-outline" size={24} color={theme.primeFg} />{" "}
|
||||
Logout
|
||||
</BaseButton>
|
||||
</SettingsButton>
|
||||
<View>
|
||||
<Text
|
||||
className="text-center text-2xl"
|
||||
@@ -32,23 +120,24 @@ const Settings = () => {
|
||||
Select Theme
|
||||
</Text>
|
||||
</View>
|
||||
<BaseButton
|
||||
<SettingsButton
|
||||
solid={theme == THEMES.defaultLight}
|
||||
onPress={() => {
|
||||
setTheme("defaultLight");
|
||||
}}
|
||||
>
|
||||
Default Light
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
</SettingsButton>
|
||||
<SettingsButton
|
||||
solid={theme == THEMES.defaultDark}
|
||||
onPress={() => {
|
||||
setTheme("defaultDark");
|
||||
}}
|
||||
>
|
||||
Default Dark
|
||||
</BaseButton>
|
||||
</SettingsButton>
|
||||
</View>
|
||||
<CaldavSettings />
|
||||
</BaseBackground>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user