loop / src / contexts / TimerContext.jsx
339 words
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { createContext, useContext, useState, useCallback } from 'react';
import { useAudioEngine } from '../hooks/useAudioEngine';
import { useTimer, formatTime } from '../hooks/useTimer';
import { DEFAULT_SOUND } from '../components/SoundPicker';

const TimerContext = createContext(null);

export function TimerProvider({ children }) {
  const [selectedSound, setSelectedSound] = useState(DEFAULT_SOUND);
  const [showSoundPicker, setShowSoundPicker] = useState(false);
  const [customTrack, setCustomTrack] = useState(null);
  const [isMuted, setIsMuted] = useState(false);

  const timer = useTimer();
  const audio = useAudioEngine();

  const handlePlayPause = useCallback((durationSeconds = null) => {
    if (timer.timerState === 'idle') {
      timer.start(durationSeconds, async () => {
        // countdown completed — stop audio
        await audio.stop();
        setIsMuted(false);
      });
      audio.play(selectedSound);
      setIsMuted(false);
    } else if (timer.timerState === 'running') {
      timer.pause();
      audio.pause();
      setIsMuted(false);
    } else if (timer.timerState === 'paused') {
      timer.resume();
      audio.resume();
      setIsMuted(false);
    }
  }, [timer, audio, selectedSound]);

  const handleStop = useCallback(async () => {
    timer.stop();
    await audio.stop();
    setIsMuted(false);
  }, [timer, audio]);

  const handleSelectSound = useCallback((sound) => {
    setSelectedSound(sound);
    if (timer.timerState === 'running') {
      audio.play(sound);
    }
    setShowSoundPicker(false);
  }, [timer.timerState, audio]);

  const handleImportTrack = useCallback((file) => {
    const track = { name: file.name, file, loop: true };
    setCustomTrack(track);
    const sound = { type: 'custom', label: file.name, file, loop: true };
    setSelectedSound(sound);
    if (timer.timerState === 'running') {
      audio.play(sound);
    }
  }, [timer.timerState, audio]);

  const handleToggleLoop = useCallback(() => {
    if (!customTrack) return;
    const newLoop = !customTrack.loop;
    setCustomTrack({ ...customTrack, loop: newLoop });
    if (selectedSound.type === 'custom') {
      const updatedSound = { ...selectedSound, loop: newLoop };
      setSelectedSound(updatedSound);
      if (timer.timerState === 'running') {
        audio.play(updatedSound);
      }
    }
  }, [customTrack, selectedSound, timer.timerState, audio]);

  const handleToggleMute = useCallback(() => {
    if (timer.timerState !== 'running') return;
    if (isMuted) {
      audio.resume();
      setIsMuted(false);
    } else {
      audio.pause();
      setIsMuted(true);
    }
  }, [timer.timerState, audio, isMuted]);

  const toggleSoundPicker = useCallback(() => {
    setShowSoundPicker((v) => !v);
  }, []);

  const value = {
    timerState: timer.timerState,
    elapsedSeconds: timer.elapsedSeconds,
    displaySeconds: timer.displaySeconds,
    duration: timer.duration,
    selectedSound,
    showSoundPicker,
    customTrack,
    isMuted,
    formatTime,
    onPlayPause: handlePlayPause,
    onStop: handleStop,
    onSelectSound: handleSelectSound,
    onImportTrack: handleImportTrack,
    onToggleLoop: handleToggleLoop,
    onToggleMute: handleToggleMute,
    onToggleSoundPicker: toggleSoundPicker,
  };

  return (
    <TimerContext.Provider value={value}>
      {children}
    </TimerContext.Provider>
  );
}

export function useTimerContext() {
  const context = useContext(TimerContext);
  if (!context) {
    throw new Error('useTimerContext must be used within a TimerProvider');
  }
  return context;
}

static mirror of HEAD · about · clone: git clone https://git.makefuture.ro/loop.git