diff --git a/com.twin.app.shoptime/assets/images/icons/ico_microphone.png b/com.twin.app.shoptime/assets/images/icons/ico_microphone.png
new file mode 100644
index 00000000..55d94cc2
Binary files /dev/null and b/com.twin.app.shoptime/assets/images/icons/ico_microphone.png differ
diff --git a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx
index 8da4a057..aed8544f 100644
--- a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx
+++ b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx
@@ -1,48 +1,64 @@
// src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.jsx
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import PropTypes from 'prop-types';
+import React, {
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
+
import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import {
+ useDispatch,
+ useSelector,
+} from 'react-redux';
+
import Spotlight from '@enact/spotlight';
-import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
+import SpotlightContainerDecorator
+ from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
-import { useDispatch, useSelector } from 'react-redux';
-import TInput, { ICONS, KINDS } from '../../../components/TInput/TInput';
-import TFullPopup from '../../../components/TFullPopup/TFullPopup';
import micIcon from '../../../../assets/images/searchpanel/image-mic.png';
import { getShopperHouseSearch } from '../../../actions/searchActions';
-import css from './VoiceInputOverlay.module.less';
-import VoicePromptScreen from './modes/VoicePromptScreen';
+import TFullPopup from '../../../components/TFullPopup/TFullPopup';
+import TInput, {
+ ICONS,
+ KINDS,
+} from '../../../components/TInput/TInput';
import VoiceListening from './modes/VoiceListening';
+import VoiceNotRecognized from './modes/VoiceNotRecognized';
+import VoiceNotRecognizedCircle from './modes/VoiceNotRecognizedCircle';
+import VoicePromptScreen from './modes/VoicePromptScreen';
+import css from './VoiceInputOverlay.module.less';
const OverlayContainer = SpotlightContainerDecorator(
{
- enterTo: 'default-element',
- restrict: 'self-only', // 포커스를 overlay 내부로만 제한
+ enterTo: "default-element",
+ restrict: "self-only", // 포커스를 overlay 내부로만 제한
},
- 'div'
+ "div"
);
-const SpottableMicButton = Spottable('div');
+const SpottableMicButton = Spottable("div");
// Voice overlay 모드 상수
export const VOICE_MODES = {
- PROMPT: 'prompt', // Try saying 화면
- LISTENING: 'listening', // 듣는 중 화면
- MODE_3: 'mode3', // 추후 추가
- MODE_4: 'mode4', // 추후 추가
+ PROMPT: "prompt", // Try saying 화면
+ LISTENING: "listening", // 듣는 중 화면
+ MODE_3: "mode3", // 추후 추가
+ MODE_4: "mode4", // 추후 추가
};
-const OVERLAY_SPOTLIGHT_ID = 'voice-input-overlay-container';
-const INPUT_SPOTLIGHT_ID = 'voice-overlay-input-box';
-const MIC_SPOTLIGHT_ID = 'voice-overlay-mic-button';
+const OVERLAY_SPOTLIGHT_ID = "voice-input-overlay-container";
+const INPUT_SPOTLIGHT_ID = "voice-overlay-input-box";
+const MIC_SPOTLIGHT_ID = "voice-overlay-mic-button";
const VoiceInputOverlay = ({
isVisible,
onClose,
mode = VOICE_MODES.PROMPT,
suggestions = [],
- searchQuery = '',
+ searchQuery = "",
onSearchChange,
onSearchSubmit,
}) => {
@@ -53,12 +69,17 @@ const VoiceInputOverlay = ({
const [currentMode, setCurrentMode] = useState(mode);
// Redux에서 voice 상태 가져오기
- const { isRegistered, lastSTTText, sttTimestamp } = useSelector((state) => state.voice);
+ const { isRegistered, lastSTTText, sttTimestamp } = useSelector(
+ (state) => state.voice
+ );
// STT 텍스트 수신 시 처리
useEffect(() => {
if (lastSTTText && sttTimestamp && isVisible) {
- console.log('[VoiceInputOverlay] STT text received in overlay:', lastSTTText);
+ console.log(
+ "[VoiceInputOverlay] STT text received in overlay:",
+ lastSTTText
+ );
// 입력창에 텍스트 표시 (부모 컴포넌트로 전달)
if (onSearchChange) {
@@ -101,9 +122,9 @@ const VoiceInputOverlay = ({
// Suggestion 버튼 클릭 핸들러 - Input 창에 텍스트만 설정
const handleSuggestionClick = useCallback(
(suggestion) => {
- console.log('[VoiceInputOverlay] Suggestion clicked:', suggestion);
+ console.log("[VoiceInputOverlay] Suggestion clicked:", suggestion);
// 따옴표 제거
- const query = suggestion.replace(/^["']|["']$/g, '').trim();
+ const query = suggestion.replace(/^["']|["']$/g, "").trim();
// Input 창에 텍스트 설정
if (onSearchChange) {
onSearchChange({ value: query });
@@ -114,7 +135,7 @@ const VoiceInputOverlay = ({
// Input 창에서 API 호출 핸들러 (엔터키 또는 돋보기 아이콘 클릭)
const handleSearchSubmit = useCallback(() => {
- console.log('[VoiceInputOverlay] Search submit:', searchQuery);
+ console.log("[VoiceInputOverlay] Search submit:", searchQuery);
if (searchQuery && searchQuery.trim()) {
// ShopperHouse API 호출
dispatch(getShopperHouseSearch(searchQuery.trim()));
@@ -129,7 +150,7 @@ const VoiceInputOverlay = ({
// Input 창에서 엔터키 핸들러
const handleInputKeyDown = useCallback(
(e) => {
- if (e.key === 'Enter' || e.keyCode === 13) {
+ if (e.key === "Enter" || e.keyCode === 13) {
e.preventDefault();
handleSearchSubmit();
}
@@ -142,19 +163,25 @@ const VoiceInputOverlay = ({
switch (currentMode) {
case VOICE_MODES.PROMPT:
return (
-
+
);
case VOICE_MODES.LISTENING:
return ;
case VOICE_MODES.MODE_3:
// 추후 MODE_3 컴포넌트 추가
- return
Mode 3 (Coming soon)
;
+ return ;
case VOICE_MODES.MODE_4:
// 추후 MODE_4 컴포넌트 추가
- return Mode 4 (Coming soon)
;
+ return ;
default:
return (
-
+
);
}
};
@@ -171,7 +198,10 @@ const VoiceInputOverlay = ({
// 마이크 버튼 클릭 (모드 전환: prompt -> listening -> close)
const handleMicClick = useCallback(
(e) => {
- console.log('[VoiceInputOverlay] handleMicClick called, currentMode:', currentMode);
+ console.log(
+ "[VoiceInputOverlay] handleMicClick called, currentMode:",
+ currentMode
+ );
// 이벤트 전파 방지 - dim 레이어의 onClick 실행 방지
if (e && e.stopPropagation) {
@@ -183,17 +213,17 @@ const VoiceInputOverlay = ({
if (currentMode === VOICE_MODES.PROMPT) {
// prompt 모드에서 클릭 시 -> listening 모드로 전환
- console.log('[VoiceInputOverlay] Switching to LISTENING mode');
+ console.log("[VoiceInputOverlay] Switching to LISTENING mode");
setCurrentMode(VOICE_MODES.LISTENING);
// 이 시점에서 webOS Voice Framework가 자동으로 음성 인식 시작
// (이미 registerVoiceFramework()로 등록되어 있으므로)
} else if (currentMode === VOICE_MODES.LISTENING) {
// listening 모드에서 클릭 시 -> 종료
- console.log('[VoiceInputOverlay] Closing from LISTENING mode');
+ console.log("[VoiceInputOverlay] Closing from LISTENING mode");
onClose();
} else {
// 기타 모드에서는 바로 종료
- console.log('[VoiceInputOverlay] Closing from other mode');
+ console.log("[VoiceInputOverlay] Closing from other mode");
onClose();
}
},
@@ -203,7 +233,7 @@ const VoiceInputOverlay = ({
// dim 레이어 클릭 핸들러 (마이크 버튼과 분리)
const handleDimClick = useCallback(
(e) => {
- console.log('[VoiceInputOverlay] dimBackground clicked');
+ console.log("[VoiceInputOverlay] dimBackground clicked");
onClose();
},
[onClose]
@@ -225,9 +255,17 @@ const VoiceInputOverlay = ({
{/* Voice 등록 상태 표시 (디버깅용) */}
- {process.env.NODE_ENV === 'development' && (
-
- Voice: {isRegistered ? '✓ Ready' : '✗ Not Ready'}
+ {process.env.NODE_ENV === "development" && (
+
+ Voice: {isRegistered ? "✓ Ready" : "✗ Not Ready"}
)}
@@ -238,7 +276,10 @@ const VoiceInputOverlay = ({
spotlightDisabled={!isVisible}
>
{/* 입력창과 마이크 버튼 - SearchPanel.inputContainer와 동일한 구조 */}
-
e.stopPropagation()}>
+
e.stopPropagation()}
+ >
{
- if (e.key === 'Enter') {
+ if (e.key === "Enter") {
handleMicClick(e);
}
}}
spotlightId={MIC_SPOTLIGHT_ID}
>
-

+
{currentMode === VOICE_MODES.LISTENING && (