Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/features/voice-conversation/ui/VoiceSettings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ const microphonePermissionState = vi.hoisted(() => ({
openSettingsError: false,
openSettings: vi.fn(),
}));
const updaterState = vi.hoisted(() => ({
pendingInstall: null as { installed: boolean } | null,
relaunch: vi.fn(),
}));
vi.mock("@/features/updates/hooks/useUpdater", () => ({
useUpdaterContext: () => ({
runtime: { pendingInstall: updaterState.pendingInstall },
relaunch: updaterState.relaunch,
}),
}));
const openAiStatusState = vi.hoisted(() => ({
enabled: null as boolean | null,
loaded: true,
Expand Down Expand Up @@ -308,6 +318,8 @@ describe("VoiceSettings", () => {
microphonePermissionState.status = "authorized";
microphonePermissionState.openSettingsError = false;
microphonePermissionState.openSettings.mockReset();
updaterState.pendingInstall = null;
updaterState.relaunch.mockReset().mockResolvedValue(undefined);
inputState.backend = "parakeet";
outputState.backend = "pocket";
platformState.current = "mac";
Expand Down Expand Up @@ -901,6 +913,49 @@ describe("VoiceSettings", () => {
expect(microphonePermissionState.openSettings).toHaveBeenCalledOnce();
});

it("offers the installed update's restart when microphone permission is denied", async () => {
microphonePermissionState.status = "denied";
updaterState.pendingInstall = { installed: true };
setupState.current = setup(pocketStatus());
renderWithProviders(<VoiceSettings />);

expect(
screen.getByText(
"Berd has installed an update. Restart Berd, then try the microphone again.",
),
).toBeInTheDocument();
expect(
screen.queryByText(/Microphone access is turned off for Berd/),
).not.toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Restart Berd" }));
expect(updaterState.relaunch).toHaveBeenCalledOnce();
expect(microphonePermissionState.openSettings).not.toHaveBeenCalled();
});

it("keeps permission settings available until an update is installed", () => {
microphonePermissionState.status = "denied";
updaterState.pendingInstall = { installed: false };
setupState.current = setup(pocketStatus());
renderWithProviders(<VoiceSettings />);

expect(
screen.getByRole("button", { name: "Open Microphone Settings" }),
).toBeInTheDocument();
expect(
screen.queryByRole("button", { name: "Restart Berd" }),
).not.toBeInTheDocument();
});

it("does not interrupt authorized microphone access for an installed update", () => {
updaterState.pendingInstall = { installed: true };
setupState.current = setup(pocketStatus());
renderWithProviders(<VoiceSettings />);

expect(
screen.queryByRole("button", { name: "Restart Berd" }),
).not.toBeInTheDocument();
});

it("shows localized guidance when microphone settings cannot open", () => {
microphonePermissionState.status = "denied";
microphonePermissionState.openSettingsError = true;
Expand Down
33 changes: 28 additions & 5 deletions src/features/voice-conversation/ui/VoiceSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CircleAlert } from "lucide-react";
import { useId } from "react";
import { useTranslation } from "react-i18next";
import { useUpdaterContext } from "@/features/updates/hooks/useUpdater";
import { getPlatform } from "@/shared/lib/platform";
import { SettingsPage } from "@/shared/ui/SettingsPage";
import { Alert, AlertDescription, AlertTitle } from "@/shared/ui/alert";
Expand Down Expand Up @@ -169,6 +170,8 @@ export function VoiceSettings() {
const siriSetup = useSiriVoiceSetup(output.backend === "siri");
const siriSupported = getPlatform() === "mac";
const microphonePermission = useMicrophonePermission(siriSupported);
const { runtime, relaunch } = useUpdaterContext();
const hasInstalledUpdate = runtime.pendingInstall?.installed === true;
const inputHeadingId = useId();
const inputDescriptionId = useId();
const outputHeadingId = useId();
Expand Down Expand Up @@ -270,18 +273,38 @@ export function VoiceSettings() {
{microphonePermission.status === "denied" ? (
<Alert variant="destructive">
<CircleAlert />
<AlertTitle>{t("voice.microphonePermissionTitle")}</AlertTitle>
<AlertTitle>
{t(
hasInstalledUpdate
? "voice.microphoneRestartTitle"
: "voice.microphonePermissionTitle",
)}
</AlertTitle>
<AlertDescription>
<p>{t("voice.microphonePermissionDenied")}</p>
<p>
{t(
hasInstalledUpdate
? "voice.microphoneRestartDescription"
: "voice.microphonePermissionDenied",
)}
</p>
<Button
type="button"
variant="alert"
size="sm"
onClick={() => void microphonePermission.openSettings()}
onClick={() =>
void (hasInstalledUpdate
? relaunch()
: microphonePermission.openSettings())
}
>
{t("voice.openMicrophoneSettings")}
{t(
hasInstalledUpdate
? "voice.restartBerd"
: "voice.openMicrophoneSettings",
)}
</Button>
{microphonePermission.openSettingsError ? (
{!hasInstalledUpdate && microphonePermission.openSettingsError ? (
<p>{t("voice.openMicrophoneSettingsError")}</p>
) : null}
</AlertDescription>
Expand Down
3 changes: 3 additions & 0 deletions src/shared/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,9 @@
"notReadyTitle": "Voice Conversation isn't ready",
"microphonePermissionDenied": "Microphone access is turned off for Berd. Allow access in System Settings to use Voice Conversation.",
"microphonePermissionTitle": "Microphone access is required",
"microphoneRestartTitle": "Restart Berd to use the microphone",
"microphoneRestartDescription": "Berd has installed an update. Restart Berd, then try the microphone again.",
"restartBerd": "Restart Berd",
"noVoiceSelected": "No voice selected",
"openMicrophoneSettings": "Open Microphone Settings",
"openMicrophoneSettingsError": "Couldn't open Microphone Settings. Open System Settings and select Privacy & Security > Microphone.",
Expand Down
3 changes: 3 additions & 0 deletions src/shared/i18n/locales/es/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,9 @@
"notReadyTitle": "La conversación por voz no está lista",
"microphonePermissionDenied": "El acceso al micrófono está desactivado para Berd. Permite el acceso en Ajustes del Sistema para usar la conversación por voz.",
"microphonePermissionTitle": "Se requiere acceso al micrófono",
"microphoneRestartTitle": "Reinicia Berd para usar el micrófono",
"microphoneRestartDescription": "Berd ha instalado una actualización. Reinicia Berd y vuelve a probar el micrófono.",
"restartBerd": "Reiniciar Berd",
"noVoiceSelected": "No hay ninguna voz seleccionada",
"openMicrophoneSettings": "Abrir ajustes del micrófono",
"openMicrophoneSettingsError": "No se pudieron abrir los ajustes del micrófono. Abre Ajustes del Sistema y selecciona Privacidad y seguridad > Micrófono.",
Expand Down
Loading