diff --git a/app/src/components/game/GameFooter.jsx b/app/src/components/game/GameFooter.jsx index 1d045b3..3bba81f 100644 --- a/app/src/components/game/GameFooter.jsx +++ b/app/src/components/game/GameFooter.jsx @@ -7,6 +7,8 @@ import React from "react"; import PropTypes from "prop-types"; +import { getCategoryIcon } from "./editors/toolboxIcons" +import { useVolume } from "../../hooks/useVolume"; export default function GameFooter({ gameConfig, @@ -17,6 +19,8 @@ export default function GameFooter({ const totalPhases = gameConfig.fases.length; const displayPhase = currentPhase ?? currentPhase; + const { isMuted, toggleVolume } = useVolume(); + const ajuda = () => { if (onHelpClick) { onHelpClick(); @@ -25,11 +29,12 @@ export default function GameFooter({ alert("Recurso de ajuda será implementado em breve!"); }; + return (
{/* Lado esquerdo - Botão de Ajuda (desativado temporariamente, tour será reimplementado) */} -
+ {/*
+
*/} +
+
+ {/* Centro - Indicador de Fase Atual/Total */}
diff --git a/app/src/components/game/editors/toolboxIcons.js b/app/src/components/game/editors/toolboxIcons.js index 0180236..d5aaf5d 100644 --- a/app/src/components/game/editors/toolboxIcons.js +++ b/app/src/components/game/editors/toolboxIcons.js @@ -42,6 +42,10 @@ export function getCategoryIcon(name) { return "fa fa-database"; case "Caneta": return "fa fa-pencil-alt"; + case "Volume Desligado": + return "fa-solid fa-volume-xmark"; + case "Volume Ligado": + return "fa-solid fa-volume-high"; default: return "fa fa-cube"; } diff --git a/app/src/hooks/useVolume.js b/app/src/hooks/useVolume.js new file mode 100644 index 0000000..53b5b0a --- /dev/null +++ b/app/src/hooks/useVolume.js @@ -0,0 +1,23 @@ +// hooks/useVolume.js +import { useCallback, useEffect, useState } from "react"; +import { gameEventBus } from "../utils/gameEvents"; + +const STORAGE_KEY = "decoda-volume-muted"; + +export function useVolume() { + const [isMuted, setIsMuted] = useState(() => localStorage.getItem(STORAGE_KEY) === "true"); + + useEffect(() => { + gameEventBus.setVolumeMuted(isMuted); + }, [isMuted]); + + const toggleVolume = useCallback(() => { + setIsMuted((prev) => { + const next = !prev; + localStorage.setItem(STORAGE_KEY, String(next)); + return next; + }); + }, []); + + return { isMuted, toggleVolume }; +} \ No newline at end of file diff --git a/app/src/shared/gameController.js b/app/src/shared/gameController.js index 8d3b93e..acdd4a1 100644 --- a/app/src/shared/gameController.js +++ b/app/src/shared/gameController.js @@ -49,14 +49,26 @@ export function setupGameController( } }; + const setVolumeMutedHandler = (event) => { + if (scene.game?.sound) { + scene.game.sound.mute = event.detail.muted; + } + }; + gameEventBus.addEventListener("executeCode", executeCodeHandler); gameEventBus.addEventListener("resetGame", resetGameHandler); gameEventBus.addEventListener("stopExecution", stopExecutionHandler); + gameEventBus.addEventListener("setVolumeMuted", setVolumeMutedHandler); + + if (scene.game?.sound) { + scene.game.sound.mute = localStorage.getItem("decoda-volume-muted") === "true"; + } const cleanup = () => { gameEventBus.removeEventListener("executeCode", executeCodeHandler); gameEventBus.removeEventListener("resetGame", resetGameHandler); gameEventBus.removeEventListener("stopExecution", stopExecutionHandler); + gameEventBus.removeEventListener("setVolumeMuted", setVolumeMutedHandler); }; scene._cleanupController = cleanup; diff --git a/app/src/utils/__tests__/gameEvents.test.js b/app/src/utils/__tests__/gameEvents.test.js index b8cab60..e59ce78 100644 --- a/app/src/utils/__tests__/gameEvents.test.js +++ b/app/src/utils/__tests__/gameEvents.test.js @@ -86,6 +86,15 @@ describe("gameEventBus", () => { }); }); + describe("setVolumeMuted", () => { + it("dispatches setVolumeMuted with muted flag in detail", async () => { + const p = listenOnce("setVolumeMuted"); + gameEventBus.setVolumeMuted(true); + const e = await p; + expect(e.detail).toEqual({ muted: true }); + }); + }); + it("is an EventTarget", () => { expect(gameEventBus).toBeInstanceOf(EventTarget); }); diff --git a/app/src/utils/gameEvents.js b/app/src/utils/gameEvents.js index 18972e0..d43148b 100644 --- a/app/src/utils/gameEvents.js +++ b/app/src/utils/gameEvents.js @@ -103,6 +103,18 @@ class GameEventBus extends EventTarget { stopExecution() { this.dispatchEvent(new CustomEvent("stopExecution")); } + + /** + * React define mute global do áudio no Phaser. + * + * @function setVolumeMuted + * @param {boolean} muted - `true` para silenciar, `false` para restaurar + * @returns {void} + * @fires setVolumeMuted - CustomEvent com detail: { muted } + */ + setVolumeMuted(muted) { + this.dispatchEvent(new CustomEvent("setVolumeMuted", { detail: { muted } })); + } } /**