import { useState } from "react"; import Constants from "expo-constants"; import { Ionicons } from "@expo/vector-icons"; import { router } from "expo-router"; import { Alert, Platform, Pressable, StyleSheet, Switch, Text, View } from "react-native"; import { TabPage } from "@/components/TabPage"; import { TabScrollView } from "@/components/TabScrollView"; import { AppBackground } from "@/components/AppBackground"; import { InstanceUrlField } from "@/components/InstanceUrlField"; import { LoadingScreen } from "@/components/LoadingScreen"; import { PageHeader } from "@/components/PageHeader"; import { PinPrompt } from "@/components/PinPrompt"; import { Button } from "@/components/ui/Button"; import { Card } from "@/components/ui/Card"; import { fonts, spacing } from "@/constants/theme"; import { useAccounts } from "@/contexts/AccountsContext"; import { useAppLock } from "@/contexts/AppLockContext"; import { useAuthClient, useSession } from "@/contexts/AuthContext"; import { type ColorMode, useAppTheme } from "@/contexts/ThemeContext"; import { startAdditionalAccountSignIn } from "@/lib/add-account"; import { confirmRemoveAccount, finishAccountRemoval } from "@/lib/account-actions"; import { api } from "@/lib/trpc"; const THEME_OPTIONS: { value: ColorMode; label: string }[] = [ { value: "system", label: "System" }, { value: "light", label: "Light" }, { value: "dark", label: "Dark" }, ]; export default function SettingsScreen() { const authClient = useAuthClient(); const { data: session } = useSession(); const { accounts, activeAccount, activeAccountId, apiUrl, switchAccount, removeAccount, refreshAccounts, clearActiveAccount, } = useAccounts(); const { colors, colorMode, setColorMode } = useAppTheme(); const switchProps = { trackColor: { false: colors.switchTrackOff, true: colors.switchTrackOn }, thumbColor: Platform.OS === "android" ? colors.switchThumb : undefined, ios_backgroundColor: colors.switchIosBackground, }; const { enabled: lockEnabled, biometricEnabled, biometricAvailable, biometricLabel, enableLock, disableLock, changePin, setUseBiometric, lock, } = useAppLock(); const profileQuery = api.settings.getProfile.useQuery(); const [pinPrompt, setPinPrompt] = useState< | { mode: "create" } | { mode: "confirm-disable" } | { mode: "change-current" } | { mode: "change-next" } | null >(null); const [pendingPin, setPendingPin] = useState(""); const [showAdvanced, setShowAdvanced] = useState(false); const [refreshingAccounts, setRefreshingAccounts] = useState(false); async function handleRefreshAccounts() { setRefreshingAccounts(true); try { await refreshAccounts(); await profileQuery.refetch(); } finally { setRefreshingAccounts(false); } } function handleRemoveAccount(accountId: string, label: string) { confirmRemoveAccount( label, () => removeAccount(accountId), async (result) => { await finishAccountRemoval({ result, clearActiveAccount, signOut: () => authClient.signOut(), }); }, ); } async function handleSignOut() { await authClient.signOut(); await clearActiveAccount(); router.replace("/(auth)/sign-in"); } function confirmSignOut() { Alert.alert("Sign out", "Sign out of this account on this device?", [ { text: "Cancel", style: "cancel" }, { text: "Sign out", style: "destructive", onPress: () => void handleSignOut() }, ]); } function confirmInstanceChange() { Alert.alert( "Server updated", "You may need to sign in again if you switched to a different instance.", [{ text: "OK" }], ); } function handleLockToggle(next: boolean) { if (next) { setPinPrompt({ mode: "create" }); return; } setPinPrompt({ mode: "confirm-disable" }); } function handleChangePin() { setPendingPin(""); setPinPrompt({ mode: "change-current" }); } function handleBiometricToggle(next: boolean) { void setUseBiometric(next); } async function handlePinPromptSubmit(pin: string) { if (pinPrompt?.mode === "create") { try { await enableLock(pin); setPinPrompt(null); } catch (err) { Alert.alert("Could not enable lock", err instanceof Error ? err.message : "Try again"); } return; } if (pinPrompt?.mode === "confirm-disable") { const success = await disableLock(pin); if (!success) { Alert.alert("Incorrect PIN", "Could not disable app lock."); return; } setPinPrompt(null); return; } if (pinPrompt?.mode === "change-current") { setPendingPin(pin); setPinPrompt({ mode: "change-next" }); return; } if (pinPrompt?.mode === "change-next") { const success = await changePin(pendingPin, pin); if (!success) { Alert.alert("Could not change PIN", "Check your current PIN and try again."); return; } setPendingPin(""); setPinPrompt(null); Alert.alert("PIN updated", "Your app lock PIN has been changed."); } } if (profileQuery.isLoading) { return ; } const profile = profileQuery.data; const appVersion = Constants.expoConfig?.version ?? "1.0.0"; return ( { setPendingPin(""); setPinPrompt(null); }} onSubmit={(pin) => void handlePinPromptSubmit(pin)} /> } keyboardShouldPersistTaps="handled" > {profile?.name ?? session?.user.name ?? "User"} {profile?.email ?? session?.user.email} {profile?.role ? ( Role: {profile.role} ) : null} {accounts.map((account) => { const isActive = account.id === activeAccountId; return ( void switchAccount(account.id)} style={({ pressed }) => [styles.accountMain, pressed && styles.pressed]} > {account.name || account.email} {account.email} {account.instanceUrl.replace(/^https?:\/\//, "")} {isActive ? ( Active ) : null} handleRemoveAccount(account.id, account.name || account.email) } style={({ pressed }) => [styles.removeButton, pressed && styles.pressed]} > ); })}