Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance: Mobile Compatibility for Timer Activation #142

Merged
merged 3 commits into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions src/components/timer/ScrambleZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function ScrambleZone() {
return (
<>
<div
id="touch"
className={
settings.features.scrambleBackground.status
? "h-auto p-2 overflow-auto text-2xl font-medium text-center rounded-md min-w-auto sm:max-w-screen-sm md:max-w-screen-md lg:max-w-screen-lg max-h-52 dark:bg-zinc-900 light:bg-neutral-100"
Expand Down
162 changes: 9 additions & 153 deletions src/components/timer/Timer.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { useEffect, useRef, useState } from "react";
import { Solve } from "@/interfaces/Solve";
import { TimerStatus } from "@/interfaces/TimerStatus";
import genId from "@/lib/genId";
import { useTimerStore } from "@/store/timerStore";
import addSolve from "@/lib/addSolve";
import findCube from "@/lib/findCube";
import SolveOptions from "./SolveOptions";
import { useSettingsModalStore } from "@/store/SettingsModalStore";
import formatTime from "@/lib/formatTime";
import translation from "@/translations/global.json";
import useTimer from "@/hooks/useTimer";

const timerStatusClasses = {
idle: "light:text-neutral-900 dark:text-white",
Expand All @@ -18,157 +13,18 @@ const timerStatusClasses = {
};

export default function Timer() {
const {
selectedCube,
scramble,
setNewScramble,
setCubes,
setSelectedCube,
lastSolve,
setLastSolve,
solvingTime,
setSolvingTime,
isSolving,
setIsSolving,
} = useTimerStore();
const { lang, settings } = useSettingsModalStore();
const { selectedCube, isSolving, lastSolve } = useTimerStore();
const { timerStatus, hideWhileSolving, solvingTime } = useTimer();

const { settings, lang } = useSettingsModalStore();

const holdTimeRequired = settings.timer.holdToStart.status ? 500 : 0;
const [timerStatus, setTimerStatus] = useState<TimerStatus>("idle");
const endTimeRef = useRef<number>(0);
const holdingTimeRef = useRef<number>(0);
const startTime = useRef<number>(0);
const runningTimeId = useRef<any>(null);
const isHolding = useRef(false);
const isReleased = useRef(true);
const hideWhileSolving = settings.features.hideWhileSolving.status;

const handleHold = (event: KeyboardEvent) => {
if ((selectedCube && event.code === "Space") || event.code === "Escape") {
if (event.code === "Escape") {
clearInterval(runningTimeId.current);
setIsSolving(false);
isReleased.current = false;
startTime.current = 0;
holdingTimeRef.current = 0;
setLastSolve(null);
setTimerStatus("idle");
setSolvingTime(0);
return;
}

if (isSolving) {
clearInterval(runningTimeId.current);
setIsSolving(false);
isReleased.current = false;

if (selectedCube && scramble) {
const lastSolve: Solve = {
id: genId(),
startTime: startTime.current,
endTime: endTimeRef.current,
scramble: scramble,
bookmark: false,
time: solvingTime,
dnf: false,
plus2: false,
rating: Math.floor(Math.random() * 20) + scramble.length,
category: selectedCube.category,
cubeId: selectedCube.id,
};

setLastSolve(lastSolve);

if (selectedCube) {
const newCubes = addSolve({
cubeId: selectedCube.id,
solve: lastSolve,
});

setCubes(newCubes);

const currentCube = findCube({ cubeId: selectedCube.id });

if (currentCube) setSelectedCube(currentCube);
}

setNewScramble(selectedCube);
}

startTime.current = 0;
holdingTimeRef.current = 0;
setTimerStatus("idle");
return;
}

const now = Date.now();
const difference = now - holdingTimeRef.current;

if (!isReleased.current) return;

if (!isHolding.current) {
holdingTimeRef.current = now;
isHolding.current = true;

if (settings.timer.holdToStart.status) {
setTimerStatus("holdingKey");
} else {
setTimerStatus("ready");
}
} else {
if (difference >= holdTimeRequired) {
setTimerStatus("ready");
}
}
}
};

const handleRelease = (event: KeyboardEvent) => {
if (event.code === "Space" || event.code === "Escape") {
isReleased.current = true;
if (event.code === "Escape") return;

const now = Date.now();
const difference: number = now - holdingTimeRef.current;

if (isHolding.current && !isSolving) {
if (difference >= holdTimeRequired) {
setIsSolving(true);
isHolding.current = false;
holdingTimeRef.current = 0;
startTime.current = Date.now();
runningTimeId.current = setInterval(() => {
endTimeRef.current = Date.now();
setSolvingTime(endTimeRef.current - (startTime.current || 0));
});
setTimerStatus("solving");
return;
}

if (difference <= holdTimeRequired) {
setIsSolving(false);
isHolding.current = false;
holdingTimeRef.current = 0;
setTimerStatus("idle");
return;
}
}
}
};

useEffect(() => {
window.addEventListener("keydown", handleHold);
window.addEventListener("keyup", handleRelease);
return () => {
window.removeEventListener("keydown", handleHold);
window.removeEventListener("keyup", handleRelease);
};
});
if (selectedCube === null) return;

return (
<>
<div className="flex flex-col items-center justify-center grow">
<div
id="touch"
className="flex flex-col items-center justify-center grow"
>
{selectedCube && (
<div
className={`text-6xl sm:text-7xl md:text-8xl lg:text-9xl font-mono select-none ${timerStatusClasses[timerStatus]}`}
Expand Down
5 changes: 4 additions & 1 deletion src/components/timer/TimerWidgets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ export default function TimerWidgets() {
const { isSolving } = useTimerStore();
if (isSolving) return null;
return (
<div className="flex items-center justify-between w-full h-20 text-xs sm:h-20 md:h-24 lg:h-32 md:text-sm">
<div
id="touch"
className="flex items-center justify-between w-full h-20 text-xs sm:h-20 md:h-24 lg:h-32 md:text-sm"
>
<OverviewPanel />
<ScramblePanel />
<StatisticsPanel />
Expand Down
Empty file removed src/hooks/useClick.ts
Empty file.
190 changes: 190 additions & 0 deletions src/hooks/useTimer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { Solve } from "@/interfaces/Solve";
import { TimerStatus } from "@/interfaces/TimerStatus";
import addSolve from "@/lib/addSolve";
import findCube from "@/lib/findCube";
import genId from "@/lib/genId";
import { useSettingsModalStore } from "@/store/SettingsModalStore";
import { useTimerStore } from "@/store/timerStore";
import { useEffect, useRef, useState } from "react";

export default function useTimer() {
const {
selectedCube,
scramble,
setNewScramble,
setCubes,
setSelectedCube,
setLastSolve,
solvingTime,
setSolvingTime,
isSolving,
setIsSolving,
} = useTimerStore();

const { settings } = useSettingsModalStore();

const holdTimeRequired = settings.timer.holdToStart.status ? 500 : 0;
const [timerStatus, setTimerStatus] = useState<TimerStatus>("idle");
const endTimeRef = useRef<number>(0);
const holdingTimeRef = useRef<number>(0);
const startTime = useRef<number>(0);
const runningTimeId = useRef<any>(null);
const isHolding = useRef(false);
const isReleased = useRef(true);
const hideWhileSolving = settings.features.hideWhileSolving.status;

function holding() {
if (isSolving) {
clearInterval(runningTimeId.current);
setIsSolving(false);
isReleased.current = false;

if (selectedCube && scramble) {
const lastSolve: Solve = {
id: genId(),
startTime: startTime.current,
endTime: endTimeRef.current,
scramble: scramble,
bookmark: false,
time: solvingTime,
dnf: false,
plus2: false,
rating: Math.floor(Math.random() * 20) + scramble.length,
category: selectedCube.category,
cubeId: selectedCube.id,
};

setLastSolve(lastSolve);

if (selectedCube) {
const newCubes = addSolve({
cubeId: selectedCube.id,
solve: lastSolve,
});

setCubes(newCubes);

const currentCube = findCube({ cubeId: selectedCube.id });

if (currentCube) setSelectedCube(currentCube);
}

setNewScramble(selectedCube);
}

startTime.current = 0;
holdingTimeRef.current = 0;
setTimerStatus("idle");
return;
}

const now = Date.now();
const difference = now - holdingTimeRef.current;

if (!isReleased.current) return;

if (!isHolding.current) {
holdingTimeRef.current = now;
isHolding.current = true;

if (settings.timer.holdToStart.status) {
setTimerStatus("holdingKey");
} else {
setTimerStatus("ready");
}
} else {
if (difference >= holdTimeRequired) {
setTimerStatus("ready");
}
}
}

function releasing() {
const now = Date.now();
const difference: number = now - holdingTimeRef.current;

if (isHolding.current && !isSolving) {
if (difference >= holdTimeRequired) {
setIsSolving(true);
isHolding.current = false;
holdingTimeRef.current = 0;
startTime.current = Date.now();
runningTimeId.current = setInterval(() => {
endTimeRef.current = Date.now();
setSolvingTime(endTimeRef.current - (startTime.current || 0));
});
setTimerStatus("solving");
return;
}

if (difference <= holdTimeRequired) {
setIsSolving(false);
isHolding.current = false;
holdingTimeRef.current = 0;
setTimerStatus("idle");
return;
}
}
}

const handleHold = (event: KeyboardEvent) => {
if ((selectedCube && event.code === "Space") || event.code === "Escape") {
if (event.code === "Escape") {
clearInterval(runningTimeId.current);
setIsSolving(false);
isReleased.current = false;
startTime.current = 0;
holdingTimeRef.current = 0;
setLastSolve(null);
setTimerStatus("idle");
setSolvingTime(0);
return;
}
holding();
}
};

const handleRelease = (event: KeyboardEvent) => {
if (event.code === "Space" || event.code === "Escape") {
isReleased.current = true;
if (event.code === "Escape") return;
releasing();
}
};

const handleTouchStart = (event: any) => {
event.preventDefault();
holding();
};

const handleTouchEnd = (event: any) => {
event.preventDefault();
isReleased.current = true;
releasing();
};

useEffect(() => {
window.addEventListener("keydown", handleHold);
window.addEventListener("keyup", handleRelease);
const touchElements = document.querySelectorAll("#touch");

touchElements.forEach((element) => {
element.addEventListener("touchstart", handleTouchStart);
element.addEventListener("touchend", handleTouchEnd);
});
return () => {
window.removeEventListener("keydown", handleHold);
window.removeEventListener("keyup", handleRelease);
touchElements.forEach((element) => {
element.removeEventListener("touchstart", handleTouchStart);
element.removeEventListener("touchend", handleTouchEnd);
});
};
});

return {
timerStatus,
hideWhileSolving,
solvingTime,
};
}
Loading