managarten/apps-archived/memoro/apps/mobile/hooks/useTimer.ts
Till-JS 61d181fbc2 chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace:
- bauntown (community website)
- maerchenzauber (AI story generation)
- memoro (voice memo app)
- news (news aggregation)
- nutriphi (nutrition tracking)
- reader (reading app)
- uload (URL shortener)
- wisekeep (AI wisdom extraction)

Update CLAUDE.md documentation:
- Add presi to active projects
- Document archived projects section
- Update workspace configuration

Archived apps can be re-activated by moving back to apps/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 07:03:59 +01:00

201 lines
5 KiB
TypeScript

import { useState, useEffect, useCallback, useRef } from 'react';
import { formatDuration, formatDurationFromMs } from '../utils/formatters';
/**
* Configuration options for the timer
*/
export interface TimerOptions {
/** Whether to track time in milliseconds (true) or seconds (false) */
useMilliseconds?: boolean;
/** Update interval in milliseconds */
updateInterval?: number;
/** Whether to use external time updates instead of internal timer */
useExternalTimeUpdates?: boolean;
}
/**
* A hook for managing timer functionality with support for audio recording and playback
* @param initialState Initial time value (in seconds or milliseconds based on options)
* @param options Configuration options
*/
const useTimer = (initialState = 0, options?: TimerOptions) => {
// Default options
const {
useMilliseconds = false,
updateInterval = 100,
useExternalTimeUpdates = false,
} = options || {};
// State for the current timer value
const [timer, setTimer] = useState(initialState);
// State for formatted time display (MM:SS)
const [formattedTime, setFormattedTime] = useState(formatDuration(initialState));
// Timer activity states
const [isActive, setIsActive] = useState(false);
const [isPaused, setIsPaused] = useState(false);
// References for tracking time
const startTimeRef = useRef<number | null>(null);
const elapsedTimeRef = useRef<number>(0);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
/**
* Clear the timer interval
*/
const clearTimerInterval = useCallback(() => {
if (intervalRef.current) {
global.clearInterval(intervalRef.current);
intervalRef.current = null;
}
}, []);
/**
* Update the formatted time display
*/
const updateFormattedTime = useCallback(
(timeValue: number) => {
if (useMilliseconds) {
setFormattedTime(formatDurationFromMs(timeValue));
} else {
setFormattedTime(formatDuration(timeValue));
}
},
[useMilliseconds]
);
/**
* Timer effect that handles the interval and time calculations
*/
useEffect(() => {
// Skip internal timer if using external time updates
if (useExternalTimeUpdates) return undefined;
if (isActive && !isPaused) {
startTimeRef.current = Date.now();
intervalRef.current = global.setInterval(() => {
const currentTime = Date.now();
const startTime = startTimeRef.current || currentTime;
const elapsed = currentTime - startTime;
// Calculate current elapsed time based on precision setting
let currentElapsed;
if (useMilliseconds) {
currentElapsed = elapsedTimeRef.current + elapsed;
setTimer(currentElapsed);
} else {
currentElapsed = elapsedTimeRef.current + elapsed / 1000;
setTimer(Math.floor(currentElapsed));
}
// Update formatted time display
updateFormattedTime(useMilliseconds ? currentElapsed : currentElapsed);
}, updateInterval);
} else if (isPaused && startTimeRef.current) {
const elapsed = Date.now() - startTimeRef.current;
if (useMilliseconds) {
elapsedTimeRef.current += elapsed;
} else {
elapsedTimeRef.current += elapsed / 1000;
}
startTimeRef.current = null;
clearTimerInterval();
}
return clearTimerInterval;
}, [
isActive,
isPaused,
clearTimerInterval,
updateFormattedTime,
useMilliseconds,
updateInterval,
useExternalTimeUpdates,
]);
/**
* Start the timer
*/
const handleStart = useCallback(() => {
setIsActive(true);
setIsPaused(false);
startTimeRef.current = Date.now();
elapsedTimeRef.current = 0;
setTimer(0);
updateFormattedTime(0);
}, [updateFormattedTime]);
/**
* Pause the timer
*/
const handlePause = useCallback(() => {
setIsPaused(true);
}, []);
/**
* Resume the timer
*/
const handleResume = useCallback(() => {
setIsPaused(false);
startTimeRef.current = Date.now();
}, []);
/**
* Reset the timer
*/
const handleReset = useCallback(() => {
setIsActive(false);
setIsPaused(false);
setTimer(0);
updateFormattedTime(0);
startTimeRef.current = null;
elapsedTimeRef.current = 0;
clearTimerInterval();
}, [clearTimerInterval, updateFormattedTime]);
/**
* Set the timer to a specific value
*/
const setTime = useCallback(
(newTime: number) => {
setTimer(newTime);
updateFormattedTime(newTime);
if (!isActive) {
elapsedTimeRef.current = useMilliseconds ? newTime : newTime * 1000;
}
},
[isActive, updateFormattedTime, useMilliseconds]
);
/**
* Update the timer with an external time value (for use with audio players)
* This is useful when the time is controlled by an external source
*/
const updateTime = useCallback(
(newTime: number) => {
setTimer(newTime);
updateFormattedTime(newTime);
},
[updateFormattedTime]
);
return {
timer,
formattedTime,
isActive,
isPaused,
handleStart,
handlePause,
handleResume,
handleReset,
setTime,
updateTime,
// Aliases for better compatibility with existing code
duration: timer,
start: handleStart,
pause: handlePause,
resume: handleResume,
reset: handleReset,
};
};
export default useTimer;