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 0000000..6fa81e8
Binary files /dev/null and b/src/assets/sounds/ui/notification.mp3 differ
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;
+};