11import { Trans , useLingui } from "@lingui/react/macro" ;
2- import { useMutation , useQuery } from "@tanstack/react-query" ;
2+ import { useMutation , useQuery , useQueryClient } from "@tanstack/react-query" ;
33import {
44 CheckIcon ,
55 ChevronDownIcon ,
@@ -16,6 +16,7 @@ import { useState } from "react";
1616import SoundIndicator from "@/components/sound-indicator" ;
1717import { useHypr } from "@/contexts" ;
1818import { useEnhancePendingState } from "@/hooks/enhance-pending" ;
19+ import { MicrophoneDeviceInfo } from "@/utils/microphone-devices" ;
1920import { commands as listenerCommands } from "@hypr/plugin-listener" ;
2021import { commands as localSttCommands } from "@hypr/plugin-local-stt" ;
2122import { Button } from "@hypr/ui/components/ui/button" ;
@@ -337,32 +338,6 @@ function AudioControlButton({
337338 ) ;
338339}
339340
340- function parseDeviceData ( result : string | null | undefined ) : { devices : string [ ] ; selected ?: string [ ] } | null {
341- if ( ! result || ! result . startsWith ( "DEVICES:" ) ) {
342- return null ;
343- }
344-
345- const devicesJson = result . substring ( 8 ) ;
346- try {
347- const parsedData = JSON . parse ( devicesJson ) ;
348-
349- // Check if it's the new format with devices and selected
350- if ( parsedData && typeof parsedData === "object" && parsedData . devices ) {
351- return parsedData ;
352- }
353-
354- // Fallback to old format (array of devices)
355- if ( Array . isArray ( parsedData ) ) {
356- return { devices : parsedData } ;
357- }
358-
359- return null ;
360- } catch ( e ) {
361- console . error ( "Failed to parse device data:" , e ) ;
362- return null ;
363- }
364- }
365-
366341function MicControlWithDropdown ( {
367342 isMuted,
368343 onMuteClick,
@@ -373,6 +348,7 @@ function MicControlWithDropdown({
373348 disabled ?: boolean ;
374349} ) {
375350 const { t } = useLingui ( ) ;
351+ const queryClient = useQueryClient ( ) ;
376352
377353 const Icon = isMuted ? MicOffIcon : MicIcon ;
378354
@@ -381,7 +357,7 @@ function MicControlWithDropdown({
381357 queryFn : ( ) => listenerCommands . checkMicrophoneAccess ( ) ,
382358 } ) ;
383359
384- const deviceQuery = useQuery ( {
360+ const deviceQuery = useQuery < MicrophoneDeviceInfo > ( {
385361 queryKey : [ "microphoneDeviceInfo" ] ,
386362 queryFn : async ( ) => {
387363 const result = await listenerCommands . getSelectedMicrophoneDevice ( ) ;
@@ -390,31 +366,6 @@ function MicControlWithDropdown({
390366 enabled : micPermissionStatus . data === true ,
391367 } ) ;
392368
393- const microphoneDevices = useQuery ( {
394- queryKey : [ "microphoneDevices" , deviceQuery . data ] ,
395- queryFn : async ( ) => {
396- const result = deviceQuery . data ;
397- return parseDeviceData ( result ) ?. devices || [ ] ;
398- } ,
399- enabled : micPermissionStatus . data === true && deviceQuery . data !== undefined ,
400- } ) ;
401-
402- const selectedDevice = useQuery ( {
403- queryKey : [ "selectedMicrophoneDevice" , deviceQuery . data ] ,
404- queryFn : async ( ) => {
405- const result = deviceQuery . data ;
406- const parsedData = parseDeviceData ( result ) ;
407-
408- if ( parsedData ?. selected ) {
409- return parsedData . selected [ 0 ] || null ; // Get first (and only) selected device
410- }
411-
412- // If no parsed data or no selected field, return the original result
413- return result || null ;
414- } ,
415- enabled : micPermissionStatus . data === true && deviceQuery . data !== undefined ,
416- } ) ;
417-
418369 const updateSelectedDevice = useMutation ( {
419370 mutationFn : ( deviceName : string | null ) => listenerCommands . setSelectedMicrophoneDevice ( deviceName ) ,
420371 onSuccess : ( _ , deviceName ) => {
@@ -424,10 +375,10 @@ function MicControlWithDropdown({
424375 duration : 2000 ,
425376 } ) ;
426377
427- // Force immediate refetch to ensure UI updates instantly
428- deviceQuery . refetch ( ) ;
429- microphoneDevices . refetch ( ) ;
430- selectedDevice . refetch ( ) ;
378+ // Invalidate all microphone-related queries
379+ queryClient . invalidateQueries ( { queryKey : [ "microphoneDeviceInfo" ] } ) ;
380+ queryClient . invalidateQueries ( { queryKey : [ "microphoneDevices" ] } ) ;
381+ queryClient . invalidateQueries ( { queryKey : [ "selectedMicrophoneDevice" ] } ) ;
431382 } ,
432383 onError : ( error , deviceName ) => {
433384 const displayName = deviceName === null ? t `System Default` : deviceName ;
@@ -437,10 +388,10 @@ function MicControlWithDropdown({
437388 duration : 4000 ,
438389 } ) ;
439390
440- // Even on error, refresh to show correct state
441- deviceQuery . refetch ( ) ;
442- microphoneDevices . refetch ( ) ;
443- selectedDevice . refetch ( ) ;
391+ // Refresh state even on error
392+ queryClient . invalidateQueries ( { queryKey : [ "microphoneDeviceInfo" ] } ) ;
393+ queryClient . invalidateQueries ( { queryKey : [ "microphoneDevices" ] } ) ;
394+ queryClient . invalidateQueries ( { queryKey : [ "selectedMicrophoneDevice" ] } ) ;
444395 } ,
445396 } ) ;
446397
@@ -450,11 +401,11 @@ function MicControlWithDropdown({
450401 } ;
451402
452403 const getSelectedDevice = ( ) => {
453- const currentDevice = selectedDevice . data ;
404+ const currentDevice = deviceQuery . data ?. selected ;
454405 if ( ! currentDevice ) {
455406 return "default" ;
456407 }
457- if ( microphoneDevices . data && ! microphoneDevices . data . includes ( currentDevice ) ) {
408+ if ( deviceQuery . data ?. devices && ! deviceQuery . data . devices . includes ( currentDevice ) ) {
458409 return "default" ;
459410 }
460411 return currentDevice ;
@@ -487,7 +438,7 @@ function MicControlWithDropdown({
487438 variant = "ghost"
488439 size = "icon"
489440 className = "w-6 px-1"
490- disabled = { microphoneDevices . isLoading || updateSelectedDevice . isPending }
441+ disabled = { deviceQuery . isLoading || updateSelectedDevice . isPending }
491442 >
492443 < ChevronDownIcon size = { 12 } />
493444 </ Button >
@@ -498,14 +449,14 @@ function MicControlWithDropdown({
498449 < Trans > Microphone</ Trans >
499450 </ div >
500451
501- { microphoneDevices . isLoading
452+ { deviceQuery . isLoading
502453 ? (
503454 < div className = "flex items-center gap-2 px-2 py-1.5 text-sm" >
504455 < Spinner size = { 14 } />
505456 < Trans > Loading devices...</ Trans >
506457 </ div >
507458 )
508- : microphoneDevices . error
459+ : deviceQuery . error
509460 ? (
510461 < div className = "px-2 py-1.5 text-sm text-red-600" >
511462 < Trans > Failed to load microphone devices. Please check permissions.</ Trans >
@@ -523,7 +474,7 @@ function MicControlWithDropdown({
523474 { getSelectedDevice ( ) === "default" && < CheckIcon size = { 16 } className = "text-green-600" /> }
524475 </ Button >
525476
526- { microphoneDevices . data ?. map ( ( device ) => (
477+ { deviceQuery . data ?. devices ?. map ( ( device ) => (
527478 < Button
528479 key = { device }
529480 variant = "ghost"
0 commit comments