fix(proxy): sync system proxy switch state with background highlight #5240, #5439, #5421, #4651, #4536

Fixed race condition where button state and background color would desync
during rapid toggles or slow backend responses.

Root causes:
- Button used actualState while background used configState
- toggleSystemProxy returned immediately via setTimeout(0), releasing lock
  before backend operation completed
- 100ms delay in updateProxyStatus caused visual flash

Changes:
- Unified state source: both button and background now use actualState
- Made toggleSystemProxy truly async with proper await chain
- Wrapped toggle function with useLockFn to serialize operations
- Reduced state refresh delay from 100ms to 50ms
- Fixed GuardState lock release timing for async operations
- Added proper error rollback with state refresh on failure

Resolves issue where proxy toggle button would show as off while
background remained green, or vice versa.
This commit is contained in:
Tunglies 2025-11-23 12:14:04 +08:00
parent ddd24eb3ac
commit 44adf55975
No known key found for this signature in database
GPG Key ID: B9B01B389469B3E8
3 changed files with 24 additions and 21 deletions

View File

@ -60,6 +60,7 @@ export function GuardState<T>(props: Props<T>) {
if (waitTime <= 0) {
await onGuard(newValue, value!);
lockRef.current = false;
} else {
// debounce guard
clearTimeout(timeRef.current);
@ -71,6 +72,8 @@ export function GuardState<T>(props: Props<T>) {
// 状态回退
onChange(saveRef.current!);
onCatch(err);
} finally {
lockRef.current = false;
}
}, waitTime);
}
@ -78,8 +81,8 @@ export function GuardState<T>(props: Props<T>) {
// 状态回退
onChange(saveRef.current!);
onCatch(err);
lockRef.current = false;
}
lockRef.current = false;
};
const { children: nestedChildren, ...restProps } = childProps;

View File

@ -123,7 +123,7 @@ const ProxyControlSwitches = ({
const sysproxyRef = useRef<DialogRef>(null);
const tunRef = useRef<DialogRef>(null);
const { enable_tun_mode, enable_system_proxy } = verge ?? {};
const { enable_tun_mode } = verge ?? {};
const showErrorNotice = useCallback(
(msg: string) => showNotice.error(msg),
@ -175,7 +175,7 @@ const ProxyControlSwitches = ({
onInfoClick={() => sysproxyRef.current?.open()}
onToggle={(value) => toggleSystemProxy(value)}
onError={onError}
highlight={enable_system_proxy}
highlight={systemProxyActualState}
/>
)}

View File

@ -1,3 +1,4 @@
import { useLockFn } from "ahooks";
import useSWR, { mutate } from "swr";
import { closeAllConnections } from "tauri-plugin-mihomo-api";
@ -41,31 +42,30 @@ export const useSystemProxyState = () => {
}
};
const updateProxyStatus = async () => {
await new Promise((resolve) => setTimeout(resolve, 100));
const updateProxyStatus = async (isEnabling: boolean) => {
// 关闭时更快响应,开启时等待系统确认
const delay = isEnabling ? 20 : 10;
await new Promise((resolve) => setTimeout(resolve, delay));
await mutate("getSystemProxy");
await mutate("getAutotemProxy");
};
const toggleSystemProxy = (enabled: boolean) => {
const toggleSystemProxy = useLockFn(async (enabled: boolean) => {
mutateVerge({ ...verge, enable_system_proxy: enabled }, false);
setTimeout(async () => {
try {
if (!enabled && verge?.auto_close_connection) {
closeAllConnections();
}
await patchVerge({ enable_system_proxy: enabled });
updateProxyStatus();
} catch (error) {
console.warn("[useSystemProxyState] toggleSystemProxy failed:", error);
mutateVerge({ ...verge, enable_system_proxy: !enabled }, false);
try {
if (!enabled && verge?.auto_close_connection) {
await closeAllConnections();
}
}, 0);
return Promise.resolve();
};
await patchVerge({ enable_system_proxy: enabled });
await updateProxyStatus(enabled);
} catch (error) {
console.warn("[useSystemProxyState] toggleSystemProxy failed:", error);
mutateVerge({ ...verge, enable_system_proxy: !enabled }, false);
await updateProxyStatus(!enabled);
throw error;
}
});
return {
actualState: getSystemProxyActualState(),