From e5b4b3476df82786c9e5fa79114e2b0376aeb0eb Mon Sep 17 00:00:00 2001 From: AguaPotavel Date: Fri, 26 Jul 2024 00:30:05 -0300 Subject: [PATCH 1/3] feat: replaced google api with browser api --- src/app/page.tsx | 7 ++- src/components/Game/AudioPlayer/Index.tsx | 9 ++++ src/components/Game/GameFeedback.tsx | 20 +++++--- src/components/Game/index.tsx | 57 +++++++++++++++++------ src/hooks/useAudioBrowser.tsx | 15 ++++++ src/hooks/useAudioServer.tsx | 3 +- 6 files changed, 84 insertions(+), 27 deletions(-) create mode 100644 src/components/Game/AudioPlayer/Index.tsx create mode 100644 src/hooks/useAudioBrowser.tsx diff --git a/src/app/page.tsx b/src/app/page.tsx index 968045e..e882193 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -40,17 +40,16 @@ export default function HomePage() { Improve your listening with just a few minutes per day!

- + : } className={`bg-button-main text-white py-3 opacity-50 cursor-not-allowed ${ isLessonComplete ? "opacity-50 cursor-not-allowed" : "" - }`} - disabled={isLessonComplete ? true : false} + disabled={false} /> - +
diff --git a/src/components/Game/AudioPlayer/Index.tsx b/src/components/Game/AudioPlayer/Index.tsx new file mode 100644 index 0000000..cb5c255 --- /dev/null +++ b/src/components/Game/AudioPlayer/Index.tsx @@ -0,0 +1,9 @@ +interface AudioStateProps { + state: "ready" | "pending" | "playing"; +} + +const PlayerReady = () => {}; + +export const AudioPlayer = ({ state }: AudioStateProps) => { + return; +}; diff --git a/src/components/Game/GameFeedback.tsx b/src/components/Game/GameFeedback.tsx index eeee668..323c97f 100644 --- a/src/components/Game/GameFeedback.tsx +++ b/src/components/Game/GameFeedback.tsx @@ -5,13 +5,11 @@ import { WrongIcon } from "../Icons/wrong"; interface GameFeedbackProps { feedback: React.ReactNode; feedbackType?: "correct" | "incorrect"; - audioSrc?: string; } export const GameFeedback = ({ feedback, - feedbackType = "correct", - audioSrc + feedbackType = "correct" }: GameFeedbackProps) => { return (
@@ -20,12 +18,21 @@ export const GameFeedback = ({ {feedbackType === "correct" ? : } - {feedbackType === "correct" ? "Your phrase is correct!" : "Correct Solution:"}
+ {feedbackType === "correct" + ? "Your phrase is correct!" + : "Correct Solution:"}{" "} +
+
+ + {feedbackType === "incorrect" && feedback} - {feedbackType === "incorrect" && feedback}
) : (
@@ -39,7 +46,6 @@ export const GameFeedback = ({
)} - ); }; diff --git a/src/components/Game/index.tsx b/src/components/Game/index.tsx index 502fc1b..5c5ef8e 100644 --- a/src/components/Game/index.tsx +++ b/src/components/Game/index.tsx @@ -1,9 +1,9 @@ import { GameInput } from "./Input"; import { GameFeedback } from "./GameFeedback"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import FooterButton from "@/components/Footer/FooterButton"; -import { AudioServer } from "@/service/AudioServer"; import { useAudioServer } from "@/hooks/useAudioServer"; +import { useAudioBrowser } from "@/hooks/useAudioBrowser"; import { useQuery } from "@tanstack/react-query"; import ProgressBar from "@/components/ProgressBar"; import Cookies from "js-cookie"; @@ -20,15 +20,23 @@ const Game = () => { const [isLessonComplete, setIsLessonComplete] = useState(false); const [correctCount, setCorrectCount] = useState(0); const [canContinue, setCanContinue] = useState(false); // Novo estado para controle de continuação + const [audioS, setAudios] = useState(null); // Novo estado para controle de continuação + const audio = useRef(); const totalSteps = 5; - const { fetchRandomPhrase, verifyAnswer, compareCorrectAnswer, compareUserAnswer } = - useAudioServer(); + const { + fetchRandomPhrase, + verifyAnswer, + compareCorrectAnswer, + compareUserAnswer + } = useAudioServer(); + + const { getAudio, audioBase64 } = useAudioBrowser(); const { data, error, isLoading, refetch } = useQuery({ queryKey: ["randomPhrase", currentStep], queryFn: fetchRandomPhrase, - enabled: currentStep <= totalSteps, + enabled: currentStep <= totalSteps }); useEffect(() => { @@ -43,19 +51,28 @@ const Game = () => { } }, [currentStep, refetch]); + useEffect(() => { + if (!data) return; + + setAudios(getAudio(data.phrase)); + }, [data]); + async function handleButtonPress() { const isCorrect = verifyAnswer(userAnswer, data?.phrase); setIsCorrect(isCorrect); if (isCorrect) { - setCorrectCount((prevCount) => { + setCorrectCount(prevCount => { const newCount = prevCount + 1; Cookies.set("correctCount", newCount.toString(), { expires: 1 }); return newCount; }); } - const correctFeedbackComponent = compareCorrectAnswer(userAnswer, data?.phrase); + const correctFeedbackComponent = compareCorrectAnswer( + userAnswer, + data?.phrase + ); const userFeedbackComponent = compareUserAnswer(userAnswer, data?.phrase); setCorrectFeedback(correctFeedbackComponent); setUserFeedback(userFeedbackComponent); @@ -64,7 +81,7 @@ const Game = () => { function handleContinue() { if (currentStep < totalSteps) { - setCurrentStep((prevStep) => prevStep + 1); + setCurrentStep(prevStep => prevStep + 1); } else { setIsLessonComplete(true); Cookies.set("lessonComplete", "true", { expires: 1 }); @@ -78,6 +95,11 @@ const Game = () => { setCanContinue(false); // Resetar o estado de continuação } + async function handlePlayAudio() { + const a = await audioS; + window.speechSynthesis.speak(a); + } + return (
{isLessonComplete ? ( @@ -95,7 +117,6 @@ const Game = () => { />
@@ -105,28 +126,36 @@ const Game = () => { Your browser does not support the audio element. )} + handlePlayAudio()}>audio {userFeedback ? (
{userFeedback}
) : ( { + onKeyDown={event => { if (event.key === "Enter") { handleButtonPress(); } }} - onChange={(e) => setUserAnswer(e.target.value)} + onChange={e => setUserAnswer(e.target.value)} value={userAnswer} /> )}
{canContinue ? ( - + Continue ) : ( - + Submit )} @@ -136,4 +165,4 @@ const Game = () => { ); }; -export default Game; \ No newline at end of file +export default Game; diff --git a/src/hooks/useAudioBrowser.tsx b/src/hooks/useAudioBrowser.tsx new file mode 100644 index 0000000..663a0b5 --- /dev/null +++ b/src/hooks/useAudioBrowser.tsx @@ -0,0 +1,15 @@ +export const useAudioBrowser = () => { + const getAudio = async (phrase: string) => { + try { + const synthesis = new SpeechSynthesisUtterance(phrase); + synthesis.lang = "en-US"; + return synthesis; + } catch (e) { + console.log(e); + } + }; + + return { + getAudio + }; +}; diff --git a/src/hooks/useAudioServer.tsx b/src/hooks/useAudioServer.tsx index 16893a6..4e77fd5 100644 --- a/src/hooks/useAudioServer.tsx +++ b/src/hooks/useAudioServer.tsx @@ -21,8 +21,7 @@ export const useAudioServer = () => { } try { - const audioBase64 = await textToSpeechAction(phrase); - return { phrase, audioBase64 }; + return { phrase }; } catch (error) { throw new Error(`Failed to load audio: ${error.message}`); } From 9ca41e130f39e87ba9f51c632fb16cb651fe768f Mon Sep 17 00:00:00 2001 From: AguaPotavel Date: Fri, 26 Jul 2024 21:09:09 -0300 Subject: [PATCH 2/3] feat: updated audio component --- src/components/Game/AudioPlayer/Index.tsx | 46 ++++++++++++++++++++--- src/components/Game/index.tsx | 44 ++++++++++++++++------ src/components/Icons/audio.tsx | 11 +++++- src/hooks/useAudioBrowser.tsx | 2 +- 4 files changed, 83 insertions(+), 20 deletions(-) diff --git a/src/components/Game/AudioPlayer/Index.tsx b/src/components/Game/AudioPlayer/Index.tsx index cb5c255..6a4d857 100644 --- a/src/components/Game/AudioPlayer/Index.tsx +++ b/src/components/Game/AudioPlayer/Index.tsx @@ -1,9 +1,45 @@ -interface AudioStateProps { - state: "ready" | "pending" | "playing"; +import { PlayIcon } from "@/components/Icons/playIcon"; +import { TimeIcon } from "@/components/Icons/time"; +import { AudioIcon } from "@/components/Icons/audio"; + +export type AudioStateEnum = "PENDING" | "READY" | "PLAYING"; + +interface ButtonProps extends React.ButtonHTMLAttributes { + state: AudioStateEnum; } -const PlayerReady = () => {}; +const Ready = () => { + return ; +}; + +const Pending = () => { + return ; +}; + +const Playing = () => { + return ; +}; + +const StatesMapper = ({ state }) => { + switch (state) { + case "PENDING": + return ; + case "READY": + return ; + case "PLAYING": + return ; + default: + return ; + } +}; -export const AudioPlayer = ({ state }: AudioStateProps) => { - return; +export const AudioPlayer = ({ state, onClick }: ButtonProps) => { + return ( + + ); }; diff --git a/src/components/Game/index.tsx b/src/components/Game/index.tsx index 5c5ef8e..9a0109a 100644 --- a/src/components/Game/index.tsx +++ b/src/components/Game/index.tsx @@ -1,5 +1,6 @@ import { GameInput } from "./Input"; import { GameFeedback } from "./GameFeedback"; +import { AudioPlayer, AudioStateEnum } from "./AudioPlayer/Index"; import { useEffect, useState, useRef } from "react"; import FooterButton from "@/components/Footer/FooterButton"; import { useAudioServer } from "@/hooks/useAudioServer"; @@ -20,8 +21,13 @@ const Game = () => { const [isLessonComplete, setIsLessonComplete] = useState(false); const [correctCount, setCorrectCount] = useState(0); const [canContinue, setCanContinue] = useState(false); // Novo estado para controle de continuação - const [audioS, setAudios] = useState(null); // Novo estado para controle de continuação - const audio = useRef(); + const [audio, setAudio] = useState<{ + audioSynthesis: SpeechSynthesisUtterance; + audioState: AudioStateEnum; + }>({ + audioSynthesis: null, + audioState: "PENDING" + }); // Novo objeto de audio const totalSteps = 5; const { @@ -31,7 +37,7 @@ const Game = () => { compareUserAnswer } = useAudioServer(); - const { getAudio, audioBase64 } = useAudioBrowser(); + const { getAudio } = useAudioBrowser(); const { data, error, isLoading, refetch } = useQuery({ queryKey: ["randomPhrase", currentStep], @@ -54,7 +60,15 @@ const Game = () => { useEffect(() => { if (!data) return; - setAudios(getAudio(data.phrase)); + async function fetchAudio() { + const audio = await getAudio(data.phrase); + setAudio(old => ({ + audioState: "READY", + audioSynthesis: audio + })); + } + + fetchAudio(); }, [data]); async function handleButtonPress() { @@ -96,8 +110,15 @@ const Game = () => { } async function handlePlayAudio() { - const a = await audioS; - window.speechSynthesis.speak(a); + audio.audioSynthesis.onstart = () => { + setAudio(old => ({ ...old, audioState: "PLAYING" })); + }; + + audio.audioSynthesis.onend = () => { + setAudio(old => ({ ...old, audioState: "READY" })); + }; + + window.speechSynthesis.speak(audio.audioSynthesis); } return ( @@ -120,13 +141,12 @@ const Game = () => { feedbackType={isCorrect ? "correct" : "incorrect"} feedback={correctFeedback} /> - {data?.audioBase64 && !userFeedback && ( - + {!userFeedback && ( + handlePlayAudio()} + /> )} - handlePlayAudio()}>audio {userFeedback ? (
{userFeedback}
diff --git a/src/components/Icons/audio.tsx b/src/components/Icons/audio.tsx index a1984f0..697547e 100644 --- a/src/components/Icons/audio.tsx +++ b/src/components/Icons/audio.tsx @@ -2,15 +2,22 @@ import * as React from "react"; interface Props { width?: number; height?: number; + color?: string; } export function AudioIcon(props: Props) { return ( - + ); diff --git a/src/hooks/useAudioBrowser.tsx b/src/hooks/useAudioBrowser.tsx index 663a0b5..b299517 100644 --- a/src/hooks/useAudioBrowser.tsx +++ b/src/hooks/useAudioBrowser.tsx @@ -5,7 +5,7 @@ export const useAudioBrowser = () => { synthesis.lang = "en-US"; return synthesis; } catch (e) { - console.log(e); + throw Error("Unable to load audio synthesizer"); } }; From ffabff780e53b2335e9b9209ae07c1b94420b2d4 Mon Sep 17 00:00:00 2001 From: AguaPotavel Date: Fri, 26 Jul 2024 21:11:22 -0300 Subject: [PATCH 3/3] fix: fix primarybutton --- src/app/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index e882193..49e91ab 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -47,7 +47,7 @@ export default function HomePage() { className={`bg-button-main text-white py-3 opacity-50 cursor-not-allowed ${ isLessonComplete ? "opacity-50 cursor-not-allowed" : "" }`} - disabled={false} + disabled={isLessonComplete ? true : false} />