Skip to content

Commit

Permalink
feat: 뽀모도로 껏켰 대응을 위한 리팩토링 (#50) #minor
Browse files Browse the repository at this point in the history
* 뽀모도로 로직을 담은 hook 구현

* 아무것도 없을땐 setInterval 실행하지 않도록

* useInterval로 훅 단순화

* 초과했을때 hook 추가

* 끝나는거 호출할때 이유를 알려주도록

* 목표 시간 초과할때 콜백 추가

* 콜백 인자관련 설명 추가

* 시간 관련 유틸 한파일로 모으기

* 뽀모도로 로직 변경

- 새로고침에도 문제 없도록
- 껏켯에도 그대로 남아있도록
- 시간 초과 했을떄 알림이 가도록

* state로 관리하는 데이터를 카테고리 전체로 변경

* 초기화 빼먹은 곳 적용

* usePomodoro 내부 코드 정리

* 카운트다운 형태로 변경
  • Loading branch information
young-do authored Sep 6, 2024
1 parent 5406cb3 commit 142a1b6
Show file tree
Hide file tree
Showing 15 changed files with 459 additions and 249 deletions.
15 changes: 15 additions & 0 deletions src/renderer/entities/pomodoro/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,18 @@ export type Pomodoro = {
export type PomodoroMode = 'focus' | 'rest-wait' | 'rest';

export type PomodoroNextAction = 'plus' | 'minus';

export type PomodoroEndReason = 'manual' | 'exceed';

export type PomodoroCycle = {
startAt: number;
endAt?: number;
goalTime: number;
exceedMaxTime: number;
mode: PomodoroMode;
};

export type PomodoroTime = {
elapsed: number;
exceeded: number;
};
181 changes: 181 additions & 0 deletions src/renderer/features/pomodoro/hooks/use-pomodoro.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { useLocalStorage } from 'usehooks-ts';

import { PomodoroCycle, PomodoroEndReason, PomodoroMode, PomodoroTime } from '@/entities/pomodoro';
import { LOCAL_STORAGE_KEY } from '@/shared/constants';
import { useInterval } from '@/shared/hooks';

// == usePomodoro 로직에 대한 description

// 집중 시작 - 시작 시각: Date.now(), 목표시간: 25 * 60 * 1000(25분, 형식 미정), 모드: focus, 끝난시간: Null로 로컬에 저장

// 집중 중 - 저장한 값과 현재 시각으로 몇분 지났는지 & 초과/미만인지 표시
// ㄴ 이 때 초과시간이 특정 시간을 넘어가면(ex, 정해논 시간보다 초과시간이 1시간이 넘어가면) 자동 휴식으로 넘어가기

// 휴식 대기 - 집중 끝난시간 값 업데이트. 시작시간: Date.now(), 목표시간: 0, 모드: rest-wait, 끝난시간: null로 저장.

// 휴식 대기 중 - 저장한 값과 현재 시각으로 몇분 지났는지 확인
// ㄴ 이 때도 초과시간이 특정 시간을 넘어가면 자동 종료(?)하기

// 휴식 시작 - 휴식 대기 끝난시간 없데이트. 휴식 시간, 목표시간, 모드, 끝난시간은 null로 저장.

// 휴식 중 - 저장한 값과 현재 시각으로 몇분 지났는지 & 초과/미만인지 표시
// ㄴ 이 때도 초과시간이 특정 시간을 넘어가면 자동 종료하기

// 집중 전환 - 휴식 끝난시간 값 업데이트 & … 계속 반복

// 뽀모도로 종료 - 저장된 값 list를 서버에 전달(물론 전달하기 전 변환 필수). 전달 성공시 저장된 값 초기화

// 강제 종료 - 뽀모도로 종료 로직 실행(단, 마지막 끝난시간은 강제 종료시점으로 해서)

export type UsePomodoroParams = {
/** 집중시간. (단위는 ms) */
focusTime: number;
/** 집중 초과 최대시간. (단위는 ms) */
focusExceedMaxTime: number;
/** 휴식대기 초과 최대시간. (단위는 ms) */
restWaitExceedMaxTime: number;
/** 휴식시간. (단위는 ms) */
restTime: number;
/** 휴식 초과 최대시간. (단위는 ms) */
restExceedMaxTime: number;
/** 뽀모도로 종료시 실행되는 콜백 */
onEndPomodoro: (cycles: PomodoroCycle[], reason: PomodoroEndReason) => void;
/** 목표시간 초과시 한번만 실행되는 콜백 */
onceExceedGoalTime?: (mode: PomodoroMode) => void;
};

const isNotNil = <T>(value: T): value is NonNullable<T> => value !== null && value !== undefined;

const updateCycles = (cycles: PomodoroCycle[], nextCycle?: PomodoroCycle): PomodoroCycle[] => {
const prevCycles = cycles.slice(0, -1);
const lastCycle = cycles[cycles.length - 1] as PomodoroCycle | undefined;

if (lastCycle?.mode === nextCycle?.mode) {
throw new Error('Invalid mode cycle');
}

return [
...prevCycles,
lastCycle && {
...lastCycle,
endAt: Date.now(),
},
nextCycle,
].filter(isNotNil);
};

export const getPomodoroTime = (cycle: PomodoroCycle): PomodoroTime => {
const now = cycle.endAt ?? Date.now();
const elapsed = now - cycle.startAt;
const exceeded = elapsed - cycle.goalTime;

return { elapsed, exceeded };
};

const defaultPomodoroTime: PomodoroTime = { elapsed: 0, exceeded: 0 };

export const usePomodoro = ({
focusTime,
focusExceedMaxTime,
restWaitExceedMaxTime,
restTime,
restExceedMaxTime,
onEndPomodoro,
onceExceedGoalTime,
}: UsePomodoroParams) => {
const [pomodoroCycles, setPomodoroCycles] = useLocalStorage<PomodoroCycle[]>(
LOCAL_STORAGE_KEY.POMODORO_CYCLES,
[],
);
const [pomodoroTime, setPomodoroTime] = useLocalStorage<PomodoroTime>(
LOCAL_STORAGE_KEY.POMODORO_TIME,
defaultPomodoroTime,
);
const [calledOnceForExceedGoalTime, setCalledOnceForExceedGoalTime] = useLocalStorage(
LOCAL_STORAGE_KEY.POMODORO_CALLED_ONCE_FOR_EXCEED_TIME,
false,
);

const startFocus = () => {
const nextCycles = updateCycles(pomodoroCycles, {
startAt: Date.now(),
goalTime: focusTime,
exceedMaxTime: focusExceedMaxTime,
mode: 'focus',
});
setPomodoroCycles(nextCycles);
setPomodoroTime(defaultPomodoroTime);
setCalledOnceForExceedGoalTime(false);
};

const startRestWait = () => {
const nextCycles = updateCycles(pomodoroCycles, {
startAt: Date.now(),
goalTime: 0,
exceedMaxTime: restWaitExceedMaxTime,
mode: 'rest-wait',
});
setPomodoroCycles(nextCycles);
setPomodoroTime(defaultPomodoroTime);
setCalledOnceForExceedGoalTime(false);
};

const startRest = () => {
const nextCycles = updateCycles(pomodoroCycles, {
startAt: Date.now(),
goalTime: restTime,
exceedMaxTime: restExceedMaxTime,
mode: 'rest',
});
setPomodoroCycles(nextCycles);
setPomodoroTime(defaultPomodoroTime);
setCalledOnceForExceedGoalTime(false);
};

const endPomodoro = (reason: PomodoroEndReason = 'manual') => {
const endedCycles = updateCycles(pomodoroCycles);
onEndPomodoro(endedCycles, reason);

// 상위로 전달했으니 cycle 데이터 초기화
setPomodoroCycles([]);
setPomodoroTime(defaultPomodoroTime);
setCalledOnceForExceedGoalTime(false);
};

useInterval(
() => {
const currentCycle = pomodoroCycles[pomodoroCycles.length - 1];
if (!currentCycle) return;

const { elapsed, exceeded } = getPomodoroTime(currentCycle);
setPomodoroTime({ elapsed, exceeded });

if (exceeded > 0 && !calledOnceForExceedGoalTime) {
onceExceedGoalTime?.(currentCycle.mode);
setCalledOnceForExceedGoalTime(true);
}

if (exceeded >= currentCycle.exceedMaxTime) {
if (currentCycle.mode === 'focus') {
startRestWait();
}
if (currentCycle.mode === 'rest-wait') {
endPomodoro('exceed');
}
if (currentCycle.mode === 'rest') {
endPomodoro('exceed');
}
}
},
pomodoroCycles.length > 0 ? 250 : null,
);

return {
pomodoroCycles,
pomodoroTime,
startFocus,
startRestWait,
startRest,
endPomodoro,
};
};
Loading

0 comments on commit 142a1b6

Please sign in to comment.