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 (
-