diff --git a/src/App.tsx b/src/App.tsx index cf96296..873d084 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -151,7 +151,12 @@ function App() { } if (onboardingStep === "accessibility") { - return ; + return ( + + ); } if (onboardingStep === "model") { diff --git a/src/components/onboarding/AccessibilityOnboarding.tsx b/src/components/onboarding/AccessibilityOnboarding.tsx index 0a4e52f..f473c37 100644 --- a/src/components/onboarding/AccessibilityOnboarding.tsx +++ b/src/components/onboarding/AccessibilityOnboarding.tsx @@ -11,10 +11,11 @@ import { toast } from "sonner"; import { commands } from "@/bindings"; import { useSettingsStore } from "@/stores/settingsStore"; import DictxTextLogo from "../icons/DictxTextLogo"; -import { Keyboard, Mic, Check, Loader2 } from "lucide-react"; +import { Keyboard, Mic, Check, Loader2, RefreshCw } from "lucide-react"; interface AccessibilityOnboardingProps { onComplete: () => void; + isReturningUser?: boolean; } type PermissionStatus = "checking" | "needed" | "waiting" | "granted"; @@ -26,6 +27,7 @@ interface PermissionsState { const AccessibilityOnboarding: React.FC = ({ onComplete, + isReturningUser = false, }) => { const { t } = useTranslation(); const refreshAudioDevices = useSettingsStore( @@ -200,6 +202,39 @@ const AccessibilityOnboarding: React.FC = ({ } }; + const handleRetryCheck = async () => { + try { + const [accessibilityGranted, microphoneGranted] = await Promise.all([ + checkAccessibilityPermission(), + checkMicrophonePermission(), + ]); + + if (accessibilityGranted) { + await Promise.all([ + commands.initializeEnigo(), + commands.initializeShortcuts(), + ]).catch((e) => + console.warn("Failed to initialize after permission grant:", e), + ); + } + + setPermissions({ + accessibility: accessibilityGranted ? "granted" : "waiting", + microphone: microphoneGranted ? "granted" : "waiting", + }); + + if (accessibilityGranted && microphoneGranted) { + await Promise.all([refreshAudioDevices(), refreshOutputDevices()]); + timeoutRef.current = setTimeout(() => onComplete(), 300); + } else { + startPolling(); + } + } catch (error) { + console.error("Failed to retry permission check:", error); + toast.error(t("onboarding.permissions.errors.checkFailed")); + } + }; + // Still checking platform/initial permissions if ( isMacOS === null || @@ -263,9 +298,18 @@ const AccessibilityOnboarding: React.FC = ({ {t("onboarding.permissions.granted")} ) : permissions.microphone === "waiting" ? ( -
- - {t("onboarding.permissions.waiting")} +
+
+ + {t("onboarding.permissions.waiting")} +
+
) : (
) : permissions.accessibility === "waiting" ? ( -
- - {t("onboarding.permissions.waiting")} +
+
+ + {t("onboarding.permissions.waiting")} +
+
) : (
+ + {/* Restart hint */} + {(permissions.accessibility === "waiting" || + permissions.microphone === "waiting") && ( +

+ {t("onboarding.permissions.restartHint")} +

+ )} + + {/* Continue anyway for returning users */} + {isReturningUser && ( + + )} ); diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 59496fe..da0f9db 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -101,6 +101,9 @@ "grant": "Grant Permission", "granted": "Granted", "waiting": "Waiting...", + "retry": "Retry", + "restartHint": "If stuck, try toggling the permission off and on in System Settings, then click Retry.", + "skipForNow": "Continue without permissions — I'll fix this later", "allGranted": "All set!", "errors": { "checkFailed": "Failed to check permissions. Please try again.",