import { DeleteForeverRounded, PauseCircleOutlineRounded, PlayCircleOutlineRounded, SettingsRounded, WarningRounded, } from '@mui/icons-material' import { Box, Typography, alpha, useTheme } from '@mui/material' import { useLockFn } from 'ahooks' import { useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { type DialogRef, Switch, TooltipIcon } from '@/components/base' import { SysproxyViewer } from '@/components/setting/mods/sysproxy-viewer' import { TunViewer } from '@/components/setting/mods/tun-viewer' import { useServiceUninstaller } from '@/hooks/use-service-uninstaller' import { useSystemProxyState } from '@/hooks/use-system-proxy-state' import { useSystemState } from '@/hooks/use-system-state' import { useVerge } from '@/hooks/use-verge' import { showNotice } from '@/services/notice-service' import { requestService } from '@/services/service-request' interface ProxySwitchProps { label?: string onError?: (err: Error) => void noRightPadding?: boolean } interface SwitchRowProps { label: string active: boolean disabled?: boolean infoTitle: string onInfoClick?: () => void extraIcons?: React.ReactNode /** Return false to roll back without reporting an error. */ onToggle: (value: boolean) => Promise onError?: (err: Error) => void highlight?: boolean } /** * 抽取的子组件:统一的开关 UI * active = 真实状态OS/配置 乐观更新 */ const SwitchRow = ({ label, active, disabled, infoTitle, onInfoClick, extraIcons, onToggle, onError, highlight, }: SwitchRowProps) => { const theme = useTheme() const [checked, setChecked] = useState(active) const pendingRef = useRef(false) if (pendingRef.current) { if (active === checked) pendingRef.current = false } else if (checked !== active) { setChecked(active) } const handleChange = (_: React.ChangeEvent, value: boolean) => { pendingRef.current = true setChecked(value) onToggle(value) .then((applied) => { if (applied === false) setChecked(active) }) .catch((err: any) => { setChecked(active) onError?.(err) }) .finally(() => { pendingRef.current = false }) } return ( {active ? ( ) : ( )} {label} {extraIcons} ) } const ProxyControlSwitches = ({ label, onError, noRightPadding = false, }: ProxySwitchProps) => { const { t } = useTranslation() const { verge, mutateVerge, patchVerge } = useVerge() const { uninstallServiceAndStartSidecar } = useServiceUninstaller() const { indicator: systemProxyIndicator, toggleSystemProxy } = useSystemProxyState() const { runState, isTunModeAvailable, isLoading } = useSystemState() // Offer to uninstall only a service that is actually there and working. const isServiceInstallReady = runState.serviceUsable const sysproxyRef = useRef(null) const tunRef = useRef(null) const { enable_tun_mode } = verge ?? {} // Enabling needs a running core; disabling only writes OS state and must stay available. const handleSystemProxyToggle = async (value: boolean) => { if (value && !isLoading && runState.mode === 'NotRunning') { showNotice.error('settings.feedback.errors.sysproxy.coreNotReady') return false } await toggleSystemProxy(value) } const handleTunToggle = async (value: boolean) => { if (value && !isTunModeAvailable) { requestService({ reason: 'tunNeedsService', restore: { enable_tun_mode: value }, }) return false } mutateVerge({ ...verge, enable_tun_mode: value }, false) await patchVerge({ enable_tun_mode: value }) } const onUninstallService = useLockFn(async () => { try { await uninstallServiceAndStartSidecar() } catch (err) { showNotice.error(err) } }) const isSystemProxyMode = label === t('settings.sections.system.toggles.systemProxy') || !label const isTunMode = label === t('settings.sections.system.toggles.tunMode') return ( {isSystemProxyMode && ( sysproxyRef.current?.open()} onToggle={handleSystemProxyToggle} onError={onError} highlight={systemProxyIndicator} /> )} {isTunMode && ( tunRef.current?.open()} onToggle={handleTunToggle} onError={onError} highlight={(enable_tun_mode && isTunModeAvailable) || false} extraIcons={ <> {!isTunModeAvailable && ( )} {isServiceInstallReady && ( )} } /> )} ) } export default ProxyControlSwitches