From 2aeae761f89ec8972dc72ce053fddf70f5fe92bf Mon Sep 17 00:00:00 2001 From: Toba Ojo Date: Tue, 30 Sep 2025 14:51:37 +0100 Subject: [PATCH] added sound context, and functionality to select sighting sound --- src/App.tsx | 35 +++++----- src/assets/sounds/ui/notification.mp3 | Bin 0 -> 13837 bytes .../Sound/SoundSettingsFields.tsx | 10 +-- src/context/SoundContext.ts | 18 +++++ .../providers/SoundContextProvider.tsx | 18 +++++ src/context/reducers/SoundContextReducer.ts | 19 ++++++ src/hooks/useSightingFeed.ts | 20 +++++- src/hooks/useSound.ts | 64 ------------------ src/types/types.ts | 11 +++ 9 files changed, 109 insertions(+), 86 deletions(-) create mode 100644 src/assets/sounds/ui/notification.mp3 create mode 100644 src/context/SoundContext.ts create mode 100644 src/context/providers/SoundContextProvider.tsx create mode 100644 src/context/reducers/SoundContextReducer.ts diff --git a/src/App.tsx b/src/App.tsx index 67ea871..3d9f929 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,25 +8,28 @@ import Session from "./pages/Session"; import { NPEDUserProvider } from "./context/providers/NPEDUserContextProvider"; import { AlertHitProvider } from "./context/providers/AlertHitProvider"; import { SoundProvider } from "react-sounds"; +import SoundContextProvider from "./context/providers/SoundContextProvider"; function App() { return ( - - - - - }> - } /> - } /> - } /> - } /> - } /> - } /> - - - - - + + + + + + }> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + + ); } diff --git a/src/assets/sounds/ui/notification.mp3 b/src/assets/sounds/ui/notification.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..6fa81e893681be5359c7c190fca590b257decbd9 GIT binary patch literal 13837 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 Ug{}sLW(583d;9;E|Mw&CUjk^((*OVf literal 0 HcmV?d00001 diff --git a/src/components/SettingForms/Sound/SoundSettingsFields.tsx b/src/components/SettingForms/Sound/SoundSettingsFields.tsx index 18a84c9..c8a712e 100644 --- a/src/components/SettingForms/Sound/SoundSettingsFields.tsx +++ b/src/components/SettingForms/Sound/SoundSettingsFields.tsx @@ -1,8 +1,10 @@ import { Field, FieldArray, Form, Formik } from "formik"; import FormGroup from "../components/FormGroup"; import type { FormValues, Hotlist } from "../../../types/types"; +import { useSoundContext } from "../../../context/SoundContext"; const SoundSettingsFields = () => { + const { state, dispatch } = useSoundContext(); const hotlists: Hotlist[] = [ { name: "hotlist0", sound: "" }, { name: "hotlist1", sound: "" }, @@ -25,15 +27,15 @@ const SoundSettingsFields = () => { ]; const initialValues: FormValues = { - sightingSound: "switch", - NPEDsound: "popup", + sightingSound: state.sightingSound ?? "switch", + NPEDsound: state.NPEDsound ?? "popup", hotlists, }; const handleSubmit = (values: FormValues) => { - console.log(values); + dispatch({ type: "UPDATE", payload: values }); }; - + console.log(state); return ( {({ values }) => ( diff --git a/src/context/SoundContext.ts b/src/context/SoundContext.ts new file mode 100644 index 0000000..e7938f3 --- /dev/null +++ b/src/context/SoundContext.ts @@ -0,0 +1,18 @@ +import { createContext, useContext, type Dispatch } from "react"; +import type { SoundPayload, SoundState } from "../types/types"; + +type SoundContextType = { + state: SoundState; + dispatch: Dispatch; +}; + +export const SoundContext = createContext( + undefined +); + +export const useSoundContext = () => { + const ctx = useContext(SoundContext); + if (!ctx) + throw new Error("useSoundContext must be used within "); + return ctx; +}; diff --git a/src/context/providers/SoundContextProvider.tsx b/src/context/providers/SoundContextProvider.tsx new file mode 100644 index 0000000..b7e1b6a --- /dev/null +++ b/src/context/providers/SoundContextProvider.tsx @@ -0,0 +1,18 @@ +import { useReducer, type ReactNode } from "react"; +import { SoundContext } from "../SoundContext"; +import { initalState, reducer } from "../reducers/SoundContextReducer"; + +type SoundContextProviderProps = { + children: ReactNode; +}; + +const SoundContextProvider = ({ children }: SoundContextProviderProps) => { + const [state, dispatch] = useReducer(reducer, initalState); + return ( + + {children} + + ); +}; + +export default SoundContextProvider; diff --git a/src/context/reducers/SoundContextReducer.ts b/src/context/reducers/SoundContextReducer.ts new file mode 100644 index 0000000..2b7bf4b --- /dev/null +++ b/src/context/reducers/SoundContextReducer.ts @@ -0,0 +1,19 @@ +import type { SoundPayload, SoundState } from "../../types/types"; + +export const initalState: SoundState = { + sightingSound: "switch", + NPEDsound: "popup", + hotlists: [], +}; + +export function reducer(state: SoundState, action: SoundPayload) { + switch (action.type) { + case "UPDATE": + return { + ...state, + sightingSound: action.payload.sightingSound, + NPEDsound: action.payload.NPEDsound, + }; + } + return state; +} diff --git a/src/hooks/useSightingFeed.ts b/src/hooks/useSightingFeed.ts index e880f07..0722cfa 100644 --- a/src/hooks/useSightingFeed.ts +++ b/src/hooks/useSightingFeed.ts @@ -1,8 +1,11 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import type { SightingType } from "../types/types"; import { useSoundOnChange } from "react-sounds"; import switchSound from "../assets/sounds/ui/switch.mp3"; +import popup from "../assets/sounds/ui/popup_open.mp3"; +import notification from "../assets/sounds/ui/notification.mp3"; +import { useSoundContext } from "../context/SoundContext"; async function fetchSighting( url: string | undefined, @@ -13,7 +16,17 @@ async function fetchSighting( return res.json(); } +function getSoundFileName(name: string) { + const sounds: Record = { + switch: switchSound, + popup: popup, + notification: notification, + }; + return sounds[name] ?? null; +} + export function useSightingFeed(url: string | undefined) { + const { state } = useSoundContext(); const [sightings, setSightings] = useState([]); const [selectedRef, setSelectedRef] = useState(null); const [sessionStarted, setSessionStarted] = useState(false); @@ -23,8 +36,11 @@ export function useSightingFeed(url: string | undefined) { const [selectedSighting, setSelectedSighting] = useState( null ); + const soundSrc = useMemo(() => { + return getSoundFileName(state.sightingSound) ?? switchSound; + }, [state.sightingSound]); - useSoundOnChange(switchSound, latestRef, { + useSoundOnChange(soundSrc, latestRef, { volume: 1, }); diff --git a/src/hooks/useSound.ts b/src/hooks/useSound.ts index 21f3455..e69de29 100644 --- a/src/hooks/useSound.ts +++ b/src/hooks/useSound.ts @@ -1,64 +0,0 @@ -// useBeep.ts -import { useEffect, useRef } from "react"; -import { useSoundEnabled } from "react-sounds"; // so it respects your SoundBtn toggle - -/** - * Plays a sound whenever `latestRef` changes. - * - * @param src Path to the sound file - * @param latestRef The primitive value to watch (e.g. sighting.ref) - * @param opts volume: 0..1, enabledOverride: force enable/disable, minGapMs: throttle interval - */ -export function useBeep( - src: string, - latestRef: number | null, - opts?: { volume?: number; enabledOverride?: boolean; minGapMs?: number } -) { - const audioRef = useRef(undefined); - const prevRef = useRef(null); - const lastPlay = useRef(0); - const [enabled] = useSoundEnabled(); - - const minGap = opts?.minGapMs ?? 250; // don’t play more than 4 times/sec - - // Create the audio element once - useEffect(() => { - const a = new Audio(src); - a.preload = "auto"; - if (opts?.volume !== undefined) a.volume = opts.volume; - audioRef.current = a; - return () => { - a.pause(); - }; - }, [src, opts?.volume]); - - // Watch for ref changes - useEffect(() => { - if (latestRef == null) return; - - const canPlay = - (opts?.enabledOverride ?? enabled) && - document.visibilityState === "visible"; - if (!canPlay) { - prevRef.current = latestRef; // consume the change - return; - } - - if (prevRef.current !== null && latestRef !== prevRef.current) { - const now = Date.now(); - if (now - lastPlay.current >= minGap) { - const a = audioRef.current; - if (a) { - try { - a.currentTime = 0; // restart from beginning - void a.play(); // fire and forget - lastPlay.current = now; - } catch (err) { - console.warn("Audio play failed:", err); - } - } - } - } - prevRef.current = latestRef; - }, [latestRef, enabled, opts?.enabledOverride, minGap]); -} diff --git a/src/types/types.ts b/src/types/types.ts index 2034252..2f0cbf9 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -278,3 +278,14 @@ export type FormValues = { export type SoundUploadValue = { soundFile: File | null; }; + +export type SoundState = { + sightingSound: SoundValue; + NPEDsound: SoundValue; + hotlists: Hotlist[]; +}; + +export type SoundPayload = { + type: string; + payload: SoundState; +};