diff --git a/src/app/page.tsx b/src/app/page.tsx
index 648e234..3a4f32f 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}
/>
-
+
diff --git a/src/components/Game/AudioPlayer/Index.tsx b/src/components/Game/AudioPlayer/Index.tsx
new file mode 100644
index 0000000..6a4d857
--- /dev/null
+++ b/src/components/Game/AudioPlayer/Index.tsx
@@ -0,0 +1,45 @@
+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 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, onClick }: ButtonProps) => {
+ 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..9a0109a 100644
--- a/src/components/Game/index.tsx
+++ b/src/components/Game/index.tsx
@@ -1,9 +1,10 @@
import { GameInput } from "./Input";
import { GameFeedback } from "./GameFeedback";
-import { useEffect, useState } from "react";
+import { AudioPlayer, AudioStateEnum } from "./AudioPlayer/Index";
+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 +21,28 @@ 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 [audio, setAudio] = useState<{
+ audioSynthesis: SpeechSynthesisUtterance;
+ audioState: AudioStateEnum;
+ }>({
+ audioSynthesis: null,
+ audioState: "PENDING"
+ }); // Novo objeto de audio
const totalSteps = 5;
- const { fetchRandomPhrase, verifyAnswer, compareCorrectAnswer, compareUserAnswer } =
- useAudioServer();
+ const {
+ fetchRandomPhrase,
+ verifyAnswer,
+ compareCorrectAnswer,
+ compareUserAnswer
+ } = useAudioServer();
+
+ const { getAudio } = useAudioBrowser();
const { data, error, isLoading, refetch } = useQuery({
queryKey: ["randomPhrase", currentStep],
queryFn: fetchRandomPhrase,
- enabled: currentStep <= totalSteps,
+ enabled: currentStep <= totalSteps
});
useEffect(() => {
@@ -43,19 +57,36 @@ const Game = () => {
}
}, [currentStep, refetch]);
+ useEffect(() => {
+ if (!data) return;
+
+ async function fetchAudio() {
+ const audio = await getAudio(data.phrase);
+ setAudio(old => ({
+ audioState: "READY",
+ audioSynthesis: audio
+ }));
+ }
+
+ fetchAudio();
+ }, [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 +95,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 +109,18 @@ const Game = () => {
setCanContinue(false); // Resetar o estado de continuação
}
+ async function handlePlayAudio() {
+ audio.audioSynthesis.onstart = () => {
+ setAudio(old => ({ ...old, audioState: "PLAYING" }));
+ };
+
+ audio.audioSynthesis.onend = () => {
+ setAudio(old => ({ ...old, audioState: "READY" }));
+ };
+
+ window.speechSynthesis.speak(audio.audioSynthesis);
+ }
+
return (
{isLessonComplete ? (
@@ -95,15 +138,14 @@ const Game = () => {
/>
- {data?.audioBase64 && !userFeedback && (
-
-
- Your browser does not support the audio element.
-
+ {!userFeedback && (
+
handlePlayAudio()}
+ />
)}
{userFeedback ? (
@@ -111,22 +153,29 @@ const Game = () => {
) : (
{
+ 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 +185,4 @@ const Game = () => {
);
};
-export default Game;
\ No newline at end of file
+export default Game;
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
new file mode 100644
index 0000000..b299517
--- /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) {
+ throw Error("Unable to load audio synthesizer");
+ }
+ };
+
+ 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}`);
}