Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions client/src/hooks/use-wake-lock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useCallback, useEffect, useRef, useState } from "react";

/**
* A custom hook to manage a screen wake lock.
*
* @returns {Object} An object with a `wakeLockEnabled` boolean and a `toggleWakeLock` function.
*/
Comment thread
Copilot marked this conversation as resolved.
export const useWakeLock = () => {
const wakeLock = useRef<WakeLockSentinel | null>(null);
const [isLocked, setIsLocked] = useState(false);
Comment thread
Copilot marked this conversation as resolved.
Comment thread
Copilot marked this conversation as resolved.

const request = useCallback(async () => {
if (!("wakeLock" in navigator)) {
console.warn("Screen Wake Lock API not supported");
return;
}
try {
wakeLock.current = await navigator.wakeLock.request("screen");
setIsLocked(true);
wakeLock.current.addEventListener("release", () => {
setIsLocked(false);
});
} catch (err) {
console.error(`Wake Lock request failed: ${err}`);
setIsLocked(false);
}
Comment thread
Copilot marked this conversation as resolved.
Comment thread
spring1843 marked this conversation as resolved.
}, []);
Comment thread
Copilot marked this conversation as resolved.

const release = useCallback(async () => {
if (wakeLock.current) {
await wakeLock.current.release();
wakeLock.current = null;
setIsLocked(false);
}
}, []);
Comment thread
Copilot marked this conversation as resolved.

useEffect(() => {
const handleVisibilityChange = () => {
if (wakeLock.current && document.visibilityState === "visible") {
request();
}
};

document.addEventListener("visibilitychange", handleVisibilityChange);
document.addEventListener("fullscreenchange", handleVisibilityChange);

return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
document.removeEventListener("fullscreenchange", handleVisibilityChange);
release();
Comment thread
Copilot marked this conversation as resolved.
Outdated
};
Comment thread
Copilot marked this conversation as resolved.
Comment on lines +103 to +108
}, [request, release]);
Comment thread
Copilot marked this conversation as resolved.
Outdated

return { isLocked, request, release };
};
16 changes: 16 additions & 0 deletions client/src/pages/tools/timer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from "@/lib/time-tools";
import { getParam, updateURL, copyShareableURL } from "@/lib/url-sharing";
import { useToast } from "@/hooks/use-toast";
import { useWakeLock } from "@/hooks/use-wake-lock";
import {
ToolButton,
ResetButton,
Expand Down Expand Up @@ -68,6 +69,21 @@ export default function Timer() {
const audioContextRef = useRef<AudioContext | null>(null);
const alarmIntervalsRef = useRef<Map<string, NodeJS.Timeout>>(new Map());
const { toast } = useToast();
const {
isLocked,
request: requestWakeLock,
release: releaseWakeLock,
} = useWakeLock();

// Effect to manage the wake lock
useEffect(() => {
const hasRunningTimers = timers.some(timer => timer.isRunning);
if (hasRunningTimers && !isLocked) {
requestWakeLock();
} else if (!hasRunningTimers && isLocked) {
Comment thread
spring1843 marked this conversation as resolved.
Outdated
releaseWakeLock();
}
Comment thread
Copilot marked this conversation as resolved.
Outdated
}, [timers, isLocked, requestWakeLock, releaseWakeLock]);
Comment thread
Copilot marked this conversation as resolved.
Comment on lines +78 to +87

// Clear any alarm handle (interval or timeout) robustly
const clearAlarmHandle = useCallback((handle: NodeJS.Timeout) => {
Expand Down
Loading